diff --git a/Cargo.lock b/Cargo.lock index 15e3469c42..7b459d154a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,7 +207,7 @@ dependencies = [ "futures-io", "futures-timer", "kv-log-macro", - "log 0.4.11", + "log 0.4.14", "memchr 2.3.3", "num_cpus", "once_cell", @@ -283,7 +283,7 @@ dependencies = [ "libp2p-plaintext", "libp2p-swarm", "libp2p-yamux", - "log 0.4.11", + "log 0.4.14", "lru 0.4.3", "prost", "prost-build", @@ -437,6 +437,20 @@ dependencies = [ "secp256k1", ] +[[package]] +name = "bitcoin-spv" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aefc3d887fcbb15bd92c1490308bf62df200ff338a9b67e4863dc7012c052cce" +dependencies = [ + "hex 0.4.3", + "primitive-types 0.7.3", + "ripemd160 0.8.0", + "serde", + "serde_json", + "sha2 0.8.2", +] + [[package]] name = "bitcoin_hashes" version = "0.10.0" @@ -481,6 +495,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "bitvec" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium 0.3.0", +] + [[package]] name = "bitvec" version = "0.18.5" @@ -643,6 +667,12 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "byte-slice-cast" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" + [[package]] name = "byte-slice-cast" version = "1.0.0" @@ -790,15 +820,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "cloudabi" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" -dependencies = [ - "bitflags", -] - [[package]] name = "coins" version = "0.1.0" @@ -844,7 +865,7 @@ dependencies = [ "metrics", "mocktopus", "num-traits 0.2.12", - "parking_lot 0.11.1", + "parking_lot 0.12.0", "primitives", "prost", "prost-build", @@ -867,6 +888,7 @@ dependencies = [ "serialization_derive", "sha2 0.8.2", "sha3", + "spv_validation", "tokio", "tokio-rustls", "utxo_signer", @@ -944,7 +966,7 @@ dependencies = [ "lazy_static", "libc", "lightning", - "log 0.4.11", + "log 0.4.14", "log4rs", "metrics", "metrics-core", @@ -953,7 +975,7 @@ dependencies = [ "num-bigint 0.2.6", "num-rational", "num-traits 0.2.12", - "parking_lot 0.11.1", + "parking_lot 0.12.0", "parking_lot_core 0.6.2", "paste", "primitives", @@ -1051,11 +1073,11 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] @@ -1140,7 +1162,7 @@ dependencies = [ "crossterm_winapi", "libc", "mio", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "signal-hook", "signal-hook-mio", "winapi 0.3.9", @@ -1183,7 +1205,7 @@ dependencies = [ "hw_common", "keys", "num-traits 0.1.43", - "parking_lot 0.11.1", + "parking_lot 0.12.0", "primitives", "rpc_task", "secp256k1", @@ -1249,6 +1271,16 @@ dependencies = [ "sct", ] +[[package]] +name = "ctor" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" +dependencies = [ + "quote 1.0.7", + "syn 1.0.72", +] + [[package]] name = "ctr" version = "0.7.0" @@ -1302,7 +1334,7 @@ checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69" name = "db_common" version = "0.1.0" dependencies = [ - "log 0.4.11", + "log 0.4.14", "rusqlite", "sql-builder", "uuid", @@ -1467,7 +1499,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.11", + "log 0.4.14", "regex 1.4.6", "termcolor", ] @@ -1568,7 +1600,7 @@ dependencies = [ "fixed-hash 0.7.0", "impl-rlp", "impl-serde", - "primitive-types", + "primitive-types 0.9.1", "uint 0.9.1", ] @@ -1654,6 +1686,18 @@ dependencies = [ "rustc-hex 2.1.0", ] +[[package]] +name = "fixed-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11498d382790b7a8f2fd211780bec78619bba81cdad3a283997c0c41f836759c" +dependencies = [ + "byteorder 1.4.3", + "rand 0.7.3", + "rustc-hex 2.1.0", + "static_assertions", +] + [[package]] name = "fixed-hash" version = "0.7.0" @@ -2307,7 +2351,7 @@ dependencies = [ "ct-logs", "futures-util", "hyper", - "log 0.4.11", + "log 0.4.14", "rustls", "tokio", "tokio-rustls", @@ -2372,13 +2416,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "impl-codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" +dependencies = [ + "parity-scale-codec 1.3.7", +] + [[package]] name = "impl-codec" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.2.0", ] [[package]] @@ -2422,9 +2475,15 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.6" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] [[package]] name = "iovec" @@ -2570,7 +2629,7 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ff57d6d215f7ca7eb35a9a64d656ba4d9d2bef114d741dc08048e75e2f5d418" dependencies = [ - "log 0.4.11", + "log 0.4.14", ] [[package]] @@ -2594,9 +2653,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.97" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" [[package]] name = "libp2p" @@ -2620,7 +2679,7 @@ dependencies = [ "libp2p-wasm-ext", "libp2p-websocket", "multiaddr", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "pin-project 1.0.7", "smallvec 1.6.1", "wasm-timer", @@ -2640,11 +2699,11 @@ dependencies = [ "futures-timer", "lazy_static", "libsecp256k1", - "log 0.4.11", + "log 0.4.14", "multiaddr", "multihash", "multistream-select", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "pin-project 1.0.7", "prost", "prost-build", @@ -2666,7 +2725,7 @@ source = "git+https://github.com/libp2p/rust-libp2p.git#20183c1ea152f5bfe183543e dependencies = [ "futures 0.3.15", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "smallvec 1.6.1", "trust-dns-resolver", ] @@ -2695,7 +2754,7 @@ dependencies = [ "futures 0.3.15", "libp2p-core", "libp2p-swarm", - "log 0.4.11", + "log 0.4.14", "prost", "prost-build", "rand 0.7.3", @@ -2711,9 +2770,9 @@ dependencies = [ "bytes 1.1.0", "futures 0.3.15", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "nohash-hasher", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "rand 0.7.3", "smallvec 1.6.1", "unsigned-varint 0.7.0", @@ -2729,7 +2788,7 @@ dependencies = [ "futures 0.3.15", "lazy_static", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "prost", "prost-build", "rand 0.8.4", @@ -2748,7 +2807,7 @@ dependencies = [ "futures 0.3.15", "libp2p-core", "libp2p-swarm", - "log 0.4.11", + "log 0.4.14", "rand 0.7.3", "void", "wasm-timer", @@ -2763,7 +2822,7 @@ dependencies = [ "bytes 1.1.0", "futures 0.3.15", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "prost", "prost-build", "unsigned-varint 0.7.0", @@ -2780,7 +2839,7 @@ dependencies = [ "futures 0.3.15", "libp2p-core", "libp2p-swarm", - "log 0.4.11", + "log 0.4.14", "lru 0.6.0", "minicbor", "rand 0.7.3", @@ -2797,7 +2856,7 @@ dependencies = [ "either", "futures 0.3.15", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "rand 0.7.3", "smallvec 1.6.1", "void", @@ -2824,7 +2883,7 @@ dependencies = [ "ipnet", "libc", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "socket2 0.4.0", "tokio", ] @@ -2851,7 +2910,7 @@ dependencies = [ "futures 0.3.15", "futures-rustls", "libp2p-core", - "log 0.4.11", + "log 0.4.14", "quicksink", "rw-stream-sink", "soketto", @@ -2866,7 +2925,7 @@ source = "git+https://github.com/libp2p/rust-libp2p.git#20183c1ea152f5bfe183543e dependencies = [ "futures 0.3.15", "libp2p-core", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "thiserror", "yamux", ] @@ -3009,7 +3068,7 @@ dependencies = [ "common", "libc", "lightning", - "parking_lot 0.11.1", + "parking_lot 0.12.0", "secp256k1", "serde_json", "winapi 0.3.9", @@ -3042,9 +3101,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard 1.1.0", ] @@ -3055,17 +3114,18 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.11", + "log 0.4.14", ] [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "serde", + "value-bag", ] [[package]] @@ -3087,9 +3147,9 @@ dependencies = [ "fnv", "humantime 2.1.0", "libc", - "log 0.4.11", + "log 0.4.14", "log-mdc", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "regex 1.4.6", "serde", "serde-value", @@ -3285,7 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", - "log 0.4.11", + "log 0.4.14", "miow", "ntapi", "winapi 0.3.9", @@ -3339,6 +3399,7 @@ dependencies = [ "http 0.2.1", "hw_common", "hyper", + "instant", "itertools 0.9.0", "js-sys", "keys", @@ -3350,7 +3411,7 @@ dependencies = [ "num-rational", "num-traits 0.2.12", "parity-util-mem", - "parking_lot 0.11.1", + "parking_lot 0.12.0", "primitives", "rand 0.6.5", "rand 0.7.3", @@ -3370,6 +3431,7 @@ dependencies = [ "serialization_derive", "sp-runtime-interface", "sp-trie", + "spv_validation", "testcontainers", "tokio", "trie-db", @@ -3399,7 +3461,7 @@ dependencies = [ "lazy_static", "libp2p", "libp2p-floodsub 0.22.0", - "log 0.4.11", + "log 0.4.14", "num-bigint 0.2.6", "num-rational", "rand 0.7.3", @@ -3494,7 +3556,7 @@ source = "git+https://github.com/libp2p/rust-libp2p.git#20183c1ea152f5bfe183543e dependencies = [ "bytes 1.1.0", "futures 0.3.15", - "log 0.4.11", + "log 0.4.14", "pin-project 1.0.7", "smallvec 1.6.1", "unsigned-varint 0.7.0", @@ -3660,6 +3722,18 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b26b16c7687c3075982af47719e481815df30bc544f7a6690763a25ca16e9d" +dependencies = [ + "arrayvec 0.5.1", + "bitvec 0.17.4", + "byte-slice-cast 0.3.5", + "serde", +] + [[package]] name = "parity-scale-codec" version = "2.2.0" @@ -3668,7 +3742,7 @@ checksum = "8975095a2a03bbbdc70a74ab11a4f76a6d0b84680d87c68d722531b0ac28e8a9" dependencies = [ "arrayvec 0.7.1", "bitvec 0.20.4", - "byte-slice-cast", + "byte-slice-cast 1.0.0", "impl-trait-for-tuples", "parity-scale-codec-derive", "serde", @@ -3704,8 +3778,8 @@ dependencies = [ "impl-trait-for-tuples", "lru 0.6.0", "parity-util-mem-derive", - "parking_lot 0.11.1", - "primitive-types", + "parking_lot 0.11.2", + "primitive-types 0.9.1", "smallvec 1.6.1", "winapi 0.3.9", ] @@ -3760,13 +3834,23 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.4", - "parking_lot_core 0.8.0", + "lock_api 0.4.6", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api 0.4.6", + "parking_lot_core 0.9.1", ] [[package]] @@ -3789,7 +3873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ "cfg-if 0.1.10", - "cloudabi 0.0.3", + "cloudabi", "libc", "redox_syscall 0.1.56", "rustc_version 0.2.3", @@ -3804,7 +3888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" dependencies = [ "cfg-if 0.1.10", - "cloudabi 0.0.3", + "cloudabi", "libc", "redox_syscall 0.1.56", "smallvec 1.6.1", @@ -3813,19 +3897,31 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ - "cfg-if 0.1.10", - "cloudabi 0.1.0", + "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.1.56", + "redox_syscall 0.2.8", "smallvec 1.6.1", "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.8", + "smallvec 1.6.1", + "windows-sys", +] + [[package]] name = "paste" version = "1.0.4" @@ -3956,6 +4052,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +[[package]] +name = "primitive-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd39dcacf71411ba488570da7bbc89b717225e46478b30ba99b92db6b149809" +dependencies = [ + "fixed-hash 0.6.1", + "impl-codec 0.4.2", + "uint 0.8.5", +] + [[package]] name = "primitive-types" version = "0.9.1" @@ -3963,7 +4070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06345ee39fbccfb06ab45f3a1a5798d9dafa04cb8921a76d227040003a234b0e" dependencies = [ "fixed-hash 0.7.0", - "impl-codec", + "impl-codec 0.5.1", "impl-rlp", "impl-serde", "uint 0.9.1", @@ -4061,7 +4168,7 @@ dependencies = [ "bytes 1.1.0", "heck", "itertools 0.10.1", - "log 0.4.11", + "log 0.4.14", "multimap", "petgraph", "prost", @@ -4141,7 +4248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ "env_logger", - "log 0.4.11", + "log 0.4.14", "rand 0.7.3", "rand_core 0.5.1", ] @@ -4374,7 +4481,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi 0.0.3", + "cloudabi", "fuchsia-cprng", "libc", "rand_core 0.4.2", @@ -4623,7 +4730,7 @@ version = "0.1.0" dependencies = [ "chain", "keys", - "log 0.4.11", + "log 0.4.14", "primitives", "rustc-hex 2.1.0", "script", @@ -4733,7 +4840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", - "log 0.4.11", + "log 0.4.14", "ring", "sct", "webpki", @@ -4782,7 +4889,7 @@ dependencies = [ "blake2b_simd", "chain", "keys", - "log 0.4.11", + "log 0.4.14", "primitives", "serde", "serialization", @@ -5059,7 +5166,7 @@ dependencies = [ name = "shared_ref_counter" version = "0.1.0" dependencies = [ - "log 0.4.11", + "log 0.4.14", ] [[package]] @@ -5212,7 +5319,7 @@ dependencies = [ "flate2", "futures 0.3.15", "httparse", - "log 0.4.11", + "log 0.4.14", "rand 0.7.3", "sha-1", ] @@ -5226,11 +5333,11 @@ dependencies = [ "byteorder 1.4.3", "hash-db", "hash256-std-hasher", - "log 0.4.11", + "log 0.4.14", "num-traits 0.2.12", - "parity-scale-codec", + "parity-scale-codec 2.2.0", "parity-util-mem", - "primitive-types", + "primitive-types 0.9.1", "secrecy", "sp-debug-derive", "sp-runtime-interface", @@ -5257,8 +5364,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5c88b4bc8d607e4e2ff767a85db58cf7101f3dd6064f06929342ea67fe8fb" dependencies = [ "impl-trait-for-tuples", - "parity-scale-codec", - "primitive-types", + "parity-scale-codec 2.2.0", + "primitive-types 0.9.1", "sp-runtime-interface-proc-macro", "sp-std", "sp-storage", @@ -5292,7 +5399,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86af458d4a0251c490cdde9dcaaccb88d398f3b97ac6694cdd49ed9337e6b961" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.2.0", "ref-cast", "sp-debug-derive", "sp-std", @@ -5304,7 +5411,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567382d8d4e14fb572752863b5cd57a78f9e9a6583332b590b726f061f3ea957" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 2.2.0", "sp-std", "tracing", "tracing-core", @@ -5318,7 +5425,7 @@ checksum = "b85b7f745da41ef825c6f7b93f1fdc897b03df94a4884adfbb70fbcd0aed1298" dependencies = [ "hash-db", "memory-db", - "parity-scale-codec", + "parity-scale-codec 2.2.0", "sp-core", "sp-std", "trie-db", @@ -5332,7 +5439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b214e125666a6416cf30a70cc6a5dacd34a4e5197f8a3d479f714af7e1dc7a47" dependencies = [ "impl-trait-for-tuples", - "parity-scale-codec", + "parity-scale-codec 2.2.0", "sp-std", ] @@ -5342,6 +5449,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spv_validation" +version = "0.1.0" +dependencies = [ + "bitcoin-spv", + "chain", + "primitives", + "rustc-hex 2.1.0", + "serialization", +] + [[package]] name = "sql-builder" version = "3.1.1" @@ -5488,7 +5606,7 @@ name = "tc_cli_client" version = "0.2.0" source = "git+https://github.com/artemii235/testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.11", + "log 0.4.14", "serde", "serde_derive", "serde_json", @@ -5502,7 +5620,7 @@ source = "git+https://github.com/artemii235/testcontainers-rs.git#65e738093488f1 dependencies = [ "hex 0.3.2", "hmac 0.7.1", - "log 0.4.11", + "log 0.4.14", "rand 0.7.3", "sha2 0.8.2", "tc_core", @@ -5514,7 +5632,7 @@ version = "0.3.0" source = "git+https://github.com/artemii235/testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ "debug_stub_derive", - "log 0.4.11", + "log 0.4.14", ] [[package]] @@ -5522,7 +5640,7 @@ name = "tc_dynamodb_local" version = "0.2.0" source = "git+https://github.com/artemii235/testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.11", + "log 0.4.14", "tc_core", ] @@ -5547,7 +5665,7 @@ name = "tc_parity_parity" version = "0.5.0" source = "git+https://github.com/artemii235/testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.11", + "log 0.4.14", "tc_core", ] @@ -5556,7 +5674,7 @@ name = "tc_postgres" version = "0.2.0" source = "git+https://github.com/artemii235/testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.11", + "log 0.4.14", "tc_core", ] @@ -5821,7 +5939,7 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "log 0.4.11", + "log 0.4.14", "pin-project-lite 0.2.6", "tokio", ] @@ -5900,7 +6018,7 @@ checksum = "9eac131e334e81b6b3be07399482042838adcd7957aa0010231d0813e39e02fa" dependencies = [ "hash-db", "hashbrown 0.11.2", - "log 0.4.11", + "log 0.4.14", "smallvec 1.6.1", ] @@ -5929,7 +6047,7 @@ dependencies = [ "idna 0.2.0", "ipnet", "lazy_static", - "log 0.4.11", + "log 0.4.14", "rand 0.8.4", "smallvec 1.6.1", "thiserror", @@ -5948,9 +6066,9 @@ dependencies = [ "futures-util", "ipconfig", "lazy_static", - "log 0.4.11", + "log 0.4.14", "lru-cache", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "resolv-conf", "smallvec 1.6.1", "thiserror", @@ -5996,6 +6114,18 @@ dependencies = [ "rustc-hex 2.1.0", ] +[[package]] +name = "uint" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9db035e67dfaf7edd9aebfe8676afcd63eed53c8a4044fed514c8cccf1835177" +dependencies = [ + "byteorder 1.4.3", + "crunchy 0.2.2", + "rustc-hex 2.1.0", + "static_assertions", +] + [[package]] name = "uint" version = "0.9.1" @@ -6151,6 +6281,16 @@ dependencies = [ "serde", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "vcpkg" version = "0.2.10" @@ -6231,7 +6371,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.11", + "log 0.4.14", "try-lock", ] @@ -6267,7 +6407,7 @@ checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.11", + "log 0.4.14", "proc-macro2", "quote 1.0.7", "syn 1.0.72", @@ -6377,7 +6517,7 @@ dependencies = [ "ethereum-types 0.4.2", "futures 0.1.29", "jsonrpc-core", - "log 0.4.11", + "log 0.4.14", "parking_lot 0.7.1", "rustc-hex 1.0.0", "serde", @@ -6483,6 +6623,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +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.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "winreg" version = "0.6.2" @@ -6525,9 +6708,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ "futures 0.3.15", - "log 0.4.11", + "log 0.4.14", "nohash-hasher", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "rand 0.8.4", "static_assertions", ] @@ -6590,7 +6773,7 @@ dependencies = [ "hex 0.4.3", "jubjub", "lazy_static", - "log 0.4.11", + "log 0.4.14", "rand 0.7.3", "rand_core 0.5.1", "ripemd160 0.9.1", diff --git a/Cargo.toml b/Cargo.toml index c2fd24f6c6..3ab42fb392 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ num-rational = { version = "0.2", features = ["serde", "bigint", "bigint-std"] } num-traits = "0.2" rpc = { path = "mm2src/mm2_bitcoin/rpc" } rpc_task = { path = "mm2src/rpc_task" } -parking_lot = { version = "0.11", features = ["nightly"] } +parking_lot = { version = "0.12.0", features = ["nightly"] } parity-util-mem = "0.9" # AP: portfolio RPCs are not documented and not used as of now # so the crate is disabled to speed up the entire removal of C code @@ -110,6 +110,7 @@ ser_error = { path = "mm2src/derives/ser_error" } ser_error_derive = { path = "mm2src/derives/ser_error_derive" } serialization = { path = "mm2src/mm2_bitcoin/serialization" } serialization_derive = { path = "mm2src/mm2_bitcoin/serialization_derive" } +spv_validation = { path = "mm2src/mm2_bitcoin/spv_validation" } sp-runtime-interface = { version = "3.0.0", default-features = false, features = ["disable_target_static_assertions"] } sp-trie = { version = "3.0", default-features = false } @@ -124,6 +125,7 @@ wasm-bindgen = { version = "0.2.50", features = ["serde-serialize", "nightly"] } wasm-bindgen-futures = { version = "0.4.1" } wasm-bindgen-test = { version = "0.3.1" } web-sys = { version = "0.3.55", features = ["console"] } +instant = {version = "0.1.12", features = ["wasm-bindgen"]} [target.'cfg(not(target_arch = "wasm32"))'.dependencies] dirs = { version = "1" } diff --git a/README.md b/README.md index 45f0c5c010..bbec952b81 100755 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ The current state can be considered as very early alpha. 1. (Optional) OSX: run `LIBRARY_PATH=/usr/local/opt/openssl/lib` 1. Run ``` - rustup install nightly-2021-12-16 - rustup default nightly-2021-12-16 + rustup install nightly-2022-02-01 + rustup default nightly-2022-02-01 rustup component add rustfmt-preview ``` 1. Run `cargo build` (or `cargo build -vv` to get verbose build output). diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index b7f1ea816d..57f1b2c7db 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -13,7 +13,7 @@ doctest = false [dependencies] async-std = { version = "1.5", features = ["unstable"] } -async-trait = "0.1" +async-trait = "0.1.52" base64 = "0.10.0" bigdecimal = { version = "0.1.0", features = ["serde"] } bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } @@ -52,7 +52,7 @@ lightning-invoice = "0.12.0" metrics = "0.12" mocktopus = "0.7.0" num-traits = "0.2" -parking_lot = { version = "0.11", features = ["nightly"] } +parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } prost = "0.8" rand = { version = "0.7", features = ["std", "small_rng"] } @@ -69,6 +69,7 @@ serde_derive = "1.0" serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } serialization = { path = "../mm2_bitcoin/serialization" } serialization_derive = { path = "../mm2_bitcoin/serialization_derive" } +spv_validation = { path = "../mm2_bitcoin/spv_validation" } sha2 = "0.8" sha3 = "0.8" utxo_signer = { path = "utxo_signer" } diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 120cdb93c3..111dfec0fb 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1210,7 +1210,7 @@ fn polygon_check_if_my_payment_sent() { let request = json!({ "method": "enable", "coin": "MATIC", - "urls": ["https://polygon-rpc.com"], + "urls": ["https://polygon-mainnet.g.alchemy.com/v2/9YYl6iMLmXXLoflMPHnMTC4Dcm2L2tFH"], "swap_contract_address": "0x9130b257d37a52e52f21054c4da3450c72f595ce", }); diff --git a/mm2src/coins/lightning_persister/Cargo.toml b/mm2src/coins/lightning_persister/Cargo.toml index 34bf6eac52..4d246bc9bb 100644 --- a/mm2src/coins/lightning_persister/Cargo.toml +++ b/mm2src/coins/lightning_persister/Cargo.toml @@ -15,7 +15,7 @@ bitcoin = "0.27.1" common = { path = "../../common" } lightning = "0.0.104" libc = "0.2" -parking_lot = { version = "0.11", features = ["nightly"] } +parking_lot = { version = "0.12.0", features = ["nightly"] } secp256k1 = { version = "0.20" } serde_json = "1.0" diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 4208ca4ee1..3be780f1a9 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -248,6 +248,7 @@ pub struct ValidatePaymentInput { pub secret_hash: Vec, pub amount: BigDecimal, pub swap_contract_address: Option, + pub confirmations: u64, } /// Swap operations (mostly based on the Hash/Time locked transactions implemented by coin wallets). diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index c34e7e018b..41790cb4ab 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -131,6 +131,7 @@ fn test_validate_maker_payment() { secret_hash: vec![1; 20], amount: correct_amount.clone(), swap_contract_address: coin.swap_contract_address(), + confirmations: 1, }; coin.validate_maker_payment(input.clone()).wait().unwrap(); @@ -851,6 +852,7 @@ fn test_validate_maker_payment_malicious() { secret_hash, amount, swap_contract_address: coin.swap_contract_address(), + confirmations: 1, }; let error = coin .validate_maker_payment(input) diff --git a/mm2src/coins/sql_tx_history_storage.rs b/mm2src/coins/sql_tx_history_storage.rs index 71596b57a6..eef606f767 100644 --- a/mm2src/coins/sql_tx_history_storage.rs +++ b/mm2src/coins/sql_tx_history_storage.rs @@ -7,14 +7,12 @@ use common::{async_blocking, PagingOptionsEnum}; use db_common::sqlite::rusqlite::types::Type; use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Row, ToSql, NO_PARAMS}; use db_common::sqlite::sql_builder::SqlBuilder; -use db_common::sqlite::{offset_by_id, validate_table_name}; +use db_common::sqlite::{offset_by_id, string_from_row, validate_table_name, CHECK_TABLE_EXISTS_SQL}; use rpc::v1::types::Bytes as BytesJson; use serde_json::{self as json}; use std::convert::TryInto; use std::sync::{Arc, Mutex}; -const CHECK_TABLE_EXISTS_SQL: &str = "SELECT name FROM sqlite_master WHERE type='table' AND name=?1;"; - fn tx_history_table(ticker: &str) -> String { ticker.to_owned() + "_tx_history" } fn tx_cache_table(ticker: &str) -> String { ticker.to_owned() + "_tx_cache" } @@ -194,17 +192,9 @@ where P::Item: ToSql, F: FnOnce(&Row<'_>) -> Result, { - let maybe_result = conn.query_row(query, params, map_fn); - if let Err(SqlError::QueryReturnedNoRows) = maybe_result { - return Ok(None); - } - - let result = maybe_result?; - Ok(Some(result)) + db_common::sqlite::query_single_row(conn, query, params, map_fn).map_err(MmError::new) } -fn string_from_row(row: &Row<'_>) -> Result { row.get(0) } - fn tx_details_from_row(row: &Row<'_>) -> Result { let json_string: String = row.get(0)?; json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 725211b6cb..8469fb1be4 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -29,13 +29,12 @@ mod bchd_pb; pub mod qtum; pub mod rpc_clients; pub mod slp; +pub mod utxo_block_header_storage; pub mod utxo_builder; pub mod utxo_common; pub mod utxo_standard; pub mod utxo_withdraw; -#[cfg(not(target_arch = "wasm32"))] pub mod tx_cache; - use async_trait::async_trait; use bigdecimal::BigDecimal; use bitcoin::network::constants::Network as BitcoinNetwork; @@ -69,6 +68,7 @@ use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as use script::{Builder, Script, SignatureVersion, TransactionInputSigner}; use serde_json::{self as json, Value as Json}; use serialization::{serialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; +use spv_validation::types::SPVError; use std::array::TryFromSliceError; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; @@ -95,6 +95,13 @@ use super::{BalanceError, BalanceFut, BalanceResult, CoinsContext, DerivationMet use crate::coin_balance::{EnableCoinScanPolicy, HDAddressBalanceScanner}; use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDWalletCoinOps, HDWalletOps, InvalidBip44ChainError}; use crate::hd_wallet_storage::{HDAccountStorageItem, HDWalletCoinStorage, HDWalletStorageError, HDWalletStorageResult}; +use crate::utxo::utxo_block_header_storage::BlockHeaderStorageError; +use utxo_block_header_storage::BlockHeaderStorage; +#[cfg(not(target_arch = "wasm32"))] pub mod tx_cache; +#[cfg(target_arch = "wasm32")] +pub mod utxo_indexedb_block_header_storage; +#[cfg(not(target_arch = "wasm32"))] +pub mod utxo_sql_block_header_storage; #[cfg(test)] pub mod utxo_tests; #[cfg(target_arch = "wasm32")] pub mod utxo_wasm_tests; @@ -510,6 +517,7 @@ pub struct UtxoCoinFields { pub history_sync_state: Mutex, /// Path to the TX cache directory pub tx_cache_directory: Option, + pub block_headers_storage: Option, /// The cache of recently send transactions used to track the spent UTXOs and replace them with new outputs /// The daemon needs some time to update the listunspent list for address which makes it return already spent UTXOs /// This cache helps to prevent UTXO reuse in such cases @@ -545,6 +553,56 @@ impl From for WithdrawError { fn from(e: UnsupportedAddr) -> Self { WithdrawError::InvalidAddress(e.to_string()) } } +#[derive(Debug)] +pub enum GetTxHeightError { + HeightNotFound, +} + +impl From for SPVError { + fn from(e: GetTxHeightError) -> Self { + match e { + GetTxHeightError::HeightNotFound => SPVError::InvalidHeight, + } + } +} + +#[derive(Debug)] +pub enum GetBlockHeaderError { + StorageError(BlockHeaderStorageError), + RpcError(JsonRpcError), + SerializationError(serialization::Error), + InvalidResponse(String), + SPVError(SPVError), + NativeNotSupported(String), + Internal(String), +} + +impl From for GetBlockHeaderError { + fn from(err: JsonRpcError) -> Self { GetBlockHeaderError::RpcError(err) } +} + +impl From for GetBlockHeaderError { + fn from(e: UtxoRpcError) -> Self { + match e { + UtxoRpcError::Transport(e) | UtxoRpcError::ResponseParseError(e) => GetBlockHeaderError::RpcError(e), + UtxoRpcError::InvalidResponse(e) => GetBlockHeaderError::InvalidResponse(e), + UtxoRpcError::Internal(e) => GetBlockHeaderError::Internal(e), + } + } +} + +impl From for GetBlockHeaderError { + fn from(e: SPVError) -> Self { GetBlockHeaderError::SPVError(e) } +} + +impl From for GetBlockHeaderError { + fn from(err: serialization::Error) -> Self { GetBlockHeaderError::SerializationError(err) } +} + +impl From for GetBlockHeaderError { + fn from(err: BlockHeaderStorageError) -> Self { GetBlockHeaderError::StorageError(err) } +} + impl UtxoCoinFields { pub fn transaction_preimage(&self) -> TransactionInputSigner { let lock_time = if self.conf.ticker == "KMD" { @@ -1031,6 +1089,14 @@ pub struct UtxoMergeParams { pub max_merge_at_once: usize, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct UtxoBlockHeaderVerificationParams { + pub difficulty_check: bool, + pub constant_difficulty: bool, + pub blocks_limit_to_check: NonZeroU64, + pub check_every: f64, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct UtxoActivationParams { pub mode: UtxoRpcMode, @@ -1053,6 +1119,7 @@ pub enum UtxoFromLegacyReqErr { UnexpectedMethod, InvalidElectrumServers(json::Error), InvalidMergeParams(json::Error), + InvalidBlockHeaderVerificationParams(json::Error), InvalidRequiredConfs(json::Error), InvalidRequiresNota(json::Error), InvalidAddressFormat(json::Error), diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index 422cf36376..fd1ad33d94 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -124,14 +124,13 @@ impl QtumCoin { let delegation_output = self.remove_delegation_output(QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)?; let outputs = vec![delegation_output]; let my_address = self.my_address().map_to_mm(DelegationError::InternalError)?; - Ok(self - .generate_delegation_transaction( - outputs, - my_address, - QRC20_GAS_LIMIT_DEFAULT, - TransactionType::RemoveDelegation, - ) - .await?) + self.generate_delegation_transaction( + outputs, + my_address, + QRC20_GAS_LIMIT_DEFAULT, + TransactionType::RemoveDelegation, + ) + .await } async fn am_i_currently_staking(&self) -> Result, MmError> { @@ -252,14 +251,13 @@ impl QtumCoin { let outputs = vec![delegation_output]; let my_address = self.my_address().map_to_mm(DelegationError::InternalError)?; - Ok(self - .generate_delegation_transaction( - outputs, - my_address, - QRC20_GAS_LIMIT_DELEGATION, - TransactionType::StakingDelegation, - ) - .await?) + self.generate_delegation_transaction( + outputs, + my_address, + QRC20_GAS_LIMIT_DELEGATION, + TransactionType::StakingDelegation, + ) + .await } async fn generate_delegation_transaction( diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index 11a65c3976..6b1f837b94 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -1025,7 +1025,7 @@ impl Into for ElectrumNonce { #[derive(Debug, Deserialize)] pub struct ElectrumBlockHeadersRes { - count: u64, + pub count: u64, pub hex: BytesJson, #[allow(dead_code)] max: u64, @@ -1044,8 +1044,8 @@ pub struct ElectrumBlockHeaderV12 { } impl ElectrumBlockHeaderV12 { - pub fn hash(&self) -> H256Json { - let block_header = BlockHeader { + fn as_block_header(&self) -> BlockHeader { + BlockHeader { version: self.version as u32, previous_header_hash: self.prev_block_hash.into(), merkle_root_hash: self.merkle_root.into(), @@ -1064,7 +1064,17 @@ impl ElectrumBlockHeaderV12 { n_height: None, n_nonce_u64: None, mix_hash: None, - }; + } + } + + pub fn as_hex(&self) -> String { + let block_header = self.as_block_header(); + let serialized = serialize(&block_header); + hex::encode(serialized) + } + + pub fn hash(&self) -> H256Json { + let block_header = self.as_block_header(); BlockHeader::hash(&block_header).into() } } @@ -1658,6 +1668,49 @@ impl ElectrumClient { rpc_func!(self, "blockchain.block.headers", start_height, count) } + pub fn retrieve_last_headers( + &self, + blocks_limit_to_check: NonZeroU64, + block_height: u64, + ) -> UtxoRpcFut<(HashMap, Vec)> { + let (from, count) = { + let from = if block_height < blocks_limit_to_check.get() { + 0 + } else { + block_height - blocks_limit_to_check.get() + }; + (from, blocks_limit_to_check) + }; + Box::new( + self.blockchain_block_headers(from, count) + .map_to_mm_fut(UtxoRpcError::from) + .and_then(move |headers| { + let (block_registry, block_headers) = { + if headers.count == 0 { + return MmError::err(UtxoRpcError::Internal("No headers available".to_string())); + } + let len = CompactInteger::from(headers.count); + let mut serialized = serialize(&len).take(); + serialized.extend(headers.hex.0.into_iter()); + let mut reader = Reader::new_with_coin_variant(serialized.as_slice(), CoinVariant::Standard); + let maybe_block_headers = reader.read_list::(); + let block_headers = match maybe_block_headers { + Ok(headers) => headers, + Err(e) => return MmError::err(UtxoRpcError::InvalidResponse(format!("{:?}", e))), + }; + let mut block_registry: HashMap = HashMap::new(); + let mut starting_height = from; + for block_header in &block_headers { + block_registry.insert(starting_height, block_header.clone()); + starting_height += 1; + } + (block_registry, block_headers) + }; + Ok((block_registry, block_headers)) + }), + ) + } + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle pub fn blockchain_transaction_get_merkle(&self, txid: H256Json, height: u64) -> RpcRes { rpc_func!(self, "blockchain.transaction.get_merkle", txid, height) diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 26de7ce3ba..dbe3c428b5 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -293,6 +293,17 @@ pub struct SlpProtocolConf { pub required_confirmations: Option, } +#[derive(Debug)] +pub struct ValidateHtlcInput { + tx: Vec, + other_pub: Public, + my_pub: Public, + time_lock: u32, + secret_hash: Vec, + amount: BigDecimal, + confirmations: u64, +} + impl SlpToken { pub fn new( decimals: u8, @@ -433,22 +444,14 @@ impl SlpToken { .await } - async fn validate_htlc( - &self, - tx: &[u8], - other_pub: &Public, - my_pub: &Public, - time_lock: u32, - secret_hash: &[u8], - amount: BigDecimal, - ) -> Result<(), MmError> { - let mut tx: UtxoTx = deserialize(tx).map_to_mm(ValidateHtlcError::TxParseError)?; + async fn validate_htlc(&self, input: ValidateHtlcInput) -> Result<(), MmError> { + let mut tx: UtxoTx = deserialize(input.tx.as_slice()).map_to_mm(ValidateHtlcError::TxParseError)?; tx.tx_hash_algo = self.platform_coin.as_ref().tx_hash_algo; if tx.outputs.len() < 2 { return MmError::err(ValidateHtlcError::TxLackOfOutputs); } - let slp_satoshis = sat_from_big_decimal(&amount, self.decimals())?; + let slp_satoshis = sat_from_big_decimal(&input.amount, self.decimals())?; let slp_unspent = SlpUnspent { bch_unspent: UnspentInfo { @@ -486,11 +489,12 @@ impl SlpToken { self.platform_coin.clone(), tx, SLP_SWAP_VOUT, - other_pub, - my_pub, - secret_hash, + &input.other_pub, + &input.my_pub, + &input.secret_hash, self.platform_dust_dec(), - time_lock, + input.time_lock, + input.confirmations, ); validate_fut @@ -1342,13 +1346,20 @@ impl SwapOps for SlpToken { let secret_hash = input.secret_hash.to_owned(); let time_lock = input.time_lock; let amount = input.amount; + let confirmations = input.confirmations; let coin = self.clone(); + let input = ValidateHtlcInput { + tx, + other_pub: maker_pub, + my_pub: taker_pub, + time_lock, + secret_hash, + amount, + confirmations, + }; let fut = async move { - try_s!( - coin.validate_htlc(&tx, &maker_pub, &taker_pub, time_lock, &secret_hash, amount) - .await - ); + try_s!(coin.validate_htlc(input).await); Ok(()) }; Box::new(fut.boxed().compat()) @@ -1357,20 +1368,20 @@ impl SwapOps for SlpToken { fn validate_taker_payment(&self, input: ValidatePaymentInput) -> Box + Send> { let taker_pub = try_fus!(Public::from_slice(&input.taker_pub)); let maker_pub = try_fus!(Public::from_slice(&input.maker_pub)); + let confirmations = input.confirmations; let coin = self.clone(); + let input = ValidateHtlcInput { + tx: input.payment_tx, + other_pub: taker_pub, + my_pub: maker_pub, + time_lock: input.time_lock, + secret_hash: input.secret_hash, + amount: input.amount, + confirmations, + }; let fut = async move { - try_s!( - coin.validate_htlc( - &input.payment_tx, - &taker_pub, - &maker_pub, - input.time_lock, - &input.secret_hash, - input.amount - ) - .await - ); + try_s!(coin.validate_htlc(input).await); Ok(()) }; Box::new(fut.boxed().compat()) @@ -1943,9 +1954,17 @@ mod slp_tests { let lock_time = 1624547837; let secret_hash = hex::decode("5d9e149ad9ccb20e9f931a69b605df2ffde60242").unwrap(); - let amount = "0.1".parse().unwrap(); - - block_on(fusd.validate_htlc(&tx, &other_pub, &my_pub, lock_time, &secret_hash, amount)).unwrap(); + let amount: BigDecimal = "0.1".parse().unwrap(); + let input = ValidateHtlcInput { + tx, + other_pub, + my_pub, + time_lock: lock_time, + secret_hash, + amount, + confirmations: 1, + }; + block_on(fusd.validate_htlc(input)).unwrap(); } #[test] @@ -2040,12 +2059,21 @@ mod slp_tests { &secret_hash, fusd.platform_dust_dec(), lock_time, + 1, ) .wait() .unwrap(); - let validity_err = - block_on(fusd.validate_htlc(&tx, &other_pub, my_pub, lock_time, &secret_hash, amount)).unwrap_err(); + let input = ValidateHtlcInput { + tx, + other_pub, + my_pub: my_pub.clone(), + time_lock: lock_time, + secret_hash, + amount, + confirmations: 1, + }; + let validity_err = block_on(fusd.validate_htlc(input)).unwrap_err(); match validity_err.into_inner() { ValidateHtlcError::InvalidSlpUtxo(e) => println!("{:?}", e), err @ _ => panic!("Unexpected err {:?}", err), diff --git a/mm2src/coins/utxo/utxo_block_header_storage.rs b/mm2src/coins/utxo/utxo_block_header_storage.rs new file mode 100644 index 0000000000..ada1453824 --- /dev/null +++ b/mm2src/coins/utxo/utxo_block_header_storage.rs @@ -0,0 +1,151 @@ +use crate::utxo::rpc_clients::ElectrumBlockHeader; +#[cfg(target_arch = "wasm32")] +use crate::utxo::utxo_indexedb_block_header_storage::IndexedDBBlockHeadersStorage; +#[cfg(not(target_arch = "wasm32"))] +use crate::utxo::utxo_sql_block_header_storage::SqliteBlockHeadersStorage; +use crate::utxo::UtxoBlockHeaderVerificationParams; +use async_trait::async_trait; +use chain::BlockHeader; +use common::mm_ctx::MmArc; +use common::mm_error::MmError; +use derive_more::Display; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; + +#[derive(Debug, Display)] +pub enum BlockHeaderStorageError { + #[display(fmt = "Can't add to the storage for {} - reason: {}", ticker, reason)] + AddToStorageError { ticker: String, reason: String }, + #[display(fmt = "Can't get from the storage for {} - reason: {}", ticker, reason)] + GetFromStorageError { ticker: String, reason: String }, + #[display( + fmt = "Can't retrieve the table from the storage for {} - reason: {}", + ticker, + reason + )] + CantRetrieveTableError { ticker: String, reason: String }, + #[display(fmt = "Can't query from the storage - query: {} - reason: {}", query, reason)] + QueryError { query: String, reason: String }, + #[display(fmt = "Can't init from the storage - ticker: {} - reason: {}", ticker, reason)] + InitializationError { ticker: String, reason: String }, + #[display(fmt = "Can't decode/deserialize from storage for {} - reason: {}", ticker, reason)] + DecodeError { ticker: String, reason: String }, +} + +pub struct BlockHeaderStorage { + pub inner: Box, + pub params: UtxoBlockHeaderVerificationParams, +} + +impl Debug for BlockHeaderStorage { + fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result { Ok(()) } +} + +pub trait InitBlockHeaderStorageOps: Send + Sync + 'static { + fn new_from_ctx(ctx: MmArc, params: UtxoBlockHeaderVerificationParams) -> Option + where + Self: Sized; +} + +#[async_trait] +pub trait BlockHeaderStorageOps: Send + Sync + 'static { + /// Initializes collection/tables in storage for a specified coin + async fn init(&self, for_coin: &str) -> Result<(), MmError>; + + async fn is_initialized_for(&self, for_coin: &str) -> Result>; + + // Adds multiple block headers to the selected coin's header storage + // Should store it as `TICKER_HEIGHT=hex_string` + // use this function for headers that comes from `blockchain_headers_subscribe` + async fn add_electrum_block_headers_to_storage( + &self, + for_coin: &str, + headers: Vec, + ) -> Result<(), MmError>; + + // Adds multiple block headers to the selected coin's header storage + // Should store it as `TICKER_HEIGHT=hex_string` + // use this function for headers that comes from `blockchain_block_headers` + async fn add_block_headers_to_storage( + &self, + for_coin: &str, + headers: HashMap, + ) -> Result<(), MmError>; + + /// Gets the block header by height from the selected coin's storage as BlockHeader + async fn get_block_header( + &self, + for_coin: &str, + height: u64, + ) -> Result, MmError>; + + /// Gets the block header by height from the selected coin's storage as hex + async fn get_block_header_raw( + &self, + for_coin: &str, + height: u64, + ) -> Result, MmError>; +} + +impl InitBlockHeaderStorageOps for BlockHeaderStorage { + #[cfg(not(target_arch = "wasm32"))] + fn new_from_ctx(ctx: MmArc, params: UtxoBlockHeaderVerificationParams) -> Option { + ctx.sqlite_connection.as_option().map(|connection| BlockHeaderStorage { + inner: Box::new(SqliteBlockHeadersStorage(connection.clone())), + params, + }) + } + + #[cfg(target_arch = "wasm32")] + fn new_from_ctx(_ctx: MmArc, params: UtxoBlockHeaderVerificationParams) -> Option { + Some(BlockHeaderStorage { + inner: Box::new(IndexedDBBlockHeadersStorage {}), + params, + }) + } +} + +#[async_trait] +impl BlockHeaderStorageOps for BlockHeaderStorage { + async fn init(&self, for_coin: &str) -> Result<(), MmError> { + self.inner.init(for_coin).await + } + + async fn is_initialized_for(&self, for_coin: &str) -> Result> { + self.inner.is_initialized_for(for_coin).await + } + + async fn add_electrum_block_headers_to_storage( + &self, + for_coin: &str, + headers: Vec, + ) -> Result<(), MmError> { + self.inner + .add_electrum_block_headers_to_storage(for_coin, headers) + .await + } + + async fn add_block_headers_to_storage( + &self, + for_coin: &str, + headers: HashMap, + ) -> Result<(), MmError> { + self.inner.add_block_headers_to_storage(for_coin, headers).await + } + + async fn get_block_header( + &self, + for_coin: &str, + height: u64, + ) -> Result, MmError> { + self.inner.get_block_header(for_coin, height).await + } + + async fn get_block_header_raw( + &self, + for_coin: &str, + height: u64, + ) -> Result, MmError> { + self.inner.get_block_header_raw(for_coin, height).await + } +} diff --git a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs index 5464f59141..7324767562 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs @@ -1,6 +1,7 @@ +use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; use crate::utxo::utxo_builder::{UtxoCoinBuildError, UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaPrivKeyBuilder}; -use crate::utxo::utxo_common::merge_utxo_loop; +use crate::utxo::utxo_common::{block_header_utxo_loop, merge_utxo_loop}; use crate::utxo::{UtxoArc, UtxoCoinFields, UtxoCommonOps, UtxoWeak}; use crate::{PrivKeyBuildPolicy, UtxoActivationParams}; use async_trait::async_trait; @@ -8,6 +9,7 @@ use common::executor::spawn; use common::log::info; use common::mm_ctx::MmArc; use common::mm_error::prelude::*; +use futures::future::{abortable, AbortHandle}; use serde_json::Value as Json; pub struct UtxoArcBuilder<'a, F, T> @@ -86,7 +88,14 @@ where let utxo_weak = utxo_arc.downgrade(); let result_coin = (self.constructor)(utxo_arc); - self.spawn_merge_utxo_loop_if_required(utxo_weak, self.constructor.clone()); + self.spawn_merge_utxo_loop_if_required(utxo_weak.clone(), self.constructor.clone()); + if let Some(abort_handler) = self.spawn_block_header_utxo_loop_if_required( + utxo_weak, + &result_coin.as_ref().block_headers_storage, + self.constructor.clone(), + ) { + self.ctx.abort_handlers.lock().unwrap().push(abort_handler); + } Ok(result_coin) } } @@ -98,6 +107,13 @@ where { } +impl<'a, F, T> BlockHeaderUtxoArcOps for UtxoArcBuilder<'a, F, T> +where + F: Fn(UtxoArc) -> T + Send + Sync + 'static, + T: AsRef + UtxoCommonOps + Send + Sync + 'static, +{ +} + pub trait MergeUtxoArcOps: UtxoCoinBuilderCommonOps where T: AsRef + UtxoCommonOps + Send + Sync + 'static, @@ -119,3 +135,34 @@ where } } } + +pub trait BlockHeaderUtxoArcOps: UtxoCoinBuilderCommonOps +where + T: AsRef + UtxoCommonOps + Send + Sync + 'static, +{ + fn spawn_block_header_utxo_loop_if_required( + &self, + weak: UtxoWeak, + maybe_storage: &Option, + constructor: F, + ) -> Option + where + F: Fn(UtxoArc) -> T + Send + Sync + 'static, + { + if maybe_storage.is_some() { + let ticker = self.ticker().to_owned(); + let (fut, abort_handle) = abortable(block_header_utxo_loop(weak, constructor)); + info!("Starting UTXO block header loop for coin {}", ticker); + spawn(async move { + if let Err(e) = fut.await { + info!( + "spawn_block_header_utxo_loop_if_required stopped for {}, reason {}", + ticker, e + ); + } + }); + return Some(abort_handle); + } + None + } +} diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 332114588e..97e60a7774 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -2,6 +2,7 @@ use crate::hd_wallet::{HDAccountsMap, HDAccountsMutex}; use crate::hd_wallet_storage::{HDWalletCoinStorage, HDWalletStorageError}; use crate::utxo::rpc_clients::{ElectrumClient, ElectrumClientImpl, ElectrumRpcRequest, EstimateFeeMethod, UtxoRpcClientEnum}; +use crate::utxo::utxo_block_header_storage::{BlockHeaderStorage, InitBlockHeaderStorageOps}; use crate::utxo::utxo_builder::utxo_conf_builder::{UtxoConfBuilder, UtxoConfError, UtxoConfResult}; use crate::utxo::{output_script, utxo_common, ElectrumBuilderArgs, ElectrumProtoVerifier, RecentlySpentOutPoints, TxFee, UtxoCoinFields, UtxoHDAccount, UtxoHDWallet, UtxoRpcMode, DEFAULT_GAP_LIMIT, UTXO_DUST_AMOUNT}; @@ -163,6 +164,7 @@ pub trait UtxoFieldsWithIguanaPrivKeyBuilder: UtxoCoinBuilderCommonOps { let tx_cache_directory = Some(self.ctx().dbdir().join("TX_CACHE")); let tx_hash_algo = self.tx_hash_algo(); let check_utxo_maturity = self.check_utxo_maturity(); + let block_headers_storage = self.block_headers_storage()?; let coin = UtxoCoinFields { conf, @@ -173,6 +175,7 @@ pub trait UtxoFieldsWithIguanaPrivKeyBuilder: UtxoCoinBuilderCommonOps { derivation_method, history_sync_state: Mutex::new(initial_history_state), tx_cache_directory, + block_headers_storage, recently_spent_outpoints: AsyncMutex::new(RecentlySpentOutPoints::new(my_script_pubkey)), tx_fee, tx_hash_algo, @@ -219,6 +222,7 @@ pub trait UtxoFieldsWithHardwareWalletBuilder: UtxoCoinBuilderCommonOps { let tx_cache_directory = Some(self.ctx().dbdir().join("TX_CACHE")); let tx_hash_algo = self.tx_hash_algo(); let check_utxo_maturity = self.check_utxo_maturity(); + let block_headers_storage = self.block_headers_storage()?; let coin = UtxoCoinFields { conf, @@ -228,6 +232,7 @@ pub trait UtxoFieldsWithHardwareWalletBuilder: UtxoCoinBuilderCommonOps { priv_key_policy: PrivKeyPolicy::HardwareWallet, derivation_method: DerivationMethod::HDWallet(hd_wallet), history_sync_state: Mutex::new(initial_history_state), + block_headers_storage, tx_cache_directory, recently_spent_outpoints, tx_fee, @@ -268,6 +273,15 @@ pub trait UtxoCoinBuilderCommonOps { fn ticker(&self) -> &str; + fn block_headers_storage(&self) -> UtxoCoinBuildResult> { + let params: Option<_> = json::from_value(self.conf()["block_header_params"].clone()) + .map_to_mm(|e| UtxoConfError::InvalidBlockHeaderParams(e.to_string()))?; + match params { + None => Ok(None), + Some(params) => Ok(BlockHeaderStorage::new_from_ctx(self.ctx().clone(), params)), + } + } + fn address_format(&self) -> UtxoCoinBuildResult { let format_from_req = self.activation_params().address_format.clone(); let format_from_conf = json::from_value::>(self.conf()["address_format"].clone()) diff --git a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs index 59d141fef8..451ccc19e9 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs @@ -40,6 +40,7 @@ pub enum UtxoConfError { InvalidConsensusBranchId(String), InvalidVersionGroupId(String), InvalidAddressFormat(String), + InvalidBlockHeaderParams(String), InvalidDecimals(String), } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 7d1b4b385c..218f8c65a7 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -14,10 +14,10 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, GetWithdrawSen use bigdecimal::{BigDecimal, Zero}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use chain::constants::SEQUENCE_FINAL; -use chain::{OutPoint, TransactionOutput}; +use chain::{BlockHeader, OutPoint, RawBlockHeader, TransactionOutput}; use common::executor::Timer; use common::jsonrpc_client::JsonRpcErrorType; -use common::log::{error, info, warn}; +use common::log::{debug, error, info, warn}; use common::mm_ctx::MmArc; use common::mm_error::prelude::*; use common::mm_metrics::MetricsArc; @@ -35,11 +35,16 @@ use rpc::v1::types::{Bytes as BytesJson, TransactionInputEnum, H256 as H256Json} use script::{Builder, Opcode, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput}; use secp256k1::{PublicKey, Signature}; use serde_json::{self as json}; -use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, SERIALIZE_TRANSACTION_WITNESS}; +use serialization::{deserialize, serialize, serialize_list, serialize_with_flags, CoinVariant, + SERIALIZE_TRANSACTION_WITNESS}; +use spv_validation::helpers_validation::validate_headers; +use spv_validation::spv_proof::SPVProof; +use spv_validation::types::SPVError; use std::cmp::Ordering; use std::collections::hash_map::{Entry, HashMap}; use std::str::FromStr; use std::sync::atomic::Ordering as AtomicOrdering; +use utxo_block_header_storage::BlockHeaderStorageOps; use utxo_signer::with_key_pair::p2sh_spend; use utxo_signer::UtxoSignerOps; @@ -67,6 +72,19 @@ lazy_static! { pub const HISTORY_TOO_LARGE_ERR_CODE: i64 = -1; +fn ten_f64() -> f64 { 10. } + +fn one_hundred() -> usize { 100 } + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct UtxoMergeParams { + merge_at: usize, + #[serde(default = "ten_f64")] + check_every: f64, + #[serde(default = "one_hundred")] + max_merge_at_once: usize, +} + pub async fn get_tx_fee(coin: &UtxoCoinFields) -> UtxoRpcResult { let conf = &coin.conf; match &coin.tx_fee { @@ -1475,6 +1493,7 @@ where &input.secret_hash, input.amount, input.time_lock, + input.confirmations, ) } @@ -1498,6 +1517,7 @@ where &input.secret_hash, input.amount, input.time_lock, + input.confirmations, ) } @@ -2991,6 +3011,60 @@ pub fn address_from_pubkey( } } +pub async fn validate_spv_proof(coin: T, tx: UtxoTx) -> Result<(), MmError> +where + T: AsRef + Send + Sync + 'static, +{ + let client = match &coin.as_ref().rpc_client { + UtxoRpcClientEnum::Native(_) => return Ok(()), + UtxoRpcClientEnum::Electrum(electrum_client) => electrum_client, + }; + if tx.outputs.is_empty() { + return MmError::err(SPVError::InvalidVout); + } + let height = get_tx_height(&tx, client).await?; + let block_header = block_header_from_storage_or_rpc(&coin, height, &coin.as_ref().block_headers_storage) + .await + .map_err(|_e| SPVError::UnableToGetHeader)?; + let raw_header = RawBlockHeader::new(block_header.raw().take())?; + + let merkle_branch = client + .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) + .compat() + .await + .map_to_mm(|_e| SPVError::UnableToGetMerkle)?; + let intermediate_nodes: Vec = merkle_branch + .merkle + .into_iter() + .map(|hash| hash.reversed().into()) + .collect(); + let proof = SPVProof { + tx_id: tx.hash(), + vin: serialize_list(&tx.inputs).take(), + vout: serialize_list(&tx.outputs).take(), + index: merkle_branch.pos as u64, + confirming_header: block_header, + raw_header, + intermediate_nodes, + }; + proof.validate().map_err(MmError::new) +} + +pub async fn get_tx_height(tx: &UtxoTx, client: &ElectrumClient) -> Result> { + for output in tx.outputs.clone() { + let script_pubkey_str = hex::encode(electrum_script_hash(&output.script_pubkey)); + if let Ok(history) = client.scripthash_get_history(script_pubkey_str.as_str()).compat().await { + if let Some(item) = history + .into_iter() + .find(|item| item.tx_hash.reversed() == H256Json(*tx.hash()) && item.height > 0) + { + return Ok(item.height as u64); + } + } + } + MmError::err(GetTxHeightError::HeightNotFound) +} + #[allow(clippy::too_many_arguments)] pub fn validate_payment( coin: T, @@ -3001,6 +3075,7 @@ pub fn validate_payment( priv_bn_hash: &[u8], amount: BigDecimal, time_lock: u32, + confirmations: u64, ) -> Box + Send> where T: AsRef + Send + Sync + 'static, @@ -3056,7 +3131,10 @@ where expected_output ); } - return Ok(()); + return match confirmations { + 0 => Ok(()), + _ => validate_spv_proof(coin, tx).await.map_err(|e| format!("{:?}", e)), + }; } }; Box::new(fut.boxed().compat()) @@ -3298,6 +3376,150 @@ fn increase_by_percent(num: u64, percent: f64) -> u64 { num + (percent.round() as u64) } +pub async fn valid_block_header_from_storage( + coin: &T, + height: u64, + storage: &BlockHeaderStorage, + client: &ElectrumClient, +) -> Result> +where + T: AsRef, +{ + match storage + .get_block_header(coin.as_ref().conf.ticker.as_str(), height) + .await? + { + None => { + let bytes = client.blockchain_block_header(height).compat().await?; + let header: BlockHeader = deserialize(bytes.0.as_slice())?; + let params = &storage.params; + let blocks_limit = params.blocks_limit_to_check; + let (headers_registry, headers) = client.retrieve_last_headers(blocks_limit, height).compat().await?; + match spv_validation::helpers_validation::validate_headers( + headers, + params.difficulty_check, + params.constant_difficulty, + ) { + Ok(_) => { + storage + .add_block_headers_to_storage(coin.as_ref().conf.ticker.as_str(), headers_registry) + .await?; + Ok(header) + }, + Err(err) => MmError::err(GetBlockHeaderError::SPVError(err)), + } + }, + Some(header) => Ok(header), + } +} + +pub async fn block_header_from_storage_or_rpc( + coin: &T, + height: u64, + storage: &Option, +) -> Result> +where + T: AsRef, +{ + let client = match &coin.as_ref().rpc_client { + UtxoRpcClientEnum::Native(_) => { + return MmError::err(GetBlockHeaderError::NativeNotSupported( + "Native client not supported".to_string(), + )) + }, + UtxoRpcClientEnum::Electrum(client) => client, + }; + + match storage { + Some(ref storage) => valid_block_header_from_storage(&coin, height, storage, client).await, + None => Ok(deserialize( + client.blockchain_block_header(height).compat().await?.as_slice(), + )?), + } +} + +macro_rules! try_loop_with_sleep { + ($e:expr, $delay: ident) => { + match $e { + Ok(res) => res, + Err(e) => { + error!("error {:?}", e); + Timer::sleep($delay).await; + continue; + }, + } + }; +} + +pub async fn block_header_utxo_loop(weak: UtxoWeak, constructor: impl Fn(UtxoArc) -> T) +where + T: AsRef + UtxoCommonOps, +{ + { + let coin = match weak.upgrade() { + Some(arc) => constructor(arc), + None => return, + }; + let ticker = coin.as_ref().conf.ticker.as_str(); + let storage = match &coin.as_ref().block_headers_storage { + None => return, + Some(storage) => storage, + }; + match storage.is_initialized_for(ticker).await { + Ok(true) => info!("Block Header Storage already initialized for {}", ticker), + Ok(false) => { + if let Err(e) = storage.init(ticker).await { + error!( + "Couldn't initiate storage - aborting the block_header_utxo_loop: {:?}", + e + ); + return; + } + info!("Block Header Storage successfully initialized for {}", ticker); + }, + Err(_e) => return, + }; + } + while let Some(arc) = weak.upgrade() { + let coin = constructor(arc); + let storage = match &coin.as_ref().block_headers_storage { + None => break, + Some(storage) => storage, + }; + let params = storage.params.clone(); + let (check_every, blocks_limit_to_check, difficulty_check, constant_difficulty) = ( + params.check_every, + params.blocks_limit_to_check, + params.difficulty_check, + params.constant_difficulty, + ); + let height = try_loop_with_sleep!(coin.as_ref().rpc_client.get_block_count().compat().await, check_every); + let client = match &coin.as_ref().rpc_client { + UtxoRpcClientEnum::Native(_) => break, + UtxoRpcClientEnum::Electrum(client) => client, + }; + let (block_registry, block_headers) = try_loop_with_sleep!( + client + .retrieve_last_headers(blocks_limit_to_check, height) + .compat() + .await, + check_every + ); + try_loop_with_sleep!( + validate_headers(block_headers, difficulty_check, constant_difficulty), + check_every + ); + + let ticker = coin.as_ref().conf.ticker.as_str(); + try_loop_with_sleep!( + storage.add_block_headers_to_storage(ticker, block_registry).await, + check_every + ); + debug!("tick block_header_utxo_loop for {}", coin.as_ref().conf.ticker); + Timer::sleep(check_every).await; + } +} + pub async fn merge_utxo_loop( weak: UtxoWeak, merge_at: usize, diff --git a/mm2src/coins/utxo/utxo_indexedb_block_header_storage.rs b/mm2src/coins/utxo/utxo_indexedb_block_header_storage.rs new file mode 100644 index 0000000000..4814aaeb6f --- /dev/null +++ b/mm2src/coins/utxo/utxo_indexedb_block_header_storage.rs @@ -0,0 +1,49 @@ +use crate::utxo::rpc_clients::ElectrumBlockHeader; +use crate::utxo::utxo_block_header_storage::BlockHeaderStorageError; +use crate::utxo::utxo_block_header_storage::BlockHeaderStorageOps; +use async_trait::async_trait; +use chain::BlockHeader; +use common::mm_error::MmError; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct IndexedDBBlockHeadersStorage {} + +#[async_trait] +impl BlockHeaderStorageOps for IndexedDBBlockHeadersStorage { + async fn init(&self, _for_coin: &str) -> Result<(), MmError> { Ok(()) } + + async fn is_initialized_for(&self, _for_coin: &str) -> Result> { Ok(true) } + + async fn add_electrum_block_headers_to_storage( + &self, + _for_coin: &str, + _headers: Vec, + ) -> Result<(), MmError> { + Ok(()) + } + + async fn add_block_headers_to_storage( + &self, + _for_coin: &str, + _headers: HashMap, + ) -> Result<(), MmError> { + Ok(()) + } + + async fn get_block_header( + &self, + _for_coin: &str, + _height: u64, + ) -> Result, MmError> { + Ok(None) + } + + async fn get_block_header_raw( + &self, + _for_coin: &str, + _height: u64, + ) -> Result, MmError> { + Ok(None) + } +} diff --git a/mm2src/coins/utxo/utxo_sql_block_header_storage.rs b/mm2src/coins/utxo/utxo_sql_block_header_storage.rs new file mode 100644 index 0000000000..75dfe4f4eb --- /dev/null +++ b/mm2src/coins/utxo/utxo_sql_block_header_storage.rs @@ -0,0 +1,320 @@ +use crate::utxo::rpc_clients::ElectrumBlockHeader; +use crate::utxo::utxo_block_header_storage::{BlockHeaderStorageError, BlockHeaderStorageOps}; +use async_trait::async_trait; +use chain::BlockHeader; +use common::async_blocking; +use common::mm_error::MmError; +use db_common::{sqlite::rusqlite::Error as SqlError, + sqlite::rusqlite::{Connection, Row, ToSql, NO_PARAMS}, + sqlite::string_from_row, + sqlite::validate_table_name, + sqlite::CHECK_TABLE_EXISTS_SQL}; +use serialization::deserialize; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +fn block_headers_cache_table(ticker: &str) -> String { ticker.to_owned() + "_block_headers_cache" } + +fn get_table_name_and_validate(for_coin: &str) -> Result> { + let table_name = block_headers_cache_table(for_coin); + validate_table_name(&table_name).map_err(|e| BlockHeaderStorageError::CantRetrieveTableError { + ticker: for_coin.to_string(), + reason: e.to_string(), + })?; + Ok(table_name) +} + +fn create_block_header_cache_table_sql(for_coin: &str) -> Result> { + let table_name = get_table_name_and_validate(for_coin)?; + let sql = "CREATE TABLE IF NOT EXISTS ".to_owned() + + &table_name + + " ( + block_height INTEGER NOT NULL UNIQUE, + hex TEXT NOT NULL + );"; + + Ok(sql) +} + +fn insert_block_header_in_cache_sql(for_coin: &str) -> Result> { + let table_name = get_table_name_and_validate(for_coin)?; + // We can simply ignore the repetitive attempt to insert the same block_height + let sql = "INSERT OR IGNORE INTO ".to_owned() + &table_name + " (block_height, hex) VALUES (?1, ?2);"; + Ok(sql) +} + +fn get_block_header_by_height(for_coin: &str) -> Result> { + let table_name = get_table_name_and_validate(for_coin)?; + let sql = "SELECT hex FROM ".to_owned() + &table_name + " WHERE block_height=?1;"; + + Ok(sql) +} + +#[derive(Clone, Debug)] +pub struct SqliteBlockHeadersStorage(pub Arc>); + +fn query_single_row( + conn: &Connection, + query: &str, + params: P, + map_fn: F, +) -> Result, MmError> +where + P: IntoIterator, + P::Item: ToSql, + F: FnOnce(&Row<'_>) -> Result, +{ + db_common::sqlite::query_single_row(conn, query, params, map_fn).map_err(|e| { + MmError::new(BlockHeaderStorageError::QueryError { + query: query.to_string(), + reason: e.to_string(), + }) + }) +} + +struct SqlBlockHeader { + block_height: String, + block_hex: String, +} + +impl From for SqlBlockHeader { + fn from(header: ElectrumBlockHeader) -> Self { + match header { + ElectrumBlockHeader::V12(h) => { + let block_hex = h.as_hex(); + let block_height = h.block_height.to_string(); + SqlBlockHeader { + block_height, + block_hex, + } + }, + ElectrumBlockHeader::V14(h) => { + let block_hex = format!("{:02x}", h.hex); + let block_height = h.height.to_string(); + SqlBlockHeader { + block_height, + block_hex, + } + }, + } + } +} +async fn common_headers_insert( + for_coin: &str, + storage: SqliteBlockHeadersStorage, + headers: Vec, +) -> Result<(), MmError> { + let for_coin = for_coin.to_owned(); + let mut conn = storage.0.lock().unwrap(); + let sql_transaction = conn + .transaction() + .map_err(|e| BlockHeaderStorageError::AddToStorageError { + ticker: for_coin.to_string(), + reason: e.to_string(), + })?; + for header in headers { + let block_cache_params = [&header.block_height, &header.block_hex]; + sql_transaction + .execute(&insert_block_header_in_cache_sql(&for_coin)?, block_cache_params) + .map_err(|e| BlockHeaderStorageError::AddToStorageError { + ticker: for_coin.to_string(), + reason: e.to_string(), + })?; + } + sql_transaction + .commit() + .map_err(|e| BlockHeaderStorageError::AddToStorageError { + ticker: for_coin.to_string(), + reason: e.to_string(), + })?; + Ok(()) +} + +#[async_trait] +impl BlockHeaderStorageOps for SqliteBlockHeadersStorage { + async fn init(&self, for_coin: &str) -> Result<(), MmError> { + let selfi = self.clone(); + let sql_cache = create_block_header_cache_table_sql(for_coin)?; + let ticker = for_coin.to_owned(); + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + conn.execute(&sql_cache, NO_PARAMS).map(|_| ()).map_err(|e| { + BlockHeaderStorageError::InitializationError { + ticker, + reason: e.to_string(), + } + })?; + Ok(()) + }) + .await + } + + async fn is_initialized_for(&self, for_coin: &str) -> Result> { + let block_headers_cache_table = get_table_name_and_validate(for_coin)?; + let selfi = self.clone(); + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + let cache_initialized = query_single_row( + &conn, + CHECK_TABLE_EXISTS_SQL, + [block_headers_cache_table], + string_from_row, + )?; + Ok(cache_initialized.is_some()) + }) + .await + } + + async fn add_electrum_block_headers_to_storage( + &self, + for_coin: &str, + headers: Vec, + ) -> Result<(), MmError> { + let headers_for_sql = headers.into_iter().map(Into::into).collect(); + common_headers_insert(for_coin, self.clone(), headers_for_sql).await + } + + async fn add_block_headers_to_storage( + &self, + for_coin: &str, + headers: HashMap, + ) -> Result<(), MmError> { + let headers_for_sql = headers + .into_iter() + .map(|(height, header)| SqlBlockHeader { + block_height: height.to_string(), + block_hex: hex::encode(header.raw()), + }) + .collect(); + common_headers_insert(for_coin, self.clone(), headers_for_sql).await + } + + async fn get_block_header( + &self, + for_coin: &str, + height: u64, + ) -> Result, MmError> { + if let Some(header_raw) = self.get_block_header_raw(for_coin, height).await? { + let header_bytes = hex::decode(header_raw).map_err(|e| BlockHeaderStorageError::DecodeError { + ticker: for_coin.to_string(), + reason: e.to_string(), + })?; + let header: BlockHeader = + deserialize(header_bytes.as_slice()).map_err(|e| BlockHeaderStorageError::DecodeError { + ticker: for_coin.to_string(), + reason: e.to_string(), + })?; + return Ok(Some(header)); + } + Ok(None) + } + + async fn get_block_header_raw( + &self, + for_coin: &str, + height: u64, + ) -> Result, MmError> { + let params = [height.to_string()]; + let sql = get_block_header_by_height(for_coin)?; + let selfi = self.clone(); + + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + query_single_row(&conn, &sql, params, string_from_row) + }) + .await + .map_err(|e| { + MmError::new(BlockHeaderStorageError::GetFromStorageError { + ticker: for_coin.to_string(), + reason: e.into_inner().to_string(), + }) + }) + } +} + +#[cfg(test)] +impl SqliteBlockHeadersStorage { + pub fn in_memory() -> Self { + SqliteBlockHeadersStorage(Arc::new(Mutex::new(Connection::open_in_memory().unwrap()))) + } + + fn is_table_empty(&self, table_name: &str) -> bool { + validate_table_name(table_name).unwrap(); + let sql = "SELECT COUNT(block_height) FROM ".to_owned() + table_name + ";"; + let conn = self.0.lock().unwrap(); + let rows_count: u32 = conn.query_row(&sql, NO_PARAMS, |row| row.get(0)).unwrap(); + rows_count == 0 + } +} + +#[cfg(test)] +mod sql_block_headers_storage_tests { + use super::*; + use crate::utxo::rpc_clients::ElectrumBlockHeaderV14; + use common::block_on; + use primitives::hash::H256; + + #[test] + fn test_init_collection() { + let for_coin = "init_collection"; + let storage = SqliteBlockHeadersStorage::in_memory(); + let initialized = block_on(storage.is_initialized_for(for_coin)).unwrap(); + assert!(!initialized); + + block_on(storage.init(for_coin)).unwrap(); + // repetitive init must not fail + block_on(storage.init(for_coin)).unwrap(); + + let initialized = block_on(storage.is_initialized_for(for_coin)).unwrap(); + assert!(initialized); + } + + #[test] + fn test_add_block_headers() { + let for_coin = "insert"; + let storage = SqliteBlockHeadersStorage::in_memory(); + let table = block_headers_cache_table(for_coin); + block_on(storage.init(for_coin)).unwrap(); + + let initialized = block_on(storage.is_initialized_for(for_coin)).unwrap(); + assert!(initialized); + + let block_header = ElectrumBlockHeaderV14 { + height: 520481, + hex: "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".into(), + }.into(); + let headers = vec![ElectrumBlockHeader::V14(block_header)]; + block_on(storage.add_electrum_block_headers_to_storage(for_coin, headers)).unwrap(); + assert!(!storage.is_table_empty(&table)); + } + + #[test] + fn test_get_block_header() { + let for_coin = "get"; + let storage = SqliteBlockHeadersStorage::in_memory(); + let table = block_headers_cache_table(for_coin); + block_on(storage.init(for_coin)).unwrap(); + + let initialized = block_on(storage.is_initialized_for(for_coin)).unwrap(); + assert!(initialized); + + let block_header = ElectrumBlockHeaderV14 { + height: 520481, + hex: "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".into(), + }.into(); + let headers = vec![ElectrumBlockHeader::V14(block_header)]; + block_on(storage.add_electrum_block_headers_to_storage(for_coin, headers)).unwrap(); + assert!(!storage.is_table_empty(&table)); + + let hex = block_on(storage.get_block_header_raw(for_coin, 520481)) + .unwrap() + .unwrap(); + assert_eq!(hex, "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".to_string()); + + let block_header = block_on(storage.get_block_header(for_coin, 520481)).unwrap().unwrap(); + assert_eq!( + block_header.hash(), + H256::from_reversed_str("0000000000000000002e31d0714a5ab23100945ff87ba2d856cd566a3c9344ec") + ) + } +} diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 72101a374b..15e89dfc02 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -146,6 +146,7 @@ fn utxo_coin_fields_for_test( derivation_method, history_sync_state: Mutex::new(HistorySyncState::NotEnabled), tx_cache_directory: None, + block_headers_storage: None, recently_spent_outpoints: AsyncMutex::new(RecentlySpentOutPoints::new(my_script_pubkey)), tx_hash_algo: TxHashAlgo::DSHA256, check_utxo_maturity: false, @@ -920,6 +921,22 @@ fn test_utxo_lock() { } } +#[test] +fn test_spv_proof() { + let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let coin = utxo_coin_for_test( + client.into(), + Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + false, + ); + + // https://rick.explorer.dexstats.info/tx/78ea7839f6d1b0dafda2ba7e34c1d8218676a58bd1b33f03a5f76391f61b72b0 + let tx_str = "0400008085202f8902bf17bf7d1daace52e08f732a6b8771743ca4b1cb765a187e72fd091a0aabfd52000000006a47304402203eaaa3c4da101240f80f9c5e9de716a22b1ec6d66080de6a0cca32011cd77223022040d9082b6242d6acf9a1a8e658779e1c655d708379862f235e8ba7b8ca4e69c6012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffffff023ca13c0e9e085dd13f481f193e8a3e8fd609020936e98b5587342d994f4d020000006b483045022100c0ba56adb8de923975052312467347d83238bd8d480ce66e8b709a7997373994022048507bcac921fdb2302fa5224ce86e41b7efc1a2e20ae63aa738dfa99b7be826012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0300e1f5050000000017a9141ee6d4c38a3c078eab87ad1a5e4b00f21259b10d870000000000000000166a1400000000000000000000000000000000000000001b94d736000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac2d08e35e000000000000000000000000000000"; + let tx: UtxoTx = tx_str.into(); + let res = block_on(utxo_common::validate_spv_proof(coin.clone(), tx)); + res.unwrap() +} + #[test] fn list_since_block_btc_serde() { // https://github.com/KomodoPlatform/atomicDEX-API/issues/563 diff --git a/mm2src/coins/utxo/utxo_withdraw.rs b/mm2src/coins/utxo/utxo_withdraw.rs index 3bac44f458..a3fd8c6f20 100644 --- a/mm2src/coins/utxo/utxo_withdraw.rs +++ b/mm2src/coins/utxo/utxo_withdraw.rs @@ -104,20 +104,20 @@ where { fn coin(&self) -> &Coin; - fn from_address(&self) -> Address; + fn sender_address(&self) -> Address; - fn from_address_string(&self) -> String; + fn sender_address_string(&self) -> String; fn request(&self) -> &WithdrawRequest; fn signature_version(&self) -> SignatureVersion { - match self.from_address().addr_format { + match self.sender_address().addr_format { UtxoAddressFormat::Segwit => SignatureVersion::WitnessV0, _ => self.coin().as_ref().conf.signature_version, } } - fn prev_script(&self) -> Script { Builder::build_p2pkh(&self.from_address().hash) } + fn prev_script(&self) -> Script { Builder::build_p2pkh(&self.sender_address().hash) } fn on_generating_transaction(&self) -> Result<(), MmError>; @@ -152,7 +152,7 @@ where let script_pubkey = output_script(&to, script_type).to_bytes(); let _utxo_lock = UTXO_LOCK.lock().await; - let (unspents, _) = coin.list_unspent_ordered(&self.from_address()).await?; + let (unspents, _) = coin.list_unspent_ordered(&self.sender_address()).await?; let (value, fee_policy) = if req.max { ( unspents.iter().fold(0, |sum, unspent| sum + unspent.value), @@ -165,7 +165,7 @@ where let outputs = vec![TransactionOutput { value, script_pubkey }]; let mut tx_builder = UtxoTxBuilder::new(coin) - .with_from_address(self.from_address()) + .with_from_address(self.sender_address()) .add_available_inputs(unspents) .add_outputs(outputs) .with_fee_policy(fee_policy); @@ -208,7 +208,7 @@ where _ => serialize(&signed).into(), }; Ok(TransactionDetails { - from: vec![self.from_address_string()], + from: vec![self.sender_address_string()], to: vec![req.to.clone()], total_amount: big_decimal_from_sat(data.spent_by_me as i64, decimals), spent_by_me: big_decimal_from_sat(data.spent_by_me as i64, decimals), @@ -248,9 +248,9 @@ where { fn coin(&self) -> &Coin { &self.coin } - fn from_address(&self) -> Address { self.from_address.clone() } + fn sender_address(&self) -> Address { self.from_address.clone() } - fn from_address_string(&self) -> String { self.from_address_string.clone() } + fn sender_address_string(&self) -> String { self.from_address_string.clone() } fn request(&self) -> &WithdrawRequest { &self.req } @@ -436,9 +436,9 @@ where { fn coin(&self) -> &Coin { &self.coin } - fn from_address(&self) -> Address { self.my_address.clone() } + fn sender_address(&self) -> Address { self.my_address.clone() } - fn from_address_string(&self) -> String { self.my_address_string.clone() } + fn sender_address_string(&self) -> String { self.my_address_string.clone() } fn request(&self) -> &WithdrawRequest { &self.req } diff --git a/mm2src/coins/utxo_signer/src/sign_common.rs b/mm2src/coins/utxo_signer/src/sign_common.rs index 74a7c8906f..fff677f8fa 100644 --- a/mm2src/coins/utxo_signer/src/sign_common.rs +++ b/mm2src/coins/utxo_signer/src/sign_common.rs @@ -105,7 +105,7 @@ pub(crate) fn script_sig_with_pub(public_key: &PublicKey, fork_id: u32, signatur let builder = Builder::default(); builder .push_data(&script_sig) - .push_data(&public_key.to_vec()) + .push_data(public_key.to_vec().as_slice()) .into_bytes() } diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 2a42690578..d07aa57c21 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -42,7 +42,7 @@ log = "0.4.8" num-bigint = { version = "0.2", features = ["serde", "std"] } num-rational = { version = "0.2", features = ["serde", "bigint", "bigint-std"] } num-traits = "0.2" -parking_lot = { version = "0.11", features = ["nightly"] } +parking_lot = { version = "0.12.0", features = ["nightly"] } parking_lot_core = { version = "0.6", features = ["nightly"] } paste = "1.0" primitives = { path = "../mm2_bitcoin/primitives" } diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 77f13fe168..fd03e2182a 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -1321,7 +1321,7 @@ pub fn round_to(bd: &BigDecimal, places: u8) -> String { if pos < dot { //println! ("{}, pos < dot, stopping at pos {}", bds, pos); - let mut integer: i64 = (&bds[0..=pos]).parse().unwrap(); + let mut integer: i64 = (bds[0..=pos]).parse().unwrap(); if prev_digit > 5 { if bda[0] == b'-' { integer = integer.checked_sub(1).unwrap() diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index 488fa0d4a9..eb3a682812 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -16,9 +16,9 @@ futures = "0.3" hex = "0.4.2" http = "0.2" hw_common = { path = "../hw_common" } +parking_lot = { version = "0.12.0", features = ["nightly"] } keys = { path = "../mm2_bitcoin/keys" } num-traits = "0.1" -parking_lot = { version = "0.11", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } rpc_task = { path = "../rpc_task" } secp256k1 = "0.20" diff --git a/mm2src/db_common/src/sqlite.rs b/mm2src/db_common/src/sqlite.rs index 7680996c31..4e11afb1ab 100644 --- a/mm2src/db_common/src/sqlite.rs +++ b/mm2src/db_common/src/sqlite.rs @@ -2,7 +2,7 @@ pub use rusqlite; pub use sql_builder; use log::debug; -use rusqlite::{Connection, Error as SqlError, Result as SqlResult, ToSql}; +use rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, ToSql}; use sql_builder::SqlBuilder; use std::sync::{Arc, Mutex, Weak}; use uuid::Uuid; @@ -10,6 +10,25 @@ use uuid::Uuid; pub type SqliteConnShared = Arc>; pub type SqliteConnWeak = Weak>; +pub const CHECK_TABLE_EXISTS_SQL: &str = "SELECT name FROM sqlite_master WHERE type='table' AND name=?1;"; + +pub fn string_from_row(row: &Row<'_>) -> Result { row.get(0) } + +pub fn query_single_row(conn: &Connection, query: &str, params: P, map_fn: F) -> Result, SqlError> +where + P: IntoIterator, + P::Item: ToSql, + F: FnOnce(&Row<'_>) -> Result, +{ + let maybe_result = conn.query_row(query, params, map_fn); + if let Err(SqlError::QueryReturnedNoRows) = maybe_result { + return Ok(None); + } + + let result = maybe_result?; + Ok(Some(result)) +} + pub fn validate_table_name(table_name: &str) -> SqlResult<()> { // As per https://stackoverflow.com/a/3247553, tables can't be the target of parameter substitution. // So we have to use a plain concatenation disallowing any characters in the table name that may lead to SQL injection. diff --git a/mm2src/docker_tests.rs b/mm2src/docker_tests.rs index 7984e3c0d3..968cb6b0e1 100644 --- a/mm2src/docker_tests.rs +++ b/mm2src/docker_tests.rs @@ -1432,7 +1432,7 @@ mod docker_tests { .unwrap(); // TODO when buy call is made immediately swap might be not put into swap ctx yet so locked // amount returns 0 - thread::sleep(Duration::from_secs(3)); + thread::sleep(Duration::from_secs(6)); let rc = block_on(mm_alice.rpc(json! ({ "userpass": mm_alice.userpass, @@ -1529,7 +1529,7 @@ mod docker_tests { .unwrap(); // TODO when sell call is made immediately swap might be not put into swap ctx yet so locked // amount returns 0 - thread::sleep(Duration::from_secs(3)); + thread::sleep(Duration::from_secs(6)); let rc = block_on(mm_alice.rpc(json! ({ "userpass": mm_alice.userpass, diff --git a/mm2src/docker_tests/qrc20_tests.rs b/mm2src/docker_tests/qrc20_tests.rs index 85e5f10177..d45fa66de9 100644 --- a/mm2src/docker_tests/qrc20_tests.rs +++ b/mm2src/docker_tests/qrc20_tests.rs @@ -207,6 +207,7 @@ fn test_taker_spends_maker_payment() { secret_hash, amount: amount.clone(), swap_contract_address: taker_coin.swap_contract_address(), + confirmations, }; taker_coin.validate_maker_payment(input).wait().unwrap(); @@ -297,6 +298,7 @@ fn test_maker_spends_taker_payment() { secret_hash: secret_hash.clone(), amount: amount.clone(), swap_contract_address: maker_coin.swap_contract_address(), + confirmations, }; maker_coin.validate_taker_payment(input).wait().unwrap(); diff --git a/mm2src/gossipsub/src/behaviour.rs b/mm2src/gossipsub/src/behaviour.rs index c702d907f9..b308c37ba8 100644 --- a/mm2src/gossipsub/src/behaviour.rs +++ b/mm2src/gossipsub/src/behaviour.rs @@ -849,7 +849,7 @@ impl Gossipsub { .collect(); let mut prunes: Vec = to_prune .remove(peer) - .unwrap_or_else(Vec::new) + .unwrap_or_default() .iter() .map(|topic_hash| GossipsubControlAction::Prune { topic_hash: topic_hash.clone(), @@ -1042,12 +1042,10 @@ impl Gossipsub { } } - pub fn get_mesh_peers(&self, topic: &TopicHash) -> Vec { - self.mesh.get(topic).cloned().unwrap_or_else(Vec::new) - } + pub fn get_mesh_peers(&self, topic: &TopicHash) -> Vec { self.mesh.get(topic).cloned().unwrap_or_default() } pub fn get_topic_peers(&self, topic: &TopicHash) -> Vec { - self.topic_peers.get(topic).cloned().unwrap_or_else(Vec::new) + self.topic_peers.get(topic).cloned().unwrap_or_default() } pub fn get_num_peers(&self) -> usize { self.peer_topics.len() } diff --git a/mm2src/lp_ordermatch.rs b/mm2src/lp_ordermatch.rs index bf3d7a347c..0bfc66fa09 100644 --- a/mm2src/lp_ordermatch.rs +++ b/mm2src/lp_ordermatch.rs @@ -3548,6 +3548,7 @@ pub struct TakerRequestForRpc<'a> { conf_settings: &'a Option, } +#[allow(clippy::needless_borrow)] pub async fn lp_auto_buy( ctx: &MmArc, base_coin: &MmCoinEnum, @@ -4067,6 +4068,7 @@ struct MakerMatchForRpc<'a> { last_updated: u64, } +#[allow(clippy::needless_borrow)] impl<'a> From<&'a MakerMatch> for MakerMatchForRpc<'a> { fn from(maker_match: &'a MakerMatch) -> MakerMatchForRpc { MakerMatchForRpc { @@ -4794,6 +4796,7 @@ struct TakerMatchForRpc<'a> { last_updated: u64, } +#[allow(clippy::needless_borrow)] impl<'a> From<&'a TakerMatch> for TakerMatchForRpc<'a> { fn from(taker_match: &'a TakerMatch) -> TakerMatchForRpc { TakerMatchForRpc { @@ -4816,6 +4819,7 @@ struct TakerOrderForRpc<'a> { rel_orderbook_ticker: &'a Option, } +#[allow(clippy::needless_borrow)] impl<'a> From<&'a TakerOrder> for TakerOrderForRpc<'a> { fn from(order: &'a TakerOrder) -> TakerOrderForRpc { TakerOrderForRpc { diff --git a/mm2src/lp_swap/maker_swap.rs b/mm2src/lp_swap/maker_swap.rs index ca84c482e5..72b9f8769e 100644 --- a/mm2src/lp_swap/maker_swap.rs +++ b/mm2src/lp_swap/maker_swap.rs @@ -767,12 +767,13 @@ impl MakerSwap { async fn validate_taker_payment(&self) -> Result<(Option, Vec), String> { let wait_duration = (self.r().data.lock_duration * 4) / 5; let wait_taker_payment = self.r().data.started_at + wait_duration; + let confirmations = self.r().data.taker_payment_confirmations; let wait_f = self .taker_coin .wait_for_confirmations( &self.r().taker_payment.clone().unwrap().tx_hex, - self.r().data.taker_payment_confirmations, + confirmations, self.r().data.taker_payment_requires_nota.unwrap_or(false), wait_taker_payment, WAIT_CONFIRM_INTERVAL, @@ -797,6 +798,7 @@ impl MakerSwap { secret_hash: dhash160(&self.r().data.secret.0).to_vec(), amount: self.taker_amount.clone(), swap_contract_address: self.r().data.taker_coin_swap_contract_address.clone(), + confirmations, }; let validated_f = self.taker_coin.validate_taker_payment(validate_input).compat(); diff --git a/mm2src/lp_swap/taker_swap.rs b/mm2src/lp_swap/taker_swap.rs index 3e32e0a08c..57aa0e5b38 100644 --- a/mm2src/lp_swap/taker_swap.rs +++ b/mm2src/lp_swap/taker_swap.rs @@ -1097,9 +1097,10 @@ impl TakerSwap { async fn validate_maker_payment(&self) -> Result<(Option, Vec), String> { log!({ "Before wait confirm" }); + let confirmations = self.r().data.maker_payment_confirmations; let f = self.maker_coin.wait_for_confirmations( &self.r().maker_payment.clone().unwrap().tx_hex, - self.r().data.maker_payment_confirmations, + confirmations, self.r().data.maker_payment_requires_nota.unwrap_or(false), self.r().data.maker_payment_wait, WAIT_CONFIRM_INTERVAL, @@ -1121,6 +1122,7 @@ impl TakerSwap { secret_hash: self.r().secret_hash.0.to_vec(), amount: self.maker_amount.to_decimal(), swap_contract_address: self.r().data.maker_coin_swap_contract_address.clone(), + confirmations, }; let validated = self.maker_coin.validate_maker_payment(validate_input).compat().await; diff --git a/mm2src/mm2_bitcoin/chain/src/block_header.rs b/mm2src/mm2_bitcoin/chain/src/block_header.rs index 95a713accc..962fca362f 100644 --- a/mm2src/mm2_bitcoin/chain/src/block_header.rs +++ b/mm2src/mm2_bitcoin/chain/src/block_header.rs @@ -2,6 +2,8 @@ use compact::Compact; use crypto::dhash256; use hash::H256; use hex::FromHex; +use primitives::bytes::Bytes; +use primitives::U256; use ser::{deserialize, serialize, Deserializable, Reader, Serializable, Stream}; use std::io; use transaction::{deserialize_tx, TxType}; @@ -264,6 +266,13 @@ impl Deserializable for BlockHeader { impl BlockHeader { pub fn hash(&self) -> H256 { dhash256(&serialize(self)) } + pub fn raw(&self) -> Bytes { serialize(self) } + pub fn target(&self) -> Result { + match self.bits { + BlockHeaderBits::Compact(compact) => compact.to_u256(), + BlockHeaderBits::U32(nb) => Ok(U256::from(nb)), + } + } } impl From<&'static str> for BlockHeader { diff --git a/mm2src/mm2_bitcoin/chain/src/lib.rs b/mm2src/mm2_bitcoin/chain/src/lib.rs index 8761e7c434..b3f474fd49 100644 --- a/mm2src/mm2_bitcoin/chain/src/lib.rs +++ b/mm2src/mm2_bitcoin/chain/src/lib.rs @@ -10,6 +10,8 @@ pub mod constants; mod block; mod block_header; mod merkle_root; +mod raw_block; +pub use raw_block::{RawBlockHeader, RawHeaderError}; mod transaction; /// `IndexedBlock` extension diff --git a/mm2src/mm2_bitcoin/chain/src/raw_block.rs b/mm2src/mm2_bitcoin/chain/src/raw_block.rs new file mode 100644 index 0000000000..075d15ad6a --- /dev/null +++ b/mm2src/mm2_bitcoin/chain/src/raw_block.rs @@ -0,0 +1,80 @@ +use crypto::dhash256; +use primitives::bytes::Bytes; +use primitives::hash::H256; +use ser::serialize; +use BlockHeader; + +pub const MIN_RAW_HEADER_SIZE: usize = 80_usize; + +/// Hex-encoded block +#[derive(Default, PartialEq, Clone, Eq, Hash)] +pub struct RawBlockHeader(Bytes); + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum RawHeaderError { + WrongLengthHeader { min_length: usize }, +} + +impl RawBlockHeader { + pub fn new(buf: Vec) -> Result { + if buf.len() < MIN_RAW_HEADER_SIZE { + return Err(RawHeaderError::WrongLengthHeader { + min_length: MIN_RAW_HEADER_SIZE, + }); + } + let header = RawBlockHeader(buf.into()); + Ok(header) + } + + /// Calculate the LE header digest + pub fn digest(&self) -> H256 { dhash256(self.0.as_slice()) } + + /// Extract the LE tx merkle root from the header + pub fn extract_merkle_root(&self) -> H256 { + let mut root = H256::default(); + root.as_mut().copy_from_slice(&self.0.as_ref()[36..68]); + root + } + + /// Extract the LE parent digest from the header + pub fn parent(&self) -> H256 { + let mut root = H256::default(); + root.as_mut().copy_from_slice(&self.0.as_ref()[4..36]); + root + } +} + +impl From for RawBlockHeader { + fn from(header: BlockHeader) -> Self { + let bytes = serialize(&header); + RawBlockHeader(bytes) + } +} + +#[cfg(test)] +mod raw_block_header_tests { + use hex::FromHex; + use primitives::hash::H256; + use raw_block::RawBlockHeader; + + #[test] + fn test_raw_header() { + // block header of https://kmdexplorer.io/block/01ad31e22fea912a974c3e1eea11dc26348676528a586f77199ac3cfe29e271f + let header_hex = "040000008e4e7283b71dd1572d220935db0a1654d1042e92378579f8abab67b143f93a02fa026610d2634b72ff729b9ea7850c0d2c25eeaf7a82878ca42a8e9912028863a2d8a734eb73a4dc734072dbfd12406f1e7121bfe0e3d6c10922495c44e5cc1c91185d5ee519011d0400b9caaf41d4b63a6ab55bb4e6925d46fc3adea7be37b713d3a615e7cf0000fd40050001a80fa65b9a46fdb1506a7a4d26f43e7995d69902489b9f6c4599c88f9c169605cc135258953da0d6299ada4ff81a76ad63c943261078d5dd1918f91cea68b65b7fc362e9df49ba57c2ea5c6dba91591c85eb0d59a1905ac66e2295b7a291a1695301489a3cc7310fd45f2b94e3b8d94f3051e9bbaada1e0641fcec6e0d6230e76753aa9574a3f3e28eaa085959beffd3231dbe1aeea3955328f3a973650a38e31632a4ffc7ec007a3345124c0b99114e2444b3ef0ada75adbd077b247bbf3229adcffbe95bc62daac88f96317d5768540b5db636f8c39a8529a736465ed830ab2c1bbddf523587abe14397a6f1835d248092c4b5b691a955572607093177a5911e317739187b41f4aa662aa6bca0401f1a0a77915ebb6947db686cff549c5f4e7b9dd93123b00a1ae8d411cfb13fa7674de21cbee8e9fc74e12aa6753b261eab3d9256c7c32cc9b16219dad73c61014e7d88d74d5e218f12e11bc47557347ff49a9ab4490647418d2a5c2da1df24d16dfb611173608fe4b10a357b0fa7a1918b9f2d7836c84bf05f384e1e678b2fdd47af0d8e66e739fe45209ede151a180aba1188058a0db093e30bc9851980cf6fbfa5adb612d1146905da662c3347d7e7e569a1041641049d951ab867bc0c6a3863c7667d43f596a849434958cee2b63dc8fa11bd0f38aa96df86ed66461993f64736345313053508c4e939506c08a766f5b6ed0950759f3901bbc4db3dc97e05bf20b9dda4ff242083db304a4e487ac2101b823998371542354e5d534b5b6ae6420cc19b11512108b61208f4d9a5a97263d2c060da893544dea6251bcadc682d2238af35f2b1c2f65a73b89a4e194f9e1eef6f0e5948ef8d0d2862f48fd3356126b00c6a2d3770ecd0d1a78fa34974b454f270b23d461e357c9356c19496522b59ff9d5b4608c542ff89e558798324021704b2cfe9f6c1a70906c43c7a690f16615f198d29fa647d84ce8461fa570b33e3eada2ed7d77e1f280a0d2e9f03c2e1db535d922b1759a191b417595f3c15d8e8b7f810527ff942e18443a3860e67ccba356809ecedc31c5d8db59c7e039dae4b53d126679e8ffa20cc26e8b9d229c8f6ee434ad053f5f4f5a94e249a13afb995aad82b4d90890187e516e114b168fc7c7e291b9738ea578a7bab0ba31030b14ba90b772b577806ea2d17856b0cb9e74254ba582a9f2638ea7ed2ca23be898c6108ff8f466b443537ed9ec56b8771bfbf0f2f6e1092a28a7fd182f111e1dbdd155ea82c6cb72d5f9e6518cc667b8226b5f5c6646125fc851e97cf125f48949f988ed37c4283072fc03dd1da3e35161e17f44c0e22c76f708bb66405737ef24176e291b4fc2eadab876115dc62d48e053a85f0ad132ef07ad5175b036fe39e1ad14fcdcdc6ac5b3daabe05161a72a50545dd812e0f9af133d061b726f491e904d89ee57811ef58d3bda151f577aed381963a30d91fb98dc49413300d132a7021a5e834e266b4ac982d76e00f43f5336b8e8028a0cacfa11813b01e50f71236a73a4c0d0757c1832b0680ada56c80edf070f438ab2bc587542f926ff8d3644b8b8a56c78576f127dec7aed9cb3e1bc2442f978a9df1dc3056a63e653132d0f419213d3cb86e7b61720de1aa3af4b3757a58156970da27560c6629257158452b9d5e4283dc6fe7df42d2fda3352d5b62ce5a984d912777c3b01837df8968a4d494db1b663e0e68197dbf196f21ea11a77095263dec548e2010460840231329d83978885ee2423e8b327785970e27c6c6d436157fb5b56119b19239edbb730ebae013d82c35df4a6e70818a74d1ef7a2e87c090ff90e32939f58ed24e85b492b5750fd2cd14b9b8517136b76b1cc6ccc6f6f027f65f1967a0eb4f32cd6e5d5315"; + let header_bytes: Vec = header_hex.from_hex().unwrap(); + let raw_header = RawBlockHeader::new(header_bytes).unwrap(); + assert_eq!( + raw_header.digest(), + H256::from_reversed_str("01ad31e22fea912a974c3e1eea11dc26348676528a586f77199ac3cfe29e271f") + ); + assert_eq!( + raw_header.extract_merkle_root(), + H256::from_reversed_str("63880212998e2aa48c87827aafee252c0d0c85a79e9b72ff724b63d2106602fa") + ); + + assert_eq!( + raw_header.parent(), + H256::from_reversed_str("023af943b167ababf8798537922e04d154160adb3509222d57d11db783724e8e") + ); + } +} diff --git a/mm2src/mm2_bitcoin/spv_validation/Cargo.toml b/mm2src/mm2_bitcoin/spv_validation/Cargo.toml new file mode 100644 index 0000000000..c8af6d79bb --- /dev/null +++ b/mm2src/mm2_bitcoin/spv_validation/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "spv_validation" +version = "0.1.0" +authors = ["Roman Sztergbaum "] + +[dependencies] +bitcoin-spv = "5.0.0" +chain = {path = "../chain"} +primitives = { path = "../primitives" } +serialization = { path = "../serialization" } +rustc-hex = "2" \ No newline at end of file diff --git a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs new file mode 100644 index 0000000000..5665af9bcc --- /dev/null +++ b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs @@ -0,0 +1,173 @@ +use bitcoin_spv::btcspv::verify_hash256_merkle; +use chain::{BlockHeader, RawBlockHeader}; +use primitives::hash::H256; +use primitives::U256; +use types::SPVError; + +/// Evaluates a Bitcoin merkle inclusion proof. +/// Note that `index` is not a reliable indicator of location within a block. +/// +/// # Arguments +/// +/// * `txid` - The txid (LE) +/// * `merkle_root` - The merkle root (as in the block header) (LE) +/// * `intermediate_nodes` - The proof's intermediate nodes (digests between leaf and root) (LE) +/// * `index` - The leaf's index in the tree (0-indexed) +/// +/// # Notes +/// Wrapper around `bitcoin_spv::validatespv::prove` +pub fn merkle_prove(txid: H256, merkle_root: H256, intermediate_nodes: Vec, index: u64) -> Result<(), SPVError> { + if txid == merkle_root && index == 0 && intermediate_nodes.is_empty() { + return Ok(()); + } + let vec: Vec = intermediate_nodes.into_iter().flat_map(|node| node.take()).collect(); + let nodes = bitcoin_spv::types::MerkleArray::new(vec.as_slice())?; + if !verify_hash256_merkle(txid.take().into(), merkle_root.take().into(), &nodes, index) { + return Err(SPVError::BadMerkleProof); + } + Ok(()) +} + +fn validate_header_prev_hash(actual: &H256, to_compare_with: &H256) -> bool { actual == to_compare_with } + +pub fn validate_header_work(digest: H256, target: &U256) -> bool { + let empty = H256::default(); + + if digest == empty { + return false; + } + + U256::from_little_endian(digest.as_slice()) < *target +} + +/// Checks validity of header chain. +/// Compares the hash of each header to the prevHash in the next header. +/// +/// # Arguments +/// +/// * `headers` - Raw byte array of header chain +/// * `difficulty_check`: Rather the difficulty need to check or not, usefull for chain like Qtum (Pos) +/// or KMD/SmartChain (Difficulty change NN) +/// * `constant_difficulty`: If we do not expect difficulty change (BTC difficulty change every 2016 blocks) +/// use this variable to false when you do not have a chance to use a checkpoint +/// +/// # Errors +/// +/// * Errors if header chain is invalid, insufficient work, unexpected difficulty change or unable to get a target +/// +/// # Notes +/// Wrapper inspired by `bitcoin_spv::validatespv::validate_header_chain` +pub fn validate_headers( + headers: Vec, + difficulty_check: bool, + constant_difficulty: bool, +) -> Result<(), SPVError> { + let mut previous_hash = H256::default(); + let mut target = U256::default(); + for (i, header) in headers.into_iter().enumerate() { + let raw_header = RawBlockHeader::from(header.clone()); + if i == 0 { + target = match header.target() { + Ok(target) => target, + Err(_) => return Err(SPVError::UnableToGetTarget), + }; + } + let cur_target = match header.target() { + Ok(target) => target, + Err(_) => return Err(SPVError::UnableToGetTarget), + }; + if (!constant_difficulty && difficulty_check) && cur_target != target { + return Err(SPVError::UnexpectedDifficultyChange); + } + if i != 0 && !validate_header_prev_hash(&raw_header.parent(), &previous_hash) { + return Err(SPVError::InvalidChain); + } + if difficulty_check && !validate_header_work(raw_header.digest(), &target) { + return Err(SPVError::InsufficientWork); + } + previous_hash = raw_header.digest(); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_merkle_prove_inclusion() { + // https://rick.explorer.dexstats.info/tx/7e9797a05abafbc1542449766ef9a41838ebbf6d24cd3223d361aa07c51981df + // merkle intermediate nodes 2 element + let tx_id: H256 = H256::from_reversed_str("7e9797a05abafbc1542449766ef9a41838ebbf6d24cd3223d361aa07c51981df"); + let merkle_pos = 1; + let merkle_root: H256 = + H256::from_reversed_str("41f138275d13690e3c5d735e2f88eb6f1aaade1207eb09fa27a65b40711f3ae0").into(); + let merkle_nodes: Vec = vec![ + H256::from_reversed_str("73dfb53e6f49854b09d98500d4899d5c4e703c4fa3a2ddadc2cd7f12b72d4182"), + H256::from_reversed_str("4274d707b2308d39a04f2940024d382fa80d994152a50d4258f5a7feead2a563"), + ]; + let result = merkle_prove(tx_id, merkle_root, merkle_nodes, merkle_pos); + result.unwrap() + } + + #[test] + fn test_merkle_prove_inclusion_single_element() { + // https://www.blockchain.com/btc/tx/c06fbab289f723c6261d3030ddb6be121f7d2508d77862bb1e484f5cd7f92b25 + // merkle intermediate nodes single element + let tx_id: H256 = H256::from_reversed_str("c06fbab289f723c6261d3030ddb6be121f7d2508d77862bb1e484f5cd7f92b25"); + let merkle_pos = 0; + let merkle_root: H256 = + H256::from_reversed_str("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719").into(); + let merkle_nodes: Vec = vec![H256::from_reversed_str( + "5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2", + )]; + let result = merkle_prove(tx_id, merkle_root, merkle_nodes, merkle_pos); + result.unwrap() + } + + #[test] + fn test_merkle_prove_inclusion_complex() { + // https://www.blockchain.com/btc/tx/b36bced99cc459506ad2b3af6990920b12f6dc84f9c7ed0dd2c3703f94a4b692 + // merkle intermediate nodes complex merkle proof inclusion + let tx_id: H256 = H256::from_reversed_str("b36bced99cc459506ad2b3af6990920b12f6dc84f9c7ed0dd2c3703f94a4b692"); + let merkle_pos = 680; + let merkle_root: H256 = + H256::from_reversed_str("def7a26d91789069dad448cb4b68658b7ba419f9fbd28dce7fe32ed0010e55df").into(); + let merkle_nodes: Vec = vec![ + H256::from_reversed_str("39141331f2b7133e72913460384927b421ffdef3e24b88521e7ac54d30019409"), + H256::from_reversed_str("39aeb77571ee0b0cf9feb7e121938b862f3994ff1254b34559378f6f2ed8b1fb"), + H256::from_reversed_str("5815f83f4eb2423c708127ea1f47feeabcf005d4aed18701d9692925f152d0b4"), + H256::from_reversed_str("efbb90aae6875af1b05a17e53fabe79ca1655329d6e107269a190739bf9d9038"), + H256::from_reversed_str("20eb7431ae5a185e89bd2ad89956fc660392ee9d231df58600ac675734013e82"), + H256::from_reversed_str("1f1dd980e6196ec4de9037941076a6030debe466dfc177e54447171b64ea99e5"), + H256::from_reversed_str("bbc4264359bec656298e31443034fc3ff9877752b765b9665b4da1eb8a32d1ff"), + H256::from_reversed_str("71788bf5224f228f390243a2664d41d96bae97ae1e4cfbc39095448e4cd1addd"), + H256::from_reversed_str("1b24a907c86e59eb698afeb4303c00fe3ecf8425270134ed3d0e62c6991621f2"), + H256::from_reversed_str("7776b46bb148c573d5eabe1436a428f3dae484557fea6efef1da901009ca5f8f"), + H256::from_reversed_str("623a90d6122a233b265aab497b13bb64b5d354d2e2112c3f554e51bfa4e6bbd3"), + H256::from_reversed_str("3104295d99163e16405b80321238a97d02e2448bb634017e2e027281cc4af9e8"), + ]; + let result = merkle_prove(tx_id, merkle_root, merkle_nodes, merkle_pos); + result.unwrap() + } + + #[test] + fn test_block_headers_no_difficulty_check() { + // morty: 1330480, 1330481, 1330482 + let headers: Vec = vec![ + "04000000bb496ba8d09f8f98b15cdaf5798163bdd70676eb1c8b538f53ab4f83da4a27000db352177c6b5ad2499a906cec33b843fb17fc1ec298cd06c7e7ceb7b62e144232d719d14c15e565c05e84ead95a2f101a1b658ee2f36eb7ca65206e27cfca473de614625be6071f09006c286bc5ec73dd27a09bf687700c06fb04d0b9a063c0aa0746c9db170000fd40050053b27dad1f5a858b78f3154039759e985ed57db10ecb772810d7f158c55083a14b9f2ba26ae9fcb82012186e2528f67c45b7b216a69fe26232ad2d179a141b1b10e4d5f108c7b920b49348f6eef2d70b7f02cb01d8d9992f8f2d7b6608806b10ff329846b188de200aa37c73ac03f6c9b79cf5613c71b7969b4abafdbc1165ad955a049269584c83b36f36a3e9becf2fe81f3b1917475eb13ecfed3813ecc32206078d8c1e2797013dfc6f6a55e06f1c06a07959ef94d53ca0fc81d03cb6f614761156ed4ff1a8e5c9f0b96f3c8c3eeb9a0720cf4ed10397330f49b83439c5083eea1d1785a10d86ca2866d0da4ca746c49118b780c55aa6cd5b4c0491cefa258ecf129307d15e001415b203e89c008f4444b236aa556dbf4f6d05e0c57642cfa142df2f8546f1d37a6b2feaf98496892b41caefbe7dc7bcbb2755752df3dbf00ac1fc558896f14541aea4cc78ec5d00bbe5398fac4a658b1ae3399777f15117c0f3de3c63bc5b3edf6543d172cfc66907f9cf8706e97b14281daeb427801dfb0910743873265ae6bae71dbf22353c321f726e68f747965858f488dd507b7e6adee42509e5720373dce5b111b420c906b0f2cb391cfb9d581e2509da3829d6718469f383e07043694db87db0ce1196449a6c9cd941a8bde507e553c0ca534238dcc93633631926102c87cd0f83720ccff60de8b05b103e086a2c2cb7943f21033a5658235fc52708907e1ea722e726808db0270bf898c51e9dd0745614857783dc11a6dcd7760d4a07ddbd83a2e02b23fa789b79eed22dc411b9b48f71c54f12387065e3ff0638701e0f6a0dd56d0ce395d150b237b60c166352e69b92173b884446d7660f5857458b97c6d4ee54f8a1f60113aff30e54c1f7c572b85dcb7a2419d2f736a9b0a6d99ea549bd74e546251c0b8be7975e9a6d96aa3467b1dc6b024745fdef43b37cf21a657a3247d9adf8c252ef210d9a4e9c7191f698ccc9b10103b8bb811cdcf1a62903786476db8195ffb3cd004c57ad07a7a3c41eee391f66a7697e69409d7a78558720f6a1b9804d72de820b7b6165b8e14a2b1316576022423f22bb82fab16127be7173ddcd43fa7ea5c4474f79321a8c4b792caf12320c3047d026b7d63216a022e83655c2d811d2bd2a559970e9155b979953f9801ce918f690f43f5e3f07f7ce27a6837bf33b2490d9add8549f1e603a750c114bb92740cc3987cb9f948a6229f175a7b577b0b60d885a0a7ef05debe921376a7acdb25eaa8bb72e120e529cd775175012efb454cf41d240a946bf140af20d9a5dbed2e196d91a7ff33c2769f140fa0bb968111e1602221deae8d162e7a471354c2051acb43ec31015aaefa0b08bf1bddbb282e86a1caf45f3b63e4c6427ba9e99aed28ef79711794511511c52daf13b735e02b9833d3467bfd16886606d5555b7cc95ff2fea3b03c82cfe60e8602d9f70a3870f5b755573b955bb300bd3733b5ddf9a61fd3cd281af39520d6dfd8b7e2b165ec91749614a3b5241e2ea12470f91b58cf6163e02dfe79392db70cd17db9497cf59c89ac8377dbd02042f6ed270c8c2bc717623b203b74676890f5f4cd905b25772a25292d76b6f42a094c27eed13793d189e395ed3f28c5731976a7b45184acee45b3cf05a9c62045644dfe39f79cd331e282edae99cea652eb82819415ac2a5c21539cdd636fb835063ace3b6befffaf50bf6866e9b1a2b35037a330faeb18ca1696693dafd26b5f5da8dcd3e50ff09249bdda695f576d25024560b643d873d07293a80fe71998ef6ccd88c0cf9f69326b463c26fe4906faaf454ae68accd7ef3edffefdd2ede23a822a2267332f0791f1c4e6d5ab4661f279f5039b36a4476e56fd5b0461e585ff30a7c661b93f1".into(), + "04000000001f22e1bc88c53b1554f8fdcf261fdb09f4cae6ef5e5032b788515f4a60d30d67d1b35fda68abc05f5af39e5ade224a5312b8dcd1f3629a7ff33355bb7ca93e32d719d14c15e565c05e84ead95a2f101a1b658ee2f36eb7ca65206e27cfca478be6146220bb071f49000b055b22a7a4bbafd6b52efb90f963d5f80126c27e437005fb47720e0000fd4005004d9875d71c540f558813142e263f597243bdd8d8105ff3d1ffd62ae51ccf22729debe510f97ab0631701dbd34b73e570597dc8825be6bd669e693037fb701040c273b44745f4e850c2d8aeca7ccab6ef7f462206a16d75358f2e8fddf9d0dbc6333ff55b1813a37f0ba240bd2d897fbd6cfdb1989ac8f3ec93b15ae4360edf84088ac9a4ea7d3d71290532bb51675e7310be1210aa33c184d693f6f7c15c5be1e89356ae3d663d0c548fceac0974fe4cb6c6559f50643280df9508460fd04f9cde55521b4c6d61c644c6c7b7473f9e39b412e3776f5e47b6c466aaf1dc76ff2114e716eb6b9614d0c93cdc229ec13b07057a7f7446c1aac51ef0950d4361fa2d20f22f29ff490bf6d6a2a267c45d88d3152d9f5291695f2f4fba65ca9763cb4176506c73b8162611b6004af7ec8d1ea55a225cca2576e4ac84ac333b663693a2f19f7786340ad9d2212d576a0b4e7700bd7d60de88940dce1f01481f9c41350eefd7b496218bcf70c4c8922dfd18d666d37d10cb0f14dd38e1225ec179dcab5501a4434674d6f9ff9f23c4df5f445cc2accf43189fc99ac56693df373a4207b0dc991009fae4796fd7e49cea4dd139ee72264dfd47f4e1ad2420d635c7a1f37950d022ffdcccc7651b645db0ba0ce94c18dcc902279b4601806beefe05016f1f85411e6562b584da0854db2e36f602d8c4974d385aee4a01d1132082c8cd7c71443162f7d7487c73d8a46f830f72a0d352d957bef5afc33c4447ef33b2491e28000d1f4687e95ffc2b9532d28ae4c48f8551bf527dbe18c672204495f2bd546566fd5770189e28c2de0974130a492ccd8737a8c6e971d02a23c4f9f27410348d1f666f93385bdc81bad8e9a9d1dbffdfa2609ebae52740b457ecd67a3bf0db02a14f5bdf3e25b35b2d3d303094e46e0e3daef559d9f0e074e512bcaf9fcc9d035083eec16806af8a93d27b4ad46754a425b6a02b1ac22f682e48f214d66b379d7042aa39f2c5f3448d05ca4b6360e162f31f197225f4ad579d69207c666711fb3f6ca814efcf430899360cced1168cd69ec0e809a89cf2cf2015f9f895a3dadd4ced6d94793e98201b1da6a0a5d90be5d06925e3ad60b9227f84b9c3060a6db6e7857d8731f975d4a993abf10d84590da02b114625109d864de070813179b651d528f66036c30a0700ee84fc5e59757a509745b64e76fa3396f3c8b01a7724cd434e6d774dad36be8a73ad29f6859352aa15236e7825947396cb98e26b912b19ddc127590e59200c4334d1d96d7585a0e349b920f2e4e59cdedac911214c42c0894f72c8a7423d7aef3ea5ef9a5b650821f46537c65509ad8dcf6558c16c04f9877c737ff81875d9fbe01d23d37e937444cf257b0b57bc1c2a774f2e2bf5f3b0881be0e2282ba97ef6aad797f8fdb4053da4e478575805c7a93076c09847544a8e89f1cb3838df7870bcf61deb2144c6f6349c966b67545703058f9227965b97835b049538fb428431a8461586b022368626d20e9b6bfdd7232a5cc6a0aa214319cb440c45443a2446d1e17713c0e1049f0fd759d1dbff493302140376cfb153330ed455a043189260cb7d2d90333a37d3584f2d907d0a73dccee299ad14141d60d1409cda688464a13b5dab37476641741717d599a60c0ac84d85869ed449f83933ad30e2591157fd1f07b73ecf26f34e91bc00f1ca86ae34ca8231b372cdc2ed18d463ac42f92859d6f0e2c483dbb23d785f1233db2033458af9d7c1e7029ac5cc33ca7d25b2b49fd71b1ae5f5ce969b6e77333bf5fbb5e6645dd0a4d0c6e82eb534ac264ddbe28513e4b82b3578c1a6cbfaa2522aa50985fe2cce43cf3363eaacca0e09c721fd603d43c3a4fdf8dde0c9ff2c054910b16aeef7c4d86b31".into(), + "04000000fcead9a1b425124f11aa97e0614120ce87bdddcad655672916f9c4564dc057002bd3df07a4602620282b276359529114ba89b59b16bec235d584c3cf5cc6b2d132d719d14c15e565c05e84ead95a2f101a1b658ee2f36eb7ca65206e27cfca47bfe61462d5b9071f1a001daf299c51afbd74fd75a98ba49a6e40ae8ad92b3afdc1cf215fd6190000fd40050044b5e035b02d138a9704f9513c0865f2733b7c09294ee504c155c283f4895559b6ac39828eac98ad393a642330589e8849040f55ce44f8f2197529d0b0ed57ccdda41f1971e153ec28ac5b4eba968741db374104d65ee234580a83bea1c0cdb67b8bc207057486eb1d90e21ba0cd4f5e9fd834821fafc1517c5d1fceb50ba6f6b102a9b4edac46f2359aec795a4e2458f51114a41289634b3b1cf250e3e38f3689f951278dfa7202a7dfe311cc098fd4a8d02c8f8a74e4a5010b18ee2e60578d5e9f1c094433a73f26e6546e20a574fc261baaa79e9910ab86ed607786a1cc88e7de51ff928d434e26eaef1437f7068c743f26d7c0eea6791e869b101fee8ab41b50af6174c5e6b731a1719f31ee3e6529efef49f31665baedc9382e9665278a84467d479f139fc7a8ef66fef9bd2fd17f7779ee315d458f691a290fa7c2179de8bb91a78458c5290d4aa45b163254006800ba2fce7479511f744fd7de96495c39be93413d8b0b187fe092537e1a7646a66a125b33333f6ecd10085e23ad168b24ee7be69d01ea021a39401e4bd41d818499e7174dd9b85542076c78cb89eeec1c190301b4709dbc963d47926e31bb0235ba6a7029d49458150f6491ac9c973b8a2c893258f907baf4bcb7c39f12b900ba2b2382cd5dd84314ee504ade835ad9a1cb13a7f5928a483ebc9415429810fd99893f2f8f83970b8b47143d617e6f9853e4d86ff378be664218f1c32531143e209f171590dd48216fec879a6b9cbf04432bf4f1a3734b69b6a9f1a358a259a0f9082cfb6c1f3d9d2d9e4522ad651ccce565f06b30c1c0b27252270c2f6608cf4f3288a7e7d4b174e646de05341f7db62b00b5ccb295f058d34b87201148828e9b3f7e08f60e100f810be27eb7f4c471cda7621106fe78bc69ec2bd27acabd55dc094b8626913b7d24d9b60939754700f32574a733a195f8b0220d56f6797de0bcd7b80d561896b816586593409f76e85a7a1035f821dee32a02fdbc26bc4cca375bed418b9d678ac589249a1a5a5b24447ee9b42e33f817066caf3d4e17d0347f6acf0cbf426d4df49413b3d12350edec2681ab9cfecd0825ccfb2649a57391d3f153050dfb4350d60e5e464229ddd6e49ece95557b8ef48c18cbffbe9fc8d7700f611a4b33a2a254afcec638c485e36daf0364da7d4302e488db7b6c41297571048cfea5452e324abb9f9e1043e625fd0853b7e03063d1c3a43aa1ee62d45d890b5e4d10640e775cff6852b6d1acd4a503b3ece3b319cbcf33ff9fdf17b8f852d748db1e05af80507f5d0e1bc44444b155d7da20f7f0b4d6d83368c3bb9e1321b39472a8677ea1d3aca43b453d35edca37b7536d19c26b764958b3c7c30f3211d7b7bb7f6a6d7fd7bf2dda6e7d7b1e533556863549bbe1394a3828596f25029b7e30495e1235f084e5edd133bc29fce4f1e5e514eb1d1cb19fd8dfbb0d130fbec4e288f23dae86311ffd6f4afbaacc2ffe1cc8811a455ba6f5659f82515b56c6ac84277bff5bef98fefc74e002e4a11866a417a429541f8a62df4108e4730d3045f92984bcf1ab2f7d03f8bb1767e91791530cd8eec412919e1f2e341e66a1588a8f485f7aa005787af946b9cb10f6685420b7e1663f66374fddc5e70720507ee2134f3b02df042fcf6db4a5bdd74cc5010793634816fe447cc68e076b225cc1ca872929ef246ce356dc8d8964ff6d7119d071eccb6dc37f75b932c44cdc30723b8357a2761c6de6ab2713e6f6a782538cb731b07950d3f459760a00cc0af406d6848014746b02653636f479d952b46fdeff976e1d159ba46ae7363d5b0042d3905a0bda12aaa6eaae1a5a0d55d4c1930aa1c004cd610866853a247239366aa20f8968ea9ca3d5d6d7321a5d0f2c".into() + ]; + validate_headers(headers, false, false).unwrap() + } + + #[test] + fn test_block_headers_difficulty_check() { + // BTC: 724609, 724610, 724611 + let headers: Vec = vec!["00200020eab6fa183da8f9e4c761b31a67a76fa6a7658eb84c760200000000000000000063cd9585d434ec0db25894ec4b1f03735f10e31709c4395ea67c50c8378f134b972f166278100a17bfd87203".into(), + "0000402045c698413fbe8b5bf10635658d2a1cec72062798e51200000000000000000000869617420a4c95b1d3d6d012419d2b6c199cff9b68dd9a790892a4da8466fb056033166278100a1743ac4d5b".into(), + "0400e02019d733c1fd76a1fa5950de7bee9d80f107276b93a67204000000000000000000a0d1dee718f5f732c041800e9aa2c25e92be3f6de28278545388db8a6ae27df64c37166278100a170a970c19".into()]; + validate_headers(headers, true, true).unwrap() + } +} diff --git a/mm2src/mm2_bitcoin/spv_validation/src/lib.rs b/mm2src/mm2_bitcoin/spv_validation/src/lib.rs new file mode 100644 index 0000000000..c0d52eb202 --- /dev/null +++ b/mm2src/mm2_bitcoin/spv_validation/src/lib.rs @@ -0,0 +1,14 @@ +extern crate bitcoin_spv; +extern crate chain; +extern crate primitives; +extern crate rustc_hex as hex; +extern crate serialization; + +/// `types` exposes simple types for on-chain evaluation of SPV proofs +pub mod types; + +/// `helpers_validation` Override function modules from bitcoin_spv and adapt for our mm2_bitcoin library +pub mod helpers_validation; + +/// `spv_proof` Contains spv proof validation logic and data structure +pub mod spv_proof; diff --git a/mm2src/mm2_bitcoin/spv_validation/src/spv_proof.rs b/mm2src/mm2_bitcoin/spv_validation/src/spv_proof.rs new file mode 100644 index 0000000000..38e8461c88 --- /dev/null +++ b/mm2src/mm2_bitcoin/spv_validation/src/spv_proof.rs @@ -0,0 +1,93 @@ +use bitcoin_spv::btcspv::{validate_vin, validate_vout}; +use chain::BlockHeader; +use chain::RawBlockHeader; +use helpers_validation::merkle_prove; +use primitives::hash::H256; +use types::SPVError; + +#[derive(PartialEq, Clone)] +pub struct SPVProof { + /// The tx id + pub tx_id: H256, + /// The vin serialized + pub vin: Vec, + /// The vout serialized + pub vout: Vec, + /// The transaction index in the merkle tree + pub index: u64, + /// The confirming UTXO header + pub confirming_header: BlockHeader, + /// The Raw confirming UTXO Header + pub raw_header: RawBlockHeader, + /// The intermediate nodes (digests between leaf and root) + pub intermediate_nodes: Vec, +} + +/// Checks validity of an entire SPV Proof +/// +/// # Arguments +/// +/// * `self` - The SPV Proof +/// +/// # Errors +/// +/// * Errors if any of the SPV Proof elements are invalid. +/// +/// # Notes +/// Re-write with our own types based on `bitcoin_spv::std_types::SPVProof::validate` +impl SPVProof { + pub fn validate_block_header(&self) -> Result<(), SPVError> { + if self.confirming_header.hash() != self.raw_header.digest() { + return Err(SPVError::WrongDigest); + } + if self.confirming_header.merkle_root_hash != self.raw_header.extract_merkle_root() { + return Err(SPVError::WrongMerkleRoot); + } + if self.confirming_header.previous_header_hash != self.raw_header.parent() { + return Err(SPVError::WrongPrevHash); + } + Ok(()) + } + + pub fn validate(&self) -> Result<(), SPVError> { + if !validate_vin(self.vin.as_slice()) { + return Err(SPVError::InvalidVin); + } + if !validate_vout(self.vout.as_slice()) { + return Err(SPVError::InvalidVout); + } + self.validate_block_header()?; + merkle_prove( + self.tx_id, + self.confirming_header.merkle_root_hash, + self.intermediate_nodes.clone(), + self.index, + ) + } +} + +#[cfg(test)] +mod spv_proof_tests { + use chain::BlockHeader; + use chain::RawBlockHeader; + use hex::FromHex; + use serialization::deserialize; + use spv_proof::SPVProof; + + #[test] + fn test_block_header() { + let header_hex = "040000008e4e7283b71dd1572d220935db0a1654d1042e92378579f8abab67b143f93a02fa026610d2634b72ff729b9ea7850c0d2c25eeaf7a82878ca42a8e9912028863a2d8a734eb73a4dc734072dbfd12406f1e7121bfe0e3d6c10922495c44e5cc1c91185d5ee519011d0400b9caaf41d4b63a6ab55bb4e6925d46fc3adea7be37b713d3a615e7cf0000fd40050001a80fa65b9a46fdb1506a7a4d26f43e7995d69902489b9f6c4599c88f9c169605cc135258953da0d6299ada4ff81a76ad63c943261078d5dd1918f91cea68b65b7fc362e9df49ba57c2ea5c6dba91591c85eb0d59a1905ac66e2295b7a291a1695301489a3cc7310fd45f2b94e3b8d94f3051e9bbaada1e0641fcec6e0d6230e76753aa9574a3f3e28eaa085959beffd3231dbe1aeea3955328f3a973650a38e31632a4ffc7ec007a3345124c0b99114e2444b3ef0ada75adbd077b247bbf3229adcffbe95bc62daac88f96317d5768540b5db636f8c39a8529a736465ed830ab2c1bbddf523587abe14397a6f1835d248092c4b5b691a955572607093177a5911e317739187b41f4aa662aa6bca0401f1a0a77915ebb6947db686cff549c5f4e7b9dd93123b00a1ae8d411cfb13fa7674de21cbee8e9fc74e12aa6753b261eab3d9256c7c32cc9b16219dad73c61014e7d88d74d5e218f12e11bc47557347ff49a9ab4490647418d2a5c2da1df24d16dfb611173608fe4b10a357b0fa7a1918b9f2d7836c84bf05f384e1e678b2fdd47af0d8e66e739fe45209ede151a180aba1188058a0db093e30bc9851980cf6fbfa5adb612d1146905da662c3347d7e7e569a1041641049d951ab867bc0c6a3863c7667d43f596a849434958cee2b63dc8fa11bd0f38aa96df86ed66461993f64736345313053508c4e939506c08a766f5b6ed0950759f3901bbc4db3dc97e05bf20b9dda4ff242083db304a4e487ac2101b823998371542354e5d534b5b6ae6420cc19b11512108b61208f4d9a5a97263d2c060da893544dea6251bcadc682d2238af35f2b1c2f65a73b89a4e194f9e1eef6f0e5948ef8d0d2862f48fd3356126b00c6a2d3770ecd0d1a78fa34974b454f270b23d461e357c9356c19496522b59ff9d5b4608c542ff89e558798324021704b2cfe9f6c1a70906c43c7a690f16615f198d29fa647d84ce8461fa570b33e3eada2ed7d77e1f280a0d2e9f03c2e1db535d922b1759a191b417595f3c15d8e8b7f810527ff942e18443a3860e67ccba356809ecedc31c5d8db59c7e039dae4b53d126679e8ffa20cc26e8b9d229c8f6ee434ad053f5f4f5a94e249a13afb995aad82b4d90890187e516e114b168fc7c7e291b9738ea578a7bab0ba31030b14ba90b772b577806ea2d17856b0cb9e74254ba582a9f2638ea7ed2ca23be898c6108ff8f466b443537ed9ec56b8771bfbf0f2f6e1092a28a7fd182f111e1dbdd155ea82c6cb72d5f9e6518cc667b8226b5f5c6646125fc851e97cf125f48949f988ed37c4283072fc03dd1da3e35161e17f44c0e22c76f708bb66405737ef24176e291b4fc2eadab876115dc62d48e053a85f0ad132ef07ad5175b036fe39e1ad14fcdcdc6ac5b3daabe05161a72a50545dd812e0f9af133d061b726f491e904d89ee57811ef58d3bda151f577aed381963a30d91fb98dc49413300d132a7021a5e834e266b4ac982d76e00f43f5336b8e8028a0cacfa11813b01e50f71236a73a4c0d0757c1832b0680ada56c80edf070f438ab2bc587542f926ff8d3644b8b8a56c78576f127dec7aed9cb3e1bc2442f978a9df1dc3056a63e653132d0f419213d3cb86e7b61720de1aa3af4b3757a58156970da27560c6629257158452b9d5e4283dc6fe7df42d2fda3352d5b62ce5a984d912777c3b01837df8968a4d494db1b663e0e68197dbf196f21ea11a77095263dec548e2010460840231329d83978885ee2423e8b327785970e27c6c6d436157fb5b56119b19239edbb730ebae013d82c35df4a6e70818a74d1ef7a2e87c090ff90e32939f58ed24e85b492b5750fd2cd14b9b8517136b76b1cc6ccc6f6f027f65f1967a0eb4f32cd6e5d5315"; + let header_bytes: Vec = header_hex.from_hex().unwrap(); + let header: BlockHeader = deserialize(header_bytes.as_slice()).unwrap(); + let spv_proof = SPVProof { + tx_id: Default::default(), + vin: vec![], + vout: vec![], + index: 0, + confirming_header: header, + raw_header: RawBlockHeader::new(header_bytes).unwrap(), + intermediate_nodes: vec![], + }; + spv_proof.validate_block_header().unwrap() + } +} diff --git a/mm2src/mm2_bitcoin/spv_validation/src/types.rs b/mm2src/mm2_bitcoin/spv_validation/src/types.rs new file mode 100644 index 0000000000..4840f7556b --- /dev/null +++ b/mm2src/mm2_bitcoin/spv_validation/src/types.rs @@ -0,0 +1,97 @@ +use chain::RawHeaderError; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SPVError { + /// Overran a checked read on a slice + ReadOverrun, + /// Attempted to parse a CompactInt without enough bytes + BadCompactInt, + /// Called `extract_op_return_data` on an output without an op_return. + MalformattedOpReturnOutput, + /// `extract_hash` identified a SH output prefix without a SH postfix. + MalformattedP2SHOutput, + /// `extract_hash` identified a PKH output prefix without a PKH postfix. + MalformattedP2PKHOutput, + /// `extract_hash` identified a Witness output with a bad length tag. + MalformattedWitnessOutput, + /// `extract_hash` could not identify the output type. + MalformattedOutput, + /// Unable to get target from block header + UnableToGetTarget, + /// Unable to get block header from network or storage + UnableToGetHeader, + /// Unable to deserialize raw block header from electrum to concrete type + MalformattedHeader, + /// Header not exactly 80 bytes. + WrongLengthHeader, + /// Header chain changed difficulties unexpectedly + UnexpectedDifficultyChange, + /// Header does not meet its own difficulty target. + InsufficientWork, + /// Header in chain does not correctly reference parent header. + InvalidChain, + /// When validating a `BitcoinHeader`, the `hash` field is not the digest + /// of the raw header. + WrongDigest, + /// When validating a `BitcoinHeader`, the `merkle_root` field does not + /// match the root found in the raw header. + WrongMerkleRoot, + /// When validating a `BitcoinHeader`, the `prevhash` field does not + /// match the parent hash found in the raw header. + WrongPrevHash, + /// A `vin` (transaction input vector) is malformatted. + InvalidVin, + /// A `vout` (transaction output vector) is malformatted or empty. + InvalidVout, + /// When validating an `SPVProof`, the `tx_id` field is not the digest + /// of the `version`, `vin`, `vout`, and `locktime`. + WrongTxID, + /// When validating an `SPVProof`, the `intermediate_nodes` is not a valid + /// merkle proof connecting the `tx_id_le` to the `confirming_header`. + BadMerkleProof, + /// Unable to get merkle tree from network or storage + UnableToGetMerkle, + /// TxOut's reported length does not match passed-in byte slice's length + OutputLengthMismatch, + /// Unable to retrieve block height / block height is zero. + InvalidHeight, + /// Block Header Not Verified / Verification failed + BlockHeaderNotVerified, + /// Any other error + UnknownError, +} + +impl From for SPVError { + fn from(e: bitcoin_spv::types::SPVError) -> Self { + match e { + bitcoin_spv::types::SPVError::ReadOverrun => SPVError::ReadOverrun, + bitcoin_spv::types::SPVError::BadCompactInt => SPVError::BadCompactInt, + bitcoin_spv::types::SPVError::MalformattedOpReturnOutput => SPVError::MalformattedOpReturnOutput, + bitcoin_spv::types::SPVError::MalformattedP2SHOutput => SPVError::MalformattedP2SHOutput, + bitcoin_spv::types::SPVError::MalformattedP2PKHOutput => SPVError::MalformattedP2PKHOutput, + bitcoin_spv::types::SPVError::MalformattedWitnessOutput => SPVError::MalformattedWitnessOutput, + bitcoin_spv::types::SPVError::MalformattedOutput => SPVError::MalformattedOutput, + bitcoin_spv::types::SPVError::WrongLengthHeader => SPVError::WrongLengthHeader, + bitcoin_spv::types::SPVError::UnexpectedDifficultyChange => SPVError::UnexpectedDifficultyChange, + bitcoin_spv::types::SPVError::InsufficientWork => SPVError::InsufficientWork, + bitcoin_spv::types::SPVError::InvalidChain => SPVError::InvalidChain, + bitcoin_spv::types::SPVError::WrongDigest => SPVError::WrongDigest, + bitcoin_spv::types::SPVError::WrongMerkleRoot => SPVError::WrongMerkleRoot, + bitcoin_spv::types::SPVError::WrongPrevHash => SPVError::WrongPrevHash, + bitcoin_spv::types::SPVError::InvalidVin => SPVError::InvalidVin, + bitcoin_spv::types::SPVError::InvalidVout => SPVError::InvalidVout, + bitcoin_spv::types::SPVError::WrongTxID => SPVError::WrongTxID, + bitcoin_spv::types::SPVError::BadMerkleProof => SPVError::BadMerkleProof, + bitcoin_spv::types::SPVError::OutputLengthMismatch => SPVError::OutputLengthMismatch, + bitcoin_spv::types::SPVError::UnknownError => SPVError::UnknownError, + } + } +} + +impl From for SPVError { + fn from(e: RawHeaderError) -> Self { + match e { + RawHeaderError::WrongLengthHeader { .. } => SPVError::WrongLengthHeader, + } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4566e0b2cb..6c54b7e3e3 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2021-12-16" +channel = "nightly-2022-02-01" components = ["rustfmt"]