--- /dev/null
+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
# These are backup files generated by rustfmt
**/*.rs.bk
+.ldk
--- /dev/null
+# 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 = "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.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4408a9bf5c378a42ca9039e4ef3e3d8df3443f76d2ebe249fd720a2c5e17d2da"
+
+[[package]]
+name = "bech32"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c"
+
+[[package]]
+name = "bitcoin"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec5f88a446d66e7474a3b8fa2e348320b574463fb78d799d90ba68f79f48e0e"
+dependencies = [
+ "bech32 0.7.2",
+ "bitcoin_hashes",
+ "secp256k1",
+]
+
+[[package]]
+name = "bitcoin-bech32"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5791779c83cf6bde070a92c22505f2faa06487fc8f372e4d485c41a71351105"
+dependencies = [
+ "bech32 0.4.1",
+]
+
+[[package]]
+name = "bitcoin_hashes"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0aaf87b776808e26ae93289bc7d025092b6d909c193f0cdee0b3a86e7bd3c776"
+
+[[package]]
+name = "bumpalo"
+version = "3.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
+
+[[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 = "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 = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
+[[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",
+ "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 = "itoa"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
+
+[[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 = [
+ "base64",
+ "bech32 0.7.2",
+ "bitcoin",
+ "bitcoin-bech32",
+ "hex",
+ "lightning",
+ "lightning-background-processor",
+ "lightning-block-sync",
+ "lightning-invoice",
+ "lightning-net-tokio",
+ "lightning-persister",
+ "rand",
+ "serde_json",
+ "time",
+ "tokio",
+]
+
+[[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.13"
+source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482"
+dependencies = [
+ "bitcoin",
+]
+
+[[package]]
+name = "lightning-background-processor"
+version = "0.0.13"
+source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482"
+dependencies = [
+ "bitcoin",
+ "lightning",
+ "lightning-persister",
+]
+
+[[package]]
+name = "lightning-block-sync"
+version = "0.0.13"
+source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482"
+dependencies = [
+ "bitcoin",
+ "chunked_transfer",
+ "futures",
+ "lightning",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lightning-invoice"
+version = "0.4.0"
+source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482"
+dependencies = [
+ "bech32 0.7.2",
+ "bitcoin_hashes",
+ "lightning",
+ "num-traits",
+ "secp256k1",
+]
+
+[[package]]
+name = "lightning-net-tokio"
+version = "0.0.13"
+source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482"
+dependencies = [
+ "bitcoin",
+ "lightning",
+ "tokio",
+]
+
+[[package]]
+name = "lightning-persister"
+version = "0.0.13"
+source = "git+https://github.com/rust-bitcoin/rust-lightning?rev=d4d322580994857b1222488f8467311d6db61482#d4d322580994857b1222488f8467311d6db61482"
+dependencies = [
+ "bitcoin",
+ "libc",
+ "lightning",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "mio"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
+dependencies = [
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[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.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",
+]
+
+[[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.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "733b114f058f260c0af7591434eef4272ae1a8ec2751766d3cb89c6df8d5e450"
+dependencies = [
+ "secp256k1-sys",
+]
+
+[[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",
+ "libc",
+ "winapi",
+]
+
+[[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",
+]
+
+[[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 = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "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",
+ "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.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
--- /dev/null
+[package]
+name = "ldk-tutorial-node"
+version = "0.1.0"
+authors = ["Valentine Wallace <vwallace@protonmail.com>"]
+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]
+# 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", 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"], 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", 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", 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" }
+tokio = { version = "1.0", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
# ldk-sample
-sample node implementation using LDK
+Sample node implementation using LDK.
+
+## Installation
+```
+git clone git@github.com:lightningdevkit/ldk-sample.git
+```
+
+## Usage
+```
+cd ldk-sample
+cargo run <bitcoind-rpc-username>:<bitcoind-rpc-password>@<bitcoind-rpc-host>:<bitcoind-rpc-port> <ldk_storage_directory_path> [<ldk-peer-listening-port>] [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)
--- /dev/null
+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"
--- /dev/null
+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::atomic::{AtomicU32, Ordering};
+use std::sync::Arc;
+use std::time::Duration;
+use tokio::sync::Mutex;
+
+pub struct BitcoindClient {
+ bitcoind_rpc_client: Arc<Mutex<RpcClient>>,
+ host: String,
+ port: u16,
+ rpc_user: String,
+ rpc_password: String,
+ fees: Arc<HashMap<Target, AtomicU32>>,
+}
+
+#[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<u32>,
+ ) -> 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<u32>)> {
+ Box::pin(async move {
+ let mut rpc = self.bitcoind_rpc_client.lock().await;
+ rpc.get_best_block().await
+ })
+ }
+}
+
+impl BitcoindClient {
+ pub async fn new(
+ host: String, port: u16, rpc_user: String, rpc_password: String,
+ ) -> std::io::Result<Self> {
+ 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<Target, AtomicU32> = 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: Arc::new(Mutex::new(bitcoind_rpc_client)),
+ host,
+ port,
+ rpc_user,
+ rpc_password,
+ 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<HashMap<Target, AtomicU32>>, rpc_client: Arc<Mutex<RpcClient>>,
+ ) {
+ 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::<FeeResponse>(
+ "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::<FeeResponse>(
+ "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::<FeeResponse>(
+ "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);
+ tokio::time::sleep(Duration::from_secs(60)).await;
+ }
+ });
+ }
+
+ pub fn get_new_rpc_client(&self) -> std::io::Result<RpcClient> {
+ 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 async fn create_raw_transaction(&self, outputs: Vec<HashMap<String, f64>>) -> RawTx {
+ let mut rpc = self.bitcoind_rpc_client.lock().await;
+
+ let outputs_json = serde_json::json!(outputs);
+ rpc.call_method::<RawTx>("createrawtransaction", &vec![serde_json::json!([]), outputs_json])
+ .await
+ .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);
+ rpc.call_method::<RawTx>("sendrawtransaction", &[raw_tx_json]).await.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);
+ rpc.call_method("signrawtransactionwithwallet", &vec![tx_hex_json]).await.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 = rpc.call_method::<NewAddress>("getnewaddress", &addr_args).await.unwrap();
+ Address::from_str(addr.0.as_str()).unwrap()
+ }
+
+ pub async fn get_blockchain_info(&self) -> BlockchainInfo {
+ let mut rpc = self.bitcoind_rpc_client.lock().await;
+ rpc.call_method::<BlockchainInfo>("getblockchaininfo", &vec![]).await.unwrap()
+ }
+}
+
+impl FeeEstimator for BitcoindClient {
+ fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
+ 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)
+ }
+ }
+ }
+}
+
+impl BroadcasterInterface for BitcoindClient {
+ fn broadcast_transaction(&self, tx: &Transaction) {
+ let bitcoind_rpc_client = self.bitcoind_rpc_client.clone();
+ let tx_serialized = serde_json::json!(encode::serialize_hex(tx));
+ tokio::spawn(async move {
+ let mut rpc = bitcoind_rpc_client.lock().await;
+ rpc.call_method::<RawTx>("sendrawtransaction", &vec![tx_serialized]).await.unwrap();
+ });
+ }
+}
--- /dev/null
+use crate::disk;
+use crate::hex_utils;
+use crate::{
+ ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, PaymentInfoStorage,
+ PeerManager,
+};
+use bitcoin::network::constants::Network;
+use bitcoin::secp256k1::key::PublicKey;
+use lightning::chain;
+use lightning::chain::keysinterface::KeysManager;
+use lightning::ln::features::InvoiceFeatures;
+use lightning::ln::{PaymentHash, PaymentSecret};
+use lightning::routing::network_graph::NetGraphMsgHandler;
+use lightning::routing::router;
+use lightning::routing::router::RouteHintHop;
+use lightning::util::config::UserConfig;
+use lightning_invoice::{utils, Currency, Invoice};
+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::time::Duration;
+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) fn parse_startup_args() -> Result<LdkUserInfo, ()> {
+ if env::args().len() < 3 {
+ println!("ldk-tutorial-node requires 3 arguments: `cargo run <bitcoind-rpc-username>:<bitcoind-rpc-password>@<bitcoind-rpc-host>:<bitcoind-rpc-port> ldk_storage_directory_path [<ldk-incoming-peer-listening-port>] [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::<u16>().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) async fn poll_for_user_input(
+ peer_manager: Arc<PeerManager>, channel_manager: Arc<ChannelManager>,
+ keys_manager: Arc<KeysManager>,
+ router: Arc<NetGraphMsgHandler<Arc<dyn chain::Access + Send + Sync>, Arc<FilesystemLogger>>>,
+ inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage,
+ event_notifier: mpsc::Sender<()>, ldk_data_dir: String, logger: Arc<FilesystemLogger>,
+ network: Network,
+) {
+ println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at <your-supplied-ldk-data-dir-path>/.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<u64, _> = 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(),
+ )
+ .is_err()
+ {
+ print!("> ");
+ io::stdout().flush().unwrap();
+ continue;
+ };
+
+ let announce_channel = match words.next() {
+ Some("--public") | Some("--public=true") => true,
+ Some("--public=false") => false,
+ Some(_) => {
+ println!("ERROR: invalid `--public` command format. Valid formats: `--public`, `--public=true` `--public=false`");
+ 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 <invoice>`");
+ 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 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() 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 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,
+ last_hops,
+ router.clone(),
+ channel_manager.clone(),
+ outbound_payments.clone(),
+ logger.clone(),
+ );
+ }
+ "getinvoice" => {
+ let amt_str = words.next();
+ if amt_str.is_none() {
+ println!("ERROR: getinvoice requires an amount in millisatoshis");
+ print!("> ");
+ io::stdout().flush().unwrap();
+ continue;
+ }
+
+ let amt_msat: Result<u64, _> = 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_msat.unwrap(),
+ inbound_payments.clone(),
+ channel_manager.clone(),
+ keys_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(),
+ )
+ .is_ok()
+ {
+ println!("SUCCESS: connected to peer {}", pubkey);
+ }
+ }
+ "listchannels" => list_channels(channel_manager.clone()),
+ "listpayments" => {
+ list_payments(inbound_payments.clone(), outbound_payments.clone())
+ }
+ "closechannel" => {
+ let channel_id_str = words.next();
+ if channel_id_str.is_none() {
+ println!("ERROR: closechannel requires a channel ID: `closechannel <channel_id>`");
+ 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 <channel_id>`");
+ 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());
+ }
+ "nodeinfo" => node_info(channel_manager.clone(), peer_manager.clone()),
+ "listpeers" => list_peers(peer_manager.clone()),
+ _ => println!("Unknown command. See `\"help\" for available commands."),
+ }
+ }
+ print!("> ");
+ io::stdout().flush().unwrap();
+ }
+}
+
+fn help() {
+ println!("openchannel pubkey@host:port <channel_amt_satoshis>");
+ println!("sendpayment <invoice>");
+ println!("getinvoice <amt_in_millisatoshis>");
+ println!("connectpeer pubkey@host:port");
+ println!("listchannels");
+ println!("listpayments");
+ println!("closechannel <channel_id>");
+ println!("forceclosechannel <channel_id>");
+}
+
+fn node_info(channel_manager: Arc<ChannelManager>, peer_manager: Arc<PeerManager>) {
+ 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<PeerManager>) {
+ println!("\t{{");
+ for pubkey in peer_manager.get_peer_node_ids() {
+ println!("\t\t pubkey: {}", pubkey);
+ }
+ println!("\t}},");
+}
+
+fn list_channels(channel_manager: Arc<ChannelManager>) {
+ 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(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 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: outbound,");
+ println!(
+ "\t\thtlc_status: {},",
+ match payment_info.status {
+ 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<PeerManager>,
+ event_notifier: mpsc::Sender<()>,
+) -> 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();
+ tokio::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 open_channel(
+ peer_pubkey: PublicKey, channel_amt_sat: u64, announce_channel: bool,
+ channel_manager: Arc<ChannelManager>,
+) -> 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 send_payment(
+ payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash,
+ payment_secret: Option<PaymentSecret>, payee_features: Option<InvoiceFeatures>,
+ route_hints: Vec<RouteHintHop>,
+ router: Arc<NetGraphMsgHandler<Arc<dyn chain::Access + Send + Sync>, Arc<FilesystemLogger>>>,
+ channel_manager: Arc<ChannelManager>, payment_storage: PaymentInfoStorage,
+ logger: Arc<FilesystemLogger>,
+) {
+ 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<_>>()),
+ &route_hints.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,
+ PaymentInfo {
+ preimage: None,
+ secret: payment_secret,
+ status,
+ amt_msat: MillisatAmount(Some(amt_msat)),
+ },
+ );
+}
+
+fn get_invoice(
+ amt_msat: u64, payment_storage: PaymentInfoStorage, channel_manager: Arc<ChannelManager>,
+ keys_manager: Arc<KeysManager>, network: Network,
+) {
+ let mut payments = payment_storage.lock().unwrap();
+ let currency = match network {
+ Network::Bitcoin => Currency::Bitcoin,
+ Network::Testnet => Currency::BitcoinTestnet,
+ Network::Regtest => Currency::Regtest,
+ 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
+ }
+ 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(
+ payment_hash,
+ PaymentInfo {
+ 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: Some(invoice.payment_secret().unwrap().clone()),
+ status: HTLCStatus::Pending,
+ amt_msat: MillisatAmount(Some(amt_msat)),
+ },
+ );
+}
+
+fn close_channel(channel_id: [u8; 32], channel_manager: Arc<ChannelManager>) {
+ 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<ChannelManager>) {
+ 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<SocketAddr, _> = 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()))
+}
--- /dev/null
+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,
+}
+
+impl TryInto<FundedTx> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<FundedTx> {
+ 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<RawTx> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<RawTx> {
+ Ok(RawTx(self.0.as_str().unwrap().to_string()))
+ }
+}
+
+pub struct SignedTx {
+ pub complete: bool,
+ pub hex: String,
+}
+
+impl TryInto<SignedTx> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<SignedTx> {
+ 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<NewAddress> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<NewAddress> {
+ Ok(NewAddress(self.0.as_str().unwrap().to_string()))
+ }
+}
+
+pub struct FeeResponse {
+ pub feerate: Option<u32>,
+ pub errored: bool,
+}
+
+impl TryInto<FeeResponse> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<FeeResponse> {
+ 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,
+ },
+ })
+ }
+}
+
+pub struct BlockchainInfo {
+ pub latest_height: usize,
+ pub latest_blockhash: BlockHash,
+}
+
+impl TryInto<BlockchainInfo> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<BlockchainInfo> {
+ Ok(BlockchainInfo {
+ latest_height: self.0["blocks"].as_u64().unwrap() as usize,
+ latest_blockhash: BlockHash::from_hex(self.0["bestblockhash"].as_str().unwrap())
+ .unwrap(),
+ })
+ }
+}
--- /dev/null
+use crate::cli;
+use bitcoin::secp256k1::key::PublicKey;
+use lightning::util::logger::{Logger, Record};
+use lightning::util::ser::Writer;
+use std::collections::HashMap;
+use std::fs;
+use std::fs::File;
+use std::io::{BufRead, BufReader};
+use std::net::SocketAddr;
+use std::path::Path;
+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<HashMap<PublicKey, SocketAddr>, 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)
+}
--- /dev/null
+use bitcoin::secp256k1::key::PublicKey;
+
+pub fn to_vec(hex: &str) -> Option<Vec<u8>> {
+ 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 to_compressed_pubkey(hex: &str) -> Option<PublicKey> {
+ 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,
+ }
+}
--- /dev/null
+pub mod bitcoind_client;
+mod cli;
+mod convert;
+mod disk;
+mod hex_utils;
+
+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::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 lightning::chain;
+use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
+use lightning::chain::chainmonitor;
+use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager};
+use lightning::chain::Filter;
+use lightning::chain::Watch;
+use lightning::ln::channelmanager;
+use lightning::ln::channelmanager::{
+ 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};
+use lightning::util::ser::ReadableArgs;
+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 std::collections::hash_map::Entry;
+use std::collections::HashMap;
+use std::fmt;
+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::time::{Duration, SystemTime};
+use tokio::sync::mpsc;
+use tokio::sync::mpsc::Receiver;
+
+pub(crate) enum HTLCStatus {
+ Pending,
+ Succeeded,
+ Failed,
+}
+
+pub(crate) struct MillisatAmount(Option<u64>);
+
+impl fmt::Display for MillisatAmount {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.0 {
+ Some(amt) => write!(f, "{}", amt),
+ None => write!(f, "unknown"),
+ }
+ }
+}
+
+pub(crate) struct PaymentInfo {
+ preimage: Option<PaymentPreimage>,
+ secret: Option<PaymentSecret>,
+ status: HTLCStatus,
+ amt_msat: MillisatAmount,
+}
+
+pub(crate) type PaymentInfoStorage = Arc<Mutex<HashMap<PaymentHash, PaymentInfo>>>;
+
+type ChainMonitor = chainmonitor::ChainMonitor<
+ InMemorySigner,
+ Arc<dyn Filter + Send + Sync>,
+ Arc<BitcoindClient>,
+ Arc<BitcoindClient>,
+ Arc<FilesystemLogger>,
+ Arc<FilesystemPersister>,
+>;
+
+pub(crate) type PeerManager = SimpleArcPeerManager<
+ SocketDescriptor,
+ ChainMonitor,
+ BitcoindClient,
+ BitcoindClient,
+ dyn chain::Access + Send + Sync,
+ FilesystemLogger,
+>;
+
+pub(crate) type ChannelManager =
+ SimpleArcChannelManager<ChainMonitor, BitcoindClient, BitcoindClient, FilesystemLogger>;
+
+async fn handle_ldk_events(
+ channel_manager: Arc<ChannelManager>, chain_monitor: Arc<ChainMonitor>,
+ bitcoind_client: Arc<BitcoindClient>, keys_manager: Arc<KeysManager>,
+ 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());
+ 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).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).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).await;
+ assert_eq!(signed_tx.complete, true);
+ let final_tx: Transaction =
+ encode::deserialize(&hex_utils::to_vec(&signed_tx.hex).unwrap()).unwrap();
+ // Give the funding transaction back to LDK for opening the channel.
+ loop_channel_manager
+ .funding_transaction_generated(&temporary_channel_id, final_tx)
+ .unwrap();
+ }
+ Event::PaymentReceived {
+ payment_hash,
+ payment_preimage,
+ payment_secret,
+ amt,
+ ..
+ } => {
+ let mut payments = inbound_payments.lock().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),
+ amt
+ );
+ print!("> ");
+ io::stdout().flush().unwrap();
+ HTLCStatus::Succeeded
+ }
+ _ => 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 } => {
+ let hashed = PaymentHash(Sha256::hash(&payment_preimage.0).into_inner());
+ let mut payments = outbound_payments.lock().unwrap();
+ for (payment_hash, payment) in payments.iter_mut() {
+ if *payment_hash == hashed {
+ payment.preimage = Some(payment_preimage);
+ payment.status = HTLCStatus::Succeeded;
+ println!(
+ "\nEVENT: successfully sent payment of {} millisatoshis from \
+ payment hash {:?} with preimage {:?}",
+ payment.amt_msat,
+ 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 = outbound_payments.lock().unwrap();
+ if payments.contains_key(&payment_hash) {
+ let payment = payments.get_mut(&payment_hash).unwrap();
+ payment.status = HTLCStatus::Failed;
+ }
+ }
+ Event::PendingHTLCsForwardable { time_forwardable } => {
+ let forwarding_channel_manager = loop_channel_manager.clone();
+ tokio::spawn(async move {
+ 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();
+ });
+ }
+ Event::SpendableOutputs { outputs } => {
+ let destination_address = bitcoind_client.get_new_address().await;
+ let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
+ 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);
+ }
+ }
+ }
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ }
+}
+
+async fn start_ldk() {
+ 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(),
+ )
+ .await
+ {
+ Ok(client) => Arc::new(client),
+ Err(e) => {
+ println!("Failed to connect to bitcoind client: {}", e);
+ return;
+ }
+ };
+
+ // ## 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<ChainMonitor> = Arc::new(chainmonitor::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);
+ 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();
+ let keys_manager = Arc::new(KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos()));
+
+ // Step 7: Read ChannelMonitor state from disk
+ let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone()).unwrap();
+
+ // Step 8: Initialize the ChannelManager
+ let user_config = UserConfig::default();
+ 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 channelmonitors.iter_mut() {
+ channel_monitor_mut_references.push(channel_monitor);
+ }
+ 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().await;
+
+ let chain_params = ChainParameters {
+ network: args.network,
+ best_block: BestBlock::new(
+ getinfo_resp.latest_blockhash,
+ getinfo_resp.latest_height as u32,
+ ),
+ };
+ 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 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<poll::ValidatedBlockHeader> = None;
+ if restarting_node {
+ let mut chain_listeners =
+ vec![(channel_manager_blockhash, &mut channel_manager as &mut dyn chain::Listen)];
+
+ 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()),
+ 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(
+ init::synchronize_listeners(
+ &mut bitcoind_client.deref(),
+ args.network,
+ &mut cache,
+ chain_listeners,
+ )
+ .await
+ .unwrap(),
+ );
+ }
+
+ // 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 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::<Arc<dyn chain::Access + Send + Sync>>,
+ logger.clone(),
+ ));
+
+ // Step 12: Initialize the PeerManager
+ let channel_manager: Arc<ChannelManager> = 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<PeerManager> = Arc::new(PeerManager::new(
+ lightning_msg_handler,
+ keys_manager.get_node_secret(),
+ &ephemeral_bytes,
+ logger.clone(),
+ ));
+
+ // ## Running LDK
+ // 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 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;
+ tokio::spawn(async move {
+ lightning_net_tokio::setup_inbound(peer_mgr.clone(), notifier.clone(), tcp_stream)
+ .await;
+ });
+ }
+ });
+
+ // 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());
+ }
+ 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 mut derefed = bitcoind_block_source.deref();
+ let chain_poller = poll::ChainPoller::new(&mut derefed, 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();
+ tokio::time::sleep(Duration::from_secs(1)).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 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 {
+ handle_ldk_events(
+ channel_manager_event_listener,
+ chain_monitor_event_listener,
+ bitcoind_rpc,
+ keys_manager_listener,
+ 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)) {
+ Ok(mut info) => {
+ for (pubkey, peer_addr) in info.drain() {
+ 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(),
+ );
+ }
+ }
+ }
+ }
+ 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(),
+ keys_manager.clone(),
+ router.clone(),
+ inbound_payments,
+ outbound_payments,
+ event_ntfn_sender,
+ ldk_data_dir.clone(),
+ logger.clone(),
+ args.network,
+ )
+ .await;
+}
+
+#[tokio::main]
+pub async fn main() {
+ start_ldk().await;
+}