From 4483a93f643afde3a048f826e470bd09fe68be1f Mon Sep 17 00:00:00 2001 From: nakul1010 Date: Sun, 30 Jul 2023 15:43:03 +0530 Subject: [PATCH 1/2] fix: merge service crate into vault --- Cargo.lock | 434 ++++-------------- Cargo.toml | 10 +- bitcoin/src/electrs/error.rs | 3 + bitcoin/src/electrs/mod.rs | 8 + bitcoin/src/error.rs | 2 + bitcoin/src/iter.rs | 2 +- bitcoin/src/lib.rs | 35 +- bitcoin/src/light/mod.rs | 25 +- docker-compose.yml | 2 +- faucet/Cargo.toml | 2 +- faucet/src/main.rs | 1 + faucet/src/service.rs | 48 ++ runtime/Cargo.toml | 15 +- runtime/metadata-parachain-interlay.scale | Bin 278918 -> 277198 bytes runtime/metadata-parachain-kintsugi.scale | Bin 278918 -> 296702 bytes runtime/src/integration/bitcoin_simulator.rs | 49 +- runtime/src/integration/mod.rs | 3 +- runtime/src/lib.rs | 12 +- runtime/src/rpc.rs | 94 ++-- runtime/src/tests.rs | 9 +- runtime/src/types.rs | 10 +- runtime/src/utils/account_id.rs | 2 - service/Cargo.toml | 30 -- service/src/error.rs | 38 -- vault/Cargo.toml | 7 +- vault/src/cancellation.rs | 12 +- {service => vault}/src/cli.rs | 5 +- .../lib.rs => vault/src/connection_manger.rs | 54 +-- vault/src/delay.rs | 4 +- vault/src/error.rs | 26 +- vault/src/execution.rs | 50 +- vault/src/issue.rs | 26 +- vault/src/lib.rs | 8 + vault/src/main.rs | 18 +- vault/src/metrics.rs | 47 +- vault/src/process.rs | 41 +- vault/src/redeem.rs | 11 +- vault/src/refund.rs | 4 +- vault/src/relay/backing.rs | 2 +- vault/src/relay/mod.rs | 10 +- vault/src/replace.rs | 17 +- vault/src/system.rs | 59 +-- {service => vault}/src/trace.rs | 0 vault/tests/vault_integration_tests.rs | 15 +- 44 files changed, 516 insertions(+), 734 deletions(-) create mode 100644 faucet/src/service.rs delete mode 100644 service/Cargo.toml delete mode 100644 service/src/error.rs rename {service => vault}/src/cli.rs (93%) rename service/src/lib.rs => vault/src/connection_manger.rs (86%) rename {service => vault}/src/trace.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index a95cee548..43eb513de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,13 +191,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -367,19 +367,17 @@ dependencies = [ [[package]] name = "bitcoin" version = "1.2.0" -source = "git+https://github.com/interlay/interbtc?rev=2ec5b959a547f28d7bab7799568321c9cff40343#2ec5b959a547f28d7bab7799568321c9cff40343" +source = "git+https://github.com/interlay/interbtc?rev=1b90616692dcce55749616b4c60bdab228279909#1b90616692dcce55749616b4c60bdab228279909" dependencies = [ "bitcoin_hashes 0.7.6", - "frame-support", "hex", "impl-serde 0.3.2", "parity-scale-codec", + "primitive-types", "scale-info", "secp256k1 0.20.1", "serde", "sha2 0.8.2", - "sp-core", - "sp-std 5.0.0", "spin 0.7.1", ] @@ -546,26 +544,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd769563b4ea2953e2825c9e6b7470a5f55f67e0be00030bf3e390a2a6071f64" -[[package]] -name = "btc-relay" -version = "1.2.0" -source = "git+https://github.com/interlay/interbtc?rev=2ec5b959a547f28d7bab7799568321c9cff40343#2ec5b959a547f28d7bab7799568321c9cff40343" -dependencies = [ - "bitcoin 1.2.0", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-timestamp", - "parity-scale-codec", - "scale-info", - "security", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 5.0.0", -] - [[package]] name = "bumpalo" version = "3.13.0" @@ -584,12 +562,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - [[package]] name = "byteorder" version = "1.4.3" @@ -680,9 +652,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.17" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", @@ -691,9 +663,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.17" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", @@ -710,7 +682,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -957,7 +929,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -979,7 +951,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -1117,9 +1089,9 @@ checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "ecdsa" -version = "0.16.7" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ "der", "digest 0.10.7", @@ -1166,9 +1138,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -1251,9 +1223,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1303,12 +1275,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "faucet" @@ -1331,10 +1300,10 @@ dependencies = [ "serde", "serde_json", "serial_test 0.9.0", - "service", "sp-keyring", "thiserror", "tokio", + "tracing", "url 2.4.0", ] @@ -1424,31 +1393,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" -[[package]] -name = "frame-benchmarking" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech//substrate?branch=polkadot-v0.9.42#ff24c60ac7d9f87727ecdd0ded9a80c56e4f4b65" -dependencies = [ - "frame-support", - "frame-support-procedural", - "frame-system", - "linregress", - "log", - "parity-scale-codec", - "paste", - "scale-info", - "serde", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-io", - "sp-runtime", - "sp-runtime-interface", - "sp-std 5.0.0", - "sp-storage", - "static_assertions", -] - [[package]] name = "frame-metadata" version = "15.1.0" @@ -1507,7 +1451,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -1519,7 +1463,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -1529,25 +1473,7 @@ source = "git+https://github.com/paritytech//substrate?branch=polkadot-v0.9.42#f dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", -] - -[[package]] -name = "frame-system" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech//substrate?branch=polkadot-v0.9.42#ff24c60ac7d9f87727ecdd0ded9a80c56e4f4b65" -dependencies = [ - "frame-support", - "log", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 5.0.0", - "sp-version", - "sp-weights", + "syn 2.0.27", ] [[package]] @@ -1641,7 +1567,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -1784,9 +1710,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df" +checksum = "aca8bbd8e0707c1887a8bbb7e6b40e228f251ff5d62c8220a4a7a53c73aff006" dependencies = [ "aho-corasick", "bstr", @@ -2257,19 +2183,17 @@ dependencies = [ [[package]] name = "interbtc-primitives" version = "1.2.0" -source = "git+https://github.com/interlay/interbtc?rev=2ec5b959a547f28d7bab7799568321c9cff40343#2ec5b959a547f28d7bab7799568321c9cff40343" +source = "git+https://github.com/interlay/interbtc?rev=1b90616692dcce55749616b4c60bdab228279909#1b90616692dcce55749616b4c60bdab228279909" dependencies = [ "bitcoin 1.2.0", "bstringify", "parity-scale-codec", + "primitive-types", "scale-decode", "scale-encode", "scale-info", "serde", - "sp-core", "sp-runtime", - "sp-std 5.0.0", - "xcm", ] [[package]] @@ -2704,24 +2628,15 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "pkg-config", "vcpkg", ] -[[package]] -name = "linregress" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de0b5f52a9f84544d268f5fabb71b38962d6aa3c6600b8bcd27d44ccf9c9c45" -dependencies = [ - "nalgebra 0.32.3", -] - [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -2945,29 +2860,13 @@ checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" dependencies = [ "approx", "matrixmultiply", - "nalgebra-macros 0.1.0", + "nalgebra-macros", "num-complex 0.4.3", "num-rational 0.4.1", "num-traits", "rand 0.8.5", "rand_distr", - "simba 0.5.1", - "typenum", -] - -[[package]] -name = "nalgebra" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" -dependencies = [ - "approx", - "matrixmultiply", - "nalgebra-macros 0.2.1", - "num-complex 0.4.3", - "num-rational 0.4.1", - "num-traits", - "simba 0.8.1", + "simba", "typenum", ] @@ -2982,17 +2881,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "nalgebra-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -3209,9 +3097,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", @@ -3289,7 +3177,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -3333,37 +3221,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "oracle-rpc-runtime-api" -version = "1.2.0" -source = "git+https://github.com/interlay/interbtc?rev=2ec5b959a547f28d7bab7799568321c9cff40343#2ec5b959a547f28d7bab7799568321c9cff40343" -dependencies = [ - "frame-support", - "parity-scale-codec", - "scale-info", - "serde", - "sp-api", - "sp-std 5.0.0", -] - -[[package]] -name = "pallet-timestamp" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech//substrate?branch=polkadot-v0.9.42#ff24c60ac7d9f87727ecdd0ded9a80c56e4f4b65" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-inherents", - "sp-io", - "sp-runtime", - "sp-std 5.0.0", - "sp-timestamp", -] - [[package]] name = "parity-scale-codec" version = "3.6.4" @@ -3504,7 +3361,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -3631,7 +3488,7 @@ checksum = "0e99670bafb56b9a106419397343bdbc8b8742c3cc449fec6345f86173f47cd4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -3713,9 +3570,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -3901,22 +3758,22 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1641819477c319ef452a075ac34a4be92eb9ba09f6841f62d594d50fdcf0bf6b" +checksum = "61ef7e18e8841942ddb1cf845054f8008410030a3997875d9e49b7a363063df1" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bf53dad9b6086826722cdc99140793afd9f62faa14a1ad07eb4f955e7a7216" +checksum = "2dfaf0c85b766276c797f3791f5bc6d5bd116b41d53049af2789666b0c0bc9fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -3927,7 +3784,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", + "regex-automata 0.3.4", "regex-syntax 0.7.4", ] @@ -3942,9 +3799,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", @@ -4082,7 +3939,6 @@ dependencies = [ "bitcoin 1.1.0", "bitcoin 1.2.0", "blake2", - "btc-relay", "cfg-if 1.0.0", "clap", "env_logger 0.8.4", @@ -4092,7 +3948,6 @@ dependencies = [ "jsonrpsee", "lazy_static", "log", - "oracle-rpc-runtime-api", "parity-scale-codec", "prometheus", "rand 0.7.3", @@ -4226,15 +4081,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "safe_arch" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" -dependencies = [ - "bytemuck", -] - [[package]] name = "scale-bits" version = "0.3.0" @@ -4486,28 +4332,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security" -version = "1.2.0" -source = "git+https://github.com/interlay/interbtc?rev=2ec5b959a547f28d7bab7799568321c9cff40343#2ec5b959a547f28d7bab7799568321c9cff40343" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "serde", - "sha2 0.8.2", - "sp-core", - "sp-runtime", - "sp-std 5.0.0", -] - [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -4518,9 +4347,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -4540,29 +4369,29 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.173" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" +checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.173" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -4629,30 +4458,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", -] - -[[package]] -name = "service" -version = "1.1.0" -dependencies = [ - "async-trait", - "bitcoin 1.1.0", - "clap", - "futures 0.3.28", - "governor", - "hyper", - "hyper-tls", - "nonzero_ext", - "runtime", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "tracing-subscriber", - "warp", + "syn 2.0.27", ] [[package]] @@ -4799,19 +4605,6 @@ dependencies = [ "paste", ] -[[package]] -name = "simba" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" -dependencies = [ - "approx", - "num-complex 0.4.3", - "num-traits", - "paste", - "wide", -] - [[package]] name = "slab" version = "0.4.8" @@ -4899,7 +4692,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5010,7 +4803,7 @@ dependencies = [ "proc-macro2", "quote", "sp-core-hashing 5.0.0", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5020,7 +4813,7 @@ source = "git+https://github.com/paritytech//substrate?branch=polkadot-v0.9.42#f dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5169,7 +4962,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5229,21 +5022,6 @@ dependencies = [ "sp-std 5.0.0", ] -[[package]] -name = "sp-timestamp" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech//substrate?branch=polkadot-v0.9.42#ff24c60ac7d9f87727ecdd0ded9a80c56e4f4b65" -dependencies = [ - "async-trait", - "futures-timer", - "log", - "parity-scale-codec", - "sp-inherents", - "sp-runtime", - "sp-std 5.0.0", - "thiserror", -] - [[package]] name = "sp-tracing" version = "6.0.0" @@ -5304,7 +5082,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5399,7 +5177,7 @@ checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" dependencies = [ "approx", "lazy_static", - "nalgebra 0.27.1", + "nalgebra", "num-traits", "rand 0.8.5", ] @@ -5498,7 +5276,7 @@ dependencies = [ "quote", "scale-info", "subxt-metadata", - "syn 2.0.26", + "syn 2.0.27", "thiserror", "tokio", ] @@ -5512,7 +5290,7 @@ dependencies = [ "darling 0.20.3", "proc-macro-error", "subxt-codegen", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5541,9 +5319,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" dependencies = [ "proc-macro2", "quote", @@ -5604,15 +5382,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ - "autocfg", "cfg-if 1.0.0", "fastrand", "redox_syscall 0.3.5", - "rustix 0.37.23", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -5633,22 +5410,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5733,7 +5510,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -5873,7 +5650,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] [[package]] @@ -6109,6 +5886,7 @@ name = "vault" version = "1.1.0" dependencies = [ "async-trait", + "backoff", "bitcoin 1.1.0", "clap", "faucet", @@ -6117,6 +5895,8 @@ dependencies = [ "git-version", "governor", "hex", + "hyper", + "hyper-tls", "jsonrpc-core", "jsonrpc-core-client", "lazy_static", @@ -6129,7 +5909,6 @@ dependencies = [ "serde", "serde_json", "serial_test 0.9.0", - "service", "sha2 0.8.2", "signal-hook", "signal-hook-tokio", @@ -6142,6 +5921,7 @@ dependencies = [ "tracing", "tracing-futures", "tracing-subscriber", + "warp", ] [[package]] @@ -6235,7 +6015,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", "wasm-bindgen-shared", ] @@ -6269,7 +6049,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6484,16 +6264,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "wide" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "winapi" version = "0.3.9" @@ -6683,9 +6453,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" dependencies = [ "memchr", ] @@ -6708,32 +6478,6 @@ dependencies = [ "tap", ] -[[package]] -name = "xcm" -version = "0.9.42" -source = "git+https://github.com/paritytech//polkadot?branch=release-v0.9.42#6f991987c0b4cbbd7d4badc9ef08d83da5fefbfd" -dependencies = [ - "bounded-collections", - "derivative", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "scale-info", - "sp-weights", - "xcm-procedural", -] - -[[package]] -name = "xcm-procedural" -version = "0.9.42" -source = "git+https://github.com/paritytech//polkadot?branch=release-v0.9.42#6f991987c0b4cbbd7d4badc9ef08d83da5fefbfd" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "syn 2.0.26", -] - [[package]] name = "yap" version = "0.10.0" @@ -6757,5 +6501,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.27", ] diff --git a/Cargo.toml b/Cargo.toml index 5966bedbd..330aa1abd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "vault", "bitcoin", "faucet", - "service", "runner" ] @@ -18,13 +17,10 @@ sp-keyring = { git = "https://github.com/paritytech//substrate", branch = "polka sp-runtime = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } [patch."https://github.com/paritytech/substrate"] -frame-benchmarking = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } frame-support = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } frame-support-procedural = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } frame-support-procedural-tools = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } frame-support-procedural-tools-derive = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } -frame-system = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } -pallet-timestamp = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-api = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-application-crypto = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-arithmetic = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } @@ -42,12 +38,8 @@ sp-staking = { git = "https://github.com/paritytech//substrate", branch = "polka sp-state-machine = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-std = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-storage = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } -sp-timestamp = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-tracing = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-trie = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-version = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } sp-wasm-interface = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } -sp-weights = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } - -[patch."https://github.com/paritytech/polkadot"] -xcm = { git = "https://github.com/paritytech//polkadot", branch = "release-v0.9.42" } \ No newline at end of file +sp-weights = { git = "https://github.com/paritytech//substrate", branch = "polkadot-v0.9.42" } \ No newline at end of file diff --git a/bitcoin/src/electrs/error.rs b/bitcoin/src/electrs/error.rs index 39bef49b8..156631b2a 100644 --- a/bitcoin/src/electrs/error.rs +++ b/bitcoin/src/electrs/error.rs @@ -35,6 +35,9 @@ pub enum Error { TryFromIntError(#[from] TryFromIntError), #[error("ParseIntError: {0}")] ParseIntError(#[from] ParseIntError), + + #[error("No txids in block")] + EmptyBlock, } impl Error { diff --git a/bitcoin/src/electrs/mod.rs b/bitcoin/src/electrs/mod.rs index 7973d77b8..536d3defe 100644 --- a/bitcoin/src/electrs/mod.rs +++ b/bitcoin/src/electrs/mod.rs @@ -136,6 +136,14 @@ impl ElectrsClient { Ok(txs) } + pub(crate) async fn get_coinbase_txid(&self, block_hash: &BlockHash) -> Result { + self.get_and_decode::>(&format!("/block/{block_hash}/txids")) + .await? + .first() + .ok_or(Error::EmptyBlock) + .and_then(|raw_txid| Ok(Txid::from_str(raw_txid)?)) + } + pub(crate) async fn get_block(&self, hash: &BlockHash) -> Result { let (header, txdata) = try_join(self.get_block_header(hash), self.get_transactions_in_block(hash)).await?; Ok(Block { header, txdata }) diff --git a/bitcoin/src/error.rs b/bitcoin/src/error.rs index 6835732f7..119cfb8c9 100644 --- a/bitcoin/src/error.rs +++ b/bitcoin/src/error.rs @@ -77,6 +77,8 @@ pub enum Error { FailedToConstructWalletName, #[error("AddressError: {0}")] AddressError(#[from] AddressError), + #[error("Failed to fetch coinbase tx")] + CoinbaseFetchingFailure, } impl Error { diff --git a/bitcoin/src/iter.rs b/bitcoin/src/iter.rs index 6bbd137d7..7f50ca51a 100644 --- a/bitcoin/src/iter.rs +++ b/bitcoin/src/iter.rs @@ -292,7 +292,7 @@ mod tests { Block { txdata: transactions.into_iter().map(dummy_tx).collect(), header: BlockHeader { - version: Version::from_consensus(4), + version: Version::from_consensus(2), bits: CompactTarget::from_consensus(0), nonce: 0, time: 0, diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 536ec32aa..cfc29f405 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -113,14 +113,21 @@ fn get_exponential_backoff() -> ExponentialBackoff { } } +#[derive(PartialEq, Eq, PartialOrd, Clone, Debug)] +pub struct RawTransactionProof { + pub user_tx_proof: Vec, + pub raw_user_tx: Vec, + pub coinbase_tx_proof: Vec, + pub raw_coinbase_tx: Vec, +} + #[derive(PartialEq, Eq, PartialOrd, Clone, Copy, Debug)] pub struct SatPerVbyte(pub u64); #[derive(Debug, Clone)] pub struct TransactionMetadata { pub txid: Txid, - pub proof: Vec, - pub raw_tx: Vec, + pub proof: RawTransactionProof, pub block_height: u32, pub block_hash: BlockHash, pub fee: Option, @@ -885,19 +892,29 @@ impl BitcoinCoreApi for BitcoinCore { .await?; let proof = retry(get_exponential_backoff(), || async { - Ok(self.get_proof(txid, &block_hash).await?) - }) - .await?; - - let raw_tx = retry(get_exponential_backoff(), || async { - Ok(self.get_raw_tx(&txid, &block_hash).await?) + // fetch coinbase info.. + let block = self.get_block(&block_hash).await?; + let coinbase_tx = block.coinbase().ok_or(Error::CoinbaseFetchingFailure)?; + let coinbase_txid = coinbase_tx.txid(); + let coinbase_tx_proof = self.get_proof(coinbase_txid, &block_hash).await?; + let raw_coinbase_tx = self.get_raw_tx(&coinbase_txid, &block_hash).await?; + + // fetch user tx info.. + let raw_user_tx = self.get_raw_tx(&txid, &block_hash).await?; + let user_tx_proof = self.get_proof(txid, &block_hash).await?; + + Ok(RawTransactionProof { + raw_coinbase_tx, + coinbase_tx_proof, + raw_user_tx, + user_tx_proof, + }) }) .await?; Ok(TransactionMetadata { txid, proof, - raw_tx, block_height, block_hash, fee, diff --git a/bitcoin/src/light/mod.rs b/bitcoin/src/light/mod.rs index 9b94ef711..b432a7467 100644 --- a/bitcoin/src/light/mod.rs +++ b/bitcoin/src/light/mod.rs @@ -7,7 +7,7 @@ pub use error::Error; use async_trait::async_trait; use backoff::future::retry; -use futures::future::{join_all, try_join, try_join_all}; +use futures::future::{join_all, try_join, try_join4, try_join_all}; use std::{convert::TryFrom, sync::Arc, time::Duration}; use tokio::{sync::Mutex, time::sleep}; @@ -82,6 +82,10 @@ impl BitcoinLight { let txid = self.electrs.send_transaction(transaction.transaction).await?; Ok(txid) } + + async fn get_coinbase_txid(&self, block_hash: &BlockHash) -> Result { + Ok(self.electrs.get_coinbase_txid(&block_hash).await?) + } } #[async_trait] @@ -236,15 +240,28 @@ impl BitcoinCoreApi for BitcoinLight { }) .await?; - let (proof, raw_tx) = retry(get_exponential_backoff(), || async { - Ok(try_join(self.get_proof(txid, &block_hash), self.get_raw_tx(&txid, &block_hash)).await?) + let proof = retry(get_exponential_backoff(), || async { + let coinbase_txid = self.get_coinbase_txid(&block_hash).await?; + + let (coinbase_tx_proof, raw_coinbase_tx, user_tx_proof, raw_user_tx) = try_join4( + self.get_proof(coinbase_txid, &block_hash), + self.get_raw_tx(&coinbase_txid, &block_hash), + self.get_proof(txid, &block_hash), + self.get_raw_tx(&txid, &block_hash), + ) + .await?; + Ok(RawTransactionProof { + coinbase_tx_proof, + raw_coinbase_tx, + user_tx_proof, + raw_user_tx, + }) }) .await?; Ok(TransactionMetadata { txid, proof, - raw_tx, block_height, block_hash, fee: Some(fee), diff --git a/docker-compose.yml b/docker-compose.yml index 5d4db6d44..d99f652ca 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: interbtc: - image: "interlayhq/interbtc:1.25.0-rc1" + image: "interlayhq/interbtc:1.25.0-rc4" command: - --rpc-external - --ws-external diff --git a/faucet/Cargo.toml b/faucet/Cargo.toml index 54b995ac0..da8d24254 100644 --- a/faucet/Cargo.toml +++ b/faucet/Cargo.toml @@ -30,13 +30,13 @@ async-trait = "0.1.40" futures = "0.3.5" git-version = "0.3.4" lazy_static = "1.4.0" +tracing = { version = "0.1", features = ["log"] } reqwest = { version = "0.11.11", features = ["json"] } url = "2.2.2" # Workspace dependencies runtime = { path = "../runtime" } -service = { path = "../service" } [dev-dependencies] serial_test = "0.9.0" diff --git a/faucet/src/main.rs b/faucet/src/main.rs index 9f6baad21..1956f8fda 100644 --- a/faucet/src/main.rs +++ b/faucet/src/main.rs @@ -1,5 +1,6 @@ mod error; mod http; +mod service; use clap::Parser; use error::Error; diff --git a/faucet/src/service.rs b/faucet/src/service.rs new file mode 100644 index 000000000..a1d34c84e --- /dev/null +++ b/faucet/src/service.rs @@ -0,0 +1,48 @@ +use futures::{future::Either, Future, FutureExt}; +pub use runtime::{ShutdownReceiver, ShutdownSender}; + +pub enum TerminationStatus { + Cancelled, + Completed(Res), +} + +pub async fn on_shutdown(shutdown_tx: ShutdownSender, future2: impl Future) { + let mut shutdown_rx = shutdown_tx.subscribe(); + let future1 = shutdown_rx.recv().fuse(); + + let _ = future1.await; + future2.await; +} + +async fn run_cancelable(mut shutdown_rx: ShutdownReceiver, future2: F) -> TerminationStatus +where + F: Future, +{ + let future1 = shutdown_rx.recv().fuse(); + let future2 = future2.fuse(); + + futures::pin_mut!(future1); + futures::pin_mut!(future2); + + match futures::future::select(future1, future2).await { + Either::Left((_, _)) => TerminationStatus::Cancelled, + Either::Right((res, _)) => TerminationStatus::Completed(res), + } +} + +pub async fn wait_or_shutdown(shutdown_tx: ShutdownSender, future2: F) -> Result<(), E> +where + F: Future>, +{ + match run_cancelable(shutdown_tx.subscribe(), future2).await { + TerminationStatus::Cancelled => { + tracing::trace!("Received shutdown signal"); + Ok(()) + } + TerminationStatus::Completed(res) => { + tracing::trace!("Sending shutdown signal"); + let _ = shutdown_tx.send(()); + res + } + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index cdff1a98c..170ed773b 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -52,23 +52,14 @@ rand = { version = "0.7", optional = true } [dependencies.primitives] git = "https://github.com/interlay/interbtc" -rev = "2ec5b959a547f28d7bab7799568321c9cff40343" +rev = "1b90616692dcce55749616b4c60bdab228279909" package = "interbtc-primitives" [dependencies.module-bitcoin] git = "https://github.com/interlay/interbtc" -rev = "2ec5b959a547f28d7bab7799568321c9cff40343" +rev = "1b90616692dcce55749616b4c60bdab228279909" package = "bitcoin" - -[dependencies.module-btc-relay] -git = "https://github.com/interlay/interbtc" -rev = "2ec5b959a547f28d7bab7799568321c9cff40343" -package = "btc-relay" - -[dependencies.module-oracle-rpc-runtime-api] -git = "https://github.com/interlay/interbtc" -rev = "2ec5b959a547f28d7bab7799568321c9cff40343" -package = "oracle-rpc-runtime-api" +features = ["parser"] [dev-dependencies] runtime = { path = ".", features = ["testing-utils"] } diff --git a/runtime/metadata-parachain-interlay.scale b/runtime/metadata-parachain-interlay.scale index 28dffb7de1102927a18a3a5bff7e98b4d75012b5..2ed1ce0d5ddb0c039d0bcc3cd31c2d6a2e22026b 100644 GIT binary patch delta 15854 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 delta 17138 zcmdUXdt6mj`uBd;+J|!v7lq((Q7#7s1O){J14RW96BX}x$rO)pl#|>qDV3=+Ic1Ym zxU)4*S<+-Pq*TN>J2Fe0oU*iH(u#^En{2YObc)Iv>;101xtaau^ZqfP-}`y}9QIz< zXFcm#&syubZC~m3|J5%8yvp3Qu0gw6yguS|K|9AIsJqoiby}!FOSC7^0qt6Q40ULm z@I0kGY>%TZ?PdE&I<0+eA5CYpSnttvPFv)is&*6oTWpD?KAT)L>8@t#PRv}m8P z%)+z+pG4Kp=o#(pJEF9QeWFx9Mh}aY={!t(-zQoPV^pF|^c|`>eFsyMmh3xDZDzDb zOkTu8wN1Ve)S~V79SdY$^NpuA?IND1v|)ZpbXJ?^mrUncxA;ZQUu2uMz+F^vgS&cq zbwycjb#+B`jg8{V-Rlc$YF)K;HCu_|7aFOYiek4y=|!b(SGBvi&~3EcMwGtTU6g6` zlr1JJilXgVIZk6Ymu=cgzRntVQC+pCwmzt~zS3QD87y1)g1Yi@Px)H(2{Irkq$u0) z$F`HQ{ff>peWNI3^Pf{u?ncMdxz#S*0#C}?;+Z$eYa{h z?QXrs<@BPxTFKfML9LXM)zmCb@24=WxnxOiGSXAnn-rco)FP@Lp=ix|eHnn%UGE&R zi)hjEy7Y<8c?DBv=j9a6%3W5tFz?#j6^gP81M_I$V^el(&s-my&`9JzBV~Ltiu~t| zT{uHk`btP~&KtXA-mDonB%@UGUKd4;+Q@YYz;4mHF~geBb9F^UDSF1w_Egn*ie0tt zVmY%W{8<#GS=+mATtW+}p>r#1Jr(7ux`>*opCtYZE6Uue(u~&l9nh$B7PX1bAEQLA zzBGr9YKKaT=!6zs_K2em4cH-4@KaPEZ6QngB zoC@x~z5ILHt=(GTq@AsME5aGL>qynhAoOplG1Jc025`*swQkuE2)< zbaNh2Q)}8U8aZ8TeRFFhW0y!ojpq?s(CwobvDWO{uOmA#kN{+7t&eO=CT3@?Z{2Y# zXMRjH2C!k;=HKj~fY!jfCJ;GV=iGeg)uQdoCRvW?LLF9 zi^;C7b(d9QNmV66%h|bwqFayRNqMc;2#bZvwB$#^TG!majnj7R{DTitQtRD&UX*Z` zJ@f{rvex8B77-==e(N9b)PD8HRy-#(?Zfk~c$Y3XF_=U7RB1g0ct-up*Wl6YwvOTa5C zgN>B0To$D@?{~61CRXLL5aF9i-rD*7c5@;kGM|M3x1{Ddc;4Kc$fB9Zn!utg>pbPf zRoW}f<5?Zk1Lm+)qIzxO<4Lqp3wXkz-SYS(BNZ&Zn8$`<8-D)yFr#1y;3Pf~N=;(k zJT?kNRZpZ+v}l>fMqJimN6RPxOl*lTN`thOEy0u~HqU3_Y#S4mGub!*eW4{nB6MLs zOU7K1rmzrg^pn5A2#21G!n5m1r#THRq}8A0GZB!57Fn%fL&kKk9-KH2jNq=V$tzz|p(+JhcH1OOe^Z-dTs|9k1kGnx96|E|8ApeFsW$%r zhTeM)rZ|={bsSLE4c+3I9`N;8j6I6?Nsc0XUKB z#*LMy+g1bWO*w1~ZPV61n`zz7Y`z2DZ2VZuP%NU<#*rz$`> z^M-MkR{>;$_T95Fkvo5mWp;ulY!_2@X_?Q(4d4A!c4_=6yX@AsKDPvyPuFwPgZKU% zo9xvx4x4Ner@ejHft&OE;m>fibhSUjnwiL6!lH2m`f4v8d59% z#zKd*{jY{<|9Yj34z=!o^-GnSwLK@D)ULIk3=YBl=f42=q(X@`b(NK+^@Y{QHEW-r z3_&}uQ;(zjpHHQs^=GGKAHO%k!rE~*qK~ISq2!{{iW+y}8dp(mMRmJ2<&8-6TKPs& z>VP)!5@KN)gq+f&jHcz4*Sf3SHMMfod|9Yakd&F+9olnmEXNQ--yDOxcGjC?Mjc}m zGqbL&(rgA{C#SCZ2HemmKm{Y&(_c}JX?x!sg`qp%44l|MrIHJ2^WgA01b^cn3i)G|Yr0x9KKjLfsD1sPzxfZf^=BO_1m(NV-Hl7z`FX+MZiz^;a#6nS zQ7&n>ets<@uD$AJ1jvF^#i6;rybhA!vtP!sFs?NY4%L46(vcp)6<-Q1uc$6_l}2z<7lF4bD&61$ zP`j@vrp#3i0j;9C-fV+A-SuU#=K3lV7s3NyMd10&SK*FmPU)t!mbt|3Sz96x_-O6i zSFsdpv`^>`q6BSn_YjuQhkUM0WIy@M-x%e# zzJBoqMtRzO|4yWQ(GX8D+Gqc6qkQeckHf`-!|AHlcP=>?<+mbFET3zxk>O%2QK)L+ zke6Em={oT!Q54JN;`CrzDH0e(sCk_Bnstvb8p`IQ&JT4Ugp*bEG=%6CEsm(PN(@uU zscz(S!0hsn%x>+=4zo}M#fmHo-6T@1Xg<^nBc$L6Fq{9@i^A2kzHE>c%`?RNepDzL ztQ4bWt8~n4>9A3%x=f`DX12*jN$QHej^ph#Ty^y|ZnIN@>Q?Ef*?6iq4JVHY_NX^S zsby*}zSDew@0Pyo^F9=-Zta7U=L@jg`bIhKOG9b9oK2y)#*c=o?R`~8{DApUIYhmB zOr=J1;I=^&!A__mIT!>SKbT@!hbkTqMCRaNf{rQPA53?sXH@Dk+ZP6qUA*p3iFEef z0MgWRDmJUJOkW02Jmzjt=BBGCP9+O<$f^~B1=0wXW-(WATOi7~C2!QAJI)(}de z0x>0;hU>OaTFy;w({CF}rL4CiB$DPaS|&=I6lK)Mi2Y95K?OScWr86kgX&<=spMTC zc{`sg%S`UH#!?vO6dg+53w|xtuS_-D2@X# zm-s^*ZDww6NbO?x2=W%wM-b!=v1J5Jjlo_Bo#(1{NhwTG%D64Orn0c8qT21hxWZ$U zfxlP1!ou#2r%6;NCdbnPb7Q^VmoxU(#|hMMdCB>abo1pU{}@FzC|QySlfM**}&tQtq7dWiM-I67y&vN}%xWFpP< z>Z#CYPNk75I@V3cpj-9(rqg<&ZF=Agg3%#BPnt<9@}K6ab>XTkb+2`m_UC=qyI1$;{Hr|` zwf)&3cbUsGpmAp9+RDN;IRD($l{f}#{lwkHG&HimLwN0a545c6z7l0S*7sm?bOSdf zPrKSLc{Uiw;ZDAjD?58|PH|I6{4SJ}0;ytsimL3E{AFauLT5*Y8yQGxdqt(2LN_&` z%*!Yn57iqPtC6`{Ra%g-8JTodc~?>FMmj@P4xqwYfe5lb1%`k&iqJ8o3ZN2V~A57perU!z{)Eo3qGqV%u(-%1Fb0{8 zrNN|;+$&3E3!5Srhr@D$q+A;AWhCXoaHtn3l}o|xMpCW=_coGp5x9?$lq)j5F;tc$U}{! zq#+M8l9GUYh>`4`BGW^Sw4@ymHCM2tGUL!bdJFxFQ1sMhoG?lHdzh}acT?#OS_fcn?Eva|;s^Q47cm-M^Tka0DiD=;v1O`A`GO4- zZL-V|mgl13n0sgytPkE1x7X*=tOI-~vv10d6EWV0!!$Nnhv$n+T?Ca+=n>%H5 zapow^*|cY*bJYMCtDH$jj{)tRwH2@h*D6`Vou&Xh!MSQ~d`iNqrJnK{clAK54AZho z@y6T0X*!(}j zUFeZoOL^e(C%vx}suD12`ey$j_5m=sUI5uJRfv#n5oYG=h7X9-Nd?i#F|QEp=I ztgUvt_ZwaER*5~wm?NYYYc#B^tF3v$sGMt7#`jfL)YZz`&y3os#%QYsOj)E{q!``j z070T^5XDc^=2W7EdZ zmi~~P*n$kE`(5(Ak(AwH&{Gs2uu&yn4~#?s~2qJp2@-`XP}4!4dyuW@0nklyiA|`|4J`?^>O-?phwPm zh34b_-SZ0V?K${F;j8pqZ#w-odaXA-tb>|*(`P#1-7-&2vHx%M*PgU~=j+gcdn@*x zq}6mn3_1l(rb(ZBiq?{rwI;wC1ZRbNgE453`1B3h53TvPZ_>=r6S$e?Kz^9JMq05c zyxI-_XpP(OTzncitkb4{`?6AsfN}UsK+Nl&aDof7$4)e)V?*&zGr6>g)bZZH%2$r5kLh$o`Rrn>7yorXQtBctJeE z;Wz2l-{$Ny&bn2@6!f8$Iq0JJ&dSE)m11M#f-k9*SyMeWEiENu!i0Hst4lpaigHQZ zXk#N8S+uwAh?iEnI1Yc8$R5N(^}%)=kwB}>n-y}hh_FFy0gM=>gV<;a5cdsYi4-PY z90V^>w74V-?ILk7%OgK=!(etD^RpP+{tJ0>+DjlB7O? z6ntYAC84KJN&s6PBx_iTMZph3wS%Ein!YE1J?;&8bzV4IM3cncaCRHC;>u{2s=FfC zAQksAG8f_C)31+Zot$#@X$dSHm`UnISGaiVZKoTG5*l}~5)5o|D3GgyLH znF*hakbzP zp!=%8a)tY<07QXWDPM43Ewd=g^qaF-9K-0ppUTD!f$!$$eM&BIek%5aOL*n5^sBD$ z!??w~92N|*e{BxS0p9y^SO%niW0k6mRf6$l^fb6VtIS2JGZ(2&E>gWkssFDm5^dDW zrnCG1U#wA;xkgo&t{4p9Ih3f7|;E zD)`R<$HQazzv_IJ^L()-91DGZK6vR^Le{PyKs1@_;@i}f~}FYgna~UW5iPS zTNwKeFJ3bOez|j0T7!0r>q`ODa24vuOW7+7lGS|`*h#RZ>y-x2sV7#a9VRQBG<)?of6c1BAqdR8mz_lvjlg@ge(!IYKOA@B zi$+YublK@2{f^nJePue_cz=*|mhL?9#$NU^B!h<@!J%z>>4eXt>?#O3k&m*?`kjAZ zYb7}8zU!&bQSlw4EuZG6#hLO71Q&h+49+4eSp(5!O7973|KDwTOiMiFDwVrZBILXPL zvM8tZ!dKW|5!@u=USo6VthnJdHi6EGme<(0xj&<`okc$wR=<7&m|Ka?5SR&{dM*Tq^y#Y*bx@daFV4Df{xo4jA8fE z-i;U{zW<)t_3uuy+f~R}o8Muf;=WEc6Y|wZooof{3)9}l-UyK0Q^q*FOoE4b^@DN1 z&WejOT2;c3V#r1qC85z?!v7sMP7F@vR=xw;;1lS-# zlEkzuGEAlP%gmMt`ACsiBzP%lUgG}gSg_xDb02;5yR3=(WEhA|@(NWZ87Mfidr-(7 zfI_y2f1lkIm@C0#dntKFpM3Oz^93|;%pV#bd%N>w#A_0A87>|A@`ES4ntfUP_&K{Uhc=Ad-Q= z#-0va26WgcZu%!Hv2T$bHhL*r_0Rsv7UBpOS)U+|YrDAq6Lu#~f204$<{C~K;2qiN zC0us_;RT<#RU$6&pM!xn1yB2=GQRkV80E(|D{V$%v!Wa_OOS6D^S@#_qmFvPw$dxG%b1{}UYCpN$02sh&;;d} z{@hnMMxd!l*pVG3q~ppdGvOko(@deD%K(1W6~IrMFeaZdTKc2US+frc&Y5KwycGPv z!l+cb&Dx7z3Vw>}D|;xH^wZx$8F3KY2&!%Kam8XJjw5fk>W9B#6@;yG&-ZL0_Q}WJ zvjag6vjNl%Gl6`BX!$qG3A_Ts=w1l1V*L*+8^RzA!ZD)j2aK2?e1Bw1VLEsJ$cFh) ziUg6Fx}D4gOtb!kl4;h0M%59i{k|A#Q<`IRq+0vtFScT_Q)b_>Ys>OZ9(iB%- zQR}QWV|GVp<-0cI8bQDd+~_DHRMnCkSGjECDlRt6bA8rU(Kv=zLlFt_;U7RW|IUXO z;Wk?7%g2Im5H1-3J>+p;o(l8khrV1TWY7BXe3oa`Ru+co^9S)GR-b%>V(q|szK9Iw z(pHuo%meW%2@pSU&YzUu?XeTER(cR%ZN`ko~nkOKZ zCb>3*DOKXd2p&y!`X>>53BzVY?_hnYlc)5y9J$dWXmy&+Yn-IQ|VB zu;Pw*z5`kjSRz=w|2`W!cDL2!i$-hz;k;W?)7QTfw!TIr8S;A#0ykMhVMy%jY#h?w z{fD$@9K{{t%>*7#o%fF9A=u!dBl*l|sPAUjGWoj8ixGTam`UdthFf_qw}@YkMRLVUhnx=5g$l6(*N(5qgRF>f=&4#9G8y^itd) z+GSU2msNXfYq-ct<$s5mc6r0guD~<>m5#GLGdee5MrTFjcnCW$r13fSb8=2+t;z)* zWh+=@x6EI(D)>psp&Oo5BSp4UNYr9iIv>d_HvMQiUya}>o0OCyE~+LC{A~Ta;b)U@ zU>6ZLOyF?|4%@)1H_Ud$)f^})5p~~b1;o%5!*h;K0BTGZt`# zcqj+NaEYTid<+MX z8jt;%Vd}+*T<(O(KO>hX^ZMS_J9Bxg-^Lzje^R#emY>e$?S5Njxi3aZRkqoTV0OJ@ zI$sF~kw~7w_d+CiV+Jpa=pz8Z0}r1^y^WmMtCbb-!0m#0hL0NCa2b2-yplb3VkoeO ze5p2@P1z+5rBBfELd=q2*mAwYP#y8naer+DW zuT4gC96(AlQViJ^n;E=w$4tHk4k$QxQ+*E@p!}6K8}yEpSv<;tAm2nebZ<~KG{{3e z9XHJ4zhLd=kVkulJT_p+qsEY-!0?zH64?_yJ?j@js;r*PKk;H6HYnH;;--cCM%Z{K zE`m`tV3E8xWcpWuU9#tCn}VO@fK=s-%?PR0yBBdUf^qiW1-!-*a?YqufVEe-(34D6 zx^3FiFGT7OFXvW*)ph@Me9nM~&r8Cxf)_JbN!P95Lzu;`-?oB}v3lDP? zxET8?Ks@foj{UKiPk>8(f}1D9%(~9abC5&LR^*HZbJp-{u%(|}!$U;#8t!kVNA?um zyOvLIL>Qn$LzQSFA!V{yJ5Csf=vvED#wFM(-4O1uSuXWHGs#TL*OxxDB$6a!07ibI^nPbfjEaGZh13q_@pM zMtk{t5F~G)GN1DG%vyej4Z=F~-y!0rSD0OVdNV(R_<)X$Jl~nmV-|YWmc#L0?WSe+ zpxg~`JyMk&1W3jFmx;-@fK^tAl3Q?sxW%t;;m$CRowD3T#Wk)86Z-nf&r?>2=TR+Y zZQ|bkG6KMbHQ-r*g5yQ}&|77ZRUVPFiARnpv&)D9e9JUG4TWX0eYVja2q8kDw>);QvtRqV6a7az>YgXBV%* znbx(7*W$I{cYF&R5GQ}f;{y?Qx+pZ+5HQSOf*OYyd>?-Z@h4B-$De>Tt>}JUh+E?? z_wxk|OCS6ozZNj69^~U-Sl{~~zm0+rpt?b!qZSrkAfu@9`2-BZS&e)ad`x#X^2v}Z zUv7lVctStl$S-i}&_CGA2g`Vje?7uo_*6+O`Xjc`8FAwudB)%imTnnlL-m~QSFjoB7`${b`T! z+c+3|=#vnPGepLd{CV)%4^M*Xv3gc3e?)~<;-5Ms)FhGh6vqc`y1fm&0%jTaG}^kv zw@>pD?)LJCqJ#V%i`%Qt>#%U`;K?HK8U8vd>IFN*$3Sfudx)38*1G2qUof(nB*$F- zj}TS+zAb&B5mRs4HS&tLiSTFnZ?Ka4p2gH~Q9KKJWQg?VxEtYr4?M?5kUd+a^9t?q z3Ke5;YKo5z^PyhLB!PAdYdfF98ofmJSk)m`w1d0%ip}kO5^wU_D-O5wUs{`F@DgS- z^9WyyWq9%kgY?nROFlhmUEux+hraU+3_<!{02Yh!@D-wLYLv^5S8EaxE&`JtBK&*6R>PTFQSMw*QSEe< zJBJx9hdKLTn&^!V!ndeJZc^&h&2pDH#raRUs_%Q7FN8M|QQ=!ijp@^t{rw>dDe9fV z`2!Cb)Tbf4YerLs9^S=oVXT?NbMNtkV#)j1|IK>c`+T_cQ13d;j|EKv@=B-?6Hx<= z8@8Wpt2q4upCg|CfDeW8_x=aig}cS{fADjobFHeEdL8hUA8>&-P>^ zRYmx*t*2J*YS)MS5gatOGu+ABG2o~(d99i(Q#+~caL zs~)fgZ#&CpLgoJ3Sx6jkh7S3Zza(SdBvd;gQvMVvhpU;UYw88AgUa7sfGGj*L0{rx z!ZCvZvrt?46)y=mrYg2X!06vCXTQRc)GVfV1KI7Op_`8ebq;sq=#BC-4%UhLJO(itdc&wYdw;_YwvDy-qeZ}~M>=MhFdNWS1x0sqnXY@f_ z{S(>n(ZD5CUJx&-YBV5;bE;Z`*)6uHzXrGuEb8wiPG0JHdfBIIj--s{1fX zsh|2htd1#z)Wi7nRsYvubqrC2J}f|8L%7$sU8QCdPqX#xw{wB&3qDW>75_YPE-G;g z#6E=SZw09x(vtLOFlHAZMueza0jfDft;e?mV#E;jV;e$0?7(%t2pOuvj3=fK1$7F< z#-S=cD-`WR)z!G}W5QL$%x@Hlk?IVzsf$!I!E#SVs_SuAkBn0HB5*;x=2Vy1(k$c$ zaA2Nj;JIDDH(E99q^pOk2%QoKhpVgkZd;>p#;AL6>9xnGU%`Cx=U8&Cv44c>iEgk^Ga8<;tq5PbsA8SFyoP!c3hlID^zrd(D_HT9cy%)Fn~nrE zL}VqX_~c5ae{DH{-rc%uqkz}fI4tf?QRDRDWGoce=yZy@(tM2)>&Aj7 zje+hlU$ev({7(mfSM@q1{28h0q+Zy&Qq`NKgcXve#`y2HAe6;-z~MG4P5lyzkjNUZ z4!3l8^_|tXk5}&ux7(puDe30t_J%~@sjeX$uedoy^3|$?cX$Pe8CR<_EYbD|yTNwR z;?GyBAx6b(SF0W*Vnt@U8XDT+l_bNgX|IJ&U4}wwx_SUI*RTwAHl(|X40W;EWC87u zWq{qb3BL(y#HM`vn$&6T4GY)1DivvY|)0El6t#;vQcu4yWCylso7{xHe2LZRM#Rlv$lSVJvrWJVccBX?3)vf+!D7% Tew(&kJT^fcYH6@<-f90oJU#|c diff --git a/runtime/metadata-parachain-kintsugi.scale b/runtime/metadata-parachain-kintsugi.scale index 4582982a2a52ed421dada4ed142c22239b38aab3..7ffb1859d12b7f450b47b103a7c6580d4f381202 100644 GIT binary patch delta 33053 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 Transaction { let address = ScriptBuf::from(address.to_script_pub_key().as_bytes().to_vec()); + // construct height: see https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki + // first byte is number of bytes in the number (will be 0x03 on main net for the next + // 150 or so years with 223-1 blocks), following bytes are little-endian representation + // of the number (including a sign bit) + let mut height_bytes = height.to_le_bytes().to_vec(); + for i in (1..4).rev() { + // remove trailing zeroes, but always keep first byte even if it's zero + if height_bytes[i] == 0 { + height_bytes.remove(i); + } else { + break; + } + } + height_bytes.insert(0, height_bytes.len() as u8); + // note that we set lock_time to height, otherwise we might generate blocks with // identical block hashes Transaction { input: vec![TxIn { previous_output: OutPoint::null(), // coinbase witness: Witness::from_slice::<&[u8]>(&[]), - script_sig: Default::default(), + script_sig: ScriptBuf::from(height_bytes), sequence: Sequence(u32::max_value()), }], output: vec![TxOut { @@ -360,7 +375,16 @@ impl BitcoinCoreApi for MockBitcoinCore { // part two: info about the transactions (we assume the txid is at index 1) let txids = block.txdata.iter().map(|x| x.txid()).collect::>(); - let partial_merkle_tree = PartialMerkleTree::from_txids(&txids, &[false, true]); + assert_eq!(txids.len(), 2); // expect coinbase and user tx + + let partial_merkle_tree = if txids[0] == txid { + PartialMerkleTree::from_txids(&txids, &[true, false]) + } else if txids[1] == txid { + PartialMerkleTree::from_txids(&txids, &[false, true]) + } else { + panic!("txid not in block") + }; + proof.append(&mut serialize(&partial_merkle_tree)); Ok(proof) @@ -441,13 +465,20 @@ impl BitcoinCoreApi for MockBitcoinCore { tokio::time::sleep(Duration::from_secs(1)).await; }; let block_hash = block.block_hash(); - let proof = self.get_proof(txid, &block_hash).await.unwrap(); - let raw_tx = self.get_raw_tx(&txid, &block_hash).await.unwrap(); + let coinbase_txid = block.coinbase().unwrap().txid(); + let coinbase_tx_proof = self.get_proof(coinbase_txid, &block_hash).await.unwrap(); + let raw_coinbase_tx = self.get_raw_tx(&coinbase_txid, &block_hash).await.unwrap(); + let user_tx_proof = self.get_proof(txid, &block_hash).await.unwrap(); + let raw_user_tx = self.get_raw_tx(&txid, &block_hash).await.unwrap(); Ok(TransactionMetadata { block_hash, - proof, - raw_tx, + proof: RawTransactionProof { + user_tx_proof, + raw_user_tx, + coinbase_tx_proof, + raw_coinbase_tx, + }, txid, block_height: block_height as u32, fee: None, diff --git a/runtime/src/integration/mod.rs b/runtime/src/integration/mod.rs index a7041cd3d..121ee9594 100644 --- a/runtime/src/integration/mod.rs +++ b/runtime/src/integration/mod.rs @@ -33,6 +33,7 @@ pub use bitcoin_simulator::MockBitcoinCore; pub async fn default_root_provider(key: AccountKeyring) -> (InterBtcParachain, TempDir) { let tmp = TempDir::new("btc-parachain-").expect("failed to create tempdir"); let root_provider = setup_provider(key).await; + try_join( root_provider.set_bitcoin_confirmations(1), root_provider.set_parachain_confirmations(1), @@ -81,7 +82,7 @@ pub async fn assert_issue( .unwrap(); parachain_rpc - .execute_issue(*issue.issue_id, &metadata.proof, &metadata.raw_tx) + .execute_issue(*issue.issue_id, &metadata.proof) .await .unwrap(); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b7836b7b7..d5dd78e0a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -71,8 +71,6 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck"; ), derive_for_type(path = "interbtc_primitives::VaultCurrencyPair", derive = "Eq, PartialEq"), derive_for_type(path = "interbtc_primitives::VaultId", derive = "Eq, PartialEq"), - derive_for_type(path = "security::types::ErrorCode", derive = "Eq, PartialEq, Ord, PartialOrd"), - derive_for_type(path = "security::types::StatusCode", derive = "Eq, PartialEq"), substitute_type(path = "primitive_types::H256", with = "::subxt::utils::Static"), substitute_type(path = "primitive_types::U256", with = "::subxt::utils::Static"), substitute_type(path = "primitive_types::H160", with = "::subxt::utils::Static"), @@ -106,6 +104,10 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck"; path = "bitcoin::types::Transaction", with = "::subxt::utils::Static<::module_bitcoin::types::Transaction>" ), + substitute_type( + path = "bitcoin::types::FullTransactionProof", + with = "::subxt::utils::Static<::module_bitcoin::types::FullTransactionProof>" + ), ) )] #[cfg_attr( @@ -123,8 +125,6 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck"; ), derive_for_type(path = "interbtc_primitives::VaultCurrencyPair", derive = "Eq, PartialEq"), derive_for_type(path = "interbtc_primitives::VaultId", derive = "Eq, PartialEq"), - derive_for_type(path = "security::types::ErrorCode", derive = "Eq, PartialEq, Ord, PartialOrd"), - derive_for_type(path = "security::types::StatusCode", derive = "Eq, PartialEq"), substitute_type(path = "primitive_types::H256", with = "::subxt::utils::Static"), substitute_type(path = "primitive_types::U256", with = "::subxt::utils::Static"), substitute_type(path = "primitive_types::H160", with = "::subxt::utils::Static"), @@ -158,6 +158,10 @@ pub const DISABLE_DIFFICULTY_CHECK: &str = "DisableDifficultyCheck"; path = "bitcoin::types::Transaction", with = "::subxt::utils::Static<::module_bitcoin::types::Transaction>" ), + substitute_type( + path = "bitcoin::types::FullTransactionProof", + with = "::subxt::utils::Static<::module_bitcoin::types::FullTransactionProof>" + ), ) )] diff --git a/runtime/src/rpc.rs b/runtime/src/rpc.rs index 8fbd3cbce..f9b7e43dc 100644 --- a/runtime/src/rpc.rs +++ b/runtime/src/rpc.rs @@ -7,16 +7,17 @@ use crate::{ AccountId, AssetRegistry, CurrencyId, Error, InterBtcRuntime, InterBtcSigner, RetryPolicy, RichH256Le, SubxtError, }; use async_trait::async_trait; +use bitcoin::RawTransactionProof; use codec::{Decode, Encode}; use futures::{future::join_all, stream::StreamExt, FutureExt, SinkExt, Stream}; use module_bitcoin::{ - merkle::MerkleProof, + merkle::{MerkleProof, PartialTransactionProof}, parser::{parse_block_header, parse_transaction}, + types::FullTransactionProof, }; -use module_oracle_rpc_runtime_api::BalanceWrapper; -use primitives::UnsignedFixedPoint; +use primitives::{BalanceWrapper, UnsignedFixedPoint}; use serde_json::Value; -use std::{future::Future, ops::Range, sync::Arc, time::Duration}; +use std::{convert::TryInto, future::Future, ops::Range, sync::Arc, time::Duration}; use subxt::{ blocks::ExtrinsicEvents, client::OnlineClient, @@ -31,6 +32,7 @@ use tokio::{ sync::RwLock, time::{sleep, timeout}, }; + // timeout before retrying parachain calls (5 minutes) const TRANSACTION_TIMEOUT: Duration = Duration::from_secs(300); @@ -890,9 +892,8 @@ pub trait ReplacePallet { /// /// * `&self` - sender of the transaction: the old vault /// * `replace_id` - the ID of the replacement request - /// * 'merkle_proof' - the merkle root of the block - /// * `raw_tx` - the transaction id in bytes - async fn execute_replace(&self, replace_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), Error>; + /// * `raw_proof` - raw tx and proofs of coinbase and user tx + async fn execute_replace(&self, replace_id: H256, raw_proof: &RawTransactionProof) -> Result<(), Error>; /// Cancel vault replacement /// @@ -966,13 +967,12 @@ impl ReplacePallet for InterBtcParachain { Ok(()) } - async fn execute_replace(&self, replace_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), Error> { - self.with_unique_signer(metadata::tx().replace().execute_replace( - Static(replace_id), - Static(MerkleProof::parse(merkle_proof)?), - Static(parse_transaction(raw_tx)?), - raw_tx.len() as u32, - )) + async fn execute_replace(&self, replace_id: H256, raw_proof: &RawTransactionProof) -> Result<(), Error> { + self.with_unique_signer( + metadata::tx() + .replace() + .execute_replace(Static(replace_id), build_full_tx_proof(raw_proof)?), + ) .await?; Ok(()) } @@ -1168,31 +1168,12 @@ impl OraclePallet for InterBtcParachain { #[async_trait] pub trait SecurityPallet { - async fn get_parachain_status(&self) -> Result; - - async fn get_error_codes(&self) -> Result, Error>; - /// Gets the current active block number of the parachain async fn get_current_active_block_number(&self) -> Result; } #[async_trait] impl SecurityPallet for InterBtcParachain { - /// Get the current security status of the parachain. - /// Should be one of; `Running`, `Error` or `Shutdown`. - async fn get_parachain_status(&self) -> Result { - self.query_finalized_or_error(metadata::storage().security().parachain_status()) - .await - } - - /// Return any `ErrorCode`s set in the security module. - async fn get_error_codes(&self) -> Result, Error> { - Ok(self - .query_finalized_or_error(metadata::storage().security().errors()) - .await? - .0) - } - /// Gets the current active block number of the parachain async fn get_current_active_block_number(&self) -> Result { self.query_finalized_or_default(metadata::storage().security().active_block_count()) @@ -1206,7 +1187,7 @@ pub trait IssuePallet { async fn request_issue(&self, amount: u128, vault_id: &VaultId) -> Result; /// Execute a issue request by providing a Bitcoin transaction inclusion proof - async fn execute_issue(&self, issue_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), Error>; + async fn execute_issue(&self, issue_id: H256, raw_proof: &RawTransactionProof) -> Result<(), Error>; /// Cancel an ongoing issue request async fn cancel_issue(&self, issue_id: H256) -> Result<(), Error>; @@ -1234,13 +1215,12 @@ impl IssuePallet for InterBtcParachain { .ok_or(Error::RequestIssueIDNotFound) } - async fn execute_issue(&self, issue_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), Error> { - self.with_unique_signer(metadata::tx().issue().execute_issue( - Static(issue_id), - Static(MerkleProof::parse(merkle_proof)?), - Static(parse_transaction(raw_tx)?), - raw_tx.len() as u32, - )) + async fn execute_issue(&self, issue_id: H256, raw_proof: &RawTransactionProof) -> Result<(), Error> { + self.with_unique_signer( + metadata::tx() + .issue() + .execute_issue(Static(issue_id), build_full_tx_proof(raw_proof)?), + ) .await?; Ok(()) } @@ -1310,7 +1290,7 @@ pub trait RedeemPallet { async fn request_redeem(&self, amount: u128, btc_address: BtcAddress, vault_id: &VaultId) -> Result; /// Execute a redeem request by providing a Bitcoin transaction inclusion proof - async fn execute_redeem(&self, redeem_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), Error>; + async fn execute_redeem(&self, redeem_id: H256, raw_proof: &RawTransactionProof) -> Result<(), Error>; /// Cancel an ongoing redeem request async fn cancel_redeem(&self, redeem_id: H256, reimburse: bool) -> Result<(), Error>; @@ -1341,13 +1321,12 @@ impl RedeemPallet for InterBtcParachain { Ok(*redeem_event.redeem_id) } - async fn execute_redeem(&self, redeem_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), Error> { - self.with_unique_signer(metadata::tx().redeem().execute_redeem( - Static(redeem_id), - Static(MerkleProof::parse(merkle_proof)?), - Static(parse_transaction(raw_tx)?), - raw_tx.len() as u32, - )) + async fn execute_redeem(&self, redeem_id: H256, raw_proof: &RawTransactionProof) -> Result<(), Error> { + self.with_unique_signer( + metadata::tx() + .redeem() + .execute_redeem(Static(redeem_id), build_full_tx_proof(raw_proof)?), + ) .await?; Ok(()) } @@ -1694,7 +1673,7 @@ impl VaultRegistryPallet for InterBtcParachain { self.with_unique_signer( metadata::tx() .nomination() - .withdraw_collateral(vault_id.clone(), amount, None), + .withdraw_collateral(vault_id.clone(), Some(amount), None), ) .await?; Ok(()) @@ -1957,3 +1936,18 @@ impl SudoPallet for InterBtcParachain { .await } } + +pub fn build_full_tx_proof(raw_proof: &RawTransactionProof) -> Result, Error> { + Ok(Static(FullTransactionProof { + user_tx_proof: PartialTransactionProof { + transaction: parse_transaction(&raw_proof.raw_user_tx[..])?, + tx_encoded_len: raw_proof.raw_user_tx.len().try_into()?, + merkle_proof: MerkleProof::parse(&raw_proof.user_tx_proof[..])?, + }, + coinbase_proof: PartialTransactionProof { + transaction: parse_transaction(&raw_proof.raw_coinbase_tx[..])?, + tx_encoded_len: raw_proof.raw_coinbase_tx.len().try_into()?, + merkle_proof: MerkleProof::parse(&raw_proof.coinbase_tx_proof[..])?, + }, + })) +} diff --git a/runtime/src/tests.rs b/runtime/src/tests.rs index bda0b3c80..28059507c 100644 --- a/runtime/src/tests.rs +++ b/runtime/src/tests.rs @@ -4,8 +4,8 @@ const DEFAULT_TESTING_CURRENCY: CurrencyId = Token(KSM); use super::{ BtcAddress, BtcPublicKey, BtcRelayPallet, CollateralBalancesPallet, CurrencyId, FixedPointNumber, FixedU128, - OraclePallet, RawBlockHeader, ReplacePallet, SecurityPallet, StatusCode, SudoPallet, Token, TryFromSymbol, - VaultRegistryPallet, KBTC, KINT, KSM, + OraclePallet, RawBlockHeader, ReplacePallet, SecurityPallet, SudoPallet, Token, TryFromSymbol, VaultRegistryPallet, + KBTC, KINT, KSM, }; use crate::{ integration::*, utils::account_id::AccountId32, FeedValuesEvent, OracleKey, RuntimeCurrencyInfo, VaultId, H160, @@ -47,14 +47,11 @@ async fn test_getters() { async { assert_eq!(parachain_rpc.get_free_balance(Token(KINT)).await.unwrap(), 1 << 60); }, - async { - assert_eq!(parachain_rpc.get_parachain_status().await.unwrap(), StatusCode::Error); - }, async { assert!(parachain_rpc.get_replace_dust_amount().await.unwrap() > 0); }, async { - assert!(parachain_rpc.get_current_active_block_number().await.unwrap() == 0); + assert!(parachain_rpc.get_current_active_block_number().await.is_ok()); } ); parachain_runner.kill().unwrap(); diff --git a/runtime/src/types.rs b/runtime/src/types.rs index a45028457..3c169bb15 100644 --- a/runtime/src/types.rs +++ b/runtime/src/types.rs @@ -6,7 +6,6 @@ use crate::{ pub use currency_id::CurrencyIdExt; pub use h256_le::RichH256Le; pub use metadata_aliases::*; -pub use module_btc_relay::{RichBlockHeader, MAIN_CHAIN_ID}; pub use primitives::{ CurrencyId, CurrencyId::{ForeignAsset, LendToken, Token}, @@ -28,10 +27,8 @@ pub type Ratio = primitives::Ratio; pub type InterBtcSigner = PairSigner; -pub type BtcAddress = module_btc_relay::BtcAddress; - +pub type BtcAddress = module_bitcoin::Address; pub type FixedU128 = crate::FixedU128; - #[allow(non_camel_case_types)] pub(crate) enum StorageMapHasher { Blake2_128, @@ -45,10 +42,7 @@ mod metadata_aliases { pub use metadata::runtime_types::interbtc_primitives::oracle::Key as OracleKey; - pub use metadata::runtime_types::{ - security::types::{ErrorCode, StatusCode}, - vault_registry::types::VaultStatus, - }; + pub use metadata::runtime_types::vault_registry::types::VaultStatus; pub type InterBtcVault = metadata::runtime_types::vault_registry::types::Vault; pub type InterBtcVaultStatic = metadata::runtime_types::vault_registry::types::Vault< diff --git a/runtime/src/utils/account_id.rs b/runtime/src/utils/account_id.rs index b8d0f0eda..6ae04ea82 100644 --- a/runtime/src/utils/account_id.rs +++ b/runtime/src/utils/account_id.rs @@ -3,11 +3,9 @@ //! Substrate-based blockchains. This wrapper is necessary because the `scale_encode::EncodeAsType` and //! `scale_decode::DecodeAsType` traits are not implemented for `sp_core::crypto::AccountId32`, //! but they are required for the latest version of the `subxt` crate. -use base58::{FromBase58, ToBase58}; use codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use sp_core::crypto::{AccountId32 as Sp_AccountId32, Ss58Codec}; -use std::convert::TryInto; use subxt::utils::Static; #[derive( diff --git a/service/Cargo.toml b/service/Cargo.toml deleted file mode 100644 index 0ceea1e8a..000000000 --- a/service/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "service" -version = "1.1.0" -authors = ["Interlay "] -edition = "2018" - -[dependencies] -async-trait = "0.1.40" -futures = "0.3.5" -clap = { version = "4.0.17", features = ["derive"]} -thiserror = "1.0" - -tokio = { version = "1.0", features = ["full"] } -hyper = { version = "0.14.27" } -hyper-tls = "0.5.0" -warp = "0.3.2" - -serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.71" - -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.2.12", features = ["registry", "env-filter", "fmt"] } -tracing-futures = { version = "0.2.5" } - -governor = "0.5.0" -nonzero_ext = "0.3.0" - -# Workspace dependencies -bitcoin = { path = "../bitcoin", features = ["cli"] } -runtime = { path = "../runtime" } diff --git a/service/src/error.rs b/service/src/error.rs deleted file mode 100644 index abafdbdd3..000000000 --- a/service/src/error.rs +++ /dev/null @@ -1,38 +0,0 @@ -use bitcoin::Error as BitcoinError; -use runtime::Error as RuntimeError; -use serde_json::Error as SerdeJsonError; -use std::{io::Error as IoError, num::ParseIntError}; -use thiserror::Error; -use tokio::task::JoinError as TokioJoinError; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Abort: {0}")] - Abort(InnerError), - #[error("Retry: {0}")] - Retry(InnerError), - - #[error("Client has shutdown")] - ClientShutdown, - #[error("OsString parsing error")] - OsStringError, - #[error("File already exists")] - FileAlreadyExists, - #[error("There is a service already running on the system, with pid {0}")] - ServiceAlreadyRunning(u32), - #[error("Process with pid {0} not found")] - ProcessNotFound(String), - - #[error("SerdeJsonError: {0}")] - SerdeJsonError(#[from] SerdeJsonError), - #[error("ParseIntError: {0}")] - ParseIntError(#[from] ParseIntError), - #[error("RuntimeError: {0}")] - RuntimeError(#[from] RuntimeError), - #[error("BitcoinError: {0}")] - BitcoinError(#[from] BitcoinError), - #[error("TokioError: {0}")] - TokioError(#[from] TokioJoinError), - #[error("System I/O error: {0}")] - IoError(#[from] IoError), -} diff --git a/vault/Cargo.toml b/vault/Cargo.toml index 9f6b9f9b0..d4559ddf4 100644 --- a/vault/Cargo.toml +++ b/vault/Cargo.toml @@ -12,11 +12,15 @@ uses-bitcoind = [] # run tests relying on bitcoind regtest node [dependencies] thiserror = "1.0" +backoff = { version = "0.3.0", features = ["tokio"] } clap = { version = "4.0.17", features = ["derive"]} tokio = { version = "1.0", features = ["full"] } tokio-stream = { version = "0.1.9", features = ["sync"] } tokio-metrics = { version = "0.1.0", default-features = false } -serde = "1.0.136" +hyper = { version = "0.14.27" } +hyper-tls = "0.5.0" +warp = "0.3.2" +serde = { version = "1.0.136", features = ["derive"] } parity-scale-codec = "3.0.0" hex = "0.4.2" futures = "0.3.5" @@ -45,7 +49,6 @@ jsonrpc-core-client = { version = "18.0.0", features = ["http", "tls"] } # Workspace dependencies bitcoin = { path = "../bitcoin", features = ["cli"] } runtime = { path = "../runtime" } -service = { path = "../service" } faucet-rpc = { package = "faucet", path = "../faucet" } # Substrate dependencies diff --git a/vault/src/cancellation.rs b/vault/src/cancellation.rs index ab0deafb9..56e2cc143 100644 --- a/vault/src/cancellation.rs +++ b/vault/src/cancellation.rs @@ -315,12 +315,12 @@ impl Cancel mod tests { use super::*; use async_trait::async_trait; + use bitcoin::RawTransactionProof; use futures::channel::mpsc; use jsonrpc_core::serde_json::{Map, Value}; use runtime::{ - subxt::utils::Static, AccountId, AssetMetadata, BtcAddress, BtcPublicKey, CurrencyId, ErrorCode, - InterBtcIssueRequest, InterBtcReplaceRequest, IssueRequestStatus, RequestIssueEvent, StatusCode, Token, - VaultId, DOT, IBTC, INTR, + subxt::utils::Static, AccountId, AssetMetadata, BtcAddress, BtcPublicKey, CurrencyId, InterBtcIssueRequest, + InterBtcReplaceRequest, IssueRequestStatus, RequestIssueEvent, Token, VaultId, DOT, IBTC, INTR, }; macro_rules! assert_err { @@ -339,7 +339,7 @@ mod tests { #[async_trait] pub trait IssuePallet { async fn request_issue(&self, amount: u128, vault_id: &VaultId) -> Result; - async fn execute_issue(&self, issue_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_issue(&self, issue_id: H256, raw_proof: &RawTransactionProof,) -> Result<(), RuntimeError>; async fn cancel_issue(&self, issue_id: H256) -> Result<(), RuntimeError>; async fn get_issue_request(&self, issue_id: H256) -> Result; async fn get_vault_issue_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -353,7 +353,7 @@ mod tests { async fn request_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn withdraw_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn accept_replace(&self, new_vault: &VaultId, old_vault: &VaultId, amount_btc: u128, collateral: u128, btc_address: BtcAddress) -> Result<(), RuntimeError>; - async fn execute_replace(&self, replace_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_replace(&self, replace_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_replace(&self, replace_id: H256) -> Result<(), RuntimeError>; async fn get_new_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; async fn get_old_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -376,8 +376,6 @@ mod tests { #[async_trait] pub trait SecurityPallet { - async fn get_parachain_status(&self) -> Result; - async fn get_error_codes(&self) -> Result, RuntimeError>; async fn get_current_active_block_number(&self) -> Result; } } diff --git a/service/src/cli.rs b/vault/src/cli.rs similarity index 93% rename from service/src/cli.rs rename to vault/src/cli.rs index d4c155de4..f4340e701 100644 --- a/service/src/cli.rs +++ b/vault/src/cli.rs @@ -1,3 +1,4 @@ +use crate::trace; use clap::Parser; use std::str::FromStr; @@ -44,8 +45,8 @@ impl FromStr for LoggingFormat { impl LoggingFormat { pub fn init_subscriber(&self) { match *self { - Self::Full => crate::trace::init_subscriber(), - Self::Json => crate::trace::init_json_subscriber(), + Self::Full => trace::init_subscriber(), + Self::Json => trace::init_json_subscriber(), } } } diff --git a/service/src/lib.rs b/vault/src/connection_manger.rs similarity index 86% rename from service/src/lib.rs rename to vault/src/connection_manger.rs index b006e3a2c..c50ae2517 100644 --- a/service/src/lib.rs +++ b/vault/src/connection_manger.rs @@ -1,4 +1,10 @@ +pub use crate::{ + cli::{LoggingFormat, MonitoringConfig, RestartPolicy, ServiceConfig}, + trace::init_subscriber, + Error, +}; use async_trait::async_trait; +use backoff::{Error as BackoffError, ExponentialBackoff}; use bitcoin::{cli::BitcoinOpts as BitcoinConfig, BitcoinCoreApi, Error as BitcoinError}; use futures::{future::Either, Future, FutureExt}; use governor::{Quota, RateLimiter}; @@ -7,22 +13,14 @@ use runtime::{ cli::ConnectionOpts as ParachainConfig, CurrencyId, InterBtcParachain as BtcParachain, InterBtcSigner, PrettyPrint, RuntimeCurrencyInfo, VaultId, }; -use std::{fmt, sync::Arc, time::Duration}; - -mod cli; -mod error; -mod trace; - -pub use cli::{LoggingFormat, MonitoringConfig, RestartPolicy, ServiceConfig}; -pub use error::Error; pub use runtime::{ShutdownReceiver, ShutdownSender}; -pub use trace::init_subscriber; +use std::{sync::Arc, time::Duration}; pub use warp; pub type DynBitcoinCoreApi = Arc; #[async_trait] -pub trait Service { +pub trait Service { const NAME: &'static str; const VERSION: &'static str; @@ -36,7 +34,7 @@ pub trait Service { constructor: Box Result + Send + Sync>, keyname: String, ) -> Self; - async fn start(&self) -> Result<(), Error>; + async fn start(&self) -> Result<(), BackoffError>; } pub struct ConnectionManager { @@ -77,9 +75,7 @@ impl ConnectionManager { } } - pub async fn start, InnerError: fmt::Display>( - &self, - ) -> Result<(), Error> { + pub async fn start>(&self) -> Result<(), Error> { loop { tracing::info!("Version: {}", S::VERSION); tracing::info!("AccountId: {}", self.signer.account_id.pretty_print()); @@ -110,6 +106,7 @@ impl ConnectionManager { // master wallet if we switch to descriptor wallets let bitcoin_core_shared = config_copy.new_client_with_network(Some(format!("{prefix}-shared")), network_copy)?; + bitcoin_core_shared.create_or_load_wallet().await?; let constructor = move |vault_id: VaultId| { let collateral_currency: CurrencyId = vault_id.collateral_currency(); @@ -137,18 +134,17 @@ impl ConnectionManager { Box::new(constructor), self.db_path.clone(), ); - match service.start().await { - Err(err @ Error::Abort(_)) => { - tracing::warn!("Disconnected: {}", err); - return Err(err); - } - Err(err) => { - tracing::warn!("Disconnected: {}", err); - } - _ => { - tracing::warn!("Disconnected"); + + let backoff = ExponentialBackoff::default(); + + backoff::future::retry(backoff, || async { + match service.start().await { + Ok(()) => Ok(()), + Err(err) => Err(err), } - } + }) + .await?; + // propagate shutdown signal from main tasks let _ = shutdown_tx.send(()); @@ -222,11 +218,3 @@ where { tokio::spawn(run_cancelable(shutdown_rx, future)); } - -pub async fn on_shutdown(shutdown_tx: ShutdownSender, future2: impl Future) { - let mut shutdown_rx = shutdown_tx.subscribe(); - let future1 = shutdown_rx.recv().fuse(); - - let _ = future1.await; - future2.await; -} diff --git a/vault/src/delay.rs b/vault/src/delay.rs index d11bce07b..b060471cd 100644 --- a/vault/src/delay.rs +++ b/vault/src/delay.rs @@ -1,8 +1,6 @@ use async_trait::async_trait; use bitcoin::{sha256, Hash}; -use runtime::{ - sp_core::crypto::AccountId32, AccountId, Error as RuntimeError, InterBtcParachain, UtilFuncs, VaultRegistryPallet, -}; +use runtime::{AccountId, Error as RuntimeError, InterBtcParachain, UtilFuncs, VaultRegistryPallet}; use std::fmt; #[async_trait] diff --git a/vault/src/error.rs b/vault/src/error.rs index 1c6bce64a..fafc4de0d 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -1,12 +1,12 @@ -use std::string::FromUtf8Error; - use bitcoin::Error as BitcoinError; use jsonrpc_core_client::RpcError; use parity_scale_codec::Error as CodecError; use rocksdb::Error as RocksDbError; use runtime::Error as RuntimeError; use serde_json::Error as SerdeJsonError; +use std::{io::Error as IoError, num::ParseIntError, string::FromUtf8Error}; use thiserror::Error; +use tokio::task::JoinError as TokioJoinError; use tokio_stream::wrappers::errors::BroadcastStreamRecvError; #[derive(Error, Debug)] @@ -44,10 +44,20 @@ pub enum Error { FromUtf8Error(#[from] FromUtf8Error), #[error("BroadcastStreamRecvError: {0}")] BroadcastStreamRecvError(#[from] BroadcastStreamRecvError), -} - -impl From for service::Error { - fn from(err: Error) -> Self { - Self::Retry(err) - } + #[error("Client has shutdown")] + ClientShutdown, + #[error("OsString parsing error")] + OsStringError, + #[error("File already exists")] + FileAlreadyExists, + #[error("There is a services already running on the system, with pid {0}")] + ServiceAlreadyRunning(u32), + #[error("Process with pid {0} not found")] + ProcessNotFound(String), + #[error("ParseIntError: {0}")] + ParseIntError(#[from] ParseIntError), + #[error("TokioError: {0}")] + TokioError(#[from] TokioJoinError), + #[error("System I/O error: {0}")] + IoError(#[from] IoError), } diff --git a/vault/src/execution.rs b/vault/src/execution.rs index ac2dccc48..285a2d79b 100644 --- a/vault/src/execution.rs +++ b/vault/src/execution.rs @@ -1,4 +1,10 @@ -use crate::{error::Error, metrics::update_bitcoin_metrics, system::VaultData, VaultIdManager, YIELD_RATE}; +use crate::{ + error::Error, + metrics::update_bitcoin_metrics, + service::{spawn_cancelable, DynBitcoinCoreApi, ShutdownSender}, + system::VaultData, + VaultIdManager, YIELD_RATE, +}; use bitcoin::{ Error as BitcoinError, Hash, SatPerVbyte, Transaction, TransactionExt, TransactionMetadata, Txid, BLOCK_INTERVAL as BITCOIN_BLOCK_INTERVAL, @@ -11,7 +17,6 @@ use runtime::{ RedeemRequestStatus, ReplacePallet, ReplaceRequestStatus, SecurityPallet, UtilFuncs, VaultId, VaultRegistryPallet, H256, }; -use service::{spawn_cancelable, DynBitcoinCoreApi, Error as ServiceError, ShutdownSender}; use std::{collections::HashMap, convert::TryInto, time::Duration}; use tokio::time::sleep; use tokio_stream::wrappers::BroadcastStream; @@ -404,7 +409,7 @@ impl Request { // Retry until success or timeout, explicitly handle the cases // where the redeem has expired or the rpc has disconnected runtime::notify_retry( - || (execute)(¶chain_rpc, self.hash, &tx_metadata.proof, &tx_metadata.raw_tx), + || (execute)(¶chain_rpc, self.hash, &tx_metadata.proof), |result| async { match result { Ok(ok) => Ok(ok), @@ -475,7 +480,7 @@ pub async fn execute_open_requests( num_confirmations: u32, payment_margin: Duration, auto_rbf: bool, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let parachain_rpc = ¶chain_rpc; let vault_id = parachain_rpc.get_account_id().clone(); @@ -665,15 +670,15 @@ mod tests { use async_trait::async_trait; use bitcoin::{ json, Address, Amount, BitcoinCoreApi, Block, BlockHash, BlockHeader, Error as BitcoinError, Hash, Network, - PrivateKey, PublicKey, Transaction, TransactionMetadata, Txid, + PrivateKey, PublicKey, RawTransactionProof, Transaction, TransactionMetadata, Txid, }; use jsonrpc_core::serde_json::{Map, Value}; use runtime::{ sp_core::H160, AccountId, AssetMetadata, BitcoinBlockHeight, BlockNumber, BtcPublicKey, CurrencyId, - Error as RuntimeError, ErrorCode, FeeRateUpdateReceiver, InterBtcRichBlockHeader, InterBtcVault, OracleKey, - RawBlockHeader, StatusCode, Token, DOT, IBTC, + Error as RuntimeError, FeeRateUpdateReceiver, InterBtcRichBlockHeader, InterBtcVault, OracleKey, + RawBlockHeader, Token, DOT, IBTC, }; - use std::{collections::BTreeSet, sync::Arc}; + use std::sync::Arc; macro_rules! assert_ok { ( $x:expr $(,)? ) => { @@ -724,7 +729,7 @@ mod tests { #[async_trait] pub trait RedeemPallet { async fn request_redeem(&self, amount: u128, btc_address: BtcAddress, vault_id: &VaultId) -> Result; - async fn execute_redeem(&self, redeem_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_redeem(&self, redeem_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_redeem(&self, redeem_id: H256, reimburse: bool) -> Result<(), RuntimeError>; async fn get_redeem_request(&self, redeem_id: H256) -> Result; async fn get_vault_redeem_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -736,7 +741,7 @@ mod tests { async fn request_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn withdraw_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn accept_replace(&self, new_vault: &VaultId, old_vault: &VaultId, amount_btc: u128, collateral: u128, btc_address: BtcAddress) -> Result<(), RuntimeError>; - async fn execute_replace(&self, replace_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_replace(&self, replace_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_replace(&self, replace_id: H256) -> Result<(), RuntimeError>; async fn get_new_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; async fn get_old_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -762,8 +767,6 @@ mod tests { #[async_trait] pub trait SecurityPallet { - async fn get_parachain_status(&self) -> Result; - async fn get_error_codes(&self) -> Result, RuntimeError>; async fn get_current_active_block_number(&self) -> Result; } @@ -910,7 +913,7 @@ mod tests { parachain_rpc .expect_get_current_active_block_number() .returning(move || Ok(current_parachain_height)); - parachain_rpc.expect_execute_redeem().returning(|_, _, _| Ok(())); + parachain_rpc.expect_execute_redeem().returning(|_, _| Ok(())); parachain_rpc.expect_wait_for_block_in_relay().returning(|_, _| Ok(())); parachain_rpc @@ -928,8 +931,12 @@ mod tests { mock_bitcoin.expect_wait_for_transaction_metadata().returning(|_, _| { Ok(TransactionMetadata { txid: Txid::all_zeros(), - proof: vec![], - raw_tx: vec![], + proof: RawTransactionProof { + coinbase_tx_proof: vec![], + raw_coinbase_tx: vec![], + raw_user_tx: vec![], + user_tx_proof: vec![], + }, block_height: 0, block_hash: BlockHash::all_zeros(), fee: None, @@ -1042,10 +1049,7 @@ mod tests { .expect_get_current_active_block_number() .times(1) .returning(|| Ok(50)); - parachain_rpc - .expect_execute_replace() - .times(1) - .returning(|_, _, _| Ok(())); + parachain_rpc.expect_execute_replace().times(1).returning(|_, _| Ok(())); parachain_rpc .expect_wait_for_block_in_relay() .times(1) @@ -1062,8 +1066,12 @@ mod tests { mock_bitcoin.expect_wait_for_transaction_metadata().returning(|_, _| { Ok(TransactionMetadata { txid: Txid::all_zeros(), - proof: vec![], - raw_tx: vec![], + proof: RawTransactionProof { + coinbase_tx_proof: vec![], + raw_coinbase_tx: vec![], + raw_user_tx: vec![], + user_tx_proof: vec![], + }, block_height: 0, block_hash: BlockHash::all_zeros(), fee: None, diff --git a/vault/src/issue.rs b/vault/src/issue.rs index a621df503..431ab0f7f 100644 --- a/vault/src/issue.rs +++ b/vault/src/issue.rs @@ -1,6 +1,6 @@ use crate::{ - delay::RandomDelay, metrics::publish_expected_bitcoin_balance, system::DatabaseConfig, Error, Event, IssueRequests, - VaultIdManager, + delay::RandomDelay, metrics::publish_expected_bitcoin_balance, service::DynBitcoinCoreApi, system::DatabaseConfig, + Error, Event, IssueRequests, VaultIdManager, }; use bitcoin::{BlockHash, Error as BitcoinError, Hash, PublicKey, Transaction, TransactionExt}; use futures::{channel::mpsc::Sender, future, SinkExt, StreamExt, TryFutureExt}; @@ -9,7 +9,6 @@ use runtime::{ InterBtcIssueRequest, InterBtcParachain, IssuePallet, IssueRequestStatus, PartialAddress, PrettyPrint, RequestIssueEvent, UtilFuncs, H256, }; -use service::{DynBitcoinCoreApi, Error as ServiceError}; use sha2::{Digest, Sha256}; use std::{ sync::Arc, @@ -48,7 +47,7 @@ pub async fn process_issue_requests( btc_start_height: u32, num_confirmations: u32, random_delay: Arc>, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { // NOTE: we should not stream transactions if using the light client // since it is quite expensive to fetch all transactions per block let mut stream = @@ -75,7 +74,7 @@ pub async fn process_issue_requests( } // stream closed, restart client - Err(ServiceError::ClientShutdown) + Err(Error::ClientShutdown) } #[derive(serde::Serialize, serde::Deserialize, Clone, Default, PartialEq, Debug)] @@ -296,12 +295,9 @@ async fn process_transaction_and_execute_issue( return Ok(()); } - // found tx, submit proof - let txid = transaction.txid(); - - // bitcoin core is currently blocking, no need to try_join - let raw_tx = bitcoin_core.get_raw_tx(&txid, &block_hash).await?; - let proof = bitcoin_core.get_proof(txid, &block_hash).await?; + let tx_metadata = bitcoin_core + .wait_for_transaction_metadata(transaction.txid(), num_confirmations) + .await?; tracing::info!( "Executing issue #{:?} on behalf of user {:?} with vault {:?}", @@ -309,7 +305,7 @@ async fn process_transaction_and_execute_issue( issue.requester.pretty_print(), issue.vault.pretty_print() ); - match btc_parachain.execute_issue(issue_id, &proof, &raw_tx).await { + match btc_parachain.execute_issue(issue_id, &tx_metadata.proof).await { Ok(_) => (), Err(err) if err.is_issue_completed() => { tracing::info!("Issue #{} has already been completed", issue_id); @@ -359,7 +355,7 @@ pub async fn listen_for_issue_requests( btc_parachain: InterBtcParachain, event_channel: Sender, issue_set: Arc, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let btc_parachain = &btc_parachain; let event_channel = &event_channel; let issue_set = &issue_set; @@ -415,7 +411,7 @@ pub async fn listen_for_issue_executes( btc_parachain: InterBtcParachain, event_channel: Sender, issue_set: Arc, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let btc_parachain = &btc_parachain; let event_channel = &event_channel; let issue_set = &issue_set; @@ -447,7 +443,7 @@ pub async fn listen_for_issue_executes( pub async fn listen_for_issue_cancels( btc_parachain: InterBtcParachain, issue_set: Arc, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let issue_set = &issue_set; btc_parachain .on_event::( diff --git a/vault/src/lib.rs b/vault/src/lib.rs index 0aef2d5a7..037723367 100644 --- a/vault/src/lib.rs +++ b/vault/src/lib.rs @@ -12,12 +12,20 @@ pub mod process; mod redeem; pub mod relay; mod replace; +// pub mod services; +mod cli; +mod connection_manger; mod system; +mod trace; mod types; pub mod service { pub use crate::{ cancellation::{CancellationScheduler, IssueCanceller, ReplaceCanceller}, + connection_manger::{ + init_subscriber, spawn_cancelable, wait_or_shutdown, warp, warp::Filter, ConnectionManager, + DynBitcoinCoreApi, MonitoringConfig, Service, ServiceConfig, ShutdownSender, + }, execution::execute_open_requests, issue::{ listen_for_issue_cancels, listen_for_issue_executes, listen_for_issue_requests, process_issue_requests, diff --git a/vault/src/main.rs b/vault/src/main.rs index 70dfa6c6a..90561b772 100644 --- a/vault/src/main.rs +++ b/vault/src/main.rs @@ -6,7 +6,6 @@ use runtime::{ InterBtcSigner, KeyPair, DEFAULT_SPEC_NAME, SS58_PREFIX, }; use secp256k1::{rand::thread_rng, SecretKey}; -use service::{warp, warp::Filter, ConnectionManager, Error as ServiceError, MonitoringConfig, ServiceConfig}; use signal_hook::consts::*; use signal_hook_tokio::Signals; use std::{ @@ -19,6 +18,7 @@ use tokio_stream::StreamExt; use vault::{ metrics::{self, increment_restart_counter}, process::PidFile, + service::{warp, warp::Filter, ConnectionManager, MonitoringConfig, ServiceConfig}, Error, VaultService, VaultServiceConfig, ABOUT, AUTHORS, NAME, VERSION, }; @@ -44,11 +44,11 @@ enum Commands { } // write the file to stdout or disk - fail if it already exists -fn try_write_file>(output: &Option, data: D) -> Result<(), ServiceError> { +fn try_write_file>(output: &Option, data: D) -> Result<(), Error> { let data = data.as_ref(); if let Some(output) = output { if output.exists() { - Err(ServiceError::FileAlreadyExists) + Err(Error::FileAlreadyExists) } else { std::fs::write(output, data)?; Ok(()) @@ -70,7 +70,7 @@ struct GenerateBitcoinKeyOpts { } impl GenerateBitcoinKeyOpts { - fn generate_and_write(&self) -> Result<(), ServiceError> { + fn generate_and_write(&self) -> Result<(), Error> { let secret_key = SecretKey::new(&mut thread_rng()); let private_key = PrivateKey::new(secret_key, self.network); let wif = private_key.to_wif(); @@ -88,7 +88,7 @@ struct GenerateParachainKeyOpts { } impl GenerateParachainKeyOpts { - fn generate_and_write(&self) -> Result<(), ServiceError> { + fn generate_and_write(&self) -> Result<(), Error> { let (pair, phrase, _) = KeyPair::generate_with_phrase(None); let mut keys = serde_json::Map::new(); @@ -130,9 +130,9 @@ pub struct RunVaultOpts { pub monitoring: MonitoringConfig, } -async fn catch_signals(mut shutdown_signals: Signals, future: F) -> Result<(), ServiceError> +async fn catch_signals(mut shutdown_signals: Signals, future: F) -> Result<(), Error> where - F: Future>> + Send + 'static, + F: Future> + Send + 'static, { let blocking_task = tokio::task::spawn(future); tokio::select! { @@ -149,7 +149,7 @@ where Ok(()) } -async fn start() -> Result<(), ServiceError> { +async fn start() -> Result<(), Error> { let cli: Cli = Cli::parse(); let opts = cli.opts; opts.service.logging_format.init_subscriber(); @@ -216,7 +216,7 @@ async fn start() -> Result<(), ServiceError> { let _pidfile = PidFile::create(&String::from(DEFAULT_SPEC_NAME), signer.account_id(), &mut sys)?; // Unless termination signals are caught, the PID file is not dropped. - let main_task = async move { vault_connection_manager.start::().await }; + let main_task = async move { vault_connection_manager.start::().await }; catch_signals( Signals::new([SIGHUP, SIGTERM, SIGINT, SIGQUIT]).expect("Failed to set up signal listener."), main_task, diff --git a/vault/src/metrics.rs b/vault/src/metrics.rs index a62186473..49caa01e7 100644 --- a/vault/src/metrics.rs +++ b/vault/src/metrics.rs @@ -2,6 +2,10 @@ use std::{collections::HashMap, convert::TryInto, sync::Arc}; use crate::{ execution::parachain_blocks_to_bitcoin_blocks_rounded_up, + service::{ + warp::{Rejection, Reply}, + DynBitcoinCoreApi, + }, system::{VaultData, VaultIdManager}, Error, }; @@ -19,10 +23,6 @@ use runtime::{ RedeemRequestStatus, ReplacePallet, RuntimeCurrencyInfo, SecurityPallet, UtilFuncs, VaultId, VaultRegistryPallet, H256, }; -use service::{ - warp::{Rejection, Reply}, - DynBitcoinCoreApi, Error as ServiceError, -}; use std::time::Duration; use tokio::{sync::RwLock, time::sleep}; use tokio_metrics::TaskMetrics; @@ -204,7 +204,7 @@ impl PerCurrencyMetrics { vault: &VaultData, parachain_rpc: P, bitcoin_transactions: Vec, - ) -> Result<(), ServiceError> { + ) -> Result<(), Error> { let vault_id = &vault.vault_id; // update fee surplus if let Ok((redeem_requests, replace_requests)) = try_join!( @@ -317,7 +317,7 @@ pub async fn metrics_handler() -> Result { Ok(metrics) } -fn raw_value_as_currency(value: u128, currency: CurrencyId) -> Result> { +fn raw_value_as_currency(value: u128, currency: CurrencyId) -> Result { let scaling_factor = currency.one()? as f64; Ok(value as f64 / scaling_factor) } @@ -325,7 +325,7 @@ fn raw_value_as_currency(value: u128, currency: CurrencyId) -> Result( vault: &VaultData, parachain_rpc: &P, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { if let Ok(actual_collateral) = parachain_rpc.get_vault_total_collateral(vault.vault_id.clone()).await { let actual_collateral = raw_value_as_currency(actual_collateral, vault.vault_id.collateral_currency())?; vault.metrics.locked_collateral.set(actual_collateral); @@ -336,7 +336,7 @@ pub async fn publish_locked_collateral( pub async fn publish_required_collateral( vault: &VaultData, parachain_rpc: &P, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { if let Ok(required_collateral) = parachain_rpc .get_required_collateral_for_vault(vault.vault_id.clone()) .await @@ -362,7 +362,7 @@ pub async fn update_bitcoin_metrics( vault: &VaultData, new_fee_entry: Option, fee_budget: Option, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { // update the average fee if let Some(amount) = new_fee_entry { { @@ -386,7 +386,7 @@ pub async fn update_bitcoin_metrics( Ok(()) } -async fn publish_fee_budget_surplus(vault: &VaultData) -> Result<(), ServiceError> { +async fn publish_fee_budget_surplus(vault: &VaultData) -> Result<(), Error> { let surplus = *FEE_BUDGET_SURPLUS.data.read().await; FEE_BUDGET_SURPLUS .gauge @@ -414,7 +414,7 @@ fn publish_bitcoin_balance(btc_rpc: &DynBitcoinCoreApi) { async fn publish_native_currency_balance( parachain_rpc: &P, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let native_currency = parachain_rpc.get_native_currency_id(); if let Ok(balance) = parachain_rpc.get_free_balance(native_currency).await { let balance = raw_value_as_currency(balance, native_currency)?; @@ -556,7 +556,7 @@ async fn publish_redeem_count(vault_id_manager: &V, redeems: pub async fn monitor_bridge_metrics( parachain_rpc: InterBtcParachain, vault_id_manager: VaultIdManager, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let parachain_rpc = ¶chain_rpc; let vault_id_manager = &vault_id_manager; parachain_rpc @@ -589,7 +589,7 @@ pub async fn monitor_bridge_metrics( pub async fn poll_metrics( parachain_rpc: P, vault_id_manager: VaultIdManager, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let parachain_rpc = ¶chain_rpc; let vault_id_manager = &vault_id_manager; @@ -613,7 +613,7 @@ pub async fn poll_metrics( vault: &VaultData, parachain_rpc: P, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { if let Ok(v) = parachain_rpc.get_vault(&vault.vault_id).await { let lowerbound = v.issued_tokens.saturating_sub(v.to_be_redeemed_tokens); let upperbound = v.issued_tokens.saturating_add(v.to_be_issued_tokens); @@ -634,7 +634,7 @@ pub async fn publish_expected_bitcoin_balance( pub async fn publish_tokio_metrics( mut metrics_iterators: HashMap>, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let frequency = Duration::from_millis(TOKIO_POLLING_INTERVAL_MS); loop { for (key, val) in metrics_iterators.iter_mut() { @@ -661,7 +661,7 @@ mod tests { use async_trait::async_trait; use bitcoin::{ json, Address, Amount, BitcoinCoreApi, Block, BlockHash, BlockHeader, Error as BitcoinError, Network, - PrivateKey, PublicKey, SatPerVbyte, Transaction, TransactionMetadata, Txid, + PrivateKey, PublicKey, RawTransactionProof, SatPerVbyte, Transaction, TransactionMetadata, Txid, }; use jsonrpc_core::serde_json::{Map, Value}; use runtime::{ @@ -669,9 +669,8 @@ mod tests { subxt::utils::Static, AccountId, AssetMetadata, AssetRegistry, Balance, BlockNumber, BtcAddress, BtcPublicKey, CurrencyId::{self, ForeignAsset, LendToken}, - Error as RuntimeError, ErrorCode, InterBtcIssueRequest, InterBtcRedeemRequest, InterBtcReplaceRequest, - InterBtcVault, LendingAssets, RequestIssueEvent, StatusCode, Token, VaultId, VaultStatus, DOT, H256, IBTC, - INTR, + Error as RuntimeError, InterBtcIssueRequest, InterBtcRedeemRequest, InterBtcReplaceRequest, InterBtcVault, + LendingAssets, RequestIssueEvent, Token, VaultId, VaultStatus, DOT, H256, IBTC, INTR, }; mockall::mock! { @@ -692,7 +691,7 @@ mod tests { #[async_trait] pub trait IssuePallet { async fn request_issue(&self, amount: u128, vault_id: &VaultId) -> Result; - async fn execute_issue(&self, issue_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_issue(&self, issue_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_issue(&self, issue_id: H256) -> Result<(), RuntimeError>; async fn get_issue_request(&self, issue_id: H256) -> Result; async fn get_vault_issue_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -703,7 +702,7 @@ mod tests { #[async_trait] pub trait RedeemPallet { async fn request_redeem(&self, amount: u128, btc_address: BtcAddress, vault_id: &VaultId) -> Result; - async fn execute_redeem(&self, redeem_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_redeem(&self, redeem_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_redeem(&self, redeem_id: H256, reimburse: bool) -> Result<(), RuntimeError>; async fn get_redeem_request(&self, redeem_id: H256) -> Result; async fn get_vault_redeem_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -742,7 +741,7 @@ mod tests { async fn request_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn withdraw_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn accept_replace(&self, new_vault: &VaultId, old_vault: &VaultId, amount_btc: u128, collateral: u128, btc_address: BtcAddress) -> Result<(), RuntimeError>; - async fn execute_replace(&self, replace_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_replace(&self, replace_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_replace(&self, replace_id: H256) -> Result<(), RuntimeError>; async fn get_new_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; async fn get_old_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; @@ -753,10 +752,6 @@ mod tests { #[async_trait] pub trait SecurityPallet { - async fn get_parachain_status(&self) -> Result; - - async fn get_error_codes(&self) -> Result, RuntimeError>; - /// Gets the current active block number of the parachain async fn get_current_active_block_number(&self) -> Result; } diff --git a/vault/src/process.rs b/vault/src/process.rs index 8d6797790..47507374b 100644 --- a/vault/src/process.rs +++ b/vault/src/process.rs @@ -8,12 +8,11 @@ use std::{ use crate::Error; use runtime::AccountId; -use service::Error as ServiceError; use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; pub trait SystemProcess { fn refresh_process(&mut self, pid: Pid) -> bool; - fn process_name(&self, pid: Pid) -> Result>; + fn process_name(&self, pid: Pid) -> Result; } impl SystemProcess for System { @@ -21,9 +20,8 @@ impl SystemProcess for System { ::refresh_process(self, pid) } - fn process_name(&self, pid: Pid) -> Result> { - let process = - ::process(self, pid).ok_or_else(|| ServiceError::ProcessNotFound(pid.to_string()))?; + fn process_name(&self, pid: Pid) -> Result { + let process = ::process(self, pid).ok_or_else(|| Error::ProcessNotFound(pid.to_string()))?; Ok(process.name().to_string()) } } @@ -42,23 +40,19 @@ impl PidFile { } } - pub fn create( - spec_name: &String, - account_id: &AccountId, - sys: &mut impl SystemProcess, - ) -> Result> { + pub fn create(spec_name: &String, account_id: &AccountId, sys: &mut impl SystemProcess) -> Result { let mut pid_file = Self::new(spec_name, account_id); if pid_file.path.exists() { let pid = pid_file.pid()?; if sys.refresh_process(pid) { match pid_name_matches_existing_client(sys, pid) { - Ok(true) => return Err(ServiceError::ServiceAlreadyRunning(pid.as_u32())), + Ok(true) => return Err(Error::ServiceAlreadyRunning(pid.as_u32())), Ok(false) => (), // There is a very small chance that a `pid` with a running process is killed exactly before // `pid_name_matches_client` is run and `ProcessNotFound` is thrown. This is as if // the `refresh_process` check had returned `false` (the pidfile process not running) // so the error should not be propagated. - Err(ServiceError::ProcessNotFound(_)) => (), + Err(Error::ProcessNotFound(_)) => (), Err(e) => return Err(e), } } @@ -69,7 +63,7 @@ impl PidFile { .clone() .into_os_string() .into_string() - .map_err(|_| ServiceError::OsStringError)?, + .map_err(|_| Error::OsStringError)?, ); } @@ -80,7 +74,7 @@ impl PidFile { .clone() .into_os_string() .into_string() - .map_err(|_| ServiceError::OsStringError)?, + .map_err(|_| Error::OsStringError)?, ); pid_file.set_pid(process::id())?; Ok(pid_file) @@ -92,13 +86,13 @@ impl PidFile { std::env::temp_dir().join(file_name) } - pub fn pid(&self) -> Result> { + pub fn pid(&self) -> Result { let mut pid = fs::read_to_string(self.path.clone())?; pid = pid.strip_suffix('\n').unwrap_or(&pid).to_string(); Ok(Pid::from_str(&pid)?) } - pub fn set_pid(&mut self, pid: u32) -> Result> { + pub fn set_pid(&mut self, pid: u32) -> Result { self.created_pidfile = true; let mut file = File::create(&self.path)?; file.write_all(format!("{pid}\n").as_bytes())?; @@ -106,7 +100,7 @@ impl PidFile { Ok(file) } - pub fn remove(&mut self) -> Result<(), ServiceError> { + pub fn remove(&mut self) -> Result<(), Error> { if self.created_pidfile { tracing::info!( "Removing PID file at: {}", @@ -114,7 +108,7 @@ impl PidFile { .clone() .into_os_string() .into_string() - .map_err(|_| ServiceError::OsStringError)?, + .map_err(|_| Error::OsStringError)?, ); fs::remove_file(&self.path)?; self.path = PathBuf::default(); @@ -133,10 +127,7 @@ impl Drop for PidFile { } } -pub fn pid_name_matches_existing_client( - sys: &mut impl SystemProcess, - pidfile_value: Pid, -) -> Result> { +pub fn pid_name_matches_existing_client(sys: &mut impl SystemProcess, pidfile_value: Pid) -> Result { let client_pid = Pid::from_u32(process::id()); Ok(sys.process_name(client_pid)? == sys.process_name(pidfile_value)?) } @@ -161,7 +152,7 @@ mod tests { pub trait SystemProcess { fn refresh_process(&mut self, pid: Pid) -> bool; - fn process_name(&self, pid: Pid) -> Result>; + fn process_name(&self, pid: Pid) -> Result; } } @@ -216,7 +207,7 @@ mod tests { assert_err!( PidFile::create(&dummy_spec_name, &dummy_account_id, &mut sys), #[allow(unused_variables)] - Err(ServiceError::ServiceAlreadyRunning(own_pid)) + Err(Error::ServiceAlreadyRunning(own_pid)) ); } @@ -237,7 +228,7 @@ mod tests { if pid == Pid::from_u32(own_pid) { Ok("vault".to_string()) } else { - Err(ServiceError::ProcessNotFound(pid.to_string())) + Err(Error::ProcessNotFound(pid.to_string())) } }); PidFile::create(&dummy_spec_name, &dummy_account_id, &mut sys).unwrap(); diff --git a/vault/src/redeem.rs b/vault/src/redeem.rs index 2aa542887..0f8e19bda 100644 --- a/vault/src/redeem.rs +++ b/vault/src/redeem.rs @@ -1,6 +1,11 @@ -use crate::{execution::*, metrics::publish_expected_bitcoin_balance, system::VaultIdManager, Error}; +use crate::{ + execution::*, + metrics::publish_expected_bitcoin_balance, + service::{spawn_cancelable, ShutdownSender}, + system::VaultIdManager, + Error, +}; use runtime::{InterBtcParachain, RedeemPallet, RequestRedeemEvent}; -use service::{spawn_cancelable, Error as ServiceError, ShutdownSender}; use std::time::Duration; /// Listen for RequestRedeemEvent directed at this vault; upon reception, transfer /// bitcoin and call execute_redeem @@ -18,7 +23,7 @@ pub async fn listen_for_redeem_requests( num_confirmations: u32, payment_margin: Duration, auto_rbf: bool, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { parachain_rpc .on_event::( |event| async { diff --git a/vault/src/refund.rs b/vault/src/refund.rs index b726dd0af..cf8553b07 100644 --- a/vault/src/refund.rs +++ b/vault/src/refund.rs @@ -1,6 +1,6 @@ use crate::{execution::*, system::VaultIdManager}; use runtime::{InterBtcParachain, RequestRefundEvent}; -use service::{spawn_cancelable, Error as ServiceError, ShutdownSender}; +use crate::service::{spawn_cancelable, ShutdownSender}; /// Listen for RequestRefundEvent directed at this vault; upon reception, transfer /// bitcoin and call execute_refund @@ -19,7 +19,7 @@ pub async fn listen_for_refund_requests( num_confirmations: u32, process_refunds: bool, auto_rbf: bool, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { parachain_rpc .on_event::( |event| async { diff --git a/vault/src/relay/backing.rs b/vault/src/relay/backing.rs index 4f3c465eb..c8faef6f9 100644 --- a/vault/src/relay/backing.rs +++ b/vault/src/relay/backing.rs @@ -1,7 +1,7 @@ use super::Error; +use crate::service::DynBitcoinCoreApi; use async_trait::async_trait; use bitcoin::{serialize, BitcoinCoreApi, Error as BitcoinError}; -use service::DynBitcoinCoreApi; #[async_trait] pub trait Backing { diff --git a/vault/src/relay/mod.rs b/vault/src/relay/mod.rs index 64cf72bd8..007f319cc 100644 --- a/vault/src/relay/mod.rs +++ b/vault/src/relay/mod.rs @@ -1,5 +1,5 @@ +use crate::{service::DynBitcoinCoreApi, Error as VaultError}; use runtime::InterBtcParachain; -use service::{DynBitcoinCoreApi, Error as ServiceError}; use std::{sync::Arc, time::Duration}; use tokio::time::sleep; @@ -185,9 +185,7 @@ impl Runner { } } -pub async fn run_relayer( - runner: Runner, -) -> Result<(), ServiceError> { +pub async fn run_relayer(runner: Runner) -> Result<(), VaultError> { loop { match runner.submit_next().await { Ok(_) => (), @@ -195,10 +193,10 @@ pub async fn run_relayer( tracing::info!("Attempted to submit block that already exists") } Err(Error::RuntimeError(ref err)) if err.is_rpc_disconnect_error() => { - return Err(ServiceError::ClientShutdown); + return Err(VaultError::ClientShutdown); } Err(Error::BitcoinError(err)) if err.is_transport_error() => { - return Err(ServiceError::ClientShutdown); + return Err(VaultError::ClientShutdown); } Err(err) => { tracing::error!("Failed to submit_next: {}", err); diff --git a/vault/src/replace.rs b/vault/src/replace.rs index b45897b52..9201067c2 100644 --- a/vault/src/replace.rs +++ b/vault/src/replace.rs @@ -1,5 +1,9 @@ use crate::{ - cancellation::Event, error::Error, execution::Request, metrics::publish_expected_bitcoin_balance, + cancellation::Event, + error::Error, + execution::Request, + metrics::publish_expected_bitcoin_balance, + service::{spawn_cancelable, DynBitcoinCoreApi, ShutdownSender}, system::VaultIdManager, }; use bitcoin::Error as BitcoinError; @@ -8,7 +12,6 @@ use runtime::{ AcceptReplaceEvent, BtcAddress, CollateralBalancesPallet, ExecuteReplaceEvent, InterBtcParachain, PartialAddress, PrettyPrint, ReplacePallet, RequestReplaceEvent, UtilFuncs, VaultId, VaultRegistryPallet, }; -use service::{spawn_cancelable, DynBitcoinCoreApi, Error as ServiceError, ShutdownSender}; use std::time::Duration; /// Listen for AcceptReplaceEvent directed at this vault and continue the replacement @@ -26,7 +29,7 @@ pub async fn listen_for_accept_replace( num_confirmations: u32, payment_margin: Duration, auto_rbf: bool, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let parachain_rpc = ¶chain_rpc; let vault_id_manager = &vault_id_manager; let shutdown_tx = &shutdown_tx; @@ -93,7 +96,7 @@ pub async fn listen_for_replace_requests( btc_rpc: VaultIdManager, event_channel: Sender, accept_replace_requests: bool, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let parachain_rpc = ¶chain_rpc; let btc_rpc = &btc_rpc; let event_channel = &event_channel; @@ -185,7 +188,7 @@ pub async fn handle_replace_request<'a, P: CollateralBalancesPallet + ReplacePal pub async fn listen_for_execute_replace( parachain_rpc: InterBtcParachain, event_channel: Sender, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let event_channel = &event_channel; let parachain_rpc = ¶chain_rpc; parachain_rpc @@ -210,7 +213,7 @@ mod tests { use async_trait::async_trait; use bitcoin::{ json, Address, Amount, BitcoinCoreApi, Block, BlockHash, BlockHeader, Error as BitcoinError, Network, - PrivateKey, PublicKey, SatPerVbyte, Transaction, TransactionMetadata, Txid, + PrivateKey, PublicKey, RawTransactionProof, SatPerVbyte, Transaction, TransactionMetadata, Txid, }; use runtime::{ AccountId, Balance, BtcAddress, BtcPublicKey, CurrencyId, Error as RuntimeError, InterBtcReplaceRequest, @@ -329,7 +332,7 @@ mod tests { async fn request_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn withdraw_replace(&self, vault_id: &VaultId, amount: u128) -> Result<(), RuntimeError>; async fn accept_replace(&self, new_vault: &VaultId, old_vault: &VaultId, amount_btc: u128, collateral: u128, btc_address: BtcAddress) -> Result<(), RuntimeError>; - async fn execute_replace(&self, replace_id: H256, merkle_proof: &[u8], raw_tx: &[u8]) -> Result<(), RuntimeError>; + async fn execute_replace(&self, replace_id: H256, raw_proof: &RawTransactionProof) -> Result<(), RuntimeError>; async fn cancel_replace(&self, replace_id: H256) -> Result<(), RuntimeError>; async fn get_new_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; async fn get_old_vault_replace_requests(&self, account_id: AccountId) -> Result, RuntimeError>; diff --git a/vault/src/system.rs b/vault/src/system.rs index 4aa883d37..91bdcc485 100644 --- a/vault/src/system.rs +++ b/vault/src/system.rs @@ -4,10 +4,11 @@ use crate::{ faucet, issue, metrics::{poll_metrics, publish_tokio_metrics, PerCurrencyMetrics}, relay::run_relayer, - service::*, + service::{wait_or_shutdown, DynBitcoinCoreApi, MonitoringConfig, Service, ShutdownSender, *}, Event, IssueRequests, CHAIN_HEIGHT_POLLING_INTERVAL, }; use async_trait::async_trait; +use backoff::Error as BackoffError; use bitcoin::{Address, ConversionError, Error as BitcoinError, Network, PublicKey}; use clap::Parser; use futures::{ @@ -22,7 +23,6 @@ use runtime::{ PrettyPrint, RegisterVaultEvent, StoreMainChainHeaderEvent, TryFromSymbol, UpdateActiveBlockEvent, UtilFuncs, VaultCurrencyPair, VaultId, VaultRegistryPallet, }; -use service::{wait_or_shutdown, DynBitcoinCoreApi, Error as ServiceError, MonitoringConfig, Service, ShutdownSender}; use std::{collections::HashMap, pin::Pin, sync::Arc, time::Duration}; use tokio::{sync::RwLock, time::sleep}; @@ -131,7 +131,7 @@ async fn active_block_listener( parachain_rpc: InterBtcParachain, issue_tx: Sender, replace_tx: Sender, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let issue_tx = &issue_tx; let replace_tx = &replace_tx; parachain_rpc @@ -150,7 +150,7 @@ async fn relay_block_listener( parachain_rpc: InterBtcParachain, issue_tx: Sender, replace_tx: Sender, -) -> Result<(), ServiceError> { +) -> Result<(), Error> { let issue_tx = &issue_tx; let replace_tx = &replace_tx; parachain_rpc @@ -356,7 +356,7 @@ impl VaultIdManager { Ok(()) } - pub async fn listen_for_vault_id_registrations(self) -> Result<(), ServiceError> { + pub async fn listen_for_vault_id_registrations(self) -> Result<(), Error> { Ok(self .btc_parachain .on_event::( @@ -420,7 +420,7 @@ pub struct VaultService { } #[async_trait] -impl Service for VaultService { +impl Service for VaultService { const NAME: &'static str = NAME; const VERSION: &'static str = VERSION; @@ -446,15 +446,12 @@ impl Service for VaultService { ) } - async fn start(&self) -> Result<(), ServiceError> { + async fn start(&self) -> Result<(), BackoffError> { self.run_service().await } } -async fn run_and_monitor_tasks( - shutdown_tx: ShutdownSender, - items: Vec<(&str, ServiceTask)>, -) -> Result<(), ServiceError> { +async fn run_and_monitor_tasks(shutdown_tx: ShutdownSender, items: Vec<(&str, ServiceTask)>) -> Result<(), Error> { let (metrics_iterators, tasks): (HashMap, Vec<_>) = items .into_iter() .filter_map(|(name, task)| { @@ -487,7 +484,7 @@ async fn run_and_monitor_tasks( } } -type Task = Pin>> + Send + 'static>>; +type Task = Pin> + Send + 'static>>; enum ServiceTask { Optional(bool, Task), @@ -497,7 +494,7 @@ enum ServiceTask { fn maybe_run(should_run: bool, task: F) -> ServiceTask where F: Future> + Send + 'static, - E: Into>, + E: Into, { ServiceTask::Optional(should_run, Box::pin(task.map_err(|x| x.into()))) } @@ -505,7 +502,7 @@ where fn run(task: F) -> ServiceTask where F: Future> + Send + 'static, - E: Into>, + E: Into, { ServiceTask::Essential(Box::pin(task.map_err(|x| x.into()))) } @@ -584,9 +581,11 @@ impl VaultService { Ok(()) } - async fn run_service(&self) -> Result<(), ServiceError> { - self.validate_bitcoin_network().await.map_err(ServiceError::Abort)?; - + async fn run_service(&self) -> Result<(), BackoffError> { + //ToDo: remove service error put all errors in error + self.validate_bitcoin_network() + .await + .map_err(|err| BackoffError::Permanent(err))?; let account_id = self.btc_parachain.get_account_id().clone(); let parsed_auto_register = self @@ -596,18 +595,21 @@ impl VaultService { .into_iter() .map(|(symbol, amount)| Ok((CurrencyId::try_from_symbol(symbol)?, amount))) .into_iter() - .collect::, Error>>() - .map_err(ServiceError::Abort)?; + .collect::, Error>>()?; // exit if auto-register uses faucet and faucet url not set if parsed_auto_register.iter().any(|(_, o)| o.is_none()) && self.config.faucet_url.is_none() { // TODO: validate before bitcoin / parachain connections - return Err(ServiceError::Abort(Error::FaucetUrlNotSet)); + return Err(BackoffError::Permanent(Error::FaucetUrlNotSet)); } let num_confirmations = match self.config.btc_confirmations { Some(x) => x, - None => self.btc_parachain.get_bitcoin_confirmations().await?, + None => self + .btc_parachain + .get_bitcoin_confirmations() + .await + .map_err(|err| BackoffError::Transient::(err.into()))?, }; tracing::info!("Using {} bitcoin confirmations", num_confirmations); @@ -634,7 +636,7 @@ impl VaultService { .into_iter() .collect::>() { - Err(Error::RuntimeError(err)) if err.is_threshold_not_set() => Err(ServiceError::Abort(err.into())), + Err(Error::RuntimeError(err)) if err.is_threshold_not_set() => Err(BackoffError::Permanent(err.into())), Err(err) => Err(err.into()), Ok(_) => Ok(()), }?; @@ -663,7 +665,7 @@ impl VaultService { ); let shutdown_clone = self.shutdown.clone(); - service::spawn_cancelable(self.shutdown.subscribe(), async move { + spawn_cancelable(self.shutdown.subscribe(), async move { tracing::info!("Checking for open requests..."); match open_request_executor.await { Ok(_) => tracing::info!("Done processing open requests"), @@ -687,7 +689,11 @@ impl VaultService { let random_delay: Arc> = if self.config.no_random_delay { Arc::new(Box::new(ZeroDelay)) } else { - Arc::new(Box::new(OrderedVaultsDelay::new(self.btc_parachain.clone()).await?)) + Arc::new(Box::new( + OrderedVaultsDelay::new(self.btc_parachain.clone()) + .await + .map_err(|err| BackoffError::Transient::(err.into()))?, + )) }; let (issue_event_tx, issue_event_rx) = mpsc::channel::(32); @@ -864,12 +870,13 @@ impl VaultService { run(async move { tokio::time::sleep(RESTART_INTERVAL).await; tracing::info!("Initiating periodic restart..."); - Err(ServiceError::ClientShutdown) + Err(Error::ClientShutdown) }), ), ]; - run_and_monitor_tasks(self.shutdown.clone(), tasks).await + run_and_monitor_tasks(self.shutdown.clone(), tasks).await?; + Ok(()) } async fn maybe_register_public_key(&self) -> Result<(), Error> { diff --git a/service/src/trace.rs b/vault/src/trace.rs similarity index 100% rename from service/src/trace.rs rename to vault/src/trace.rs diff --git a/vault/tests/vault_integration_tests.rs b/vault/tests/vault_integration_tests.rs index 11aec80d3..1c249c3e8 100644 --- a/vault/tests/vault_integration_tests.rs +++ b/vault/tests/vault_integration_tests.rs @@ -16,10 +16,9 @@ use runtime::{ PartialAddress, RedeemPallet, ReplacePallet, ShutdownSender, SudoPallet, UtilFuncs, VaultId, VaultRegistryPallet, }; use serial_test::serial; -use service::DynBitcoinCoreApi; use sp_keyring::AccountKeyring; use std::{process::Child, sync::Arc, time::Duration}; -use vault::{self, Event as CancellationEvent, IssueRequests, VaultIdManager, ZeroDelay}; +use vault::{self, service::DynBitcoinCoreApi, Event as CancellationEvent, IssueRequests, VaultIdManager, ZeroDelay}; const TIMEOUT: Duration = Duration::from_secs(90); @@ -33,7 +32,7 @@ where { let _parachain_runner: Child = start_chain().await.unwrap(); - service::init_subscriber(); + vault::service::init_subscriber(); let (parachain_rpc, _tmp_dir) = default_root_provider(AccountKeyring::Alice).await; parachain_rpc @@ -85,7 +84,7 @@ where #[tokio::test(flavor = "multi_thread")] #[serial] async fn test_redeem_succeeds() { - test_with_vault(|client, vault_id, vault_provider| async move { + test_with_vault(|_client, vault_id, vault_provider| async move { let relayer_provider = setup_provider(AccountKeyring::Bob).await; let user_provider = setup_provider(AccountKeyring::Dave).await; @@ -149,7 +148,7 @@ async fn test_redeem_succeeds() { #[tokio::test(flavor = "multi_thread")] #[serial] async fn test_replace_succeeds() { - test_with_vault(|client, old_vault_id, old_vault_provider| async move { + test_with_vault(|_client, old_vault_id, old_vault_provider| async move { let relayer_provider = setup_provider(AccountKeyring::Bob).await; let new_vault_provider = setup_provider(AccountKeyring::Eve).await; let new_vault_id = VaultId::new( @@ -262,7 +261,7 @@ async fn test_replace_succeeds() { #[tokio::test(flavor = "multi_thread")] #[serial] async fn test_withdraw_replace_succeeds() { - test_with_vault(|client, old_vault_id, old_vault_provider| async move { + test_with_vault(|_client, old_vault_id, old_vault_provider| async move { let relayer_provider = setup_provider(AccountKeyring::Bob).await; let new_vault_provider = setup_provider(AccountKeyring::Eve).await; let new_vault_id = VaultId::new( @@ -595,7 +594,7 @@ async fn test_issue_overpayment_succeeds() { } }), user_provider - .execute_issue(*issue.issue_id, &metadata.proof, &metadata.raw_tx) + .execute_issue(*issue.issue_id, &metadata.proof) .map(Result::unwrap), ) .await; @@ -1033,7 +1032,7 @@ mod test_with_bitcoind { .unwrap(); parachain_rpc - .execute_issue(*issue.issue_id, &metadata.proof, &metadata.raw_tx) + .execute_issue(*issue.issue_id, &metadata.proof) .await .unwrap(); } From 88306132ac0c70789d920c6849be8ed7d1f2246d Mon Sep 17 00:00:00 2001 From: nakul1010 Date: Thu, 3 Aug 2023 11:14:57 +0530 Subject: [PATCH 2/2] fix: add conversion trait --- ...ection_manger.rs => connection_manager.rs} | 21 +++++++++++-------- vault/src/error.rs | 9 ++++++++ vault/src/lib.rs | 7 +++---- vault/src/system.rs | 5 ++--- 4 files changed, 26 insertions(+), 16 deletions(-) rename vault/src/{connection_manger.rs => connection_manager.rs} (94%) diff --git a/vault/src/connection_manger.rs b/vault/src/connection_manager.rs similarity index 94% rename from vault/src/connection_manger.rs rename to vault/src/connection_manager.rs index c50ae2517..d24b69ca5 100644 --- a/vault/src/connection_manger.rs +++ b/vault/src/connection_manager.rs @@ -4,7 +4,7 @@ pub use crate::{ Error, }; use async_trait::async_trait; -use backoff::{Error as BackoffError, ExponentialBackoff}; +use backoff::Error as BackoffError; use bitcoin::{cli::BitcoinOpts as BitcoinConfig, BitcoinCoreApi, Error as BitcoinError}; use futures::{future::Either, Future, FutureExt}; use governor::{Quota, RateLimiter}; @@ -135,15 +135,18 @@ impl ConnectionManager { self.db_path.clone(), ); - let backoff = ExponentialBackoff::default(); - - backoff::future::retry(backoff, || async { - match service.start().await { - Ok(()) => Ok(()), - Err(err) => Err(err), + match service.start().await { + Err(err @ backoff::Error::Permanent(_)) => { + tracing::warn!("Disconnected: {}", err); + return Err(err.into()); } - }) - .await?; + Err(err) => { + tracing::warn!("Disconnected: {}", err); + } + _ => { + tracing::warn!("Disconnected"); + } + }; // propagate shutdown signal from main tasks let _ = shutdown_tx.send(()); diff --git a/vault/src/error.rs b/vault/src/error.rs index fafc4de0d..6a067c129 100644 --- a/vault/src/error.rs +++ b/vault/src/error.rs @@ -61,3 +61,12 @@ pub enum Error { #[error("System I/O error: {0}")] IoError(#[from] IoError), } + +impl From> for Error { + fn from(err: backoff::Error) -> Self { + match err { + backoff::Error::Permanent(err) => err, + backoff::Error::Transient(err) => err, + } + } +} diff --git a/vault/src/lib.rs b/vault/src/lib.rs index 037723367..8dac97932 100644 --- a/vault/src/lib.rs +++ b/vault/src/lib.rs @@ -2,6 +2,8 @@ #![feature(array_zip)] mod cancellation; +mod cli; +mod connection_manager; pub mod delay; mod error; mod execution; @@ -12,9 +14,6 @@ pub mod process; mod redeem; pub mod relay; mod replace; -// pub mod services; -mod cli; -mod connection_manger; mod system; mod trace; mod types; @@ -22,7 +21,7 @@ mod types; pub mod service { pub use crate::{ cancellation::{CancellationScheduler, IssueCanceller, ReplaceCanceller}, - connection_manger::{ + connection_manager::{ init_subscriber, spawn_cancelable, wait_or_shutdown, warp, warp::Filter, ConnectionManager, DynBitcoinCoreApi, MonitoringConfig, Service, ServiceConfig, ShutdownSender, }, diff --git a/vault/src/system.rs b/vault/src/system.rs index 91bdcc485..71c48bcff 100644 --- a/vault/src/system.rs +++ b/vault/src/system.rs @@ -582,7 +582,6 @@ impl VaultService { } async fn run_service(&self) -> Result<(), BackoffError> { - //ToDo: remove service error put all errors in error self.validate_bitcoin_network() .await .map_err(|err| BackoffError::Permanent(err))?; @@ -609,7 +608,7 @@ impl VaultService { .btc_parachain .get_bitcoin_confirmations() .await - .map_err(|err| BackoffError::Transient::(err.into()))?, + .map_err(|err| Error::RuntimeError(err))?, }; tracing::info!("Using {} bitcoin confirmations", num_confirmations); @@ -692,7 +691,7 @@ impl VaultService { Arc::new(Box::new( OrderedVaultsDelay::new(self.btc_parachain.clone()) .await - .map_err(|err| BackoffError::Transient::(err.into()))?, + .map_err(|err| Error::RuntimeError(err))?, )) };