From d0caa2540e383f835aa650cc3a233a9a7a9ce2d4 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 9 Feb 2021 13:00:55 -0500 Subject: [PATCH 01/22] Initial commit --- Cargo.toml | 9 +++++++++ src/main.rs | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..66b986cf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ldk-tutorial-node" +version = "0.1.0" +authors = ["Valentine Wallace "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From a6962463ad036f472c03a67975fd94ba4350f63b Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 23 Feb 2021 19:29:56 -0500 Subject: [PATCH 02/22] Fee estimator WIP --- Cargo.lock | 998 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 14 + src/main.rs | 67 +++- 3 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..f167acc1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,998 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "background-processor" +version = "0.1.0" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d#d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" +dependencies = [ + "bitcoin 0.24.0", + "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d)", + "lightning-persister 0.0.1 (git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d)", +] + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bech32" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" + +[[package]] +name = "bitcoin" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6558aeb12c290cce541a222cba280387161f2bd180a7feb85f8f172cd8ba80e8" +dependencies = [ + "bech32", + "bitcoin_hashes 0.8.0", + "secp256k1 0.18.0", +] + +[[package]] +name = "bitcoin" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec5f88a446d66e7474a3b8fa2e348320b574463fb78d799d90ba68f79f48e0e" +dependencies = [ + "bech32", + "bitcoin_hashes 0.9.4", + "secp256k1 0.20.1", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0ab784be052cc1e915a78b8aaf5101eebbc2d0ab2b6f5124985f3677ae2bea" + +[[package]] +name = "bitcoin_hashes" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aaf87b776808e26ae93289bc7d025092b6d909c193f0cdee0b3a86e7bd3c776" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bumpalo" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "cc" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "const_fn" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" + +[[package]] +name = "futures-executor" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" + +[[package]] +name = "futures-macro" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" + +[[package]] +name = "futures-task" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite 0.2.4", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "ldk-tutorial-node" +version = "0.1.0" +dependencies = [ + "background-processor", + "base64", + "bitcoin 0.26.0", + "hex", + "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=c35002fa9c16042badfa5e7bf819df5f1d2ae60a)", + "lightning-block-sync", + "lightning-invoice", + "lightning-net-tokio", + "lightning-persister 0.0.1 (git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8)", + "rand", + "serde_json", + "time", + "tokio 0.2.25", +] + +[[package]] +name = "libc" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" + +[[package]] +name = "lightning" +version = "0.0.12" +dependencies = [ + "bitcoin 0.24.0", +] + +[[package]] +name = "lightning" +version = "0.0.12" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8#aa127f55edc4439b03426644d178e402397329e8" +dependencies = [ + "bitcoin 0.24.0", +] + +[[package]] +name = "lightning" +version = "0.0.12" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=c35002fa9c16042badfa5e7bf819df5f1d2ae60a#c35002fa9c16042badfa5e7bf819df5f1d2ae60a" +dependencies = [ + "bitcoin 0.24.0", +] + +[[package]] +name = "lightning" +version = "0.0.12" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d#d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" +dependencies = [ + "bitcoin 0.24.0", +] + +[[package]] +name = "lightning" +version = "0.0.12" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=ff00f6f8861419b73269e6c51d75ac9de75f1d1f#ff00f6f8861419b73269e6c51d75ac9de75f1d1f" +dependencies = [ + "bitcoin 0.24.0", +] + +[[package]] +name = "lightning-block-sync" +version = "0.0.1" +dependencies = [ + "bitcoin 0.24.0", + "chunked_transfer", + "futures", + "lightning 0.0.12", + "serde", + "serde_json", + "tokio 1.2.0", +] + +[[package]] +name = "lightning-invoice" +version = "0.4.0" +source = "git+https://github.com/rust-bitcoin/rust-lightning-invoice?rev=ea25dc7e46a6339493032c500db4fe3a8fdb1acd#ea25dc7e46a6339493032c500db4fe3a8fdb1acd" +dependencies = [ + "bech32", + "bitcoin_hashes 0.9.4", + "num-traits", + "secp256k1 0.20.1", +] + +[[package]] +name = "lightning-net-tokio" +version = "0.0.5" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=ff00f6f8861419b73269e6c51d75ac9de75f1d1f#ff00f6f8861419b73269e6c51d75ac9de75f1d1f" +dependencies = [ + "bitcoin 0.24.0", + "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=ff00f6f8861419b73269e6c51d75ac9de75f1d1f)", + "tokio 1.2.0", +] + +[[package]] +name = "lightning-persister" +version = "0.0.1" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8#aa127f55edc4439b03426644d178e402397329e8" +dependencies = [ + "bitcoin 0.24.0", + "libc", + "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8)", + "winapi 0.3.9", +] + +[[package]] +name = "lightning-persister" +version = "0.0.1" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d#d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" +dependencies = [ + "bitcoin 0.24.0", + "libc", + "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d)", + "winapi 0.3.9", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" +dependencies = [ + "libc", + "log", + "miow 0.3.6", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-project-lite" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi 0.3.9", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "secp256k1" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3f534ef4e9dfa6c4b2ccca131f791daddf9cc2123b4124615cb144ea91611a" +dependencies = [ + "secp256k1-sys 0.2.0", +] + +[[package]] +name = "secp256k1" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733b114f058f260c0af7591434eef4272ae1a8ec2751766d3cb89c6df8d5e450" +dependencies = [ + "secp256k1-sys 0.4.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71453d9b088b19ae43a803d88ecaa876b11ed8999df024cca4becb6be9aee351" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e4b6455ee49f5901c8985b88f98fb0a0e1d90a6661f5a03f4888bd987dad29" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "standback" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "time" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "fnv", + "iovec", + "lazy_static", + "memchr", + "mio 0.6.23", + "num_cpus", + "pin-project-lite 0.1.11", + "slab", +] + +[[package]] +name = "tokio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a" +dependencies = [ + "autocfg", + "bytes 1.0.1", + "libc", + "memchr", + "mio 0.7.7", + "num_cpus", + "pin-project-lite 0.2.4", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasm-bindgen" +version = "0.2.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[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-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[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 = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml index 66b986cf..154874a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,17 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" } +base64 = "0.13.0" +bitcoin = "0.26" +hex = "0.3" +lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "c35002fa9c16042badfa5e7bf819df5f1d2ae60a" } +# lightning-block-sync = { git = "https://github.com/valentinewallace/rust-lightning", rev = "21deebf64b4e6ba3f104dc6c68efa5d3abc4c0f7", features = ["tokio", "rpc-client"] } +lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client", "expose-rpc" ] } +lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "ea25dc7e46a6339493032c500db4fe3a8fdb1acd" } +lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "ff00f6f8861419b73269e6c51d75ac9de75f1d1f" } +lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "aa127f55edc4439b03426644d178e402397329e8" } +time = "0.2" +rand = "0.4" +serde_json = { version = "1.0" } +tokio = { version = "0.2", features = ["io-std", "io-util", "rt-threaded", "tcp", "time", "sync"] } diff --git a/src/main.rs b/src/main.rs index e7a11a96..d8b0f23b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,68 @@ +use base64; +use serde_json; + +use lightning::chain::chaininterface::{ConfirmationTarget, FeeEstimator}; +use lightning::util::logger::{Logger, Record}; +use lightning_block_sync::http::HttpEndpoint; +use lightning_block_sync::rpc::RpcClient; + +use std::sync::Mutex; + +pub struct BitcoindFeeEstimator { + bitcoind_rpc_client: Mutex, +} + +impl BitcoindFeeEstimator { + fn new(host: String, port: u16, path: Option, rpc_user: String, rpc_password: String) -> + std::io::Result + { + let mut http_endpoint = HttpEndpoint::for_host(host).with_port(port); + if let Some(p) = path { + http_endpoint = http_endpoint.with_path(p); + } + let rpc_credentials = base64::encode(format!("{}:{}", rpc_user, rpc_password)); + let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; + Ok(Self { + bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client) + }) + } +} + +impl FeeEstimator for BitcoindFeeEstimator { + fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { + let mut rpc_client_guard = self.bitcoind_rpc_client.lock().unwrap(); + match confirmation_target { + ConfirmationTarget::Background => { + let conf_target = serde_json::json!(144); + let estimate_mode = serde_json::json!("ECONOMICAL"); + let resp = rpc_client_guard.call_method("estimatesmartfee", + &vec![conf_target, estimate_mode]).unwrap(); + resp["feerate"].as_u64().unwrap() as u32 + }, + ConfirmationTarget::Normal => { + let conf_target = serde_json::json!(18); + let estimate_mode = serde_json::json!("ECONOMICAL"); + let resp = rpc_client_guard.call_method("estimatesmartfee", + &vec![conf_target, estimate_mode]).unwrap(); + resp["feerate"].as_u64().unwrap() as u32 + }, + ConfirmationTarget::HighPriority => { + let conf_target = serde_json::json!(6); + let estimate_mode = serde_json::json!("CONSERVATIVE"); + let resp = rpc_client_guard.call_method("estimatesmartfee", + &vec![conf_target, estimate_mode]).unwrap(); + resp["feerate"].as_u64().unwrap() as u32 + }, + } + } +} + fn main() { - println!("Hello, world!"); + let bitcoind_host = "127.0.0.1".to_string(); + let bitcoind_port = 18443; + let rpc_user = "polaruser".to_string(); + let rpc_password = "polarpass".to_string(); + let fee_estimator = BitcoindFeeEstimator::new(bitcoind_host, bitcoind_port, None, rpc_user, rpc_password).unwrap(); + let normal_fee = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + println!("VMW: {}", normal_fee); } From 1b034d63a4cb4e0b249ae6240166513b6be76737 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 2 Mar 2021 15:54:17 -0500 Subject: [PATCH 03/22] Initial node --- Cargo.lock | 275 ++++--------------------- Cargo.toml | 23 ++- src/bitcoind_client.rs | 76 +++++++ src/cli.rs | 100 +++++++++ src/main.rs | 448 ++++++++++++++++++++++++++++++++++++----- src/utils.rs | 42 ++++ 6 files changed, 679 insertions(+), 285 deletions(-) create mode 100644 src/bitcoind_client.rs create mode 100644 src/cli.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index f167acc1..cdd93357 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,11 +9,10 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "background-processor" version = "0.1.0" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d#d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" dependencies = [ - "bitcoin 0.24.0", - "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d)", - "lightning-persister 0.0.1 (git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d)", + "bitcoin", + "lightning", + "lightning-persister", ] [[package]] @@ -28,6 +27,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bech32" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4408a9bf5c378a42ca9039e4ef3e3d8df3443f76d2ebe249fd720a2c5e17d2da" + [[package]] name = "bech32" version = "0.7.2" @@ -40,20 +45,18 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6558aeb12c290cce541a222cba280387161f2bd180a7feb85f8f172cd8ba80e8" dependencies = [ - "bech32", + "bech32 0.7.2", "bitcoin_hashes 0.8.0", "secp256k1 0.18.0", ] [[package]] -name = "bitcoin" -version = "0.26.0" +name = "bitcoin-bech32" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec5f88a446d66e7474a3b8fa2e348320b574463fb78d799d90ba68f79f48e0e" +checksum = "b5791779c83cf6bde070a92c22505f2faa06487fc8f372e4d485c41a71351105" dependencies = [ - "bech32", - "bitcoin_hashes 0.9.4", - "secp256k1 0.20.1", + "bech32 0.4.1", ] [[package]] @@ -68,24 +71,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0aaf87b776808e26ae93289bc7d025092b6d909c193f0cdee0b3a86e7bd3c776" -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - [[package]] name = "bumpalo" version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.0.1" @@ -98,12 +89,6 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -128,34 +113,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "futures" version = "0.3.12" @@ -244,7 +207,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.4", + "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -266,31 +229,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -303,17 +247,18 @@ version = "0.1.0" dependencies = [ "background-processor", "base64", - "bitcoin 0.26.0", + "bitcoin", + "bitcoin-bech32", "hex", - "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=c35002fa9c16042badfa5e7bf819df5f1d2ae60a)", + "lightning", "lightning-block-sync", "lightning-invoice", "lightning-net-tokio", - "lightning-persister 0.0.1 (git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8)", + "lightning-persister", "rand", "serde_json", "time", - "tokio 0.2.25", + "tokio", ] [[package]] @@ -326,52 +271,20 @@ checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" name = "lightning" version = "0.0.12" dependencies = [ - "bitcoin 0.24.0", -] - -[[package]] -name = "lightning" -version = "0.0.12" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8#aa127f55edc4439b03426644d178e402397329e8" -dependencies = [ - "bitcoin 0.24.0", -] - -[[package]] -name = "lightning" -version = "0.0.12" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=c35002fa9c16042badfa5e7bf819df5f1d2ae60a#c35002fa9c16042badfa5e7bf819df5f1d2ae60a" -dependencies = [ - "bitcoin 0.24.0", -] - -[[package]] -name = "lightning" -version = "0.0.12" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d#d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" -dependencies = [ - "bitcoin 0.24.0", -] - -[[package]] -name = "lightning" -version = "0.0.12" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=ff00f6f8861419b73269e6c51d75ac9de75f1d1f#ff00f6f8861419b73269e6c51d75ac9de75f1d1f" -dependencies = [ - "bitcoin 0.24.0", + "bitcoin", ] [[package]] name = "lightning-block-sync" version = "0.0.1" dependencies = [ - "bitcoin 0.24.0", + "bitcoin", "chunked_transfer", "futures", - "lightning 0.0.12", + "lightning", "serde", "serde_json", - "tokio 1.2.0", + "tokio", ] [[package]] @@ -379,7 +292,7 @@ name = "lightning-invoice" version = "0.4.0" source = "git+https://github.com/rust-bitcoin/rust-lightning-invoice?rev=ea25dc7e46a6339493032c500db4fe3a8fdb1acd#ea25dc7e46a6339493032c500db4fe3a8fdb1acd" dependencies = [ - "bech32", + "bech32 0.7.2", "bitcoin_hashes 0.9.4", "num-traits", "secp256k1 0.20.1", @@ -388,33 +301,20 @@ dependencies = [ [[package]] name = "lightning-net-tokio" version = "0.0.5" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=ff00f6f8861419b73269e6c51d75ac9de75f1d1f#ff00f6f8861419b73269e6c51d75ac9de75f1d1f" -dependencies = [ - "bitcoin 0.24.0", - "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=ff00f6f8861419b73269e6c51d75ac9de75f1d1f)", - "tokio 1.2.0", -] - -[[package]] -name = "lightning-persister" -version = "0.0.1" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8#aa127f55edc4439b03426644d178e402397329e8" dependencies = [ - "bitcoin 0.24.0", - "libc", - "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=aa127f55edc4439b03426644d178e402397329e8)", - "winapi 0.3.9", + "bitcoin", + "lightning", + "tokio", ] [[package]] name = "lightning-persister" version = "0.0.1" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d#d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" dependencies = [ - "bitcoin 0.24.0", + "bitcoin", "libc", - "lightning 0.0.12 (git+https://github.com/rust-bitcoin/rust-lightning?rev=d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d)", - "winapi 0.3.9", + "lightning", + "winapi", ] [[package]] @@ -423,7 +323,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -432,25 +332,6 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.7.7" @@ -459,21 +340,9 @@ checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" dependencies = [ "libc", "log", - "miow 0.3.6", + "miow", "ntapi", - "winapi 0.3.9", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "winapi", ] [[package]] @@ -483,18 +352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ "socket2", - "winapi 0.3.9", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -503,7 +361,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -531,12 +389,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" -[[package]] -name = "pin-project-lite" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" - [[package]] name = "pin-project-lite" version = "0.2.4" @@ -589,7 +441,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -731,9 +583,9 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -817,7 +669,7 @@ dependencies = [ "stdweb", "time-macros", "version_check", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -843,23 +695,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" -dependencies = [ - "bytes 0.5.6", - "fnv", - "iovec", - "lazy_static", - "memchr", - "mio 0.6.23", - "num_cpus", - "pin-project-lite 0.1.11", - "slab", -] - [[package]] name = "tokio" version = "1.2.0" @@ -867,12 +702,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a" dependencies = [ "autocfg", - "bytes 1.0.1", + "bytes", "libc", "memchr", - "mio 0.7.7", + "mio", "num_cpus", - "pin-project-lite 0.2.4", + "pin-project-lite", "tokio-macros", ] @@ -905,7 +740,7 @@ version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -953,12 +788,6 @@ version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -969,12 +798,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -986,13 +809,3 @@ 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 = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] diff --git a/Cargo.toml b/Cargo.toml index 154874a4..b09f09cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,24 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" } +# background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" } +background-processor = { path = "../rust-lightning/background-processor" } base64 = "0.13.0" -bitcoin = "0.26" +bitcoin = "0.24" +bitcoin-bech32 = "0.7" hex = "0.3" -lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "c35002fa9c16042badfa5e7bf819df5f1d2ae60a" } -# lightning-block-sync = { git = "https://github.com/valentinewallace/rust-lightning", rev = "21deebf64b4e6ba3f104dc6c68efa5d3abc4c0f7", features = ["tokio", "rpc-client"] } -lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client", "expose-rpc" ] } +# lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "c35002fa9c16042badfa5e7bf819df5f1d2ae60a" } +lightning = { path = "../rust-lightning/lightning" } +lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client", "generic-rpc-client" ] } lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "ea25dc7e46a6339493032c500db4fe3a8fdb1acd" } -lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "ff00f6f8861419b73269e6c51d75ac9de75f1d1f" } -lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "aa127f55edc4439b03426644d178e402397329e8" } +# lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "ff00f6f8861419b73269e6c51d75ac9de75f1d1f" } +lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } +# lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "aa127f55edc4439b03426644d178e402397329e8" } +lightning-persister = { path = "../rust-lightning/lightning-persister" } time = "0.2" rand = "0.4" serde_json = { version = "1.0" } -tokio = { version = "0.2", features = ["io-std", "io-util", "rt-threaded", "tcp", "time", "sync"] } +# tokio = { version = "0.2", features = ["io-std", "io-util", "rt-threaded", "tcp", "time", "sync"] } +# tokio = { version = "1.0", features = [ "full", "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } +# tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } +tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs new file mode 100644 index 00000000..97e5556e --- /dev/null +++ b/src/bitcoind_client.rs @@ -0,0 +1,76 @@ +use base64; +use serde_json; + +use bitcoin::blockdata::transaction::Transaction; +use bitcoin::consensus::encode; +use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use lightning_block_sync::http::HttpEndpoint; +use lightning_block_sync::rpc::RpcClient; + +use std::sync::Mutex; + +pub struct BitcoindClient { + pub bitcoind_rpc_client: Mutex, +} + +impl BitcoindClient { + pub fn new(host: String, port: u16, path: Option, rpc_user: String, rpc_password: String) -> + std::io::Result + { + let mut http_endpoint = HttpEndpoint::for_host(host).with_port(port); + if let Some(p) = path { + http_endpoint = http_endpoint.with_path(p); + } + let rpc_credentials = base64::encode(format!("{}:{}", rpc_user, rpc_password)); + let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; + Ok(Self { + bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client) + }) + } +} + +impl FeeEstimator for BitcoindClient { + fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { + let mut rpc_client_guard = self.bitcoind_rpc_client.lock().unwrap(); + match confirmation_target { + ConfirmationTarget::Background => { + let conf_target = serde_json::json!(144); + let estimate_mode = serde_json::json!("ECONOMICAL"); + let resp = rpc_client_guard.call_method("estimatesmartfee", + &vec![conf_target, estimate_mode]).unwrap(); + if !resp["errors"].is_null() && resp["errors"].as_array().unwrap().len() > 0 { + return 253 + } + resp["feerate"].as_u64().unwrap() as u32 + }, + ConfirmationTarget::Normal => { + let conf_target = serde_json::json!(18); + let estimate_mode = serde_json::json!("ECONOMICAL"); + let resp = rpc_client_guard.call_method("estimatesmartfee", + &vec![conf_target, estimate_mode]).unwrap(); + if !resp["errors"].is_null() && resp["errors"].as_array().unwrap().len() > 0 { + return 253 + } + resp["feerate"].as_u64().unwrap() as u32 + }, + ConfirmationTarget::HighPriority => { + let conf_target = serde_json::json!(6); + let estimate_mode = serde_json::json!("CONSERVATIVE"); + let resp = rpc_client_guard.call_method("estimatesmartfee", + &vec![conf_target, estimate_mode]).unwrap(); + if !resp["errors"].is_null() && resp["errors"].as_array().unwrap().len() > 0 { + return 253 + } + resp["feerate"].as_u64().unwrap() as u32 + }, + } + } +} + +impl BroadcasterInterface for BitcoindClient { + fn broadcast_transaction(&self, tx: &Transaction) { + let mut rpc_client_guard = self.bitcoind_rpc_client.lock().unwrap(); + let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); + rpc_client_guard.call_method("sendrawtransaction", &vec![tx_serialized]).unwrap(); + } +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..c046d0cf --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,100 @@ +use bitcoin::secp256k1::key::PublicKey; +use crate::{FilesystemLogger, PeerManager, ChannelManager}; +use crate::utils; +use std::io; +use std::io::{BufRead, Write}; +use std::net::{SocketAddr, TcpStream}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; +// use tokio::runtime::Runtime; +use tokio::runtime::{Builder, Runtime}; +use tokio::sync::mpsc; + +pub fn poll_for_user_input(peer_manager: Arc, channel_manager: Arc, event_notifier: mpsc::Sender<()>) { + println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available in the `logs` folder of the current directory."); + print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print + let stdin = io::stdin(); + for line in stdin.lock().lines() { + let line = line.unwrap(); + let mut words = line.split_whitespace(); + if let Some(word) = words.next() { + match word { + "help" => handle_help(), + "openchannel" => { + let peer_pubkey_and_ip_addr = words.next(); + let channel_value_sat = words.next(); + if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { + println!("ERROR: openchannel takes 2 arguments: `openchannel pubkey@host:port channel_amt_satoshis`") + } else { + let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); + let channel_value_sat = channel_value_sat.unwrap(); + let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@"); + let pubkey = pubkey_and_addr.next(); + let peer_addr_str = pubkey_and_addr.next(); + if peer_addr_str.is_none() || peer_addr_str.is_none() { + println!("ERROR: incorrectly formatted peer info. Should be formatted as: `pubkey@host:port`"); + } else { + let pubkey = pubkey.unwrap(); + let peer_addr_str = peer_addr_str.unwrap(); + let peer_addr_res: Result = peer_addr_str.parse(); + let chan_amt: Result = channel_value_sat.parse(); + if let Some(pk) = utils::hex_to_compressed_pubkey(pubkey) { + if let Ok(addr) = peer_addr_res { + if let Ok(amt) = chan_amt { + handle_open_channel(pk, addr, amt, + peer_manager.clone(), + channel_manager.clone(), + event_notifier.clone()); + } else { + println!("ERROR: channel amount must be a number"); + } + } else { + println!("ERROR: couldn't parse + pubkey@host:port into a socket address"); + } + } else { + println!("ERROR: unable to parse given pubkey for node"); + } + } + } + }, + _ => println!("hello") + } + } + print!("> "); io::stdout().flush().unwrap(); + + } +} + +fn handle_help() { + println!("") +} + +fn handle_open_channel(peer_pubkey: PublicKey, peer_socket: SocketAddr, + channel_amt_sat: u64, peer_manager: Arc, + channel_manager: Arc, event_notifier: + mpsc::Sender<()>) +{ + // let runtime = Runtime::new().expect("Unable to create a runtime").enable_all(); + let runtime = Builder::new_multi_thread() + .worker_threads(3) + .enable_all() + .build() + .unwrap(); + match TcpStream::connect_timeout(&peer_socket, Duration::from_secs(10)) { + Ok(stream) => { + runtime.block_on(|| { + lightning_net_tokio::setup_outbound(peer_manager, + event_notifier, + peer_pubkey, + stream); + }); + match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) { + Ok(_) => println!("SUCCESS: channel created! Mine some blocks to open it."), + Err(e) => println!("ERROR: failed to open channel: {:?}", e) + } + }, + Err(e) => println!("ERROR: failed to connect to peer: {:?}", e) + } +} diff --git a/src/main.rs b/src/main.rs index d8b0f23b..f740cd1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,60 +1,232 @@ -use base64; -use serde_json; +mod bitcoind_client; +mod cli; +mod utils; -use lightning::chain::chaininterface::{ConfirmationTarget, FeeEstimator}; +use background_processor::BackgroundProcessor; +use bitcoin::{BlockHash, Txid}; +use bitcoin::blockdata::constants::genesis_block; +use bitcoin::blockdata::transaction::Transaction; +use bitcoin::consensus::encode; +use bitcoin::hashes::hex::FromHex; +use bitcoin::network::constants::Network; +use bitcoin::secp256k1::Secp256k1; +use bitcoin::util::address::Address; +use bitcoin_bech32::WitnessProgram; +use crate::bitcoind_client::BitcoindClient; +use lightning::chain; +use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use lightning::chain::chainmonitor::ChainMonitor; +use lightning::chain::channelmonitor::ChannelMonitor; +use lightning::chain::Filter; +use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager}; +use lightning::chain::transaction::OutPoint; +use lightning::chain::Watch; +use lightning::ln::channelmanager; +use lightning::ln::channelmanager::{ChannelManagerReadArgs, PaymentHash, PaymentPreimage, + SimpleArcChannelManager}; +use lightning::ln::peer_handler::{MessageHandler, SimpleArcPeerManager}; +use lightning::util::config::UserConfig; +use lightning::util::events::{Event, EventsProvider}; use lightning::util::logger::{Logger, Record}; +use lightning::util::ser::{ReadableArgs, Writer}; +use lightning_block_sync::UnboundedCache; +use lightning_block_sync::SpvClient; use lightning_block_sync::http::HttpEndpoint; +use lightning_block_sync::init; +use lightning_block_sync::poll; +use lightning_block_sync::poll::{ChainTip, Poll}; use lightning_block_sync::rpc::RpcClient; +use lightning_net_tokio::SocketDescriptor; +use lightning_persister::FilesystemPersister; +use rand::{thread_rng, Rng}; +use lightning::routing::network_graph::NetGraphMsgHandler; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fs; +use std::fs::File; +use std::io::Cursor; +use std::path::Path; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, SystemTime}; +use time::OffsetDateTime; +use tokio::runtime::Runtime; +use tokio::sync::mpsc; -use std::sync::Mutex; +const NETWORK: Network = Network::Regtest; -pub struct BitcoindFeeEstimator { - bitcoind_rpc_client: Mutex, +pub struct FilesystemLogger{} +impl Logger for FilesystemLogger { + fn log(&self, record: &Record) { + let raw_log = record.args.to_string(); + let log = format!("{} {:<5} [{}:{}] {}", OffsetDateTime::now_utc().format("%F %T"), + record.level.to_string(), record.module_path, record.line, raw_log); + fs::create_dir_all("logs").unwrap(); + fs::OpenOptions::new().create(true).append(true).open("./logs/logs.txt").unwrap() + .write_all(log.as_bytes()).unwrap(); + } } -impl BitcoindFeeEstimator { - fn new(host: String, port: u16, path: Option, rpc_user: String, rpc_password: String) -> - std::io::Result - { - let mut http_endpoint = HttpEndpoint::for_host(host).with_port(port); - if let Some(p) = path { - http_endpoint = http_endpoint.with_path(p); +fn read_channelmonitors_from_disk(path: String, keys_manager: Arc) -> + Result)>, std::io::Error> +{ + if !Path::new(&path).exists() { + return Ok(HashMap::new()) + } + let mut outpoint_to_channelmonitor = HashMap::new(); + for file_option in fs::read_dir(path).unwrap() { + let file = file_option.unwrap(); + let owned_file_name = file.file_name(); + let filename = owned_file_name.to_str(); + if !filename.is_some() || !filename.unwrap().is_ascii() || filename.unwrap().len() < 65 { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid ChannelMonitor file name")); + } + + let txid = Txid::from_hex(filename.unwrap().split_at(64).0); + if txid.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx ID in filename")); } - let rpc_credentials = base64::encode(format!("{}:{}", rpc_user, rpc_password)); - let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; - Ok(Self { - bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client) - }) + + let index = filename.unwrap().split_at(65).1.split('.').next().unwrap().parse(); + if index.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx index in filename")); + } + + let contents = fs::read(&file.path())?; + + if let Ok((blockhash, channel_monitor)) = + <(BlockHash, ChannelMonitor)>::read(&mut Cursor::new(&contents), + &*keys_manager) + { + outpoint_to_channelmonitor.insert(OutPoint { txid: txid.unwrap(), index: index.unwrap() }, + (blockhash, channel_monitor)); + } else { + return Err(std::io::Error::new(std::io::ErrorKind::Other, + "Failed to deserialize ChannelMonitor")); + } } + Ok(outpoint_to_channelmonitor) +} + +type Invoice = String; + +enum HTLCDirection { + Inbound, + Outbound } -impl FeeEstimator for BitcoindFeeEstimator { - fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { - let mut rpc_client_guard = self.bitcoind_rpc_client.lock().unwrap(); - match confirmation_target { - ConfirmationTarget::Background => { - let conf_target = serde_json::json!(144); - let estimate_mode = serde_json::json!("ECONOMICAL"); - let resp = rpc_client_guard.call_method("estimatesmartfee", - &vec![conf_target, estimate_mode]).unwrap(); - resp["feerate"].as_u64().unwrap() as u32 - }, - ConfirmationTarget::Normal => { - let conf_target = serde_json::json!(18); - let estimate_mode = serde_json::json!("ECONOMICAL"); - let resp = rpc_client_guard.call_method("estimatesmartfee", - &vec![conf_target, estimate_mode]).unwrap(); - resp["feerate"].as_u64().unwrap() as u32 - }, - ConfirmationTarget::HighPriority => { - let conf_target = serde_json::json!(6); - let estimate_mode = serde_json::json!("CONSERVATIVE"); - let resp = rpc_client_guard.call_method("estimatesmartfee", - &vec![conf_target, estimate_mode]).unwrap(); - resp["feerate"].as_u64().unwrap() as u32 - }, +type PaymentInfoStorage = Arc, HTLCDirection)>>>; + +type ArcChainMonitor = ChainMonitor, Arc, +Arc, Arc, Arc>; + +pub(crate) type PeerManager = SimpleArcPeerManager; + +pub(crate) type ChannelManager = SimpleArcChannelManager; + + +fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc, + chain_monitor: Arc, bitcoind_rpc_client: Arc, + keys_manager: Arc, mut pending_txs: HashMap, + htlcs: PaymentInfoStorage) -> HashMap +{ + peer_manager.process_events(); + let mut check_for_more_events = true; + while check_for_more_events { + let loop_channel_manager = channel_manager.clone(); + check_for_more_events = false; + let mut events = channel_manager.get_and_clear_pending_events(); + events.append(&mut chain_monitor.get_and_clear_pending_events()); + let mut rpc = bitcoind_rpc_client.bitcoind_rpc_client.lock().unwrap(); + for event in events { + match event { + Event::FundingGenerationReady { temporary_channel_id, channel_value_satoshis, + output_script, .. } => { + let addr = WitnessProgram::from_scriptpubkey(&output_script[..], match NETWORK { + Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin, + Network::Testnet => bitcoin_bech32::constants::Network::Testnet, + Network::Regtest => bitcoin_bech32::constants::Network::Regtest, + } + ).expect("Lightning funding tx should always be to a SegWit output").to_address(); + let outputs = format!("{{\"{}\": {}}}", addr, channel_value_satoshis as f64 / 1_000_000_00.0).to_string(); + let tx_hex = rpc.call_method("createrawtransaction", &vec![serde_json::json!(outputs)]).unwrap(); + let raw_tx = format!("\"{}\"", tx_hex.as_str().unwrap()).to_string(); + let funded_tx = rpc.call_method("fundrawtransaction", &vec![serde_json::json!(raw_tx)]).unwrap(); + let change_output_position = funded_tx["changepos"].as_i64().unwrap(); + assert!(change_output_position == 0 || change_output_position == 1); + let funded_tx = format!("\"{}\"", funded_tx["hex"].as_str().unwrap()).to_string(); + let signed_tx = rpc.call_method("signrawtransactionwithwallet", + &vec![serde_json::json!(funded_tx)]).unwrap(); + assert_eq!(signed_tx["complete"].as_bool().unwrap(), true); + let final_tx: Transaction = encode::deserialize(&utils::hex_to_vec(&signed_tx["hex"].as_str().unwrap()).unwrap()).unwrap(); + let outpoint = OutPoint { + txid: final_tx.txid(), + index: if change_output_position == 0 { 1 } else { 0 } + }; + loop_channel_manager.funding_transaction_generated(&temporary_channel_id, outpoint); + pending_txs.insert(outpoint, final_tx); + check_for_more_events = true; + }, + Event::FundingBroadcastSafe { funding_txo, .. } => { + let funding_tx = pending_txs.remove(&funding_txo).unwrap(); + bitcoind_rpc_client.broadcast_transaction(&funding_tx); + }, + Event::PaymentReceived { payment_hash, payment_secret, amt: amt_msat } => { + let payment_info = htlcs.lock().unwrap(); + if let Some(htlc_info) = payment_info.get(&payment_hash) { + assert!(loop_channel_manager.claim_funds(htlc_info.1.unwrap().clone(), + &payment_secret, amt_msat)); + } else { + loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); + } + check_for_more_events = true; + }, + Event::PaymentSent { payment_preimage } => { + let payment_info = htlcs.lock().unwrap(); + for (invoice, preimage_option, _) in payment_info.values() { + if let Some(preimage) = preimage_option { + if payment_preimage == *preimage { + println!("NEW EVENT: successfully sent payment from invoice {} with preimage {}", + invoice, utils::hex_str(&payment_preimage.0)); + } + } + } + }, + Event::PaymentFailed { payment_hash, rejected_by_dest } => { + let payment_info = htlcs.lock().unwrap(); + let htlc_info = payment_info.get(&payment_hash).unwrap(); + print!("NEW EVENT: Failed to send payment to invoice {}:", htlc_info.0); + if rejected_by_dest { + println!("rejected by destination node"); + } else { + println!("route failed"); + } + }, + Event::PendingHTLCsForwardable { .. } => { + loop_channel_manager.process_pending_htlc_forwards(); + check_for_more_events = true; + }, + Event::SpendableOutputs { outputs } => { + let addr_args = vec![serde_json::json!("LDK output address")]; + let destination_address_str = rpc.call_method("getnewaddress", &addr_args).unwrap(); + let destination_address = Address::from_str(destination_address_str.as_str().unwrap()).unwrap(); + let output_descriptors = &outputs.iter().map(|a| a).collect::>(); + let tx_feerate = bitcoind_rpc_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + let spending_tx = keys_manager.spend_spendable_outputs(output_descriptors, + Vec::new(), + destination_address.script_pubkey(), + tx_feerate, &Secp256k1::new()).unwrap(); + bitcoind_rpc_client.broadcast_transaction(&spending_tx); + // XXX maybe need to rescan and blah? but contrary to what matt's saying, it + // looks like spend_spendable's got us covered + } + } } } + pending_txs } fn main() { @@ -62,7 +234,191 @@ fn main() { let bitcoind_port = 18443; let rpc_user = "polaruser".to_string(); let rpc_password = "polarpass".to_string(); - let fee_estimator = BitcoindFeeEstimator::new(bitcoind_host, bitcoind_port, None, rpc_user, rpc_password).unwrap(); - let normal_fee = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); - println!("VMW: {}", normal_fee); + let bitcoind_client = Arc::new(BitcoindClient::new(bitcoind_host.clone(), bitcoind_port, None, + rpc_user.clone(), rpc_password.clone()).unwrap()); + + // ## Setup + // Step 1: Initialize the FeeEstimator + let fee_estimator = bitcoind_client.clone(); + + // Step 2: Initialize the Logger + let logger = Arc::new(FilesystemLogger{}); + + // Step 3: Initialize the BroadcasterInterface + let broadcaster = bitcoind_client.clone(); + + // Step 4: Initialize Persist + let persister = Arc::new(FilesystemPersister::new(".".to_string())); + + // Step 5: Initialize the ChainMonitor + let chain_monitor: Arc = Arc::new(ChainMonitor::new(None, broadcaster.clone(), + logger.clone(), fee_estimator.clone(), + persister.clone())); + + // Step 6: Initialize the KeysManager + let node_privkey = if let Ok(seed) = fs::read("./key_seed") { // the private key that corresponds + assert_eq!(seed.len(), 32); // to our lightning node's pubkey + let mut key = [0; 32]; + key.copy_from_slice(&seed); + key + } else { + let mut key = [0; 32]; + thread_rng().fill_bytes(&mut key); + let mut f = File::create("./key_seed").unwrap(); + f.write_all(&key).expect("Failed to write seed to disk"); + f.sync_all().expect("Failed to sync seed to disk"); + key + }; + let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let keys_manager = Arc::new(KeysManager::new(&node_privkey, cur.as_secs(), cur.subsec_nanos())); + + // Step 7: Read ChannelMonitor state from disk + let mut outpoint_to_channelmonitor = read_channelmonitors_from_disk("./monitors".to_string(), + keys_manager.clone()).unwrap(); + + // Step 9: Read ChannelManager state from disk + let user_config = UserConfig::default(); + let mut channel_manager: ChannelManager; + let mut channel_manager_last_blockhash: Option = None; + if let Ok(mut f) = fs::File::open("./manager") { + let (last_block_hash_option, channel_manager_from_disk) = { + let mut channel_monitor_mut_references = Vec::new(); + for (_, channel_monitor) in outpoint_to_channelmonitor.iter_mut() { + channel_monitor_mut_references.push(&mut channel_monitor.1); + } + let read_args = ChannelManagerReadArgs::new(keys_manager.clone(), fee_estimator.clone(), + chain_monitor.clone(), broadcaster.clone(), + logger.clone(), user_config, + channel_monitor_mut_references); + <(Option, ChannelManager)>::read(&mut f, read_args).unwrap() + }; + channel_manager = channel_manager_from_disk; + channel_manager_last_blockhash = last_block_hash_option; + } else { + let mut bitcoind_rpc_client = bitcoind_client.bitcoind_rpc_client.lock().unwrap(); + let current_chain_height: usize = bitcoind_rpc_client + .call_method("getblockchaininfo", &vec![]).unwrap()["blocks"].as_u64().unwrap() as usize; + channel_manager = channelmanager::ChannelManager::new(Network::Regtest, fee_estimator.clone(), + chain_monitor.clone(), broadcaster.clone(), + logger.clone(), keys_manager.clone(), + user_config, current_chain_height); + } + + // Step 10: Sync ChannelMonitors to chain tip if restarting + let mut chain_tip = None; + let mut chain_listener_channel_monitors = Vec::new(); + let mut cache = UnboundedCache::new(); + let rpc_credentials = base64::encode(format!("{}:{}", rpc_user, rpc_password)); + let mut block_source = RpcClient::new(&rpc_credentials, HttpEndpoint::for_host(bitcoind_host) + .with_port(bitcoind_port)).unwrap(); + let runtime = Runtime::new().expect("Unable to create a runtime"); + if outpoint_to_channelmonitor.len() > 0 { + for (outpoint, blockhash_and_monitor) in outpoint_to_channelmonitor.drain() { + let blockhash = blockhash_and_monitor.0; + let channel_monitor = blockhash_and_monitor.1; + chain_listener_channel_monitors.push((blockhash, (RefCell::new(channel_monitor), + broadcaster.clone(), fee_estimator.clone(), + logger.clone()), outpoint)); + } + + let mut chain_listeners = Vec::new(); + for monitor_listener_info in chain_listener_channel_monitors.iter_mut() { + chain_listeners.push((monitor_listener_info.0, + &mut monitor_listener_info.1 as &mut dyn chain::Listen)); + } + // Because `sync_listeners` is an async function and we want to run it synchronously, + // we run it in a tokio Runtime. + chain_tip = Some(runtime.block_on(init::sync_listeners(&mut block_source, Network::Regtest, + &mut cache, chain_listeners)).unwrap()); + } + + // Step 11: Give ChannelMonitors to ChainMonitor + if chain_listener_channel_monitors.len() > 0 { + for item in chain_listener_channel_monitors.drain(..) { + let channel_monitor = item.1.0.into_inner(); + let funding_outpoint = item.2; + chain_monitor.watch_channel(funding_outpoint, channel_monitor).unwrap(); + } + } + + // Step 12: Sync ChannelManager to chain tip if restarting + if let Some(channel_manager_blockhash) = channel_manager_last_blockhash { + let chain_listener = vec![ + (channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)]; + chain_tip = Some(runtime.block_on(init::sync_listeners(&mut block_source, Network::Regtest, + &mut cache, chain_listener)).unwrap()); + } + + // Step 13: Optional: Initialize the NetGraphMsgHandler + // XXX persist routing data + let genesis = genesis_block(Network::Regtest).header.block_hash(); + let router = Arc::new(NetGraphMsgHandler::new(genesis, None::>, logger.clone())); + + // Step 14: Initialize the PeerManager + let channel_manager = Arc::new(channel_manager); + let mut ephemeral_bytes = [0; 32]; + rand::thread_rng().fill_bytes(&mut ephemeral_bytes); + let lightning_msg_handler = MessageHandler { chan_handler: channel_manager.clone(), + route_handler: router.clone() }; + let peer_manager: Arc = Arc::new(PeerManager::new(lightning_msg_handler, + keys_manager.get_node_secret(), + &ephemeral_bytes, logger.clone())); + + // ## Running LDK + // Step 15: Initialize LDK Event Handling + let (event_ntfn_sender, mut event_ntfn_receiver) = mpsc::channel(2); + let peer_manager_event_listener = peer_manager.clone(); + let channel_manager_event_listener = channel_manager.clone(); + let chain_monitor_event_listener = chain_monitor.clone(); + let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); + let payment_info_for_events = payment_info.clone(); + thread::spawn(move || async move { + let mut pending_txs = HashMap::new(); + loop { + event_ntfn_receiver.recv().await.unwrap(); + pending_txs = handle_ldk_events(peer_manager_event_listener.clone(), + channel_manager_event_listener.clone(), + chain_monitor_event_listener.clone(), + bitcoind_client.clone(), keys_manager.clone(), + pending_txs, payment_info_for_events.clone()); + } + }); + + // Step 16: Initialize Peer Connection Handling + let peer_manager_connection_handler = peer_manager.clone(); + let event_notifier = event_ntfn_sender.clone(); + thread::spawn(move || async move { + let listener = std::net::TcpListener::bind("0.0.0.0:9735").unwrap(); + loop { + let tcp_stream = listener.accept().unwrap().0; + lightning_net_tokio::setup_inbound(peer_manager_connection_handler.clone(), + event_notifier.clone(), tcp_stream).await; + } + }); + + // Step 17: Connect and Disconnect Blocks + let mut chain_poller = poll::ChainPoller::new(&mut block_source, Network::Regtest); + if chain_tip.is_none() { + match runtime.block_on(chain_poller.poll_chain_tip(None)).unwrap() { + ChainTip::Better(header) => chain_tip = Some(header), + _ => panic!("Unexpected chain tip") + } + } + let chain_listener = (chain_monitor.clone(), channel_manager.clone()); + let _spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); + + // Step 17 & 18: Initialize ChannelManager persistence & Once Per Minute: ChannelManager's + // timer_chan_freshness_every_min() and PeerManager's timer_tick_occurred + let persist_channel_manager_callback = move |node: &ChannelManager| { + FilesystemPersister::persist_manager("./".to_string(), &*node) + }; + BackgroundProcessor::start(persist_channel_manager_callback, channel_manager.clone(), logger.clone()); + let peer_manager_processor = peer_manager.clone(); + thread::spawn(move || { + loop { + peer_manager_processor.timer_tick_occured(); + thread::sleep(Duration::new(60, 0)); + } + }); + cli::poll_for_user_input(peer_manager.clone(), channel_manager.clone(), event_ntfn_sender.clone()); } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..5ca6a4d4 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,42 @@ +use bitcoin::secp256k1::key::PublicKey; + +pub fn hex_to_vec(hex: &str) -> Option> { + let mut out = Vec::with_capacity(hex.len() / 2); + + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', + _ => return None, + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + + Some(out) +} + +#[inline] +pub fn hex_str(value: &[u8]) -> String { + let mut res = String::with_capacity(64); + for v in value { + res += &format!("{:02x}", v); + } + res +} + +pub fn hex_to_compressed_pubkey(hex: &str) -> Option { + let data = match hex_to_vec(&hex[0..33*2]) { + Some(bytes) => bytes, + None => return None + }; + match PublicKey::from_slice(&data) { + Ok(pk) => Some(pk), + Err(_) => None, + } +} From 224d32a53718a00f01456711d2ee524e59c5ff46 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 3 Mar 2021 18:44:05 -0500 Subject: [PATCH 04/22] Next draft. Features: connectpeer, listchannels, sendpayment, autoreconnect to chan peers on startup --- .gitignore | 1 + Cargo.lock | 41 +-- Cargo.toml | 8 +- src/bitcoind_client.rs | 164 +++++++--- src/cli.rs | 545 +++++++++++++++++++++++++++++---- src/convert.rs | 86 ++++++ src/disk.rs | 105 +++++++ src/{utils.rs => hex_utils.rs} | 6 +- src/main.rs | 430 +++++++++++++------------- 9 files changed, 1024 insertions(+), 362 deletions(-) create mode 100644 src/convert.rs create mode 100644 src/disk.rs rename src/{utils.rs => hex_utils.rs} (82%) diff --git a/.gitignore b/.gitignore index 088ba6ba..bf68587d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +.ldk diff --git a/Cargo.lock b/Cargo.lock index cdd93357..6204cdd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,13 +41,13 @@ checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" [[package]] name = "bitcoin" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6558aeb12c290cce541a222cba280387161f2bd180a7feb85f8f172cd8ba80e8" +checksum = "1ec5f88a446d66e7474a3b8fa2e348320b574463fb78d799d90ba68f79f48e0e" dependencies = [ "bech32 0.7.2", - "bitcoin_hashes 0.8.0", - "secp256k1 0.18.0", + "bitcoin_hashes", + "secp256k1", ] [[package]] @@ -59,12 +59,6 @@ dependencies = [ "bech32 0.4.1", ] -[[package]] -name = "bitcoin_hashes" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0ab784be052cc1e915a78b8aaf5101eebbc2d0ab2b6f5124985f3677ae2bea" - [[package]] name = "bitcoin_hashes" version = "0.9.4" @@ -247,6 +241,7 @@ version = "0.1.0" dependencies = [ "background-processor", "base64", + "bech32 0.7.2", "bitcoin", "bitcoin-bech32", "hex", @@ -284,18 +279,16 @@ dependencies = [ "lightning", "serde", "serde_json", - "tokio", ] [[package]] name = "lightning-invoice" version = "0.4.0" -source = "git+https://github.com/rust-bitcoin/rust-lightning-invoice?rev=ea25dc7e46a6339493032c500db4fe3a8fdb1acd#ea25dc7e46a6339493032c500db4fe3a8fdb1acd" dependencies = [ "bech32 0.7.2", - "bitcoin_hashes 0.9.4", + "bitcoin_hashes", "num-traits", - "secp256k1 0.20.1", + "secp256k1", ] [[package]] @@ -483,31 +476,13 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -[[package]] -name = "secp256k1" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3f534ef4e9dfa6c4b2ccca131f791daddf9cc2123b4124615cb144ea91611a" -dependencies = [ - "secp256k1-sys 0.2.0", -] - [[package]] name = "secp256k1" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "733b114f058f260c0af7591434eef4272ae1a8ec2751766d3cb89c6df8d5e450" dependencies = [ - "secp256k1-sys 0.4.0", -] - -[[package]] -name = "secp256k1-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71453d9b088b19ae43a803d88ecaa876b11ed8999df024cca4becb6be9aee351" -dependencies = [ - "cc", + "secp256k1-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b09f09cb..7c097ca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,15 @@ edition = "2018" # background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" } background-processor = { path = "../rust-lightning/background-processor" } base64 = "0.13.0" -bitcoin = "0.24" +bitcoin = "0.26" bitcoin-bech32 = "0.7" +bech32 = "0.7" hex = "0.3" # lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "c35002fa9c16042badfa5e7bf819df5f1d2ae60a" } lightning = { path = "../rust-lightning/lightning" } -lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client", "generic-rpc-client" ] } -lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "ea25dc7e46a6339493032c500db4fe3a8fdb1acd" } +lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } +# lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "ea25dc7e46a6339493032c500db4fe3a8fdb1acd" } +lightning-invoice = { path = "../rust-lightning-invoice" } # lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "ff00f6f8861419b73269e6c51d75ac9de75f1d1f" } lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } # lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "aa127f55edc4439b03426644d178e402397329e8" } diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 97e5556e..de9f870f 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -1,76 +1,148 @@ use base64; -use serde_json; - use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; +use bitcoin::util::address::Address; +use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx}; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning_block_sync::http::HttpEndpoint; use lightning_block_sync::rpc::RpcClient; - +use serde_json; +use std::collections::HashMap; +use std::str::FromStr; use std::sync::Mutex; +use tokio::runtime::{Handle, Runtime}; pub struct BitcoindClient { - pub bitcoind_rpc_client: Mutex, + bitcoind_rpc_client: Mutex, + host: String, + port: u16, + rpc_user: String, + rpc_password: String, + runtime: Mutex, } impl BitcoindClient { - pub fn new(host: String, port: u16, path: Option, rpc_user: String, rpc_password: String) -> + pub fn new(host: String, port: u16, rpc_user: String, rpc_password: String) -> std::io::Result { - let mut http_endpoint = HttpEndpoint::for_host(host).with_port(port); - if let Some(p) = path { - http_endpoint = http_endpoint.with_path(p); - } - let rpc_credentials = base64::encode(format!("{}:{}", rpc_user, rpc_password)); + let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port); + let rpc_credentials = base64::encode(format!("{}:{}", rpc_user.clone(), + rpc_password.clone())); let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; - Ok(Self { - bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client) - }) + let client = Self { + bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client), + host, + port, + rpc_user, + rpc_password, + runtime: Mutex::new(Runtime::new().unwrap()), + // runtime: Mutex::new(runtime), + }; + Ok(client) + } + + pub fn get_new_rpc_client(&self) -> std::io::Result { + let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port); + let rpc_credentials = base64::encode(format!("{}:{}", + self.rpc_user.clone(), + self.rpc_password.clone())); + RpcClient::new(&rpc_credentials, http_endpoint) + } + + pub fn create_raw_transaction(&self, outputs: Vec>) -> RawTx { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let outputs_json = serde_json::json!(outputs); + runtime.block_on(rpc.call_method::("createrawtransaction", &vec![serde_json::json!([]), outputs_json])).unwrap() + } + + pub fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let raw_tx_json = serde_json::json!(raw_tx.0); + runtime.block_on(rpc.call_method("fundrawtransaction", &[raw_tx_json])).unwrap() + } + + pub fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let tx_hex_json = serde_json::json!(tx_hex); + runtime.block_on(rpc.call_method("signrawtransactionwithwallet", + &vec![tx_hex_json])).unwrap() + } + + pub fn get_new_address(&self) -> Address { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let addr_args = vec![serde_json::json!("LDK output address")]; + let addr = runtime.block_on(rpc.call_method::("getnewaddress", &addr_args)).unwrap(); + Address::from_str(addr.0.as_str()).unwrap() + } + + pub fn get_blockchain_info(&self) -> BlockchainInfo { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + runtime.block_on(rpc.call_method::("getblockchaininfo", + &vec![])).unwrap() } } impl FeeEstimator for BitcoindClient { fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { - let mut rpc_client_guard = self.bitcoind_rpc_client.lock().unwrap(); - match confirmation_target { - ConfirmationTarget::Background => { - let conf_target = serde_json::json!(144); - let estimate_mode = serde_json::json!("ECONOMICAL"); - let resp = rpc_client_guard.call_method("estimatesmartfee", - &vec![conf_target, estimate_mode]).unwrap(); - if !resp["errors"].is_null() && resp["errors"].as_array().unwrap().len() > 0 { - return 253 - } - resp["feerate"].as_u64().unwrap() as u32 - }, - ConfirmationTarget::Normal => { - let conf_target = serde_json::json!(18); - let estimate_mode = serde_json::json!("ECONOMICAL"); - let resp = rpc_client_guard.call_method("estimatesmartfee", - &vec![conf_target, estimate_mode]).unwrap(); - if !resp["errors"].is_null() && resp["errors"].as_array().unwrap().len() > 0 { - return 253 - } - resp["feerate"].as_u64().unwrap() as u32 - }, - ConfirmationTarget::HighPriority => { - let conf_target = serde_json::json!(6); - let estimate_mode = serde_json::json!("CONSERVATIVE"); - let resp = rpc_client_guard.call_method("estimatesmartfee", - &vec![conf_target, estimate_mode]).unwrap(); - if !resp["errors"].is_null() && resp["errors"].as_array().unwrap().len() > 0 { - return 253 - } - resp["feerate"].as_u64().unwrap() as u32 + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let (conf_target, estimate_mode, default) = match confirmation_target { + ConfirmationTarget::Background => (144, "ECONOMICAL", 253), + ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000), + ConfirmationTarget::HighPriority => (6, "ECONOMICAL", 50000), + }; + + // If we're already in a tokio runtime, then we need to get out of it before we can broadcast. + let conf_target_json = serde_json::json!(conf_target); + let estimate_mode_json = serde_json::json!(estimate_mode); + let resp = match Handle::try_current() { + Ok(_) => { + tokio::task::block_in_place(|| { + runtime.block_on(rpc.call_method::("estimatesmartfee", + &vec![conf_target_json, + estimate_mode_json])).unwrap() + }) }, + _ => runtime.block_on(rpc.call_method::("estimatesmartfee", + &vec![conf_target_json, + estimate_mode_json])).unwrap() + }; + if resp.errored { + return default } + resp.feerate.unwrap() } } impl BroadcasterInterface for BitcoindClient { fn broadcast_transaction(&self, tx: &Transaction) { - let mut rpc_client_guard = self.bitcoind_rpc_client.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + let runtime = self.runtime.lock().unwrap(); + let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); - rpc_client_guard.call_method("sendrawtransaction", &vec![tx_serialized]).unwrap(); + // If we're already in a tokio runtime, then we need to get out of it before we can broadcast. + match Handle::try_current() { + Ok(_) => { + tokio::task::block_in_place(|| { + runtime.block_on(rpc.call_method::("sendrawtransaction", + &vec![tx_serialized])).unwrap(); + }); + }, + _ => { + runtime.block_on(rpc.call_method::("sendrawtransaction", + &vec![tx_serialized])).unwrap(); + } + } } } diff --git a/src/cli.rs b/src/cli.rs index c046d0cf..20c43147 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,65 +1,274 @@ -use bitcoin::secp256k1::key::PublicKey; -use crate::{FilesystemLogger, PeerManager, ChannelManager}; -use crate::utils; +use bitcoin::network::constants::Network; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256::Hash as Sha256Hash; +use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::key::{PublicKey, SecretKey}; +use crate::{ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, NETWORK, + PaymentInfoStorage, PeerManager}; +use crate::disk; +use crate::hex_utils; +use lightning::chain; +use lightning::ln::channelmanager::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::ln::features::InvoiceFeatures; +use lightning::routing::network_graph::NetGraphMsgHandler; +use lightning::routing::router; +use lightning::util::config::UserConfig; +use rand; +use rand::Rng; +use std::env; use std::io; use std::io::{BufRead, Write}; use std::net::{SocketAddr, TcpStream}; +use std::ops::Deref; +use std::path::Path; +use std::str::FromStr; use std::sync::Arc; -use std::thread; use std::time::Duration; -// use tokio::runtime::Runtime; -use tokio::runtime::{Builder, Runtime}; +use tokio::runtime::Handle; use tokio::sync::mpsc; -pub fn poll_for_user_input(peer_manager: Arc, channel_manager: Arc, event_notifier: mpsc::Sender<()>) { - println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available in the `logs` folder of the current directory."); - print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print +pub(crate) struct LdkUserInfo { + pub(crate) bitcoind_rpc_username: String, + pub(crate) bitcoind_rpc_password: String, + pub(crate) bitcoind_rpc_port: u16, + pub(crate) bitcoind_rpc_host: String, + pub(crate) ldk_storage_dir_path: String, + pub(crate) ldk_peer_listening_port: u16, +} + +pub(crate) fn parse_startup_args() -> Result { + if env::args().len() < 4 { + println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [optional: ]`"); + return Err(()) + } + let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); + let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.split("@").collect(); + if bitcoind_rpc_info_parts.len() != 2 { + println!("ERROR: bad bitcoind RPC URL provided"); + return Err(()) + } + let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); + if rpc_user_and_password.len() != 2 { + println!("ERROR: bad bitcoind RPC username/password combo provided"); + return Err(()) + } + let bitcoind_rpc_username = rpc_user_and_password[0].to_string(); + let bitcoind_rpc_password = rpc_user_and_password[1].to_string(); + let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect(); + if bitcoind_rpc_path.len() != 2 { + println!("ERROR: bad bitcoind RPC path provided"); + return Err(()) + } + let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string(); + let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::().unwrap(); + + let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); + + let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { + Some(Ok(p)) => p, + Some(Err(e)) => panic!(e), + None => 9735, + }; + Ok(LdkUserInfo { + bitcoind_rpc_username, + bitcoind_rpc_password, + bitcoind_rpc_host, + bitcoind_rpc_port, + ldk_storage_dir_path, + ldk_peer_listening_port, + }) +} + +pub(crate) fn poll_for_user_input(peer_manager: Arc, channel_manager: + Arc, router: Arc, Arc>>, payment_storage: + PaymentInfoStorage, node_privkey: SecretKey, event_notifier: + mpsc::Sender<()>, ldk_data_dir: String, logger: Arc, + runtime_handle: Handle) { + println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available in the `logs` folder of /.ldk/logs"); let stdin = io::stdin(); + print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print for line in stdin.lock().lines() { + let _ = event_notifier.try_send(()); let line = line.unwrap(); let mut words = line.split_whitespace(); if let Some(word) = words.next() { match word { - "help" => handle_help(), + "help" => help(), "openchannel" => { let peer_pubkey_and_ip_addr = words.next(); let channel_value_sat = words.next(); if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { - println!("ERROR: openchannel takes 2 arguments: `openchannel pubkey@host:port channel_amt_satoshis`") - } else { - let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); - let channel_value_sat = channel_value_sat.unwrap(); - let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@"); - let pubkey = pubkey_and_addr.next(); - let peer_addr_str = pubkey_and_addr.next(); - if peer_addr_str.is_none() || peer_addr_str.is_none() { - println!("ERROR: incorrectly formatted peer info. Should be formatted as: `pubkey@host:port`"); - } else { - let pubkey = pubkey.unwrap(); - let peer_addr_str = peer_addr_str.unwrap(); - let peer_addr_res: Result = peer_addr_str.parse(); - let chan_amt: Result = channel_value_sat.parse(); - if let Some(pk) = utils::hex_to_compressed_pubkey(pubkey) { - if let Ok(addr) = peer_addr_res { - if let Ok(amt) = chan_amt { - handle_open_channel(pk, addr, amt, - peer_manager.clone(), - channel_manager.clone(), - event_notifier.clone()); - } else { - println!("ERROR: channel amount must be a number"); + println!("ERROR: openchannel takes 2 arguments: `openchannel pubkey@host:port channel_amt_satoshis`"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); + let (pubkey, peer_addr) = match parse_peer_info(peer_pubkey_and_ip_addr.to_string()) { + Ok(info) => info, + Err(e) => { + println!("{:?}", e.into_inner().unwrap()); + print!("> "); io::stdout().flush().unwrap(); continue; + } + }; + + let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); + if chan_amt_sat.is_err() { + println!("ERROR: channel amount must be a number"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + + if connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone(), + event_notifier.clone(), + runtime_handle.clone()).is_err() { + print!("> "); io::stdout().flush().unwrap(); continue; + }; + + if open_channel(pubkey, chan_amt_sat.unwrap(), channel_manager.clone()).is_ok() { + let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); + let _ = disk::persist_channel_peer(Path::new(&peer_data_path), peer_pubkey_and_ip_addr); + } + }, + "sendpayment" => { + let invoice_str = words.next(); + if invoice_str.is_none() { + println!("ERROR: sendpayment requires an invoice: `sendpayment `"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + + let invoice_res = lightning_invoice::Invoice::from_str(invoice_str.unwrap()); + if invoice_res.is_err() { + println!("ERROR: invalid invoice: {:?}", invoice_res.unwrap_err()); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let invoice = invoice_res.unwrap(); + + let amt_pico_btc = invoice.amount_pico_btc(); + if amt_pico_btc.is_none () { + println!("ERROR: invalid invoice: must contain amount to pay"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let amt_msat = amt_pico_btc.unwrap() / 10; + + let payee_pubkey = invoice.recover_payee_pub_key(); + let final_cltv = *invoice.min_final_cltv_expiry().unwrap_or(&9) as u32; + + let mut payment_hash = PaymentHash([0; 32]); + payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); + + + let payment_secret = match invoice.payment_secret() { + Some(secret) => { + let mut payment_secret = PaymentSecret([0; 32]); + payment_secret.0.copy_from_slice(&secret.0); + Some(payment_secret) + }, + None => None + }; + + let mut invoice_features = InvoiceFeatures::empty(); + for field in &invoice.into_signed_raw().raw_invoice().data.tagged_fields { + match field { + lightning_invoice::RawTaggedField::UnknownSemantics(vec) => { + if vec[0] == bech32::u5::try_from_u8(5).unwrap() { + if vec.len() >= 6 && vec[5].to_u8() & 0b10000 != 0 { + invoice_features.set_supports_var_onion_optin(); + } + if vec.len() >= 6 && vec[5].to_u8() & 0b01000 != 0 { + invoice_features.set_requires_var_onion_optin(); + } + if vec.len() >= 4 && vec[3].to_u8() & 0b00001 != 0 { + invoice_features.set_supports_payment_secret(); + } + if vec.len() >= 5 && vec[4].to_u8() & 0b10000 != 0 { + invoice_features.set_requires_payment_secret(); + } + if vec.len() >= 4 && vec[3].to_u8() & 0b00100 != 0 { + invoice_features.set_supports_basic_mpp(); + } + if vec.len() >= 4 && vec[3].to_u8() & 0b00010 != 0 { + invoice_features.set_requires_basic_mpp(); } - } else { - println!("ERROR: couldn't parse - pubkey@host:port into a socket address"); } - } else { - println!("ERROR: unable to parse given pubkey for node"); - } + }, + _ => {} } } + let invoice_features_opt = match invoice_features == InvoiceFeatures::empty() { + true => None, + false => Some(invoice_features) + }; + send_payment(payee_pubkey, amt_msat, final_cltv, payment_hash, + payment_secret, invoice_features_opt, router.clone(), + channel_manager.clone(), payment_storage.clone(), + logger.clone()); }, - _ => println!("hello") + "getinvoice" => { + let amt_str = words.next(); + if amt_str.is_none() { + println!("ERROR: getinvoice requires an amount in satoshis"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + + let amt_sat: Result = amt_str.unwrap().parse(); + if amt_sat.is_err() { + println!("ERROR: getinvoice provided payment amount was not a number"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + get_invoice(amt_sat.unwrap(), payment_storage.clone(), node_privkey.clone(), + channel_manager.clone(), router.clone()); + }, + "connectpeer" => { + let peer_pubkey_and_ip_addr = words.next(); + if peer_pubkey_and_ip_addr.is_none() { + println!("ERROR: connectpeer requires peer connection info: `connectpeer pubkey@host:port`"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let (pubkey, peer_addr) = match parse_peer_info(peer_pubkey_and_ip_addr.unwrap().to_string()) { + Ok(info) => info, + Err(e) => { + println!("{:?}", e.into_inner().unwrap()); + print!("> "); io::stdout().flush().unwrap(); continue; + } + }; + if connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone(), + event_notifier.clone(), runtime_handle.clone()).is_ok() { + println!("SUCCESS: connected to peer {}", pubkey); + } + + }, + "listchannels" => list_channels(channel_manager.clone()), + "listpayments" => list_payments(payment_storage.clone()), + "closechannel" => { + let channel_id_str = words.next(); + if channel_id_str.is_none() { + println!("ERROR: closechannel requires a channel ID: `closechannel `"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); + if channel_id_vec.is_none() { + println!("ERROR: couldn't parse channel_id as hex"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let mut channel_id = [0; 32]; + channel_id.copy_from_slice(&channel_id_vec.unwrap()); + close_channel(channel_id, channel_manager.clone()); + }, + "forceclosechannel" => { + let channel_id_str = words.next(); + if channel_id_str.is_none() { + println!("ERROR: forceclosechannel requires a channel ID: `forceclosechannel `"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); + if channel_id_vec.is_none() { + println!("ERROR: couldn't parse channel_id as hex"); + print!("> "); io::stdout().flush().unwrap(); continue; + } + let mut channel_id = [0; 32]; + channel_id.copy_from_slice(&channel_id_vec.unwrap()); + force_close_channel(channel_id, channel_manager.clone()); + } + _ => println!("Unknown command. See `\"help\" for available commands.") } } print!("> "); io::stdout().flush().unwrap(); @@ -67,34 +276,244 @@ pub fn poll_for_user_input(peer_manager: Arc, channel_manager: Arc< } } -fn handle_help() { - println!("") +fn help() { + println!("openchannel pubkey@host:port channel_amt_satoshis"); + println!("sendpayment "); + println!("getinvoice "); + println!("connectpeer pubkey@host:port"); + println!("listchannels"); + println!("listpayments"); + println!("closechannel "); + println!("forceclosechannel "); } -fn handle_open_channel(peer_pubkey: PublicKey, peer_socket: SocketAddr, - channel_amt_sat: u64, peer_manager: Arc, - channel_manager: Arc, event_notifier: - mpsc::Sender<()>) -{ - // let runtime = Runtime::new().expect("Unable to create a runtime").enable_all(); - let runtime = Builder::new_multi_thread() - .worker_threads(3) - .enable_all() - .build() - .unwrap(); - match TcpStream::connect_timeout(&peer_socket, Duration::from_secs(10)) { +fn list_channels(channel_manager: Arc) { + print!("["); + for chan_info in channel_manager.list_channels() { + println!(""); + println!("\t{{"); + println!("\t\tchannel_id: {},", hex_utils::hex_str(&chan_info.channel_id[..])); + println!("\t\tpeer_pubkey: {},", hex_utils::hex_str(&chan_info.remote_network_id.serialize())); + let mut pending_channel = false; + match chan_info.short_channel_id { + Some(id) => println!("\t\tshort_channel_id: {},", id), + None => { + pending_channel = true; + } + } + println!("\t\tpending_open: {},", pending_channel); + println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis); + println!("\t\tchannel_can_send_payments: {},", chan_info.is_live); + println!("\t}},"); + } + println!("]"); +} + +fn list_payments(payment_storage: PaymentInfoStorage) { + let payments = payment_storage.lock().unwrap(); + print!("["); + for (payment_hash, payment_info) in payments.deref() { + println!(""); + println!("\t{{"); + println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); + println!("\t\thtlc_direction: {},", if payment_info.1 == HTLCDirection::Inbound { "inbound" } else { "outbound" }); + println!("\t\thtlc_status: {},", match payment_info.2 { + HTLCStatus::Pending => "pending", + HTLCStatus::Succeeded => "succeeded", + HTLCStatus::Failed => "failed" + }); + + + println!("\t}},"); + } + println!("]"); +} + +pub(crate) fn connect_peer_if_necessary(pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: + Arc, event_notifier: mpsc::Sender<()>, runtime: Handle) -> + Result<(), ()> { + for node_pubkey in peer_manager.get_peer_node_ids() { + if node_pubkey == pubkey { + return Ok(()) + } + } + match TcpStream::connect_timeout(&peer_addr, Duration::from_secs(10)) { Ok(stream) => { - runtime.block_on(|| { - lightning_net_tokio::setup_outbound(peer_manager, - event_notifier, - peer_pubkey, - stream); + let peer_mgr = peer_manager.clone(); + let event_ntfns = event_notifier.clone(); + runtime.spawn(async move { + lightning_net_tokio::setup_outbound(peer_mgr, event_ntfns, pubkey, stream).await; }); - match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) { - Ok(_) => println!("SUCCESS: channel created! Mine some blocks to open it."), - Err(e) => println!("ERROR: failed to open channel: {:?}", e) + let mut peer_connected = false; + while !peer_connected { + for node_pubkey in peer_manager.get_peer_node_ids() { + if node_pubkey == pubkey { peer_connected = true; } + } } }, - Err(e) => println!("ERROR: failed to connect to peer: {:?}", e) + Err(e) => {println!("ERROR: failed to connect to peer: {:?}", e); + return Err(()) + } + } + Ok(()) +} + +fn open_channel(peer_pubkey: PublicKey, channel_amt_sat: u64, channel_manager: Arc) + -> Result<(), ()> { + let mut config = UserConfig::default(); + // lnd's max to_self_delay is 2016, so we want to be compatible. + config.peer_channel_config_limits.their_to_self_delay = 2016; + match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) { + Ok(_) => { + println!("EVENT: initiated channel with peer {}. ", peer_pubkey); + return Ok(()) + }, + Err(e) => { + println!("ERROR: failed to open channel: {:?}", e); + return Err(()) + } + } +} + +fn send_payment(payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, + payment_secret: Option, payee_features: + Option, router: Arc, Arc>>, channel_manager: + Arc, payment_storage: PaymentInfoStorage, logger: + Arc) { + let network_graph = router.network_graph.read().unwrap(); + let first_hops = channel_manager.list_usable_channels(); + let payer_pubkey = channel_manager.get_our_node_id(); + + let route = router::get_route(&payer_pubkey, &network_graph, &payee, payee_features, + Some(&first_hops.iter().collect::>()), &vec![], amt_msat, + final_cltv, logger); + if let Err(e) = route { + println!("ERROR: failed to find route: {}", e.err); + return + } + let status = match channel_manager.send_payment(&route.unwrap(), payment_hash, &payment_secret) { + Ok(()) => { + println!("EVENT: initiated sending {} msats to {}", amt_msat, payee); + HTLCStatus::Pending + }, + Err(e) => { + println!("ERROR: failed to send payment: {:?}", e); + HTLCStatus::Failed + } + }; + let mut payments = payment_storage.lock().unwrap(); + payments.insert(payment_hash, (None, HTLCDirection::Outbound, status)); +} + +fn get_invoice(amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, + channel_manager: Arc, router: Arc, Arc>>) { + let mut payments = payment_storage.lock().unwrap(); + let secp_ctx = Secp256k1::new(); + + let mut preimage = [0; 32]; + rand::thread_rng().fill_bytes(&mut preimage); + let payment_hash = Sha256Hash::hash(&preimage); + + + let our_node_pubkey = channel_manager.get_our_node_id(); + let invoice = lightning_invoice::InvoiceBuilder::new(match NETWORK { + Network::Bitcoin => lightning_invoice::Currency::Bitcoin, + Network::Testnet => lightning_invoice::Currency::BitcoinTestnet, + Network::Regtest => lightning_invoice::Currency::Regtest, + Network::Signet => panic!("Signet invoices not supported") + }).payment_hash(payment_hash).description("rust-lightning-bitcoinrpc invoice".to_string()) + .amount_pico_btc(amt_sat * 10) + .current_timestamp() + .payee_pub_key(our_node_pubkey); + + // Add route hints to the invoice. + // let our_channels = channel_manager.list_usable_channels(); + // let network_graph = router.network_graph.read().unwrap(); + // let network_channels = network_graph.get_channels(); + // for channel in our_channels { + // let short_channel_id_opt = channel.short_channel_id; + // if short_channel_id_opt.is_none() { + // continue + // } + + // let short_channel_id = short_channel_id_opt.unwrap(); + // let channel_routing_info_opt = network_channels.get(&short_channel_id); + // if channel_routing_info_opt.is_none() { + // continue + // } + + // let channel_routing_info = channel_routing_info_opt.unwrap(); + // let mut counterparty = channel_routing_info.node_two; + // let mut counterparty_chan_fees_opt = channel_routing_info.one_to_two.as_ref(); + // if channel_routing_info.node_two != our_node_pubkey { // e.g. if our counterparty is node_one + // counterparty = channel_routing_info.node_one; + // counterparty_chan_fees_opt = channel_routing_info.two_to_one.as_ref(); + // } + // if counterparty_chan_fees_opt.is_none() { + // continue + // } + + // let counterparty_chan_fees = counterparty_chan_fees_opt.unwrap(); + // invoice = invoice.route(vec![ + // lightning_invoice::RouteHop { + // short_channel_id: short_channel_id.to_be_bytes(), + // cltv_expiry_delta: counterparty_chan_fees.cltv_expiry_delta, + // fee_base_msat: counterparty_chan_fees.fees.base_msat, + // fee_proportional_millionths: counterparty_chan_fees.fees.proportional_millionths, + // pubkey: counterparty, + // } + // ]); + // } + + // Sign the invoice. + let invoice = invoice.build_signed(|msg_hash| { + secp_ctx.sign_recoverable(msg_hash, &our_node_privkey) + }); + + match invoice { + Ok(invoice) => println!("SUCCESS: generated invoice: {}", invoice), + Err(e) => println!("ERROR: failed to create invoice: {:?}", e), + } + + payments.insert(PaymentHash(payment_hash.into_inner()), (Some(PaymentPreimage(preimage)), + HTLCDirection::Inbound, + HTLCStatus::Pending)); +} + +fn close_channel(channel_id: [u8; 32], channel_manager: Arc) { + match channel_manager.close_channel(&channel_id) { + Ok(()) => println!("EVENT: initiating channel close"), + Err(e) => println!("ERROR: failed to close channel: {:?}", e) + } +} + +fn force_close_channel(channel_id: [u8; 32], channel_manager: Arc) { + match channel_manager.force_close_channel(&channel_id) { + Ok(()) => println!("EVENT: initiating channel force-close"), + Err(e) => println!("ERROR: failed to force-close channel: {:?}", e) + } +} + +pub(crate) fn parse_peer_info(peer_pubkey_and_ip_addr: String) -> Result<(PublicKey, SocketAddr), std::io::Error> { + let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@"); + let pubkey = pubkey_and_addr.next(); + let peer_addr_str = pubkey_and_addr.next(); + if peer_addr_str.is_none() || peer_addr_str.is_none() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "ERROR: incorrectly formatted peer + info. Should be formatted as: `pubkey@host:port`")); + } + + let peer_addr: Result = peer_addr_str.unwrap().parse(); + if peer_addr.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "ERROR: couldn't parse pubkey@host:port into a socket address")); } + + let pubkey = hex_utils::to_compressed_pubkey(pubkey.unwrap()); + if pubkey.is_none() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "ERROR: unable to parse given pubkey for node")); + } + + Ok((pubkey.unwrap(), peer_addr.unwrap())) } diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 00000000..c92a9268 --- /dev/null +++ b/src/convert.rs @@ -0,0 +1,86 @@ +use bitcoin::BlockHash; +use bitcoin::hashes::hex::FromHex; +use lightning_block_sync::http::JsonResponse; +use std::convert::TryInto; + +pub struct FundedTx { + pub changepos: i64, + pub hex: String, +} + +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(FundedTx { + changepos: self.0["changepos"].as_i64().unwrap(), + hex: self.0["hex"].as_str().unwrap().to_string(), + }) + } +} + +pub struct RawTx(pub String); + +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(RawTx(self.0.as_str().unwrap().to_string())) + } +} + +pub struct SignedTx { + pub complete: bool, + pub hex: String, +} + +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(SignedTx { + hex: self.0["hex"].as_str().unwrap().to_string(), + complete: self.0["complete"].as_bool().unwrap(), + }) + } +} + +pub struct NewAddress(pub String); +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(NewAddress(self.0.as_str().unwrap().to_string())) + } +} + +pub struct FeeResponse { + pub feerate: Option, + pub errored: bool, +} + +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + let errored = !self.0["errors"].is_null(); + Ok(FeeResponse { + errored, + feerate: match errored { + true => None, + // The feerate from bitcoind is in BTC/kb, and we want satoshis/kb. + false => Some((self.0["feerate"].as_f64().unwrap() * 100_000_000.0).round() as u32) + } + }) + } +} + +pub struct BlockchainInfo { + pub latest_height: usize, + pub latest_blockhash: BlockHash, +} + +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(BlockchainInfo { + latest_height: self.0["blocks"].as_u64().unwrap() as usize, + latest_blockhash: BlockHash::from_hex(self.0["bestblockhash"].as_str().unwrap()).unwrap(), + }) + } +} diff --git a/src/disk.rs b/src/disk.rs new file mode 100644 index 00000000..81e2e959 --- /dev/null +++ b/src/disk.rs @@ -0,0 +1,105 @@ +use bitcoin::{BlockHash, Txid}; +use bitcoin::hashes::hex::FromHex; +use bitcoin::secp256k1::key::PublicKey; +use crate::cli; +use lightning::chain::channelmonitor::ChannelMonitor; +use lightning::chain::keysinterface::{InMemorySigner, KeysManager}; +use lightning::chain::transaction::OutPoint; +use lightning::util::logger::{Logger, Record}; +use lightning::util::ser::{ReadableArgs, Writer}; +use std::collections::HashMap; +use std::fs; +use std::fs::File; +// use std::io::{BufRead, BufReader, Cursor, Write}; +use std::io::{BufRead, BufReader, Cursor}; +use std::net::SocketAddr; +use std::path::Path; +use std::sync::Arc; +use time::OffsetDateTime; + +pub(crate) struct FilesystemLogger{ + data_dir: String +} +impl FilesystemLogger { + pub(crate) fn new(data_dir: String) -> Self { + let logs_path = format!("{}/logs", data_dir); + fs::create_dir_all(logs_path.clone()).unwrap(); + Self { + data_dir: logs_path + } + } +} +impl Logger for FilesystemLogger { + fn log(&self, record: &Record) { + let raw_log = record.args.to_string(); + let log = format!("{} {:<5} [{}:{}] {}\n", OffsetDateTime::now_utc().format("%F %T"), + record.level.to_string(), record.module_path, record.line, raw_log); + let logs_file_path = format!("{}/logs.txt", self.data_dir.clone()); + fs::OpenOptions::new().create(true).append(true).open(logs_file_path).unwrap() + .write_all(log.as_bytes()).unwrap(); + } +} +pub(crate) fn persist_channel_peer(path: &Path, peer_info: &str) -> std::io::Result<()> { + let mut file = fs::OpenOptions::new().create(true).append(true).open(path)?; + file.write_all(format!("{}\n", peer_info).as_bytes()) +} + +pub(crate) fn read_channel_peer_data(path: &Path) -> Result, std::io::Error> { + let mut peer_data = HashMap::new(); + if !Path::new(&path).exists() { + return Ok(HashMap::new()) + } + let file = File::open(path)?; + let reader = BufReader::new(file); + for line in reader.lines() { + match cli::parse_peer_info(line.unwrap()) { + Ok((pubkey, socket_addr)) => { + peer_data.insert(pubkey, socket_addr); + }, + Err(e) => return Err(e) + } + } + Ok(peer_data) +} + + +pub(crate) fn read_channelmonitors_from_disk(path: String, keys_manager: Arc) -> + Result)>, std::io::Error> +{ + if !Path::new(&path).exists() { + return Ok(HashMap::new()) + } + let mut outpoint_to_channelmonitor = HashMap::new(); + for file_option in fs::read_dir(path).unwrap() { + let file = file_option.unwrap(); + let owned_file_name = file.file_name(); + let filename = owned_file_name.to_str(); + if !filename.is_some() || !filename.unwrap().is_ascii() || filename.unwrap().len() < 65 { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid ChannelMonitor file name")); + } + + let txid = Txid::from_hex(filename.unwrap().split_at(64).0); + if txid.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx ID in filename")); + } + + let index = filename.unwrap().split_at(65).1.split('.').next().unwrap().parse(); + if index.is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx index in filename")); + } + + let contents = fs::read(&file.path())?; + + if let Ok((blockhash, channel_monitor)) = + <(BlockHash, ChannelMonitor)>::read(&mut Cursor::new(&contents), + &*keys_manager) + { + outpoint_to_channelmonitor.insert(OutPoint { txid: txid.unwrap(), index: index.unwrap() }, + (blockhash, channel_monitor)); + } else { + return Err(std::io::Error::new(std::io::ErrorKind::Other, + "Failed to deserialize ChannelMonitor")); + } + } + Ok(outpoint_to_channelmonitor) +} diff --git a/src/utils.rs b/src/hex_utils.rs similarity index 82% rename from src/utils.rs rename to src/hex_utils.rs index 5ca6a4d4..c9c55b9b 100644 --- a/src/utils.rs +++ b/src/hex_utils.rs @@ -1,6 +1,6 @@ use bitcoin::secp256k1::key::PublicKey; -pub fn hex_to_vec(hex: &str) -> Option> { +pub fn to_vec(hex: &str) -> Option> { let mut out = Vec::with_capacity(hex.len() / 2); let mut b = 0; @@ -30,8 +30,8 @@ pub fn hex_str(value: &[u8]) -> String { res } -pub fn hex_to_compressed_pubkey(hex: &str) -> Option { - let data = match hex_to_vec(&hex[0..33*2]) { +pub fn to_compressed_pubkey(hex: &str) -> Option { + let data = match to_vec(&hex[0..33*2]) { Some(bytes) => bytes, None => return None }; diff --git a/src/main.rs b/src/main.rs index f740cd1d..3c417ef3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,122 +1,71 @@ mod bitcoind_client; mod cli; -mod utils; +mod convert; +mod disk; +mod hex_utils; use background_processor::BackgroundProcessor; -use bitcoin::{BlockHash, Txid}; +use bitcoin::BlockHash; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; -use bitcoin::hashes::hex::FromHex; +use bitcoin::hashes::Hash; +use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::network::constants::Network; use bitcoin::secp256k1::Secp256k1; -use bitcoin::util::address::Address; use bitcoin_bech32::WitnessProgram; use crate::bitcoind_client::BitcoindClient; +use crate::disk::FilesystemLogger; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning::chain::chainmonitor::ChainMonitor; -use lightning::chain::channelmonitor::ChannelMonitor; use lightning::chain::Filter; use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager}; use lightning::chain::transaction::OutPoint; use lightning::chain::Watch; use lightning::ln::channelmanager; -use lightning::ln::channelmanager::{ChannelManagerReadArgs, PaymentHash, PaymentPreimage, +use lightning::ln::channelmanager::{ChainParameters, ChannelManagerReadArgs, PaymentHash, PaymentPreimage, SimpleArcChannelManager}; use lightning::ln::peer_handler::{MessageHandler, SimpleArcPeerManager}; use lightning::util::config::UserConfig; use lightning::util::events::{Event, EventsProvider}; -use lightning::util::logger::{Logger, Record}; -use lightning::util::ser::{ReadableArgs, Writer}; +use lightning::util::ser::ReadableArgs; use lightning_block_sync::UnboundedCache; use lightning_block_sync::SpvClient; -use lightning_block_sync::http::HttpEndpoint; use lightning_block_sync::init; use lightning_block_sync::poll; -use lightning_block_sync::poll::{ChainTip, Poll}; -use lightning_block_sync::rpc::RpcClient; use lightning_net_tokio::SocketDescriptor; use lightning_persister::FilesystemPersister; use rand::{thread_rng, Rng}; use lightning::routing::network_graph::NetGraphMsgHandler; -use std::cell::RefCell; use std::collections::HashMap; use std::fs; use std::fs::File; -use std::io::Cursor; +use std::io; +use std::io:: Write; use std::path::Path; -use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, SystemTime}; -use time::OffsetDateTime; use tokio::runtime::Runtime; use tokio::sync::mpsc; -const NETWORK: Network = Network::Regtest; - -pub struct FilesystemLogger{} -impl Logger for FilesystemLogger { - fn log(&self, record: &Record) { - let raw_log = record.args.to_string(); - let log = format!("{} {:<5} [{}:{}] {}", OffsetDateTime::now_utc().format("%F %T"), - record.level.to_string(), record.module_path, record.line, raw_log); - fs::create_dir_all("logs").unwrap(); - fs::OpenOptions::new().create(true).append(true).open("./logs/logs.txt").unwrap() - .write_all(log.as_bytes()).unwrap(); - } -} - -fn read_channelmonitors_from_disk(path: String, keys_manager: Arc) -> - Result)>, std::io::Error> -{ - if !Path::new(&path).exists() { - return Ok(HashMap::new()) - } - let mut outpoint_to_channelmonitor = HashMap::new(); - for file_option in fs::read_dir(path).unwrap() { - let file = file_option.unwrap(); - let owned_file_name = file.file_name(); - let filename = owned_file_name.to_str(); - if !filename.is_some() || !filename.unwrap().is_ascii() || filename.unwrap().len() < 65 { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid ChannelMonitor file name")); - } - - let txid = Txid::from_hex(filename.unwrap().split_at(64).0); - if txid.is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx ID in filename")); - } - - let index = filename.unwrap().split_at(65).1.split('.').next().unwrap().parse(); - if index.is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx index in filename")); - } +pub(crate) const NETWORK: Network = Network::Regtest; - let contents = fs::read(&file.path())?; - - if let Ok((blockhash, channel_monitor)) = - <(BlockHash, ChannelMonitor)>::read(&mut Cursor::new(&contents), - &*keys_manager) - { - outpoint_to_channelmonitor.insert(OutPoint { txid: txid.unwrap(), index: index.unwrap() }, - (blockhash, channel_monitor)); - } else { - return Err(std::io::Error::new(std::io::ErrorKind::Other, - "Failed to deserialize ChannelMonitor")); - } - } - Ok(outpoint_to_channelmonitor) -} - -type Invoice = String; - -enum HTLCDirection { +#[derive(PartialEq)] +pub(crate) enum HTLCDirection { Inbound, Outbound } -type PaymentInfoStorage = Arc, HTLCDirection)>>>; +pub(crate) enum HTLCStatus { + Pending, + Succeeded, + Failed, +} + +pub(crate) type PaymentInfoStorage = Arc, + HTLCDirection, HTLCStatus)>>>; type ArcChainMonitor = ChainMonitor, Arc, Arc, Arc, Arc>; @@ -127,128 +76,159 @@ BitcoindClient, dyn chain::Access, FilesystemLogger>; pub(crate) type ChannelManager = SimpleArcChannelManager; - fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc, - chain_monitor: Arc, bitcoind_rpc_client: Arc, - keys_manager: Arc, mut pending_txs: HashMap, - htlcs: PaymentInfoStorage) -> HashMap + chain_monitor: Arc, bitcoind_client: Arc, + keys_manager: Arc, payment_storage: PaymentInfoStorage) { - peer_manager.process_events(); - let mut check_for_more_events = true; - while check_for_more_events { + let mut pending_txs: HashMap = HashMap::new(); + loop { + peer_manager.process_events(); let loop_channel_manager = channel_manager.clone(); - check_for_more_events = false; let mut events = channel_manager.get_and_clear_pending_events(); events.append(&mut chain_monitor.get_and_clear_pending_events()); - let mut rpc = bitcoind_rpc_client.bitcoind_rpc_client.lock().unwrap(); for event in events { match event { Event::FundingGenerationReady { temporary_channel_id, channel_value_satoshis, output_script, .. } => { + // Construct the raw transaction with one output, that is paid the amount of the + // channel. let addr = WitnessProgram::from_scriptpubkey(&output_script[..], match NETWORK { Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin, Network::Testnet => bitcoin_bech32::constants::Network::Testnet, Network::Regtest => bitcoin_bech32::constants::Network::Regtest, + Network::Signet => panic!("Signet unsupported"), } ).expect("Lightning funding tx should always be to a SegWit output").to_address(); - let outputs = format!("{{\"{}\": {}}}", addr, channel_value_satoshis as f64 / 1_000_000_00.0).to_string(); - let tx_hex = rpc.call_method("createrawtransaction", &vec![serde_json::json!(outputs)]).unwrap(); - let raw_tx = format!("\"{}\"", tx_hex.as_str().unwrap()).to_string(); - let funded_tx = rpc.call_method("fundrawtransaction", &vec![serde_json::json!(raw_tx)]).unwrap(); - let change_output_position = funded_tx["changepos"].as_i64().unwrap(); + let mut outputs = vec![HashMap::with_capacity(1)]; + outputs[0].insert(addr, channel_value_satoshis as f64 / 100_000_000.0); + let raw_tx = bitcoind_client.create_raw_transaction(outputs); + + // Have your wallet put the inputs into the transaction such that the output is + // satisfied. + let funded_tx = bitcoind_client.fund_raw_transaction(raw_tx); + let change_output_position = funded_tx.changepos; assert!(change_output_position == 0 || change_output_position == 1); - let funded_tx = format!("\"{}\"", funded_tx["hex"].as_str().unwrap()).to_string(); - let signed_tx = rpc.call_method("signrawtransactionwithwallet", - &vec![serde_json::json!(funded_tx)]).unwrap(); - assert_eq!(signed_tx["complete"].as_bool().unwrap(), true); - let final_tx: Transaction = encode::deserialize(&utils::hex_to_vec(&signed_tx["hex"].as_str().unwrap()).unwrap()).unwrap(); + + // Sign the final funding transaction and broadcast it. + let signed_tx = bitcoind_client.sign_raw_transaction_with_wallet(funded_tx.hex); + assert_eq!(signed_tx.complete, true); + let final_tx: Transaction = encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap(); let outpoint = OutPoint { txid: final_tx.txid(), index: if change_output_position == 0 { 1 } else { 0 } }; - loop_channel_manager.funding_transaction_generated(&temporary_channel_id, outpoint); + loop_channel_manager.funding_transaction_generated(&temporary_channel_id, + outpoint); pending_txs.insert(outpoint, final_tx); - check_for_more_events = true; }, Event::FundingBroadcastSafe { funding_txo, .. } => { let funding_tx = pending_txs.remove(&funding_txo).unwrap(); - bitcoind_rpc_client.broadcast_transaction(&funding_tx); + bitcoind_client.broadcast_transaction(&funding_tx); + println!("\nEVENT: broadcasted funding transaction"); + print!("> "); io::stdout().flush().unwrap(); }, Event::PaymentReceived { payment_hash, payment_secret, amt: amt_msat } => { - let payment_info = htlcs.lock().unwrap(); - if let Some(htlc_info) = payment_info.get(&payment_hash) { - assert!(loop_channel_manager.claim_funds(htlc_info.1.unwrap().clone(), - &payment_secret, amt_msat)); + let mut payments = payment_storage.lock().unwrap(); + if let Some((Some(preimage), _, _)) = payments.get(&payment_hash) { + assert!(loop_channel_manager.claim_funds(preimage.clone(), &payment_secret, + amt_msat)); + println!("\nEVENT: received payment from payment_hash {} of {} satoshis", + hex_utils::hex_str(&payment_hash.0), amt_msat / 1000); + print!("> "); io::stdout().flush().unwrap(); + let (_, _, ref mut status) = payments.get_mut(&payment_hash).unwrap(); + *status = HTLCStatus::Succeeded; } else { + println!("\nERROR: we received a payment but didn't know the preimage"); + print!("> "); io::stdout().flush().unwrap(); loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); + payments.insert(payment_hash, (None, HTLCDirection::Inbound, HTLCStatus::Failed)); } - check_for_more_events = true; }, Event::PaymentSent { payment_preimage } => { - let payment_info = htlcs.lock().unwrap(); - for (invoice, preimage_option, _) in payment_info.values() { - if let Some(preimage) = preimage_option { - if payment_preimage == *preimage { - println!("NEW EVENT: successfully sent payment from invoice {} with preimage {}", - invoice, utils::hex_str(&payment_preimage.0)); - } + let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); + let mut payments = payment_storage.lock().unwrap(); + for (payment_hash, (preimage_option, _, status)) in payments.iter_mut() { + if *payment_hash == hashed { + *preimage_option = Some(payment_preimage); + *status = HTLCStatus::Succeeded; + println!("\nNEW EVENT: successfully sent payment from payment hash \ + {:?} with preimage {:?}", hex_utils::hex_str(&payment_hash.0), + hex_utils::hex_str(&payment_preimage.0)); + print!("> "); io::stdout().flush().unwrap(); } } }, - Event::PaymentFailed { payment_hash, rejected_by_dest } => { - let payment_info = htlcs.lock().unwrap(); - let htlc_info = payment_info.get(&payment_hash).unwrap(); - print!("NEW EVENT: Failed to send payment to invoice {}:", htlc_info.0); + Event::PaymentFailed { payment_hash, rejected_by_dest, .. } => { + print!("\nNEW EVENT: Failed to send payment to payment hash {:?}: ", + hex_utils::hex_str(&payment_hash.0)); if rejected_by_dest { println!("rejected by destination node"); } else { println!("route failed"); + } + print!("> "); io::stdout().flush().unwrap(); + + let mut payments = payment_storage.lock().unwrap(); + if payments.contains_key(&payment_hash) { + let (_, _, ref mut status) = payments.get_mut(&payment_hash).unwrap(); + *status = HTLCStatus::Failed; } }, Event::PendingHTLCsForwardable { .. } => { loop_channel_manager.process_pending_htlc_forwards(); - check_for_more_events = true; }, Event::SpendableOutputs { outputs } => { - let addr_args = vec![serde_json::json!("LDK output address")]; - let destination_address_str = rpc.call_method("getnewaddress", &addr_args).unwrap(); - let destination_address = Address::from_str(destination_address_str.as_str().unwrap()).unwrap(); + let destination_address = bitcoind_client.get_new_address(); let output_descriptors = &outputs.iter().map(|a| a).collect::>(); - let tx_feerate = bitcoind_rpc_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + let tx_feerate = bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); let spending_tx = keys_manager.spend_spendable_outputs(output_descriptors, Vec::new(), destination_address.script_pubkey(), tx_feerate, &Secp256k1::new()).unwrap(); - bitcoind_rpc_client.broadcast_transaction(&spending_tx); + bitcoind_client.broadcast_transaction(&spending_tx); // XXX maybe need to rescan and blah? but contrary to what matt's saying, it // looks like spend_spendable's got us covered } } } + thread::sleep(Duration::new(1, 0)); } - pending_txs } fn main() { - let bitcoind_host = "127.0.0.1".to_string(); - let bitcoind_port = 18443; - let rpc_user = "polaruser".to_string(); - let rpc_password = "polarpass".to_string(); - let bitcoind_client = Arc::new(BitcoindClient::new(bitcoind_host.clone(), bitcoind_port, None, - rpc_user.clone(), rpc_password.clone()).unwrap()); + let args = match cli::parse_startup_args() { + Ok(user_args) => user_args, + Err(()) => return + }; + + // Initialize the LDK data directory if necessary. + let ldk_data_dir = format!("{}/.ldk", args.ldk_storage_dir_path); + fs::create_dir_all(ldk_data_dir.clone()).unwrap(); + + // Initialize our bitcoind client. + let bitcoind_client = Arc::new(BitcoindClient::new(args.bitcoind_rpc_host.clone(), + args.bitcoind_rpc_port, + args.bitcoind_rpc_username.clone(), + args.bitcoind_rpc_password.clone()).unwrap()); + let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); // ## Setup // Step 1: Initialize the FeeEstimator + + // BitcoindClient implements the FeeEstimator trait, so it'll act as our fee estimator. let fee_estimator = bitcoind_client.clone(); // Step 2: Initialize the Logger - let logger = Arc::new(FilesystemLogger{}); + let logger = Arc::new(FilesystemLogger::new(ldk_data_dir.clone())); // Step 3: Initialize the BroadcasterInterface + + // BitcoindClient implements the BroadcasterInterface trait, so it'll act as our transaction + // broadcaster. let broadcaster = bitcoind_client.clone(); // Step 4: Initialize Persist - let persister = Arc::new(FilesystemPersister::new(".".to_string())); + let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone())); // Step 5: Initialize the ChainMonitor let chain_monitor: Arc = Arc::new(ChainMonitor::new(None, broadcaster.clone(), @@ -256,32 +236,37 @@ fn main() { persister.clone())); // Step 6: Initialize the KeysManager - let node_privkey = if let Ok(seed) = fs::read("./key_seed") { // the private key that corresponds - assert_eq!(seed.len(), 32); // to our lightning node's pubkey + + // The key seed that we use to derive the node privkey (that corresponds to the node pubkey) and + // other secret key material. + let keys_seed_path = format!("{}/keys_seed", ldk_data_dir.clone()); + let keys_seed = if let Ok(seed) = fs::read(keys_seed_path.clone()) { + assert_eq!(seed.len(), 32); let mut key = [0; 32]; key.copy_from_slice(&seed); key } else { let mut key = [0; 32]; thread_rng().fill_bytes(&mut key); - let mut f = File::create("./key_seed").unwrap(); - f.write_all(&key).expect("Failed to write seed to disk"); - f.sync_all().expect("Failed to sync seed to disk"); + let mut f = File::create(keys_seed_path).unwrap(); + f.write_all(&key).expect("Failed to write node keys seed to disk"); + f.sync_all().expect("Failed to sync node keys seed to disk"); key }; let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let keys_manager = Arc::new(KeysManager::new(&node_privkey, cur.as_secs(), cur.subsec_nanos())); + let keys_manager = Arc::new(KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos())); // Step 7: Read ChannelMonitor state from disk - let mut outpoint_to_channelmonitor = read_channelmonitors_from_disk("./monitors".to_string(), - keys_manager.clone()).unwrap(); + let monitors_path = format!("{}/monitors", ldk_data_dir.clone()); + let mut outpoint_to_channelmonitor = disk::read_channelmonitors_from_disk(monitors_path.to_string(), + keys_manager.clone()).unwrap(); - // Step 9: Read ChannelManager state from disk + // Step 9: Initialize the ChannelManager let user_config = UserConfig::default(); - let mut channel_manager: ChannelManager; - let mut channel_manager_last_blockhash: Option = None; - if let Ok(mut f) = fs::File::open("./manager") { - let (last_block_hash_option, channel_manager_from_disk) = { + let runtime = Runtime::new().unwrap(); + let mut restarting_node = true; + let (channel_manager_blockhash, mut channel_manager) = { + if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { let mut channel_monitor_mut_references = Vec::new(); for (_, channel_monitor) in outpoint_to_channelmonitor.iter_mut() { channel_monitor_mut_references.push(&mut channel_monitor.1); @@ -290,72 +275,63 @@ fn main() { chain_monitor.clone(), broadcaster.clone(), logger.clone(), user_config, channel_monitor_mut_references); - <(Option, ChannelManager)>::read(&mut f, read_args).unwrap() - }; - channel_manager = channel_manager_from_disk; - channel_manager_last_blockhash = last_block_hash_option; - } else { - let mut bitcoind_rpc_client = bitcoind_client.bitcoind_rpc_client.lock().unwrap(); - let current_chain_height: usize = bitcoind_rpc_client - .call_method("getblockchaininfo", &vec![]).unwrap()["blocks"].as_u64().unwrap() as usize; - channel_manager = channelmanager::ChannelManager::new(Network::Regtest, fee_estimator.clone(), - chain_monitor.clone(), broadcaster.clone(), - logger.clone(), keys_manager.clone(), - user_config, current_chain_height); - } + <(BlockHash, ChannelManager)>::read(&mut f, read_args).unwrap() + } else { // We're starting a fresh node. + restarting_node = false; + let getinfo_resp = bitcoind_client.get_blockchain_info(); + let chain_params = ChainParameters { + network: NETWORK, + latest_hash: getinfo_resp.latest_blockhash, + latest_height: getinfo_resp.latest_height, + }; + let fresh_channel_manager = channelmanager::ChannelManager::new(fee_estimator.clone(), + chain_monitor.clone(), + broadcaster.clone(), + logger.clone(), + keys_manager.clone(), + user_config, chain_params); + (getinfo_resp.latest_blockhash, fresh_channel_manager) + } + }; - // Step 10: Sync ChannelMonitors to chain tip if restarting - let mut chain_tip = None; + // Step 10: Sync ChannelMonitors and ChannelManager to chain tip let mut chain_listener_channel_monitors = Vec::new(); let mut cache = UnboundedCache::new(); - let rpc_credentials = base64::encode(format!("{}:{}", rpc_user, rpc_password)); - let mut block_source = RpcClient::new(&rpc_credentials, HttpEndpoint::for_host(bitcoind_host) - .with_port(bitcoind_port)).unwrap(); - let runtime = Runtime::new().expect("Unable to create a runtime"); - if outpoint_to_channelmonitor.len() > 0 { + let mut chain_tip: Option = None; + if restarting_node { + let mut chain_listeners = vec![ + (channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)]; + for (outpoint, blockhash_and_monitor) in outpoint_to_channelmonitor.drain() { let blockhash = blockhash_and_monitor.0; let channel_monitor = blockhash_and_monitor.1; - chain_listener_channel_monitors.push((blockhash, (RefCell::new(channel_monitor), + chain_listener_channel_monitors.push((blockhash, (channel_monitor, broadcaster.clone(), fee_estimator.clone(), logger.clone()), outpoint)); } - let mut chain_listeners = Vec::new(); for monitor_listener_info in chain_listener_channel_monitors.iter_mut() { chain_listeners.push((monitor_listener_info.0, &mut monitor_listener_info.1 as &mut dyn chain::Listen)); } - // Because `sync_listeners` is an async function and we want to run it synchronously, - // we run it in a tokio Runtime. - chain_tip = Some(runtime.block_on(init::sync_listeners(&mut block_source, Network::Regtest, - &mut cache, chain_listeners)).unwrap()); + chain_tip = Some(runtime.block_on(init::synchronize_listeners(&mut bitcoind_rpc_client, NETWORK, + &mut cache, chain_listeners)).unwrap()); } // Step 11: Give ChannelMonitors to ChainMonitor - if chain_listener_channel_monitors.len() > 0 { - for item in chain_listener_channel_monitors.drain(..) { - let channel_monitor = item.1.0.into_inner(); - let funding_outpoint = item.2; - chain_monitor.watch_channel(funding_outpoint, channel_monitor).unwrap(); - } - } - - // Step 12: Sync ChannelManager to chain tip if restarting - if let Some(channel_manager_blockhash) = channel_manager_last_blockhash { - let chain_listener = vec![ - (channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)]; - chain_tip = Some(runtime.block_on(init::sync_listeners(&mut block_source, Network::Regtest, - &mut cache, chain_listener)).unwrap()); + for item in chain_listener_channel_monitors.drain(..) { + let channel_monitor = item.1.0; + let funding_outpoint = item.2; + chain_monitor.watch_channel(funding_outpoint, channel_monitor).unwrap(); } // Step 13: Optional: Initialize the NetGraphMsgHandler // XXX persist routing data - let genesis = genesis_block(Network::Regtest).header.block_hash(); + let genesis = genesis_block(NETWORK).header.block_hash(); let router = Arc::new(NetGraphMsgHandler::new(genesis, None::>, logger.clone())); // Step 14: Initialize the PeerManager - let channel_manager = Arc::new(channel_manager); + let channel_manager: Arc = Arc::new(channel_manager); let mut ephemeral_bytes = [0; 32]; rand::thread_rng().fill_bytes(&mut ephemeral_bytes); let lightning_msg_handler = MessageHandler { chan_handler: channel_manager.clone(), @@ -365,30 +341,16 @@ fn main() { &ephemeral_bytes, logger.clone())); // ## Running LDK - // Step 15: Initialize LDK Event Handling - let (event_ntfn_sender, mut event_ntfn_receiver) = mpsc::channel(2); - let peer_manager_event_listener = peer_manager.clone(); - let channel_manager_event_listener = channel_manager.clone(); - let chain_monitor_event_listener = chain_monitor.clone(); - let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); - let payment_info_for_events = payment_info.clone(); - thread::spawn(move || async move { - let mut pending_txs = HashMap::new(); - loop { - event_ntfn_receiver.recv().await.unwrap(); - pending_txs = handle_ldk_events(peer_manager_event_listener.clone(), - channel_manager_event_listener.clone(), - chain_monitor_event_listener.clone(), - bitcoind_client.clone(), keys_manager.clone(), - pending_txs, payment_info_for_events.clone()); - } - }); - // Step 16: Initialize Peer Connection Handling + + // We poll for events in handle_ldk_events(..) rather than waiting for them over the + // mpsc::channel, so we can leave the event receiver as unused. + let (event_ntfn_sender, mut _event_ntfn_receiver) = mpsc::channel(2); let peer_manager_connection_handler = peer_manager.clone(); let event_notifier = event_ntfn_sender.clone(); - thread::spawn(move || async move { - let listener = std::net::TcpListener::bind("0.0.0.0:9735").unwrap(); + let listening_port = args.ldk_peer_listening_port; + runtime.spawn(async move { + let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", listening_port)).unwrap(); loop { let tcp_stream = listener.accept().unwrap().0; lightning_net_tokio::setup_inbound(peer_manager_connection_handler.clone(), @@ -397,28 +359,68 @@ fn main() { }); // Step 17: Connect and Disconnect Blocks - let mut chain_poller = poll::ChainPoller::new(&mut block_source, Network::Regtest); if chain_tip.is_none() { - match runtime.block_on(chain_poller.poll_chain_tip(None)).unwrap() { - ChainTip::Better(header) => chain_tip = Some(header), - _ => panic!("Unexpected chain tip") - } + chain_tip = Some(runtime.block_on(init::validate_best_block_header(&mut bitcoind_rpc_client)).unwrap()); } - let chain_listener = (chain_monitor.clone(), channel_manager.clone()); - let _spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); + let channel_manager_listener = channel_manager.clone(); + let chain_monitor_listener = chain_monitor.clone(); + runtime.spawn(async move { + let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, NETWORK); + let chain_listener = (chain_monitor_listener, channel_manager_listener); + let mut spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, + &chain_listener); + loop { + spv_client.poll_best_tip().await.unwrap(); + thread::sleep(Duration::new(1, 0)); + } + }); // Step 17 & 18: Initialize ChannelManager persistence & Once Per Minute: ChannelManager's // timer_chan_freshness_every_min() and PeerManager's timer_tick_occurred + let runtime_handle = runtime.handle(); + let data_dir = ldk_data_dir.clone(); let persist_channel_manager_callback = move |node: &ChannelManager| { - FilesystemPersister::persist_manager("./".to_string(), &*node) + FilesystemPersister::persist_manager(data_dir.clone(), &*node) }; - BackgroundProcessor::start(persist_channel_manager_callback, channel_manager.clone(), logger.clone()); + BackgroundProcessor::start(persist_channel_manager_callback, channel_manager.clone(), + logger.clone()); + let peer_manager_processor = peer_manager.clone(); - thread::spawn(move || { + runtime_handle.spawn(async move { loop { - peer_manager_processor.timer_tick_occured(); + peer_manager_processor.timer_tick_occurred(); thread::sleep(Duration::new(60, 0)); } }); - cli::poll_for_user_input(peer_manager.clone(), channel_manager.clone(), event_ntfn_sender.clone()); + + // Step 15: Initialize LDK Event Handling + let peer_manager_event_listener = peer_manager.clone(); + let channel_manager_event_listener = channel_manager.clone(); + let chain_monitor_event_listener = chain_monitor.clone(); + let keys_manager_listener = keys_manager.clone(); + let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); + let payment_info_for_events = payment_info.clone(); + let handle = runtime_handle.clone(); + thread::spawn(move || { + handle_ldk_events(peer_manager_event_listener, channel_manager_event_listener, + chain_monitor_event_listener, bitcoind_client.clone(), + keys_manager_listener, payment_info_for_events); + }); + + // Reconnect to channel peers if possible. + let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); + match disk::read_channel_peer_data(Path::new(&peer_data_path)) { + Ok(mut info) => { + for (pubkey, peer_addr) in info.drain() { + let _ = cli::connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone(), + event_ntfn_sender.clone(), handle.clone()); + } + }, + Err(e) => println!("ERROR: errored reading channel peer info from disk: {:?}", e), + } + + // Start the CLI. + cli::poll_for_user_input(peer_manager.clone(), channel_manager.clone(), router.clone(), + payment_info, keys_manager.get_node_secret(), event_ntfn_sender, + ldk_data_dir.clone(), logger.clone(), handle); } From 25a4f605443ed2bc2bb9548f5679a86fc76d0333 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 18 Mar 2021 12:00:01 -0400 Subject: [PATCH 05/22] Add readme, fix license and a few other cleanups --- Cargo.lock | 34 +++++----- Cargo.toml | 21 +++---- README.md | 16 ++++- src/bitcoind_client.rs | 7 ++- src/cli.rs | 137 ++++++++++++++++++++++------------------- src/disk.rs | 2 +- src/main.rs | 77 ++++++++++++++--------- 7 files changed, 169 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6204cdd9..aa92a5ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,15 +6,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "background-processor" -version = "0.1.0" -dependencies = [ - "bitcoin", - "lightning", - "lightning-persister", -] - [[package]] name = "base-x" version = "0.2.8" @@ -239,13 +230,13 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "ldk-tutorial-node" version = "0.1.0" dependencies = [ - "background-processor", "base64", "bech32 0.7.2", "bitcoin", "bitcoin-bech32", "hex", "lightning", + "lightning-background-processor", "lightning-block-sync", "lightning-invoice", "lightning-net-tokio", @@ -264,14 +255,26 @@ checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" [[package]] name = "lightning" -version = "0.0.12" +version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", ] +[[package]] +name = "lightning-background-processor" +version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" +dependencies = [ + "bitcoin", + "lightning", + "lightning-persister", +] + [[package]] name = "lightning-block-sync" -version = "0.0.1" +version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "chunked_transfer", @@ -284,6 +287,7 @@ dependencies = [ [[package]] name = "lightning-invoice" version = "0.4.0" +source = "git+https://github.com/rust-bitcoin/rust-lightning-invoice?rev=aa3a57b9dca5205fa25fa333a2db165d7e77b3b0#aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" dependencies = [ "bech32 0.7.2", "bitcoin_hashes", @@ -293,7 +297,8 @@ dependencies = [ [[package]] name = "lightning-net-tokio" -version = "0.0.5" +version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "lightning", @@ -302,7 +307,8 @@ dependencies = [ [[package]] name = "lightning-persister" -version = "0.0.1" +version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "libc", diff --git a/Cargo.toml b/Cargo.toml index 7c097ca0..1dd17c01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,31 +2,24 @@ name = "ldk-tutorial-node" version = "0.1.0" authors = ["Valentine Wallace "] +license = "MIT OR Apache-2.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d6f41d3c0b38b9ec9e06a3acfdd9f4b1d007a27d" } -background-processor = { path = "../rust-lightning/background-processor" } +lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } base64 = "0.13.0" bitcoin = "0.26" bitcoin-bech32 = "0.7" bech32 = "0.7" hex = "0.3" -# lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "c35002fa9c16042badfa5e7bf819df5f1d2ae60a" } -lightning = { path = "../rust-lightning/lightning" } -lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } -# lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "ea25dc7e46a6339493032c500db4fe3a8fdb1acd" } -lightning-invoice = { path = "../rust-lightning-invoice" } -# lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "ff00f6f8861419b73269e6c51d75ac9de75f1d1f" } -lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } -# lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "aa127f55edc4439b03426644d178e402397329e8" } -lightning-persister = { path = "../rust-lightning/lightning-persister" } +lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" } +lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } time = "0.2" rand = "0.4" serde_json = { version = "1.0" } -# tokio = { version = "0.2", features = ["io-std", "io-util", "rt-threaded", "tcp", "time", "sync"] } -# tokio = { version = "1.0", features = [ "full", "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } -# tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] } diff --git a/README.md b/README.md index 6358f4ff..740aa25b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ # ldk-sample -sample node implementation using LDK +Sample node implementation using LDK. + +## Installation +``` +git clone git@github.com:valentinewallace/ldk-sample.git +``` + +## Usage +``` +cd ldk-sample +cargo run :@: [] [bitcoin-network] +``` +`bitcoin-network`: defaults to `testnet`. Options: `testnet`, `regtest`. + +`ldk-peer-listening-port`: defaults to 9735. ## License diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index de9f870f..71e6272f 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -36,7 +36,6 @@ impl BitcoindClient { rpc_user, rpc_password, runtime: Mutex::new(Runtime::new().unwrap()), - // runtime: Mutex::new(runtime), }; Ok(client) } @@ -103,7 +102,8 @@ impl FeeEstimator for BitcoindClient { ConfirmationTarget::HighPriority => (6, "ECONOMICAL", 50000), }; - // If we're already in a tokio runtime, then we need to get out of it before we can broadcast. + // This function may be called from a tokio runtime, or not. So we need to check before + // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". let conf_target_json = serde_json::json!(conf_target); let estimate_mode_json = serde_json::json!(estimate_mode); let resp = match Handle::try_current() { @@ -131,7 +131,8 @@ impl BroadcasterInterface for BitcoindClient { let runtime = self.runtime.lock().unwrap(); let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); - // If we're already in a tokio runtime, then we need to get out of it before we can broadcast. + // This function may be called from a tokio runtime, or not. So we need to check before + // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". match Handle::try_current() { Ok(_) => { tokio::task::block_in_place(|| { diff --git a/src/cli.rs b/src/cli.rs index 20c43147..1e11086b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -3,8 +3,8 @@ use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::key::{PublicKey, SecretKey}; -use crate::{ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, NETWORK, - PaymentInfoStorage, PeerManager}; +use crate::{ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, + PaymentInfoStorage, PeerManager, SatoshiAmount}; use crate::disk; use crate::hex_utils; use lightning::chain; @@ -34,11 +34,12 @@ pub(crate) struct LdkUserInfo { pub(crate) bitcoind_rpc_host: String, pub(crate) ldk_storage_dir_path: String, pub(crate) ldk_peer_listening_port: u16, + pub(crate) network: Network, } pub(crate) fn parse_startup_args() -> Result { if env::args().len() < 4 { - println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [optional: ]`"); + println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [] [bitcoin-network]`"); return Err(()) } let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); @@ -64,10 +65,25 @@ pub(crate) fn parse_startup_args() -> Result { let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); + let mut ldk_peer_port_set = true; let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { Some(Ok(p)) => p, Some(Err(e)) => panic!(e), - None => 9735, + None => { + ldk_peer_port_set = false; + 9735 + }, + }; + + let arg_idx = match ldk_peer_port_set { + true => 4, + false => 3, + }; + let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) { + Some("testnet") => Network::Testnet, + Some("regtest") => Network::Regtest, + Some(_) => panic!("Unsupported network provided. Options are: `regtest`, `testnet`"), + None => Network::Testnet }; Ok(LdkUserInfo { bitcoind_rpc_username, @@ -76,6 +92,7 @@ pub(crate) fn parse_startup_args() -> Result { bitcoind_rpc_port, ldk_storage_dir_path, ldk_peer_listening_port, + network, }) } @@ -84,8 +101,8 @@ pub(crate) fn poll_for_user_input(peer_manager: Arc, channel_manage chain::Access>, Arc>>, payment_storage: PaymentInfoStorage, node_privkey: SecretKey, event_notifier: mpsc::Sender<()>, ldk_data_dir: String, logger: Arc, - runtime_handle: Handle) { - println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available in the `logs` folder of /.ldk/logs"); + runtime_handle: Handle, network: Network) { + println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at /.ldk/logs"); let stdin = io::stdin(); print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print for line in stdin.lock().lines() { @@ -165,28 +182,30 @@ pub(crate) fn poll_for_user_input(peer_manager: Arc, channel_manage None => None }; + // rust-lightning-invoice doesn't currently support features, so we parse features + // manually from the invoice. let mut invoice_features = InvoiceFeatures::empty(); for field in &invoice.into_signed_raw().raw_invoice().data.tagged_fields { match field { lightning_invoice::RawTaggedField::UnknownSemantics(vec) => { if vec[0] == bech32::u5::try_from_u8(5).unwrap() { if vec.len() >= 6 && vec[5].to_u8() & 0b10000 != 0 { - invoice_features.set_supports_var_onion_optin(); + invoice_features = invoice_features.set_variable_length_onion_optional(); } if vec.len() >= 6 && vec[5].to_u8() & 0b01000 != 0 { - invoice_features.set_requires_var_onion_optin(); + invoice_features = invoice_features.set_variable_length_onion_required(); } if vec.len() >= 4 && vec[3].to_u8() & 0b00001 != 0 { - invoice_features.set_supports_payment_secret(); + invoice_features = invoice_features.set_payment_secret_optional(); } if vec.len() >= 5 && vec[4].to_u8() & 0b10000 != 0 { - invoice_features.set_requires_payment_secret(); + invoice_features = invoice_features.set_payment_secret_required(); } if vec.len() >= 4 && vec[3].to_u8() & 0b00100 != 0 { - invoice_features.set_supports_basic_mpp(); + invoice_features = invoice_features.set_basic_mpp_optional(); } if vec.len() >= 4 && vec[3].to_u8() & 0b00010 != 0 { - invoice_features.set_requires_basic_mpp(); + invoice_features = invoice_features.set_basic_mpp_required(); } } }, @@ -215,7 +234,7 @@ pub(crate) fn poll_for_user_input(peer_manager: Arc, channel_manage print!("> "); io::stdout().flush().unwrap(); continue; } get_invoice(amt_sat.unwrap(), payment_storage.clone(), node_privkey.clone(), - channel_manager.clone(), router.clone()); + channel_manager.clone(), network); }, "connectpeer" => { let peer_pubkey_and_ip_addr = words.next(); @@ -277,7 +296,7 @@ pub(crate) fn poll_for_user_input(peer_manager: Arc, channel_manage } fn help() { - println!("openchannel pubkey@host:port channel_amt_satoshis"); + println!("openchannel pubkey@host:port "); println!("sendpayment "); println!("getinvoice "); println!("connectpeer pubkey@host:port"); @@ -313,10 +332,15 @@ fn list_payments(payment_storage: PaymentInfoStorage) { let payments = payment_storage.lock().unwrap(); print!("["); for (payment_hash, payment_info) in payments.deref() { + let direction_str = match payment_info.1 { + HTLCDirection::Inbound => "inbound", + HTLCDirection::Outbound => "outbound", + }; println!(""); println!("\t{{"); + println!("\t\tamount_satoshis: {},", payment_info.3); println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); - println!("\t\thtlc_direction: {},", if payment_info.1 == HTLCDirection::Inbound { "inbound" } else { "outbound" }); + println!("\t\thtlc_direction: {},", direction_str); println!("\t\thtlc_status: {},", match payment_info.2 { HTLCStatus::Pending => "pending", HTLCStatus::Succeeded => "succeeded", @@ -376,11 +400,10 @@ fn open_channel(peer_pubkey: PublicKey, channel_amt_sat: u64, channel_manager: A } fn send_payment(payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, - payment_secret: Option, payee_features: - Option, router: Arc, Arc>>, channel_manager: - Arc, payment_storage: PaymentInfoStorage, logger: - Arc) { + payment_secret: Option, payee_features: Option, + router: Arc, Arc>>, + channel_manager: Arc, payment_storage: PaymentInfoStorage, logger: + Arc) { let network_graph = router.network_graph.read().unwrap(); let first_hops = channel_manager.list_usable_channels(); let payer_pubkey = channel_manager.get_our_node_id(); @@ -403,12 +426,12 @@ fn send_payment(payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: } }; let mut payments = payment_storage.lock().unwrap(); - payments.insert(payment_hash, (None, HTLCDirection::Outbound, status)); + payments.insert(payment_hash, (None, HTLCDirection::Outbound, status, + SatoshiAmount(Some(amt_msat * 1000)))); } fn get_invoice(amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, - channel_manager: Arc, router: Arc, Arc>>) { + channel_manager: Arc, network: Network) { let mut payments = payment_storage.lock().unwrap(); let secp_ctx = Secp256k1::new(); @@ -418,54 +441,39 @@ fn get_invoice(amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privk let our_node_pubkey = channel_manager.get_our_node_id(); - let invoice = lightning_invoice::InvoiceBuilder::new(match NETWORK { + let mut invoice = lightning_invoice::InvoiceBuilder::new(match network { Network::Bitcoin => lightning_invoice::Currency::Bitcoin, Network::Testnet => lightning_invoice::Currency::BitcoinTestnet, Network::Regtest => lightning_invoice::Currency::Regtest, Network::Signet => panic!("Signet invoices not supported") - }).payment_hash(payment_hash).description("rust-lightning-bitcoinrpc invoice".to_string()) - .amount_pico_btc(amt_sat * 10) + }) + .payment_hash(payment_hash).description("rust-lightning-bitcoinrpc invoice".to_string()) + .amount_pico_btc(amt_sat * 10_000) .current_timestamp() .payee_pub_key(our_node_pubkey); // Add route hints to the invoice. - // let our_channels = channel_manager.list_usable_channels(); - // let network_graph = router.network_graph.read().unwrap(); - // let network_channels = network_graph.get_channels(); - // for channel in our_channels { - // let short_channel_id_opt = channel.short_channel_id; - // if short_channel_id_opt.is_none() { - // continue - // } - - // let short_channel_id = short_channel_id_opt.unwrap(); - // let channel_routing_info_opt = network_channels.get(&short_channel_id); - // if channel_routing_info_opt.is_none() { - // continue - // } - - // let channel_routing_info = channel_routing_info_opt.unwrap(); - // let mut counterparty = channel_routing_info.node_two; - // let mut counterparty_chan_fees_opt = channel_routing_info.one_to_two.as_ref(); - // if channel_routing_info.node_two != our_node_pubkey { // e.g. if our counterparty is node_one - // counterparty = channel_routing_info.node_one; - // counterparty_chan_fees_opt = channel_routing_info.two_to_one.as_ref(); - // } - // if counterparty_chan_fees_opt.is_none() { - // continue - // } - - // let counterparty_chan_fees = counterparty_chan_fees_opt.unwrap(); - // invoice = invoice.route(vec![ - // lightning_invoice::RouteHop { - // short_channel_id: short_channel_id.to_be_bytes(), - // cltv_expiry_delta: counterparty_chan_fees.cltv_expiry_delta, - // fee_base_msat: counterparty_chan_fees.fees.base_msat, - // fee_proportional_millionths: counterparty_chan_fees.fees.proportional_millionths, - // pubkey: counterparty, - // } - // ]); - // } + let our_channels = channel_manager.list_usable_channels(); + for channel in our_channels { + let short_channel_id = match channel.short_channel_id { + Some(id) => id.to_be_bytes(), + None => continue + }; + let forwarding_info = match channel.counterparty_forwarding_info { + Some(info) => info, + None => continue, + }; + println!("VMW: adding routehop, info.fee base: {}", forwarding_info.fee_base_msat); + invoice = invoice.route(vec![ + lightning_invoice::RouteHop { + pubkey: channel.remote_network_id, + short_channel_id, + fee_base_msat: forwarding_info.fee_base_msat, + fee_proportional_millionths: forwarding_info.fee_proportional_millionths, + cltv_expiry_delta: forwarding_info.cltv_expiry_delta, + } + ]); + } // Sign the invoice. let invoice = invoice.build_signed(|msg_hash| { @@ -479,7 +487,8 @@ fn get_invoice(amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privk payments.insert(PaymentHash(payment_hash.into_inner()), (Some(PaymentPreimage(preimage)), HTLCDirection::Inbound, - HTLCStatus::Pending)); + HTLCStatus::Pending, + SatoshiAmount(Some(amt_sat)))); } fn close_channel(channel_id: [u8; 32], channel_manager: Arc) { diff --git a/src/disk.rs b/src/disk.rs index 81e2e959..cd4ea909 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -63,7 +63,7 @@ pub(crate) fn read_channel_peer_data(path: &Path) -> Result) -> +pub(crate) fn read_channelmonitors(path: String, keys_manager: Arc) -> Result)>, std::io::Error> { if !Path::new(&path).exists() { diff --git a/src/main.rs b/src/main.rs index 3c417ef3..421db95e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod convert; mod disk; mod hex_utils; -use background_processor::BackgroundProcessor; +use lightning_background_processor::BackgroundProcessor; use bitcoin::BlockHash; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::Transaction; @@ -39,6 +39,7 @@ use lightning_persister::FilesystemPersister; use rand::{thread_rng, Rng}; use lightning::routing::network_graph::NetGraphMsgHandler; use std::collections::HashMap; +use std::fmt; use std::fs; use std::fs::File; use std::io; @@ -50,8 +51,6 @@ use std::time::{Duration, SystemTime}; use tokio::runtime::Runtime; use tokio::sync::mpsc; -pub(crate) const NETWORK: Network = Network::Regtest; - #[derive(PartialEq)] pub(crate) enum HTLCDirection { Inbound, @@ -64,8 +63,21 @@ pub(crate) enum HTLCStatus { Failed, } +pub(crate) struct SatoshiAmount(Option); + +impl fmt::Display for SatoshiAmount { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Some(amt) => write!(f, "{}", amt), + None => write!(f, "unknown") + + } + } +} + pub(crate) type PaymentInfoStorage = Arc, - HTLCDirection, HTLCStatus)>>>; + HTLCDirection, HTLCStatus, + SatoshiAmount)>>>; type ArcChainMonitor = ChainMonitor, Arc, Arc, Arc, Arc>; @@ -78,7 +90,8 @@ FilesystemLogger>; fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc, chain_monitor: Arc, bitcoind_client: Arc, - keys_manager: Arc, payment_storage: PaymentInfoStorage) + keys_manager: Arc, payment_storage: PaymentInfoStorage, + network: Network) { let mut pending_txs: HashMap = HashMap::new(); loop { @@ -92,7 +105,7 @@ fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc { // Construct the raw transaction with one output, that is paid the amount of the // channel. - let addr = WitnessProgram::from_scriptpubkey(&output_script[..], match NETWORK { + let addr = WitnessProgram::from_scriptpubkey(&output_script[..], match network { Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin, Network::Testnet => bitcoin_bech32::constants::Network::Testnet, Network::Regtest => bitcoin_bech32::constants::Network::Regtest, @@ -129,31 +142,33 @@ fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc { let mut payments = payment_storage.lock().unwrap(); - if let Some((Some(preimage), _, _)) = payments.get(&payment_hash) { + if let Some((Some(preimage), _, _, _)) = payments.get(&payment_hash) { assert!(loop_channel_manager.claim_funds(preimage.clone(), &payment_secret, amt_msat)); println!("\nEVENT: received payment from payment_hash {} of {} satoshis", hex_utils::hex_str(&payment_hash.0), amt_msat / 1000); print!("> "); io::stdout().flush().unwrap(); - let (_, _, ref mut status) = payments.get_mut(&payment_hash).unwrap(); + let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); *status = HTLCStatus::Succeeded; } else { println!("\nERROR: we received a payment but didn't know the preimage"); print!("> "); io::stdout().flush().unwrap(); loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); - payments.insert(payment_hash, (None, HTLCDirection::Inbound, HTLCStatus::Failed)); + payments.insert(payment_hash, (None, HTLCDirection::Inbound, + HTLCStatus::Failed, SatoshiAmount(None))); } }, Event::PaymentSent { payment_preimage } => { let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); let mut payments = payment_storage.lock().unwrap(); - for (payment_hash, (preimage_option, _, status)) in payments.iter_mut() { + for (payment_hash, (preimage_option, _, status, amt_sat)) in payments.iter_mut() { if *payment_hash == hashed { *preimage_option = Some(payment_preimage); *status = HTLCStatus::Succeeded; - println!("\nNEW EVENT: successfully sent payment from payment hash \ - {:?} with preimage {:?}", hex_utils::hex_str(&payment_hash.0), - hex_utils::hex_str(&payment_preimage.0)); + println!("\nNEW EVENT: successfully sent payment of {} satoshis from \ + payment hash {:?} with preimage {:?}", amt_sat, + hex_utils::hex_str(&payment_hash.0), + hex_utils::hex_str(&payment_preimage.0)); print!("> "); io::stdout().flush().unwrap(); } } @@ -170,7 +185,7 @@ fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc, channel_manager: Arc Arc::new(client), + Err(e) => { + println!("Failed to connect to bitcoind client: {}", e); + return + } + }; let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); // ## Setup @@ -258,8 +277,8 @@ fn main() { // Step 7: Read ChannelMonitor state from disk let monitors_path = format!("{}/monitors", ldk_data_dir.clone()); - let mut outpoint_to_channelmonitor = disk::read_channelmonitors_from_disk(monitors_path.to_string(), - keys_manager.clone()).unwrap(); + let mut outpoint_to_channelmonitor = disk::read_channelmonitors(monitors_path.to_string(), + keys_manager.clone()).unwrap(); // Step 9: Initialize the ChannelManager let user_config = UserConfig::default(); @@ -280,7 +299,7 @@ fn main() { restarting_node = false; let getinfo_resp = bitcoind_client.get_blockchain_info(); let chain_params = ChainParameters { - network: NETWORK, + network: args.network, latest_hash: getinfo_resp.latest_blockhash, latest_height: getinfo_resp.latest_height, }; @@ -314,7 +333,7 @@ fn main() { chain_listeners.push((monitor_listener_info.0, &mut monitor_listener_info.1 as &mut dyn chain::Listen)); } - chain_tip = Some(runtime.block_on(init::synchronize_listeners(&mut bitcoind_rpc_client, NETWORK, + chain_tip = Some(runtime.block_on(init::synchronize_listeners(&mut bitcoind_rpc_client, args.network, &mut cache, chain_listeners)).unwrap()); } @@ -327,7 +346,7 @@ fn main() { // Step 13: Optional: Initialize the NetGraphMsgHandler // XXX persist routing data - let genesis = genesis_block(NETWORK).header.block_hash(); + let genesis = genesis_block(args.network).header.block_hash(); let router = Arc::new(NetGraphMsgHandler::new(genesis, None::>, logger.clone())); // Step 14: Initialize the PeerManager @@ -364,8 +383,9 @@ fn main() { } let channel_manager_listener = channel_manager.clone(); let chain_monitor_listener = chain_monitor.clone(); + let network = args.network; runtime.spawn(async move { - let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, NETWORK); + let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, network); let chain_listener = (chain_monitor_listener, channel_manager_listener); let mut spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); @@ -401,10 +421,11 @@ fn main() { let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); let payment_info_for_events = payment_info.clone(); let handle = runtime_handle.clone(); + let network = args.network; thread::spawn(move || { handle_ldk_events(peer_manager_event_listener, channel_manager_event_listener, chain_monitor_event_listener, bitcoind_client.clone(), - keys_manager_listener, payment_info_for_events); + keys_manager_listener, payment_info_for_events, network); }); // Reconnect to channel peers if possible. @@ -422,5 +443,5 @@ fn main() { // Start the CLI. cli::poll_for_user_input(peer_manager.clone(), channel_manager.clone(), router.clone(), payment_info, keys_manager.get_node_secret(), event_ntfn_sender, - ldk_data_dir.clone(), logger.clone(), handle); + ldk_data_dir.clone(), logger.clone(), handle, args.network); } From 80916829ec20eb8a510462e765a03c3ed051f2e0 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 18 Mar 2021 18:30:20 -0400 Subject: [PATCH 06/22] Forward HTLCs after a timeout and a few other cleanups --- README.md | 2 +- src/main.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 740aa25b..4b9e99a8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Sample node implementation using LDK. ## Installation ``` -git clone git@github.com:valentinewallace/ldk-sample.git +git clone git@github.com:lightningdevkit/ldk-sample.git ``` ## Usage diff --git a/src/main.rs b/src/main.rs index 421db95e..37d464a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,6 +130,7 @@ fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc, channel_manager: Arc { + Event::PaymentFailed { payment_hash, rejected_by_dest } => { print!("\nNEW EVENT: Failed to send payment to payment hash {:?}: ", hex_utils::hex_str(&payment_hash.0)); if rejected_by_dest { @@ -189,8 +190,14 @@ fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc { - loop_channel_manager.process_pending_htlc_forwards(); + Event::PendingHTLCsForwardable { time_forwardable } => { + let forwarding_channel_manager = loop_channel_manager.clone(); + thread::spawn(move || { + let min = time_forwardable.as_secs(); + let seconds_to_sleep = thread_rng().gen_range(min, min * 5); + thread::sleep(Duration::new(seconds_to_sleep, 0)); + forwarding_channel_manager.process_pending_htlc_forwards(); + }); }, Event::SpendableOutputs { outputs } => { let destination_address = bitcoind_client.get_new_address(); From d9e9c01d33f291cf4846c0406a3e00c9ecbd543d Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 19 Mar 2021 16:26:43 -0400 Subject: [PATCH 07/22] add rustfmt + CI + other fixups --- .github/workflows/build.yml | 27 + README.md | 4 +- rustfmt.toml | 5 + src/bitcoind_client.rs | 263 +++++---- src/cli.rs | 1078 ++++++++++++++++++++--------------- src/convert.rs | 101 ++-- src/disk.rs | 170 +++--- src/hex_utils.rs | 58 +- src/main.rs | 902 ++++++++++++++++------------- 9 files changed, 1459 insertions(+), 1149 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 rustfmt.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..1a70ea03 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Continuous Integration Checks + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + toolchain: [ stable, beta ] + include: + - toolchain: stable + check-fmt: true + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v2 + - name: Install Rust ${{ matrix.toolchain }} toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + override: true + profile: minimal + - name: Build on Rust ${{ matrix.toolchain }} + run: cargo build --verbose --color always + - name: Check formatting + if: matrix.check-fmt + run: rustup component add rustfmt && cargo fmt --all -- --check diff --git a/README.md b/README.md index 4b9e99a8..5dda263e 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,15 @@ git clone git@github.com:lightningdevkit/ldk-sample.git cd ldk-sample cargo run :@: [] [bitcoin-network] ``` +`bitcoind`'s RPC username and password likely can be found through `cat ~/.bitcoin/.cookie`. + `bitcoin-network`: defaults to `testnet`. Options: `testnet`, `regtest`. `ldk-peer-listening-port`: defaults to 9735. ## License -Licensed under either of +Licensed under either: * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..c61104c9 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,5 @@ +hard_tabs = true # use tab characters for indentation, spaces for alignment +use_field_init_shorthand = true +max_width = 100 +use_small_heuristics = "Max" +fn_args_layout = "Compressed" \ No newline at end of file diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 71e6272f..f18cd407 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -1,8 +1,8 @@ +use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx}; use base64; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; use bitcoin::util::address::Address; -use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx}; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning_block_sync::http::HttpEndpoint; use lightning_block_sync::rpc::RpcClient; @@ -13,137 +13,150 @@ use std::sync::Mutex; use tokio::runtime::{Handle, Runtime}; pub struct BitcoindClient { - bitcoind_rpc_client: Mutex, - host: String, - port: u16, - rpc_user: String, - rpc_password: String, - runtime: Mutex, + bitcoind_rpc_client: Mutex, + host: String, + port: u16, + rpc_user: String, + rpc_password: String, + runtime: Mutex, } impl BitcoindClient { - pub fn new(host: String, port: u16, rpc_user: String, rpc_password: String) -> - std::io::Result - { - let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port); - let rpc_credentials = base64::encode(format!("{}:{}", rpc_user.clone(), - rpc_password.clone())); - let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; - let client = Self { - bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client), - host, - port, - rpc_user, - rpc_password, - runtime: Mutex::new(Runtime::new().unwrap()), - }; - Ok(client) - } - - pub fn get_new_rpc_client(&self) -> std::io::Result { - let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port); - let rpc_credentials = base64::encode(format!("{}:{}", - self.rpc_user.clone(), - self.rpc_password.clone())); - RpcClient::new(&rpc_credentials, http_endpoint) - } - - pub fn create_raw_transaction(&self, outputs: Vec>) -> RawTx { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - let outputs_json = serde_json::json!(outputs); - runtime.block_on(rpc.call_method::("createrawtransaction", &vec![serde_json::json!([]), outputs_json])).unwrap() - } - - pub fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - let raw_tx_json = serde_json::json!(raw_tx.0); - runtime.block_on(rpc.call_method("fundrawtransaction", &[raw_tx_json])).unwrap() - } - - pub fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - let tx_hex_json = serde_json::json!(tx_hex); - runtime.block_on(rpc.call_method("signrawtransactionwithwallet", - &vec![tx_hex_json])).unwrap() - } - - pub fn get_new_address(&self) -> Address { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - let addr_args = vec![serde_json::json!("LDK output address")]; - let addr = runtime.block_on(rpc.call_method::("getnewaddress", &addr_args)).unwrap(); - Address::from_str(addr.0.as_str()).unwrap() - } - - pub fn get_blockchain_info(&self) -> BlockchainInfo { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - runtime.block_on(rpc.call_method::("getblockchaininfo", - &vec![])).unwrap() - } + pub fn new( + host: String, port: u16, rpc_user: String, rpc_password: String, + ) -> std::io::Result { + let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port); + let rpc_credentials = + base64::encode(format!("{}:{}", rpc_user.clone(), rpc_password.clone())); + let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; + let client = Self { + bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client), + host, + port, + rpc_user, + rpc_password, + runtime: Mutex::new(Runtime::new().unwrap()), + }; + Ok(client) + } + + pub fn get_new_rpc_client(&self) -> std::io::Result { + let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port); + let rpc_credentials = + base64::encode(format!("{}:{}", self.rpc_user.clone(), self.rpc_password.clone())); + RpcClient::new(&rpc_credentials, http_endpoint) + } + + pub fn create_raw_transaction(&self, outputs: Vec>) -> RawTx { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let outputs_json = serde_json::json!(outputs); + runtime + .block_on(rpc.call_method::( + "createrawtransaction", + &vec![serde_json::json!([]), outputs_json], + )) + .unwrap() + } + + pub fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let raw_tx_json = serde_json::json!(raw_tx.0); + runtime.block_on(rpc.call_method("fundrawtransaction", &[raw_tx_json])).unwrap() + } + + pub fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let tx_hex_json = serde_json::json!(tx_hex); + runtime + .block_on(rpc.call_method("signrawtransactionwithwallet", &vec![tx_hex_json])) + .unwrap() + } + + pub fn get_new_address(&self) -> Address { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let addr_args = vec![serde_json::json!("LDK output address")]; + let addr = + runtime.block_on(rpc.call_method::("getnewaddress", &addr_args)).unwrap(); + Address::from_str(addr.0.as_str()).unwrap() + } + + pub fn get_blockchain_info(&self) -> BlockchainInfo { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + runtime.block_on(rpc.call_method::("getblockchaininfo", &vec![])).unwrap() + } } impl FeeEstimator for BitcoindClient { - fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - let (conf_target, estimate_mode, default) = match confirmation_target { - ConfirmationTarget::Background => (144, "ECONOMICAL", 253), - ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000), - ConfirmationTarget::HighPriority => (6, "ECONOMICAL", 50000), - }; - - // This function may be called from a tokio runtime, or not. So we need to check before - // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". - let conf_target_json = serde_json::json!(conf_target); - let estimate_mode_json = serde_json::json!(estimate_mode); - let resp = match Handle::try_current() { - Ok(_) => { - tokio::task::block_in_place(|| { - runtime.block_on(rpc.call_method::("estimatesmartfee", - &vec![conf_target_json, - estimate_mode_json])).unwrap() - }) - }, - _ => runtime.block_on(rpc.call_method::("estimatesmartfee", - &vec![conf_target_json, - estimate_mode_json])).unwrap() - }; - if resp.errored { - return default - } - resp.feerate.unwrap() - } + fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { + let runtime = self.runtime.lock().unwrap(); + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + + let (conf_target, estimate_mode, default) = match confirmation_target { + ConfirmationTarget::Background => (144, "ECONOMICAL", 253), + ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000), + ConfirmationTarget::HighPriority => (6, "ECONOMICAL", 50000), + }; + + // This function may be called from a tokio runtime, or not. So we need to check before + // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". + let conf_target_json = serde_json::json!(conf_target); + let estimate_mode_json = serde_json::json!(estimate_mode); + let resp = match Handle::try_current() { + Ok(_) => tokio::task::block_in_place(|| { + runtime + .block_on(rpc.call_method::( + "estimatesmartfee", + &vec![conf_target_json, estimate_mode_json], + )) + .unwrap() + }), + _ => runtime + .block_on(rpc.call_method::( + "estimatesmartfee", + &vec![conf_target_json, estimate_mode_json], + )) + .unwrap(), + }; + if resp.errored { + return default; + } + resp.feerate.unwrap() + } } impl BroadcasterInterface for BitcoindClient { - fn broadcast_transaction(&self, tx: &Transaction) { - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - let runtime = self.runtime.lock().unwrap(); - - let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); - // This function may be called from a tokio runtime, or not. So we need to check before - // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". - match Handle::try_current() { - Ok(_) => { - tokio::task::block_in_place(|| { - runtime.block_on(rpc.call_method::("sendrawtransaction", - &vec![tx_serialized])).unwrap(); - }); - }, - _ => { - runtime.block_on(rpc.call_method::("sendrawtransaction", - &vec![tx_serialized])).unwrap(); - } - } - } + fn broadcast_transaction(&self, tx: &Transaction) { + let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + let runtime = self.runtime.lock().unwrap(); + + let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); + // This function may be called from a tokio runtime, or not. So we need to check before + // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". + match Handle::try_current() { + Ok(_) => { + tokio::task::block_in_place(|| { + runtime + .block_on( + rpc.call_method::("sendrawtransaction", &vec![tx_serialized]), + ) + .unwrap(); + }); + } + _ => { + runtime + .block_on(rpc.call_method::("sendrawtransaction", &vec![tx_serialized])) + .unwrap(); + } + } + } } diff --git a/src/cli.rs b/src/cli.rs index 1e11086b..21c40236 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,12 +1,14 @@ -use bitcoin::network::constants::Network; -use bitcoin::hashes::Hash; -use bitcoin::hashes::sha256::Hash as Sha256Hash; -use bitcoin::secp256k1::Secp256k1; -use bitcoin::secp256k1::key::{PublicKey, SecretKey}; -use crate::{ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, - PaymentInfoStorage, PeerManager, SatoshiAmount}; use crate::disk; use crate::hex_utils; +use crate::{ + ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, PaymentInfoStorage, PeerManager, + SatoshiAmount, +}; +use bitcoin::hashes::sha256::Hash as Sha256Hash; +use bitcoin::hashes::Hash; +use bitcoin::network::constants::Network; +use bitcoin::secp256k1::key::{PublicKey, SecretKey}; +use bitcoin::secp256k1::Secp256k1; use lightning::chain; use lightning::ln::channelmanager::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::features::InvoiceFeatures; @@ -28,501 +30,629 @@ use tokio::runtime::Handle; use tokio::sync::mpsc; pub(crate) struct LdkUserInfo { - pub(crate) bitcoind_rpc_username: String, - pub(crate) bitcoind_rpc_password: String, - pub(crate) bitcoind_rpc_port: u16, - pub(crate) bitcoind_rpc_host: String, - pub(crate) ldk_storage_dir_path: String, - pub(crate) ldk_peer_listening_port: u16, - pub(crate) network: Network, + pub(crate) bitcoind_rpc_username: String, + pub(crate) bitcoind_rpc_password: String, + pub(crate) bitcoind_rpc_port: u16, + pub(crate) bitcoind_rpc_host: String, + pub(crate) ldk_storage_dir_path: String, + pub(crate) ldk_peer_listening_port: u16, + pub(crate) network: Network, } pub(crate) fn parse_startup_args() -> Result { - if env::args().len() < 4 { - println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [] [bitcoin-network]`"); - return Err(()) - } - let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); - let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.split("@").collect(); - if bitcoind_rpc_info_parts.len() != 2 { - println!("ERROR: bad bitcoind RPC URL provided"); - return Err(()) - } - let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); - if rpc_user_and_password.len() != 2 { - println!("ERROR: bad bitcoind RPC username/password combo provided"); - return Err(()) - } - let bitcoind_rpc_username = rpc_user_and_password[0].to_string(); - let bitcoind_rpc_password = rpc_user_and_password[1].to_string(); - let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect(); - if bitcoind_rpc_path.len() != 2 { - println!("ERROR: bad bitcoind RPC path provided"); - return Err(()) - } - let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string(); - let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::().unwrap(); - - let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); - - let mut ldk_peer_port_set = true; - let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { - Some(Ok(p)) => p, - Some(Err(e)) => panic!(e), - None => { - ldk_peer_port_set = false; - 9735 - }, - }; - - let arg_idx = match ldk_peer_port_set { - true => 4, - false => 3, - }; - let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) { - Some("testnet") => Network::Testnet, - Some("regtest") => Network::Regtest, - Some(_) => panic!("Unsupported network provided. Options are: `regtest`, `testnet`"), - None => Network::Testnet - }; - Ok(LdkUserInfo { - bitcoind_rpc_username, - bitcoind_rpc_password, - bitcoind_rpc_host, - bitcoind_rpc_port, - ldk_storage_dir_path, - ldk_peer_listening_port, - network, - }) + if env::args().len() < 4 { + println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [] [bitcoin-network]`"); + return Err(()); + } + let bitcoind_rpc_info = env::args().skip(1).next().unwrap(); + let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.split("@").collect(); + if bitcoind_rpc_info_parts.len() != 2 { + println!("ERROR: bad bitcoind RPC URL provided"); + return Err(()); + } + let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect(); + if rpc_user_and_password.len() != 2 { + println!("ERROR: bad bitcoind RPC username/password combo provided"); + return Err(()); + } + let bitcoind_rpc_username = rpc_user_and_password[0].to_string(); + let bitcoind_rpc_password = rpc_user_and_password[1].to_string(); + let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect(); + if bitcoind_rpc_path.len() != 2 { + println!("ERROR: bad bitcoind RPC path provided"); + return Err(()); + } + let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string(); + let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::().unwrap(); + + let ldk_storage_dir_path = env::args().skip(2).next().unwrap(); + + let mut ldk_peer_port_set = true; + let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { + Some(Ok(p)) => p, + Some(Err(e)) => panic!(e), + None => { + ldk_peer_port_set = false; + 9735 + } + }; + + let arg_idx = match ldk_peer_port_set { + true => 4, + false => 3, + }; + let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) { + Some("testnet") => Network::Testnet, + Some("regtest") => Network::Regtest, + Some(_) => panic!("Unsupported network provided. Options are: `regtest`, `testnet`"), + None => Network::Testnet, + }; + Ok(LdkUserInfo { + bitcoind_rpc_username, + bitcoind_rpc_password, + bitcoind_rpc_host, + bitcoind_rpc_port, + ldk_storage_dir_path, + ldk_peer_listening_port, + network, + }) } -pub(crate) fn poll_for_user_input(peer_manager: Arc, channel_manager: - Arc, router: Arc, Arc>>, payment_storage: - PaymentInfoStorage, node_privkey: SecretKey, event_notifier: - mpsc::Sender<()>, ldk_data_dir: String, logger: Arc, - runtime_handle: Handle, network: Network) { - println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at /.ldk/logs"); - let stdin = io::stdin(); - print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print - for line in stdin.lock().lines() { - let _ = event_notifier.try_send(()); - let line = line.unwrap(); - let mut words = line.split_whitespace(); - if let Some(word) = words.next() { - match word { - "help" => help(), - "openchannel" => { - let peer_pubkey_and_ip_addr = words.next(); - let channel_value_sat = words.next(); - if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { - println!("ERROR: openchannel takes 2 arguments: `openchannel pubkey@host:port channel_amt_satoshis`"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); - let (pubkey, peer_addr) = match parse_peer_info(peer_pubkey_and_ip_addr.to_string()) { - Ok(info) => info, - Err(e) => { - println!("{:?}", e.into_inner().unwrap()); - print!("> "); io::stdout().flush().unwrap(); continue; - } - }; - - let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); - if chan_amt_sat.is_err() { - println!("ERROR: channel amount must be a number"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - - if connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone(), - event_notifier.clone(), - runtime_handle.clone()).is_err() { - print!("> "); io::stdout().flush().unwrap(); continue; - }; - - if open_channel(pubkey, chan_amt_sat.unwrap(), channel_manager.clone()).is_ok() { - let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); - let _ = disk::persist_channel_peer(Path::new(&peer_data_path), peer_pubkey_and_ip_addr); - } - }, - "sendpayment" => { - let invoice_str = words.next(); - if invoice_str.is_none() { - println!("ERROR: sendpayment requires an invoice: `sendpayment `"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - - let invoice_res = lightning_invoice::Invoice::from_str(invoice_str.unwrap()); - if invoice_res.is_err() { - println!("ERROR: invalid invoice: {:?}", invoice_res.unwrap_err()); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let invoice = invoice_res.unwrap(); - - let amt_pico_btc = invoice.amount_pico_btc(); - if amt_pico_btc.is_none () { - println!("ERROR: invalid invoice: must contain amount to pay"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let amt_msat = amt_pico_btc.unwrap() / 10; - - let payee_pubkey = invoice.recover_payee_pub_key(); - let final_cltv = *invoice.min_final_cltv_expiry().unwrap_or(&9) as u32; - - let mut payment_hash = PaymentHash([0; 32]); - payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); - - - let payment_secret = match invoice.payment_secret() { - Some(secret) => { - let mut payment_secret = PaymentSecret([0; 32]); - payment_secret.0.copy_from_slice(&secret.0); - Some(payment_secret) - }, - None => None - }; - - // rust-lightning-invoice doesn't currently support features, so we parse features - // manually from the invoice. - let mut invoice_features = InvoiceFeatures::empty(); - for field in &invoice.into_signed_raw().raw_invoice().data.tagged_fields { - match field { - lightning_invoice::RawTaggedField::UnknownSemantics(vec) => { - if vec[0] == bech32::u5::try_from_u8(5).unwrap() { - if vec.len() >= 6 && vec[5].to_u8() & 0b10000 != 0 { - invoice_features = invoice_features.set_variable_length_onion_optional(); - } - if vec.len() >= 6 && vec[5].to_u8() & 0b01000 != 0 { - invoice_features = invoice_features.set_variable_length_onion_required(); - } - if vec.len() >= 4 && vec[3].to_u8() & 0b00001 != 0 { - invoice_features = invoice_features.set_payment_secret_optional(); - } - if vec.len() >= 5 && vec[4].to_u8() & 0b10000 != 0 { - invoice_features = invoice_features.set_payment_secret_required(); - } - if vec.len() >= 4 && vec[3].to_u8() & 0b00100 != 0 { - invoice_features = invoice_features.set_basic_mpp_optional(); - } - if vec.len() >= 4 && vec[3].to_u8() & 0b00010 != 0 { - invoice_features = invoice_features.set_basic_mpp_required(); - } - } - }, - _ => {} - } - } - let invoice_features_opt = match invoice_features == InvoiceFeatures::empty() { - true => None, - false => Some(invoice_features) - }; - send_payment(payee_pubkey, amt_msat, final_cltv, payment_hash, - payment_secret, invoice_features_opt, router.clone(), - channel_manager.clone(), payment_storage.clone(), - logger.clone()); - }, - "getinvoice" => { - let amt_str = words.next(); - if amt_str.is_none() { - println!("ERROR: getinvoice requires an amount in satoshis"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - - let amt_sat: Result = amt_str.unwrap().parse(); - if amt_sat.is_err() { - println!("ERROR: getinvoice provided payment amount was not a number"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - get_invoice(amt_sat.unwrap(), payment_storage.clone(), node_privkey.clone(), - channel_manager.clone(), network); - }, - "connectpeer" => { - let peer_pubkey_and_ip_addr = words.next(); - if peer_pubkey_and_ip_addr.is_none() { - println!("ERROR: connectpeer requires peer connection info: `connectpeer pubkey@host:port`"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let (pubkey, peer_addr) = match parse_peer_info(peer_pubkey_and_ip_addr.unwrap().to_string()) { - Ok(info) => info, - Err(e) => { - println!("{:?}", e.into_inner().unwrap()); - print!("> "); io::stdout().flush().unwrap(); continue; - } - }; - if connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone(), - event_notifier.clone(), runtime_handle.clone()).is_ok() { - println!("SUCCESS: connected to peer {}", pubkey); - } - - }, - "listchannels" => list_channels(channel_manager.clone()), - "listpayments" => list_payments(payment_storage.clone()), - "closechannel" => { - let channel_id_str = words.next(); - if channel_id_str.is_none() { - println!("ERROR: closechannel requires a channel ID: `closechannel `"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); - if channel_id_vec.is_none() { - println!("ERROR: couldn't parse channel_id as hex"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let mut channel_id = [0; 32]; - channel_id.copy_from_slice(&channel_id_vec.unwrap()); - close_channel(channel_id, channel_manager.clone()); - }, - "forceclosechannel" => { - let channel_id_str = words.next(); - if channel_id_str.is_none() { - println!("ERROR: forceclosechannel requires a channel ID: `forceclosechannel `"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); - if channel_id_vec.is_none() { - println!("ERROR: couldn't parse channel_id as hex"); - print!("> "); io::stdout().flush().unwrap(); continue; - } - let mut channel_id = [0; 32]; - channel_id.copy_from_slice(&channel_id_vec.unwrap()); - force_close_channel(channel_id, channel_manager.clone()); - } - _ => println!("Unknown command. See `\"help\" for available commands.") - } - } - print!("> "); io::stdout().flush().unwrap(); - - } +pub(crate) fn poll_for_user_input( + peer_manager: Arc, channel_manager: Arc, + router: Arc, Arc>>, + payment_storage: PaymentInfoStorage, node_privkey: SecretKey, event_notifier: mpsc::Sender<()>, + ldk_data_dir: String, logger: Arc, runtime_handle: Handle, network: Network, +) { + println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at /.ldk/logs"); + let stdin = io::stdin(); + print!("> "); + io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print + for line in stdin.lock().lines() { + let _ = event_notifier.try_send(()); + let line = line.unwrap(); + let mut words = line.split_whitespace(); + if let Some(word) = words.next() { + match word { + "help" => help(), + "openchannel" => { + let peer_pubkey_and_ip_addr = words.next(); + let channel_value_sat = words.next(); + if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { + println!("ERROR: openchannel has 2 required arguments: `openchannel pubkey@host:port channel_amt_satoshis` [--public]"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); + let (pubkey, peer_addr) = + match parse_peer_info(peer_pubkey_and_ip_addr.to_string()) { + Ok(info) => info, + Err(e) => { + println!("{:?}", e.into_inner().unwrap()); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + }; + + let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); + if chan_amt_sat.is_err() { + println!("ERROR: channel amount must be a number"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + + if connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + event_notifier.clone(), + runtime_handle.clone(), + ) + .is_err() + { + print!("> "); + io::stdout().flush().unwrap(); + continue; + }; + + // let private_channel = match words.next().as_ref().map(String::as_str) { + let announce_channel = match words.next() { + Some("--public") | Some("--public=true") | Some("--public true") => true, + Some("--public=false") | Some("--public false") => false, + Some(_) => { + println!("ERROR: invalid `--public` command format"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + None => false, + }; + + if open_channel( + pubkey, + chan_amt_sat.unwrap(), + announce_channel, + channel_manager.clone(), + ) + .is_ok() + { + let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); + let _ = disk::persist_channel_peer( + Path::new(&peer_data_path), + peer_pubkey_and_ip_addr, + ); + } + } + "sendpayment" => { + let invoice_str = words.next(); + if invoice_str.is_none() { + println!("ERROR: sendpayment requires an invoice: `sendpayment `"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + + let invoice_res = lightning_invoice::Invoice::from_str(invoice_str.unwrap()); + if invoice_res.is_err() { + println!("ERROR: invalid invoice: {:?}", invoice_res.unwrap_err()); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let invoice = invoice_res.unwrap(); + + let amt_pico_btc = invoice.amount_pico_btc(); + if amt_pico_btc.is_none() { + println!("ERROR: invalid invoice: must contain amount to pay"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let amt_msat = amt_pico_btc.unwrap() / 10; + + let payee_pubkey = invoice.recover_payee_pub_key(); + let final_cltv = *invoice.min_final_cltv_expiry().unwrap_or(&9) as u32; + + let mut payment_hash = PaymentHash([0; 32]); + payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); + + let payment_secret = match invoice.payment_secret() { + Some(secret) => { + let mut payment_secret = PaymentSecret([0; 32]); + payment_secret.0.copy_from_slice(&secret.0); + Some(payment_secret) + } + None => None, + }; + + // rust-lightning-invoice doesn't currently support features, so we parse features + // manually from the invoice. + let mut invoice_features = InvoiceFeatures::empty(); + for field in &invoice.into_signed_raw().raw_invoice().data.tagged_fields { + match field { + lightning_invoice::RawTaggedField::UnknownSemantics(vec) => { + if vec[0] == bech32::u5::try_from_u8(5).unwrap() { + if vec.len() >= 6 && vec[5].to_u8() & 0b10000 != 0 { + invoice_features = + invoice_features.set_variable_length_onion_optional(); + } + if vec.len() >= 6 && vec[5].to_u8() & 0b01000 != 0 { + invoice_features = + invoice_features.set_variable_length_onion_required(); + } + if vec.len() >= 4 && vec[3].to_u8() & 0b00001 != 0 { + invoice_features = + invoice_features.set_payment_secret_optional(); + } + if vec.len() >= 5 && vec[4].to_u8() & 0b10000 != 0 { + invoice_features = + invoice_features.set_payment_secret_required(); + } + if vec.len() >= 4 && vec[3].to_u8() & 0b00100 != 0 { + invoice_features = + invoice_features.set_basic_mpp_optional(); + } + if vec.len() >= 4 && vec[3].to_u8() & 0b00010 != 0 { + invoice_features = + invoice_features.set_basic_mpp_required(); + } + } + } + _ => {} + } + } + let invoice_features_opt = match invoice_features == InvoiceFeatures::empty() { + true => None, + false => Some(invoice_features), + }; + send_payment( + payee_pubkey, + amt_msat, + final_cltv, + payment_hash, + payment_secret, + invoice_features_opt, + router.clone(), + channel_manager.clone(), + payment_storage.clone(), + logger.clone(), + ); + } + "getinvoice" => { + let amt_str = words.next(); + if amt_str.is_none() { + println!("ERROR: getinvoice requires an amount in satoshis"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + + let amt_sat: Result = amt_str.unwrap().parse(); + if amt_sat.is_err() { + println!("ERROR: getinvoice provided payment amount was not a number"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + get_invoice( + amt_sat.unwrap(), + payment_storage.clone(), + node_privkey.clone(), + channel_manager.clone(), + network, + ); + } + "connectpeer" => { + let peer_pubkey_and_ip_addr = words.next(); + if peer_pubkey_and_ip_addr.is_none() { + println!("ERROR: connectpeer requires peer connection info: `connectpeer pubkey@host:port`"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let (pubkey, peer_addr) = + match parse_peer_info(peer_pubkey_and_ip_addr.unwrap().to_string()) { + Ok(info) => info, + Err(e) => { + println!("{:?}", e.into_inner().unwrap()); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + }; + if connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + event_notifier.clone(), + runtime_handle.clone(), + ) + .is_ok() + { + println!("SUCCESS: connected to peer {}", pubkey); + } + } + "listchannels" => list_channels(channel_manager.clone()), + "listpayments" => list_payments(payment_storage.clone()), + "closechannel" => { + let channel_id_str = words.next(); + if channel_id_str.is_none() { + println!("ERROR: closechannel requires a channel ID: `closechannel `"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); + if channel_id_vec.is_none() { + println!("ERROR: couldn't parse channel_id as hex"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let mut channel_id = [0; 32]; + channel_id.copy_from_slice(&channel_id_vec.unwrap()); + close_channel(channel_id, channel_manager.clone()); + } + "forceclosechannel" => { + let channel_id_str = words.next(); + if channel_id_str.is_none() { + println!("ERROR: forceclosechannel requires a channel ID: `forceclosechannel `"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap()); + if channel_id_vec.is_none() { + println!("ERROR: couldn't parse channel_id as hex"); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + let mut channel_id = [0; 32]; + channel_id.copy_from_slice(&channel_id_vec.unwrap()); + force_close_channel(channel_id, channel_manager.clone()); + } + _ => println!("Unknown command. See `\"help\" for available commands."), + } + } + print!("> "); + io::stdout().flush().unwrap(); + } } fn help() { - println!("openchannel pubkey@host:port "); - println!("sendpayment "); - println!("getinvoice "); - println!("connectpeer pubkey@host:port"); - println!("listchannels"); - println!("listpayments"); - println!("closechannel "); - println!("forceclosechannel "); + println!("openchannel pubkey@host:port "); + println!("sendpayment "); + println!("getinvoice "); + println!("connectpeer pubkey@host:port"); + println!("listchannels"); + println!("listpayments"); + println!("closechannel "); + println!("forceclosechannel "); } fn list_channels(channel_manager: Arc) { - print!("["); - for chan_info in channel_manager.list_channels() { - println!(""); - println!("\t{{"); - println!("\t\tchannel_id: {},", hex_utils::hex_str(&chan_info.channel_id[..])); - println!("\t\tpeer_pubkey: {},", hex_utils::hex_str(&chan_info.remote_network_id.serialize())); - let mut pending_channel = false; - match chan_info.short_channel_id { - Some(id) => println!("\t\tshort_channel_id: {},", id), - None => { - pending_channel = true; - } - } - println!("\t\tpending_open: {},", pending_channel); - println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis); - println!("\t\tchannel_can_send_payments: {},", chan_info.is_live); - println!("\t}},"); - } - println!("]"); + print!("["); + for chan_info in channel_manager.list_channels() { + println!(""); + println!("\t{{"); + println!("\t\tchannel_id: {},", hex_utils::hex_str(&chan_info.channel_id[..])); + println!( + "\t\tpeer_pubkey: {},", + hex_utils::hex_str(&chan_info.remote_network_id.serialize()) + ); + let mut pending_channel = false; + match chan_info.short_channel_id { + Some(id) => println!("\t\tshort_channel_id: {},", id), + None => { + pending_channel = true; + } + } + println!("\t\tpending_open: {},", pending_channel); + println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis); + println!("\t\tchannel_can_send_payments: {},", chan_info.is_live); + println!("\t}},"); + } + println!("]"); } fn list_payments(payment_storage: PaymentInfoStorage) { - let payments = payment_storage.lock().unwrap(); - print!("["); - for (payment_hash, payment_info) in payments.deref() { - let direction_str = match payment_info.1 { - HTLCDirection::Inbound => "inbound", - HTLCDirection::Outbound => "outbound", - }; - println!(""); - println!("\t{{"); - println!("\t\tamount_satoshis: {},", payment_info.3); - println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); - println!("\t\thtlc_direction: {},", direction_str); - println!("\t\thtlc_status: {},", match payment_info.2 { - HTLCStatus::Pending => "pending", - HTLCStatus::Succeeded => "succeeded", - HTLCStatus::Failed => "failed" - }); - - - println!("\t}},"); - } - println!("]"); -} - -pub(crate) fn connect_peer_if_necessary(pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: - Arc, event_notifier: mpsc::Sender<()>, runtime: Handle) -> - Result<(), ()> { - for node_pubkey in peer_manager.get_peer_node_ids() { - if node_pubkey == pubkey { - return Ok(()) - } - } - match TcpStream::connect_timeout(&peer_addr, Duration::from_secs(10)) { - Ok(stream) => { - let peer_mgr = peer_manager.clone(); - let event_ntfns = event_notifier.clone(); - runtime.spawn(async move { - lightning_net_tokio::setup_outbound(peer_mgr, event_ntfns, pubkey, stream).await; - }); - let mut peer_connected = false; - while !peer_connected { - for node_pubkey in peer_manager.get_peer_node_ids() { - if node_pubkey == pubkey { peer_connected = true; } - } - } - }, - Err(e) => {println!("ERROR: failed to connect to peer: {:?}", e); - return Err(()) - } - } - Ok(()) + let payments = payment_storage.lock().unwrap(); + print!("["); + for (payment_hash, payment_info) in payments.deref() { + let direction_str = match payment_info.1 { + HTLCDirection::Inbound => "inbound", + HTLCDirection::Outbound => "outbound", + }; + println!(""); + println!("\t{{"); + println!("\t\tamount_satoshis: {},", payment_info.3); + println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); + println!("\t\thtlc_direction: {},", direction_str); + println!( + "\t\thtlc_status: {},", + match payment_info.2 { + HTLCStatus::Pending => "pending", + HTLCStatus::Succeeded => "succeeded", + HTLCStatus::Failed => "failed", + } + ); + + println!("\t}},"); + } + println!("]"); } -fn open_channel(peer_pubkey: PublicKey, channel_amt_sat: u64, channel_manager: Arc) - -> Result<(), ()> { - let mut config = UserConfig::default(); - // lnd's max to_self_delay is 2016, so we want to be compatible. - config.peer_channel_config_limits.their_to_self_delay = 2016; - match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) { - Ok(_) => { - println!("EVENT: initiated channel with peer {}. ", peer_pubkey); - return Ok(()) - }, - Err(e) => { - println!("ERROR: failed to open channel: {:?}", e); - return Err(()) - } - } +pub(crate) fn connect_peer_if_necessary( + pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc, + event_notifier: mpsc::Sender<()>, runtime: Handle, +) -> Result<(), ()> { + for node_pubkey in peer_manager.get_peer_node_ids() { + if node_pubkey == pubkey { + return Ok(()); + } + } + match TcpStream::connect_timeout(&peer_addr, Duration::from_secs(10)) { + Ok(stream) => { + let peer_mgr = peer_manager.clone(); + let event_ntfns = event_notifier.clone(); + runtime.spawn(async move { + lightning_net_tokio::setup_outbound(peer_mgr, event_ntfns, pubkey, stream).await; + }); + let mut peer_connected = false; + while !peer_connected { + for node_pubkey in peer_manager.get_peer_node_ids() { + if node_pubkey == pubkey { + peer_connected = true; + } + } + } + } + Err(e) => { + println!("ERROR: failed to connect to peer: {:?}", e); + return Err(()); + } + } + Ok(()) } -fn send_payment(payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, - payment_secret: Option, payee_features: Option, - router: Arc, Arc>>, - channel_manager: Arc, payment_storage: PaymentInfoStorage, logger: - Arc) { - let network_graph = router.network_graph.read().unwrap(); - let first_hops = channel_manager.list_usable_channels(); - let payer_pubkey = channel_manager.get_our_node_id(); - - let route = router::get_route(&payer_pubkey, &network_graph, &payee, payee_features, - Some(&first_hops.iter().collect::>()), &vec![], amt_msat, - final_cltv, logger); - if let Err(e) = route { - println!("ERROR: failed to find route: {}", e.err); - return - } - let status = match channel_manager.send_payment(&route.unwrap(), payment_hash, &payment_secret) { - Ok(()) => { - println!("EVENT: initiated sending {} msats to {}", amt_msat, payee); - HTLCStatus::Pending - }, - Err(e) => { - println!("ERROR: failed to send payment: {:?}", e); - HTLCStatus::Failed - } - }; - let mut payments = payment_storage.lock().unwrap(); - payments.insert(payment_hash, (None, HTLCDirection::Outbound, status, - SatoshiAmount(Some(amt_msat * 1000)))); +fn open_channel( + peer_pubkey: PublicKey, channel_amt_sat: u64, announce_channel: bool, + channel_manager: Arc, +) -> Result<(), ()> { + let mut config = UserConfig::default(); + if announce_channel { + config.channel_options.announced_channel = true; + } + // lnd's max to_self_delay is 2016, so we want to be compatible. + config.peer_channel_config_limits.their_to_self_delay = 2016; + match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) { + Ok(_) => { + println!("EVENT: initiated channel with peer {}. ", peer_pubkey); + return Ok(()); + } + Err(e) => { + println!("ERROR: failed to open channel: {:?}", e); + return Err(()); + } + } } -fn get_invoice(amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, - channel_manager: Arc, network: Network) { - let mut payments = payment_storage.lock().unwrap(); - let secp_ctx = Secp256k1::new(); - - let mut preimage = [0; 32]; - rand::thread_rng().fill_bytes(&mut preimage); - let payment_hash = Sha256Hash::hash(&preimage); - - - let our_node_pubkey = channel_manager.get_our_node_id(); - let mut invoice = lightning_invoice::InvoiceBuilder::new(match network { - Network::Bitcoin => lightning_invoice::Currency::Bitcoin, - Network::Testnet => lightning_invoice::Currency::BitcoinTestnet, - Network::Regtest => lightning_invoice::Currency::Regtest, - Network::Signet => panic!("Signet invoices not supported") - }) - .payment_hash(payment_hash).description("rust-lightning-bitcoinrpc invoice".to_string()) - .amount_pico_btc(amt_sat * 10_000) - .current_timestamp() - .payee_pub_key(our_node_pubkey); - - // Add route hints to the invoice. - let our_channels = channel_manager.list_usable_channels(); - for channel in our_channels { - let short_channel_id = match channel.short_channel_id { - Some(id) => id.to_be_bytes(), - None => continue - }; - let forwarding_info = match channel.counterparty_forwarding_info { - Some(info) => info, - None => continue, - }; - println!("VMW: adding routehop, info.fee base: {}", forwarding_info.fee_base_msat); - invoice = invoice.route(vec![ - lightning_invoice::RouteHop { - pubkey: channel.remote_network_id, - short_channel_id, - fee_base_msat: forwarding_info.fee_base_msat, - fee_proportional_millionths: forwarding_info.fee_proportional_millionths, - cltv_expiry_delta: forwarding_info.cltv_expiry_delta, - } - ]); - } - - // Sign the invoice. - let invoice = invoice.build_signed(|msg_hash| { - secp_ctx.sign_recoverable(msg_hash, &our_node_privkey) - }); - - match invoice { - Ok(invoice) => println!("SUCCESS: generated invoice: {}", invoice), - Err(e) => println!("ERROR: failed to create invoice: {:?}", e), +fn send_payment( + payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, + payment_secret: Option, payee_features: Option, + router: Arc, Arc>>, + channel_manager: Arc, payment_storage: PaymentInfoStorage, + logger: Arc, +) { + let network_graph = router.network_graph.read().unwrap(); + let first_hops = channel_manager.list_usable_channels(); + let payer_pubkey = channel_manager.get_our_node_id(); + + let route = router::get_route( + &payer_pubkey, + &network_graph, + &payee, + payee_features, + Some(&first_hops.iter().collect::>()), + &vec![], + amt_msat, + final_cltv, + logger, + ); + if let Err(e) = route { + println!("ERROR: failed to find route: {}", e.err); + return; + } + let status = match channel_manager.send_payment(&route.unwrap(), payment_hash, &payment_secret) + { + Ok(()) => { + println!("EVENT: initiated sending {} msats to {}", amt_msat, payee); + HTLCStatus::Pending } + Err(e) => { + println!("ERROR: failed to send payment: {:?}", e); + HTLCStatus::Failed + } + }; + let mut payments = payment_storage.lock().unwrap(); + payments.insert( + payment_hash, + (None, HTLCDirection::Outbound, status, SatoshiAmount(Some(amt_msat / 1000))), + ); +} - payments.insert(PaymentHash(payment_hash.into_inner()), (Some(PaymentPreimage(preimage)), - HTLCDirection::Inbound, - HTLCStatus::Pending, - SatoshiAmount(Some(amt_sat)))); +fn get_invoice( + amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, + channel_manager: Arc, network: Network, +) { + let mut payments = payment_storage.lock().unwrap(); + let secp_ctx = Secp256k1::new(); + + let mut preimage = [0; 32]; + rand::thread_rng().fill_bytes(&mut preimage); + let payment_hash = Sha256Hash::hash(&preimage); + + let our_node_pubkey = channel_manager.get_our_node_id(); + let mut invoice = lightning_invoice::InvoiceBuilder::new(match network { + Network::Bitcoin => lightning_invoice::Currency::Bitcoin, + Network::Testnet => lightning_invoice::Currency::BitcoinTestnet, + Network::Regtest => lightning_invoice::Currency::Regtest, + Network::Signet => panic!("Signet invoices not supported"), + }) + .payment_hash(payment_hash) + .description("rust-lightning-bitcoinrpc invoice".to_string()) + .amount_pico_btc(amt_sat * 10_000) + .current_timestamp() + .payee_pub_key(our_node_pubkey); + + // Add route hints to the invoice. + let our_channels = channel_manager.list_usable_channels(); + for channel in our_channels { + let short_channel_id = match channel.short_channel_id { + Some(id) => id.to_be_bytes(), + None => continue, + }; + let forwarding_info = match channel.counterparty_forwarding_info { + Some(info) => info, + None => continue, + }; + println!("VMW: adding routehop, info.fee base: {}", forwarding_info.fee_base_msat); + invoice = invoice.route(vec![lightning_invoice::RouteHop { + pubkey: channel.remote_network_id, + short_channel_id, + fee_base_msat: forwarding_info.fee_base_msat, + fee_proportional_millionths: forwarding_info.fee_proportional_millionths, + cltv_expiry_delta: forwarding_info.cltv_expiry_delta, + }]); + } + + // Sign the invoice. + let invoice = + invoice.build_signed(|msg_hash| secp_ctx.sign_recoverable(msg_hash, &our_node_privkey)); + + match invoice { + Ok(invoice) => println!("SUCCESS: generated invoice: {}", invoice), + Err(e) => println!("ERROR: failed to create invoice: {:?}", e), + } + + payments.insert( + PaymentHash(payment_hash.into_inner()), + ( + Some(PaymentPreimage(preimage)), + HTLCDirection::Inbound, + HTLCStatus::Pending, + SatoshiAmount(Some(amt_sat)), + ), + ); } fn close_channel(channel_id: [u8; 32], channel_manager: Arc) { - match channel_manager.close_channel(&channel_id) { - Ok(()) => println!("EVENT: initiating channel close"), - Err(e) => println!("ERROR: failed to close channel: {:?}", e) - } + match channel_manager.close_channel(&channel_id) { + Ok(()) => println!("EVENT: initiating channel close"), + Err(e) => println!("ERROR: failed to close channel: {:?}", e), + } } fn force_close_channel(channel_id: [u8; 32], channel_manager: Arc) { - match channel_manager.force_close_channel(&channel_id) { - Ok(()) => println!("EVENT: initiating channel force-close"), - Err(e) => println!("ERROR: failed to force-close channel: {:?}", e) - } + match channel_manager.force_close_channel(&channel_id) { + Ok(()) => println!("EVENT: initiating channel force-close"), + Err(e) => println!("ERROR: failed to force-close channel: {:?}", e), + } } -pub(crate) fn parse_peer_info(peer_pubkey_and_ip_addr: String) -> Result<(PublicKey, SocketAddr), std::io::Error> { - let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@"); - let pubkey = pubkey_and_addr.next(); - let peer_addr_str = pubkey_and_addr.next(); - if peer_addr_str.is_none() || peer_addr_str.is_none() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "ERROR: incorrectly formatted peer - info. Should be formatted as: `pubkey@host:port`")); - } - - let peer_addr: Result = peer_addr_str.unwrap().parse(); - if peer_addr.is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "ERROR: couldn't parse pubkey@host:port into a socket address")); - } - - let pubkey = hex_utils::to_compressed_pubkey(pubkey.unwrap()); - if pubkey.is_none() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "ERROR: unable to parse given pubkey for node")); - } - - Ok((pubkey.unwrap(), peer_addr.unwrap())) +pub(crate) fn parse_peer_info( + peer_pubkey_and_ip_addr: String, +) -> Result<(PublicKey, SocketAddr), std::io::Error> { + let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@"); + let pubkey = pubkey_and_addr.next(); + let peer_addr_str = pubkey_and_addr.next(); + if peer_addr_str.is_none() || peer_addr_str.is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ERROR: incorrectly formatted peer + info. Should be formatted as: `pubkey@host:port`", + )); + } + + let peer_addr: Result = peer_addr_str.unwrap().parse(); + if peer_addr.is_err() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ERROR: couldn't parse pubkey@host:port into a socket address", + )); + } + + let pubkey = hex_utils::to_compressed_pubkey(pubkey.unwrap()); + if pubkey.is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ERROR: unable to parse given pubkey for node", + )); + } + + Ok((pubkey.unwrap(), peer_addr.unwrap())) } diff --git a/src/convert.rs b/src/convert.rs index c92a9268..00ff84dc 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,86 +1,87 @@ -use bitcoin::BlockHash; use bitcoin::hashes::hex::FromHex; +use bitcoin::BlockHash; use lightning_block_sync::http::JsonResponse; use std::convert::TryInto; pub struct FundedTx { - pub changepos: i64, - pub hex: String, + pub changepos: i64, + pub hex: String, } impl TryInto for JsonResponse { - type Error = std::io::Error; - fn try_into(self) -> std::io::Result { - Ok(FundedTx { - changepos: self.0["changepos"].as_i64().unwrap(), - hex: self.0["hex"].as_str().unwrap().to_string(), - }) - } + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(FundedTx { + changepos: self.0["changepos"].as_i64().unwrap(), + hex: self.0["hex"].as_str().unwrap().to_string(), + }) + } } pub struct RawTx(pub String); impl TryInto for JsonResponse { - type Error = std::io::Error; - fn try_into(self) -> std::io::Result { - Ok(RawTx(self.0.as_str().unwrap().to_string())) - } + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(RawTx(self.0.as_str().unwrap().to_string())) + } } pub struct SignedTx { - pub complete: bool, - pub hex: String, + pub complete: bool, + pub hex: String, } impl TryInto for JsonResponse { - type Error = std::io::Error; - fn try_into(self) -> std::io::Result { - Ok(SignedTx { - hex: self.0["hex"].as_str().unwrap().to_string(), - complete: self.0["complete"].as_bool().unwrap(), - }) - } + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(SignedTx { + hex: self.0["hex"].as_str().unwrap().to_string(), + complete: self.0["complete"].as_bool().unwrap(), + }) + } } pub struct NewAddress(pub String); impl TryInto for JsonResponse { - type Error = std::io::Error; - fn try_into(self) -> std::io::Result { - Ok(NewAddress(self.0.as_str().unwrap().to_string())) - } + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(NewAddress(self.0.as_str().unwrap().to_string())) + } } pub struct FeeResponse { - pub feerate: Option, - pub errored: bool, + pub feerate: Option, + pub errored: bool, } impl TryInto for JsonResponse { - type Error = std::io::Error; - fn try_into(self) -> std::io::Result { - let errored = !self.0["errors"].is_null(); - Ok(FeeResponse { - errored, - feerate: match errored { - true => None, - // The feerate from bitcoind is in BTC/kb, and we want satoshis/kb. - false => Some((self.0["feerate"].as_f64().unwrap() * 100_000_000.0).round() as u32) - } - }) - } + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + let errored = !self.0["errors"].is_null(); + Ok(FeeResponse { + errored, + feerate: match errored { + true => None, + // The feerate from bitcoind is in BTC/kb, and we want satoshis/kb. + false => Some((self.0["feerate"].as_f64().unwrap() * 100_000_000.0).round() as u32), + }, + }) + } } pub struct BlockchainInfo { - pub latest_height: usize, - pub latest_blockhash: BlockHash, + pub latest_height: usize, + pub latest_blockhash: BlockHash, } impl TryInto for JsonResponse { - type Error = std::io::Error; - fn try_into(self) -> std::io::Result { - Ok(BlockchainInfo { - latest_height: self.0["blocks"].as_u64().unwrap() as usize, - latest_blockhash: BlockHash::from_hex(self.0["bestblockhash"].as_str().unwrap()).unwrap(), - }) - } + type Error = std::io::Error; + fn try_into(self) -> std::io::Result { + Ok(BlockchainInfo { + latest_height: self.0["blocks"].as_u64().unwrap() as usize, + latest_blockhash: BlockHash::from_hex(self.0["bestblockhash"].as_str().unwrap()) + .unwrap(), + }) + } } diff --git a/src/disk.rs b/src/disk.rs index cd4ea909..13ac5e76 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -1,7 +1,7 @@ -use bitcoin::{BlockHash, Txid}; +use crate::cli; use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::key::PublicKey; -use crate::cli; +use bitcoin::{BlockHash, Txid}; use lightning::chain::channelmonitor::ChannelMonitor; use lightning::chain::keysinterface::{InMemorySigner, KeysManager}; use lightning::chain::transaction::OutPoint; @@ -17,89 +17,113 @@ use std::path::Path; use std::sync::Arc; use time::OffsetDateTime; -pub(crate) struct FilesystemLogger{ - data_dir: String +pub(crate) struct FilesystemLogger { + data_dir: String, } impl FilesystemLogger { - pub(crate) fn new(data_dir: String) -> Self { - let logs_path = format!("{}/logs", data_dir); - fs::create_dir_all(logs_path.clone()).unwrap(); - Self { - data_dir: logs_path - } - } + pub(crate) fn new(data_dir: String) -> Self { + let logs_path = format!("{}/logs", data_dir); + fs::create_dir_all(logs_path.clone()).unwrap(); + Self { data_dir: logs_path } + } } impl Logger for FilesystemLogger { - fn log(&self, record: &Record) { - let raw_log = record.args.to_string(); - let log = format!("{} {:<5} [{}:{}] {}\n", OffsetDateTime::now_utc().format("%F %T"), - record.level.to_string(), record.module_path, record.line, raw_log); - let logs_file_path = format!("{}/logs.txt", self.data_dir.clone()); - fs::OpenOptions::new().create(true).append(true).open(logs_file_path).unwrap() - .write_all(log.as_bytes()).unwrap(); - } + fn log(&self, record: &Record) { + let raw_log = record.args.to_string(); + let log = format!( + "{} {:<5} [{}:{}] {}\n", + OffsetDateTime::now_utc().format("%F %T"), + record.level.to_string(), + record.module_path, + record.line, + raw_log + ); + let logs_file_path = format!("{}/logs.txt", self.data_dir.clone()); + fs::OpenOptions::new() + .create(true) + .append(true) + .open(logs_file_path) + .unwrap() + .write_all(log.as_bytes()) + .unwrap(); + } } pub(crate) fn persist_channel_peer(path: &Path, peer_info: &str) -> std::io::Result<()> { - let mut file = fs::OpenOptions::new().create(true).append(true).open(path)?; - file.write_all(format!("{}\n", peer_info).as_bytes()) + let mut file = fs::OpenOptions::new().create(true).append(true).open(path)?; + file.write_all(format!("{}\n", peer_info).as_bytes()) } -pub(crate) fn read_channel_peer_data(path: &Path) -> Result, std::io::Error> { - let mut peer_data = HashMap::new(); - if !Path::new(&path).exists() { - return Ok(HashMap::new()) - } - let file = File::open(path)?; - let reader = BufReader::new(file); - for line in reader.lines() { - match cli::parse_peer_info(line.unwrap()) { - Ok((pubkey, socket_addr)) => { - peer_data.insert(pubkey, socket_addr); - }, - Err(e) => return Err(e) - } - } - Ok(peer_data) +pub(crate) fn read_channel_peer_data( + path: &Path, +) -> Result, std::io::Error> { + let mut peer_data = HashMap::new(); + if !Path::new(&path).exists() { + return Ok(HashMap::new()); + } + let file = File::open(path)?; + let reader = BufReader::new(file); + for line in reader.lines() { + match cli::parse_peer_info(line.unwrap()) { + Ok((pubkey, socket_addr)) => { + peer_data.insert(pubkey, socket_addr); + } + Err(e) => return Err(e), + } + } + Ok(peer_data) } +pub(crate) fn read_channelmonitors( + path: String, keys_manager: Arc, +) -> Result)>, std::io::Error> { + if !Path::new(&path).exists() { + return Ok(HashMap::new()); + } + let mut outpoint_to_channelmonitor = HashMap::new(); + for file_option in fs::read_dir(path).unwrap() { + let file = file_option.unwrap(); + let owned_file_name = file.file_name(); + let filename = owned_file_name.to_str(); + if !filename.is_some() || !filename.unwrap().is_ascii() || filename.unwrap().len() < 65 { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Invalid ChannelMonitor file name", + )); + } -pub(crate) fn read_channelmonitors(path: String, keys_manager: Arc) -> - Result)>, std::io::Error> -{ - if !Path::new(&path).exists() { - return Ok(HashMap::new()) - } - let mut outpoint_to_channelmonitor = HashMap::new(); - for file_option in fs::read_dir(path).unwrap() { - let file = file_option.unwrap(); - let owned_file_name = file.file_name(); - let filename = owned_file_name.to_str(); - if !filename.is_some() || !filename.unwrap().is_ascii() || filename.unwrap().len() < 65 { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid ChannelMonitor file name")); - } - - let txid = Txid::from_hex(filename.unwrap().split_at(64).0); - if txid.is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx ID in filename")); - } + let txid = Txid::from_hex(filename.unwrap().split_at(64).0); + if txid.is_err() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Invalid tx ID in filename", + )); + } - let index = filename.unwrap().split_at(65).1.split('.').next().unwrap().parse(); - if index.is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "Invalid tx index in filename")); - } + let index = filename.unwrap().split_at(65).1.split('.').next().unwrap().parse(); + if index.is_err() { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Invalid tx index in filename", + )); + } - let contents = fs::read(&file.path())?; + let contents = fs::read(&file.path())?; - if let Ok((blockhash, channel_monitor)) = - <(BlockHash, ChannelMonitor)>::read(&mut Cursor::new(&contents), - &*keys_manager) - { - outpoint_to_channelmonitor.insert(OutPoint { txid: txid.unwrap(), index: index.unwrap() }, - (blockhash, channel_monitor)); - } else { - return Err(std::io::Error::new(std::io::ErrorKind::Other, - "Failed to deserialize ChannelMonitor")); - } - } - Ok(outpoint_to_channelmonitor) + if let Ok((blockhash, channel_monitor)) = + <(BlockHash, ChannelMonitor)>::read( + &mut Cursor::new(&contents), + &*keys_manager, + ) { + outpoint_to_channelmonitor.insert( + OutPoint { txid: txid.unwrap(), index: index.unwrap() }, + (blockhash, channel_monitor), + ); + } else { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to deserialize ChannelMonitor", + )); + } + } + Ok(outpoint_to_channelmonitor) } diff --git a/src/hex_utils.rs b/src/hex_utils.rs index c9c55b9b..70fe28b1 100644 --- a/src/hex_utils.rs +++ b/src/hex_utils.rs @@ -1,42 +1,42 @@ use bitcoin::secp256k1::key::PublicKey; pub fn to_vec(hex: &str) -> Option> { - let mut out = Vec::with_capacity(hex.len() / 2); + let mut out = Vec::with_capacity(hex.len() / 2); - let mut b = 0; - for (idx, c) in hex.as_bytes().iter().enumerate() { - b <<= 4; - match *c { - b'A'..=b'F' => b |= c - b'A' + 10, - b'a'..=b'f' => b |= c - b'a' + 10, - b'0'..=b'9' => b |= c - b'0', - _ => return None, - } - if (idx & 1) == 1 { - out.push(b); - b = 0; - } - } + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', + _ => return None, + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } - Some(out) + Some(out) } #[inline] pub fn hex_str(value: &[u8]) -> String { - let mut res = String::with_capacity(64); - for v in value { - res += &format!("{:02x}", v); - } - res + let mut res = String::with_capacity(64); + for v in value { + res += &format!("{:02x}", v); + } + res } pub fn to_compressed_pubkey(hex: &str) -> Option { - let data = match to_vec(&hex[0..33*2]) { - Some(bytes) => bytes, - None => return None - }; - match PublicKey::from_slice(&data) { - Ok(pk) => Some(pk), - Err(_) => None, - } + let data = match to_vec(&hex[0..33 * 2]) { + Some(bytes) => bytes, + None => return None, + }; + match PublicKey::from_slice(&data) { + Ok(pk) => Some(pk), + Err(_) => None, + } } diff --git a/src/main.rs b/src/main.rs index 37d464a9..0b45f0e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,46 +4,47 @@ mod convert; mod disk; mod hex_utils; -use lightning_background_processor::BackgroundProcessor; -use bitcoin::BlockHash; +use crate::bitcoind_client::BitcoindClient; +use crate::disk::FilesystemLogger; use bitcoin::blockdata::constants::genesis_block; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; -use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; +use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::Secp256k1; +use bitcoin::BlockHash; use bitcoin_bech32::WitnessProgram; -use crate::bitcoind_client::BitcoindClient; -use crate::disk::FilesystemLogger; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning::chain::chainmonitor::ChainMonitor; -use lightning::chain::Filter; use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager}; use lightning::chain::transaction::OutPoint; +use lightning::chain::Filter; use lightning::chain::Watch; use lightning::ln::channelmanager; -use lightning::ln::channelmanager::{ChainParameters, ChannelManagerReadArgs, PaymentHash, PaymentPreimage, - SimpleArcChannelManager}; +use lightning::ln::channelmanager::{ + ChainParameters, ChannelManagerReadArgs, PaymentHash, PaymentPreimage, SimpleArcChannelManager, +}; use lightning::ln::peer_handler::{MessageHandler, SimpleArcPeerManager}; +use lightning::routing::network_graph::NetGraphMsgHandler; use lightning::util::config::UserConfig; use lightning::util::events::{Event, EventsProvider}; use lightning::util::ser::ReadableArgs; -use lightning_block_sync::UnboundedCache; -use lightning_block_sync::SpvClient; +use lightning_background_processor::BackgroundProcessor; use lightning_block_sync::init; use lightning_block_sync::poll; +use lightning_block_sync::SpvClient; +use lightning_block_sync::UnboundedCache; use lightning_net_tokio::SocketDescriptor; use lightning_persister::FilesystemPersister; use rand::{thread_rng, Rng}; -use lightning::routing::network_graph::NetGraphMsgHandler; use std::collections::HashMap; use std::fmt; use std::fs; use std::fs::File; use std::io; -use std::io:: Write; +use std::io::Write; use std::path::Path; use std::sync::{Arc, Mutex}; use std::thread; @@ -53,402 +54,509 @@ use tokio::sync::mpsc; #[derive(PartialEq)] pub(crate) enum HTLCDirection { - Inbound, - Outbound + Inbound, + Outbound, } pub(crate) enum HTLCStatus { - Pending, - Succeeded, - Failed, + Pending, + Succeeded, + Failed, } pub(crate) struct SatoshiAmount(Option); impl fmt::Display for SatoshiAmount { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - Some(amt) => write!(f, "{}", amt), - None => write!(f, "unknown") - - } - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Some(amt) => write!(f, "{}", amt), + None => write!(f, "unknown"), + } + } } -pub(crate) type PaymentInfoStorage = Arc, - HTLCDirection, HTLCStatus, - SatoshiAmount)>>>; - -type ArcChainMonitor = ChainMonitor, Arc, -Arc, Arc, Arc>; - -pub(crate) type PeerManager = SimpleArcPeerManager; - -pub(crate) type ChannelManager = SimpleArcChannelManager; - -fn handle_ldk_events(peer_manager: Arc, channel_manager: Arc, - chain_monitor: Arc, bitcoind_client: Arc, - keys_manager: Arc, payment_storage: PaymentInfoStorage, - network: Network) -{ - let mut pending_txs: HashMap = HashMap::new(); - loop { - peer_manager.process_events(); - let loop_channel_manager = channel_manager.clone(); - let mut events = channel_manager.get_and_clear_pending_events(); - events.append(&mut chain_monitor.get_and_clear_pending_events()); - for event in events { - match event { - Event::FundingGenerationReady { temporary_channel_id, channel_value_satoshis, - output_script, .. } => { - // Construct the raw transaction with one output, that is paid the amount of the - // channel. - let addr = WitnessProgram::from_scriptpubkey(&output_script[..], match network { - Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin, - Network::Testnet => bitcoin_bech32::constants::Network::Testnet, - Network::Regtest => bitcoin_bech32::constants::Network::Regtest, - Network::Signet => panic!("Signet unsupported"), - } - ).expect("Lightning funding tx should always be to a SegWit output").to_address(); - let mut outputs = vec![HashMap::with_capacity(1)]; - outputs[0].insert(addr, channel_value_satoshis as f64 / 100_000_000.0); - let raw_tx = bitcoind_client.create_raw_transaction(outputs); - - // Have your wallet put the inputs into the transaction such that the output is - // satisfied. - let funded_tx = bitcoind_client.fund_raw_transaction(raw_tx); - let change_output_position = funded_tx.changepos; - assert!(change_output_position == 0 || change_output_position == 1); - - // Sign the final funding transaction and broadcast it. - let signed_tx = bitcoind_client.sign_raw_transaction_with_wallet(funded_tx.hex); - assert_eq!(signed_tx.complete, true); - let final_tx: Transaction = encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap(); - let outpoint = OutPoint { - txid: final_tx.txid(), - index: if change_output_position == 0 { 1 } else { 0 } - }; - // Give the funding transaction back to LDK for opening the channel. - loop_channel_manager.funding_transaction_generated(&temporary_channel_id, - outpoint); - pending_txs.insert(outpoint, final_tx); - }, - Event::FundingBroadcastSafe { funding_txo, .. } => { - let funding_tx = pending_txs.remove(&funding_txo).unwrap(); - bitcoind_client.broadcast_transaction(&funding_tx); - println!("\nEVENT: broadcasted funding transaction"); - print!("> "); io::stdout().flush().unwrap(); - }, - Event::PaymentReceived { payment_hash, payment_secret, amt: amt_msat } => { - let mut payments = payment_storage.lock().unwrap(); - if let Some((Some(preimage), _, _, _)) = payments.get(&payment_hash) { - assert!(loop_channel_manager.claim_funds(preimage.clone(), &payment_secret, - amt_msat)); - println!("\nEVENT: received payment from payment_hash {} of {} satoshis", - hex_utils::hex_str(&payment_hash.0), amt_msat / 1000); - print!("> "); io::stdout().flush().unwrap(); - let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); - *status = HTLCStatus::Succeeded; - } else { - println!("\nERROR: we received a payment but didn't know the preimage"); - print!("> "); io::stdout().flush().unwrap(); - loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); - payments.insert(payment_hash, (None, HTLCDirection::Inbound, - HTLCStatus::Failed, SatoshiAmount(None))); - } - }, - Event::PaymentSent { payment_preimage } => { - let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); - let mut payments = payment_storage.lock().unwrap(); - for (payment_hash, (preimage_option, _, status, amt_sat)) in payments.iter_mut() { - if *payment_hash == hashed { - *preimage_option = Some(payment_preimage); - *status = HTLCStatus::Succeeded; - println!("\nNEW EVENT: successfully sent payment of {} satoshis from \ - payment hash {:?} with preimage {:?}", amt_sat, - hex_utils::hex_str(&payment_hash.0), - hex_utils::hex_str(&payment_preimage.0)); - print!("> "); io::stdout().flush().unwrap(); - } - } - }, - Event::PaymentFailed { payment_hash, rejected_by_dest } => { - print!("\nNEW EVENT: Failed to send payment to payment hash {:?}: ", - hex_utils::hex_str(&payment_hash.0)); - if rejected_by_dest { - println!("rejected by destination node"); - } else { - println!("route failed"); - } - print!("> "); io::stdout().flush().unwrap(); - - let mut payments = payment_storage.lock().unwrap(); - if payments.contains_key(&payment_hash) { - let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); - *status = HTLCStatus::Failed; - } - }, - Event::PendingHTLCsForwardable { time_forwardable } => { - let forwarding_channel_manager = loop_channel_manager.clone(); - thread::spawn(move || { - let min = time_forwardable.as_secs(); - let seconds_to_sleep = thread_rng().gen_range(min, min * 5); - thread::sleep(Duration::new(seconds_to_sleep, 0)); - forwarding_channel_manager.process_pending_htlc_forwards(); - }); - }, - Event::SpendableOutputs { outputs } => { - let destination_address = bitcoind_client.get_new_address(); - let output_descriptors = &outputs.iter().map(|a| a).collect::>(); - let tx_feerate = bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); - let spending_tx = keys_manager.spend_spendable_outputs(output_descriptors, - Vec::new(), - destination_address.script_pubkey(), - tx_feerate, &Secp256k1::new()).unwrap(); - bitcoind_client.broadcast_transaction(&spending_tx); - // XXX maybe need to rescan and blah? - } - } - } - thread::sleep(Duration::new(1, 0)); - } +pub(crate) type PaymentInfoStorage = Arc< + Mutex< + HashMap, HTLCDirection, HTLCStatus, SatoshiAmount)>, + >, +>; + +type ArcChainMonitor = ChainMonitor< + InMemorySigner, + Arc, + Arc, + Arc, + Arc, + Arc, +>; + +pub(crate) type PeerManager = SimpleArcPeerManager< + SocketDescriptor, + ArcChainMonitor, + BitcoindClient, + BitcoindClient, + dyn chain::Access, + FilesystemLogger, +>; + +pub(crate) type ChannelManager = + SimpleArcChannelManager; + +fn handle_ldk_events( + peer_manager: Arc, channel_manager: Arc, + chain_monitor: Arc, bitcoind_client: Arc, + keys_manager: Arc, payment_storage: PaymentInfoStorage, network: Network, +) { + let mut pending_txs: HashMap = HashMap::new(); + loop { + peer_manager.process_events(); + let loop_channel_manager = channel_manager.clone(); + let mut events = channel_manager.get_and_clear_pending_events(); + events.append(&mut chain_monitor.get_and_clear_pending_events()); + for event in events { + match event { + Event::FundingGenerationReady { + temporary_channel_id, + channel_value_satoshis, + output_script, + .. + } => { + // Construct the raw transaction with one output, that is paid the amount of the + // channel. + let addr = WitnessProgram::from_scriptpubkey( + &output_script[..], + match network { + Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin, + Network::Testnet => bitcoin_bech32::constants::Network::Testnet, + Network::Regtest => bitcoin_bech32::constants::Network::Regtest, + Network::Signet => panic!("Signet unsupported"), + }, + ) + .expect("Lightning funding tx should always be to a SegWit output") + .to_address(); + let mut outputs = vec![HashMap::with_capacity(1)]; + outputs[0].insert(addr, channel_value_satoshis as f64 / 100_000_000.0); + let raw_tx = bitcoind_client.create_raw_transaction(outputs); + + // Have your wallet put the inputs into the transaction such that the output is + // satisfied. + let funded_tx = bitcoind_client.fund_raw_transaction(raw_tx); + let change_output_position = funded_tx.changepos; + assert!(change_output_position == 0 || change_output_position == 1); + + // Sign the final funding transaction and broadcast it. + let signed_tx = bitcoind_client.sign_raw_transaction_with_wallet(funded_tx.hex); + assert_eq!(signed_tx.complete, true); + let final_tx: Transaction = + encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap(); + let outpoint = OutPoint { + txid: final_tx.txid(), + index: if change_output_position == 0 { 1 } else { 0 }, + }; + // Give the funding transaction back to LDK for opening the channel. + loop_channel_manager + .funding_transaction_generated(&temporary_channel_id, outpoint); + pending_txs.insert(outpoint, final_tx); + } + Event::FundingBroadcastSafe { funding_txo, .. } => { + let funding_tx = pending_txs.remove(&funding_txo).unwrap(); + bitcoind_client.broadcast_transaction(&funding_tx); + println!("\nEVENT: broadcasted funding transaction"); + print!("> "); + io::stdout().flush().unwrap(); + } + Event::PaymentReceived { payment_hash, payment_secret, amt: amt_msat } => { + let mut payments = payment_storage.lock().unwrap(); + if let Some((Some(preimage), _, _, _)) = payments.get(&payment_hash) { + assert!(loop_channel_manager.claim_funds( + preimage.clone(), + &payment_secret, + amt_msat + )); + println!( + "\nEVENT: received payment from payment_hash {} of {} satoshis", + hex_utils::hex_str(&payment_hash.0), + amt_msat / 1000 + ); + print!("> "); + io::stdout().flush().unwrap(); + let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); + *status = HTLCStatus::Succeeded; + } else { + println!("\nERROR: we received a payment but didn't know the preimage"); + print!("> "); + io::stdout().flush().unwrap(); + loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); + payments.insert( + payment_hash, + (None, HTLCDirection::Inbound, HTLCStatus::Failed, SatoshiAmount(None)), + ); + } + } + Event::PaymentSent { payment_preimage } => { + let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); + let mut payments = payment_storage.lock().unwrap(); + for (payment_hash, (preimage_option, _, status, amt_sat)) in payments.iter_mut() + { + if *payment_hash == hashed { + *preimage_option = Some(payment_preimage); + *status = HTLCStatus::Succeeded; + println!( + "\nEVENT: successfully sent payment of {} satoshis from \ + payment hash {:?} with preimage {:?}", + amt_sat, + hex_utils::hex_str(&payment_hash.0), + hex_utils::hex_str(&payment_preimage.0) + ); + print!("> "); + io::stdout().flush().unwrap(); + } + } + } + Event::PaymentFailed { payment_hash, rejected_by_dest } => { + print!( + "\nEVENT: Failed to send payment to payment hash {:?}: ", + hex_utils::hex_str(&payment_hash.0) + ); + if rejected_by_dest { + println!("rejected by destination node"); + } else { + println!("route failed"); + } + print!("> "); + io::stdout().flush().unwrap(); + + let mut payments = payment_storage.lock().unwrap(); + if payments.contains_key(&payment_hash) { + let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); + *status = HTLCStatus::Failed; + } + } + Event::PendingHTLCsForwardable { time_forwardable } => { + let forwarding_channel_manager = loop_channel_manager.clone(); + thread::spawn(move || { + let min = time_forwardable.as_secs(); + let seconds_to_sleep = thread_rng().gen_range(min, min * 5); + thread::sleep(Duration::new(seconds_to_sleep, 0)); + forwarding_channel_manager.process_pending_htlc_forwards(); + }); + } + Event::SpendableOutputs { outputs } => { + let destination_address = bitcoind_client.get_new_address(); + let output_descriptors = &outputs.iter().map(|a| a).collect::>(); + let tx_feerate = + bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + let spending_tx = keys_manager + .spend_spendable_outputs( + output_descriptors, + Vec::new(), + destination_address.script_pubkey(), + tx_feerate, + &Secp256k1::new(), + ) + .unwrap(); + bitcoind_client.broadcast_transaction(&spending_tx); + // XXX maybe need to rescan and blah? + } + } + } + thread::sleep(Duration::new(1, 0)); + } } fn main() { - let args = match cli::parse_startup_args() { - Ok(user_args) => user_args, - Err(()) => return - }; - - // Initialize the LDK data directory if necessary. - let ldk_data_dir = format!("{}/.ldk", args.ldk_storage_dir_path); - fs::create_dir_all(ldk_data_dir.clone()).unwrap(); - - // Initialize our bitcoind client. - let bitcoind_client = match BitcoindClient::new(args.bitcoind_rpc_host.clone(), - args.bitcoind_rpc_port, args.bitcoind_rpc_username.clone(), - args.bitcoind_rpc_password.clone()) { - Ok(client) => Arc::new(client), - Err(e) => { - println!("Failed to connect to bitcoind client: {}", e); - return - } - }; - let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); - - // ## Setup - // Step 1: Initialize the FeeEstimator - - // BitcoindClient implements the FeeEstimator trait, so it'll act as our fee estimator. - let fee_estimator = bitcoind_client.clone(); - - // Step 2: Initialize the Logger - let logger = Arc::new(FilesystemLogger::new(ldk_data_dir.clone())); - - // Step 3: Initialize the BroadcasterInterface - - // BitcoindClient implements the BroadcasterInterface trait, so it'll act as our transaction - // broadcaster. - let broadcaster = bitcoind_client.clone(); - - // Step 4: Initialize Persist - let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone())); - - // Step 5: Initialize the ChainMonitor - let chain_monitor: Arc = Arc::new(ChainMonitor::new(None, broadcaster.clone(), - logger.clone(), fee_estimator.clone(), - persister.clone())); - - // Step 6: Initialize the KeysManager - - // The key seed that we use to derive the node privkey (that corresponds to the node pubkey) and - // other secret key material. - let keys_seed_path = format!("{}/keys_seed", ldk_data_dir.clone()); - let keys_seed = if let Ok(seed) = fs::read(keys_seed_path.clone()) { - assert_eq!(seed.len(), 32); - let mut key = [0; 32]; - key.copy_from_slice(&seed); - key - } else { - let mut key = [0; 32]; - thread_rng().fill_bytes(&mut key); - let mut f = File::create(keys_seed_path).unwrap(); - f.write_all(&key).expect("Failed to write node keys seed to disk"); - f.sync_all().expect("Failed to sync node keys seed to disk"); - key - }; - let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); - let keys_manager = Arc::new(KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos())); - - // Step 7: Read ChannelMonitor state from disk - let monitors_path = format!("{}/monitors", ldk_data_dir.clone()); - let mut outpoint_to_channelmonitor = disk::read_channelmonitors(monitors_path.to_string(), - keys_manager.clone()).unwrap(); - - // Step 9: Initialize the ChannelManager - let user_config = UserConfig::default(); - let runtime = Runtime::new().unwrap(); - let mut restarting_node = true; - let (channel_manager_blockhash, mut channel_manager) = { - if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { - let mut channel_monitor_mut_references = Vec::new(); - for (_, channel_monitor) in outpoint_to_channelmonitor.iter_mut() { - channel_monitor_mut_references.push(&mut channel_monitor.1); - } - let read_args = ChannelManagerReadArgs::new(keys_manager.clone(), fee_estimator.clone(), - chain_monitor.clone(), broadcaster.clone(), - logger.clone(), user_config, - channel_monitor_mut_references); - <(BlockHash, ChannelManager)>::read(&mut f, read_args).unwrap() - } else { // We're starting a fresh node. - restarting_node = false; - let getinfo_resp = bitcoind_client.get_blockchain_info(); - let chain_params = ChainParameters { - network: args.network, - latest_hash: getinfo_resp.latest_blockhash, - latest_height: getinfo_resp.latest_height, - }; - let fresh_channel_manager = channelmanager::ChannelManager::new(fee_estimator.clone(), - chain_monitor.clone(), - broadcaster.clone(), - logger.clone(), - keys_manager.clone(), - user_config, chain_params); - (getinfo_resp.latest_blockhash, fresh_channel_manager) - } - }; - - // Step 10: Sync ChannelMonitors and ChannelManager to chain tip - let mut chain_listener_channel_monitors = Vec::new(); - let mut cache = UnboundedCache::new(); - let mut chain_tip: Option = None; - if restarting_node { - let mut chain_listeners = vec![ - (channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)]; - - for (outpoint, blockhash_and_monitor) in outpoint_to_channelmonitor.drain() { - let blockhash = blockhash_and_monitor.0; - let channel_monitor = blockhash_and_monitor.1; - chain_listener_channel_monitors.push((blockhash, (channel_monitor, - broadcaster.clone(), fee_estimator.clone(), - logger.clone()), outpoint)); - } - - for monitor_listener_info in chain_listener_channel_monitors.iter_mut() { - chain_listeners.push((monitor_listener_info.0, - &mut monitor_listener_info.1 as &mut dyn chain::Listen)); - } - chain_tip = Some(runtime.block_on(init::synchronize_listeners(&mut bitcoind_rpc_client, args.network, - &mut cache, chain_listeners)).unwrap()); - } - - // Step 11: Give ChannelMonitors to ChainMonitor - for item in chain_listener_channel_monitors.drain(..) { - let channel_monitor = item.1.0; - let funding_outpoint = item.2; - chain_monitor.watch_channel(funding_outpoint, channel_monitor).unwrap(); - } - - // Step 13: Optional: Initialize the NetGraphMsgHandler - // XXX persist routing data - let genesis = genesis_block(args.network).header.block_hash(); - let router = Arc::new(NetGraphMsgHandler::new(genesis, None::>, logger.clone())); - - // Step 14: Initialize the PeerManager - let channel_manager: Arc = Arc::new(channel_manager); - let mut ephemeral_bytes = [0; 32]; - rand::thread_rng().fill_bytes(&mut ephemeral_bytes); - let lightning_msg_handler = MessageHandler { chan_handler: channel_manager.clone(), - route_handler: router.clone() }; - let peer_manager: Arc = Arc::new(PeerManager::new(lightning_msg_handler, - keys_manager.get_node_secret(), - &ephemeral_bytes, logger.clone())); - - // ## Running LDK - // Step 16: Initialize Peer Connection Handling - - // We poll for events in handle_ldk_events(..) rather than waiting for them over the - // mpsc::channel, so we can leave the event receiver as unused. - let (event_ntfn_sender, mut _event_ntfn_receiver) = mpsc::channel(2); - let peer_manager_connection_handler = peer_manager.clone(); - let event_notifier = event_ntfn_sender.clone(); - let listening_port = args.ldk_peer_listening_port; - runtime.spawn(async move { - let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", listening_port)).unwrap(); - loop { - let tcp_stream = listener.accept().unwrap().0; - lightning_net_tokio::setup_inbound(peer_manager_connection_handler.clone(), - event_notifier.clone(), tcp_stream).await; - } - }); - - // Step 17: Connect and Disconnect Blocks - if chain_tip.is_none() { - chain_tip = Some(runtime.block_on(init::validate_best_block_header(&mut bitcoind_rpc_client)).unwrap()); - } - let channel_manager_listener = channel_manager.clone(); - let chain_monitor_listener = chain_monitor.clone(); - let network = args.network; - runtime.spawn(async move { - let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, network); - let chain_listener = (chain_monitor_listener, channel_manager_listener); - let mut spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, - &chain_listener); - loop { - spv_client.poll_best_tip().await.unwrap(); - thread::sleep(Duration::new(1, 0)); - } - }); - - // Step 17 & 18: Initialize ChannelManager persistence & Once Per Minute: ChannelManager's - // timer_chan_freshness_every_min() and PeerManager's timer_tick_occurred - let runtime_handle = runtime.handle(); - let data_dir = ldk_data_dir.clone(); - let persist_channel_manager_callback = move |node: &ChannelManager| { - FilesystemPersister::persist_manager(data_dir.clone(), &*node) - }; - BackgroundProcessor::start(persist_channel_manager_callback, channel_manager.clone(), - logger.clone()); - - let peer_manager_processor = peer_manager.clone(); - runtime_handle.spawn(async move { - loop { - peer_manager_processor.timer_tick_occurred(); - thread::sleep(Duration::new(60, 0)); - } - }); - - // Step 15: Initialize LDK Event Handling - let peer_manager_event_listener = peer_manager.clone(); - let channel_manager_event_listener = channel_manager.clone(); - let chain_monitor_event_listener = chain_monitor.clone(); - let keys_manager_listener = keys_manager.clone(); - let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); - let payment_info_for_events = payment_info.clone(); - let handle = runtime_handle.clone(); - let network = args.network; - thread::spawn(move || { - handle_ldk_events(peer_manager_event_listener, channel_manager_event_listener, - chain_monitor_event_listener, bitcoind_client.clone(), - keys_manager_listener, payment_info_for_events, network); - }); - - // Reconnect to channel peers if possible. - let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); - match disk::read_channel_peer_data(Path::new(&peer_data_path)) { - Ok(mut info) => { - for (pubkey, peer_addr) in info.drain() { - let _ = cli::connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone(), - event_ntfn_sender.clone(), handle.clone()); - } - }, - Err(e) => println!("ERROR: errored reading channel peer info from disk: {:?}", e), - } - - // Start the CLI. - cli::poll_for_user_input(peer_manager.clone(), channel_manager.clone(), router.clone(), - payment_info, keys_manager.get_node_secret(), event_ntfn_sender, - ldk_data_dir.clone(), logger.clone(), handle, args.network); + let args = match cli::parse_startup_args() { + Ok(user_args) => user_args, + Err(()) => return, + }; + + // Initialize the LDK data directory if necessary. + let ldk_data_dir = format!("{}/.ldk", args.ldk_storage_dir_path); + fs::create_dir_all(ldk_data_dir.clone()).unwrap(); + + // Initialize our bitcoind client. + let bitcoind_client = match BitcoindClient::new( + args.bitcoind_rpc_host.clone(), + args.bitcoind_rpc_port, + args.bitcoind_rpc_username.clone(), + args.bitcoind_rpc_password.clone(), + ) { + Ok(client) => Arc::new(client), + Err(e) => { + println!("Failed to connect to bitcoind client: {}", e); + return; + } + }; + let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); + + // ## Setup + // Step 1: Initialize the FeeEstimator + + // BitcoindClient implements the FeeEstimator trait, so it'll act as our fee estimator. + let fee_estimator = bitcoind_client.clone(); + + // Step 2: Initialize the Logger + let logger = Arc::new(FilesystemLogger::new(ldk_data_dir.clone())); + + // Step 3: Initialize the BroadcasterInterface + + // BitcoindClient implements the BroadcasterInterface trait, so it'll act as our transaction + // broadcaster. + let broadcaster = bitcoind_client.clone(); + + // Step 4: Initialize Persist + let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone())); + + // Step 5: Initialize the ChainMonitor + let chain_monitor: Arc = Arc::new(ChainMonitor::new( + None, + broadcaster.clone(), + logger.clone(), + fee_estimator.clone(), + persister.clone(), + )); + + // Step 6: Initialize the KeysManager + + // The key seed that we use to derive the node privkey (that corresponds to the node pubkey) and + // other secret key material. + let keys_seed_path = format!("{}/keys_seed", ldk_data_dir.clone()); + let keys_seed = if let Ok(seed) = fs::read(keys_seed_path.clone()) { + assert_eq!(seed.len(), 32); + let mut key = [0; 32]; + key.copy_from_slice(&seed); + key + } else { + let mut key = [0; 32]; + thread_rng().fill_bytes(&mut key); + let mut f = File::create(keys_seed_path).unwrap(); + f.write_all(&key).expect("Failed to write node keys seed to disk"); + f.sync_all().expect("Failed to sync node keys seed to disk"); + key + }; + let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let keys_manager = Arc::new(KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos())); + + // Step 7: Read ChannelMonitor state from disk + let monitors_path = format!("{}/monitors", ldk_data_dir.clone()); + let mut outpoint_to_channelmonitor = + disk::read_channelmonitors(monitors_path.to_string(), keys_manager.clone()).unwrap(); + + // Step 9: Initialize the ChannelManager + let user_config = UserConfig::default(); + let runtime = Runtime::new().unwrap(); + let mut restarting_node = true; + let (channel_manager_blockhash, mut channel_manager) = { + if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { + let mut channel_monitor_mut_references = Vec::new(); + for (_, channel_monitor) in outpoint_to_channelmonitor.iter_mut() { + channel_monitor_mut_references.push(&mut channel_monitor.1); + } + let read_args = ChannelManagerReadArgs::new( + keys_manager.clone(), + fee_estimator.clone(), + chain_monitor.clone(), + broadcaster.clone(), + logger.clone(), + user_config, + channel_monitor_mut_references, + ); + <(BlockHash, ChannelManager)>::read(&mut f, read_args).unwrap() + } else { + // We're starting a fresh node. + restarting_node = false; + let getinfo_resp = bitcoind_client.get_blockchain_info(); + let chain_params = ChainParameters { + network: args.network, + latest_hash: getinfo_resp.latest_blockhash, + latest_height: getinfo_resp.latest_height, + }; + let fresh_channel_manager = channelmanager::ChannelManager::new( + fee_estimator.clone(), + chain_monitor.clone(), + broadcaster.clone(), + logger.clone(), + keys_manager.clone(), + user_config, + chain_params, + ); + (getinfo_resp.latest_blockhash, fresh_channel_manager) + } + }; + + // Step 10: Sync ChannelMonitors and ChannelManager to chain tip + let mut chain_listener_channel_monitors = Vec::new(); + let mut cache = UnboundedCache::new(); + let mut chain_tip: Option = None; + if restarting_node { + let mut chain_listeners = + vec![(channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)]; + + for (outpoint, blockhash_and_monitor) in outpoint_to_channelmonitor.drain() { + let blockhash = blockhash_and_monitor.0; + let channel_monitor = blockhash_and_monitor.1; + chain_listener_channel_monitors.push(( + blockhash, + (channel_monitor, broadcaster.clone(), fee_estimator.clone(), logger.clone()), + outpoint, + )); + } + + for monitor_listener_info in chain_listener_channel_monitors.iter_mut() { + chain_listeners.push(( + monitor_listener_info.0, + &mut monitor_listener_info.1 as &mut dyn chain::Listen, + )); + } + chain_tip = Some( + runtime + .block_on(init::synchronize_listeners( + &mut bitcoind_rpc_client, + args.network, + &mut cache, + chain_listeners, + )) + .unwrap(), + ); + } + + // Step 11: Give ChannelMonitors to ChainMonitor + for item in chain_listener_channel_monitors.drain(..) { + let channel_monitor = item.1 .0; + let funding_outpoint = item.2; + chain_monitor.watch_channel(funding_outpoint, channel_monitor).unwrap(); + } + + // Step 13: Optional: Initialize the NetGraphMsgHandler + // XXX persist routing data + let genesis = genesis_block(args.network).header.block_hash(); + let router = + Arc::new(NetGraphMsgHandler::new(genesis, None::>, logger.clone())); + + // Step 14: Initialize the PeerManager + let channel_manager: Arc = Arc::new(channel_manager); + let mut ephemeral_bytes = [0; 32]; + rand::thread_rng().fill_bytes(&mut ephemeral_bytes); + let lightning_msg_handler = + MessageHandler { chan_handler: channel_manager.clone(), route_handler: router.clone() }; + let peer_manager: Arc = Arc::new(PeerManager::new( + lightning_msg_handler, + keys_manager.get_node_secret(), + &ephemeral_bytes, + logger.clone(), + )); + + // ## Running LDK + // Step 16: Initialize Peer Connection Handling + + // We poll for events in handle_ldk_events(..) rather than waiting for them over the + // mpsc::channel, so we can leave the event receiver as unused. + let (event_ntfn_sender, mut _event_ntfn_receiver) = mpsc::channel(2); + let peer_manager_connection_handler = peer_manager.clone(); + let event_notifier = event_ntfn_sender.clone(); + let listening_port = args.ldk_peer_listening_port; + runtime.spawn(async move { + let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", listening_port)).unwrap(); + loop { + let tcp_stream = listener.accept().unwrap().0; + lightning_net_tokio::setup_inbound( + peer_manager_connection_handler.clone(), + event_notifier.clone(), + tcp_stream, + ) + .await; + } + }); + + // Step 17: Connect and Disconnect Blocks + if chain_tip.is_none() { + chain_tip = Some( + runtime.block_on(init::validate_best_block_header(&mut bitcoind_rpc_client)).unwrap(), + ); + } + let channel_manager_listener = channel_manager.clone(); + let chain_monitor_listener = chain_monitor.clone(); + let network = args.network; + runtime.spawn(async move { + let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, network); + let chain_listener = (chain_monitor_listener, channel_manager_listener); + let mut spv_client = + SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); + loop { + spv_client.poll_best_tip().await.unwrap(); + thread::sleep(Duration::new(1, 0)); + } + }); + + // Step 17 & 18: Initialize ChannelManager persistence & Once Per Minute: ChannelManager's + // timer_chan_freshness_every_min() and PeerManager's timer_tick_occurred + let runtime_handle = runtime.handle(); + let data_dir = ldk_data_dir.clone(); + let persist_channel_manager_callback = + move |node: &ChannelManager| FilesystemPersister::persist_manager(data_dir.clone(), &*node); + BackgroundProcessor::start( + persist_channel_manager_callback, + channel_manager.clone(), + logger.clone(), + ); + + let peer_manager_processor = peer_manager.clone(); + runtime_handle.spawn(async move { + loop { + peer_manager_processor.timer_tick_occurred(); + thread::sleep(Duration::new(60, 0)); + } + }); + + // Step 15: Initialize LDK Event Handling + let peer_manager_event_listener = peer_manager.clone(); + let channel_manager_event_listener = channel_manager.clone(); + let chain_monitor_event_listener = chain_monitor.clone(); + let keys_manager_listener = keys_manager.clone(); + let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); + let payment_info_for_events = payment_info.clone(); + let handle = runtime_handle.clone(); + let network = args.network; + thread::spawn(move || { + handle_ldk_events( + peer_manager_event_listener, + channel_manager_event_listener, + chain_monitor_event_listener, + bitcoind_client.clone(), + keys_manager_listener, + payment_info_for_events, + network, + ); + }); + + // Reconnect to channel peers if possible. + let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); + match disk::read_channel_peer_data(Path::new(&peer_data_path)) { + Ok(mut info) => { + for (pubkey, peer_addr) in info.drain() { + let _ = cli::connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + event_ntfn_sender.clone(), + handle.clone(), + ); + } + } + Err(e) => println!("ERROR: errored reading channel peer info from disk: {:?}", e), + } + + // Start the CLI. + cli::poll_for_user_input( + peer_manager.clone(), + channel_manager.clone(), + router.clone(), + payment_info, + keys_manager.get_node_secret(), + event_ntfn_sender, + ldk_data_dir.clone(), + logger.clone(), + handle, + args.network, + ); } From ac0678c9a3fb7c0bcdb31ab249fe058fe9e56af1 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 19 Mar 2021 18:37:28 -0400 Subject: [PATCH 08/22] working before async refactor --- src/cli.rs | 6 +++--- src/main.rs | 30 ++++++++++++++++-------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 21c40236..ca1b84fe 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -160,10 +160,10 @@ pub(crate) fn poll_for_user_input( // let private_channel = match words.next().as_ref().map(String::as_str) { let announce_channel = match words.next() { - Some("--public") | Some("--public=true") | Some("--public true") => true, - Some("--public=false") | Some("--public false") => false, + Some("--public") | Some("--public=true") => true, + Some("--public=false") => false, Some(_) => { - println!("ERROR: invalid `--public` command format"); + println!("ERROR: invalid `--public` command format. Valid formats: `--public`, `--public=true` `--public=false`"); print!("> "); io::stdout().flush().unwrap(); continue; diff --git a/src/main.rs b/src/main.rs index 0b45f0e6..529e1705 100644 --- a/src/main.rs +++ b/src/main.rs @@ -484,7 +484,7 @@ fn main() { SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); loop { spv_client.poll_best_tip().await.unwrap(); - thread::sleep(Duration::new(1, 0)); + tokio::time::sleep(Duration::from_secs(1)).await; } }); @@ -501,11 +501,9 @@ fn main() { ); let peer_manager_processor = peer_manager.clone(); - runtime_handle.spawn(async move { - loop { - peer_manager_processor.timer_tick_occurred(); - thread::sleep(Duration::new(60, 0)); - } + thread::spawn(move || loop { + peer_manager_processor.timer_tick_occurred(); + thread::sleep(Duration::new(60, 0)); }); // Step 15: Initialize LDK Event Handling @@ -515,7 +513,6 @@ fn main() { let keys_manager_listener = keys_manager.clone(); let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); let payment_info_for_events = payment_info.clone(); - let handle = runtime_handle.clone(); let network = args.network; thread::spawn(move || { handle_ldk_events( @@ -530,17 +527,22 @@ fn main() { }); // Reconnect to channel peers if possible. + let handle = runtime_handle.clone(); let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); match disk::read_channel_peer_data(Path::new(&peer_data_path)) { Ok(mut info) => { for (pubkey, peer_addr) in info.drain() { - let _ = cli::connect_peer_if_necessary( - pubkey, - peer_addr, - peer_manager.clone(), - event_ntfn_sender.clone(), - handle.clone(), - ); + for chan_info in channel_manager.list_channels() { + if pubkey == chan_info.remote_network_id { + let _ = cli::connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + event_ntfn_sender.clone(), + handle.clone(), + ); + } + } } } Err(e) => println!("ERROR: errored reading channel peer info from disk: {:?}", e), From b630ab9aecd9cae3ca264aaafb1af70ce81de0d7 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 24 Mar 2021 16:03:57 -0400 Subject: [PATCH 09/22] Initial refactor to add tokio::main and be fully async --- Cargo.lock | 5 - Cargo.toml | 15 +- src/bitcoind_client.rs | 326 ++++++++++++++++++++++++++++++----------- src/cli.rs | 11 +- src/convert.rs | 13 +- src/main.rs | 131 ++++++++++------- 6 files changed, 340 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa92a5ec..7eec1706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,6 @@ checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" [[package]] name = "lightning" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", ] @@ -264,7 +263,6 @@ dependencies = [ [[package]] name = "lightning-background-processor" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "lightning", @@ -274,7 +272,6 @@ dependencies = [ [[package]] name = "lightning-block-sync" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "chunked_transfer", @@ -298,7 +295,6 @@ dependencies = [ [[package]] name = "lightning-net-tokio" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "lightning", @@ -308,7 +304,6 @@ dependencies = [ [[package]] name = "lightning-persister" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=32f6205848806a3b2876a2ae36b1db7d5fa22f7d#32f6205848806a3b2876a2ae36b1db7d5fa22f7d" dependencies = [ "bitcoin", "libc", diff --git a/Cargo.toml b/Cargo.toml index 1dd17c01..0b8434ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,17 +8,22 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +# lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-background-processor = { path = "../rust-lightning/background-processor" } base64 = "0.13.0" bitcoin = "0.26" bitcoin-bech32 = "0.7" bech32 = "0.7" hex = "0.3" -lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +# lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning = { path = "../rust-lightning/lightning" } +# lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" } -lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +# lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } +# lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } +lightning-persister = { path = "../rust-lightning/lightning-persister" } time = "0.2" rand = "0.4" serde_json = { version = "1.0" } diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index f18cd407..48d54f82 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -1,45 +1,176 @@ use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx}; use base64; +use bitcoin::blockdata::block::Block; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode; +use bitcoin::hash_types::BlockHash; use bitcoin::util::address::Address; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning_block_sync::http::HttpEndpoint; use lightning_block_sync::rpc::RpcClient; +use lightning_block_sync::{AsyncBlockSourceResult, BlockHeaderData, BlockSource}; use serde_json; use std::collections::HashMap; use std::str::FromStr; -use std::sync::Mutex; -use tokio::runtime::{Handle, Runtime}; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::Mutex; pub struct BitcoindClient { - bitcoind_rpc_client: Mutex, + bitcoind_rpc_client: Arc>, host: String, port: u16, rpc_user: String, rpc_password: String, - runtime: Mutex, + fees: Arc>, +} + +#[derive(Clone, Eq, Hash, PartialEq)] +pub enum Target { + Background, + Normal, + HighPriority, +} + +impl BlockSource for &BitcoindClient { + fn get_header<'a>( + &'a mut self, header_hash: &'a BlockHash, height_hint: Option, + ) -> AsyncBlockSourceResult<'a, BlockHeaderData> { + Box::pin(async move { + let mut rpc = self.bitcoind_rpc_client.lock().await; + rpc.get_header(header_hash, height_hint).await + }) + } + + fn get_block<'a>( + &'a mut self, header_hash: &'a BlockHash, + ) -> AsyncBlockSourceResult<'a, Block> { + Box::pin(async move { + let mut rpc = self.bitcoind_rpc_client.lock().await; + rpc.get_block(header_hash).await + }) + } + + fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<(BlockHash, Option)> { + Box::pin(async move { + let mut rpc = self.bitcoind_rpc_client.lock().await; + rpc.get_best_block().await + }) + } } impl BitcoindClient { - pub fn new( + pub async fn new( host: String, port: u16, rpc_user: String, rpc_password: String, ) -> std::io::Result { let http_endpoint = HttpEndpoint::for_host(host.clone()).with_port(port); let rpc_credentials = base64::encode(format!("{}:{}", rpc_user.clone(), rpc_password.clone())); let bitcoind_rpc_client = RpcClient::new(&rpc_credentials, http_endpoint)?; + let mut fees: HashMap = HashMap::new(); + fees.insert(Target::Background, AtomicU32::new(253)); + fees.insert(Target::Normal, AtomicU32::new(2000)); + fees.insert(Target::HighPriority, AtomicU32::new(5000)); let client = Self { - bitcoind_rpc_client: Mutex::new(bitcoind_rpc_client), + bitcoind_rpc_client: Arc::new(Mutex::new(bitcoind_rpc_client)), host, port, rpc_user, rpc_password, - runtime: Mutex::new(Runtime::new().unwrap()), + fees: Arc::new(fees), }; + BitcoindClient::poll_for_fee_estimates( + client.fees.clone(), + client.bitcoind_rpc_client.clone(), + ) + .await; Ok(client) } + async fn poll_for_fee_estimates( + fees: Arc>, rpc_client: Arc>, + ) { + tokio::spawn(async move { + loop { + let background_estimate = { + let mut rpc = rpc_client.lock().await; + let background_conf_target = serde_json::json!(144); + let background_estimate_mode = serde_json::json!("ECONOMICAL"); + let resp = rpc + .call_method::( + "estimatesmartfee", + &vec![background_conf_target, background_estimate_mode], + ) + .await + .unwrap(); + match resp.feerate { + Some(fee) => fee, + None => 253, + } + }; + // if background_estimate. + + let normal_estimate = { + let mut rpc = rpc_client.lock().await; + let normal_conf_target = serde_json::json!(18); + let normal_estimate_mode = serde_json::json!("ECONOMICAL"); + let resp = rpc + .call_method::( + "estimatesmartfee", + &vec![normal_conf_target, normal_estimate_mode], + ) + .await + .unwrap(); + match resp.feerate { + Some(fee) => fee, + None => 2000, + } + }; + + let high_prio_estimate = { + let mut rpc = rpc_client.lock().await; + let high_prio_conf_target = serde_json::json!(6); + let high_prio_estimate_mode = serde_json::json!("CONSERVATIVE"); + let resp = rpc + .call_method::( + "estimatesmartfee", + &vec![high_prio_conf_target, high_prio_estimate_mode], + ) + .await + .unwrap(); + + match resp.feerate { + Some(fee) => fee, + None => 5000, + } + }; + + fees.get(&Target::Background) + .unwrap() + .store(background_estimate, Ordering::Release); + fees.get(&Target::Normal).unwrap().store(normal_estimate, Ordering::Release); + fees.get(&Target::HighPriority) + .unwrap() + .store(high_prio_estimate, Ordering::Release); + // match fees.get(Target::Background) { + // Some(fee) => fee.store(background_estimate, Ordering::Release), + // None => + // } + // if let Some(fee) = background_estimate.feerate { + // fees.get("background").unwrap().store(fee, Ordering::Release); + // } + // if let Some(fee) = normal_estimate.feerate { + // fees.get("normal").unwrap().store(fee, Ordering::Release); + // } + // if let Some(fee) = high_prio_estimate.feerate { + // fees.get("high_prio").unwrap().store(fee, Ordering::Release); + // } + tokio::time::sleep(Duration::from_secs(60)).await; + } + }); + } + pub fn get_new_rpc_client(&self) -> std::io::Result { let http_endpoint = HttpEndpoint::for_host(self.host.clone()).with_port(self.port); let rpc_credentials = @@ -47,116 +178,137 @@ impl BitcoindClient { RpcClient::new(&rpc_credentials, http_endpoint) } - pub fn create_raw_transaction(&self, outputs: Vec>) -> RawTx { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + pub async fn create_raw_transaction(&self, outputs: Vec>) -> RawTx { + let mut rpc = self.bitcoind_rpc_client.lock().await; let outputs_json = serde_json::json!(outputs); - runtime - .block_on(rpc.call_method::( - "createrawtransaction", - &vec![serde_json::json!([]), outputs_json], - )) + rpc.call_method::("createrawtransaction", &vec![serde_json::json!([]), outputs_json]) + .await .unwrap() } - pub fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + pub async fn fund_raw_transaction(&self, raw_tx: RawTx) -> FundedTx { + let mut rpc = self.bitcoind_rpc_client.lock().await; + + let raw_tx_json = serde_json::json!(raw_tx.0); + rpc.call_method("fundrawtransaction", &[raw_tx_json]).await.unwrap() + } + + pub async fn send_raw_transaction(&self, raw_tx: RawTx) { + let mut rpc = self.bitcoind_rpc_client.lock().await; let raw_tx_json = serde_json::json!(raw_tx.0); - runtime.block_on(rpc.call_method("fundrawtransaction", &[raw_tx_json])).unwrap() + rpc.call_method::("sendrawtransaction", &[raw_tx_json]).await.unwrap(); } - pub fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + pub async fn sign_raw_transaction_with_wallet(&self, tx_hex: String) -> SignedTx { + let mut rpc = self.bitcoind_rpc_client.lock().await; let tx_hex_json = serde_json::json!(tx_hex); - runtime - .block_on(rpc.call_method("signrawtransactionwithwallet", &vec![tx_hex_json])) - .unwrap() + rpc.call_method("signrawtransactionwithwallet", &vec![tx_hex_json]).await.unwrap() } - pub fn get_new_address(&self) -> Address { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + pub async fn get_new_address(&self) -> Address { + let mut rpc = self.bitcoind_rpc_client.lock().await; let addr_args = vec![serde_json::json!("LDK output address")]; - let addr = - runtime.block_on(rpc.call_method::("getnewaddress", &addr_args)).unwrap(); + let addr = rpc.call_method::("getnewaddress", &addr_args).await.unwrap(); Address::from_str(addr.0.as_str()).unwrap() } - pub fn get_blockchain_info(&self) -> BlockchainInfo { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - runtime.block_on(rpc.call_method::("getblockchaininfo", &vec![])).unwrap() + pub async fn get_blockchain_info(&self) -> BlockchainInfo { + let mut rpc = self.bitcoind_rpc_client.lock().await; + rpc.call_method::("getblockchaininfo", &vec![]).await.unwrap() } } impl FeeEstimator for BitcoindClient { fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 { - let runtime = self.runtime.lock().unwrap(); - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + match confirmation_target { + ConfirmationTarget::Background => { + self.fees.get(&Target::Background).unwrap().load(Ordering::Acquire) + } + ConfirmationTarget::Normal => { + self.fees.get(&Target::Normal).unwrap().load(Ordering::Acquire) + } + ConfirmationTarget::HighPriority => { + self.fees.get(&Target::HighPriority).unwrap().load(Ordering::Acquire) + } + } + // self.fees.g + // 253 + // match confirmation_target { + // ConfirmationTarget::Background => + // } + // let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - let (conf_target, estimate_mode, default) = match confirmation_target { - ConfirmationTarget::Background => (144, "ECONOMICAL", 253), - ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000), - ConfirmationTarget::HighPriority => (6, "ECONOMICAL", 50000), - }; + // let (conf_target, estimate_mode, default) = match confirmation_target { + // ConfirmationTarget::Background => (144, "ECONOMICAL", 253), + // ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000), + // ConfirmationTarget::HighPriority => (6, "CONSERVATIVE", 50000), + // }; - // This function may be called from a tokio runtime, or not. So we need to check before - // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". - let conf_target_json = serde_json::json!(conf_target); - let estimate_mode_json = serde_json::json!(estimate_mode); - let resp = match Handle::try_current() { - Ok(_) => tokio::task::block_in_place(|| { - runtime - .block_on(rpc.call_method::( - "estimatesmartfee", - &vec![conf_target_json, estimate_mode_json], - )) - .unwrap() - }), - _ => runtime - .block_on(rpc.call_method::( - "estimatesmartfee", - &vec![conf_target_json, estimate_mode_json], - )) - .unwrap(), - }; - if resp.errored { - return default; - } - resp.feerate.unwrap() + // // This function may be called from a tokio runtime, or not. So we need to check before + // // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". + // let conf_target_json = serde_json::json!(conf_target); + // let estimate_mode_json = serde_json::json!(estimate_mode); + // let resp = match Handle::try_current() { + // Ok(_) => tokio::task::block_in_place(|| { + // runtime + // .block_on(rpc.call_method::( + // "estimatesmartfee", + // &vec![conf_target_json, estimate_mode_json], + // )) + // .unwrap() + // }), + // _ => runtime + // .block_on(rpc.call_method::( + // "estimatesmartfee", + // &vec![conf_target_json, estimate_mode_json], + // )) + // .unwrap(), + // }; + // if resp.errored { + // return default; + // } + // resp.feerate.unwrap() } } impl BroadcasterInterface for BitcoindClient { fn broadcast_transaction(&self, tx: &Transaction) { - let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - let runtime = self.runtime.lock().unwrap(); - + let bitcoind_rpc_client = self.bitcoind_rpc_client.clone(); let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); - // This function may be called from a tokio runtime, or not. So we need to check before - // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". - match Handle::try_current() { - Ok(_) => { - tokio::task::block_in_place(|| { - runtime - .block_on( - rpc.call_method::("sendrawtransaction", &vec![tx_serialized]), - ) - .unwrap(); - }); - } - _ => { - runtime - .block_on(rpc.call_method::("sendrawtransaction", &vec![tx_serialized])) - .unwrap(); - } - } + tokio::spawn(async move { + let mut rpc = bitcoind_rpc_client.lock().await; + rpc.call_method::("sendrawtransaction", &vec![tx_serialized]).await.unwrap(); + }); + // let bitcoind_rpc_client = self.bitcoind_rpc_client.clone(); + // tokio::spawn(async move { + // let rpc = bitcoind_rpc_client.lock().await; + // rpc.call_method:: + // }); + // let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); + // let runtime = self.runtime.lock().unwrap(); + + // let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); + // // This function may be called from a tokio runtime, or not. So we need to check before + // // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". + // match Handle::try_current() { + // Ok(_) => { + // tokio::task::block_in_place(|| { + // runtime + // .block_on( + // rpc.call_method::("sendrawtransaction", &vec![tx_serialized]), + // ) + // .unwrap(); + // }); + // } + // _ => { + // runtime + // .block_on(rpc.call_method::("sendrawtransaction", &vec![tx_serialized])) + // .unwrap(); + // } + // } } } diff --git a/src/cli.rs b/src/cli.rs index ca1b84fe..56c788fa 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -26,7 +26,6 @@ use std::path::Path; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use tokio::runtime::Handle; use tokio::sync::mpsc; pub(crate) struct LdkUserInfo { @@ -98,11 +97,11 @@ pub(crate) fn parse_startup_args() -> Result { }) } -pub(crate) fn poll_for_user_input( +pub(crate) async fn poll_for_user_input( peer_manager: Arc, channel_manager: Arc, router: Arc, Arc>>, payment_storage: PaymentInfoStorage, node_privkey: SecretKey, event_notifier: mpsc::Sender<()>, - ldk_data_dir: String, logger: Arc, runtime_handle: Handle, network: Network, + ldk_data_dir: String, logger: Arc, network: Network, ) { println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at /.ldk/logs"); let stdin = io::stdin(); @@ -149,7 +148,6 @@ pub(crate) fn poll_for_user_input( peer_addr, peer_manager.clone(), event_notifier.clone(), - runtime_handle.clone(), ) .is_err() { @@ -328,7 +326,6 @@ pub(crate) fn poll_for_user_input( peer_addr, peer_manager.clone(), event_notifier.clone(), - runtime_handle.clone(), ) .is_ok() { @@ -448,7 +445,7 @@ fn list_payments(payment_storage: PaymentInfoStorage) { pub(crate) fn connect_peer_if_necessary( pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc, - event_notifier: mpsc::Sender<()>, runtime: Handle, + event_notifier: mpsc::Sender<()>, ) -> Result<(), ()> { for node_pubkey in peer_manager.get_peer_node_ids() { if node_pubkey == pubkey { @@ -459,7 +456,7 @@ pub(crate) fn connect_peer_if_necessary( Ok(stream) => { let peer_mgr = peer_manager.clone(); let event_ntfns = event_notifier.clone(); - runtime.spawn(async move { + tokio::spawn(async move { lightning_net_tokio::setup_outbound(peer_mgr, event_ntfns, pubkey, stream).await; }); let mut peer_connected = false; diff --git a/src/convert.rs b/src/convert.rs index 00ff84dc..06677bc5 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -52,6 +52,7 @@ impl TryInto for JsonResponse { pub struct FeeResponse { pub feerate: Option, + // pub errors: Array, pub errored: bool, } @@ -61,11 +62,13 @@ impl TryInto for JsonResponse { let errored = !self.0["errors"].is_null(); Ok(FeeResponse { errored, - feerate: match errored { - true => None, - // The feerate from bitcoind is in BTC/kb, and we want satoshis/kb. - false => Some((self.0["feerate"].as_f64().unwrap() * 100_000_000.0).round() as u32), - }, + feerate: match self.0["feerate"].as_f64() { + Some(fee) => Some((fee * 100_000_000.0).round() as u32), + None => None + } + // true => None, + // // The feerate from bitcoind is in BTC/kb, and we want satoshis/kb. + // false => Some((self.0["feerate"].as_f64().unwrap() * 100_000_000.0).round() as u32), }) } } diff --git a/src/main.rs b/src/main.rs index 529e1705..8c5793ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -mod bitcoind_client; +pub mod bitcoind_client; mod cli; mod convert; mod disk; @@ -17,7 +17,7 @@ use bitcoin::BlockHash; use bitcoin_bech32::WitnessProgram; use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; -use lightning::chain::chainmonitor::ChainMonitor; +use lightning::chain::chainmonitor; use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager}; use lightning::chain::transaction::OutPoint; use lightning::chain::Filter; @@ -45,11 +45,10 @@ use std::fs; use std::fs::File; use std::io; use std::io::Write; +use std::ops::Deref; use std::path::Path; use std::sync::{Arc, Mutex}; -use std::thread; use std::time::{Duration, SystemTime}; -use tokio::runtime::Runtime; use tokio::sync::mpsc; #[derive(PartialEq)] @@ -81,7 +80,7 @@ pub(crate) type PaymentInfoStorage = Arc< >, >; -type ArcChainMonitor = ChainMonitor< +type ChainMonitor = chainmonitor::ChainMonitor< InMemorySigner, Arc, Arc, @@ -92,7 +91,7 @@ type ArcChainMonitor = ChainMonitor< pub(crate) type PeerManager = SimpleArcPeerManager< SocketDescriptor, - ArcChainMonitor, + ChainMonitor, BitcoindClient, BitcoindClient, dyn chain::Access, @@ -100,11 +99,11 @@ pub(crate) type PeerManager = SimpleArcPeerManager< >; pub(crate) type ChannelManager = - SimpleArcChannelManager; + SimpleArcChannelManager; -fn handle_ldk_events( +async fn handle_ldk_events( peer_manager: Arc, channel_manager: Arc, - chain_monitor: Arc, bitcoind_client: Arc, + chain_monitor: Arc, bitcoind_client: Arc, keys_manager: Arc, payment_storage: PaymentInfoStorage, network: Network, ) { let mut pending_txs: HashMap = HashMap::new(); @@ -136,16 +135,17 @@ fn handle_ldk_events( .to_address(); let mut outputs = vec![HashMap::with_capacity(1)]; outputs[0].insert(addr, channel_value_satoshis as f64 / 100_000_000.0); - let raw_tx = bitcoind_client.create_raw_transaction(outputs); + let raw_tx = bitcoind_client.create_raw_transaction(outputs).await; // Have your wallet put the inputs into the transaction such that the output is // satisfied. - let funded_tx = bitcoind_client.fund_raw_transaction(raw_tx); + let funded_tx = bitcoind_client.fund_raw_transaction(raw_tx).await; let change_output_position = funded_tx.changepos; assert!(change_output_position == 0 || change_output_position == 1); // Sign the final funding transaction and broadcast it. - let signed_tx = bitcoind_client.sign_raw_transaction_with_wallet(funded_tx.hex); + let signed_tx = + bitcoind_client.sign_raw_transaction_with_wallet(funded_tx.hex).await; assert_eq!(signed_tx.complete, true); let final_tx: Transaction = encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap(); @@ -234,15 +234,16 @@ fn handle_ldk_events( } Event::PendingHTLCsForwardable { time_forwardable } => { let forwarding_channel_manager = loop_channel_manager.clone(); - thread::spawn(move || { + tokio::spawn(async move { let min = time_forwardable.as_secs(); let seconds_to_sleep = thread_rng().gen_range(min, min * 5); - thread::sleep(Duration::new(seconds_to_sleep, 0)); + // thread::sleep(Duration::new(seconds_to_sleep, 0)); + tokio::time::sleep(Duration::from_secs(seconds_to_sleep)).await; forwarding_channel_manager.process_pending_htlc_forwards(); }); } Event::SpendableOutputs { outputs } => { - let destination_address = bitcoind_client.get_new_address(); + let destination_address = bitcoind_client.get_new_address().await; let output_descriptors = &outputs.iter().map(|a| a).collect::>(); let tx_feerate = bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); @@ -260,11 +261,12 @@ fn handle_ldk_events( } } } - thread::sleep(Duration::new(1, 0)); + tokio::time::sleep(Duration::from_secs(1)).await; } } -fn main() { +#[tokio::main] +pub async fn main() { let args = match cli::parse_startup_args() { Ok(user_args) => user_args, Err(()) => return, @@ -275,19 +277,21 @@ fn main() { fs::create_dir_all(ldk_data_dir.clone()).unwrap(); // Initialize our bitcoind client. - let bitcoind_client = match BitcoindClient::new( + let mut bitcoind_client = match BitcoindClient::new( args.bitcoind_rpc_host.clone(), args.bitcoind_rpc_port, args.bitcoind_rpc_username.clone(), args.bitcoind_rpc_password.clone(), - ) { + ) + .await + { Ok(client) => Arc::new(client), Err(e) => { println!("Failed to connect to bitcoind client: {}", e); return; } }; - let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); + // let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); // ## Setup // Step 1: Initialize the FeeEstimator @@ -308,7 +312,7 @@ fn main() { let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone())); // Step 5: Initialize the ChainMonitor - let chain_monitor: Arc = Arc::new(ChainMonitor::new( + let chain_monitor: Arc = Arc::new(chainmonitor::ChainMonitor::new( None, broadcaster.clone(), logger.clone(), @@ -329,9 +333,16 @@ fn main() { } else { let mut key = [0; 32]; thread_rng().fill_bytes(&mut key); - let mut f = File::create(keys_seed_path).unwrap(); - f.write_all(&key).expect("Failed to write node keys seed to disk"); - f.sync_all().expect("Failed to sync node keys seed to disk"); + match File::create(keys_seed_path.clone()) { + Ok(mut f) => { + f.write_all(&key).expect("Failed to write node keys seed to disk"); + f.sync_all().expect("Failed to sync node keys seed to disk"); + } + Err(e) => { + println!("ERROR: Unable to create keys seed file {}: {}", keys_seed_path, e); + return; + } + } key }; let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); @@ -344,7 +355,6 @@ fn main() { // Step 9: Initialize the ChannelManager let user_config = UserConfig::default(); - let runtime = Runtime::new().unwrap(); let mut restarting_node = true; let (channel_manager_blockhash, mut channel_manager) = { if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { @@ -365,7 +375,8 @@ fn main() { } else { // We're starting a fresh node. restarting_node = false; - let getinfo_resp = bitcoind_client.get_blockchain_info(); + let getinfo_resp = bitcoind_client.get_blockchain_info().await; + let chain_params = ChainParameters { network: args.network, latest_hash: getinfo_resp.latest_blockhash, @@ -409,14 +420,15 @@ fn main() { )); } chain_tip = Some( - runtime - .block_on(init::synchronize_listeners( - &mut bitcoind_rpc_client, - args.network, - &mut cache, - chain_listeners, - )) - .unwrap(), + init::synchronize_listeners( + // &mut bitcoind_rpc_client, + &mut bitcoind_client.deref(), + args.network, + &mut cache, + chain_listeners, + ) + .await + .unwrap(), ); } @@ -451,11 +463,11 @@ fn main() { // We poll for events in handle_ldk_events(..) rather than waiting for them over the // mpsc::channel, so we can leave the event receiver as unused. - let (event_ntfn_sender, mut _event_ntfn_receiver) = mpsc::channel(2); + let (event_ntfn_sender, _event_ntfn_receiver) = mpsc::channel(2); let peer_manager_connection_handler = peer_manager.clone(); let event_notifier = event_ntfn_sender.clone(); let listening_port = args.ldk_peer_listening_port; - runtime.spawn(async move { + tokio::spawn(async move { let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", listening_port)).unwrap(); loop { let tcp_stream = listener.accept().unwrap().0; @@ -470,15 +482,21 @@ fn main() { // Step 17: Connect and Disconnect Blocks if chain_tip.is_none() { - chain_tip = Some( - runtime.block_on(init::validate_best_block_header(&mut bitcoind_rpc_client)).unwrap(), - ); + // chain_tip = Some(init::validate_best_block_header(&mut bitcoind_client).await.unwrap()); + chain_tip = + Some(init::validate_best_block_header(&mut bitcoind_client.deref()).await.unwrap()); + // chain_tip = Some(init::validate_best_block_header(&mut bitcoind_rpc_client).await.unwrap()); } let channel_manager_listener = channel_manager.clone(); let chain_monitor_listener = chain_monitor.clone(); + let bitcoind_block_source = bitcoind_client.clone(); let network = args.network; - runtime.spawn(async move { - let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, network); + tokio::spawn(async move { + // let chain_poller = poll::ChainPoller::new(&mut bitcoind_client, network); + let mut derefed = bitcoind_block_source.deref(); + // let chain_poller = poll::ChainPoller::new(&mut bitcoind_block_source.deref(), network); + let chain_poller = poll::ChainPoller::new(&mut derefed, network); + // let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, network); let chain_listener = (chain_monitor_listener, channel_manager_listener); let mut spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); @@ -490,7 +508,6 @@ fn main() { // Step 17 & 18: Initialize ChannelManager persistence & Once Per Minute: ChannelManager's // timer_chan_freshness_every_min() and PeerManager's timer_tick_occurred - let runtime_handle = runtime.handle(); let data_dir = ldk_data_dir.clone(); let persist_channel_manager_callback = move |node: &ChannelManager| FilesystemPersister::persist_manager(data_dir.clone(), &*node); @@ -501,9 +518,11 @@ fn main() { ); let peer_manager_processor = peer_manager.clone(); - thread::spawn(move || loop { - peer_manager_processor.timer_tick_occurred(); - thread::sleep(Duration::new(60, 0)); + tokio::spawn(async move { + loop { + peer_manager_processor.timer_tick_occurred(); + tokio::time::sleep(Duration::from_secs(60)).await; + } }); // Step 15: Initialize LDK Event Handling @@ -514,20 +533,29 @@ fn main() { let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); let payment_info_for_events = payment_info.clone(); let network = args.network; - thread::spawn(move || { + // let bitcoind = BitcoindClient::new( + // args.bitcoind_rpc_host.clone(), + // args.bitcoind_rpc_port, + // args.bitcoind_rpc_username.clone(), + // args.bitcoind_rpc_password.clone(), + // ).await.unwrap(); + let bitcoind_rpc = bitcoind_client.clone(); + tokio::spawn(async move { handle_ldk_events( peer_manager_event_listener, channel_manager_event_listener, chain_monitor_event_listener, - bitcoind_client.clone(), + // bitcoind_client.clone(), + bitcoind_rpc, + // bitcoind, keys_manager_listener, payment_info_for_events, network, - ); + ) + .await; }); // Reconnect to channel peers if possible. - let handle = runtime_handle.clone(); let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); match disk::read_channel_peer_data(Path::new(&peer_data_path)) { Ok(mut info) => { @@ -539,7 +567,6 @@ fn main() { peer_addr, peer_manager.clone(), event_ntfn_sender.clone(), - handle.clone(), ); } } @@ -558,7 +585,7 @@ fn main() { event_ntfn_sender, ldk_data_dir.clone(), logger.clone(), - handle, args.network, - ); + ) + .await; } From 678119951c5b3460d24da64d0f2d755ef4f198e6 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 30 Mar 2021 13:44:17 -0400 Subject: [PATCH 10/22] Post-async refactor cleanup --- src/bitcoind_client.rs | 77 ------------------------------------------ src/cli.rs | 2 -- src/convert.rs | 12 +++---- src/main.rs | 18 +--------- 4 files changed, 5 insertions(+), 104 deletions(-) diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 48d54f82..58510ae7 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -153,19 +153,6 @@ impl BitcoindClient { fees.get(&Target::HighPriority) .unwrap() .store(high_prio_estimate, Ordering::Release); - // match fees.get(Target::Background) { - // Some(fee) => fee.store(background_estimate, Ordering::Release), - // None => - // } - // if let Some(fee) = background_estimate.feerate { - // fees.get("background").unwrap().store(fee, Ordering::Release); - // } - // if let Some(fee) = normal_estimate.feerate { - // fees.get("normal").unwrap().store(fee, Ordering::Release); - // } - // if let Some(fee) = high_prio_estimate.feerate { - // fees.get("high_prio").unwrap().store(fee, Ordering::Release); - // } tokio::time::sleep(Duration::from_secs(60)).await; } }); @@ -235,43 +222,6 @@ impl FeeEstimator for BitcoindClient { self.fees.get(&Target::HighPriority).unwrap().load(Ordering::Acquire) } } - // self.fees.g - // 253 - // match confirmation_target { - // ConfirmationTarget::Background => - // } - // let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - - // let (conf_target, estimate_mode, default) = match confirmation_target { - // ConfirmationTarget::Background => (144, "ECONOMICAL", 253), - // ConfirmationTarget::Normal => (18, "ECONOMICAL", 20000), - // ConfirmationTarget::HighPriority => (6, "CONSERVATIVE", 50000), - // }; - - // // This function may be called from a tokio runtime, or not. So we need to check before - // // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". - // let conf_target_json = serde_json::json!(conf_target); - // let estimate_mode_json = serde_json::json!(estimate_mode); - // let resp = match Handle::try_current() { - // Ok(_) => tokio::task::block_in_place(|| { - // runtime - // .block_on(rpc.call_method::( - // "estimatesmartfee", - // &vec![conf_target_json, estimate_mode_json], - // )) - // .unwrap() - // }), - // _ => runtime - // .block_on(rpc.call_method::( - // "estimatesmartfee", - // &vec![conf_target_json, estimate_mode_json], - // )) - // .unwrap(), - // }; - // if resp.errored { - // return default; - // } - // resp.feerate.unwrap() } } @@ -283,32 +233,5 @@ impl BroadcasterInterface for BitcoindClient { let mut rpc = bitcoind_rpc_client.lock().await; rpc.call_method::("sendrawtransaction", &vec![tx_serialized]).await.unwrap(); }); - // let bitcoind_rpc_client = self.bitcoind_rpc_client.clone(); - // tokio::spawn(async move { - // let rpc = bitcoind_rpc_client.lock().await; - // rpc.call_method:: - // }); - // let mut rpc = self.bitcoind_rpc_client.lock().unwrap(); - // let runtime = self.runtime.lock().unwrap(); - - // let tx_serialized = serde_json::json!(encode::serialize_hex(tx)); - // // This function may be called from a tokio runtime, or not. So we need to check before - // // making the call to avoid the error "cannot run a tokio runtime from within a tokio runtime". - // match Handle::try_current() { - // Ok(_) => { - // tokio::task::block_in_place(|| { - // runtime - // .block_on( - // rpc.call_method::("sendrawtransaction", &vec![tx_serialized]), - // ) - // .unwrap(); - // }); - // } - // _ => { - // runtime - // .block_on(rpc.call_method::("sendrawtransaction", &vec![tx_serialized])) - // .unwrap(); - // } - // } } } diff --git a/src/cli.rs b/src/cli.rs index 56c788fa..95892112 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -156,7 +156,6 @@ pub(crate) async fn poll_for_user_input( continue; }; - // let private_channel = match words.next().as_ref().map(String::as_str) { let announce_channel = match words.next() { Some("--public") | Some("--public=true") => true, Some("--public=false") => false, @@ -577,7 +576,6 @@ fn get_invoice( Some(info) => info, None => continue, }; - println!("VMW: adding routehop, info.fee base: {}", forwarding_info.fee_base_msat); invoice = invoice.route(vec![lightning_invoice::RouteHop { pubkey: channel.remote_network_id, short_channel_id, diff --git a/src/convert.rs b/src/convert.rs index 06677bc5..7dbd8cce 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -52,7 +52,6 @@ impl TryInto for JsonResponse { pub struct FeeResponse { pub feerate: Option, - // pub errors: Array, pub errored: bool, } @@ -62,13 +61,10 @@ impl TryInto for JsonResponse { let errored = !self.0["errors"].is_null(); Ok(FeeResponse { errored, - feerate: match self.0["feerate"].as_f64() { - Some(fee) => Some((fee * 100_000_000.0).round() as u32), - None => None - } - // true => None, - // // The feerate from bitcoind is in BTC/kb, and we want satoshis/kb. - // false => Some((self.0["feerate"].as_f64().unwrap() * 100_000_000.0).round() as u32), + feerate: match self.0["feerate"].as_f64() { + Some(fee) => Some((fee * 100_000_000.0).round() as u32), + None => None, + }, }) } } diff --git a/src/main.rs b/src/main.rs index 8c5793ac..ab950867 100644 --- a/src/main.rs +++ b/src/main.rs @@ -237,7 +237,6 @@ async fn handle_ldk_events( tokio::spawn(async move { let min = time_forwardable.as_secs(); let seconds_to_sleep = thread_rng().gen_range(min, min * 5); - // thread::sleep(Duration::new(seconds_to_sleep, 0)); tokio::time::sleep(Duration::from_secs(seconds_to_sleep)).await; forwarding_channel_manager.process_pending_htlc_forwards(); }); @@ -277,7 +276,7 @@ pub async fn main() { fs::create_dir_all(ldk_data_dir.clone()).unwrap(); // Initialize our bitcoind client. - let mut bitcoind_client = match BitcoindClient::new( + let bitcoind_client = match BitcoindClient::new( args.bitcoind_rpc_host.clone(), args.bitcoind_rpc_port, args.bitcoind_rpc_username.clone(), @@ -291,7 +290,6 @@ pub async fn main() { return; } }; - // let mut bitcoind_rpc_client = bitcoind_client.get_new_rpc_client().unwrap(); // ## Setup // Step 1: Initialize the FeeEstimator @@ -421,7 +419,6 @@ pub async fn main() { } chain_tip = Some( init::synchronize_listeners( - // &mut bitcoind_rpc_client, &mut bitcoind_client.deref(), args.network, &mut cache, @@ -482,21 +479,16 @@ pub async fn main() { // Step 17: Connect and Disconnect Blocks if chain_tip.is_none() { - // chain_tip = Some(init::validate_best_block_header(&mut bitcoind_client).await.unwrap()); chain_tip = Some(init::validate_best_block_header(&mut bitcoind_client.deref()).await.unwrap()); - // chain_tip = Some(init::validate_best_block_header(&mut bitcoind_rpc_client).await.unwrap()); } let channel_manager_listener = channel_manager.clone(); let chain_monitor_listener = chain_monitor.clone(); let bitcoind_block_source = bitcoind_client.clone(); let network = args.network; tokio::spawn(async move { - // let chain_poller = poll::ChainPoller::new(&mut bitcoind_client, network); let mut derefed = bitcoind_block_source.deref(); - // let chain_poller = poll::ChainPoller::new(&mut bitcoind_block_source.deref(), network); let chain_poller = poll::ChainPoller::new(&mut derefed, network); - // let chain_poller = poll::ChainPoller::new(&mut bitcoind_rpc_client, network); let chain_listener = (chain_monitor_listener, channel_manager_listener); let mut spv_client = SpvClient::new(chain_tip.unwrap(), chain_poller, &mut cache, &chain_listener); @@ -533,21 +525,13 @@ pub async fn main() { let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); let payment_info_for_events = payment_info.clone(); let network = args.network; - // let bitcoind = BitcoindClient::new( - // args.bitcoind_rpc_host.clone(), - // args.bitcoind_rpc_port, - // args.bitcoind_rpc_username.clone(), - // args.bitcoind_rpc_password.clone(), - // ).await.unwrap(); let bitcoind_rpc = bitcoind_client.clone(); tokio::spawn(async move { handle_ldk_events( peer_manager_event_listener, channel_manager_event_listener, chain_monitor_event_listener, - // bitcoind_client.clone(), bitcoind_rpc, - // bitcoind, keys_manager_listener, payment_info_for_events, network, From b4a50585c59e578b89c959ed01def10d9def1b39 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 30 Mar 2021 14:56:55 -0400 Subject: [PATCH 11/22] Update to RL master and fix PendingHTLCsForwardable --- Cargo.lock | 5 +++++ Cargo.toml | 20 ++++++++++---------- src/main.rs | 6 +++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7eec1706..1fd4f83e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,7 @@ checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" [[package]] name = "lightning" version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" dependencies = [ "bitcoin", ] @@ -263,6 +264,7 @@ dependencies = [ [[package]] name = "lightning-background-processor" version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" dependencies = [ "bitcoin", "lightning", @@ -272,6 +274,7 @@ dependencies = [ [[package]] name = "lightning-block-sync" version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" dependencies = [ "bitcoin", "chunked_transfer", @@ -295,6 +298,7 @@ dependencies = [ [[package]] name = "lightning-net-tokio" version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" dependencies = [ "bitcoin", "lightning", @@ -304,6 +308,7 @@ dependencies = [ [[package]] name = "lightning-persister" version = "0.0.13" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" dependencies = [ "bitcoin", "libc", diff --git a/Cargo.toml b/Cargo.toml index 0b8434ce..4ce68dff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,22 +8,22 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning-background-processor = { path = "../rust-lightning/background-processor" } +lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +# lightning-background-processor = { path = "../rust-lightning/background-processor" } base64 = "0.13.0" bitcoin = "0.26" bitcoin-bech32 = "0.7" bech32 = "0.7" hex = "0.3" -# lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning = { path = "../rust-lightning/lightning" } -# lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } +lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +# lightning = { path = "../rust-lightning/lightning" } +lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +# lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" } -# lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } -# lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "32f6205848806a3b2876a2ae36b1db7d5fa22f7d" } -lightning-persister = { path = "../rust-lightning/lightning-persister" } +lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +# lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } +lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +# lightning-persister = { path = "../rust-lightning/lightning-persister" } time = "0.2" rand = "0.4" serde_json = { version = "1.0" } diff --git a/src/main.rs b/src/main.rs index ab950867..493a07e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -235,9 +235,9 @@ async fn handle_ldk_events( Event::PendingHTLCsForwardable { time_forwardable } => { let forwarding_channel_manager = loop_channel_manager.clone(); tokio::spawn(async move { - let min = time_forwardable.as_secs(); - let seconds_to_sleep = thread_rng().gen_range(min, min * 5); - tokio::time::sleep(Duration::from_secs(seconds_to_sleep)).await; + let min = time_forwardable.as_millis() as u64; + let millis_to_sleep = thread_rng().gen_range(min, min * 5) as u64; + tokio::time::sleep(Duration::from_millis(millis_to_sleep)).await; forwarding_channel_manager.process_pending_htlc_forwards(); }); } From d85a979e3a03d1e234fb25f676a23736dd3c5e68 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 2 Apr 2021 15:31:07 -0400 Subject: [PATCH 12/22] Remove unnecessary todo --- src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 493a07e5..4b1a8d5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -256,7 +256,6 @@ async fn handle_ldk_events( ) .unwrap(); bitcoind_client.broadcast_transaction(&spending_tx); - // XXX maybe need to rescan and blah? } } } From b17e2989950b4e6f3ac68369b6b18387d760692c Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 2 Apr 2021 15:35:52 -0400 Subject: [PATCH 13/22] Read routehints from invoices when sending a payment --- src/cli.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 95892112..d22f549d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,8 +12,9 @@ use bitcoin::secp256k1::Secp256k1; use lightning::chain; use lightning::ln::channelmanager::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::ln::features::InvoiceFeatures; -use lightning::routing::network_graph::NetGraphMsgHandler; +use lightning::routing::network_graph::{NetGraphMsgHandler, RoutingFees}; use lightning::routing::router; +use lightning::routing::router::RouteHint; use lightning::util::config::UserConfig; use rand; use rand::Rng; @@ -200,6 +201,8 @@ pub(crate) async fn poll_for_user_input( continue; } let invoice = invoice_res.unwrap(); + let route_hints: Vec = + invoice.routes().iter().map(|&route| route.clone()).collect(); let amt_pico_btc = invoice.amount_pico_btc(); if amt_pico_btc.is_none() { @@ -272,6 +275,7 @@ pub(crate) async fn poll_for_user_input( payment_hash, payment_secret, invoice_features_opt, + route_hints, router.clone(), channel_manager.clone(), payment_storage.clone(), @@ -500,6 +504,7 @@ fn open_channel( fn send_payment( payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, payment_secret: Option, payee_features: Option, + mut route_hints: Vec, router: Arc, Arc>>, channel_manager: Arc, payment_storage: PaymentInfoStorage, logger: Arc, @@ -508,13 +513,29 @@ fn send_payment( let first_hops = channel_manager.list_usable_channels(); let payer_pubkey = channel_manager.get_our_node_id(); + let mut hints: Vec = Vec::new(); + for route in route_hints.drain(..) { + let route_hops = route.into_inner(); + let last_hop = &route_hops[route_hops.len() - 1]; + hints.push(RouteHint { + src_node_id: last_hop.pubkey, + short_channel_id: u64::from_be_bytes(last_hop.short_channel_id), + fees: RoutingFees { + base_msat: last_hop.fee_base_msat, + proportional_millionths: last_hop.fee_proportional_millionths, + }, + cltv_expiry_delta: last_hop.cltv_expiry_delta, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }) + } let route = router::get_route( &payer_pubkey, &network_graph, &payee, payee_features, Some(&first_hops.iter().collect::>()), - &vec![], + &hints.iter().collect::>(), amt_msat, final_cltv, logger, From 0a08f4a623389f9e78464bf3649a3a82bd3d5152 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 2 Apr 2021 15:36:17 -0400 Subject: [PATCH 14/22] Include min_final_cltv when generating invoices --- src/cli.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index d22f549d..0144c001 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -588,6 +588,7 @@ fn get_invoice( // Add route hints to the invoice. let our_channels = channel_manager.list_usable_channels(); + let mut min_final_cltv_expiry = 9; for channel in our_channels { let short_channel_id = match channel.short_channel_id { Some(id) => id.to_be_bytes(), @@ -597,6 +598,9 @@ fn get_invoice( Some(info) => info, None => continue, }; + if forwarding_info.cltv_expiry_delta > min_final_cltv_expiry { + min_final_cltv_expiry = forwarding_info.cltv_expiry_delta; + } invoice = invoice.route(vec![lightning_invoice::RouteHop { pubkey: channel.remote_network_id, short_channel_id, @@ -605,6 +609,7 @@ fn get_invoice( cltv_expiry_delta: forwarding_info.cltv_expiry_delta, }]); } + invoice = invoice.min_final_cltv_expiry(min_final_cltv_expiry.into()); // Sign the invoice. let invoice = From 80357570090fdbe4be7e98660d70184173da77f8 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 13 Apr 2021 22:23:47 -0400 Subject: [PATCH 15/22] Move startup to separate from `main` for better errors --- src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4b1a8d5b..5f5dc0af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -263,8 +263,7 @@ async fn handle_ldk_events( } } -#[tokio::main] -pub async fn main() { +async fn start_ldk() { let args = match cli::parse_startup_args() { Ok(user_args) => user_args, Err(()) => return, @@ -572,3 +571,8 @@ pub async fn main() { ) .await; } + +#[tokio::main] +pub async fn main() { + start_ldk().await; +} From 0f8635873858a05cdffee21eb99dd2e100e6cebc Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 13 Apr 2021 22:34:45 -0400 Subject: [PATCH 16/22] Update to latest upstream RL --- Cargo.lock | 10 ++++---- Cargo.toml | 10 ++++---- src/disk.rs | 66 ++--------------------------------------------------- src/main.rs | 39 +++++++++---------------------- 4 files changed, 23 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1fd4f83e..8c606e91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,7 @@ checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" [[package]] name = "lightning" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" dependencies = [ "bitcoin", ] @@ -264,7 +264,7 @@ dependencies = [ [[package]] name = "lightning-background-processor" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" dependencies = [ "bitcoin", "lightning", @@ -274,7 +274,7 @@ dependencies = [ [[package]] name = "lightning-block-sync" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" dependencies = [ "bitcoin", "chunked_transfer", @@ -298,7 +298,7 @@ dependencies = [ [[package]] name = "lightning-net-tokio" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" dependencies = [ "bitcoin", "lightning", @@ -308,7 +308,7 @@ dependencies = [ [[package]] name = "lightning-persister" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=6fcac8bc65ed6d372e0b8c367e9934c754f99ff3#6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" dependencies = [ "bitcoin", "libc", diff --git a/Cargo.toml b/Cargo.toml index 4ce68dff..35bae496 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,21 +8,21 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning-background-processor = { path = "../rust-lightning/background-processor" } base64 = "0.13.0" bitcoin = "0.26" bitcoin-bech32 = "0.7" bech32 = "0.7" hex = "0.3" -lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning = { path = "../rust-lightning/lightning" } -lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" } -lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } -lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "6fcac8bc65ed6d372e0b8c367e9934c754f99ff3" } +lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning-persister = { path = "../rust-lightning/lightning-persister" } time = "0.2" rand = "0.4" diff --git a/src/disk.rs b/src/disk.rs index 13ac5e76..827cb7e9 100644 --- a/src/disk.rs +++ b/src/disk.rs @@ -1,20 +1,13 @@ use crate::cli; -use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::key::PublicKey; -use bitcoin::{BlockHash, Txid}; -use lightning::chain::channelmonitor::ChannelMonitor; -use lightning::chain::keysinterface::{InMemorySigner, KeysManager}; -use lightning::chain::transaction::OutPoint; use lightning::util::logger::{Logger, Record}; -use lightning::util::ser::{ReadableArgs, Writer}; +use lightning::util::ser::Writer; use std::collections::HashMap; use std::fs; use std::fs::File; -// use std::io::{BufRead, BufReader, Cursor, Write}; -use std::io::{BufRead, BufReader, Cursor}; +use std::io::{BufRead, BufReader}; use std::net::SocketAddr; use std::path::Path; -use std::sync::Arc; use time::OffsetDateTime; pub(crate) struct FilesystemLogger { @@ -72,58 +65,3 @@ pub(crate) fn read_channel_peer_data( } Ok(peer_data) } - -pub(crate) fn read_channelmonitors( - path: String, keys_manager: Arc, -) -> Result)>, std::io::Error> { - if !Path::new(&path).exists() { - return Ok(HashMap::new()); - } - let mut outpoint_to_channelmonitor = HashMap::new(); - for file_option in fs::read_dir(path).unwrap() { - let file = file_option.unwrap(); - let owned_file_name = file.file_name(); - let filename = owned_file_name.to_str(); - if !filename.is_some() || !filename.unwrap().is_ascii() || filename.unwrap().len() < 65 { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Invalid ChannelMonitor file name", - )); - } - - let txid = Txid::from_hex(filename.unwrap().split_at(64).0); - if txid.is_err() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Invalid tx ID in filename", - )); - } - - let index = filename.unwrap().split_at(65).1.split('.').next().unwrap().parse(); - if index.is_err() { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Invalid tx index in filename", - )); - } - - let contents = fs::read(&file.path())?; - - if let Ok((blockhash, channel_monitor)) = - <(BlockHash, ChannelMonitor)>::read( - &mut Cursor::new(&contents), - &*keys_manager, - ) { - outpoint_to_channelmonitor.insert( - OutPoint { txid: txid.unwrap(), index: index.unwrap() }, - (blockhash, channel_monitor), - ); - } else { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to deserialize ChannelMonitor", - )); - } - } - Ok(outpoint_to_channelmonitor) -} diff --git a/src/main.rs b/src/main.rs index 5f5dc0af..5f213fca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,6 @@ use lightning::chain; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; use lightning::chain::chainmonitor; use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager}; -use lightning::chain::transaction::OutPoint; use lightning::chain::Filter; use lightning::chain::Watch; use lightning::ln::channelmanager; @@ -102,13 +101,11 @@ pub(crate) type ChannelManager = SimpleArcChannelManager; async fn handle_ldk_events( - peer_manager: Arc, channel_manager: Arc, - chain_monitor: Arc, bitcoind_client: Arc, - keys_manager: Arc, payment_storage: PaymentInfoStorage, network: Network, + channel_manager: Arc, chain_monitor: Arc, + bitcoind_client: Arc, keys_manager: Arc, + payment_storage: PaymentInfoStorage, network: Network, ) { - let mut pending_txs: HashMap = HashMap::new(); loop { - peer_manager.process_events(); let loop_channel_manager = channel_manager.clone(); let mut events = channel_manager.get_and_clear_pending_events(); events.append(&mut chain_monitor.get_and_clear_pending_events()); @@ -149,22 +146,12 @@ async fn handle_ldk_events( assert_eq!(signed_tx.complete, true); let final_tx: Transaction = encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap(); - let outpoint = OutPoint { - txid: final_tx.txid(), - index: if change_output_position == 0 { 1 } else { 0 }, - }; // Give the funding transaction back to LDK for opening the channel. loop_channel_manager - .funding_transaction_generated(&temporary_channel_id, outpoint); - pending_txs.insert(outpoint, final_tx); + .funding_transaction_generated(&temporary_channel_id, final_tx) + .unwrap(); } Event::FundingBroadcastSafe { funding_txo, .. } => { - let funding_tx = pending_txs.remove(&funding_txo).unwrap(); - bitcoind_client.broadcast_transaction(&funding_tx); - println!("\nEVENT: broadcasted funding transaction"); - print!("> "); - io::stdout().flush().unwrap(); - } Event::PaymentReceived { payment_hash, payment_secret, amt: amt_msat } => { let mut payments = payment_storage.lock().unwrap(); if let Some((Some(preimage), _, _, _)) = payments.get(&payment_hash) { @@ -345,9 +332,7 @@ async fn start_ldk() { let keys_manager = Arc::new(KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos())); // Step 7: Read ChannelMonitor state from disk - let monitors_path = format!("{}/monitors", ldk_data_dir.clone()); - let mut outpoint_to_channelmonitor = - disk::read_channelmonitors(monitors_path.to_string(), keys_manager.clone()).unwrap(); + let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone()).unwrap(); // Step 9: Initialize the ChannelManager let user_config = UserConfig::default(); @@ -355,8 +340,8 @@ async fn start_ldk() { let (channel_manager_blockhash, mut channel_manager) = { if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { let mut channel_monitor_mut_references = Vec::new(); - for (_, channel_monitor) in outpoint_to_channelmonitor.iter_mut() { - channel_monitor_mut_references.push(&mut channel_monitor.1); + for (_, channel_monitor) in channelmonitors.iter_mut() { + channel_monitor_mut_references.push(channel_monitor); } let read_args = ChannelManagerReadArgs::new( keys_manager.clone(), @@ -399,9 +384,8 @@ async fn start_ldk() { let mut chain_listeners = vec![(channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)]; - for (outpoint, blockhash_and_monitor) in outpoint_to_channelmonitor.drain() { - let blockhash = blockhash_and_monitor.0; - let channel_monitor = blockhash_and_monitor.1; + for (blockhash, channel_monitor) in channelmonitors.drain(..) { + let outpoint = channel_monitor.get_funding_txo().0; chain_listener_channel_monitors.push(( blockhash, (channel_monitor, broadcaster.clone(), fee_estimator.clone(), logger.clone()), @@ -504,6 +488,7 @@ async fn start_ldk() { BackgroundProcessor::start( persist_channel_manager_callback, channel_manager.clone(), + peer_manager.clone(), logger.clone(), ); @@ -516,7 +501,6 @@ async fn start_ldk() { }); // Step 15: Initialize LDK Event Handling - let peer_manager_event_listener = peer_manager.clone(); let channel_manager_event_listener = channel_manager.clone(); let chain_monitor_event_listener = chain_monitor.clone(); let keys_manager_listener = keys_manager.clone(); @@ -526,7 +510,6 @@ async fn start_ldk() { let bitcoind_rpc = bitcoind_client.clone(); tokio::spawn(async move { handle_ldk_events( - peer_manager_event_listener, channel_manager_event_listener, chain_monitor_event_listener, bitcoind_rpc, From 5ec1c3f55f5c6794972846ccbe6dc67440313dab Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 13 Apr 2021 22:35:26 -0400 Subject: [PATCH 17/22] SatoshiAmount -> MillisatAmount and PaymentInfo struct --- src/cli.rs | 48 +++++++++++++++++++++-------------- src/main.rs | 73 +++++++++++++++++++++++++++-------------------------- 2 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 0144c001..e2f4a637 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,8 @@ use crate::disk; use crate::hex_utils; use crate::{ - ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, PaymentInfoStorage, PeerManager, - SatoshiAmount, + ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, MillisatAmount, PaymentInfo, + PaymentInfoStorage, PeerManager, }; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; @@ -285,21 +285,21 @@ pub(crate) async fn poll_for_user_input( "getinvoice" => { let amt_str = words.next(); if amt_str.is_none() { - println!("ERROR: getinvoice requires an amount in satoshis"); + println!("ERROR: getinvoice requires an amount in millisatoshis"); print!("> "); io::stdout().flush().unwrap(); continue; } - let amt_sat: Result = amt_str.unwrap().parse(); - if amt_sat.is_err() { + let amt_msat: Result = amt_str.unwrap().parse(); + if amt_msat.is_err() { println!("ERROR: getinvoice provided payment amount was not a number"); print!("> "); io::stdout().flush().unwrap(); continue; } get_invoice( - amt_sat.unwrap(), + amt_msat.unwrap(), payment_storage.clone(), node_privkey.clone(), channel_manager.clone(), @@ -386,7 +386,7 @@ pub(crate) async fn poll_for_user_input( fn help() { println!("openchannel pubkey@host:port "); println!("sendpayment "); - println!("getinvoice "); + println!("getinvoice "); println!("connectpeer pubkey@host:port"); println!("listchannels"); println!("listpayments"); @@ -423,18 +423,18 @@ fn list_payments(payment_storage: PaymentInfoStorage) { let payments = payment_storage.lock().unwrap(); print!("["); for (payment_hash, payment_info) in payments.deref() { - let direction_str = match payment_info.1 { + let direction_str = match payment_info.direction { HTLCDirection::Inbound => "inbound", HTLCDirection::Outbound => "outbound", }; println!(""); println!("\t{{"); - println!("\t\tamount_satoshis: {},", payment_info.3); + println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat); println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); println!("\t\thtlc_direction: {},", direction_str); println!( "\t\thtlc_status: {},", - match payment_info.2 { + match payment_info.status { HTLCStatus::Pending => "pending", HTLCStatus::Succeeded => "succeeded", HTLCStatus::Failed => "failed", @@ -558,12 +558,18 @@ fn send_payment( let mut payments = payment_storage.lock().unwrap(); payments.insert( payment_hash, - (None, HTLCDirection::Outbound, status, SatoshiAmount(Some(amt_msat / 1000))), + PaymentInfo { + preimage: None, + secret: payment_secret, + direction: HTLCDirection::Outbound, + status, + amt_msat: MillisatAmount(Some(amt_msat)), + }, ); } fn get_invoice( - amt_sat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, + amt_msat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, channel_manager: Arc, network: Network, ) { let mut payments = payment_storage.lock().unwrap(); @@ -582,7 +588,7 @@ fn get_invoice( }) .payment_hash(payment_hash) .description("rust-lightning-bitcoinrpc invoice".to_string()) - .amount_pico_btc(amt_sat * 10_000) + .amount_pico_btc(amt_msat * 10) .current_timestamp() .payee_pub_key(our_node_pubkey); @@ -622,12 +628,16 @@ fn get_invoice( payments.insert( PaymentHash(payment_hash.into_inner()), - ( - Some(PaymentPreimage(preimage)), - HTLCDirection::Inbound, - HTLCStatus::Pending, - SatoshiAmount(Some(amt_sat)), - ), + PaymentInfo { + preimage: Some(PaymentPreimage(preimage)), + // We can't add payment secrets to invoices until we support features in invoices. + // Otherwise lnd errors with "destination hop doesn't understand payment addresses" + // (for context, lnd calls payment secrets "payment addresses"). + secret: None, + direction: HTLCDirection::Inbound, + status: HTLCStatus::Pending, + amt_msat: MillisatAmount(Some(amt_msat)), + }, ); } diff --git a/src/main.rs b/src/main.rs index 5f213fca..87ad4e9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,8 @@ use lightning::chain::Filter; use lightning::chain::Watch; use lightning::ln::channelmanager; use lightning::ln::channelmanager::{ - ChainParameters, ChannelManagerReadArgs, PaymentHash, PaymentPreimage, SimpleArcChannelManager, + ChainParameters, ChannelManagerReadArgs, PaymentHash, PaymentPreimage, PaymentSecret, + SimpleArcChannelManager, }; use lightning::ln::peer_handler::{MessageHandler, SimpleArcPeerManager}; use lightning::routing::network_graph::NetGraphMsgHandler; @@ -62,9 +63,9 @@ pub(crate) enum HTLCStatus { Failed, } -pub(crate) struct SatoshiAmount(Option); +pub(crate) struct MillisatAmount(Option); -impl fmt::Display for SatoshiAmount { +impl fmt::Display for MillisatAmount { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { Some(amt) => write!(f, "{}", amt), @@ -73,11 +74,15 @@ impl fmt::Display for SatoshiAmount { } } -pub(crate) type PaymentInfoStorage = Arc< - Mutex< - HashMap, HTLCDirection, HTLCStatus, SatoshiAmount)>, - >, ->; +pub(crate) struct PaymentInfo { + preimage: Option, + secret: Option, + direction: HTLCDirection, + status: HTLCStatus, + amt_msat: MillisatAmount, +} + +pub(crate) type PaymentInfoStorage = Arc>>; type ChainMonitor = chainmonitor::ChainMonitor< InMemorySigner, @@ -151,47 +156,50 @@ async fn handle_ldk_events( .funding_transaction_generated(&temporary_channel_id, final_tx) .unwrap(); } - Event::FundingBroadcastSafe { funding_txo, .. } => { - Event::PaymentReceived { payment_hash, payment_secret, amt: amt_msat } => { + Event::PaymentReceived { payment_hash, .. } => { let mut payments = payment_storage.lock().unwrap(); - if let Some((Some(preimage), _, _, _)) = payments.get(&payment_hash) { + if let Some(payment) = payments.get_mut(&payment_hash) { assert!(loop_channel_manager.claim_funds( - preimage.clone(), - &payment_secret, - amt_msat + payment.preimage.unwrap().clone(), + &payment.secret, + payment.amt_msat.0.unwrap(), )); println!( - "\nEVENT: received payment from payment_hash {} of {} satoshis", + "\nEVENT: received payment from payment_hash {} of {} millisatoshis", hex_utils::hex_str(&payment_hash.0), - amt_msat / 1000 + payment.amt_msat ); print!("> "); io::stdout().flush().unwrap(); - let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); - *status = HTLCStatus::Succeeded; + payment.status = HTLCStatus::Succeeded; } else { println!("\nERROR: we received a payment but didn't know the preimage"); print!("> "); io::stdout().flush().unwrap(); - loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); + loop_channel_manager.fail_htlc_backwards(&payment_hash, &None); payments.insert( payment_hash, - (None, HTLCDirection::Inbound, HTLCStatus::Failed, SatoshiAmount(None)), + PaymentInfo { + preimage: None, + secret: None, + direction: HTLCDirection::Inbound, + status: HTLCStatus::Failed, + amt_msat: MillisatAmount(None), + }, ); } } Event::PaymentSent { payment_preimage } => { let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); let mut payments = payment_storage.lock().unwrap(); - for (payment_hash, (preimage_option, _, status, amt_sat)) in payments.iter_mut() - { + for (payment_hash, payment) in payments.iter_mut() { if *payment_hash == hashed { - *preimage_option = Some(payment_preimage); - *status = HTLCStatus::Succeeded; + payment.preimage = Some(payment_preimage); + payment.status = HTLCStatus::Succeeded; println!( - "\nEVENT: successfully sent payment of {} satoshis from \ + "\nEVENT: successfully sent payment of {} millisatoshis from \ payment hash {:?} with preimage {:?}", - amt_sat, + payment.amt_msat, hex_utils::hex_str(&payment_hash.0), hex_utils::hex_str(&payment_preimage.0) ); @@ -215,8 +223,8 @@ async fn handle_ldk_events( let mut payments = payment_storage.lock().unwrap(); if payments.contains_key(&payment_hash) { - let (_, _, ref mut status, _) = payments.get_mut(&payment_hash).unwrap(); - *status = HTLCStatus::Failed; + let payment = payments.get_mut(&payment_hash).unwrap(); + payment.status = HTLCStatus::Failed; } } Event::PendingHTLCsForwardable { time_forwardable } => { @@ -492,18 +500,11 @@ async fn start_ldk() { logger.clone(), ); - let peer_manager_processor = peer_manager.clone(); - tokio::spawn(async move { - loop { - peer_manager_processor.timer_tick_occurred(); - tokio::time::sleep(Duration::from_secs(60)).await; - } - }); - // Step 15: Initialize LDK Event Handling let channel_manager_event_listener = channel_manager.clone(); let chain_monitor_event_listener = chain_monitor.clone(); let keys_manager_listener = keys_manager.clone(); + // TODO: persist payment info to disk let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); let payment_info_for_events = payment_info.clone(); let network = args.network; From 9157f86d3f75c4b122c319e2639455b90f7fd1f9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 14 Apr 2021 11:31:11 -0400 Subject: [PATCH 18/22] Remove HTLCDirection struct We're going to split payment storage into two maps, one for inbound and one for outbound. --- src/cli.rs | 10 ++-------- src/main.rs | 8 -------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index e2f4a637..2dc6b7df 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use crate::disk; use crate::hex_utils; use crate::{ - ChannelManager, FilesystemLogger, HTLCDirection, HTLCStatus, MillisatAmount, PaymentInfo, + ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, PaymentInfoStorage, PeerManager, }; use bitcoin::hashes::sha256::Hash as Sha256Hash; @@ -423,15 +423,11 @@ fn list_payments(payment_storage: PaymentInfoStorage) { let payments = payment_storage.lock().unwrap(); print!("["); for (payment_hash, payment_info) in payments.deref() { - let direction_str = match payment_info.direction { - HTLCDirection::Inbound => "inbound", - HTLCDirection::Outbound => "outbound", - }; println!(""); println!("\t{{"); println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat); println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); - println!("\t\thtlc_direction: {},", direction_str); + // println!("\t\thtlc_direction: {},", direction_str); println!( "\t\thtlc_status: {},", match payment_info.status { @@ -561,7 +557,6 @@ fn send_payment( PaymentInfo { preimage: None, secret: payment_secret, - direction: HTLCDirection::Outbound, status, amt_msat: MillisatAmount(Some(amt_msat)), }, @@ -634,7 +629,6 @@ fn get_invoice( // Otherwise lnd errors with "destination hop doesn't understand payment addresses" // (for context, lnd calls payment secrets "payment addresses"). secret: None, - direction: HTLCDirection::Inbound, status: HTLCStatus::Pending, amt_msat: MillisatAmount(Some(amt_msat)), }, diff --git a/src/main.rs b/src/main.rs index 87ad4e9a..32981a4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,12 +51,6 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use tokio::sync::mpsc; -#[derive(PartialEq)] -pub(crate) enum HTLCDirection { - Inbound, - Outbound, -} - pub(crate) enum HTLCStatus { Pending, Succeeded, @@ -77,7 +71,6 @@ impl fmt::Display for MillisatAmount { pub(crate) struct PaymentInfo { preimage: Option, secret: Option, - direction: HTLCDirection, status: HTLCStatus, amt_msat: MillisatAmount, } @@ -182,7 +175,6 @@ async fn handle_ldk_events( PaymentInfo { preimage: None, secret: None, - direction: HTLCDirection::Inbound, status: HTLCStatus::Failed, amt_msat: MillisatAmount(None), }, From ed6f3c60e3b6cea7041b3b7582afc8b4932c2d86 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 14 Apr 2021 14:25:14 -0400 Subject: [PATCH 19/22] Split payments hashmap into inbound and outbound To prevent clashes between payment hashes --- src/cli.rs | 44 +++++++++++++++++++++++++++++++++----------- src/main.rs | 20 ++++++++++++-------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 2dc6b7df..67734809 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,8 @@ use crate::disk; use crate::hex_utils; use crate::{ - ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, - PaymentInfoStorage, PeerManager, + ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, PaymentInfoStorage, + PeerManager, }; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; @@ -101,8 +101,9 @@ pub(crate) fn parse_startup_args() -> Result { pub(crate) async fn poll_for_user_input( peer_manager: Arc, channel_manager: Arc, router: Arc, Arc>>, - payment_storage: PaymentInfoStorage, node_privkey: SecretKey, event_notifier: mpsc::Sender<()>, - ldk_data_dir: String, logger: Arc, network: Network, + inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, + node_privkey: SecretKey, event_notifier: mpsc::Sender<()>, ldk_data_dir: String, + logger: Arc, network: Network, ) { println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at /.ldk/logs"); let stdin = io::stdin(); @@ -278,7 +279,7 @@ pub(crate) async fn poll_for_user_input( route_hints, router.clone(), channel_manager.clone(), - payment_storage.clone(), + outbound_payments.clone(), logger.clone(), ); } @@ -300,7 +301,7 @@ pub(crate) async fn poll_for_user_input( } get_invoice( amt_msat.unwrap(), - payment_storage.clone(), + inbound_payments.clone(), node_privkey.clone(), channel_manager.clone(), network, @@ -336,7 +337,9 @@ pub(crate) async fn poll_for_user_input( } } "listchannels" => list_channels(channel_manager.clone()), - "listpayments" => list_payments(payment_storage.clone()), + "listpayments" => { + list_payments(inbound_payments.clone(), outbound_payments.clone()) + } "closechannel" => { let channel_id_str = words.next(); if channel_id_str.is_none() { @@ -419,15 +422,34 @@ fn list_channels(channel_manager: Arc) { println!("]"); } -fn list_payments(payment_storage: PaymentInfoStorage) { - let payments = payment_storage.lock().unwrap(); +fn list_payments(inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage) { + let inbound = inbound_payments.lock().unwrap(); + let outbound = outbound_payments.lock().unwrap(); print!("["); - for (payment_hash, payment_info) in payments.deref() { + for (payment_hash, payment_info) in inbound.deref() { + println!(""); + println!("\t{{"); + println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat); + println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); + println!("\t\thtlc_direction: inbound,"); + println!( + "\t\thtlc_status: {},", + match payment_info.status { + HTLCStatus::Pending => "pending", + HTLCStatus::Succeeded => "succeeded", + HTLCStatus::Failed => "failed", + } + ); + + println!("\t}},"); + } + + for (payment_hash, payment_info) in outbound.deref() { println!(""); println!("\t{{"); println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat); println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0)); - // println!("\t\thtlc_direction: {},", direction_str); + println!("\t\thtlc_direction: outbound,"); println!( "\t\thtlc_status: {},", match payment_info.status { diff --git a/src/main.rs b/src/main.rs index 32981a4a..70011292 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,7 +101,7 @@ pub(crate) type ChannelManager = async fn handle_ldk_events( channel_manager: Arc, chain_monitor: Arc, bitcoind_client: Arc, keys_manager: Arc, - payment_storage: PaymentInfoStorage, network: Network, + inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, network: Network, ) { loop { let loop_channel_manager = channel_manager.clone(); @@ -150,7 +150,7 @@ async fn handle_ldk_events( .unwrap(); } Event::PaymentReceived { payment_hash, .. } => { - let mut payments = payment_storage.lock().unwrap(); + let mut payments = inbound_payments.lock().unwrap(); if let Some(payment) = payments.get_mut(&payment_hash) { assert!(loop_channel_manager.claim_funds( payment.preimage.unwrap().clone(), @@ -183,7 +183,7 @@ async fn handle_ldk_events( } Event::PaymentSent { payment_preimage } => { let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner()); - let mut payments = payment_storage.lock().unwrap(); + let mut payments = outbound_payments.lock().unwrap(); for (payment_hash, payment) in payments.iter_mut() { if *payment_hash == hashed { payment.preimage = Some(payment_preimage); @@ -213,7 +213,7 @@ async fn handle_ldk_events( print!("> "); io::stdout().flush().unwrap(); - let mut payments = payment_storage.lock().unwrap(); + let mut payments = outbound_payments.lock().unwrap(); if payments.contains_key(&payment_hash) { let payment = payments.get_mut(&payment_hash).unwrap(); payment.status = HTLCStatus::Failed; @@ -497,8 +497,10 @@ async fn start_ldk() { let chain_monitor_event_listener = chain_monitor.clone(); let keys_manager_listener = keys_manager.clone(); // TODO: persist payment info to disk - let payment_info: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); - let payment_info_for_events = payment_info.clone(); + let inbound_payments: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); + let outbound_payments: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new())); + let inbound_pmts_for_events = inbound_payments.clone(); + let outbound_pmts_for_events = outbound_payments.clone(); let network = args.network; let bitcoind_rpc = bitcoind_client.clone(); tokio::spawn(async move { @@ -507,7 +509,8 @@ async fn start_ldk() { chain_monitor_event_listener, bitcoind_rpc, keys_manager_listener, - payment_info_for_events, + inbound_pmts_for_events, + outbound_pmts_for_events, network, ) .await; @@ -538,7 +541,8 @@ async fn start_ldk() { peer_manager.clone(), channel_manager.clone(), router.clone(), - payment_info, + inbound_payments, + outbound_payments, keys_manager.get_node_secret(), event_ntfn_sender, ldk_data_dir.clone(), From 85e890be4909361b7163fa09fa4bb271463af271 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 14 Apr 2021 14:58:27 -0400 Subject: [PATCH 20/22] Check payment secret before succeeding payment --- src/main.rs | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 70011292..927284aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,34 +149,43 @@ async fn handle_ldk_events( .funding_transaction_generated(&temporary_channel_id, final_tx) .unwrap(); } - Event::PaymentReceived { payment_hash, .. } => { + Event::PaymentReceived { payment_hash, payment_secret, amt } => { let mut payments = inbound_payments.lock().unwrap(); if let Some(payment) = payments.get_mut(&payment_hash) { - assert!(loop_channel_manager.claim_funds( - payment.preimage.unwrap().clone(), - &payment.secret, - payment.amt_msat.0.unwrap(), - )); - println!( - "\nEVENT: received payment from payment_hash {} of {} millisatoshis", - hex_utils::hex_str(&payment_hash.0), - payment.amt_msat - ); - print!("> "); - io::stdout().flush().unwrap(); - payment.status = HTLCStatus::Succeeded; + if payment.secret == payment_secret { + assert!(loop_channel_manager.claim_funds( + payment.preimage.unwrap().clone(), + &payment.secret, + payment.amt_msat.0.unwrap(), + )); + println!( + "\nEVENT: received payment from payment hash {} of {} millisatoshis", + hex_utils::hex_str(&payment_hash.0), + payment.amt_msat + ); + print!("> "); + io::stdout().flush().unwrap(); + payment.status = HTLCStatus::Succeeded; + } else { + loop_channel_manager + .fail_htlc_backwards(&payment_hash, &payment.secret); + println!("\nERROR: we received a payment from payment hash {} but the payment secret didn't match", hex_utils::hex_str(&payment_hash.0)); + print!("> "); + io::stdout().flush().unwrap(); + payment.status = HTLCStatus::Failed; + } } else { - println!("\nERROR: we received a payment but didn't know the preimage"); + loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); + println!("\nERROR: we received a payment for payment hash {} but didn't know the preimage", hex_utils::hex_str(&payment_hash.0)); print!("> "); io::stdout().flush().unwrap(); - loop_channel_manager.fail_htlc_backwards(&payment_hash, &None); payments.insert( payment_hash, PaymentInfo { preimage: None, - secret: None, + secret: payment_secret, status: HTLCStatus::Failed, - amt_msat: MillisatAmount(None), + amt_msat: MillisatAmount(Some(amt)), }, ); } From 432106edae85e3e5edc43e2a7c9053ac2d1e5532 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 14 Apr 2021 15:02:56 -0400 Subject: [PATCH 21/22] Adapt to the lightning_invoice crate moving to Rust-Lightning --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cli.rs | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c606e91..229654bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,7 +287,7 @@ dependencies = [ [[package]] name = "lightning-invoice" version = "0.4.0" -source = "git+https://github.com/rust-bitcoin/rust-lightning-invoice?rev=aa3a57b9dca5205fa25fa333a2db165d7e77b3b0#aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" dependencies = [ "bech32 0.7.2", "bitcoin_hashes", diff --git a/Cargo.toml b/Cargo.toml index 35bae496..2fae143f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d5 # lightning = { path = "../rust-lightning/lightning" } lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } -lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning-invoice", rev = "aa3a57b9dca5205fa25fa333a2db165d7e77b3b0" } +lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } # lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } diff --git a/src/cli.rs b/src/cli.rs index 67734809..8033cdad 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -16,6 +16,7 @@ use lightning::routing::network_graph::{NetGraphMsgHandler, RoutingFees}; use lightning::routing::router; use lightning::routing::router::RouteHint; use lightning::util::config::UserConfig; +use lightning_invoice::{Currency, Invoice, InvoiceBuilder, Route, RouteHop}; use rand; use rand::Rng; use std::env; @@ -194,7 +195,7 @@ pub(crate) async fn poll_for_user_input( continue; } - let invoice_res = lightning_invoice::Invoice::from_str(invoice_str.unwrap()); + let invoice_res = Invoice::from_str(invoice_str.unwrap()); if invoice_res.is_err() { println!("ERROR: invalid invoice: {:?}", invoice_res.unwrap_err()); print!("> "); @@ -202,7 +203,7 @@ pub(crate) async fn poll_for_user_input( continue; } let invoice = invoice_res.unwrap(); - let route_hints: Vec = + let route_hints: Vec = invoice.routes().iter().map(|&route| route.clone()).collect(); let amt_pico_btc = invoice.amount_pico_btc(); @@ -522,7 +523,7 @@ fn open_channel( fn send_payment( payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, payment_secret: Option, payee_features: Option, - mut route_hints: Vec, + mut route_hints: Vec, router: Arc, Arc>>, channel_manager: Arc, payment_storage: PaymentInfoStorage, logger: Arc, @@ -597,10 +598,10 @@ fn get_invoice( let payment_hash = Sha256Hash::hash(&preimage); let our_node_pubkey = channel_manager.get_our_node_id(); - let mut invoice = lightning_invoice::InvoiceBuilder::new(match network { - Network::Bitcoin => lightning_invoice::Currency::Bitcoin, - Network::Testnet => lightning_invoice::Currency::BitcoinTestnet, - Network::Regtest => lightning_invoice::Currency::Regtest, + let mut invoice = InvoiceBuilder::new(match network { + Network::Bitcoin => Currency::Bitcoin, + Network::Testnet => Currency::BitcoinTestnet, + Network::Regtest => Currency::Regtest, Network::Signet => panic!("Signet invoices not supported"), }) .payment_hash(payment_hash) @@ -624,7 +625,7 @@ fn get_invoice( if forwarding_info.cltv_expiry_delta > min_final_cltv_expiry { min_final_cltv_expiry = forwarding_info.cltv_expiry_delta; } - invoice = invoice.route(vec![lightning_invoice::RouteHop { + invoice = invoice.route(vec![RouteHop { pubkey: channel.remote_network_id, short_channel_id, fee_base_msat: forwarding_info.fee_base_msat, From a73d564d2b7f0733578747ed1c2604bd880efcd9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sun, 2 May 2021 17:22:44 -0400 Subject: [PATCH 22/22] Update to latest RL + fix step numbers + nodeinfo + listpeers commands --- Cargo.lock | 13 +-- Cargo.toml | 27 +++++-- rustfmt.toml | 2 +- src/cli.rs | 219 +++++++++++++++++++-------------------------------- src/main.rs | 146 ++++++++++++++++++---------------- 5 files changed, 186 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 229654bd..243c2c07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,7 @@ checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" [[package]] name = "lightning" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482" dependencies = [ "bitcoin", ] @@ -264,7 +264,7 @@ dependencies = [ [[package]] name = "lightning-background-processor" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482" dependencies = [ "bitcoin", "lightning", @@ -274,7 +274,7 @@ dependencies = [ [[package]] name = "lightning-block-sync" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482" dependencies = [ "bitcoin", "chunked_transfer", @@ -287,10 +287,11 @@ dependencies = [ [[package]] name = "lightning-invoice" version = "0.4.0" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482" dependencies = [ "bech32 0.7.2", "bitcoin_hashes", + "lightning", "num-traits", "secp256k1", ] @@ -298,7 +299,7 @@ dependencies = [ [[package]] name = "lightning-net-tokio" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482" dependencies = [ "bitcoin", "lightning", @@ -308,7 +309,7 @@ dependencies = [ [[package]] name = "lightning-persister" version = "0.0.13" -source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=3d51b11fe99c9d6a41b0d662efdf5693d9600552#3d51b11fe99c9d6a41b0d662efdf5693d9600552" +source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482" dependencies = [ "bitcoin", "libc", diff --git a/Cargo.toml b/Cargo.toml index 2fae143f..b32be572 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,22 +8,35 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } -# lightning-background-processor = { path = "../rust-lightning/background-processor" } +# lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", branch = "main" } +lightning-background-processor = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d4d322580994857b1222488f8467311d6db61482" } +# lightning-background-processor = { path = "../rust-lightning/lightning-background-processor" } base64 = "0.13.0" bitcoin = "0.26" bitcoin-bech32 = "0.7" bech32 = "0.7" hex = "0.3" -lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } + +# lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", branch = "main" } +lightning = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d4d322580994857b1222488f8467311d6db61482" } # lightning = { path = "../rust-lightning/lightning" } -lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } + +# lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], branch = "main" } +lightning-block-sync = { git = "https://github.com/rust-bitcoin/rust-lightning", features = ["rpc-client"], rev = "d4d322580994857b1222488f8467311d6db61482" } # lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client"] } -lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } -lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } + +# lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning", branch = "main" } +lightning-invoice = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d4d322580994857b1222488f8467311d6db61482" } +# lightning-invoice = { path = "../rust-lightning/lightning-invoice" } + +# lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", branch = "main" } +lightning-net-tokio = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d4d322580994857b1222488f8467311d6db61482" } # lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } -lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "3d51b11fe99c9d6a41b0d662efdf5693d9600552" } + +# lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", branch = "main" } +lightning-persister = { git = "https://github.com/rust-bitcoin/rust-lightning", rev = "d4d322580994857b1222488f8467311d6db61482" } # lightning-persister = { path = "../rust-lightning/lightning-persister" } + time = "0.2" rand = "0.4" serde_json = { version = "1.0" } diff --git a/rustfmt.toml b/rustfmt.toml index c61104c9..c00f6554 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,4 +2,4 @@ hard_tabs = true # use tab characters for indentation, spaces for alignment use_field_init_shorthand = true max_width = 100 use_small_heuristics = "Max" -fn_args_layout = "Compressed" \ No newline at end of file +fn_args_layout = "Compressed" diff --git a/src/cli.rs b/src/cli.rs index 8033cdad..c5dda5fc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,21 +4,17 @@ use crate::{ ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, PaymentInfoStorage, PeerManager, }; -use bitcoin::hashes::sha256::Hash as Sha256Hash; -use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; -use bitcoin::secp256k1::key::{PublicKey, SecretKey}; -use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::key::PublicKey; use lightning::chain; -use lightning::ln::channelmanager::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning::chain::keysinterface::KeysManager; use lightning::ln::features::InvoiceFeatures; -use lightning::routing::network_graph::{NetGraphMsgHandler, RoutingFees}; +use lightning::ln::{PaymentHash, PaymentSecret}; +use lightning::routing::network_graph::NetGraphMsgHandler; use lightning::routing::router; -use lightning::routing::router::RouteHint; +use lightning::routing::router::RouteHintHop; use lightning::util::config::UserConfig; -use lightning_invoice::{Currency, Invoice, InvoiceBuilder, Route, RouteHop}; -use rand; -use rand::Rng; +use lightning_invoice::{utils, Currency, Invoice}; use std::env; use std::io; use std::io::{BufRead, Write}; @@ -41,7 +37,7 @@ pub(crate) struct LdkUserInfo { } pub(crate) fn parse_startup_args() -> Result { - if env::args().len() < 4 { + if env::args().len() < 3 { println!("ldk-tutorial-node requires 3 arguments: `cargo run :@: ldk_storage_directory_path [] [bitcoin-network]`"); return Err(()); } @@ -71,7 +67,7 @@ pub(crate) fn parse_startup_args() -> Result { let mut ldk_peer_port_set = true; let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) { Some(Ok(p)) => p, - Some(Err(e)) => panic!(e), + Some(Err(e)) => panic!("{}", e), None => { ldk_peer_port_set = false; 9735 @@ -101,10 +97,11 @@ pub(crate) fn parse_startup_args() -> Result { pub(crate) async fn poll_for_user_input( peer_manager: Arc, channel_manager: Arc, - router: Arc, Arc>>, + keys_manager: Arc, + router: Arc, Arc>>, inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, - node_privkey: SecretKey, event_notifier: mpsc::Sender<()>, ldk_data_dir: String, - logger: Arc, network: Network, + event_notifier: mpsc::Sender<()>, ldk_data_dir: String, logger: Arc, + network: Network, ) { println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at /.ldk/logs"); let stdin = io::stdin(); @@ -195,16 +192,20 @@ pub(crate) async fn poll_for_user_input( continue; } - let invoice_res = Invoice::from_str(invoice_str.unwrap()); - if invoice_res.is_err() { - println!("ERROR: invalid invoice: {:?}", invoice_res.unwrap_err()); - print!("> "); - io::stdout().flush().unwrap(); - continue; + let invoice = match Invoice::from_str(invoice_str.unwrap()) { + Ok(inv) => inv, + Err(e) => { + println!("ERROR: invalid invoice: {:?}", e); + print!("> "); + io::stdout().flush().unwrap(); + continue; + } + }; + let mut route_hints = invoice.routes().clone(); + let mut last_hops = Vec::new(); + for hint in route_hints.drain(..) { + last_hops.push(hint[hint.len() - 1].clone()); } - let invoice = invoice_res.unwrap(); - let route_hints: Vec = - invoice.routes().iter().map(|&route| route.clone()).collect(); let amt_pico_btc = invoice.amount_pico_btc(); if amt_pico_btc.is_none() { @@ -216,7 +217,7 @@ pub(crate) async fn poll_for_user_input( let amt_msat = amt_pico_btc.unwrap() / 10; let payee_pubkey = invoice.recover_payee_pub_key(); - let final_cltv = *invoice.min_final_cltv_expiry().unwrap_or(&9) as u32; + let final_cltv = invoice.min_final_cltv_expiry() as u32; let mut payment_hash = PaymentHash([0; 32]); payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); @@ -230,54 +231,19 @@ pub(crate) async fn poll_for_user_input( None => None, }; - // rust-lightning-invoice doesn't currently support features, so we parse features - // manually from the invoice. - let mut invoice_features = InvoiceFeatures::empty(); - for field in &invoice.into_signed_raw().raw_invoice().data.tagged_fields { - match field { - lightning_invoice::RawTaggedField::UnknownSemantics(vec) => { - if vec[0] == bech32::u5::try_from_u8(5).unwrap() { - if vec.len() >= 6 && vec[5].to_u8() & 0b10000 != 0 { - invoice_features = - invoice_features.set_variable_length_onion_optional(); - } - if vec.len() >= 6 && vec[5].to_u8() & 0b01000 != 0 { - invoice_features = - invoice_features.set_variable_length_onion_required(); - } - if vec.len() >= 4 && vec[3].to_u8() & 0b00001 != 0 { - invoice_features = - invoice_features.set_payment_secret_optional(); - } - if vec.len() >= 5 && vec[4].to_u8() & 0b10000 != 0 { - invoice_features = - invoice_features.set_payment_secret_required(); - } - if vec.len() >= 4 && vec[3].to_u8() & 0b00100 != 0 { - invoice_features = - invoice_features.set_basic_mpp_optional(); - } - if vec.len() >= 4 && vec[3].to_u8() & 0b00010 != 0 { - invoice_features = - invoice_features.set_basic_mpp_required(); - } - } - } - _ => {} - } - } - let invoice_features_opt = match invoice_features == InvoiceFeatures::empty() { - true => None, - false => Some(invoice_features), + let invoice_features = match invoice.features() { + Some(feat) => Some(feat.clone()), + None => None, }; + send_payment( payee_pubkey, amt_msat, final_cltv, payment_hash, payment_secret, - invoice_features_opt, - route_hints, + invoice_features, + last_hops, router.clone(), channel_manager.clone(), outbound_payments.clone(), @@ -303,8 +269,8 @@ pub(crate) async fn poll_for_user_input( get_invoice( amt_msat.unwrap(), inbound_payments.clone(), - node_privkey.clone(), channel_manager.clone(), + keys_manager.clone(), network, ); } @@ -379,6 +345,8 @@ pub(crate) async fn poll_for_user_input( channel_id.copy_from_slice(&channel_id_vec.unwrap()); force_close_channel(channel_id, channel_manager.clone()); } + "nodeinfo" => node_info(channel_manager.clone(), peer_manager.clone()), + "listpeers" => list_peers(peer_manager.clone()), _ => println!("Unknown command. See `\"help\" for available commands."), } } @@ -398,6 +366,23 @@ fn help() { println!("forceclosechannel "); } +fn node_info(channel_manager: Arc, peer_manager: Arc) { + println!("\t{{"); + println!("\t\t node_pubkey: {}", channel_manager.get_our_node_id()); + println!("\t\t num_channels: {}", channel_manager.list_channels().len()); + println!("\t\t num_usable_channels: {}", channel_manager.list_usable_channels().len()); + println!("\t\t num_peers: {}", peer_manager.get_peer_node_ids().len()); + println!("\t}},"); +} + +fn list_peers(peer_manager: Arc) { + println!("\t{{"); + for pubkey in peer_manager.get_peer_node_ids() { + println!("\t\t pubkey: {}", pubkey); + } + println!("\t}},"); +} + fn list_channels(channel_manager: Arc) { print!("["); for chan_info in channel_manager.list_channels() { @@ -523,8 +508,8 @@ fn open_channel( fn send_payment( payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash, payment_secret: Option, payee_features: Option, - mut route_hints: Vec, - router: Arc, Arc>>, + route_hints: Vec, + router: Arc, Arc>>, channel_manager: Arc, payment_storage: PaymentInfoStorage, logger: Arc, ) { @@ -532,29 +517,13 @@ fn send_payment( let first_hops = channel_manager.list_usable_channels(); let payer_pubkey = channel_manager.get_our_node_id(); - let mut hints: Vec = Vec::new(); - for route in route_hints.drain(..) { - let route_hops = route.into_inner(); - let last_hop = &route_hops[route_hops.len() - 1]; - hints.push(RouteHint { - src_node_id: last_hop.pubkey, - short_channel_id: u64::from_be_bytes(last_hop.short_channel_id), - fees: RoutingFees { - base_msat: last_hop.fee_base_msat, - proportional_millionths: last_hop.fee_proportional_millionths, - }, - cltv_expiry_delta: last_hop.cltv_expiry_delta, - htlc_minimum_msat: None, - htlc_maximum_msat: None, - }) - } let route = router::get_route( &payer_pubkey, &network_graph, &payee, payee_features, Some(&first_hops.iter().collect::>()), - &hints.iter().collect::>(), + &route_hints.iter().collect::>(), amt_msat, final_cltv, logger, @@ -587,71 +556,43 @@ fn send_payment( } fn get_invoice( - amt_msat: u64, payment_storage: PaymentInfoStorage, our_node_privkey: SecretKey, - channel_manager: Arc, network: Network, + amt_msat: u64, payment_storage: PaymentInfoStorage, channel_manager: Arc, + keys_manager: Arc, network: Network, ) { let mut payments = payment_storage.lock().unwrap(); - let secp_ctx = Secp256k1::new(); - - let mut preimage = [0; 32]; - rand::thread_rng().fill_bytes(&mut preimage); - let payment_hash = Sha256Hash::hash(&preimage); - - let our_node_pubkey = channel_manager.get_our_node_id(); - let mut invoice = InvoiceBuilder::new(match network { + let currency = match network { Network::Bitcoin => Currency::Bitcoin, Network::Testnet => Currency::BitcoinTestnet, Network::Regtest => Currency::Regtest, - Network::Signet => panic!("Signet invoices not supported"), - }) - .payment_hash(payment_hash) - .description("rust-lightning-bitcoinrpc invoice".to_string()) - .amount_pico_btc(amt_msat * 10) - .current_timestamp() - .payee_pub_key(our_node_pubkey); - - // Add route hints to the invoice. - let our_channels = channel_manager.list_usable_channels(); - let mut min_final_cltv_expiry = 9; - for channel in our_channels { - let short_channel_id = match channel.short_channel_id { - Some(id) => id.to_be_bytes(), - None => continue, - }; - let forwarding_info = match channel.counterparty_forwarding_info { - Some(info) => info, - None => continue, - }; - if forwarding_info.cltv_expiry_delta > min_final_cltv_expiry { - min_final_cltv_expiry = forwarding_info.cltv_expiry_delta; + Network::Signet => panic!("Signet unsupported"), + }; + let invoice = match utils::create_invoice_from_channelmanager( + &channel_manager, + keys_manager, + currency, + Some(amt_msat), + "ldk-tutorial-node".to_string(), + ) { + Ok(inv) => { + println!("SUCCESS: generated invoice: {}", inv); + inv } - invoice = invoice.route(vec![RouteHop { - pubkey: channel.remote_network_id, - short_channel_id, - fee_base_msat: forwarding_info.fee_base_msat, - fee_proportional_millionths: forwarding_info.fee_proportional_millionths, - cltv_expiry_delta: forwarding_info.cltv_expiry_delta, - }]); - } - invoice = invoice.min_final_cltv_expiry(min_final_cltv_expiry.into()); - - // Sign the invoice. - let invoice = - invoice.build_signed(|msg_hash| secp_ctx.sign_recoverable(msg_hash, &our_node_privkey)); - - match invoice { - Ok(invoice) => println!("SUCCESS: generated invoice: {}", invoice), - Err(e) => println!("ERROR: failed to create invoice: {:?}", e), - } + Err(e) => { + println!("ERROR: failed to create invoice: {:?}", e); + return; + } + }; + let mut payment_hash = PaymentHash([0; 32]); + payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]); payments.insert( - PaymentHash(payment_hash.into_inner()), + payment_hash, PaymentInfo { - preimage: Some(PaymentPreimage(preimage)), + preimage: None, // We can't add payment secrets to invoices until we support features in invoices. // Otherwise lnd errors with "destination hop doesn't understand payment addresses" // (for context, lnd calls payment secrets "payment addresses"). - secret: None, + secret: Some(invoice.payment_secret().unwrap().clone()), status: HTLCStatus::Pending, amt_msat: MillisatAmount(Some(amt_msat)), }, diff --git a/src/main.rs b/src/main.rs index 927284aa..b024e935 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,10 +23,10 @@ use lightning::chain::Filter; use lightning::chain::Watch; use lightning::ln::channelmanager; use lightning::ln::channelmanager::{ - ChainParameters, ChannelManagerReadArgs, PaymentHash, PaymentPreimage, PaymentSecret, - SimpleArcChannelManager, + BestBlock, ChainParameters, ChannelManagerReadArgs, SimpleArcChannelManager, }; use lightning::ln::peer_handler::{MessageHandler, SimpleArcPeerManager}; +use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret}; use lightning::routing::network_graph::NetGraphMsgHandler; use lightning::util::config::UserConfig; use lightning::util::events::{Event, EventsProvider}; @@ -39,6 +39,7 @@ use lightning_block_sync::UnboundedCache; use lightning_net_tokio::SocketDescriptor; use lightning_persister::FilesystemPersister; use rand::{thread_rng, Rng}; +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::fmt; use std::fs; @@ -50,6 +51,7 @@ use std::path::Path; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use tokio::sync::mpsc; +use tokio::sync::mpsc::Receiver; pub(crate) enum HTLCStatus { Pending, @@ -79,7 +81,7 @@ pub(crate) type PaymentInfoStorage = Arc type ChainMonitor = chainmonitor::ChainMonitor< InMemorySigner, - Arc, + Arc, Arc, Arc, Arc, @@ -91,7 +93,7 @@ pub(crate) type PeerManager = SimpleArcPeerManager< ChainMonitor, BitcoindClient, BitcoindClient, - dyn chain::Access, + dyn chain::Access + Send + Sync, FilesystemLogger, >; @@ -102,8 +104,14 @@ async fn handle_ldk_events( channel_manager: Arc, chain_monitor: Arc, bitcoind_client: Arc, keys_manager: Arc, inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage, network: Network, + mut event_receiver: Receiver<()>, ) { loop { + let received = event_receiver.recv(); + if received.await.is_none() { + println!("LDK Event channel closed!"); + return; + } let loop_channel_manager = channel_manager.clone(); let mut events = channel_manager.get_and_clear_pending_events(); events.append(&mut chain_monitor.get_and_clear_pending_events()); @@ -149,45 +157,42 @@ async fn handle_ldk_events( .funding_transaction_generated(&temporary_channel_id, final_tx) .unwrap(); } - Event::PaymentReceived { payment_hash, payment_secret, amt } => { + Event::PaymentReceived { + payment_hash, + payment_preimage, + payment_secret, + amt, + .. + } => { let mut payments = inbound_payments.lock().unwrap(); - if let Some(payment) = payments.get_mut(&payment_hash) { - if payment.secret == payment_secret { - assert!(loop_channel_manager.claim_funds( - payment.preimage.unwrap().clone(), - &payment.secret, - payment.amt_msat.0.unwrap(), - )); + let status = match loop_channel_manager.claim_funds(payment_preimage.unwrap()) { + true => { println!( - "\nEVENT: received payment from payment hash {} of {} millisatoshis", - hex_utils::hex_str(&payment_hash.0), - payment.amt_msat - ); + "\nEVENT: received payment from payment hash {} of {} millisatoshis", + hex_utils::hex_str(&payment_hash.0), + amt + ); print!("> "); io::stdout().flush().unwrap(); - payment.status = HTLCStatus::Succeeded; - } else { - loop_channel_manager - .fail_htlc_backwards(&payment_hash, &payment.secret); - println!("\nERROR: we received a payment from payment hash {} but the payment secret didn't match", hex_utils::hex_str(&payment_hash.0)); - print!("> "); - io::stdout().flush().unwrap(); - payment.status = HTLCStatus::Failed; + HTLCStatus::Succeeded } - } else { - loop_channel_manager.fail_htlc_backwards(&payment_hash, &payment_secret); - println!("\nERROR: we received a payment for payment hash {} but didn't know the preimage", hex_utils::hex_str(&payment_hash.0)); - print!("> "); - io::stdout().flush().unwrap(); - payments.insert( - payment_hash, - PaymentInfo { - preimage: None, - secret: payment_secret, - status: HTLCStatus::Failed, + _ => HTLCStatus::Failed, + }; + match payments.entry(payment_hash) { + Entry::Occupied(mut e) => { + let payment = e.get_mut(); + payment.status = status; + payment.preimage = Some(payment_preimage.unwrap()); + payment.secret = Some(payment_secret); + } + Entry::Vacant(e) => { + e.insert(PaymentInfo { + preimage: Some(payment_preimage.unwrap()), + secret: Some(payment_secret), + status, amt_msat: MillisatAmount(Some(amt)), - }, - ); + }); + } } } Event::PaymentSent { payment_preimage } => { @@ -343,7 +348,7 @@ async fn start_ldk() { // Step 7: Read ChannelMonitor state from disk let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone()).unwrap(); - // Step 9: Initialize the ChannelManager + // Step 8: Initialize the ChannelManager let user_config = UserConfig::default(); let mut restarting_node = true; let (channel_manager_blockhash, mut channel_manager) = { @@ -369,8 +374,10 @@ async fn start_ldk() { let chain_params = ChainParameters { network: args.network, - latest_hash: getinfo_resp.latest_blockhash, - latest_height: getinfo_resp.latest_height, + best_block: BestBlock::new( + getinfo_resp.latest_blockhash, + getinfo_resp.latest_height as u32, + ), }; let fresh_channel_manager = channelmanager::ChannelManager::new( fee_estimator.clone(), @@ -385,7 +392,7 @@ async fn start_ldk() { } }; - // Step 10: Sync ChannelMonitors and ChannelManager to chain tip + // Step 9: Sync ChannelMonitors and ChannelManager to chain tip let mut chain_listener_channel_monitors = Vec::new(); let mut cache = UnboundedCache::new(); let mut chain_tip: Option = None; @@ -420,20 +427,23 @@ async fn start_ldk() { ); } - // Step 11: Give ChannelMonitors to ChainMonitor + // Step 10: Give ChannelMonitors to ChainMonitor for item in chain_listener_channel_monitors.drain(..) { let channel_monitor = item.1 .0; let funding_outpoint = item.2; chain_monitor.watch_channel(funding_outpoint, channel_monitor).unwrap(); } - // Step 13: Optional: Initialize the NetGraphMsgHandler + // Step 11: Optional: Initialize the NetGraphMsgHandler // XXX persist routing data let genesis = genesis_block(args.network).header.block_hash(); - let router = - Arc::new(NetGraphMsgHandler::new(genesis, None::>, logger.clone())); + let router = Arc::new(NetGraphMsgHandler::new( + genesis, + None::>, + logger.clone(), + )); - // Step 14: Initialize the PeerManager + // Step 12: Initialize the PeerManager let channel_manager: Arc = Arc::new(channel_manager); let mut ephemeral_bytes = [0; 32]; rand::thread_rng().fill_bytes(&mut ephemeral_bytes); @@ -447,28 +457,28 @@ async fn start_ldk() { )); // ## Running LDK - // Step 16: Initialize Peer Connection Handling + // Step 13: Initialize networking // We poll for events in handle_ldk_events(..) rather than waiting for them over the // mpsc::channel, so we can leave the event receiver as unused. - let (event_ntfn_sender, _event_ntfn_receiver) = mpsc::channel(2); + let (event_ntfn_sender, event_ntfn_receiver) = mpsc::channel(2); let peer_manager_connection_handler = peer_manager.clone(); let event_notifier = event_ntfn_sender.clone(); let listening_port = args.ldk_peer_listening_port; tokio::spawn(async move { let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", listening_port)).unwrap(); loop { + let peer_mgr = peer_manager_connection_handler.clone(); + let notifier = event_notifier.clone(); let tcp_stream = listener.accept().unwrap().0; - lightning_net_tokio::setup_inbound( - peer_manager_connection_handler.clone(), - event_notifier.clone(), - tcp_stream, - ) - .await; + tokio::spawn(async move { + lightning_net_tokio::setup_inbound(peer_mgr.clone(), notifier.clone(), tcp_stream) + .await; + }); } }); - // Step 17: Connect and Disconnect Blocks + // Step 14: Connect and Disconnect Blocks if chain_tip.is_none() { chain_tip = Some(init::validate_best_block_header(&mut bitcoind_client.deref()).await.unwrap()); @@ -489,18 +499,6 @@ async fn start_ldk() { } }); - // Step 17 & 18: Initialize ChannelManager persistence & Once Per Minute: ChannelManager's - // timer_chan_freshness_every_min() and PeerManager's timer_tick_occurred - let data_dir = ldk_data_dir.clone(); - let persist_channel_manager_callback = - move |node: &ChannelManager| FilesystemPersister::persist_manager(data_dir.clone(), &*node); - BackgroundProcessor::start( - persist_channel_manager_callback, - channel_manager.clone(), - peer_manager.clone(), - logger.clone(), - ); - // Step 15: Initialize LDK Event Handling let channel_manager_event_listener = channel_manager.clone(); let chain_monitor_event_listener = chain_monitor.clone(); @@ -521,10 +519,22 @@ async fn start_ldk() { inbound_pmts_for_events, outbound_pmts_for_events, network, + event_ntfn_receiver, ) .await; }); + // Step 16 & 17: Persist ChannelManager & Background Processing + let data_dir = ldk_data_dir.clone(); + let persist_channel_manager_callback = + move |node: &ChannelManager| FilesystemPersister::persist_manager(data_dir.clone(), &*node); + BackgroundProcessor::start( + persist_channel_manager_callback, + channel_manager.clone(), + peer_manager.clone(), + logger.clone(), + ); + // Reconnect to channel peers if possible. let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone()); match disk::read_channel_peer_data(Path::new(&peer_data_path)) { @@ -549,10 +559,10 @@ async fn start_ldk() { cli::poll_for_user_input( peer_manager.clone(), channel_manager.clone(), + keys_manager.clone(), router.clone(), inbound_payments, outbound_payments, - keys_manager.get_node_secret(), event_ntfn_sender, ldk_data_dir.clone(), logger.clone(),