diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ea6c4cd6..462d0f9b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,7 +39,7 @@ jobs: - name: test run: cargo test --verbose --all-features --lib - build-payjoin-cli: + test-payjoin-cli: runs-on: ubuntu-latest defaults: run: @@ -58,7 +58,7 @@ jobs: override: true - name: build payjoin cli example run: | - cargo build + cargo test --verbose --features=local-https fmt: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 7292d7b3..9e2e85d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target -*config.toml \ No newline at end of file +*config.toml +*seen_inputs.json \ No newline at end of file diff --git a/payjoin-cli/CHANGELOG.md b/payjoin-cli/CHANGELOG.md new file mode 100644 index 00000000..f1f1c1aa --- /dev/null +++ b/payjoin-cli/CHANGELOG.md @@ -0,0 +1,5 @@ +# payjoin-cli Changelog + +## 0.0.1 + +- Release initial payjoin-cli to send and receive payjoin from bitcoind diff --git a/payjoin-cli/Cargo.lock b/payjoin-cli/Cargo.lock index f568c469..e39f4420 100644 --- a/payjoin-cli/Cargo.lock +++ b/payjoin-cli/Cargo.lock @@ -244,7 +244,7 @@ checksum = "9d6c0ee9354e3dac217db4cb1dd31941073a87fe53c86bcf3eb2b8bc97f00a08" dependencies = [ "bitcoin-private", "bitcoincore-rpc-json", - "jsonrpc", + "jsonrpc 0.14.1", "log", "serde", "serde_json", @@ -262,6 +262,24 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bitcoind" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395fe8137d13c6859fa68421779cd5a4db8cd7b0ef3122537e56f7a49a0598a9" +dependencies = [ + "anyhow", + "bitcoin_hashes", + "core-rpc", + "flate2", + "log", + "minreq", + "tar", + "tempfile", + "which", + "zip", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -270,9 +288,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -320,17 +338,44 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cc" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] @@ -427,6 +472,32 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core-rpc" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d77079e1b71c2778d6e1daf191adadcd4ff5ec3ccad8298a79061d865b235b" +dependencies = [ + "bitcoin-private", + "core-rpc-json", + "jsonrpc 0.13.0", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "core-rpc-json" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581898ed9a83f31c64731b1d8ca2dfffcfec14edf1635afacd5234cddbde3a41" +dependencies = [ + "bitcoin", + "bitcoin-private", + "serde", + "serde_json", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -481,6 +552,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -538,10 +615,20 @@ checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "windows-sys", ] +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -638,7 +725,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -705,6 +792,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + [[package]] name = "http" version = "0.2.9" @@ -868,6 +964,17 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonrpc" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8d6b3f301ba426b30feca834a2a18d48d5b54e5065496b5c1b05537bee3639" +dependencies = [ + "base64 0.13.1", + "serde", + "serde_json", +] + [[package]] name = "jsonrpc" version = "0.14.1" @@ -887,9 +994,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linked-hash-map" @@ -899,9 +1006,19 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" @@ -946,6 +1063,19 @@ dependencies = [ "adler", ] +[[package]] +name = "minreq" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3371dfc7b772c540da1380123674a8e20583aca99907087d990ca58cf44203" +dependencies = [ + "log", + "once_cell", + "rustls", + "rustls-webpki", + "webpki-roots", +] + [[package]] name = "mio" version = "0.8.8" @@ -953,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1110,6 +1240,29 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -1134,6 +1287,7 @@ dependencies = [ "anyhow", "bip21", "bitcoincore-rpc", + "bitcoind", "clap", "config", "env_logger", @@ -1143,6 +1297,7 @@ dependencies = [ "reqwest", "rouille", "serde", + "tokio", ] [[package]] @@ -1296,8 +1451,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4954fbc00dcd4d8282c987710e50ba513d351400dbdd00e803a05172a90d8976" dependencies = [ "pem", - "ring", - "time", + "ring 0.16.20", + "time 0.3.22", "yasna", ] @@ -1310,6 +1465,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.9.3" @@ -1385,12 +1549,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "ron" version = "0.7.1" @@ -1421,7 +1599,7 @@ dependencies = [ "serde_json", "sha1_smol", "threadpool", - "time", + "time 0.3.22", "tiny_http", "url", ] @@ -1444,17 +1622,39 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.7" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", "windows-sys", ] +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "log", + "ring 0.17.5", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.15" @@ -1476,6 +1676,22 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + [[package]] name = "secp256k1" version = "0.27.0" @@ -1580,6 +1796,15 @@ dependencies = [ "digest", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.8" @@ -1589,6 +1814,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + [[package]] name = "socket2" version = "0.4.9" @@ -1605,6 +1836,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.10.0" @@ -1622,6 +1859,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.7.1" @@ -1630,7 +1878,7 @@ checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys", ] @@ -1673,6 +1921,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.22" @@ -1732,11 +1991,25 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys", ] +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1859,6 +2132,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.0" @@ -1897,6 +2176,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1979,6 +2264,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2094,6 +2397,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "xattr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +dependencies = [ + "libc", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -2109,7 +2421,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time", + "time 0.3.22", ] [[package]] @@ -2117,3 +2429,17 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time 0.1.45", +] diff --git a/payjoin-cli/Cargo.toml b/payjoin-cli/Cargo.toml index c16e50d1..e8a6442b 100644 --- a/payjoin-cli/Cargo.toml +++ b/payjoin-cli/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" resolver = "2" [[bin]] -name = "payjoin" +name = "payjoin-cli" path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -29,3 +29,7 @@ reqwest = { version = "0.11.4", features = ["blocking"] } rcgen = { version = "0.11.1", optional = true } rouille = "3.6.2" serde = { version = "1.0.160", features = ["derive"] } + +[dev-dependencies] +bitcoind = { version = "0.31.1", features = ["0_21_2"] } +tokio = { version = "1.12.0", features = ["full"] } \ No newline at end of file diff --git a/payjoin-cli.Dockerfile b/payjoin-cli/Dockerfile similarity index 100% rename from payjoin-cli.Dockerfile rename to payjoin-cli/Dockerfile diff --git a/payjoin-cli/README.md b/payjoin-cli/README.md index 707105c1..a9bcf0eb 100644 --- a/payjoin-cli/README.md +++ b/payjoin-cli/README.md @@ -64,7 +64,7 @@ Using the previously generated bip21 URI, run the following command from the sender directory: ```console - RUST_LOG=debug cargo run -- send "[BIP21 URI]" + RUST_LOG=debug cargo run -- send --fee-rate ``` You should see the payjoin transaction occur and be able to verify the diff --git a/payjoin-cli/src/app.rs b/payjoin-cli/src/app.rs index 731e7b8b..a205a9ea 100644 --- a/payjoin-cli/src/app.rs +++ b/payjoin-cli/src/app.rs @@ -81,22 +81,27 @@ impl App { .psbt; let psbt = Psbt::from_str(&psbt).with_context(|| "Failed to load PSBT from base64")?; log::debug!("Original psbt: {:#?}", psbt); - + let fallback_tx = psbt.clone().extract_tx(); let (req, ctx) = payjoin::send::RequestBuilder::from_psbt_and_uri(psbt, uri) .with_context(|| "Failed to build payjoin request")? .build_recommended(fee_rate) .with_context(|| "Failed to build payjoin request")?; - let client = reqwest::blocking::Client::builder() .danger_accept_invalid_certs(self.config.danger_accept_invalid_certs) .build() .with_context(|| "Failed to build reqwest http client")?; + println!("Sending fallback request to {}", &req.url); let mut response = client .post(req.url) .body(req.body) .header("Content-Type", "text/plain") .send() .with_context(|| "HTTP request failed")?; + println!("Sent fallback transaction txid: {}", fallback_tx.txid()); + println!( + "Sent fallback transaction hex: {:#}", + payjoin::bitcoin::consensus::encode::serialize_hex(&fallback_tx) + ); // TODO display well-known errors and log::debug the rest let psbt = ctx.process_response(&mut response).with_context(|| "Failed to process response")?; @@ -116,7 +121,7 @@ impl App { .bitcoind .send_raw_transaction(&tx) .with_context(|| "Failed to send raw transaction")?; - log::info!("Transaction sent: {}", txid); + println!("Payjoin sent: {}", txid); Ok(()) } @@ -324,7 +329,10 @@ impl App { log::debug!("Receiver's Payjoin proposal PSBT Rsponse: {:#?}", payjoin_proposal_psbt); let payload = payjoin::base64::encode(&payjoin_proposal_psbt.serialize()); - log::info!("successful response"); + println!( + "Responded with Payjoin proposal {}", + payjoin_proposal_psbt.clone().extract_tx().txid() + ); Ok(Response::text(payload)) } @@ -410,7 +418,7 @@ impl AppConfig { .set_default("pj_host", "0.0.0.0:3000")? .set_default("pj_endpoint", "https://localhost:3000")? .set_default("sub_only", false)? - .add_source(File::new("config.toml", FileFormat::Toml)); + .add_source(File::new("config.toml", FileFormat::Toml).required(false)); let builder = match matches.subcommand() { Some(("send", matches)) => builder.set_override_option( diff --git a/payjoin-cli/src/main.rs b/payjoin-cli/src/main.rs index 75f18f6c..44226c11 100644 --- a/payjoin-cli/src/main.rs +++ b/payjoin-cli/src/main.rs @@ -59,7 +59,9 @@ fn cli() -> ArgMatches { .value_parser(value_parser!(f32)), ) .arg(Arg::new("DANGER_ACCEPT_INVALID_CERTS") + .long("danger-accept-invalid-certs") .hide(true) + .action(clap::ArgAction::SetTrue) .help("Wicked dangerous! Vulnerable to MITM attacks! Accept invalid certs for the payjoin endpoint")) ) .subcommand( diff --git a/payjoin-cli/tests/e2e.rs b/payjoin-cli/tests/e2e.rs new file mode 100644 index 00000000..7df527d5 --- /dev/null +++ b/payjoin-cli/tests/e2e.rs @@ -0,0 +1,140 @@ +#[cfg(all(feature = "local-https"))] +mod e2e { + use std::env; + use std::process::Stdio; + + use bitcoind::bitcoincore_rpc::core_rpc_json::AddressType; + use bitcoind::bitcoincore_rpc::RpcApi; + use log::{log_enabled, Level}; + use payjoin::bitcoin::Amount; + use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; + use tokio::process::Command; + + const RECEIVE_SATS: &str = "54321"; + + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] + async fn send_receive_payjoin() { + env::set_var("RUST_LOG", "debug"); + + let bitcoind_exe = env::var("BITCOIND_EXE") + .ok() + .or_else(|| bitcoind::downloaded_exe_path().ok()) + .expect("version feature or env BITCOIND_EXE is required for tests"); + let mut conf = bitcoind::Conf::default(); + conf.view_stdout = log_enabled!(Level::Debug); + let bitcoind = bitcoind::BitcoinD::with_conf(bitcoind_exe, &conf).unwrap(); + let receiver = bitcoind.create_wallet("receiver").unwrap(); + let receiver_address = + receiver.get_new_address(None, Some(AddressType::Bech32)).unwrap().assume_checked(); + let sender = bitcoind.create_wallet("sender").unwrap(); + let sender_address = + sender.get_new_address(None, Some(AddressType::Bech32)).unwrap().assume_checked(); + bitcoind.client.generate_to_address(1, &receiver_address).unwrap(); + bitcoind.client.generate_to_address(101, &sender_address).unwrap(); + + assert_eq!( + Amount::from_btc(50.0).unwrap(), + receiver.get_balances().unwrap().mine.trusted, + "receiver doesn't own bitcoin" + ); + + assert_eq!( + Amount::from_btc(50.0).unwrap(), + sender.get_balances().unwrap().mine.trusted, + "sender doesn't own bitcoin" + ); + + let receiver_rpchost = format!("{}/wallet/receiver", bitcoind.params.rpc_socket); + let sender_rpchost = format!("{}/wallet/sender", bitcoind.params.rpc_socket); + let cookie_file = &bitcoind.params.cookie_file; + let pj_host = find_free_port(); + let pj_endpoint = format!("https://localhost:{}", pj_host); + + let payjoin_cli = "target/debug/payjoin-cli"; + + let mut cli_receiver = Command::new(payjoin_cli) + .arg("--rpchost") + .arg(&receiver_rpchost) + .arg("--cookie-file") + .arg(&cookie_file) + .arg("receive") + .arg(RECEIVE_SATS) + .arg("--host-port") + .arg(&pj_host.to_string()) + .arg("--endpoint") + .arg(&pj_endpoint) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute payjoin-cli"); + + let stdout = cli_receiver.stdout.take().expect("Failed to take stdout of child process"); + let reader = BufReader::new(stdout); + let mut stdout = tokio::io::stdout(); + let mut bip21 = String::new(); + + let mut lines = reader.lines(); + + while let Some(line) = lines.next_line().await.expect("Failed to read line from stdout") { + // Write to stdout regardless + stdout + .write_all(format!("{}\n", line).as_bytes()) + .await + .expect("Failed to write to stdout"); + + if line.starts_with("BITCOIN") { + bip21 = line; + break; + } + } + log::debug!("Got bip21 {}", &bip21); + + let mut cli_sender = Command::new(payjoin_cli) + .arg("--rpchost") + .arg(&sender_rpchost) + .arg("--cookie-file") + .arg(&cookie_file) + .arg("send") + .arg(&bip21) + .arg("--fee-rate") + .arg("1") + .arg("--danger-accept-invalid-certs") + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to execute payjoin-cli"); + + let stdout = cli_sender.stdout.take().expect("Failed to take stdout of child process"); + let reader = BufReader::new(stdout); + let (tx, mut rx) = tokio::sync::mpsc::channel(1); + + let mut lines = reader.lines(); + tokio::spawn(async move { + let mut stdout = tokio::io::stdout(); + while let Some(line) = lines.next_line().await.expect("Failed to read line from stdout") + { + stdout + .write_all(format!("{}\n", line).as_bytes()) + .await + .expect("Failed to write to stdout"); + if line.contains("Payjoin sent") { + let _ = tx.send(true).await; + break; + } + } + }); + + let timeout = tokio::time::Duration::from_secs(10); + let payjoin_sent = tokio::time::timeout(timeout, rx.recv()).await; + + cli_receiver.kill().await.expect("Failed to kill payjoin-cli"); + cli_sender.kill().await.expect("Failed to kill payjoin-cli"); + + assert!(payjoin_sent.unwrap_or(Some(false)).unwrap(), "Payjoin send was not detected"); + } + + fn find_free_port() -> u16 { + let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + listener.local_addr().unwrap().port() + } +}