Merge pull request #4 from valentinewallace/sample-v0
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 4 May 2021 00:21:10 +0000 (17:21 -0700)
committerGitHub <noreply@github.com>
Tue, 4 May 2021 00:21:10 +0000 (17:21 -0700)
Sample v0

12 files changed:
.github/workflows/build.yml [new file with mode: 0644]
.gitignore
Cargo.lock [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
README.md
rustfmt.toml [new file with mode: 0644]
src/bitcoind_client.rs [new file with mode: 0644]
src/cli.rs [new file with mode: 0644]
src/convert.rs [new file with mode: 0644]
src/disk.rs [new file with mode: 0644]
src/hex_utils.rs [new file with mode: 0644]
src/main.rs [new file with mode: 0644]

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644 (file)
index 0000000..1a70ea0
--- /dev/null
@@ -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
index 088ba6ba7d345b76aa2b8dc021dd25e1323189b3..bf68587d855fbcf7926fa4b8a739b27af82765b7 100644 (file)
@@ -8,3 +8,4 @@ Cargo.lock
 
 # These are backup files generated by rustfmt
 **/*.rs.bk
+.ldk
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644 (file)
index 0000000..243c2c0
--- /dev/null
@@ -0,0 +1,793 @@
+# 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"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..b32be57
--- /dev/null
@@ -0,0 +1,43 @@
+[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" ] }
index 6358f4ff81e5cac1ce012d546bd7637983cbd8ab..5dda263e31da6b421623d87ebaa0c47e8e2a2876 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,9 +1,25 @@
 # 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)
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644 (file)
index 0000000..c00f655
--- /dev/null
@@ -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"
diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs
new file mode 100644 (file)
index 0000000..58510ae
--- /dev/null
@@ -0,0 +1,237 @@
+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();
+               });
+       }
+}
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644 (file)
index 0000000..c5dda5f
--- /dev/null
@@ -0,0 +1,647 @@
+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()))
+}
diff --git a/src/convert.rs b/src/convert.rs
new file mode 100644 (file)
index 0000000..7dbd8cc
--- /dev/null
@@ -0,0 +1,86 @@
+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(),
+               })
+       }
+}
diff --git a/src/disk.rs b/src/disk.rs
new file mode 100644 (file)
index 0000000..827cb7e
--- /dev/null
@@ -0,0 +1,67 @@
+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)
+}
diff --git a/src/hex_utils.rs b/src/hex_utils.rs
new file mode 100644 (file)
index 0000000..70fe28b
--- /dev/null
@@ -0,0 +1,42 @@
+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,
+       }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644 (file)
index 0000000..b024e93
--- /dev/null
@@ -0,0 +1,577 @@
+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;
+}