depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit c1cfa0e5a18a8cd2455eb8fb115f71f8841e3168
parent 029cae75e52654c51daa671cec151874ce93a448
Author: Antoine A <>
Date:   Thu,  2 Dec 2021 18:26:49 +0100

Refactor to new architecture

Diffstat:
MCargo.lock | 524++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mbtc-wire/Cargo.toml | 12++++++++----
Mbtc-wire/src/main.rs | 427+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mscript/test_bank.sh | 12++++++------
Mwire-gateway/Cargo.toml | 20++++++++++----------
Awire-gateway/db/schema.sql | 28++++++++++++++++++++++++++++
Awire-gateway/src/lib.rs | 4++++
Mwire-gateway/src/main.rs | 355+++++++++++++++++++------------------------------------------------------------
8 files changed, 722 insertions(+), 660 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -9,6 +9,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] name = "argh" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -51,6 +60,17 @@ dependencies = [ ] [[package]] +name = "async-trait" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -74,6 +94,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" [[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] name = "base64-compat" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -140,6 +166,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -160,11 +195,13 @@ dependencies = [ "bitcoincore-rpc", "criterion", "fastrand", - "owo-colors", + "postgres", "rand", - "rustyline", "serde", "thiserror", + "uri-pack", + "url", + "wire-gateway", ] [[package]] @@ -218,14 +255,12 @@ dependencies = [ ] [[package]] -name = "clipboard-win" -version = "4.2.2" +name = "cpufeatures" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ - "error-code", - "str-buf", - "winapi", + "libc", ] [[package]] @@ -318,6 +353,16 @@ dependencies = [ ] [[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -375,24 +420,12 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "digest" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", + "generic-array", ] [[package]] @@ -402,20 +435,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] -name = "endian-type" -version = "0.1.2" +name = "env_logger" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] [[package]] -name = "error-code" -version = "2.3.0" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" -dependencies = [ - "libc", - "str-buf", -] +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" @@ -427,17 +460,6 @@ dependencies = [ ] [[package]] -name = "fd-lock" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc110fe50727d46a428eed832df40affe9bf74d077cac1bf3f2718e823f14c5" -dependencies = [ - "cfg-if", - "libc", - "windows-sys", -] - -[[package]] name = "flate2" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -466,12 +488,28 @@ dependencies = [ ] [[package]] +name = "futures" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] name = "futures-channel" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -481,6 +519,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" [[package]] +name = "futures-executor" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" + +[[package]] +name = "futures-macro" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" + +[[package]] name = "futures-task" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -492,10 +564,26 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", ] [[package]] @@ -534,6 +622,16 @@ dependencies = [ ] [[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] name = "http" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -665,6 +763,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" [[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -680,6 +787,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] + +[[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -727,28 +845,6 @@ dependencies = [ ] [[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nix" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3bb9a13fa32bc5aeb64150cd3f32d6cf4c748f8f8a417cce5d2eb976a8370ba" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "memoffset", -] - -[[package]] name = "ntapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -783,10 +879,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] -name = "owo-colors" -version = "3.1.0" +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ad6d222cdc2351ccabb7af4f68bfaecd601b33c5f10d410ec89d2a273f6fff" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] [[package]] name = "percent-encoding" @@ -795,6 +916,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] +name = "phf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] name = "pin-project-lite" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -835,6 +974,49 @@ dependencies = [ ] [[package]] +name = "postgres" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb76d6535496f633fa799bb872ffb4790e9cbdedda9d35564ca0252f930c0dd5" +dependencies = [ + "bytes", + "fallible-iterator", + "futures", + "log", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b145e6a4ed52cb316a27787fc20fe8a25221cb476479f61e4e0327c15b98d91a" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04619f94ba0cc80999f4fc7073607cb825bc739a883cb6d20900fc5e009d6b0d" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] name = "ppv-lite86" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -850,22 +1032,34 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.10" +name = "quickcheck" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" dependencies = [ "proc-macro2", + "quote", + "syn", ] [[package]] -name = "radix_trie" -version = "0.2.1" +name = "quote" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ - "endian-type", - "nibble_vec", + "proc-macro2", ] [[package]] @@ -943,21 +1137,13 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" -dependencies = [ - "getrandom", - "redox_syscall", -] - -[[package]] name = "regex" version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -989,30 +1175,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" [[package]] -name = "rustyline" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790487c3881a63489ae77126f57048b42d62d3b2bafbf37453ea19eedb6340d6" -dependencies = [ - "bitflags", - "cfg-if", - "clipboard-win", - "dirs-next", - "fd-lock", - "libc", - "log", - "memchr", - "nix", - "radix_trie", - "scopeguard", - "smallvec", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "winapi", -] - -[[package]] name = "ryu" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1135,6 +1297,31 @@ dependencies = [ ] [[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "siphasher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] name = "smallvec" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1151,10 +1338,14 @@ dependencies = [ ] [[package]] -name = "str-buf" -version = "1.0.5" +name = "stringprep" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] [[package]] name = "strsim" @@ -1163,6 +1354,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] name = "syn" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1256,6 +1453,43 @@ dependencies = [ ] [[package]] +name = "tokio-postgres" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6c8b33df661b548dcd8f9bf87debb8c56c05657ed291122e1188698c2ece95" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "socket2", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] name = "tower-service" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1288,6 +1522,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + +[[package]] name = "unicode-bidi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1327,6 +1567,8 @@ dependencies = [ "criterion", "csv", "fastrand", + "quickcheck", + "quickcheck_macros", "serde_json", "thiserror", "url", @@ -1346,10 +1588,10 @@ dependencies = [ ] [[package]] -name = "utf8parse" -version = "0.2.0" +name = "version_check" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "walkdir" @@ -1474,55 +1716,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" - -[[package]] -name = "windows_i686_gnu" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" - -[[package]] -name = "windows_i686_msvc" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" - -[[package]] name = "wire-gateway" version = "0.1.0" dependencies = [ "async-compression", "base32", - "btc-wire", "hyper", "rand", "serde", @@ -1531,6 +1729,6 @@ dependencies = [ "serde_with", "thiserror", "tokio", - "uri-pack", + "tokio-postgres", "url", ] diff --git a/btc-wire/Cargo.toml b/btc-wire/Cargo.toml @@ -8,8 +8,6 @@ edition = "2021" bitcoincore-rpc = "0.14.0" # Cli args argh = "0.1.6" -# Readline -rustyline = "9.0.0" # Bech32 encoding and decoding bech32 = "0.8.1" # Secure random @@ -18,10 +16,16 @@ rand = { version = "0.8.4", features = ["getrandom"] } fastrand = "1.5.0" # Serialization library serde = { version = "1.0.130", features = ["derive"] } -# Zero allocation terminal color -owo-colors = "3.1.0" # Error macros thiserror = "1.0.30" +# Postgres client +postgres = "0.19.2" +# Optimized uri binary format +uri-pack = { path = "../uri-pack" } +# Url format +url = { version = "2.2.2", features = ["serde"] } +# Wire gateway api +wire-gateway = { path = "../wire-gateway" } [dev-dependencies] # statistics-driven micro-benchmarks diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs @@ -1,238 +1,249 @@ -use std::{collections::HashSet, str::FromStr, time::Duration}; - -use bitcoincore_rpc::{bitcoin::Amount, RpcApi}; +use bitcoincore_rpc::{ + bitcoin::{hashes::Hash, Address, Amount as BtcAmount, BlockHash, Txid}, + json::GetTransactionResultDetailCategory as Category, + Client as RPC, RpcApi, +}; use btc_wire::{ - rpc_utils::{ - common_rpc, dirty_guess_network, network_dir_path, received_since, wallet_rpc, CLIENT, WIRE, - }, - ClientExtended, + rpc_utils::{common_rpc, dirty_guess_network, sender_address, wallet_rpc, WIRE}, + segwit::DecodeSegWitErr, + ClientExtended, GetOpReturnErr, GetSegwitErr, }; - -#[derive(argh::FromArgs)] -/// Bitcoin metadata tester -struct Args { - #[argh(subcommand)] - cmd: Cmd, +use postgres::{Client, NoTls}; +use std::{ + collections::HashMap, + str::FromStr, + time::{Duration, SystemTime}, +}; +use url::Url; +use wire_gateway::api_common::Amount; + +#[repr(u8)] +enum Status { + /// Client have ask for a transaction + Proposed = 0, + /// Transaction have been announced to the bitcoin network + Pending = 1, + /// Transaction have been mined + Confirmed = 2, + /// The wire cannot failed to send this transaction and will try latter + Delayed = 3, } -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "mine")] -/// Mine block for the given wallet -struct MineCmd { - #[argh(option, short = 'r')] - /// repeat every ? ms - repeat: Option<u64>, - - #[argh(positional)] - /// the wallet name - wallet: String, - - #[argh(positional, default = "1")] - /// repeat every ? ms - amount: u64, +fn btc_payto_url(addr: &Address) -> Url { + Url::from_str(&format!("payto://bitcoin/{}", addr.to_string())).unwrap() } -#[derive(argh::FromArgs)] - -#[argh(subcommand, name = "send")] -/// Send message -struct SendRole { - #[argh(switch, short = 'm')] - /// mine on send - mine: bool, +fn btc_payto_addr(url: &Url) -> Result<Address, String> { + if url.domain() != Some("bitcoin") { + return Err("".to_string()); + } + let str = url.path().trim_start_matches('/'); + return Ok(Address::from_str(str).map_err(|_| "".to_string())?); } -#[derive(argh::FromArgs)] +fn btc_amount_to_taler_amount(amount: &BtcAmount) -> Amount { + let sat = amount.as_sat(); + return Amount::new("BTC", sat / 100_000_000, (sat % 100_000_000) as u32); +} -/// Receive message -#[argh(subcommand, name = "receive")] -struct ReceiveRole {} +fn taler_amount_to_btc_amount(amount: &Amount) -> Result<BtcAmount, String> { + if amount.currency != "BTC" { + return Err("Wrong currency".to_string()); + } -#[derive(argh::FromArgs)] -#[argh(subcommand)] -enum Role { - Send(SendRole), - Receive(ReceiveRole), + let sat = amount.value * 100_000_000 + amount.fraction as u64; + return Ok(BtcAmount::from_sat(sat)); } -enum Metadata { - SegWit, - OpReturn, +fn encode_info(wtid: &[u8; 32], url: &Url) -> Vec<u8> { + let mut buffer = Vec::new(); + buffer.extend_from_slice(wtid); + let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); + let packed = uri_pack::pack_uri(&parts).unwrap(); + buffer.push((url.scheme() == "http:") as u8); + buffer.extend_from_slice(&packed); + return buffer; } -impl ToString for Metadata { - fn to_string(&self) -> String { - match self { - Metadata::SegWit => "SegWit", - Metadata::OpReturn => "OpReturn", - } - .to_string() +fn decode_info(bytes: &[u8]) -> ([u8; 32], Url) { + let mut packed = uri_pack::unpack_uri(&bytes[33..]).unwrap(); + packed.insert_str(0, "://"); + if bytes[32] != 0 { + packed.insert(0, 's'); } + packed.insert_str(0, "http"); + let url = Url::parse(&packed).unwrap(); + return (bytes[..32].try_into().unwrap(), url); } -impl FromStr for Metadata { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "SegWit" | "Segwit" | "segwit" | "seg" | "s" => Ok(Self::SegWit), - "OpReturn" | "Opreturn" | "opreturn" | "op" | "o" => Ok(Self::OpReturn), - _ => Err(format!("Unknown")), +#[cfg(test)] +mod test { + use btc_wire::test::rand_key; + use url::Url; + + use crate::{decode_info, encode_info}; + + #[test] + fn decode_encode_info() { + let key = rand_key(); + let urls = [ + "https://git.taler.net/", + "https://git.taler.net/depolymerization.git/", + ]; + + for url in urls { + let url = Url::parse(url).unwrap(); + let encode = encode_info(&key, &url); + let decode = decode_info(&encode); + assert_eq!(key, decode.0); + assert_eq!(url, decode.1); } } } -#[derive(argh::FromArgs)] -#[argh(subcommand, name = "msg")] -/// Msg exchange msg using metadata -struct MsgCmd { - #[argh(subcommand)] - role: Role, - - #[argh(positional)] - method: Option<Metadata>, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand)] -enum Cmd { - Mine(MineCmd), - Msg(MsgCmd), -} - -fn main() { - // Guess network by trying to connect to a JSON RPC server - let network = dirty_guess_network(); - { - let existing_wallets: HashSet<String> = - std::fs::read_dir(network_dir_path(network).join("wallets")) - .unwrap() - .filter_map(|it| it.ok()) - .map(|it| it.file_name().to_string_lossy().to_string()) - .collect(); - - let rpc = common_rpc(network).expect("Failed to open common client"); - if !existing_wallets.contains(CLIENT) || !existing_wallets.contains(WIRE) { - println!("Generate new wallets"); - // Create wallets - rpc.create_wallet(&WIRE, None, None, None, None).unwrap(); - rpc.create_wallet(&CLIENT, None, None, None, None).unwrap(); - // Add 50 BTC to client wallet - let rpc = wallet_rpc(network, CLIENT); - let addr = rpc.get_new_address(None, None).unwrap(); - rpc.generate_to_address(101 /* Need 100 blocks to validate */, &addr) - .unwrap(); - } else { - rpc.load_wallet(&WIRE).ok(); - rpc.load_wallet(&CLIENT).ok(); +/// Listen for new proposed transactions and announce them on the bitcoin network +fn sender(rpc: RPC, mut db: Client) -> Result<(), Box<dyn std::error::Error>> { + loop { + let mut announced = Vec::new(); + let rows = db.query( + "SELECT id, amount, wtid, debit_acc, credit_acc, exchange_url FROM tx_out WHERE status=$1", + &[&(Status::Proposed as i16)], + )?; + + for row in rows { + let result: Result<i32, Box<dyn std::error::Error>> = (|| { + let id: i32 = row.get(0); + let amount: Amount = Amount::from_str(row.get(1))?; + let reserve_pub: &[u8] = row.get(2); + let debit_addr: Address = btc_payto_addr(&Url::parse(row.get(3))?)?; + let credit_addr: Address = btc_payto_addr(&Url::parse(row.get(4))?)?; + let exchange_base_url: Url = Url::parse(row.get(5))?; + let metadata = encode_info(reserve_pub.try_into()?, &exchange_base_url); + rpc.send_op_return( + &credit_addr, + taler_amount_to_btc_amount(&amount)?, + &metadata, + )?; + println!("{} >> {} {} PENDING", &debit_addr, &credit_addr, &amount); + Ok(id) + })(); + match result { + Ok(id) => announced.push(id), + Err(err) => println!("sender: {}", err), + } } + + db.execute( + "UPDATE tx_out SET status = $1 WHERE id = ANY($2)", + &[&(Status::Pending as i16), &announced], + )?; + std::thread::sleep(Duration::from_millis(300)); } +} - let args: Args = argh::from_env(); - - match args.cmd { - Cmd::Mine(MineCmd { - repeat, - wallet, - amount, - }) => { - let rpc = wallet_rpc(network, &wallet); - let balance = rpc.get_balance(None, None).unwrap(); - let addr = rpc.get_new_address(None, None).unwrap(); - println!("{} {}", wallet, balance); - if amount == 0 { - return; - } - loop { - println!("Mine {} block", amount); - rpc.generate_to_address(amount, &addr).unwrap(); - - let balance = rpc.get_balance(None, None).unwrap(); - println!("{} {}", wallet, balance); - - if let Some(wait) = repeat { - std::thread::sleep(Duration::from_millis(wait)) - } else { - return; - } - } - } - Cmd::Msg(MsgCmd { role, method }) => { - println!("Initial state:"); - let client_rpc = wallet_rpc(network, CLIENT); - let wire_rpc = wallet_rpc(network, WIRE); - let client_addr = client_rpc.get_new_address(None, None).unwrap(); - let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); - println!("{} {}", WIRE, wire_rpc.get_balance(None, None).unwrap()); - println!("{} {}", CLIENT, client_rpc.get_balance(None, None).unwrap()); - let method = method.unwrap_or(Metadata::SegWit); - match role { - Role::Send(SendRole { mine }) => { - println!("Send message using {}", method.to_string()); - let mut rl = rustyline::Editor::<()>::new(); - loop { - let rl = rl.readline(">> "); - match rl { - Ok(line) => { - match method { - Metadata::SegWit => { - for chunk in line.as_bytes().chunks(32) { - let mut key = [0; 32]; - key[..chunk.len()].copy_from_slice(&chunk); - client_rpc - .send_segwit_key( - &wire_addr, - Amount::from_sat(4200), - &key, - ) - .unwrap(); - } - } - Metadata::OpReturn => { - for chunk in line.as_bytes().chunks(80) { - client_rpc - .send_op_return( - &wire_addr, - Amount::from_sat(4200), - chunk, - ) - .unwrap(); - } - } - } - - if mine { - client_rpc.generate_to_address(1, &client_addr).unwrap(); - } - } - Err(_) => break, - } +/// Listen for mined block and index confirmed transactions into the database +fn watcher(rpc: RPC, mut db: Client) -> Result<(), Box<dyn std::error::Error>> { + let stored_hash = db + .query("SELECT value FROM state WHERE name='last_hash'", &[]) + .unwrap(); + let mut last_hash: Option<BlockHash> = if stored_hash.len() == 1 { + Some(BlockHash::from_slice(stored_hash[0].get(0)).unwrap()) + } else { + None + }; + let confirmation = 1; + + loop { + let list = + rpc.list_since_block(last_hash.as_ref(), Some(confirmation), None, Some(true))?; + + // List all confirmed send and receive transactions since last check + let txs: HashMap<Txid, Category> = list + .transactions + .into_iter() + .filter_map(|tx| { + let cat = tx.detail.category; + (tx.info.confirmations >= confirmation as i32 + && (cat == Category::Send || cat == Category::Receive)) + .then(|| (tx.info.txid, cat)) + }) + .collect(); + + for (id, category) in txs { + match category { + Category::Send => match rpc.get_tx_op_return(&id) { + Ok((full, metadata)) => { + let (wtid, exchange_base_url) = decode_info(&metadata); + let credit_addr = sender_address(&rpc, &full)?; + let amount = + btc_amount_to_taler_amount(&full.tx.amount.abs().to_unsigned()?.into()); + let row = db.query_one( + "UPDATE tx_out SET status=$1 WHERE wtid=$2 AND exchange_url=$3 RETURNING debit_acc", + &[ + &(Status::Confirmed as i16), + &wtid.as_ref(), + &exchange_base_url.to_string(), + ], + )?; + let debit_addr = btc_payto_addr(&Url::parse(row.get(0))?)?; + println!("{} >> {} {} CONFIRMED", &debit_addr, &credit_addr, &amount); } - } - Role::Receive(_) => { - println!("Receive message using {}", method.to_string()); - let (_, mut hash) = received_since(&wire_rpc, None).unwrap(); - loop { - wire_rpc.wait_for_new_block(0).ok(); - println!("new block"); - let (ids, nhash) = received_since(&wire_rpc, Some(&hash)).unwrap(); - hash = nhash; - for id in ids { - let msg: String = match method { - Metadata::SegWit => { - let (_, decoded) = wire_rpc.get_tx_segwit_key(&id).unwrap(); - String::from_utf8_lossy(&decoded).to_string() - } - Metadata::OpReturn => { - let (_, decoded) = wire_rpc.get_tx_op_return(&id).unwrap(); - String::from_utf8_lossy(&decoded).to_string() - } - }; - println!("> {}", msg); - } + Err(err) => match err { + GetOpReturnErr::MissingOpReturn => {} // ignore + err => println!("send: {} {}", id, err), + }, + }, + Category::Receive => match rpc.get_tx_segwit_key(&id) { + Ok((full, reserve_pub)) => { + let debit_addr = sender_address(&rpc, &full)?; + let credit_addr = full.tx.details[0].address.as_ref().unwrap(); + let time = full.tx.info.blocktime.unwrap(); + let date = SystemTime::UNIX_EPOCH + Duration::from_secs(time); + let amount = btc_amount_to_taler_amount(&full.tx.amount.to_unsigned()?); + db.execute("INSERT INTO tx_in (_date, amount, reserve_pub, debit_acc, credit_acc) VALUES ($1, $2, $3, $4, $5)", &[ + &date, &amount.to_string(), &reserve_pub.as_ref(), &btc_payto_url(&debit_addr).to_string(), &btc_payto_url(&credit_addr).to_string() + ])?; + println!("{} << {} {}", &debit_addr, &credit_addr, &amount); } - } + Err(err) => match err { + GetSegwitErr::Decode( + DecodeSegWitErr::MissingSegWitAddress | DecodeSegWitErr::NoMagicIdMatch, + ) => {} + err => println!("receive: {} {}", id, err), + }, + }, + Category::Generate | Category::Immature | Category::Orphan => {} } } + if let Some(_) = last_hash { + db.execute( + "UPDATE state SET value=$1 WHERE name='last_hash'", + &[&list.lastblock.as_ref()], + )?; + } else { + db.execute( + "INSERT INTO state (name, value) VALUES ('last_hash', $1)", + &[&list.lastblock.as_ref()], + )?; + }; + last_hash = Some(list.lastblock); + println!("Wait for block"); + rpc.wait_for_new_block(0).ok(); } } + +fn main() { + // Guess network by trying to connect to a JSON RPC server + let network = dirty_guess_network(); + let rpc = common_rpc(network).unwrap(); + rpc.load_wallet(&WIRE).ok(); + let rpc_watcher = wallet_rpc(network, "wire"); + let rpc_sender = wallet_rpc(network, "wire"); + + let postgres_config = "postgres://localhost/wire_gateway?user=postgres"; + let db_watcher = Client::connect(&postgres_config, NoTls).unwrap(); + let db_sender = Client::connect(&postgres_config, NoTls).unwrap(); + let join = std::thread::spawn(move || sender(rpc_sender, db_sender).unwrap()); + watcher(rpc_watcher, db_watcher).unwrap(); + join.join().unwrap(); +} diff --git a/script/test_bank.sh b/script/test_bank.sh @@ -17,11 +17,11 @@ trap cleanup EXIT echo "OK" -BANK_ENDPOINT=http://localhost:8080/ +BANK_ENDPOINT=http://172.21.80.1:8080/ echo -n "Making wire transfer to exchange ..." -btc-wire-cli transfer 0.00004 -btc-wire-cli nblock +btc-wire-cli.exe transfer 0.00004 +btc-wire-cli.exe nblock echo " OK" echo -n "Requesting exchange incoming transaction list ..." @@ -32,12 +32,12 @@ echo " OK" echo -n "Making wire transfer from exchange..." -ADDRESS=`bitcoin-cli -rpcwallet=client getnewaddress` +ADDRESS=`bitcoin-cli.exe -rpcwallet=client getnewaddress` taler-exchange-wire-gateway-client \ -b $BANK_ENDPOINT \ -C payto://bitcoin/$ADDRESS \ - -a BTC:0.00002 > /dev/null -btc-wire-cli nblock + la BTC:0.00002 > /dev/null +btc-wire-cli.exe nblock echo " OK" diff --git a/wire-gateway/Cargo.toml b/wire-gateway/Cargo.toml @@ -5,13 +5,15 @@ edition = "2021" [dependencies] # Http library -hyper = { version = "0.14.15", features = [ - "http1", - "server", - "runtime", -] } +hyper = { version = "0.14.15", features = ["http1", "server", "runtime"] } # Async runtime -tokio = { version = "1.14.0", features = ["net", "macros", "rt-multi-thread", "io-std", "io-util"] } +tokio = { version = "1.14.0", features = [ + "net", + "macros", + "rt-multi-thread", + "io-std", + "io-util", +] } # Serialization framework serde = { version = "1.0.130", features = ["derive"] } # Serialization helper @@ -30,7 +32,5 @@ async-compression = { version = "0.3.8", features = ["tokio", "zlib"] } rand = { version = "0.8.4", features = ["getrandom"] } # Url format url = { version = "2.2.2", features = ["serde"] } -# Bitcoin taler util -btc-wire = { path = "../btc-wire" } -# Optimized uri binary format -uri-pack = { path = "../uri-pack" } +# Async postgres client +tokio-postgres = { version = "0.7.5" } diff --git a/wire-gateway/db/schema.sql b/wire-gateway/db/schema.sql @@ -0,0 +1,27 @@ +-- Key value state +CREATE TABLE state ( + name TEXT PRIMARY KEY, + value BYTEA NOT NULL +); + +-- Incoming transactions +CREATE TABLE tx_in { + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL, + amount TEXT NOT NULL, + reserve_pub BYTEA NOT NULL, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL +} + +-- Outgoing transactions +CREATE TABLE tx_out { + id SERIAL PRIMARY KEY, + _date TIMESTAMP NOT NULL, + amount TEXT NOT NULL, + wtid BYTEA NOT NULL, + debit_acc TEXT NOT NULL, + credit_acc TEXT NOT NULL, + exchange_url TEXT NOT NULL, + status SMALLINT NOT NULL +} +\ No newline at end of file diff --git a/wire-gateway/src/lib.rs b/wire-gateway/src/lib.rs @@ -0,0 +1,3 @@ +pub mod api_common; +pub mod api_wire; +pub mod error_codes; +\ No newline at end of file diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs @@ -1,22 +1,8 @@ -use std::{ - collections::HashMap, - str::FromStr, - time::{Duration, SystemTime}, -}; +use std::str::FromStr; -use api_common::{Amount, SafeUint64, ShortHashCode, Timestamp}; +use api_common::{Amount, SafeUint64, Timestamp, ShortHashCode}; use api_wire::{OutgoingBankTransaction, OutgoingHistory}; use async_compression::tokio::bufread::ZlibDecoder; -use btc_wire::{ - bitcoincore_rpc::{ - bitcoin::{Address, Amount as BtcAmount, BlockHash, Txid}, - json::GetTransactionResultDetailCategory as Category, - Client, RpcApi, - }, - rpc_utils::{common_rpc, dirty_guess_network, sender_address, wallet_rpc}, - segwit::DecodeSegWitErr, - ClientExtended, GetOpReturnErr, GetSegwitErr, -}; use error_codes::ErrorCode; use hyper::{ header, @@ -24,7 +10,8 @@ use hyper::{ service::{make_service_fn, service_fn}, Body, Error, Method, Request, Response, Server, StatusCode, }; -use tokio::{io::AsyncReadExt, sync::Mutex}; +use tokio::io::AsyncReadExt; +use tokio_postgres::{Client, NoTls}; use url::Url; use crate::{ @@ -36,205 +23,29 @@ use crate::{ mod error_codes; -fn btc_payto_url(addr: &Address) -> Url { - Url::from_str(&format!("payto://bitcoin/{}", addr.to_string())).unwrap() -} - -fn btc_payto_addr(url: &Url) -> Result<&str, ServerErr> { - if url.domain() != Some("bitcoin") - || url.scheme() != "payto" - || url.username() != "" - || url.password().is_some() - || url.query().is_some() - || url.fragment().is_some() - { - return Err(( - StatusCode::BAD_REQUEST, - ErrorCode::GENERIC_PAYTO_URI_MALFORMED, - )); - } - return Ok(url.path().trim_start_matches('/')); -} - -impl Into<Amount> for BtcAmount { - fn into(self) -> Amount { - let sat = self.as_sat(); - return Amount::new("BTC", sat / 100_000_000, (sat % 100_000_000) as u32); - } -} - -impl TryFrom<Amount> for BtcAmount { - type Error = String; - - fn try_from(value: Amount) -> Result<Self, Self::Error> { - if value.currency != "BTC" { - return Err("Wrong currency".to_string()); - } - - let sat = value.value * 100_000_000 + value.fraction as u64; - return Ok(Self::from_sat(sat)); - } -} - -fn encode_info(wtid: &[u8; 32], url: &Url) -> Vec<u8> { - let mut buffer = Vec::new(); - buffer.extend_from_slice(wtid); - let parts = format!("{}{}", url.domain().unwrap_or(""), url.path()); - let packed = uri_pack::pack_uri(&parts).unwrap(); - buffer.push((url.scheme() == "http:") as u8); - buffer.extend_from_slice(&packed); - return buffer; -} - -fn decode_info(bytes: &[u8]) -> ([u8; 32], Url) { - let mut packed = uri_pack::unpack_uri(&bytes[33..]).unwrap(); - packed.insert_str(0, "://"); - if bytes[32] == 0 { - packed.insert(0, 's'); - } - packed.insert_str(0, "http"); - let url = Url::parse(&packed).unwrap(); - return (bytes[..32].try_into().unwrap(), url); -} - -#[cfg(test)] -mod test { - use btc_wire::test::rand_key; - use url::Url; - - use crate::{decode_info, encode_info}; - - #[test] - fn decode_encode_info() { - let key = rand_key(); - let urls = [ - "https://git.taler.net/", - "https://git.taler.net/depolymerization.git/", - ]; - - for url in urls { - let url = Url::parse(url).unwrap(); - let encode = encode_info(&key, &url); - let decode = decode_info(&encode); - assert_eq!(key, decode.0); - assert_eq!(url, decode.1); - dbg!(encode.len() - 32, urls.len()); - } - } +fn check_pay_to(url: &Url) -> bool { + return url.domain() == Some("bitcoin") + && url.scheme() == "payto" + && url.username() == "" + && url.password().is_none() + && url.query().is_none() + && url.fragment().is_none(); } #[tokio::main] async fn main() { - let network = dirty_guess_network(); - { - let common = common_rpc(network).unwrap(); - common.load_wallet("wire").ok(); - } - let state = ServerState { - incoming: Mutex::new(Vec::new()), - outgoing: Mutex::new(Vec::new()), - client: Mutex::new(wallet_rpc(network, "wire")), - }; - let state: &'static ServerState = Box::leak(Box::new(state)); - - // BTC worker thread - - std::thread::spawn(move || { - let result: Result<(), Box<dyn std::error::Error>> = (move || { - let rpc = wallet_rpc(network, "wire"); - let self_addr = rpc.get_new_address(None, None)?; - let mut last_hash: Option<BlockHash> = None; - let confirmation = 1; - - loop { - let txs = - rpc.list_since_block(last_hash.as_ref(), Some(confirmation), None, Some(true))?; - last_hash = Some(txs.lastblock); - - // List all confirmed send and receive transactions since last check - let txs: HashMap<Txid, Category> = txs - .transactions - .into_iter() - .filter_map(|tx| { - let cat = tx.detail.category; - (tx.info.confirmations >= confirmation as i32 - && (cat == Category::Send || cat == Category::Receive)) - .then(|| (tx.info.txid, cat)) - }) - .collect(); + let (client, connection) = + tokio_postgres::connect("postgres://localhost/wire_gateway?user=postgres", NoTls) + .await + .unwrap(); - for (id, category) in txs { - match category { - Category::Send => match rpc.get_tx_op_return(&id) { - Ok((full, metadata)) => { - let (wtid, exchange_base_url) = decode_info(&metadata); - let credit_addr = sender_address(&rpc, &full)?; - let time = full.tx.info.blocktime.unwrap(); - let date = Timestamp::from( - SystemTime::UNIX_EPOCH + Duration::from_secs(time), - ); - let amount = full.tx.amount.abs().to_unsigned()?.into(); - let mut lock = state.outgoing.blocking_lock(); - println!("{} >> {} {}", &self_addr, &credit_addr, &amount); - let array: [u8; 32] = wtid[..32].try_into()?; - let wtid = Base32::from(array); - let row_id = lock.len() as u64 + 1; - lock.push(OutgoingTransaction { - row_id: SafeUint64::try_from(row_id)?, - date, - amount, - debit_account: btc_payto_url(&self_addr), - credit_account: btc_payto_url(&credit_addr), - wtid, - exchange_base_url, - }); - } - Err(err) => match err { - GetOpReturnErr::MissingOpReturn => {} // ignore - err => println!("send: {} {}", id, err), - }, - }, - Category::Receive => match rpc.get_tx_segwit_key(&id) { - Ok((full, reserve_pub)) => { - let debit_addr = sender_address(&rpc, &full)?; - let credit_addr = full.tx.details[0].address.as_ref().unwrap(); - let time = full.tx.info.blocktime.unwrap(); - let date = Timestamp::from( - SystemTime::UNIX_EPOCH + Duration::from_secs(time), - ); - let amount: Amount = full.tx.amount.to_unsigned().unwrap().into(); - dbg!(full.tx.amount.to_unsigned(), amount); - let amount = full.tx.amount.to_unsigned()?.into(); - let mut lock = state.incoming.blocking_lock(); - println!("{} << {} {}", &debit_addr, &credit_addr, &amount); - let row_id = lock.len() as u64 + 1; - lock.push(IncomingTransaction { - row_id: SafeUint64::try_from(row_id)?, - date, - amount, - reserve_pub: reserve_pub.into(), - debit_account: btc_payto_url(&debit_addr), - credit_account: btc_payto_url(credit_addr), - }); - } - Err(err) => match err { - GetSegwitErr::Decode( - DecodeSegWitErr::MissingSegWitAddress - | DecodeSegWitErr::NoMagicIdMatch, - ) => {} - err => println!("receive: {} {}", id, err), - }, - }, - Category::Generate | Category::Immature | Category::Orphan => {} - } - } - println!("Wait for block"); - rpc.wait_for_new_block(0).ok(); - } - })(); - dbg!(result).unwrap(); + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } }); - + let state = ServerState { client }; + let state: &'static ServerState = Box::leak(Box::new(state)); let addr = ([0, 0, 0, 0], 8080).into(); let make_service = make_service_fn(move |_| async move { Ok::<_, Error>(service_fn(move |req| async move { @@ -273,29 +84,8 @@ async fn main() { } } -struct IncomingTransaction { - row_id: SafeUint64, - date: Timestamp, - amount: Amount, - reserve_pub: ShortHashCode, - debit_account: Url, - credit_account: Url, -} - -struct OutgoingTransaction { - row_id: SafeUint64, - date: Timestamp, - amount: Amount, - wtid: ShortHashCode, - debit_account: Url, - credit_account: Url, - exchange_base_url: Url, -} - struct ServerState { - incoming: Mutex<Vec<IncomingTransaction>>, - outgoing: Mutex<Vec<OutgoingTransaction>>, - client: Mutex<Client>, + client: Client, } pub mod api_common; @@ -352,30 +142,30 @@ async fn router( "/transfer" => { assert_method(&parts, Method::POST)?; let request: TransferRequest = parse_json(&parts, body).await; - let client = state.client.lock().await; - let address = btc_payto_addr(&request.credit_account)?; - let to = Address::from_str(address).map_err(|_| { - ( + if !check_pay_to(&request.credit_account) { + return Err(( StatusCode::BAD_REQUEST, - ErrorCode::GENERIC_PARAMETER_MALFORMED, - ) - })?; - let amount: BtcAmount = request.amount.try_into().map_err(|_| { - ( + ErrorCode::GENERIC_PAYTO_URI_MALFORMED, + )); + } + if request.amount.currency != "BTC" { + return Err(( StatusCode::BAD_REQUEST, ErrorCode::GENERIC_PARAMETER_MALFORMED, - ) - })?; - let metadata = encode_info(&request.wtid, &request.exchange_base_url); - client - .send_op_return(&to, amount, &metadata) - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, ErrorCode::INVALID))?; + )); + } let timestamp = Timestamp::now(); + let row = state.client.query_one("INSERT INTO tx_out (_date, amount, wtid, debit_acc, credit_acc, exchange_url, status) VALUES (now(), $1, $2, $3, $4, $5, $6) RETURNING id", &[ + &request.amount.to_string(), &request.wtid.as_ref(), &"payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj", &request.credit_account.to_string(), &request.exchange_base_url.to_string(), &0i16 + ]).await.unwrap(); json_response( StatusCode::OK, &TransferResponse { timestamp, - row_id: SafeUint64::try_from(0).unwrap(), + row_id: { + let id: i32 = row.get(0); + SafeUint64::try_from(id as u64).unwrap() + }, }, ) .await @@ -384,16 +174,29 @@ async fn router( assert_method(&parts, Method::GET)?; let params: HistoryParams = serde_urlencoded::from_str(parts.uri.query().unwrap_or("")).unwrap(); - let guard = state.incoming.lock().await; - let transactions: Vec<IncomingBankTransaction> = guard - .iter() - .map(|tx| IncomingBankTransaction::IncomingReserveTransaction { - row_id: tx.row_id, - date: tx.date, - amount: tx.amount.clone(), - credit_account: tx.credit_account.clone(), - debit_account: tx.debit_account.clone(), - reserve_pub: tx.reserve_pub.clone(), + let transactions = state + .client + .query( + "SELECT id, _date, amount, reserve_pub, debit_acc, credit_acc FROM tx_in", + &[], + ) + .await + .unwrap() + .into_iter() + .map(|row| IncomingBankTransaction::IncomingReserveTransaction { + row_id: { + let id: i32 = row.get(0); + SafeUint64::try_from(id as u64).unwrap() + }, + date: Timestamp::Time(row.get(1)), + amount: Amount::from_str(row.get(2)).unwrap(), + reserve_pub: { + let slice: &[u8] = row.get(3); + let array: [u8; 32] = slice.try_into().unwrap(); + ShortHashCode::from(array) + }, + debit_account: Url::parse(row.get(4)).unwrap(), + credit_account: Url::parse(row.get(5)).unwrap(), }) .collect(); json_response( @@ -408,17 +211,31 @@ async fn router( assert_method(&parts, Method::GET)?; let params: HistoryParams = serde_urlencoded::from_str(parts.uri.query().unwrap_or("")).unwrap(); - let guard = state.outgoing.lock().await; - let transactions: Vec<OutgoingBankTransaction> = guard - .iter() - .map(|tx| OutgoingBankTransaction { - row_id: tx.row_id, - date: tx.date, - amount: tx.amount.clone(), - credit_account: tx.credit_account.clone(), - wtid: tx.wtid.clone(), - debit_account: tx.debit_account.clone(), - exchange_base_url: tx.exchange_base_url.clone(), + + let transactions = state + .client + .query( + "SELECT id, _date, amount, wtid, debit_acc, credit_acc, exchange_url FROM tx_out", + &[], + ) + .await + .unwrap() + .into_iter() + .map(|row| OutgoingBankTransaction { + row_id: { + let id: i32 = row.get(0); + SafeUint64::try_from(id as u64).unwrap() + }, + date: Timestamp::Time(row.get(1)), + amount: Amount::from_str(row.get(2)).unwrap(), + wtid: { + let slice : &[u8] = row.get(3); + let array: [u8; 32] = slice.try_into().unwrap(); + ShortHashCode::from(array) + }, + debit_account: Url::parse(row.get(4)).unwrap(), + credit_account: Url::parse(row.get(5)).unwrap(), + exchange_base_url: Url::parse(row.get(6)).unwrap(), }) .collect(); json_response(