From 60ab3728abe45ad4aa32b7dc35ccb10e29f3b8ad Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Wed, 3 Jun 2020 08:39:23 +0100 Subject: [PATCH] Slatepack Workflow (#423) * debugging tor sends * use fixed yeast version of hyper-socks2 * send command working according to slatepack * changes to handling proof address * modifications of recieve command to work with slatepack and to attempt to return to sender via TOR * finalize command tested * attempting to add invoice processing * fixes to tests, propagation of test mode where needed * modify return values of api functions that can send as sync * cleanup, testing and post_tx function * revert changes to API, many test fixes * deprecate http on the command-line warning --- Cargo.lock | 175 ++++---- api/src/foreign.rs | 96 +++- api/src/foreign_rpc.rs | 28 +- api/src/lib.rs | 2 +- api/src/owner.rs | 203 +++++++-- controller/src/command.rs | 698 +++++++++++++++++++++--------- controller/src/controller.rs | 54 ++- controller/tests/file.rs | 6 +- controller/tests/invoice.rs | 4 +- controller/tests/no_change.rs | 2 +- controller/tests/repost.rs | 2 +- controller/tests/self_send.rs | 2 +- controller/tests/slatepack.rs | 6 +- impls/Cargo.toml | 3 +- impls/src/adapters/http.rs | 98 +++-- impls/src/adapters/keybase.rs | 2 +- impls/src/adapters/mod.rs | 2 +- impls/src/lifecycle/seed.rs | 22 +- impls/src/tor/process.rs | 2 +- libwallet/src/api_impl/foreign.rs | 48 +- libwallet/src/api_impl/types.rs | 4 - libwallet/src/slate.rs | 15 + src/bin/grin-wallet.yml | 41 +- src/cmd/wallet.rs | 2 +- src/cmd/wallet_args.rs | 360 +++++++++------ tests/cmd_line_basic.rs | 274 +++++++----- tests/owner_v3_lifecycle.rs | 5 +- util/src/ov3.rs | 7 +- 28 files changed, 1425 insertions(+), 738 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 617ada5b..50b886e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ dependencies = [ "rand 0.7.3", "scrypt", "secrecy", - "sha2 0.8.1", + "sha2 0.8.2", "subtle 2.2.2", "x25519-dalek", "zeroize 1.1.0", @@ -195,9 +195,9 @@ version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", ] [[package]] @@ -271,7 +271,7 @@ dependencies = [ "byteorder", "crypto-mac 0.7.0", "pbkdf2 0.3.0", - "sha2 0.8.1", + "sha2 0.8.2", "zeroize 1.1.0", ] @@ -297,7 +297,7 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", "regex", "rustc-hash", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.53" +version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" +checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" dependencies = [ "jobserver", ] @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -760,9 +760,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c0346158a19b3627234e15596f5e465c360fcdb97d817bcb255e0510f5a788" +checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69" [[package]] name = "difference" @@ -892,7 +892,7 @@ dependencies = [ "clear_on_drop", "curve25519-dalek", "rand 0.7.3", - "sha2 0.8.1", + "sha2 0.8.2", ] [[package]] @@ -945,9 +945,9 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", "synstructure 0.12.3", ] @@ -1073,9 +1073,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", ] [[package]] @@ -1176,7 +1176,7 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "grin_api" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "bytes 0.5.4", "easy-jsonrpc-mw", @@ -1209,7 +1209,7 @@ dependencies = [ [[package]] name = "grin_chain" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "bit-vec", "bitflags 1.2.1", @@ -1232,7 +1232,7 @@ dependencies = [ [[package]] name = "grin_core" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "blake2-rfc", "byteorder", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "grin_keychain" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "blake2-rfc", "byteorder", @@ -1280,7 +1280,7 @@ dependencies = [ [[package]] name = "grin_p2p" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "bitflags 1.2.1", "chrono", @@ -1301,7 +1301,7 @@ dependencies = [ [[package]] name = "grin_pool" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "blake2-rfc", "chrono", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "grin_store" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "byteorder", "croaring-mw", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "grin_util" version = "4.0.0-alpha.1" -source = "git+https://github.com/mimblewimble/grin#5b825fbf0ddb7ccc6c2fcd49c1ec585fc0583331" +source = "git+https://github.com/mimblewimble/grin#e7d2c71ca6c90695ecc5f1e9dc1cd3eab6050718" dependencies = [ "backtrace", "base64 0.9.3", @@ -1528,7 +1528,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha2 0.8.1", + "sha2 0.8.2", "strum", "strum_macros", "uuid", @@ -1730,8 +1730,7 @@ dependencies = [ [[package]] name = "hyper-socks2" version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b04745c5e7e8f4f93209f411644659055ce99f1973bd615015e3a7321ec2771" +source = "git+https://github.com/yeastplume/hyper-socks2#08c3e5f6e8fb642110abe61e8a381195ac60cb6c" dependencies = [ "async-socks5", "futures 0.3.5", @@ -1824,9 +1823,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" +checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" dependencies = [ "wasm-bindgen", ] @@ -1887,9 +1886,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.70" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "libgit2-sys" @@ -2106,7 +2105,7 @@ checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ "log", "mio", - "miow 0.3.3", + "miow 0.3.4", "winapi 0.3.8", ] @@ -2135,9 +2134,9 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" +checksum = "22dfdd1d51b2639a5abd17ed07005c3af05fb7a2a3b1a1d0d7af1000a520c1c7" dependencies = [ "socket2", "winapi 0.3.8", @@ -2239,7 +2238,7 @@ checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" dependencies = [ "lexical-core", "memchr 2.3.3", - "version_check 0.9.1", + "version_check 0.9.2", ] [[package]] @@ -2432,9 +2431,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.56" +version = "0.9.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e" +checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990" dependencies = [ "autocfg 1.0.0", "cc", @@ -2581,9 +2580,9 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", ] [[package]] @@ -2606,9 +2605,9 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "podio" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" +checksum = "b18befed8bc2b61abc79a457295e7e838417326da1586050b919414073977f19" [[package]] name = "poly1305" @@ -2651,9 +2650,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" @@ -2672,9 +2671,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.13" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" +checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" dependencies = [ "unicode-xid 0.2.0", ] @@ -2700,7 +2699,7 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", ] [[package]] @@ -2938,9 +2937,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.7" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "aho-corasick", "memchr 2.3.3", @@ -2950,9 +2949,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "remove_dir_all" @@ -3170,7 +3169,7 @@ dependencies = [ "byteorder", "hmac 0.7.1", "pbkdf2 0.3.0", - "sha2 0.8.1", + "sha2 0.8.2", ] [[package]] @@ -3282,9 +3281,9 @@ version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", ] [[package]] @@ -3324,9 +3323,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ "block-buffer 0.7.3", "digest 0.8.1", @@ -3493,11 +3492,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.22" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" +checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", "unicode-xid 0.2.0", ] @@ -3520,9 +3519,9 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", "unicode-xid 0.2.0", ] @@ -3596,22 +3595,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7" +checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d" +checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", ] [[package]] @@ -3693,9 +3692,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", ] [[package]] @@ -3922,9 +3921,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "void" @@ -3961,9 +3960,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" +checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3971,24 +3970,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" +checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" +checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ "quote 1.0.6", "wasm-bindgen-macro-support", @@ -3996,28 +3995,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" +checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.62" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" +checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" [[package]] name = "web-sys" -version = "0.3.39" +version = "0.3.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" +checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" dependencies = [ "js-sys", "wasm-bindgen", @@ -4157,9 +4156,9 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" dependencies = [ - "proc-macro2 1.0.13", + "proc-macro2 1.0.17", "quote 1.0.6", - "syn 1.0.22", + "syn 1.0.27", "synstructure 0.12.3", ] diff --git a/api/src/foreign.rs b/api/src/foreign.rs index 319c7975..9a543062 100644 --- a/api/src/foreign.rs +++ b/api/src/foreign.rs @@ -14,12 +14,14 @@ //! Foreign API External Definition +use crate::config::TorConfig; use crate::keychain::Keychain; use crate::libwallet::api_impl::foreign; use crate::libwallet::{ BlockFees, CbData, Error, NodeClient, NodeVersionInfo, Slate, VersionInfo, WalletInst, WalletLCProvider, }; +use crate::try_slatepack_sync_workflow; use crate::util::secp::key::SecretKey; use crate::util::Mutex; use std::sync::Arc; @@ -38,8 +40,10 @@ pub enum ForeignCheckMiddlewareFn { VerifySlateMessages, /// receive_tx ReceiveTx, - /// finalize_invoice_tx + /// finalize_invoice_tx (delete HF3) FinalizeInvoiceTx, + /// finalize_tx + FinalizeTx, } /// Main interface into all wallet API functions. @@ -70,6 +74,9 @@ where middleware: Option, /// Stored keychain mask (in case the stored wallet seed is tokenized) keychain_mask: Option, + /// Optional TOR configuration, holding address of sender and + /// data directory + tor_config: Mutex>, } impl<'a, L, C, K> Foreign<'a, L, C, K> @@ -158,7 +165,7 @@ where /// // All wallet functions operate on an Arc::Mutex to allow multithreading where needed /// let mut wallet = Arc::new(Mutex::new(wallet)); /// - /// let api_foreign = Foreign::new(wallet.clone(), None, None); + /// let api_foreign = Foreign::new(wallet.clone(), None, None, false); /// // .. perform wallet operations /// /// ``` @@ -167,15 +174,30 @@ where wallet_inst: Arc>>>, keychain_mask: Option, middleware: Option, + doctest_mode: bool, ) -> Self { Foreign { wallet_inst, - doctest_mode: false, + doctest_mode, middleware, keychain_mask, + tor_config: Mutex::new(None), } } + /// Set the TOR configuration for this instance of the ForeignAPI, used during + /// `recieve_tx` when a return address is specified + /// + /// # Arguments + /// * `tor_config` - The optional [TorConfig](#) to use + /// # Returns + /// * Nothing + + pub fn set_tor_config(&self, tor_config: Option) { + let mut lock = self.tor_config.lock(); + *lock = tor_config; + } + /// Return the version capabilities of the running ForeignApi Node /// # Arguments /// None @@ -186,7 +208,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None); + /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); /// /// let version_info = api_foreign.check_version(); /// // check and proceed accordingly @@ -238,7 +260,7 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None); + /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); /// /// let block_fees = BlockFees { /// fees: 800000, @@ -294,6 +316,9 @@ where /// excess value). /// * `dest_acct_name` - The name of the account into which the slate should be received. If /// `None`, the default account is used. + /// * `r_addr` - If included, attempt to send the slate back to the sender using the slatepack sync + /// send (TOR). If providing this argument, check the `state` field of the slate to see if the + /// sync_send was successful (it should be S3 if the synced send sent successfully). /// /// # Returns /// * a result containing: @@ -310,12 +335,12 @@ where /// ``` /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None); + /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); /// # let slate = Slate::blank(2, false); /// /// // . . . /// // Obtain a sent slate somehow - /// let result = api_foreign.receive_tx(&slate, None); + /// let result = api_foreign.receive_tx(&slate, None, None); /// /// if let Ok(slate) = result { /// // Send back to recipient somehow @@ -323,7 +348,12 @@ where /// } /// ``` - pub fn receive_tx(&self, slate: &Slate, dest_acct_name: Option<&str>) -> Result { + pub fn receive_tx( + &self, + slate: &Slate, + dest_acct_name: Option<&str>, + r_addr: Option, + ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { @@ -333,23 +363,41 @@ where Some(slate), )?; } - foreign::receive_tx( + let ret_slate = foreign::receive_tx( &mut **w, (&self.keychain_mask).as_ref(), slate, dest_acct_name, self.doctest_mode, - ) + )?; + match r_addr { + Some(a) => { + let tor_config_lock = self.tor_config.lock(); + let res = try_slatepack_sync_workflow( + &ret_slate, + &a, + tor_config_lock.clone(), + None, + true, + self.doctest_mode, + ); + match res { + Ok(s) => return Ok(s.unwrap()), + Err(_) => return Ok(ret_slate), + } + } + None => Ok(ret_slate), + } } - /// Finalizes an invoice transaction initiated by this wallet's Owner api. + /// Finalizes a (standard or invoice) transaction initiated by this wallet's Owner api. /// This step assumes the paying party has completed round 1 and 2 of slate - /// creation, and added their partial signatures. The invoicer will verify + /// creation, and added their partial signatures. This wallet will verify /// and add their partial sig, then create the finalized transaction, /// ready to post to a node. /// - /// Note that this function DOES NOT POST the transaction to a node - /// for validation. This is done in separately via the + /// This function posts to the node if the `post_automatically` + /// argument is sent to true. Posting can be done in separately via the /// [`post_tx`](struct.Owner.html#method.post_tx) function. /// /// This function also stores the final transaction in the user's wallet files for retrieval @@ -357,7 +405,8 @@ where /// /// # Arguments /// * `slate` - The transaction [`Slate`](../grin_wallet_libwallet/slate/struct.Slate.html). The - /// payer should have filled in round 1 and 2. + /// * `post_automatically` - If true, post the finalized transaction to the configured listening + /// node /// /// # Returns /// * Ok([`slate`](../grin_wallet_libwallet/slate/struct.Slate.html)) if successful, @@ -370,7 +419,7 @@ where /// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config); /// /// let mut api_owner = Owner::new(wallet.clone(), None); - /// let mut api_foreign = Foreign::new(wallet.clone(), None, None); + /// let mut api_foreign = Foreign::new(wallet.clone(), None, None, false); /// /// // . . . /// // Issue the invoice tx via the owner API @@ -385,11 +434,11 @@ where /// // ... /// # let slate = Slate::blank(2, true); /// - /// let slate = api_foreign.finalize_invoice_tx(&slate); + /// let slate = api_foreign.finalize_tx(&slate, true); /// // if okay, then post via the owner API /// ``` - pub fn finalize_invoice_tx(&self, slate: &Slate) -> Result { + pub fn finalize_tx(&self, slate: &Slate, post_automatically: bool) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; if let Some(m) = self.middleware.as_ref() { @@ -399,7 +448,16 @@ where Some(slate), )?; } - foreign::finalize_invoice_tx(&mut **w, (&self.keychain_mask).as_ref(), slate) + let post_automatically = match self.doctest_mode { + true => false, + false => post_automatically, + }; + foreign::finalize_tx( + &mut **w, + (&self.keychain_mask).as_ref(), + slate, + post_automatically, + ) } } diff --git a/api/src/foreign_rpc.rs b/api/src/foreign_rpc.rs index f01b79bf..899655d7 100644 --- a/api/src/foreign_rpc.rs +++ b/api/src/foreign_rpc.rs @@ -183,13 +183,12 @@ pub trait ForeignRpc { &self, slate: VersionedSlate, dest_acct_name: Option, - //TODO: Remove post-HF3 - message: Option, + dest: Option, ) -> Result; /** - Networked version of [Foreign::finalize_invoice_tx](struct.Foreign.html#method.finalize_invoice_tx). + Networked version of [Foreign::finalize_tx](struct.Foreign.html#method.finalize_tx). # Json rpc example @@ -198,7 +197,7 @@ pub trait ForeignRpc { # r#" { "jsonrpc": "2.0", - "method": "finalize_invoice_tx", + "method": "finalize_tx", "id": 1, "params": [{ "ver": "4:2", @@ -279,6 +278,9 @@ pub trait ForeignRpc { # ,false, 5, false, true); ``` */ + fn finalize_tx(&self, slate: VersionedSlate) -> Result; + + /// For backwards-compatibility. Remove HF3 fn finalize_invoice_tx(&self, slate: VersionedSlate) -> Result; } @@ -301,8 +303,7 @@ where &self, in_slate: VersionedSlate, dest_acct_name: Option, - //TODO: Remove post HF3 - _message: Option, + dest: Option, ) -> Result { let version = in_slate.version(); let slate_from = Slate::from(in_slate); @@ -310,15 +311,24 @@ where self, &slate_from, dest_acct_name.as_ref().map(String::as_str), + dest, ) .map_err(|e| e.kind())?; Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) } + fn finalize_tx(&self, in_slate: VersionedSlate) -> Result { + let version = in_slate.version(); + let out_slate = + Foreign::finalize_tx(self, &Slate::from(in_slate), true).map_err(|e| e.kind())?; + Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) + } + + //TODO: Delete HF3 fn finalize_invoice_tx(&self, in_slate: VersionedSlate) -> Result { let version = in_slate.version(); let out_slate = - Foreign::finalize_invoice_tx(self, &Slate::from(in_slate)).map_err(|e| e.kind())?; + Foreign::finalize_tx(self, &Slate::from(in_slate), false).map_err(|e| e.kind())?; Ok(VersionedSlate::into_version(out_slate, version).map_err(|e| e.kind())?) } } @@ -513,8 +523,8 @@ pub fn run_doctest_foreign( } let mut api_foreign = match init_invoice_tx { - false => Foreign::new(wallet1, mask1, Some(test_check_middleware)), - true => Foreign::new(wallet2, mask2, Some(test_check_middleware)), + false => Foreign::new(wallet1, mask1, Some(test_check_middleware), true), + true => Foreign::new(wallet2, mask2, Some(test_check_middleware), true), }; api_foreign.doctest_mode = true; let foreign_api = &api_foreign as &dyn ForeignRpc; diff --git a/api/src/lib.rs b/api/src/lib.rs index a3ea1fd9..caf22511 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -47,7 +47,7 @@ mod types; pub use crate::foreign::{Foreign, ForeignCheckMiddleware, ForeignCheckMiddlewareFn}; pub use crate::foreign_rpc::ForeignRpc; -pub use crate::owner::Owner; +pub use crate::owner::{try_slatepack_sync_workflow, Owner}; pub use crate::owner_rpc::OwnerRpc; pub use crate::foreign_rpc::foreign_rpc as foreign_rpc_client; diff --git a/api/src/owner.rs b/api/src/owner.rs index 72c19ab8..3a392224 100644 --- a/api/src/owner.rs +++ b/api/src/owner.rs @@ -21,18 +21,21 @@ use uuid::Uuid; use crate::config::{TorConfig, WalletConfig}; use crate::core::core::Transaction; use crate::core::global; -use crate::impls::create_sender; +use crate::impls::HttpSlateSender; +use crate::impls::SlateSender as _; use crate::keychain::{Identifier, Keychain}; use crate::libwallet::api_impl::owner_updater::{start_updater_log_thread, StatusMessage}; use crate::libwallet::api_impl::{owner, owner_updater}; use crate::libwallet::{ - AcctPathMapping, Error, ErrorKind, InitTxArgs, IssueInvoiceTxArgs, NodeClient, - NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, - TxLogEntry, WalletInfo, WalletInst, WalletLCProvider, + AcctPathMapping, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, NodeHeightResult, + OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress, TxLogEntry, WalletInfo, + WalletInst, WalletLCProvider, }; use crate::util::logger::LoggingConfig; use crate::util::secp::key::SecretKey; use crate::util::{from_hex, static_secp_instance, Mutex, ZeroingString}; +use grin_wallet_util::OnionV3Address; +use std::convert::TryFrom; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; @@ -63,6 +66,8 @@ where pub wallet_inst: Arc>>>, /// Flag to normalize some output during testing. Can mostly be ignored. pub doctest_mode: bool, + /// retail TLD during doctest + pub doctest_retain_tld: bool, /// Share ECDH key pub shared_key: Arc>>, /// Update thread @@ -189,6 +194,7 @@ where Owner { wallet_inst, doctest_mode: false, + doctest_retain_tld: false, shared_key: Arc::new(Mutex::new(None)), updater, updater_running, @@ -586,9 +592,10 @@ where /// /// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html), /// of the [`args`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), field is Some, this - /// function will attempt to perform a synchronous send to the recipient specified in the `dest` - /// field according to the `method` field, and will also finalize and post the transaction if - /// the `finalize` field is set. + /// function will attempt to send the slate back to the sender using the slatepack sync + /// send (TOR). If providing this argument, check the `state` field of the slate to see if the + /// sync_send was successful (it should be S2 if the sync sent successfully). It will also post + /// the transction if the `post_tx` field is set. /// /// # Arguments /// * `keychain_mask` - Wallet secret mask to XOR against the stored wallet seed before using, if @@ -648,39 +655,47 @@ where args: InitTxArgs, ) -> Result { let send_args = args.send_args.clone(); - let mut slate = { + let slate = { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; owner::init_send_tx(&mut **w, keychain_mask, args, self.doctest_mode)? }; - // Helper functionality. If send arguments exist, attempt to send + // Helper functionality. If send arguments exist, attempt to send sync and + // finalize match send_args { Some(sa) => { - //TODO: in case of keybase, the response might take 60s and leave the service hanging - match sa.method.as_ref() { - "http" | "keybase" => {} - _ => { - error!("unsupported payment method: {}", sa.method); - return Err(ErrorKind::ClientCallback( - "unsupported payment method".to_owned(), - ) - .into()); - } - }; let tor_config_lock = self.tor_config.lock(); - let comm_adapter = create_sender(&sa.method, &sa.dest, tor_config_lock.clone()) - .map_err(|e| ErrorKind::GenericError(format!("{}", e)))?; - slate = comm_adapter.send_tx(&slate)?; - self.tx_lock_outputs(keychain_mask, &slate)?; - let slate = match sa.finalize { - true => self.finalize_tx(keychain_mask, &slate)?, - false => slate, - }; - - if sa.post_tx { - self.post_tx(keychain_mask, &slate, sa.fluff)?; + let res = try_slatepack_sync_workflow( + &slate, + &sa.dest, + tor_config_lock.clone(), + None, + false, + self.doctest_mode, + ); + match res { + Ok(Some(s)) => { + if sa.post_tx { + self.tx_lock_outputs(keychain_mask, &s)?; + let ret_slate = self.finalize_tx(keychain_mask, &s)?; + let result = self.post_tx(keychain_mask, &ret_slate, sa.fluff); + match result { + Ok(_) => { + info!("Tx sent ok",); + return Ok(ret_slate); + } + Err(e) => { + error!("Tx sent fail: {}", e); + return Err(e); + } + } + } else { + return Ok(slate); + } + } + Ok(None) => Ok(slate), + Err(_) => Ok(slate), } - Ok(slate) } None => Ok(slate), } @@ -742,6 +757,12 @@ where /// it is up to the caller to present the request for payment to the user /// and verify that payment should go ahead. /// + /// If the `send_args` [`InitTxSendArgs`](../grin_wallet_libwallet/types/struct.InitTxSendArgs.html), + /// of the [`args`](../grin_wallet_libwallet/types/struct.InitTxArgs.html), field is Some, this + /// function will attempt to send the slate back to the initiator using the slatepack sync + /// send (TOR). If providing this argument, check the `state` field of the slate to see if the + /// sync_send was successful (it should be I3 if the sync sent successfully). + /// /// This function also stores the final transaction in the user's wallet files for retrieval /// via the [`get_stored_tx`](struct.Owner.html#method.get_stored_tx) function. /// @@ -794,7 +815,28 @@ where ) -> Result { let mut w_lock = self.wallet_inst.lock(); let w = w_lock.lc_provider()?.wallet_inst()?; - owner::process_invoice_tx(&mut **w, keychain_mask, slate, args, self.doctest_mode) + let send_args = args.send_args.clone(); + let slate = + owner::process_invoice_tx(&mut **w, keychain_mask, slate, args, self.doctest_mode)?; + // Helper functionality. If send arguments exist, attempt to send + match send_args { + Some(sa) => { + let tor_config_lock = self.tor_config.lock(); + let res = try_slatepack_sync_workflow( + &slate, + &sa.dest, + tor_config_lock.clone(), + None, + true, + self.doctest_mode, + ); + match res { + Ok(s) => Ok(s.unwrap()), + Err(_) => Ok(slate), + } + } + None => Ok(slate), + } } /// Locks the outputs associated with the inputs to the transaction in the given @@ -1277,7 +1319,7 @@ where pub fn get_top_level_directory(&self) -> Result { let mut w_lock = self.wallet_inst.lock(); let lc = w_lock.lc_provider()?; - if self.doctest_mode { + if self.doctest_mode && !self.doctest_retain_tld { Ok("/doctest/dir".to_owned()) } else { lc.get_top_level_directory() @@ -2249,6 +2291,99 @@ where } } +/// attempt to send slate synchronously, starting with TOR and downgrading to HTTP +pub fn try_slatepack_sync_workflow( + slate: &Slate, + dest: &str, + tor_config: Option, + tor_sender: Option, + send_to_finalize: bool, + test_mode: bool, +) -> Result, libwallet::Error> { + let mut ret_slate = Slate::blank(2, false); + let mut send_sync = |mut sender: HttpSlateSender, method_str: &str| match sender + .send_tx(&slate, send_to_finalize) + { + Ok(s) => { + ret_slate = s; + return Ok(()); + } + Err(e) => { + debug!( + "Send ({}): Could not send Slate via {}: {}", + method_str, method_str, e + ); + return Err(e); + } + }; + + // First, try TOR + match SlatepackAddress::try_from(dest) { + Ok(address) => { + let tor_addr = OnionV3Address::try_from(&address).unwrap(); + // Try sending to the destination via TOR + let sender = match tor_sender { + None => { + if test_mode { + None + } else { + match HttpSlateSender::with_socks_proxy( + &tor_addr.to_http_str(), + &tor_config.as_ref().unwrap().socks_proxy_addr, + &tor_config.as_ref().unwrap().send_config_dir, + ) { + Ok(s) => Some(s), + Err(e) => { + debug!("Send (TOR): Cannot create TOR Slate sender {:?}", e); + None + } + } + } + } + Some(s) => { + if test_mode { + None + } else { + Some(s) + } + } + }; + if let Some(s) = sender { + warn!("Attempting to send transaction via TOR"); + match send_sync(s, "TOR") { + Ok(_) => return Ok(Some(ret_slate)), + Err(e) => { + debug!("Unable to send via TOR: {}", e); + warn!("Unable to send transaction via TOR. Attempting alternate methods."); + } + } + } + } + Err(e) => { + debug!("Send (TOR): Destination is not SlatepackAddress {:?}", e); + } + } + + // Try Fallback to HTTP for deprecation period + match HttpSlateSender::new(&dest) { + Ok(sender) => { + println!("Attempting to send transaction via HTTP (deprecated)"); + match send_sync(sender, "HTTP") { + Ok(_) => return Ok(Some(ret_slate)), + Err(e) => { + debug!("Unable to send via HTTP: {}", e); + warn!("Unable to send transaction via HTTP. Will output Slatepack."); + return Ok(None); + } + } + } + Err(e) => { + debug!("Send (HTTP): Cannot create HTTP Slate sender {:?}", e); + return Ok(None); + } + } +} + #[doc(hidden)] #[macro_export] macro_rules! doctest_helper_setup_doc_env { diff --git a/controller/src/command.rs b/controller/src/command.rs index 0b5a3ff2..07d610b3 100644 --- a/controller/src/command.rs +++ b/controller/src/command.rs @@ -15,21 +15,23 @@ //! Grin wallet command-line function implementations use crate::api::TLSConfig; -use crate::apiwallet::Owner; +use crate::apiwallet::{try_slatepack_sync_workflow, Owner}; use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME}; use crate::core::{core, global}; use crate::error::{Error, ErrorKind}; -use crate::impls::{create_sender, KeybaseAllChannels, SlateGetter as _, SlateReceiver as _}; -use crate::impls::{HttpSlateSender, PathToSlate, SlatePutter}; +use crate::impls::SlateGetter as _; +use crate::impls::{HttpSlateSender, PathToSlate, PathToSlatepack, SlatePutter}; use crate::keychain; use crate::libwallet::{ self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateVersion, - SlatepackAddress, WalletLCProvider, + SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider, }; use crate::util::secp::key::SecretKey; use crate::util::{Mutex, ZeroingString}; use crate::{controller, display}; +use grin_wallet_util::OnionV3Address; use serde_json as json; +use std::convert::TryFrom; use std::fs::File; use std::io::{Read, Write}; use std::sync::atomic::Ordering; @@ -71,6 +73,7 @@ pub fn init( owner_api: &mut Owner, _g_args: &GlobalArgs, args: InitArgs, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, @@ -88,7 +91,7 @@ where args.recovery_phrase, args.list_length, args.password.clone(), - false, + test_mode, )?; let m = p.get_mnemonic(None, args.password)?; @@ -115,72 +118,51 @@ where } /// Arguments for listen command -pub struct ListenArgs { - pub method: String, -} +pub struct ListenArgs {} pub fn listen( owner_api: &mut Owner, keychain_mask: Arc>>, config: &WalletConfig, tor_config: &TorConfig, - args: &ListenArgs, + _args: &ListenArgs, g_args: &GlobalArgs, cli_mode: bool, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let res = match args.method.as_str() { - "http" => { - let wallet_inst = owner_api.wallet_inst.clone(); - let config = config.clone(); - let tor_config = tor_config.clone(); - let g_args = g_args.clone(); - let api_thread = thread::Builder::new() - .name("wallet-http-listener".to_string()) - .spawn(move || { - let res = controller::foreign_listener( - wallet_inst, - keychain_mask, - &config.api_listen_addr(), - g_args.tls_conf.clone(), - tor_config.use_tor_listener, - ); - if let Err(e) = res { - error!("Error starting listener: {}", e); - } - }); - if let Ok(t) = api_thread { - if !cli_mode { - let r = t.join(); - if let Err(_) = r { - error!("Error starting listener"); - return Err(ErrorKind::ListenerError.into()); - } - } + let wallet_inst = owner_api.wallet_inst.clone(); + let config = config.clone(); + let tor_config = tor_config.clone(); + let g_args = g_args.clone(); + let api_thread = thread::Builder::new() + .name("wallet-http-listener".to_string()) + .spawn(move || { + let res = controller::foreign_listener( + wallet_inst, + keychain_mask, + &config.api_listen_addr(), + g_args.tls_conf.clone(), + tor_config.use_tor_listener, + test_mode, + Some(tor_config.clone()), + ); + if let Err(e) = res { + error!("Error starting listener: {}", e); + } + }); + if let Ok(t) = api_thread { + if !cli_mode { + let r = t.join(); + if let Err(_) = r { + error!("Error starting listener"); + return Err(ErrorKind::ListenerError.into()); } - Ok(()) } - "keybase" => KeybaseAllChannels::new()?.listen( - config.clone(), - g_args.password.clone().unwrap(), - &g_args.account, - g_args.node_api_secret.clone(), - ), - method => { - return Err(ErrorKind::ArgumentError(format!( - "No listener for method \"{}\".", - method - )) - .into()); - } - }; - - if let Err(e) = res { - return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into()); } Ok(()) } @@ -191,6 +173,7 @@ pub fn owner_api( config: &WalletConfig, tor_config: &TorConfig, g_args: &GlobalArgs, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + Send + Sync + 'static, @@ -208,6 +191,7 @@ where g_args.tls_conf.clone(), config.owner_api_include_foreign.clone(), Some(tor_config.clone()), + test_mode, ); if let Err(e) = res { return Err(ErrorKind::LibWallet(e.kind(), e.cause_string()).into()); @@ -260,12 +244,12 @@ where } /// Arguments for the send command +#[derive(Clone)] pub struct SendArgs { pub amount: u64, pub minimum_confirmations: u64, pub selection_strategy: String, pub estimate_selection_strategies: bool, - pub method: String, pub dest: String, pub change_outputs: usize, pub fluff: bool, @@ -283,6 +267,7 @@ pub fn send( tor_config: Option, args: SendArgs, dark_scheme: bool, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, @@ -295,55 +280,121 @@ where // TODO: This block is temporary, for the period between the release of v4.0.0 and HF3, // after which this should be removable let mut args = args; + + //TODO: Remove block post HF3 + // All this block does is determine whether the slate should be + // output as a V3 Slate for the receiver + let mut tor_sender = None; + let is_pre_fork; { - let invalid = || { - ErrorKind::GenericError(format!( - "Invalid wallet comm type and destination. method: {}, dest: {}", - args.method, args.dest - )) + is_pre_fork = { + let cur_height = { + libwallet::wallet_lock!(wallet_inst, w); + w.w2n_client().get_chain_tip()?.0 + }; + match global::get_chain_type() { + global::ChainTypes::Mainnet => { + if cur_height < 786240 && !args.output_v4_slate { + true + } else { + false + } + } + global::ChainTypes::Floonet => { + if cur_height < 552960 && !args.output_v4_slate { + true + } else { + false + } + } + _ => false, + } }; - let trailing = match args.dest.ends_with('/') { - true => "", - false => "/", - }; - let url_str = format!("{}{}v2/foreign", args.dest, trailing); - match args.method.as_ref() { - "http" => { - let v_sender = HttpSlateSender::new(&args.dest).map_err(|_| invalid())?; - let other_version = v_sender.check_other_version(&url_str)?; - if other_version == SlateVersion::V3 { - args.target_slate_version = Some(3); + + if is_pre_fork { + let trailing = match args.dest.ends_with('/') { + true => "", + false => "/", + }; + + let mut address_found = false; + // For sync methods, derive intended endpoint from dest + match SlatepackAddress::try_from(args.dest.as_str()) { + Ok(address) => { + let tor_addr = OnionV3Address::try_from(&address).unwrap(); + // Try pinging the destination via TOR + debug!("Version ping: TOR address is: {}", tor_addr); + match HttpSlateSender::with_socks_proxy( + &tor_addr.to_http_str(), + &tor_config.as_ref().unwrap().socks_proxy_addr, + &tor_config.as_ref().unwrap().send_config_dir, + ) { + Ok(mut sender) => { + let url_str = + format!("{}{}v2/foreign", tor_addr.to_http_str(), trailing); + if let Ok(v) = sender.check_other_version(&url_str) { + if v == SlateVersion::V3 { + args.target_slate_version = Some(3); + } + address_found = true; + } + tor_sender = Some(sender); + } + Err(e) => { + debug!( + "Version ping: Couldn't create slate sender for TOR: {:?}", + e + ); + } + } + } + Err(e) => { + debug!("Version ping: Address is not SlatepackAddress: {:?}", e); } } - "tor" => { - let v_sender = HttpSlateSender::with_socks_proxy( - &args.dest, - &tor_config.as_ref().unwrap().socks_proxy_addr, - &tor_config.as_ref().unwrap().send_config_dir, - ) - .map_err(|_| invalid())?; - let other_version = v_sender.check_other_version(&url_str)?; - if other_version == SlateVersion::V3 { - args.target_slate_version = Some(3); + + // now try http + if !address_found { + // Try pinging the destination via TOR + match HttpSlateSender::new(&args.dest) { + Ok(mut sender) => { + let url_str = format!("{}{}v2/foreign", args.dest, trailing); + match sender.check_other_version(&url_str) { + Ok(v) => { + if v == SlateVersion::V3 { + args.target_slate_version = Some(3); + } + address_found = true; + } + Err(e) => { + debug!( + "Version ping: Couldn't get other version for HTTP: {:?}", + e + ); + } + } + } + Err(e) => { + debug!( + "Version ping: Couldn't create slate sender for HTTP: {:?}", + e + ); + } } } - "file" => { + + if !address_found { + // otherwise, determine slate format based on block height // For files spit out a V3 Slate if we're before HF3, // Or V4 slate otherwise - let cur_height = { - libwallet::wallet_lock!(wallet_inst, w); - w.w2n_client().get_chain_tip()?.0 - }; - // TODO: Floonet HF4 - if cur_height < 786240 && !args.output_v4_slate { + if is_pre_fork { args.target_slate_version = Some(3); } } - _ => {} } - } - // end block to delete post HF3 + } // end pre HF3 Block + let mut slate = Slate::blank(2, false); controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { if args.estimate_selection_strategies { let strategies = vec!["smallest", "all"] @@ -364,6 +415,7 @@ where }) .collect(); display::estimate(args.amount, strategies, dark_scheme); + return Ok(()); } else { let init_args = InitTxArgs { src_acct_name: None, @@ -379,7 +431,7 @@ where ..Default::default() }; let result = api.init_send_tx(m, init_args); - let mut slate = match result { + slate = match result { Ok(s) => { info!( "Tx created: {} grin to {} (strategy '{}')", @@ -394,57 +446,208 @@ where return Err(e); } }; - - match args.method.as_str() { - "file" => { - PathToSlate((&args.dest).into()).put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - return Ok(()); - } - "binfile" => { - PathToSlate((&args.dest).into()).put_tx(&slate, true)?; - api.tx_lock_outputs(m, &slate)?; - return Ok(()); - } - "self" => { - api.tx_lock_outputs(m, &slate)?; - let km = match keychain_mask.as_ref() { - None => None, - Some(&m) => Some(m.to_owned()), - }; - controller::foreign_single_use(wallet_inst, km, |api| { - slate = api.receive_tx(&slate, Some(&args.dest))?; - Ok(()) - })?; - } - method => { - let sender = create_sender(method, &args.dest, tor_config)?; - slate = sender.send_tx(&slate)?; - api.tx_lock_outputs(m, &slate)?; - } - } - - slate = api.finalize_tx(m, &slate)?; - let result = api.post_tx(m, &slate, args.fluff); - match result { - Ok(_) => { - info!("Tx sent ok",); - return Ok(()); - } - Err(e) => { - error!("Tx sent fail: {}", e); - return Err(e); - } - } } Ok(()) })?; + + let res = + try_slatepack_sync_workflow(&slate, &args.dest, tor_config, tor_sender, false, test_mode); + + match res { + Ok(Some(s)) => { + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + api.tx_lock_outputs(m, &s)?; + let ret_slate = api.finalize_tx(m, &s)?; + let result = api.post_tx(m, &ret_slate, args.fluff); + match result { + Ok(_) => { + println!("Tx sent successfully",); + Ok(()) + } + Err(e) => { + error!("Tx sent fail: {}", e); + Err(e.into()) + } + } + })?; + } + Ok(None) => { + output_slatepack( + owner_api, + keychain_mask, + &slate, + args.dest.as_str(), + true, + false, + is_pre_fork, + )?; + } + Err(e) => return Err(e.into()), + } Ok(()) } +pub fn output_slatepack( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + slate: &Slate, + dest: &str, + lock: bool, + file_only: bool, + is_pre_fork: bool, +) -> Result<(), libwallet::Error> +where + L: WalletLCProvider<'static, C, K> + 'static, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + // Output the slatepack file to stdout and to a file + let mut message = String::from(""); + let mut address = None; + let mut tld = String::from(""); + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + address = match SlatepackAddress::try_from(dest) { + Ok(a) => Some(a), + Err(_) => None, + }; + // encrypt for recipient by default + let recipients = match address.clone() { + Some(a) => vec![a], + None => vec![], + }; + message = api.create_slatepack_message(m, &slate, Some(0), recipients)?; + tld = api.get_top_level_directory()?; + Ok(()) + })?; + + // create a directory to which files will be output + let slate_dir = format!("{}/{}", tld, "slatepack"); + let _ = std::fs::create_dir_all(slate_dir.clone()); + let out_file_name = format!("{}/{}.{}.slatepack", slate_dir, slate.id, slate.state); + + if lock { + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + api.tx_lock_outputs(m, &slate)?; + Ok(()) + })?; + } + + // TODO: Remove HF3 + if is_pre_fork || file_only { + PathToSlate((&out_file_name).into()).put_tx(&slate, false)?; + println!(); + println!("Transaction file was output to:"); + println!(); + println!("{}", out_file_name); + println!(); + if !file_only { + println!("Please send this file to the other party manually"); + } + return Ok(()); + } + + println!("{}", out_file_name); + let mut output = File::create(out_file_name.clone())?; + output.write_all(&message.as_bytes())?; + output.sync_all()?; + + println!(); + println!("Slatepack data follows. Please provide this output to the other party"); + println!(); + println!("--- CUT BELOW THIS LINE ---"); + println!(); + println!("{}", message); + println!("--- CUT ABOVE THIS LINE ---"); + println!(); + println!("Slatepack data was also output to"); + println!(); + println!("{}", out_file_name); + println!(); + if address.is_some() { + println!("The slatepack data is encrypted for the recipient only"); + } else { + println!("The slatepack data is NOT encrypted"); + } + println!(); + Ok(()) +} + +// Parse a slate and slatepack from a message +pub fn parse_slatepack( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + filename: Option, + message: Option, +) -> Result<(Slate, Option), Error> +where + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + let mut ret_address = None; + let slate = match filename { + Some(f) => { + // first try regular slate - remove HF3 + let mut sl = match PathToSlate((&f).into()).get_tx() { + Ok(s) => Some(s.0), //pre HF3, regular slate + Err(_) => None, + }; + // otherwise, get slate from slatepack + if sl.is_none() { + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { + let dec_key = api.get_slatepack_secret_key(m, 0)?; + let packer = Slatepacker::new(SlatepackerArgs { + sender: None, + recipients: vec![], + dec_key: Some(&dec_key), + }); + let pts = PathToSlatepack::new(f.into(), &packer, true); + sl = Some(pts.get_tx()?.0); + ret_address = pts.get_slatepack()?.sender; + Ok(()) + })?; + } + sl + } + None => None, + }; + + let slate = match slate { + Some(s) => s, + None => { + // try and parse directly from input_slatepack_message + let mut slate = Slate::blank(2, false); + match message { + Some(message) => { + controller::owner_single_use( + None, + keychain_mask, + Some(owner_api), + |api, m| { + slate = + api.slate_from_slatepack_message(m, message.clone(), vec![0])?; + let slatepack = api.decode_slatepack_message(message)?; + ret_address = slatepack.sender; + Ok(()) + }, + )?; + } + None => { + let msg = "No slate provided via file or direct input"; + return Err(ErrorKind::GenericError(msg.into()).into()); + } + } + slate + } + }; + Ok((slate, ret_address)) +} + /// Receive command argument +#[derive(Clone)] pub struct ReceiveArgs { - pub input: String, + pub input_file: Option, + pub input_slatepack_message: Option, } pub fn receive( @@ -452,35 +655,71 @@ pub fn receive( keychain_mask: Option<&SecretKey>, g_args: &GlobalArgs, args: ReceiveArgs, + tor_config: Option, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K>, C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let (mut slate, was_bin) = PathToSlate((&args.input).into()).get_tx()?; + let (mut slate, ret_address) = parse_slatepack( + owner_api, + keychain_mask, + args.input_file, + args.input_slatepack_message, + )?; + let km = match keychain_mask.as_ref() { None => None, Some(&m) => Some(m.to_owned()), }; + controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| { - slate = api.receive_tx(&slate, Some(&g_args.account))?; + slate = api.receive_tx(&slate, Some(&g_args.account), None)?; Ok(()) })?; - PathToSlate(format!("{}.response", args.input).into()).put_tx(&slate, was_bin)?; - info!( - "Response file {}.response generated, and can be sent back to the transaction originator.", - args.input - ); - Ok(()) + + let dest = match ret_address { + Some(a) => String::try_from(&a).unwrap(), + None => String::from(""), + }; + + let res = try_slatepack_sync_workflow(&slate, &dest, tor_config, None, true, test_mode); + + match res { + Ok(Some(_)) => { + println!(); + println!( + "Transaction recieved and sent back to sender at {} for finalization.", + dest + ); + println!(); + Ok(()) + } + Ok(None) => { + output_slatepack( + owner_api, + keychain_mask, + &slate, + &dest, + false, + false, + slate.version_info.version < 4, + )?; + Ok(()) + } + Err(e) => Err(e.into()), + } } /// Finalize command args +#[derive(Clone)] pub struct FinalizeArgs { - pub input: String, + pub input_file: Option, + pub input_slatepack_message: Option, pub fluff: bool, pub nopost: bool, - pub dest: Option, } pub fn finalize( @@ -493,7 +732,12 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let (mut slate, was_bin) = PathToSlate((&args.input).into()).get_tx()?; + let (mut slate, _ret_address) = parse_slatepack( + owner_api, + keychain_mask, + args.input_file.clone(), + args.input_slatepack_message.clone(), + )?; // Rather than duplicating the entire command, we'll just // try to determine what kind of finalization this is @@ -512,7 +756,7 @@ where Some(&m) => Some(m.to_owned()), }; controller::foreign_single_use(owner_api.wallet_inst.clone(), km, |api| { - slate = api.finalize_invoice_tx(&slate)?; + slate = api.finalize_tx(&slate, false)?; Ok(()) })?; } else { @@ -522,7 +766,7 @@ where })?; } - if !args.nopost { + if !&args.nopost { controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { let result = api.post_tx(m, &slate, args.fluff); match result { @@ -530,6 +774,7 @@ where info!( "Transaction sent successfully, check the wallet again for confirmation." ); + println!("Transaction posted"); Ok(()) } Err(e) => { @@ -540,9 +785,17 @@ where })?; } - if args.dest.is_some() { - PathToSlate((&args.dest.unwrap()).into()).put_tx(&slate, was_bin)?; - } + println!("Transaction finalized successfully"); + + output_slatepack( + owner_api, + keychain_mask, + &slate, + "", + false, + true, + slate.version_info.version < 4, + )?; Ok(()) } @@ -553,9 +806,6 @@ pub struct IssueInvoiceArgs { pub dest: String, /// issue invoice tx args pub issue_args: IssueInvoiceTxArgs, - /// whether to output as bin - pub bin: bool, - // TODO: Remove HF3 /// whether to output a V4 slate pub output_v4_slate: bool, } @@ -571,24 +821,52 @@ where K: keychain::Keychain + 'static, { //TODO: Remove block HF3 - let args = { - let mut a = args; - let wallet_inst = owner_api.wallet_inst.clone(); + let is_pre_fork = { let cur_height = { + let wallet_inst = owner_api.wallet_inst.clone(); libwallet::wallet_lock!(wallet_inst, w); w.w2n_client().get_chain_tip()?.0 }; - // TODO: Floonet HF4 - if cur_height < 786240 && !a.output_v4_slate && !a.bin { - a.issue_args.target_slate_version = Some(3); + match global::get_chain_type() { + global::ChainTypes::Mainnet => { + if cur_height < 786240 && !&args.output_v4_slate { + true + } else { + false + } + } + global::ChainTypes::Floonet => { + if cur_height < 552960 && !&args.output_v4_slate { + true + } else { + false + } + } + _ => false, } - a }; + + let mut issue_args = args.issue_args.clone(); + + if is_pre_fork { + issue_args.target_slate_version = Some(3); + } + + let mut slate = Slate::blank(2, false); controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - let slate = api.issue_invoice_tx(m, args.issue_args)?; - PathToSlate((&args.dest).into()).put_tx(&slate, args.bin)?; + slate = api.issue_invoice_tx(m, issue_args)?; Ok(()) })?; + + output_slatepack( + owner_api, + keychain_mask, + &slate, + args.dest.as_str(), + false, + false, + is_pre_fork, + )?; Ok(()) } @@ -596,10 +874,9 @@ where pub struct ProcessInvoiceArgs { pub minimum_confirmations: u64, pub selection_strategy: String, - pub method: String, - pub dest: String, + pub ret_address: Option, pub max_outputs: usize, - pub input: String, + pub slate: Slate, pub estimate_selection_strategies: bool, pub ttl_blocks: Option, } @@ -611,14 +888,19 @@ pub fn process_invoice( tor_config: Option, args: ProcessInvoiceArgs, dark_scheme: bool, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let (slate, _) = PathToSlate((&args.input).into()).get_tx()?; - let wallet_inst = owner_api.wallet_inst.clone(); + let mut slate = args.slate.clone(); + let dest = match args.ret_address.clone() { + Some(a) => String::try_from(&a).unwrap(), + None => String::from(""), + }; + controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { if args.estimate_selection_strategies { let strategies = vec!["smallest", "all"] @@ -639,6 +921,7 @@ where }) .collect(); display::estimate(slate.amount, strategies, dark_scheme); + return Ok(()); } else { let init_args = InitTxArgs { src_acct_name: None, @@ -652,12 +935,11 @@ where ..Default::default() }; let result = api.process_invoice_tx(m, &slate, init_args); - let mut slate = match result { + slate = match result { Ok(s) => { info!( - "Invoice processed: {} grin to {} (strategy '{}')", + "Invoice processed: {} grin (strategy '{}')", core::amount_to_hr_string(slate.amount, false), - args.dest, args.selection_strategy, ); s @@ -667,40 +949,38 @@ where return Err(e); } }; - - match args.method.as_str() { - "file" => { - let slate_putter = PathToSlate((&args.dest).into()); - slate_putter.put_tx(&slate, false)?; - api.tx_lock_outputs(m, &slate)?; - } - "filebin" => { - let slate_putter = PathToSlate((&args.dest).into()); - slate_putter.put_tx(&slate, true)?; - api.tx_lock_outputs(m, &slate)?; - } - "self" => { - api.tx_lock_outputs(m, &slate)?; - let km = match keychain_mask.as_ref() { - None => None, - Some(&m) => Some(m.to_owned()), - }; - controller::foreign_single_use(wallet_inst, km, |api| { - slate = api.finalize_invoice_tx(&slate)?; - Ok(()) - })?; - } - method => { - let sender = create_sender(method, &args.dest, tor_config)?; - slate = sender.send_tx(&slate)?; - api.tx_lock_outputs(m, &slate)?; - } - } } Ok(()) })?; - Ok(()) + + let res = try_slatepack_sync_workflow(&slate, &dest, tor_config, None, true, test_mode); + + match res { + Ok(Some(_)) => { + println!(); + println!( + "Transaction paid and sent back to initiator at {} for finalization.", + dest + ); + println!(); + Ok(()) + } + Ok(None) => { + output_slatepack( + owner_api, + keychain_mask, + &slate, + &dest, + true, + false, + slate.version_info.version < 4, + )?; + Ok(()) + } + Err(e) => Err(e.into()), + } } + /// Info command args pub struct InfoArgs { pub minimum_confirmations: u64, @@ -828,8 +1108,10 @@ where } /// Post +#[derive(Clone)] pub struct PostArgs { - pub input: String, + pub input_file: Option, + pub input_slatepack_message: Option, pub fluff: bool, } @@ -843,10 +1125,16 @@ where C: NodeClient + 'static, K: keychain::Keychain + 'static, { - let slate = PathToSlate((&args.input).into()).get_tx()?.0; + let (slate, _ret_address) = parse_slatepack( + owner_api, + keychain_mask, + args.input_file, + args.input_slatepack_message, + )?; + let fluff = args.fluff; controller::owner_single_use(None, keychain_mask, Some(owner_api), |api, m| { - api.post_tx(m, &slate, args.fluff)?; + api.post_tx(m, &slate, fluff)?; info!("Posted transaction"); return Ok(()); })?; diff --git a/controller/src/controller.rs b/controller/src/controller.rs index 8351ac28..0db390e0 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -18,8 +18,8 @@ use crate::api::{self, ApiServer, BasicAuthMiddleware, ResponseFuture, Router, T use crate::config::TorConfig; use crate::keychain::Keychain; use crate::libwallet::{ - address, Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, WalletInst, WalletLCProvider, - GRIN_BLOCK_HEADER_VERSION, + address, Error, ErrorKind, NodeClient, NodeVersionInfo, Slate, SlatepackAddress, WalletInst, + WalletLCProvider, GRIN_BLOCK_HEADER_VERSION, }; use crate::util::secp::key::SecretKey; use crate::util::{from_hex, static_secp_instance, to_base64, Mutex}; @@ -32,6 +32,7 @@ use hyper::{Body, Request, Response, StatusCode}; use serde::{Deserialize, Serialize}; use serde_json; use std::collections::HashMap; +use std::convert::TryFrom; use std::net::SocketAddr; use std::sync::Arc; @@ -59,12 +60,12 @@ fn check_middleware( // allow coinbases to be built regardless ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()), _ => { - let mut bhv = 2; + let mut bhv = 3; if let Some(n) = node_version_info { bhv = n.block_header_version; } if let Some(s) = slate { - if bhv > 3 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION { + if bhv > 4 && s.version_info.block_header_version < GRIN_BLOCK_HEADER_VERSION { Err(ErrorKind::Compatibility( "Incoming Slate is not compatible with this wallet. \ Please upgrade the node or use a different one." @@ -82,7 +83,7 @@ fn init_tor_listener( wallet: Arc + 'static>>>, keychain_mask: Arc>>, addr: &str, -) -> Result +) -> Result<(tor_process::TorProcess, SlatepackAddress), Error> where L: WalletLCProvider<'static, C, K> + 'static, C: NodeClient + 'static, @@ -101,6 +102,7 @@ where .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; let onion_address = OnionV3Address::from_private(&sec_key.0) .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e).into()))?; + let sp_address = SlatepackAddress::try_from(onion_address.clone())?; warn!( "Starting TOR Hidden Service for API listener at address {}, binding to {}", onion_address, addr @@ -115,7 +117,7 @@ where .completion_percent(100) .launch() .map_err(|e| ErrorKind::TorProcess(format!("{:?}", e).into()))?; - Ok(process) + Ok((process, sp_address)) } /// Instantiate wallet Owner API for a single-use (command line) call @@ -167,6 +169,7 @@ where wallet, keychain_mask, Some(check_middleware), + false, ))?; Ok(()) } @@ -183,6 +186,7 @@ pub fn owner_listener( tls_config: Option, owner_api_include_foreign: Option, tor_config: Option, + test_mode: bool, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, @@ -208,7 +212,7 @@ where let api_handler_v3 = OwnerAPIHandlerV3::new( wallet.clone(), keychain_mask.clone(), - tor_config, + tor_config.clone(), running_foreign, ); @@ -219,7 +223,8 @@ where // If so configured, add the foreign API to the same port if running_foreign { warn!("Starting HTTP Foreign API on Owner server at {}.", addr); - let foreign_api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask); + let foreign_api_handler_v2 = + ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config)); router .add_route("/v2/foreign", Arc::new(foreign_api_handler_v2)) .map_err(|_| ErrorKind::GenericError("Router failed to add route".to_string()))?; @@ -247,6 +252,8 @@ pub fn foreign_listener( addr: &str, tls_config: Option, use_tor: bool, + test_mode: bool, + tor_config: Option, ) -> Result<(), Error> where L: WalletLCProvider<'static, C, K> + 'static, @@ -260,20 +267,21 @@ where let _ = lc.wallet_inst()?; } // need to keep in scope while the main listener is running - let _tor_process = match use_tor { + let (_tor_process, address) = match use_tor { true => match init_tor_listener(wallet.clone(), keychain_mask.clone(), addr) { - Ok(tp) => Some(tp), + Ok((tp, addr)) => (Some(tp), Some(addr)), Err(e) => { warn!("Unable to start TOR listener; Check that TOR executable is installed and on your path"); warn!("Tor Error: {}", e); warn!("Listener will be available via HTTP only"); - None + (None, None) } }, - false => None, + false => (None, None), }; - let api_handler_v2 = ForeignAPIHandlerV2::new(wallet, keychain_mask); + let api_handler_v2 = + ForeignAPIHandlerV2::new(wallet, keychain_mask, test_mode, Mutex::new(tor_config)); let mut router = Router::new(); router @@ -290,6 +298,9 @@ where ))?; warn!("HTTP Foreign listener started."); + if let Some(a) = address { + warn!("Slatepack Address is: {}", a); + } api_thread .join() @@ -669,6 +680,10 @@ where pub wallet: Arc + 'static>>>, /// Keychain mask pub keychain_mask: Arc>>, + /// run in doctest mode + pub test_mode: bool, + /// tor config + pub tor_config: Mutex>, } impl ForeignAPIHandlerV2 @@ -681,10 +696,14 @@ where pub fn new( wallet: Arc + 'static>>>, keychain_mask: Arc>>, + test_mode: bool, + tor_config: Mutex>, ) -> ForeignAPIHandlerV2 { ForeignAPIHandlerV2 { wallet, keychain_mask, + test_mode, + tor_config, } } @@ -707,8 +726,11 @@ where req: Request, mask: Option, wallet: Arc + 'static>>>, + test_mode: bool, + tor_config: Option, ) -> Result, Error> { - let api = Foreign::new(wallet, mask, Some(check_middleware)); + let api = Foreign::new(wallet, mask, Some(check_middleware), test_mode); + api.set_tor_config(tor_config); let res = Self::call_api(req, api).await?; Ok(json_response_pretty(&res)) } @@ -723,9 +745,11 @@ where fn post(&self, req: Request) -> ResponseFuture { let mask = self.keychain_mask.lock().clone(); let wallet = self.wallet.clone(); + let test_mode = self.test_mode; + let tor_config = self.tor_config.lock().clone(); Box::pin(async move { - match Self::handle_post_request(req, mask, wallet).await { + match Self::handle_post_request(req, mask, wallet, test_mode, tor_config).await { Ok(v) => Ok(v), Err(e) => { error!("Request Error: {:?}", e); diff --git a/controller/tests/file.rs b/controller/tests/file.rs index 36cb4ecb..3f09d83b 100644 --- a/controller/tests/file.rs +++ b/controller/tests/file.rs @@ -143,7 +143,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), // wallet 2 receives file, completes, sends file back wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - slate = api.receive_tx(&slate, None)?; + slate = api.receive_tx(&slate, None, None)?; PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; Ok(()) })?; @@ -226,7 +226,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { // Wallet 2 receives the invoice transaction slate = PathToSlate((&receive_file).into()).get_tx()?.0; - slate = api.finalize_invoice_tx(&slate)?; + slate = api.finalize_tx(&slate, false)?; PathToSlate((&final_file).into()).put_tx(&slate, use_bin)?; Ok(()) })?; @@ -276,7 +276,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { slate = PathToSlate((&send_file).into()).get_tx()?.0; - slate = api.receive_tx(&slate, None)?; + slate = api.receive_tx(&slate, None, None)?; PathToSlate((&receive_file).into()).put_tx(&slate, use_bin)?; Ok(()) })?; diff --git a/controller/tests/invoice.rs b/controller/tests/invoice.rs index 973a3177..c4eacb8b 100644 --- a/controller/tests/invoice.rs +++ b/controller/tests/invoice.rs @@ -128,7 +128,7 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // wallet 2 finalizes and posts wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { // Wallet 2 receives the invoice transaction - slate = api.finalize_invoice_tx(&slate)?; + slate = api.finalize_tx(&slate, false)?; Ok(()) })?; assert_eq!(slate.state, SlateState::Invoice3); @@ -200,7 +200,7 @@ fn invoice_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // wallet 1 finalizes and posts wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { // Wallet 2 receives the invoice transaction - slate = api.finalize_invoice_tx(&slate)?; + slate = api.finalize_tx(&slate, false)?; Ok(()) })?; diff --git a/controller/tests/no_change.rs b/controller/tests/no_change.rs index 3283791e..ef3970c9 100644 --- a/controller/tests/no_change.rs +++ b/controller/tests/no_change.rs @@ -158,7 +158,7 @@ fn no_change_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { // wallet 2 finalizes and posts wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { // Wallet 2 receives the invoice transaction - slate = api.finalize_invoice_tx(&slate)?; + slate = api.finalize_tx(&slate, false)?; Ok(()) })?; wallet::controller::owner_single_use(Some(wallet2.clone()), mask1, None, |api, m| { diff --git a/controller/tests/repost.rs b/controller/tests/repost.rs index 0ea9d2c1..5f7dcaca 100644 --- a/controller/tests/repost.rs +++ b/controller/tests/repost.rs @@ -134,7 +134,7 @@ fn file_repost_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { slate = PathToSlate((&send_file).into()).get_tx()?.0; - slate = api.receive_tx(&slate, None)?; + slate = api.receive_tx(&slate, None, None)?; PathToSlate((&receive_file).into()).put_tx(&slate, false)?; Ok(()) })?; diff --git a/controller/tests/self_send.rs b/controller/tests/self_send.rs index 4d362db4..d79bc632 100644 --- a/controller/tests/self_send.rs +++ b/controller/tests/self_send.rs @@ -97,7 +97,7 @@ fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { api.tx_lock_outputs(m, &slate)?; // Send directly to self wallet::controller::foreign_single_use(wallet1.clone(), mask1_i.clone(), |api| { - slate = api.receive_tx(&slate, Some("listener"))?; + slate = api.receive_tx(&slate, Some("listener"), None)?; Ok(()) })?; slate = api.finalize_tx(m, &slate)?; diff --git a/controller/tests/slatepack.rs b/controller/tests/slatepack.rs index c53f4511..5854f926 100644 --- a/controller/tests/slatepack.rs +++ b/controller/tests/slatepack.rs @@ -237,7 +237,7 @@ fn slatepack_exchange_test_impl( // wallet 2 receives file, completes, sends file back wallet::controller::foreign_single_use(wallet2.clone(), mask2_i.clone(), |api| { - slate = api.receive_tx(&slate, None)?; + slate = api.receive_tx(&slate, None, None)?; output_slatepack( &slate, &receive_file, @@ -351,7 +351,7 @@ fn slatepack_exchange_test_impl( // Wallet 2 receives the invoice transaction let res = slate_from_packed(&receive_file, use_armored, (&dec_key_2).as_ref())?; slate = res.1; - slate = api.finalize_invoice_tx(&slate)?; + slate = api.finalize_tx(&slate, false)?; output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?; Ok(()) })?; @@ -411,7 +411,7 @@ fn slatepack_exchange_test_impl( let res = slate_from_packed(&send_file, use_armored, (&dec_key_2).as_ref())?; slatepack = res.0; slate = res.1; - slate = api.receive_tx(&slate, None)?; + slate = api.receive_tx(&slate, None, None)?; output_slatepack( &slate, &receive_file, diff --git a/impls/Cargo.toml b/impls/Cargo.toml index c33720ab..75f34be8 100644 --- a/impls/Cargo.toml +++ b/impls/Cargo.toml @@ -33,7 +33,8 @@ hyper-timeout = "0.3" #Socks/Tor byteorder = "1" hyper = "0.13" -hyper-socks2 = "0.4" +#hyper-socks2 = "0.4" +hyper-socks2 = { git = "https://github.com/yeastplume/hyper-socks2", branch = "master" } ed25519-dalek = "1.0.0-pre.1" x25519-dalek = "0.6" data-encoding = "2" diff --git a/impls/src/adapters/http.rs b/impls/src/adapters/http.rs index d70e4e08..6697e65d 100644 --- a/impls/src/adapters/http.rs +++ b/impls/src/adapters/http.rs @@ -21,6 +21,7 @@ use serde::Serialize; use serde_json::{json, Value}; use std::net::SocketAddr; use std::path::MAIN_SEPARATOR; +use std::sync::Arc; use crate::tor::config as tor_config; use crate::tor::process as tor_process; @@ -33,6 +34,7 @@ pub struct HttpSlateSender { use_socks: bool, socks_proxy_addr: Option, tor_config_dir: String, + process: Option>, } impl HttpSlateSender { @@ -46,6 +48,7 @@ impl HttpSlateSender { use_socks: false, socks_proxy_addr: None, tor_config_dir: String::from(""), + process: None, }) } } @@ -64,8 +67,39 @@ impl HttpSlateSender { Ok(ret) } + /// launch TOR process + pub fn launch_tor(&mut self) -> Result<(), Error> { + // set up tor send process if needed + let mut tor = tor_process::TorProcess::new(); + if self.use_socks && self.process.is_none() { + let tor_dir = format!( + "{}{}{}", + &self.tor_config_dir, MAIN_SEPARATOR, TOR_CONFIG_PATH + ); + info!( + "Starting TOR Process for send at {:?}", + self.socks_proxy_addr + ); + tor_config::output_tor_sender_config( + &tor_dir, + &self.socks_proxy_addr.unwrap().to_string(), + ) + .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e)))?; + // Start TOR process + tor.torrc_path(&format!("{}/torrc", &tor_dir)) + .working_dir(&tor_dir) + .timeout(20) + .completion_percent(100) + .launch() + .map_err(|e| ErrorKind::TorProcess(format!("{:?}", e)))?; + self.process = Some(Arc::new(tor)); + } + Ok(()) + } + /// Check version of the listening wallet - pub fn check_other_version(&self, url: &str) -> Result { + pub fn check_other_version(&mut self, url: &str) -> Result { + self.launch_tor()?; let req = json!({ "jsonrpc": "2.0", "method": "check_version", @@ -81,8 +115,8 @@ impl HttpSlateSender { report = "Other wallet is incompatible and requires an upgrade. \ Please urge the other wallet owner to upgrade and try the transaction again." .to_string(); + error!("{}", report); } - error!("{}", report); ErrorKind::ClientCallback(report) })?; @@ -144,37 +178,14 @@ impl HttpSlateSender { } impl SlateSender for HttpSlateSender { - fn send_tx(&self, slate: &Slate) -> Result { + fn send_tx(&mut self, slate: &Slate, finalize: bool) -> Result { let trailing = match self.base_url.ends_with('/') { true => "", false => "/", }; let url_str = format!("{}{}v2/foreign", self.base_url, trailing); - // set up tor send process if needed - let mut tor = tor_process::TorProcess::new(); - if self.use_socks { - let tor_dir = format!( - "{}{}{}", - &self.tor_config_dir, MAIN_SEPARATOR, TOR_CONFIG_PATH - ); - warn!( - "Starting TOR Process for send at {:?}", - self.socks_proxy_addr - ); - tor_config::output_tor_sender_config( - &tor_dir, - &self.socks_proxy_addr.unwrap().to_string(), - ) - .map_err(|e| ErrorKind::TorConfig(format!("{:?}", e)))?; - // Start TOR process - tor.torrc_path(&format!("{}/torrc", &tor_dir)) - .working_dir(&tor_dir) - .timeout(20) - .completion_percent(100) - .launch() - .map_err(|e| ErrorKind::TorProcess(format!("{:?}", e)))?; - } + self.launch_tor()?; let slate_send = match self.check_other_version(&url_str)? { SlateVersion::V4 => VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?, @@ -192,16 +203,27 @@ impl SlateSender for HttpSlateSender { } }; // Note: not using easy-jsonrpc as don't want the dependencies in this crate - let req = json!({ - "jsonrpc": "2.0", - "method": "receive_tx", - "id": 1, - "params": [ - slate_send, - null, - null - ] - }); + let req = match finalize { + false => json!({ + "jsonrpc": "2.0", + "method": "receive_tx", + "id": 1, + "params": [ + slate_send, + null, + null + ] + }), + true => json!({ + "jsonrpc": "2.0", + "method": "finalize_tx", + "id": 1, + "params": [ + slate_send + ] + }), + }; + trace!("Sending receive_tx request: {}", req); let res: String = self.post(&url_str, None, req).map_err(|e| { @@ -209,7 +231,6 @@ impl SlateSender for HttpSlateSender { "Sending transaction slate to other wallet (is recipient listening?): {}", e ); - error!("{}", report); ErrorKind::ClientCallback(report) })?; @@ -225,6 +246,7 @@ impl SlateSender for HttpSlateSender { } let slate_value = res["result"]["Ok"].clone(); + trace!("slate_value: {}", slate_value); let slate = Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap()) .map_err(|e| { diff --git a/impls/src/adapters/keybase.rs b/impls/src/adapters/keybase.rs index 5dba044e..d78aef5d 100644 --- a/impls/src/adapters/keybase.rs +++ b/impls/src/adapters/keybase.rs @@ -292,7 +292,7 @@ fn poll(nseconds: u64, channel: &str) -> Option { impl SlateSender for KeybaseChannel { /// Send a slate to a keybase username then wait for a response for TTL seconds. - fn send_tx(&self, slate: &Slate) -> Result { + fn send_tx(&mut self, slate: &Slate, _finalize: bool) -> Result { let id = slate.id; // Send original slate to recipient with the SLATE_NEW topic diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index f1102955..6dace27b 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -31,7 +31,7 @@ use crate::util::ZeroingString; pub trait SlateSender { /// Send a transaction slate to another listening wallet and return result /// TODO: Probably need a slate wrapper type - fn send_tx(&self, slate: &Slate) -> Result; + fn send_tx(&mut self, slate: &Slate, finalize: bool) -> Result; } pub trait SlateReceiver { diff --git a/impls/src/lifecycle/seed.rs b/impls/src/lifecycle/seed.rs index a886ac53..6b74b1d1 100644 --- a/impls/src/lifecycle/seed.rs +++ b/impls/src/lifecycle/seed.rs @@ -76,11 +76,23 @@ impl WalletSeed { Ok(result) } - pub fn init_new(seed_length: usize) -> WalletSeed { + pub fn init_new( + seed_length: usize, + test_mode: bool, + password: Option, + ) -> WalletSeed { let mut seed: Vec = vec![]; let mut rng = thread_rng(); - for _ in 0..seed_length { - seed.push(rng.gen()); + if !test_mode { + for _ in 0..seed_length { + seed.push(rng.gen()); + } + } else { + // Hash password and use for test seed so we have a way of keeping test wallets unique + // but predictable + seed = blake2::blake2b::blake2b(32, b"", password.unwrap().as_bytes()) + .as_bytes() + .to_vec(); } WalletSeed(seed) } @@ -167,7 +179,7 @@ impl WalletSeed { let seed = match recovery_phrase { Some(p) => WalletSeed::from_mnemonic(p)?, - None => WalletSeed::init_new(seed_length), + None => WalletSeed::init_new(seed_length, test_mode, Some(password.clone())), }; let enc_seed = EncryptedWalletSeed::from_seed(&seed, password)?; @@ -324,7 +336,7 @@ mod tests { #[test] fn wallet_seed_encrypt() { let password = ZeroingString::from("passwoid"); - let wallet_seed = WalletSeed::init_new(32); + let wallet_seed = WalletSeed::init_new(32, false, None); let mut enc_wallet_seed = EncryptedWalletSeed::from_seed(&wallet_seed, password.clone()).unwrap(); println!("EWS: {:?}", enc_wallet_seed); diff --git a/impls/src/tor/process.rs b/impls/src/tor/process.rs index f9d7c14d..8ad1c05f 100644 --- a/impls/src/tor/process.rs +++ b/impls/src/tor/process.rs @@ -284,7 +284,7 @@ impl TorProcess { impl Drop for TorProcess { // kill the child fn drop(&mut self) { - trace!("DROPPING TOR PROCESS"); + debug!("Dropping TOR process"); self.kill().unwrap_or(()); } } diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index e426477b..0e048282 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -15,7 +15,8 @@ //! Generic implementation of owner API functions use strum::IntoEnumIterator; -use crate::api_impl::owner::check_ttl; +use crate::api_impl::owner::finalize_tx as owner_finalize; +use crate::api_impl::owner::{check_ttl, post_tx}; use crate::grin_core::core::transaction::Transaction; use crate::grin_keychain::Keychain; use crate::grin_util::secp::key::SecretKey; @@ -132,11 +133,12 @@ where Ok(ret_slate) } -/// Receive an tx that this wallet has issued -pub fn finalize_invoice_tx<'a, T: ?Sized, C, K>( +/// Receive a tx that this wallet has issued +pub fn finalize_tx<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, slate: &Slate, + post_automatically: bool, ) -> Result where T: WalletBackend<'a, C, K>, @@ -144,24 +146,32 @@ where K: Keychain + 'a, { let mut sl = slate.clone(); - check_ttl(w, &sl)?; let context = w.get_private_context(keychain_mask, sl.id.as_bytes())?; - if sl.is_compact() { - let mut temp_ctx = context.clone(); - temp_ctx.sec_key = context.initial_sec_key.clone(); - temp_ctx.sec_nonce = context.initial_sec_nonce.clone(); - selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &temp_ctx, false)?; + let is_invoice = context.is_invoice; + if is_invoice { + check_ttl(w, &sl)?; + if sl.is_compact() { + let mut temp_ctx = context.clone(); + temp_ctx.sec_key = context.initial_sec_key.clone(); + temp_ctx.sec_nonce = context.initial_sec_nonce.clone(); + selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &temp_ctx, false)?; + } + tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?; + tx::update_stored_tx(&mut *w, keychain_mask, &context, &mut sl, true)?; + { + let mut batch = w.batch(keychain_mask)?; + batch.delete_private_context(sl.id.as_bytes())?; + batch.commit()?; + } + sl.state = SlateState::Invoice3; + if sl.is_compact() { + sl.amount = 0; + } + } else { + sl = owner_finalize(w, keychain_mask, slate)?; } - tx::complete_tx(&mut *w, keychain_mask, &mut sl, &context)?; - tx::update_stored_tx(&mut *w, keychain_mask, &context, &mut sl, true)?; - { - let mut batch = w.batch(keychain_mask)?; - batch.delete_private_context(sl.id.as_bytes())?; - batch.commit()?; - } - sl.state = SlateState::Invoice3; - if sl.is_compact() { - sl.amount = 0; + if post_automatically { + post_tx(w.w2n_client(), sl.tx_or_err()?, true)?; } Ok(sl) } diff --git a/libwallet/src/api_impl/types.rs b/libwallet/src/api_impl/types.rs index 3e5980b8..21ad6aca 100644 --- a/libwallet/src/api_impl/types.rs +++ b/libwallet/src/api_impl/types.rs @@ -80,12 +80,8 @@ pub struct InitTxArgs { /// in one go #[derive(Clone, Serialize, Deserialize)] pub struct InitTxSendArgs { - /// The transaction method. Can currently be 'http' or 'keybase'. - pub method: String, /// The destination, contents will depend on the particular method pub dest: String, - /// Whether to finalize the result immediately if the send was successful - pub finalize: bool, /// Whether to post the transasction if the send and finalize were successful pub post_tx: bool, /// Whether to use dandelion when posting. If false, skip the dandelion relay diff --git a/libwallet/src/slate.rs b/libwallet/src/slate.rs index b75f78c6..b1f7da20 100644 --- a/libwallet/src/slate.rs +++ b/libwallet/src/slate.rs @@ -161,6 +161,21 @@ pub enum SlateState { Invoice3, } +impl fmt::Display for SlateState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let res = match self { + SlateState::Unknown => "UN", + SlateState::Standard1 => "S1", + SlateState::Standard2 => "S2", + SlateState::Standard3 => "S3", + SlateState::Invoice1 => "I1", + SlateState::Invoice2 => "I2", + SlateState::Invoice3 => "I3", + }; + write!(f, "{}", res) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] /// Kernel features arguments definition pub struct KernelFeaturesArgs { diff --git a/src/bin/grin-wallet.yml b/src/bin/grin-wallet.yml index 36fc1a36..cf07022c 100644 --- a/src/bin/grin-wallet.yml +++ b/src/bin/grin-wallet.yml @@ -118,32 +118,15 @@ subcommands: long: change_outputs default_value: "1" takes_value: true - - method: - help: Method for sending this transaction - short: m - long: method - possible_values: - - http - - file - - binfile - - self - - keybase - default_value: http - takes_value: true - dest: help: Send the transaction to the provided server (start with http://) or save as file. short: d long: dest takes_value: true - request_payment_proof: - help: Request a payment proof from the recipient. If sending to a tor address, the address will be filled automatically. + help: Request a payment proof from the recipient. If present, the destination must be provided as a slatepack address. short: y long: request_payment_proof - - proof_address: - help: Recipient proof address. If not using TOR, must be provided seprarately by the recipient - short: z - long: proof_address - takes_value: true - fluff: help: Fluff the transaction (ignore Dandelion relay protocol) short: f @@ -187,11 +170,6 @@ subcommands: help: Do not post the transaction. short: n long: nopost - - dest: - help: Specify file to save the finalized slate. - short: d - long: dest - takes_value: true - invoice: about: Initialize an invoice transaction. args: @@ -203,11 +181,7 @@ subcommands: short: d long: dest takes_value: true - - bin: - help: Whether to output file as binary - short: b - long: bin - #TODO: Remove HF3 + #TODO: Remove HF3 - v4: help: Output a V4 slate prior to HF3 block long: v4 @@ -234,17 +208,6 @@ subcommands: help: Estimates all possible Coin/Output selection strategies. short: e long: estimate-selection - - method: - help: Method for sending the processed invoice back to the invoice creator - short: m - long: method - possible_values: - - filebin - - file - - http - - self - default_value: file - takes_value: true - dest: help: Send the transaction to the provided server (start with http://) or save as file. short: d diff --git a/src/cmd/wallet.rs b/src/cmd/wallet.rs index b9ffc7ef..30c5c556 100644 --- a/src/cmd/wallet.rs +++ b/src/cmd/wallet.rs @@ -20,7 +20,7 @@ use semver::Version; use std::thread; use std::time::Duration; -const MIN_COMPAT_NODE_VERSION: &str = "3.0.0"; +const MIN_COMPAT_NODE_VERSION: &str = "4.0.0-alpha.1"; pub fn wallet_command( wallet_args: &ArgMatches<'_>, diff --git a/src/cmd/wallet_args.rs b/src/cmd/wallet_args.rs index f480f5fe..57269445 100644 --- a/src/cmd/wallet_args.rs +++ b/src/cmd/wallet_args.rs @@ -25,11 +25,9 @@ use grin_wallet_api::Owner; use grin_wallet_config::{config_file_exists, TorConfig, WalletConfig}; use grin_wallet_controller::command; use grin_wallet_controller::{Error, ErrorKind}; -use grin_wallet_impls::tor::config::is_tor_address; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl}; -use grin_wallet_impls::{PathToSlate, SlateGetter as _}; +use grin_wallet_libwallet::{self, Slate, SlatepackAddress, SlatepackArmor}; use grin_wallet_libwallet::{IssueInvoiceTxArgs, NodeClient, WalletInst, WalletLCProvider}; -use grin_wallet_libwallet::{Slate, SlatepackAddress}; use grin_wallet_util::grin_core as core; use grin_wallet_util::grin_core::core::amount_to_hr_string; use grin_wallet_util::grin_keychain as keychain; @@ -137,7 +135,39 @@ where Ok(phrase) } -fn prompt_pay_invoice(slate: &Slate, method: &str, dest: &str) -> Result { +fn prompt_slatepack() -> Result { + let interface = Arc::new(Interface::new("slatepack_input")?); + let mut message = String::from(""); + interface.set_report_signal(Signal::Interrupt, true); + interface.set_prompt("")?; + loop { + println!("Please paste your encoded slatepack message:"); + let res = interface.read_line()?; + match res { + ReadResult::Eof => break, + ReadResult::Signal(sig) => { + if sig == Signal::Interrupt { + interface.cancel_read_line()?; + return Err(ParseError::CancelledError); + } + } + ReadResult::Input(line) => { + if SlatepackArmor::decode(&line).is_ok() { + message = line; + break; + } else { + println!(); + println!("Input is not a valid slatepack."); + println!(); + interface.set_buffer(&line)?; + } + } + } + } + Ok(message) +} + +fn prompt_pay_invoice(slate: &Slate, dest: &str) -> Result { let interface = Arc::new(Interface::new("pay")?); let amount = amount_to_hr_string(slate.amount, false); interface.set_report_signal(Signal::Interrupt, true); @@ -154,10 +184,11 @@ fn prompt_pay_invoice(slate: &Slate, method: &str, dest: &str) -> Result 0 { + println!("* The wallet will IMMEDIATELY attempt to send the resulting transaction to the wallet listening at: '{}'.", dest); + println!("* If other wallet is not listening, the resulting transaction will output as a slatepack which you can manually send back to the invoice creator."); } else { - println!("* The resulting transaction will be saved to the file '{}', which you can manually send back to the invoice creator.", dest); + println!("* The resulting transaction will output as a slatepack which you can manually send back to the invoice creator."); } println!(); println!("Please review the above information carefully before proceeding"); @@ -189,6 +220,39 @@ fn prompt_pay_invoice(slate: &Slate, method: &str, dest: &str) -> Result Result { + let interface = Arc::new(Interface::new("http")?); + interface.set_report_signal(Signal::Interrupt, true); + interface.set_prompt("To proceed, type 'UNDERSTOOD' > ")?; + println!(); + println!("Http(s) is being deprecated in favour of the Slatepack Workflow"); + println!("This sending method is planned for removal as of the last scheduled Hardfork in Grin 5.0.0"); + println!("Please see https://github.com/mimblewimble/grin-rfcs/pull/55 for details"); + loop { + let res = interface.read_line()?; + match res { + ReadResult::Eof => return Ok(false), + ReadResult::Signal(sig) => { + if sig == Signal::Interrupt { + interface.cancel_read_line()?; + return Err(ParseError::CancelledError); + } + } + ReadResult::Input(line) => match line.trim() { + "Q" | "q" => return Err(ParseError::CancelledError), + result => { + if result == "UNDERSTOOD" { + return Ok(true); + } else { + println!("Please enter the phrase 'UNDERSTOOD' (without quotes) to continue or Q to quit"); + println!(); + } + } + }, + } + } +} + // instantiate wallet (needed by most functions) pub fn inst_wallet( @@ -349,13 +413,10 @@ pub fn parse_listen_args( if let Some(port) = args.value_of("port") { config.api_listen_port = port.parse().unwrap(); } - let method = parse_required(args, "method")?; if args.is_present("no_tor") { tor_config.use_tor_listener = false; } - Ok(command::ListenArgs { - method: method.to_owned(), - }) + Ok(command::ListenArgs {}) } pub fn parse_owner_api_args( @@ -404,36 +465,14 @@ pub fn parse_send_args(args: &ArgMatches) -> Result d, - None => "default", - } - } else { - if !estimate_selection_strategies { - parse_required(args, "dest")? - } else { - "" - } - } + let dest = match args.value_of("dest") { + Some(d) => d, + None => "default", }; - if !estimate_selection_strategies - && method == "http" - && !dest.starts_with("http://") - && !dest.starts_with("https://") - && is_tor_address(&dest).is_err() - { - let msg = format!( - "HTTP Destination should start with http://: or https://: {}", - dest, - ); - return Err(ParseError::ArgumentError(msg)); + if dest.to_uppercase().starts_with("HTTP") { + prompt_deprecate_http()?; } // change_outputs @@ -465,23 +504,25 @@ pub fn parse_send_args(args: &ArgMatches) -> Result { - // if the destination address is a TOR address, we don't need the address - // separately - match OnionV3Address::try_from(dest) { - Ok(a) => Some(SlatepackAddress::try_from(a).unwrap()), - Err(_) => { - let addr = parse_required(args, "proof_address")?; - match OnionV3Address::try_from(addr) { - Ok(a) => Some(SlatepackAddress::try_from(a).unwrap()), - Err(e) => { - let msg = format!("Invalid proof address: {:?}", e); - return Err(ParseError::ArgumentError(msg)); - } + true => match OnionV3Address::try_from(dest) { + Ok(a) => Some(SlatepackAddress::try_from(a).unwrap()), + Err(_) => { + let addr = match parse_required(args, "dest") { + Ok(a) => a, + Err(_) => { + let msg = format!("Destination Slatepack address must be provided (-d) if payment proof is requested"); + return Err(ParseError::ArgumentError(msg)); + } + }; + match SlatepackAddress::try_from(addr) { + Ok(a) => Some(a), + Err(e) => { + let msg = format!("Invalid slatepack address: {:?}", e); + return Err(ParseError::ArgumentError(msg)); } } } - } + }, false => None, } }; @@ -491,7 +532,6 @@ pub fn parse_send_args(args: &ArgMatches) -> Result Result Result { - // input - let tx_file = parse_required(receive_args, "input")?; +pub fn parse_receive_args(args: &ArgMatches) -> Result { + // input file + let input_file = match args.is_present("input") { + true => { + let file = args.value_of("input").unwrap().to_owned(); + // validate input + if !Path::new(&file).is_file() { + let msg = format!("File {} not found.", &file); + return Err(ParseError::ArgumentError(msg)); + } + Some(file) + } + false => None, + }; - // validate input - if !Path::new(&tx_file).is_file() { - let msg = format!("File {} not found.", &tx_file); - return Err(ParseError::ArgumentError(msg)); + let mut input_slatepack_message = None; + if input_file.is_none() { + input_slatepack_message = Some(prompt_slatepack()?); } Ok(command::ReceiveArgs { - input: tx_file.to_owned(), + input_file, + input_slatepack_message, }) } pub fn parse_finalize_args(args: &ArgMatches) -> Result { let fluff = args.is_present("fluff"); let nopost = args.is_present("nopost"); - let tx_file = parse_required(args, "input")?; - if !Path::new(&tx_file).is_file() { - let msg = format!("File {} not found.", tx_file); - return Err(ParseError::ArgumentError(msg)); - } - - let dest_file = match args.is_present("dest") { - true => Some(args.value_of("dest").unwrap().to_owned()), + let input_file = match args.is_present("input") { + true => { + let file = args.value_of("input").unwrap().to_owned(); + // validate input + if !Path::new(&file).is_file() { + let msg = format!("File {} not found.", &file); + return Err(ParseError::ArgumentError(msg)); + } + Some(file) + } false => None, }; + let mut input_slatepack_message = None; + if input_file.is_none() { + input_slatepack_message = Some(prompt_slatepack()?); + } + Ok(command::FinalizeArgs { - input: tx_file.to_owned(), + input_file, + input_slatepack_message, fluff: fluff, nopost: nopost, - dest: dest_file.to_owned(), }) } @@ -556,7 +614,6 @@ pub fn parse_issue_invoice_args( return Err(ParseError::ArgumentError(msg)); } }; - let bin = args.is_present("bin"); // TODO: Remove HF3 let output_v4_slate = args.is_present("v4"); @@ -571,11 +628,15 @@ pub fn parse_issue_invoice_args( false => None, } }; - // dest (output file) - let dest = parse_required(args, "dest")?; + + // dest, for encryption + let dest = match args.value_of("dest") { + Some(d) => d, + None => "default", + }; + Ok(command::IssueInvoiceArgs { dest: dest.into(), - bin, output_v4_slate, issue_args: IssueInvoiceTxArgs { dest_acct_name: None, @@ -585,9 +646,50 @@ pub fn parse_issue_invoice_args( }) } +fn get_slate( + owner_api: &mut Owner, + keychain_mask: Option<&SecretKey>, + args: &ArgMatches, +) -> Result<(Slate, Option), Error> +where + L: WalletLCProvider<'static, C, K>, + C: NodeClient + 'static, + K: keychain::Keychain + 'static, +{ + let input_file = match args.is_present("input") { + true => { + let file = args.value_of("input").unwrap().to_owned(); + // validate input + if !Path::new(&file).is_file() { + let msg = format!("File {} not found.", &file); + return Err(ErrorKind::GenericError(msg).into()); + } + Some(file) + } + false => None, + }; + + let mut input_slatepack_message = None; + if input_file.is_none() { + input_slatepack_message = Some(prompt_slatepack().map_err(|e| { + let msg = format!("{}", e); + ErrorKind::GenericError(msg) + })?) + } + + command::parse_slatepack( + owner_api, + keychain_mask, + input_file, + input_slatepack_message, + ) +} + pub fn parse_process_invoice_args( args: &ArgMatches, prompt: bool, + slate: Slate, + ret_address: Option, ) -> Result { // minimum_confirmations let min_c = parse_required(args, "minimum_confirmations")?; @@ -599,65 +701,28 @@ pub fn parse_process_invoice_args( // estimate_selection_strategies let estimate_selection_strategies = args.is_present("estimate_selection_strategies"); - // method - let method = parse_required(args, "method")?; - - // dest - let dest = { - if method == "self" { - match args.value_of("dest") { - Some(d) => d, - None => "default", - } - } else { - if !estimate_selection_strategies { - parse_required(args, "dest")? - } else { - "" - } - } - }; - if !estimate_selection_strategies - && method == "http" - && !dest.starts_with("http://") - && !dest.starts_with("https://") - { - let msg = format!( - "HTTP Destination should start with http://: or https://: {}", - dest, - ); - return Err(ParseError::ArgumentError(msg)); - } - // ttl_blocks let ttl_blocks = parse_u64_or_none(args.value_of("ttl_blocks")); // max_outputs let max_outputs = 500; - // file input only - let tx_file = parse_required(args, "input")?; - if prompt { - // Now we need to prompt the user whether they want to do this, - // which requires reading the slate - - let slate = match PathToSlate((&tx_file).into()).get_tx() { - Ok(s) => s.0, - Err(e) => return Err(ParseError::ArgumentError(format!("{}", e))), + let dest = match ret_address.clone() { + Some(a) => String::try_from(&a).unwrap(), + None => String::from(""), }; - - prompt_pay_invoice(&slate, method, dest)?; + // Now we need to prompt the user whether they want to do this, + prompt_pay_invoice(&slate, &dest)?; } Ok(command::ProcessInvoiceArgs { minimum_confirmations: min_c, selection_strategy: selection_strategy.to_owned(), estimate_selection_strategies, - method: method.to_owned(), - dest: dest.to_owned(), - max_outputs: max_outputs, - input: tx_file.to_owned(), + ret_address, + slate, + max_outputs, ttl_blocks, }) } @@ -712,12 +777,31 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result } pub fn parse_post_args(args: &ArgMatches) -> Result { - let tx_file = parse_required(args, "input")?; let fluff = args.is_present("fluff"); + // input file + let input_file = match args.is_present("input") { + true => { + let file = args.value_of("input").unwrap().to_owned(); + // validate input + if !Path::new(&file).is_file() { + let msg = format!("File {} not found.", &file); + return Err(ParseError::ArgumentError(msg)); + } + Some(file) + } + false => None, + }; + + let mut input_slatepack_message = None; + if input_file.is_none() { + input_slatepack_message = Some(prompt_slatepack()?); + } + Ok(command::PostArgs { - input: tx_file.to_owned(), - fluff: fluff, + input_file, + input_slatepack_message, + fluff, }) } @@ -973,6 +1057,12 @@ where K: keychain::Keychain + 'static, { let km = (&keychain_mask).as_ref(); + + if test_mode { + owner_api.doctest_mode = true; + owner_api.doctest_retain_tld = true; + } + match wallet_args.subcommand() { ("init", Some(args)) => { let a = arg_parse!(parse_init_args( @@ -982,7 +1072,7 @@ where &args, test_mode, )); - command::init(owner_api, &global_wallet_args, a) + command::init(owner_api, &global_wallet_args, a, test_mode) } ("recover", Some(_)) => { let a = arg_parse!(parse_recover_args(&global_wallet_args,)); @@ -1000,6 +1090,7 @@ where &a, &global_wallet_args.clone(), cli_mode, + test_mode, ) } ("owner_api", Some(args)) => { @@ -1007,7 +1098,7 @@ where let mut g = global_wallet_args.clone(); g.tls_conf = None; arg_parse!(parse_owner_api_args(&mut c, &args)); - command::owner_api(owner_api, keychain_mask, &c, &tor_config, &g) + command::owner_api(owner_api, keychain_mask, &c, &tor_config, &g, test_mode) } ("web", Some(_)) => command::owner_api( owner_api, @@ -1015,6 +1106,7 @@ where wallet_config, tor_config, global_wallet_args, + test_mode, ), ("account", Some(args)) => { let a = arg_parse!(parse_account_args(&args)); @@ -1028,11 +1120,19 @@ where Some(tor_config.clone()), a, wallet_config.dark_background_color_scheme.unwrap_or(true), + test_mode, ) } ("receive", Some(args)) => { let a = arg_parse!(parse_receive_args(&args)); - command::receive(owner_api, km, &global_wallet_args, a) + command::receive( + owner_api, + km, + &global_wallet_args, + a, + Some(tor_config.clone()), + test_mode, + ) } ("finalize", Some(args)) => { let a = arg_parse!(parse_finalize_args(&args)); @@ -1043,13 +1143,19 @@ where command::issue_invoice_tx(owner_api, km, a) } ("pay", Some(args)) => { - let a = arg_parse!(parse_process_invoice_args(&args, !test_mode)); + // get slate first + let (slate, address) = get_slate(owner_api, km, args)?; + + let a = arg_parse!(parse_process_invoice_args( + &args, !test_mode, slate, address + )); command::process_invoice( owner_api, km, Some(tor_config.clone()), a, wallet_config.dark_background_color_scheme.unwrap_or(true), + test_mode, ) } ("info", Some(args)) => { diff --git a/tests/cmd_line_basic.rs b/tests/cmd_line_basic.rs index 74694f30..3de7b43a 100644 --- a/tests/cmd_line_basic.rs +++ b/tests/cmd_line_basic.rs @@ -48,7 +48,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let app = App::from_yaml(yml); // wallet init - let arg_vec = vec!["grin-wallet", "-p", "password", "init", "-h"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "init", "-h"]; // should create new wallet file let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone()); execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?; @@ -64,7 +64,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), - "password", + "password1", "default", )?; wallet_proxy.add_wallet( @@ -75,6 +75,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: ); // Create wallet 2 + let arg_vec = vec!["grin-wallet", "-p", "password2", "init", "-h"]; let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone()); execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; @@ -83,7 +84,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let (wallet2, mask2_i) = instantiate_wallet( wallet_config2.clone(), client2.clone(), - "password", + "password2", "default", )?; wallet_proxy.add_wallet( @@ -101,13 +102,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: }); // Create some accounts in wallet 1 - let arg_vec = vec!["grin-wallet", "-p", "password", "account", "-c", "mining"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "account", "-c", "mining"]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "account", "-c", "account_1", @@ -118,7 +119,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password2", "account", "-c", "account_1", @@ -130,7 +131,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password2", "account", "-c", "account_2", @@ -138,18 +139,18 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; // let's see those accounts - let arg_vec = vec!["grin-wallet", "-p", "password", "account"]; - execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + let arg_vec = vec!["grin-wallet", "-p", "password1", "account"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; // let's see those accounts - let arg_vec = vec!["grin-wallet", "-p", "password", "account"]; + let arg_vec = vec!["grin-wallet", "-p", "password2", "account"]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; // Mine a bit into wallet 1 so we have something to send // (TODO: Be able to stop listeners so we can test this better) let wallet_config1 = config1.clone().members.unwrap().wallet; let (wallet1, mask1_i) = - instantiate_wallet(wallet_config1, client1.clone(), "password", "default")?; + instantiate_wallet(wallet_config1, client1.clone(), "password1", "default")?; let mask1 = (&mask1_i).as_ref(); grin_wallet_controller::controller::owner_single_use( Some(wallet1.clone()), @@ -166,31 +167,32 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false); // Update info and check - let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "info"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "-a", "mining", "info"]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; // try a file exchange - let file_name = format!("{}/tx1.part_tx", test_dir); - let response_file_name = format!("{}/tx1.part_tx.response", test_dir); + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b00.S1.slatepack", + test_dir + ); + let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "-a", "mining", "send", - "-m", - "file", - "-d", - &file_name, "10", ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let arg_vec = vec!["grin-wallet", "-a", "mining", "-p", "password1", "txs"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password2", "-a", "account_1", "receive", @@ -202,15 +204,20 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: // shouldn't be allowed to receive twice assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err()); + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b00.S2.slatepack", + test_dir + ); + let arg_vec = vec![ "grin-wallet", "-a", "mining", "-p", - "password", + "password1", "finalize", "-i", - &response_file_name, + &file_name, ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; bh += 1; @@ -219,7 +226,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), - "password", + "password1", "default", )?; let mask1 = (&mask1_i).as_ref(); @@ -245,10 +252,10 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: bh += 10; // update info for each - let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "info"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "-a", "mining", "info"]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; - let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "account_1", "info"]; + let arg_vec = vec!["grin-wallet", "-p", "password2", "-a", "account_1", "info"]; execute_command(&app, test_dir, "wallet2", &client1, arg_vec)?; // check results in wallet 2 @@ -256,7 +263,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: let (wallet2, mask2_i) = instantiate_wallet( wallet_config2.clone(), client2.clone(), - "password", + "password2", "default", )?; let mask2 = (&mask2_i).as_ref(); @@ -274,28 +281,113 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: }, )?; - // Self-send to same account, using smallest strategy + // Send encrypted from wallet 1 to wallet 2 + // output wallet 2's address for test creation purposes, let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password2", + "-a", + "account_1", + "address", + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; + + // Send encrypted to wallet 2 + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", "-a", "mining", "send", - "-m", - "file", "-d", + "slatepack1ak8aaxpjg6ct5uje4lgzvjp65l0nrmgxndp5xjy74sumzp7wasyspux2f5", + "10", + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b01.S1.slatepack", + test_dir + ); + let arg_vec = vec![ + "grin-wallet", + "-p", + "password2", + "-a", + "account_1", + "receive", + "-i", &file_name, + ]; + execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?; + + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b01.S2.slatepack", + test_dir + ); + + let arg_vec = vec![ + "grin-wallet", + "-a", + "mining", + "-p", + "password1", + "finalize", + "-i", + &file_name, + ]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + bh += 1; + + // Check our transaction log, should have bh entries + let wallet_config1 = config1.clone().members.unwrap().wallet; + let (wallet1, mask1_i) = instantiate_wallet( + wallet_config1.clone(), + client1.clone(), + "password1", + "default", + )?; + let mask1 = (&mask1_i).as_ref(); + + grin_wallet_controller::controller::owner_single_use( + Some(wallet1.clone()), + mask1, + None, + |api, m| { + api.set_active_account(m, "mining")?; + let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; + assert!(refreshed); + assert_eq!(txs.len(), bh as usize); + Ok(()) + }, + )?; + + // Send to self + let arg_vec = vec![ + "grin-wallet", + "-p", + "password1", + "-a", + "mining", + "send", + "-o", + "3", "-s", "smallest", "10", ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b02.S1.slatepack", + test_dir + ); let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "-a", "mining", "receive", @@ -304,25 +396,30 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?; + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b02.S2.slatepack", + test_dir + ); + let arg_vec = vec![ "grin-wallet", "-a", "mining", "-p", - "password", + "password1", "finalize", "-i", - &response_file_name, + &file_name, ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; bh += 1; - // Check our transaction log, should have bh entries + one for the self receive + // Check our transaction log, should have bh entries + 1 for self-seld let wallet_config1 = config1.clone().members.unwrap().wallet; let (wallet1, mask1_i) = instantiate_wallet( wallet_config1.clone(), client1.clone(), - "password", + "password1", "default", )?; let mask1 = (&mask1_i).as_ref(); @@ -340,111 +437,55 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: }, )?; - // Try using the self-send method, splitting up outputs for the fun of it - let arg_vec = vec![ - "grin-wallet", - "-p", - "password", - "-a", - "mining", - "send", - "-m", - "self", - "-d", - "mining", - "-o", - "3", - "-s", - "smallest", - "10", - ]; - execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; - bh += 1; - - // Check our transaction log, should have bh entries + 2 for the self receives - let wallet_config1 = config1.clone().members.unwrap().wallet; - let (wallet1, mask1_i) = instantiate_wallet( - wallet_config1.clone(), - client1.clone(), - "password", - "default", - )?; - let mask1 = (&mask1_i).as_ref(); - - grin_wallet_controller::controller::owner_single_use( - Some(wallet1.clone()), - mask1, - None, - |api, m| { - api.set_active_account(m, "mining")?; - let (refreshed, txs) = api.retrieve_txs(m, true, None, None)?; - assert!(refreshed); - assert_eq!(txs.len(), bh as usize + 2); - Ok(()) - }, - )?; - // Another file exchange, don't send, but unlock with repair command let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "-a", "mining", "send", - "-m", - "file", - "-d", - &file_name, "10", ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; - let arg_vec = vec!["grin-wallet", "-p", "password", "scan", "-d"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "scan", "-d"]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; // Another file exchange, cancel this time let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "-a", "mining", "send", - "-m", - "file", - "-d", - &file_name, "10", ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let arg_vec = vec!["grin-wallet", "-a", "mining", "-p", "password1", "txs"]; + execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "-a", "mining", "cancel", "-i", - "26", + "25", ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; // issue an invoice tx, wallet 2 - let file_name = format!("{}/invoice.slate", test_dir); - let arg_vec = vec![ - "grin-wallet", - "-p", - "password", - "invoice", - "-d", - &file_name, - "-b", - "65", - ]; + let arg_vec = vec!["grin-wallet", "-p", "password2", "invoice", "65"]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; - let output_file_name = format!("{}/invoice.slate.paid", test_dir); + let file_name = format!( + "{}/wallet2/slatepack/0436430c-2b02-624c-2032-570501212b05.I1.slatepack", + test_dir + ); // now pay the invoice tx, wallet 1 let arg_vec = vec![ @@ -452,23 +493,26 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: "-a", "mining", "-p", - "password", + "password1", "pay", "-i", &file_name, - "-d", - &output_file_name, ]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; + let file_name = format!( + "{}/wallet1/slatepack/0436430c-2b02-624c-2032-570501212b05.I2.slatepack", + test_dir + ); + // and finalize, wallet 2 let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password2", "finalize", "-i", - &output_file_name, + &file_name, ]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; @@ -477,14 +521,14 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: //bh += 5; // txs and outputs (mostly spit out for a visual in test logs) - let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "txs"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "-a", "mining", "txs"]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; // message output (mostly spit out for a visual in test logs) let arg_vec = vec![ "grin-wallet", "-p", - "password", + "password1", "-a", "mining", "txs", @@ -494,13 +538,13 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; // txs and outputs (mostly spit out for a visual in test logs) - let arg_vec = vec!["grin-wallet", "-p", "password", "-a", "mining", "outputs"]; + let arg_vec = vec!["grin-wallet", "-p", "password1", "-a", "mining", "outputs"]; execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?; - let arg_vec = vec!["grin-wallet", "-p", "password", "txs"]; + let arg_vec = vec!["grin-wallet", "-p", "password2", "txs"]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; - let arg_vec = vec!["grin-wallet", "-p", "password", "outputs"]; + let arg_vec = vec!["grin-wallet", "-p", "password2", "outputs"]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; // get tx output via -tx parameter @@ -518,7 +562,7 @@ fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet_controller:: Ok(()) }, )?; - let arg_vec = vec!["grin-wallet", "-p", "password", "txs", "-t", &tx_id[..]]; + let arg_vec = vec!["grin-wallet", "-p", "password2", "txs", "-t", &tx_id[..]]; execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?; // let logging finish diff --git a/tests/owner_v3_lifecycle.rs b/tests/owner_v3_lifecycle.rs index eb7dcb27..8b1edae0 100644 --- a/tests/owner_v3_lifecycle.rs +++ b/tests/owner_v3_lifecycle.rs @@ -399,7 +399,6 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { assert!(res.is_ok()); slate = res.unwrap(); api.tx_lock_outputs(m, &slate)?; - Ok(()) }, )?; @@ -409,9 +408,9 @@ fn owner_v3_lifecycle() -> Result<(), grin_wallet_controller::Error> { let req = serde_json::json!({ "jsonrpc": "2.0", "id": 1, - "method": "finalize_invoice_tx", + "method": "finalize_tx", "params": { - "slate": VersionedSlate::into_version(slate, SlateVersion::V3)?, + "slate": VersionedSlate::into_version(slate, SlateVersion::V4)?, } }); let res = diff --git a/util/src/ov3.rs b/util/src/ov3.rs index 0edb1277..00bed2ea 100644 --- a/util/src/ov3.rs +++ b/util/src/ov3.rs @@ -75,7 +75,7 @@ impl OnionV3Address { } /// Return as onion v3 address string - fn to_ov3_str(&self) -> String { + pub fn to_ov3_str(&self) -> String { // calculate checksum let mut hasher = Sha3_256::new(); hasher.input(b".onion checksum"); @@ -91,6 +91,11 @@ impl OnionV3Address { let ret = BASE32.encode(&address_bytes); ret.to_lowercase() } + + /// return as http url + pub fn to_http_str(&self) -> String { + format!("http://{}.onion", self.to_ov3_str()) + } } impl fmt::Display for OnionV3Address {