diff --git a/CHANGELOG.md b/CHANGELOG.md index d39ac66..3a6eab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,20 @@ +# 0.3.x + +1. Fix issue with coordinator not storing empty responses +2. Report script improvements +3. Height checks in service not clientchain +4. Challenge asset key import +5. Docker secrets +6. Docker secrets +7. Bitcoin library updates +8. Docker secrets +9. Enhancements + +# 0.3.0 + +* RPC API + # 0.2.0 * Finished core interfaces and testing diff --git a/Cargo.lock b/Cargo.lock index 863d691..37b59c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,14 +21,6 @@ name = "arrayref" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "arrayvec" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "atty" version = "0.2.13" @@ -62,75 +54,29 @@ dependencies = [ [[package]] name = "bech32" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bech32" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bitcoin" -version = "0.17.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitcoin-bech32 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", - "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "strason 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "bitcoin" -version = "0.18.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitcoin-bech32 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin_hashes 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", - "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_test 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bitcoin-amount" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bitcoin-bech32" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bech32 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bitcoin-bech32" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bech32 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "secp256k1 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitcoin_hashes" -version = "0.3.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -160,7 +106,7 @@ dependencies = [ "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "try_from 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -201,7 +147,7 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.26" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -216,7 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -236,7 +182,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -250,11 +196,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "coordinator" -version = "0.1.0" +version = "0.3.9" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", @@ -262,32 +207,31 @@ dependencies = [ "jsonrpc-http-server 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mongodb 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", - "ocean-rpc 0.3.0 (git+https://github.com/commerceblock/rust-ocean-rpc)", - "rust-ocean 0.6.0 (git+https://github.com/commerceblock/rust-ocean)", - "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "ocean-rpc 0.8.0 (git+https://github.com/commerceblock/rust-ocean-rpc)", + "rust-ocean 0.10.0 (git+https://github.com/commerceblock/rust-ocean)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-deque" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-epoch" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -308,6 +252,16 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crypto-mac" version = "0.6.2" @@ -387,7 +341,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -427,6 +381,14 @@ dependencies = [ "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hermit-abi" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hex" version = "0.3.2" @@ -494,7 +456,7 @@ dependencies = [ "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -538,7 +500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -568,8 +530,8 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -580,8 +542,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -595,7 +557,7 @@ dependencies = [ "jsonrpc-server-utils 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -608,10 +570,10 @@ dependencies = [ "jsonrpc-core 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -686,6 +648,11 @@ name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "md-5" version = "0.7.0" @@ -708,7 +675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memoffset" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -794,11 +761,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "nom" version = "4.2.3" @@ -815,8 +777,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -825,7 +787,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -833,12 +795,12 @@ name = "num-traits" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -846,44 +808,39 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ocean-rpc" -version = "0.3.0" -source = "git+https://github.com/commerceblock/rust-ocean-rpc#a8ffb9e94751a608eeb8c5d8a4e082266658184f" +version = "0.8.0" +source = "git+https://github.com/commerceblock/rust-ocean-rpc#1215da59f5bca1f2f86727d1212b7367acff3290" dependencies = [ - "bitcoin 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin-amount 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ocean-rpc-json 0.3.0 (git+https://github.com/commerceblock/rust-ocean-rpc)", - "rust-ocean 0.6.0 (git+https://github.com/commerceblock/rust-ocean)", - "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "ocean-rpc-json 0.8.0 (git+https://github.com/commerceblock/rust-ocean-rpc)", + "rust-ocean 0.10.0 (git+https://github.com/commerceblock/rust-ocean)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ocean-rpc-json" -version = "0.3.0" -source = "git+https://github.com/commerceblock/rust-ocean-rpc#a8ffb9e94751a608eeb8c5d8a4e082266658184f" +version = "0.8.0" +source = "git+https://github.com/commerceblock/rust-ocean-rpc#1215da59f5bca1f2f86727d1212b7367acff3290" dependencies = [ - "bitcoin 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin-amount 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-ocean 0.10.0 (git+https://github.com/commerceblock/rust-ocean)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -907,7 +864,7 @@ dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1137,13 +1094,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rust-ocean" -version = "0.6.0" -source = "git+https://github.com/commerceblock/rust-ocean#b3728b67611c52f1bd408e588cf396562ffb4c7a" +version = "0.10.0" +source = "git+https://github.com/commerceblock/rust-ocean#56a64d11f6f073caebfb37c2fc92b97a288be8ec" dependencies = [ - "bech32 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1179,12 +1134,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "secp256k1" -version = "0.12.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1220,10 +1174,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1240,12 +1194,12 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1256,7 +1210,7 @@ dependencies = [ "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1267,14 +1221,6 @@ dependencies = [ "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "serde_test" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "sha-1" version = "0.7.0" @@ -1304,15 +1250,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "strason" -version = "0.4.0" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1325,7 +1266,7 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1350,8 +1291,8 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1380,7 +1321,7 @@ dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1463,7 +1404,7 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1498,13 +1439,13 @@ name = "tokio-threadpool" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1556,7 +1497,7 @@ name = "toml" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1599,10 +1540,10 @@ dependencies = [ [[package]] name = "unicase" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1615,10 +1556,10 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1646,6 +1587,11 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "want" version = "0.2.0" @@ -1731,19 +1677,13 @@ dependencies = [ "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" -"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -"checksum bech32 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad20b907fd16610c3960c7fe9dae13dd243343409bab80299774c9a8b5d7bed8" -"checksum bech32 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "58946044516aa9dc922182e0d6e9d124a31aafe6b421614654eb27cf90cec09c" -"checksum bitcoin 0.17.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0105a5a9c83f4ac366ca45f99ba502805a097fdf426ade4af0a822853cdd0886" -"checksum bitcoin 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5e7935f613ba170459072926f01dc5ddb8aa22382dc4badf44bbb55e2d243d" -"checksum bitcoin-amount 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0bd5ae6712113fac4edfa917b1d865801f476b021a108e6749ce20d3057abb9e" -"checksum bitcoin-bech32 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0a5cfe5abcb5040b36d4ea8acba95288fefebd7959b59475f2c4ec705974b4c" -"checksum bitcoin-bech32 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4e67e8ccfc663811145e6cabdb9a2a6978877f72b048516e83eb95622e9b2554" -"checksum bitcoin_hashes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b7a2e9773ee7ae7f2560f0426c938f57902dcb9e39321b0cbd608f47ed579a4" +"checksum bech32 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0089c35ab7c6f2bc55ab23f769913f0ac65b1023e7e74638a1f43128dd5df2" +"checksum bitcoin 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01337c65290ce0c948738d821aa9f87488ca6c2b0f02d672ef778f9ae54f22c8" +"checksum bitcoin_hashes 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "db6b697833d852acea530c9e815e6adc724267856b6506bc500362a068a39c7b" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" "checksum bson 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8984b7b33b1f8ac97468df3cefa76c7035abb0786473aa2a437dea0c72855702" @@ -1752,16 +1692,17 @@ dependencies = [ "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -"checksum cc 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "389803e36973d242e7fecb092b2de44a3d35ac62524b3b9339e51d577d668e02" +"checksum cc 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9107d78ed62b3fa5a86e7d18e647abed48cfd8f8fab6c72f4cdb982d196f7e6" "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" -"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" -"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" +"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" "checksum crypto-mac 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7afa06d05a046c7a47c3a849907ec303504608c927f4e85f7bfff22b7180d971" "checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" @@ -1777,6 +1718,7 @@ dependencies = [ "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum globset 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" "checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" +"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a" "checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" @@ -1805,25 +1747,25 @@ dependencies = [ "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum md-5 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9402eaae33a9e144ce18ef488a0e4ca19869673c7bcdbbfe2030fdc3f84211cd" "checksum md5 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" -"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum mongodb 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "d558c8c1ee6140954f82b53558135bb9ecb7b258e10dbd7206d985d134d388e0" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9c3f34cdd24f334cb265d9bf8bfa8a241920d026916785747a92f0e55541a1a" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" -"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" -"checksum ocean-rpc 0.3.0 (git+https://github.com/commerceblock/rust-ocean-rpc)" = "" -"checksum ocean-rpc-json 0.3.0 (git+https://github.com/commerceblock/rust-ocean-rpc)" = "" +"checksum num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "443c53b3c3531dfcbfa499d8893944db78474ad7a1d87fa2d94d1a2231693ac6" +"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" +"checksum ocean-rpc 0.8.0 (git+https://github.com/commerceblock/rust-ocean-rpc)" = "" +"checksum ocean-rpc-json 0.8.0 (git+https://github.com/commerceblock/rust-ocean-rpc)" = "" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum pbkdf2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0c09cddfbfc98de7f76931acf44460972edb4023eb14d0c6d4018800e552d8e0" @@ -1851,31 +1793,29 @@ dependencies = [ "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" -"checksum rust-ocean 0.6.0 (git+https://github.com/commerceblock/rust-ocean)" = "" +"checksum rust-ocean 0.10.0 (git+https://github.com/commerceblock/rust-ocean)" = "" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" "checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" "checksum scan_fmt 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b87497427f9fbe539ee6b9626f5a5e899331fdf1c1d62f14c637a462969db30" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" -"checksum secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfaccd3a23619349e0878d9a241f34b1982343cdf67367058cd7d078d326b63e" +"checksum secp256k1 0.15.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4d311229f403d64002e9eed9964dfa5a0a0c1ac443344f7546bf48e916c6053a" "checksum semver 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bee2bc909ab2d8d60dab26e8cad85b25d795b14603a0dcb627b78b9d30b6454b" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum separator 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7acc4d818f952ed02e7911df5da8098c8b00a3c5ba2832e035a750b56e8fc32b" "checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" -"checksum serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "9796c9b7ba2ffe7a9ce53c2287dfc48080f4b2b362fcc245a259b3a7201119dd" +"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" "checksum serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0b833c5ad67d52ced5f5938b2980f32a9c1c5ef047f0b4fb3127e7a423c76153" -"checksum serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" +"checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8" "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" -"checksum serde_test 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)" = "4aaf891d32257c9f65259b841af6e0b35d2ee22e41f1becbead01f0217f3e000" "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" -"checksum strason 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dcd1098ae32c583b8d538072380c340a01e46fbca379d6248ff77721373e2cef" +"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" "checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum syn 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "661641ea2aa15845cddeb97dad000d22070bb5c1fb456b96c1cba883ec691e92" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum textnonce 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dafb35214e317d6c0a72b16d1aa667bbc0fea57e302798e7bc520e0f39988006" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" @@ -1902,13 +1842,14 @@ dependencies = [ "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa9b3b49edd3468c0e6565d85783f51af95212b6fa3986a5500954f00b460874" "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -"checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" +"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" +"checksum unicode-normalization 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" diff --git a/Cargo.toml b/Cargo.toml index 558c720..0bc9fe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coordinator" -version = "0.1.0" +version = "0.3.9" authors = ["nkostoulas "] description = "Guardnode Coordinator implementation for the Commerceblock Covalence system" homepage = "https://github.com/commerceblock" @@ -19,6 +19,4 @@ mongodb = "0.3.11" jsonrpc-http-server = "11.0" rust-ocean = { git = "https://github.com/commerceblock/rust-ocean"} ocean-rpc = { git = "https://github.com/commerceblock/rust-ocean-rpc"} -bitcoin = { version = "0.17", features = [ "serde-decimal" ] } -bitcoin_hashes = "0.3" -secp256k1 = "0.12.2" +bitcoin = { version = "0.20", features = [ "use-serde" ] } diff --git a/config/default.toml b/config/default.toml index 719d1b1..cff639f 100644 --- a/config/default.toml +++ b/config/default.toml @@ -1,14 +1,14 @@ # Log level option used to set RUST_LOG for the rust env logger -log_level = "coordinator,demo" +# log_level = "coordinator,demo" # Duration that we wait for challenge responses from guardnodes, in seconds -challenge_duration = 60 +# challenge_duration = 60 # Frequency of creating new challenges, in number of blocks -challenge_frequency = 2 +# challenge_frequency = 2 -# Duration that we attempt to verify a challenge transaction before skipping, in seconds -verify_duration = 150 +# Block find time of service chain, in seconds +# block_time = 60 # Host address that the listener binds to and receives guardnode requests listener_host = "127.0.0.1:9999" @@ -28,11 +28,12 @@ host = "127.0.0.1:5555" user = "user1" pass = "password1" genesis_hash = "ff8950160a77988cdc485913568d06c2d69a8c952ef0f179b4b097e3de63d7cc" +block_time = 60 asset = "CHALLENGE" asset_key = "cScSHCQp9AEwzZoucRpX9bMRkLCJ4LoQWBNFTZuD6tPX9qwNMWfQ" +chain = "ocean_test" +payment_asset = "CBT" [storage] host = "localhost:27017" name = "coordinator" -user = "user" -pass = "pass" diff --git a/docker-compose.yml b/docker-compose.yml index 8a0886c..9fa705c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,6 +46,8 @@ services: CO_CLIENTCHAIN_PASS: password1 CO_CLIENTCHAIN_GENESIS_HASH: d1fbd07bd9a7a80a85445b8e28246f0b644c01ac7412e81ce3ffc0815386ad77 CO_CLIENTCHAIN_ASSET_KEY: cScSHCQp9AEwzZoucRpX9bMRkLCJ4LoQWBNFTZuD6tPX9qwNMWfQ + CO_CLIENTCHAIN_CHAIN: ocean_test + CO_CLIENTCHAIN_PAYMENT_ASSET: CBT # cb service chain node connectivity CO_SERVICE_HOST: service_chain:5555 diff --git a/examples/demo.rs b/examples/demo.rs index e941f2b..fca821f 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -1,30 +1,33 @@ //! Simple demo of coordinator with mock guard node +//! +//! Demo Coordinator connects to demo client chain (can be built by running +//! ./scripts/demo.sh) and sends challenges to mock guardnodes that made bids. +//! When guardnode responses are received by coordinator they are verified and +//! can be stored ready for fee payments to be made. #[macro_use] extern crate log; extern crate bitcoin; -extern crate bitcoin_hashes; extern crate coordinator; extern crate env_logger; extern crate hyper; extern crate ocean_rpc; -extern crate secp256k1; use std::sync::Arc; use std::{env, thread, time}; use bitcoin::consensus::encode::serialize; -use bitcoin_hashes::{hex::FromHex, hex::ToHex, sha256d}; +use bitcoin::hashes::{hex::FromHex, hex::ToHex, sha256d}; +use bitcoin::secp256k1::{Message, Secp256k1, SecretKey}; use hyper::{ rt::{self, Future, Stream}, Body, Client, Method, Request, }; use ocean_rpc::RpcApi; -use secp256k1::{Message, Secp256k1, SecretKey}; -use coordinator::clientchain::get_first_unspent; use coordinator::coordinator as coordinator_main; -use coordinator::ocean::OceanClient; +use coordinator::interfaces::clientchain::get_first_unspent; +use coordinator::util::ocean::OceanClient; /// Demo coordinator with listener and challenge service running /// mock implementation for service chain interface and ocean @@ -33,9 +36,11 @@ use coordinator::ocean::OceanClient; fn main() { let mut config = coordinator::config::Config::new().unwrap(); config.challenge_duration = 5; - config.verify_duration = 30; + config.challenge_frequency = 2; + config.block_time = 10; + config.clientchain.block_time = 10; - env::set_var("RUST_LOG", &config.log_level); + env::set_var("RUST_LOG", "coordinator,demo"); env::set_var("RUST_BACKTRACE", "1"); env_logger::init(); @@ -51,7 +56,7 @@ fn main() { // auto client chain block generation let client_rpc_clone = client_rpc.clone(); thread::spawn(move || loop { - thread::sleep(time::Duration::from_secs(5)); + thread::sleep(time::Duration::from_secs(10)); if let Err(e) = client_rpc_clone.clone().client.generate(1) { error!("{}", e); } @@ -68,7 +73,6 @@ fn main() { for bid in client_rpc.get_request_bids(&request_txid).unwrap().unwrap().bids { if bid.fee_pub_key.to_string() == guardnode_pubkey { guardnode_txid = bid.txid; - println!("guardnode bid txid: {}", guardnode_txid); break; } } diff --git a/examples/hyperclient.rs b/examples/hyperclient.rs index ff1cf02..48e6340 100644 --- a/examples/hyperclient.rs +++ b/examples/hyperclient.rs @@ -1,19 +1,17 @@ //! Mock Example of client sending a POST request to server extern crate bitcoin; -extern crate bitcoin_hashes; extern crate hyper; -extern crate secp256k1; use bitcoin::consensus::encode::serialize; -use bitcoin_hashes::hex::{FromHex, ToHex}; -use bitcoin_hashes::sha256d; +use bitcoin::hashes::hex::{FromHex, ToHex}; +use bitcoin::hashes::sha256d; +use bitcoin::secp256k1::{Message, Secp256k1, SecretKey}; use hyper::{ header::HeaderValue, rt::{self, Future, Stream}, Body, Client, Method, Request, }; -use secp256k1::{Message, Secp256k1, SecretKey}; fn main() { let client = Client::new(); diff --git a/examples/rpc.rs b/examples/rpc.rs index 1720d70..5a689aa 100644 --- a/examples/rpc.rs +++ b/examples/rpc.rs @@ -3,7 +3,7 @@ extern crate ocean_rpc; extern crate rust_ocean; -use ocean_rpc::{Client, Error, RpcApi}; +use ocean_rpc::{Auth, Client, Error, RpcApi}; fn main_result() -> Result<(), Error> { let mut args = std::env::args(); @@ -14,7 +14,7 @@ fn main_result() -> Result<(), Error> { let user = args.next(); let pass = args.next(); - let rpc = Client::new(url, user, pass); + let rpc = Client::new(url, Auth::UserPass(user.unwrap(), pass.unwrap())).unwrap(); let blockchain_info = rpc.get_blockchain_info()?; println!("info\n{:?}", blockchain_info); diff --git a/scripts/create_request.sh b/scripts/create_request.sh index 72000b5..6fca816 100755 --- a/scripts/create_request.sh +++ b/scripts/create_request.sh @@ -1,3 +1,12 @@ +# Script builds, signs and sends a request transaction on service chain whose +# connectivity information is specified in $RPC_USER, RPC_PASS, RPC_CONNECT, +# and RPC_PORT env variables from the local ocean wallet. +# +# Several checks are made for validity of given arguments. If all is well then an +# unspent permission asset output is found (or provided in argument 9 and 10) and +# used as input to a raw request tx. The request is broadcast and its spending script +# address imported to the wallet to allow for automatic renewal of the request. + #!/bin/bash shopt -s expand_aliases alias ocl="$HOME/jsonrpc-cli/jsonrpc-cli --user=$RPC_USER --pass=$RPC_PASS --format=jsonpretty --resultonly=on --highlight=off http://$RPC_CONNECT:$RPC_PORT/" diff --git a/scripts/demo.sh b/scripts/demo.sh index 525b3e7..bb56203 100755 --- a/scripts/demo.sh +++ b/scripts/demo.sh @@ -1,3 +1,8 @@ +# Script spins up new chain with datadir=~/co-client-dir/ and creates a new +# demo request transaction along with 2 bids. This can be used to initialise +# exmples/demo.rs to perform coordinator functionality on a mock client and service +# chain. + #!/bin/bash shopt -s expand_aliases diff --git a/scripts/report.py b/scripts/report.py index b4dc02b..b6e9afc 100755 --- a/scripts/report.py +++ b/scripts/report.py @@ -1,3 +1,11 @@ +# Script to generate report of responses from guardnodes using +# coordinator RPC API interface. +# +# First connection to a local Ocean node is made, the request data for a +# txid is found via getrequest RPC and fee size is calculated. +# Then getrequestresponse RPC is called to get challenge responses which can be +# used to determine rewards for guardnodes. + #!/usr/bin/env python3 import requests import json @@ -74,9 +82,9 @@ def calculate_fees(rpc, start_height, end_height): return fee addr_prefix = 235 -txid = "78f954d07de5badbc1526a60fe0ea639216f17f490a3bf41e48840453eca243f" -url = 'https://userApi:passwordApi@coordinator-api.testnet.commerceblock.com:10006' -rpc = connect("ocean", "oceanpass", "localhost", "7043") +txid = "6e993034df3203c0867c98f420f85b5ffecd7cb8580e2b6f2d33764e1cbfb074" +url = 'http://userApi:passwordApi@localhost:3333' +rpc = connect("user1", "password1", "localhost", "5555") payload = '{{"jsonrpc": "2.0", "method": "getrequest", "params": {{"txid": "{}"}}, "id": 1}}'.format(txid) headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} @@ -90,11 +98,8 @@ def calculate_fees(rpc, start_height, end_height): print("") print("Calculating total fees...") -# For requests that are serving the service chain the fee start/end heights -# can be picked up from the request information. For requests in client chains -# these heights need to be found manually and inserted below to calculate fees -fee_start_height = request['start_blockheight'] -fee_end_height = request['end_blockheight'] +fee_start_height = request['start_blockheight_clientchain'] +fee_end_height = request['end_blockheight_clientchain'] fee = calculate_fees(rpc, fee_start_height, fee_end_height) fee_percentage = request['fee_percentage'] fee_out = fee*fee_percentage/100 @@ -112,21 +117,17 @@ def calculate_fees(rpc, start_height, end_height): fee_per_guard = float(fee_out/len(bids)) print("") -payload = '{{"jsonrpc": "2.0", "method": "getrequestresponses", "params": {{"txid": "{}"}}, "id": 1}}'.format(txid) +payload = '{{"jsonrpc": "2.0", "method": "getrequestresponse", "params": {{"txid": "{}"}}, "id": 1}}'.format(txid) headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} r = requests.post(url, data=payload, headers=headers) result = json.loads(json.loads(r.content)['result']) -challenge_resps = result["responses"] -num_of_challenges = len(challenge_resps) +challenge_resps = result["response"] +num_of_challenges = challenge_resps["num_challenges"] print("Number of challenges: {}".format(num_of_challenges)) resps = {} -for challenge_resp in challenge_resps: - for bid_resp in challenge_resp: - if bid_resp in resps: - resps[bid_resp] += (1/num_of_challenges) - else: - resps[bid_resp] = (1/num_of_challenges) +for (bid, resp_num) in challenge_resps["bid_responses"].items(): + resps[bid] = resp_num / num_of_challenges print("Results") for bid, key in bids.items(): @@ -135,4 +136,4 @@ def calculate_fees(rpc, start_height, end_height): performance = resps[bid] print("Bid {0}\npubkey: {1}\naddress: {2}\nperformance: {3:.2f}%\nreward: {4}\n".\ format(bid, key, key_to_p2pkh(key, addr_prefix), 100*performance, fee_per_guard*performance)) -print("End") \ No newline at end of file +print("End") diff --git a/src/api.rs b/src/api.rs index 1b7581e..bf58f07 100644 --- a/src/api.rs +++ b/src/api.rs @@ -8,16 +8,16 @@ use std::sync::Arc; use std::thread; use base64::decode; -use bitcoin_hashes::sha256d; +use bitcoin::hashes::sha256d; use hyper::{Body, Request, StatusCode}; use jsonrpc_http_server::jsonrpc_core::{Error, ErrorCode, IoHandler, Params, Value}; use jsonrpc_http_server::{hyper::header, AccessControlAllowOrigin, DomainsValidation, Response, ServerBuilder}; use serde::{Deserialize, Serialize}; -use crate::challenger::ChallengeResponseIds; use crate::config::ApiConfig; -use crate::request::{BidSet, Request as ServiceRequest}; -use crate::storage::Storage; +use crate::interfaces::response::Response as ChallengeResponse; +use crate::interfaces::storage::Storage; +use crate::interfaces::{bid::BidSet, request::Request as ServiceRequest}; #[derive(Deserialize, Debug)] struct GetRequestParams { @@ -59,7 +59,7 @@ struct GetRequestsResponse { /// Get requests RPC call returning all stored requests fn get_requests(storage: Arc) -> futures::Finished { - let requests = storage.get_requests().unwrap(); + let requests = storage.get_requests(None).unwrap(); let mut response = GetRequestsResponse { requests: vec![] }; for request in requests { let bids = storage.get_bids(request.txid).unwrap(); @@ -74,19 +74,27 @@ struct GetRequestResponsesParams { } #[derive(Serialize, Debug)] -struct GetRequestResponsesResponse { - responses: Vec, +struct GetRequestResponseResponse { + response: ChallengeResponse, } /// Get requests responses RPC call returning all responses for a specific /// request transaction id hash -fn get_request_responses(params: Params, storage: Arc) -> futures::Finished { +fn get_request_response(params: Params, storage: Arc) -> futures::Finished { let try_parse = params.parse::(); match try_parse { Ok(parse) => { - let responses = storage.get_responses(parse.txid).unwrap(); - let res_serialized = serde_json::to_string(&GetRequestResponsesResponse { responses }).unwrap(); - return futures::finished(Value::String(res_serialized)); + let response_get = storage.get_response(parse.txid).unwrap(); + if let Some(response) = response_get { + let res_serialized = serde_json::to_string(&GetRequestResponseResponse { response }).unwrap(); + return futures::finished(Value::String(res_serialized)); + } else { + return futures::failed(Error { + code: ErrorCode::InvalidParams, + message: "Invalid params: `txid` does not exist.".to_string(), + data: None, + }); + } } Err(e) => return futures::failed(e), } @@ -119,8 +127,8 @@ pub fn run_api_server( ) -> thread::JoinHandle<()> { let mut io = IoHandler::default(); let storage_ref = storage.clone(); - io.add_method("getrequestresponses", move |params: Params| { - get_request_responses(params, storage_ref.clone()) + io.add_method("getrequestresponse", move |params: Params| { + get_request_response(params, storage_ref.clone()) }); let storage_ref = storage.clone(); io.add_method("getrequest", move |params: Params| { @@ -161,6 +169,7 @@ mod tests { use futures::Future; + use crate::challenger::ChallengeResponseIds; use crate::util::testing::{gen_challenge_state, gen_dummy_hash, MockStorage}; #[test] @@ -185,7 +194,7 @@ mod tests { let resp = get_request(params, storage.clone()); assert_eq!( format!( - r#"{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"}}]}}"#, + r#"{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10,"start_blockheight_clientchain":0,"end_blockheight_clientchain":0,"is_payment_complete":false}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","payment":null}}]}}"#, dummy_hash.to_string() ), resp.wait().unwrap() @@ -207,7 +216,7 @@ mod tests { let resp = get_requests(storage.clone()); assert_eq!( format!( - r#"{{"requests":[{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"}}]}}]}}"#, + r#"{{"requests":[{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10,"start_blockheight_clientchain":0,"end_blockheight_clientchain":0,"is_payment_complete":false}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","payment":null}}]}}]}}"#, dummy_hash.to_string() ), resp.wait().unwrap() @@ -219,7 +228,7 @@ mod tests { let resp = get_requests(storage.clone()); assert_eq!( format!( - r#"{{"requests":[{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"}}]}},{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"}}]}}]}}"#, + r#"{{"requests":[{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10,"start_blockheight_clientchain":0,"end_blockheight_clientchain":0,"is_payment_complete":false}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","payment":null}}]}},{{"request":{{"txid":"{}","start_blockheight":2,"end_blockheight":5,"genesis_blockhash":"0000000000000000000000000000000000000000000000000000000000000000","fee_percentage":5,"num_tickets":10,"start_blockheight_clientchain":0,"end_blockheight_clientchain":0,"is_payment_complete":false}},"bids":[{{"txid":"1234567890000000000000000000000000000000000000000000000000000000","pubkey":"026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3","payment":null}}]}}]}}"#, dummy_hash.to_string(), dummy_hash2.to_string() ), @@ -228,10 +237,20 @@ mod tests { } #[test] - fn get_request_responses_test() { + fn get_request_response_test() { let storage = Arc::new(MockStorage::new()); let dummy_hash = gen_dummy_hash(1); let dummy_hash_bid = gen_dummy_hash(2); + + // no such request + let s = format!(r#"{{"txid": "{}"}}"#, dummy_hash.to_string()); + let params: Params = serde_json::from_str(&s).unwrap(); + let resp = get_request_response(params, storage.clone()); + assert_eq!( + "Invalid params: `txid` does not exist.", + resp.wait().unwrap_err().message + ); + let mut dummy_response_set = ChallengeResponseIds::new(); let _ = dummy_response_set.insert(dummy_hash_bid); let _ = storage.save_response(dummy_hash, &dummy_response_set); @@ -239,7 +258,7 @@ mod tests { // invalid key let s = format!(r#"{{"hash": "{}"}}"#, dummy_hash.to_string()); let params: Params = serde_json::from_str(&s).unwrap(); - let resp = get_request_responses(params, storage.clone()); + let resp = get_request_response(params, storage.clone()); assert_eq!( "Invalid params: missing field `txid`.", resp.wait().unwrap_err().message @@ -248,18 +267,21 @@ mod tests { // invalid value let s = format!(r#"{{"txid": "{}a"}}"#, dummy_hash.to_string()); let params: Params = serde_json::from_str(&s).unwrap(); - let resp = get_request_responses(params, storage.clone()); + let resp = get_request_response(params, storage.clone()); assert_eq!( - "Invalid params: bad hex string length 65 (expected 64).", + "Invalid params: odd hex string length 65.", resp.wait().unwrap_err().message ); // valid key and value let s = format!(r#"{{"txid": "{}"}}"#, dummy_hash.to_string()); let params: Params = serde_json::from_str(&s).unwrap(); - let resp = get_request_responses(params, storage.clone()); + let resp = get_request_response(params, storage.clone()); assert_eq!( - format!("{{\"responses\":[[\"{}\"]]}}", dummy_hash_bid.to_string()), + format!( + r#"{{"response":{{"num_challenges":1,"bid_responses":{{"{}":1}}}}}}"#, + dummy_hash_bid.to_string() + ), resp.wait().unwrap() ); } diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..f760e7d --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,35 @@ +//! # Main +//! +//! Main daemon entry + +#[macro_use] +extern crate log; +extern crate coordinator; +extern crate env_logger; + +use std::env; +use std::process; + +fn main() { + // Fetch config which is set from default values in config + // and any values overriden by the corresponding env variable + match coordinator::config::Config::new() { + Ok(config) => { + // To see results set RUST_LOG to one of the following: + // info, warning, debug, error, coordinator(for all) + env::set_var("RUST_LOG", &config.log_level); + env::set_var("RUST_BACKTRACE", "1"); + // Init env logger with value set from config + env_logger::init(); + if let Err(e) = coordinator::coordinator::run(config) { + error!("daemon failure: {}", e); + } + } + Err(e) => { + env::set_var("RUST_LOG", "error"); + env_logger::init(); + error!("config failure: {}", e); + } + } + process::exit(1); +} diff --git a/src/challenger.rs b/src/challenger.rs index 5deab01..5d56a50 100644 --- a/src/challenger.rs +++ b/src/challenger.rs @@ -1,46 +1,52 @@ //! Challenger //! -//! Methods and models for fetching, structuring and running challenge requests +//! Methods and models for fetching, structuring, storing and running challenge +//! requests use std::collections::HashSet; use std::sync::mpsc::{Receiver, RecvTimeoutError}; use std::sync::{Arc, Mutex}; use std::{thread, time}; -use bitcoin_hashes::sha256d; +use bitcoin::hashes::sha256d; -use crate::clientchain::ClientChain; use crate::error::{CError, Error, Result}; -use crate::request::{Bid, BidSet, Request}; -use crate::service::Service; -use crate::storage::Storage; +use crate::interfaces::clientchain::ClientChain; +use crate::interfaces::service::Service; +use crate::interfaces::storage::Storage; +use crate::interfaces::{ + bid::{Bid, BidSet}, + request::Request, +}; -/// Number of verify attempts for challenge transaction -pub const CHALLENGER_VERIFY_ATTEMPTS: u32 = 5; +/// Verify attempt interval to client in ms +pub const CHALLENGER_VERIFY_INTERVAL: u64 = 100; /// Attempts to verify that a challenge has been included in the client chain -/// Method tries a fixed number of attempts CHALLENGER_VERIFY_ATTEMPTS for a -/// variable delay time to allow easy configuration +/// This makes attempts every CHALLENGER_VERIFY_INTERVAL ms and for the verify +/// duration specified, which is variable in order to allow easy configuration fn verify_challenge( hash: &sha256d::Hash, clientchain: &K, - attempt_delay: time::Duration, -) -> Result { + verify_duration: time::Duration, +) -> Result<()> { info! {"verifying challenge hash: {}", hash} - for i in 0..CHALLENGER_VERIFY_ATTEMPTS { - // fixed number of attempts? - if clientchain.verify_challenge(&hash)? { - info! {"challenge verified"} - return Ok(true); - } - warn! {"attempt {} failed", i+1} - if i + 1 == CHALLENGER_VERIFY_ATTEMPTS { + let start_time = time::Instant::now(); + loop { + let now = time::Instant::now(); + if start_time + verify_duration > now { + if clientchain.verify_challenge(&hash)? { + info! {"challenge verified"} + return Ok(()); + } + } else { break; } - info! {"sleeping for {:?}...", attempt_delay/CHALLENGER_VERIFY_ATTEMPTS} - thread::sleep(attempt_delay / CHALLENGER_VERIFY_ATTEMPTS) + // This will potentially be replaced by subscribing to the ocean node + // for transaction updates but this is good enough for now + thread::sleep(std::time::Duration::from_millis(CHALLENGER_VERIFY_INTERVAL)) } - Ok(false) + Err(Error::from(CError::UnverifiedChallenge)) } /// Get responses to the challenge by reading data from the channel receiver @@ -96,7 +102,7 @@ pub fn run_challenge_request( refresh_delay: time::Duration, ) -> Result<()> { let request = challenge_state.lock().unwrap().request.clone(); // clone as const and drop mutex - info! {"Running challenge request: {:?}", request}; + info! {"Running challenge request: {:?}", request.txid}; let mut prev_challenge_height: u64 = 0; loop { let challenge_height = service.get_blockheight()?; @@ -113,9 +119,9 @@ pub fn run_challenge_request( let challenge_hash = clientchain.send_challenge()?; challenge_state.lock().unwrap().latest_challenge = Some(challenge_hash); - if !verify_challenge(&challenge_hash, clientchain, verify_duration)? { + if let Err(e) = verify_challenge(&challenge_hash, clientchain, verify_duration) { challenge_state.lock().unwrap().latest_challenge = None; // stop receiving responses - continue; + return Err(e); } info! {"fetching responses..."} @@ -130,6 +136,35 @@ pub fn run_challenge_request( Ok(()) } +/// Update challenge state request with client chain start and end block +/// heights and store challenge state +/// If request already stored set challenge state request to request in +/// storage (catcher for coordinator failure after storing request but +/// before request service period over) +pub fn update_challenge_request_state( + clientchain: &K, + storage: Arc, + challenge: &mut ChallengeState, + block_time_servicechain: u64, + block_time_clientchain: u64, +) -> Result<()> { + match storage.get_request(challenge.request.txid)? { + Some(req) => challenge.request = req, + None => { + // Set request's start_blockheight_clientchain + challenge.request.start_blockheight_clientchain = clientchain.get_blockheight()?; + let service_period_time_s = (challenge.request.end_blockheight - challenge.request.start_blockheight) + * block_time_servicechain as u32; + // Calculate and set request's end_blockheight_clientchain + challenge.request.end_blockheight_clientchain = challenge.request.start_blockheight_clientchain + + (service_period_time_s as f32 / block_time_clientchain as f32).floor() as u32; + storage.save_challenge_state(&challenge)?; // Store Challenge + // Request + } + } + Ok(()) +} + /// Tuple struct to store a verified challenge response /// for a winning bid on a specific challenge hash #[derive(Debug, Hash, Clone)] @@ -209,22 +244,29 @@ mod tests { use std::sync::mpsc::{channel, Receiver, Sender}; use crate::error::Error; - use crate::util::testing::{gen_dummy_hash, MockClientChain, MockService, MockStorage}; + use crate::interfaces::response::Response; + use crate::util::testing::{gen_challenge_state, gen_dummy_hash, MockClientChain, MockService, MockStorage}; #[test] fn verify_challenge_test() { let mut clientchain = MockClientChain::new(); let dummy_hash = gen_dummy_hash(5); - assert!(verify_challenge(&dummy_hash, &clientchain, time::Duration::from_nanos(1)).unwrap() == true); + // duration doesn't matter here + assert!(verify_challenge(&dummy_hash, &clientchain, time::Duration::from_millis(10)).unwrap() == ()); clientchain.return_false = true; - assert!(verify_challenge(&dummy_hash, &clientchain, time::Duration::from_nanos(1)).unwrap() == false); + let res = verify_challenge(&dummy_hash, &clientchain, time::Duration::from_millis(10)); + match res { + Ok(_) => assert!(false, "should not return Ok"), + Err(Error::Coordinator(e)) => assert_eq!(CError::UnverifiedChallenge.to_string(), e.to_string()), + Err(_) => assert!(false, "should not return any error"), + } clientchain.return_false = false; clientchain.return_err = true; assert!( - verify_challenge(&dummy_hash, &clientchain, time::Duration::from_nanos(1)).is_err(), + verify_challenge(&dummy_hash, &clientchain, time::Duration::from_millis(10)).is_err(), "verify_challenge failed" ) } @@ -271,6 +313,64 @@ mod tests { } } + #[test] + fn update_challenge_request_state_test() { + let clientchain = MockClientChain::new(); + let storage = Arc::new(MockStorage::new()); + + let dummy_hash = gen_dummy_hash(11); + let mut challenge = gen_challenge_state(&dummy_hash); + let num_service_chain_blocks = challenge.request.end_blockheight - challenge.request.start_blockheight; + + // Test challenge state request set and stored correctly + let _ = clientchain.height.replace(1); + let mut comparison_challenge_request = challenge.request.clone(); // Clone request for comparison + let _ = update_challenge_request_state(&clientchain, storage.clone(), &mut challenge, 1, 1); + // All fields stay the same but start and end blockheight_clientchain + comparison_challenge_request.start_blockheight_clientchain = *clientchain.height.borrow(); + comparison_challenge_request.end_blockheight_clientchain = + challenge.request.start_blockheight_clientchain + num_service_chain_blocks; // start_height + number of servcie chain blocks + assert_eq!(challenge.request, comparison_challenge_request); + assert_eq!( + storage.get_request(challenge.request.txid).unwrap().unwrap(), + comparison_challenge_request + ); + + // Test challenge state set and storage performed correctly + // for client chain block time half of service chain block time + let storage = Arc::new(MockStorage::new()); //reset storage + let _ = clientchain.height.replace(1); + let mut comparison_challenge_request = challenge.request.clone(); // Clone request for comparison + let _ = update_challenge_request_state(&clientchain, storage.clone(), &mut challenge, 2, 1); + // All fields stay the same but start and end blockheight_clientchain + comparison_challenge_request.start_blockheight_clientchain = *clientchain.height.borrow(); + comparison_challenge_request.end_blockheight_clientchain = + challenge.request.start_blockheight_clientchain + 2 * num_service_chain_blocks; // start_height + (2 times client chain blocks as service chain blocks in same + // time period * number of service chain block) + assert_eq!(challenge.request, comparison_challenge_request); + assert_eq!( + storage.get_request(challenge.request.txid).unwrap().unwrap(), + comparison_challenge_request + ); + + // Test stored version unchanged if attempt is made to store request a second + // time + let old_challenge = challenge.clone(); // save old challenge state + challenge.request.fee_percentage = 25; // alter random field + let new_challenge = challenge.clone(); // save new challenge state + let _ = update_challenge_request_state(&clientchain, storage.clone(), &mut challenge, 2, 1); + assert_eq!(challenge.request, old_challenge.request); + assert_eq!( + storage.get_request(challenge.request.txid).unwrap().unwrap(), + old_challenge.request + ); + assert_ne!(challenge.request, new_challenge.request); + assert_ne!( + storage.get_request(challenge.request.txid).unwrap().unwrap(), + new_challenge.request + ); + } + #[test] fn check_request_test() { let service = MockService::new(); @@ -365,12 +465,13 @@ mod tests { // test normal operation of run_challenge_request by adding some responses for // the first challenge let _ = service.height.replace(dummy_request.start_blockheight as u64); // set height for fetch_next to succeed + let challenge_state = fetch_next(&service, &dummy_hash).unwrap().unwrap(); storage.save_challenge_state(&challenge_state).unwrap(); let (vtx, vrx): (Sender, Receiver) = channel(); - let _ = clientchain.height.replace((dummy_request.start_blockheight as u64) + 1); // set height +1 for challenge hash response + let _ = clientchain.height.replace((dummy_request.start_blockheight) + 1); // set height +1 for challenge hash response let dummy_challenge_hash = clientchain.send_challenge().unwrap(); let dummy_bid = challenge_state.bids.iter().next().unwrap().clone(); vtx.send(ChallengeResponse(dummy_challenge_hash, dummy_bid.clone())) @@ -387,16 +488,17 @@ mod tests { storage.clone(), time::Duration::from_millis(10), time::Duration::from_millis(10), - 3, + 50, time::Duration::from_millis(10), ); + match res { Ok(_) => { - let resps = storage.get_responses(dummy_request.txid).unwrap(); - assert_eq!(1, resps.len()); + let resps = storage.get_response(dummy_request.txid).unwrap(); + assert_eq!(resps, None); let bids = storage.get_bids(dummy_request.txid).unwrap(); assert_eq!(challenge_state.bids, bids); - let requests = storage.get_requests().unwrap(); + let requests = storage.get_requests(None).unwrap(); assert_eq!(1, requests.len()); assert_eq!(&challenge_state.request, &requests[0]); assert_eq!( @@ -423,16 +525,21 @@ mod tests { 1, time::Duration::from_millis(10), ); + match res { Ok(_) => { - let resps = storage.get_responses(dummy_request.txid).unwrap(); - assert_eq!(5, resps.len()); - assert_eq!(1, resps[1].len()); - assert_eq!(dummy_bid.txid, *resps[1].iter().next().unwrap()); - assert_eq!(5, storage.challenge_responses.borrow().len()); + let resps = storage.get_response(dummy_request.txid).unwrap(); + assert_eq!( + resps.unwrap(), + Response { + num_challenges: 4, + bid_responses: [(dummy_bid.txid, 1)].iter().cloned().collect() + } + ); + assert_eq!(1, storage.challenge_responses.borrow().len()); let bids = storage.get_bids(dummy_request.txid).unwrap(); assert_eq!(challenge_state.bids, bids); - let requests = storage.get_requests().unwrap(); + let requests = storage.get_requests(None).unwrap(); assert_eq!(1, requests.len()); assert_eq!(&challenge_state.request, &requests[0]); assert_eq!( @@ -522,10 +629,12 @@ mod tests { time::Duration::from_millis(10), ); match res { - Ok(_) => { + Ok(_) => assert!(false, "should not return Ok"), + Err(Error::Coordinator(e)) => { assert_eq!(0, storage.challenge_responses.borrow().len()); + assert_eq!(CError::UnverifiedChallenge.to_string(), e.to_string()); } - Err(_) => assert!(false, "should not return error"), + Err(_) => assert!(false, "should not return any error"), } clientchain.return_false = false; diff --git a/src/config.rs b/src/config.rs index f4312a8..cb4ecd6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,13 +2,16 @@ //! //! Config module handling config options from file/env +use std::env; +use std::str::FromStr; + use config_rs::{Config as ConfigRs, Environment, File}; -use error::InputErrorType::{GenHash, PrivKey}; +use ocean::Address; use serde::{Deserialize, Serialize}; -use std::env; -use util::checks::{check_hash_string, check_privkey_string}; +use crate::error::InputErrorType::{GenHash, MissingArgument, PrivKey}; use crate::error::{CError, Error, Result}; +use crate::util::checks::{check_hash_string, check_privkey_string}; #[derive(Debug, Serialize, Deserialize)] /// Api specific config @@ -52,7 +55,7 @@ impl Default for ServiceConfig { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] /// Clientchain specific config pub struct ClientChainConfig { /// Client rpc host @@ -63,10 +66,20 @@ pub struct ClientChainConfig { pub pass: String, /// Client genesis hash pub genesis_hash: String, + /// Block time in seconds + pub block_time: u64, /// Client asset label pub asset: String, /// Client asset key pub asset_key: String, + /// Client chain name + pub chain: String, + /// Payment asset label or asset id or ANY asset to be used for payments + pub payment_asset: String, + /// Payment key; optional as the coordinator might not be doing payments + pub payment_key: Option, + /// Payment address corresponding to payment key + pub payment_addr: Option, } impl Default for ClientChainConfig { @@ -76,8 +89,13 @@ impl Default for ClientChainConfig { user: String::new(), pass: String::new(), genesis_hash: String::new(), + block_time: CONFIG_BLOCK_TIME_DEFAULT, asset: String::from("CHALLENGE"), asset_key: String::new(), + chain: String::new(), + payment_asset: String::new(), + payment_key: None, + payment_addr: None, } } } @@ -115,8 +133,8 @@ pub struct Config { pub challenge_duration: u64, /// Challenge frequency in number of blocks pub challenge_frequency: u64, - /// Verify duration in seconds - pub verify_duration: u64, + /// Block time of service chain in seconds + pub block_time: u64, /// Listener host address pub listener_host: String, /// Api configuration @@ -129,13 +147,18 @@ pub struct Config { pub storage: StorageConfig, } +/// Config default variable definitons +const CONFIG_CHALLENGE_DURATION_DEFAULT: u64 = 60; +const CONFIG_CHALLENGE_FREQUENCY_DEFAULT: u64 = 1; +const CONFIG_BLOCK_TIME_DEFAULT: u64 = 60; + impl Default for Config { fn default() -> Config { Config { log_level: String::from("coordinator"), - challenge_duration: 60, - challenge_frequency: 1, - verify_duration: 150, + challenge_duration: CONFIG_CHALLENGE_DURATION_DEFAULT, + challenge_frequency: CONFIG_CHALLENGE_FREQUENCY_DEFAULT, + block_time: CONFIG_BLOCK_TIME_DEFAULT, listener_host: String::from("localhost:80"), api: ApiConfig::default(), service: ServiceConfig::default(), @@ -210,6 +233,21 @@ impl Config { if let Ok(v) = env::var("CO_CLIENTCHAIN_GENESIS_HASH") { let _ = conf_rs.set("clientchain.genesis_hash", v)?; } + if let Ok(v) = env::var("CO_CLIENTCHAIN_BLOCK_TIME") { + let _ = conf_rs.set("clientchain.block_time", v)?; + } + if let Ok(v) = env::var("CO_CLIENTCHAIN_CHAIN") { + let _ = conf_rs.set("clientchain.chain", v)?; + } + if let Ok(v) = env::var("CO_CLIENTCHAIN_PAYMENT_ASSET") { + let _ = conf_rs.set("clientchain.payment_asset", v)?; + } + if let Ok(v) = env::var("CO_CLIENTCHAIN_PAYMENT_KEY") { + let _ = conf_rs.set("clientchain.payment_key", v)?; + } + if let Ok(v) = env::var("CO_CLIENTCHAIN_PAYMENT_ADDR") { + let _ = conf_rs.set("clientchain.payment_addr", v)?; + } if let Ok(v) = env::var("CO_STORAGE_HOST") { let _ = conf_rs.set("storage.host", v)?; @@ -225,18 +263,34 @@ impl Config { } // Perform type checks - if let Ok(key) = conf_rs.get_str("clientchain.asset_key") { - if !check_privkey_string(&key) { - println!("{}", CError::InputError(PrivKey, key.clone())); - return Err(Error::from(CError::InputError(PrivKey, key))); - } - } - if let Ok(hash) = conf_rs.get_str("clientchain.genesis_hash") { - if !check_hash_string(&hash) { - println!("{}", CError::InputError(GenHash, hash.clone())); - return Err(Error::from(CError::InputError(GenHash, hash))); - } + let key = conf_rs.get_str("clientchain.asset_key")?; + if !check_privkey_string(&key) { + return Err(Error::from(CError::InputError(PrivKey, key))); + } + let payment_key = conf_rs.get::>("clientchain.payment_key")?; + if !payment_key.is_none() && !check_privkey_string(&payment_key.clone().unwrap()) { + return Err(Error::from(CError::InputError(PrivKey, payment_key.unwrap()))); + } + if let Some(payment_addr) = conf_rs.get::>("clientchain.payment_addr")? { + let _ = Address::from_str(&payment_addr)?; } + let hash = conf_rs.get_str("clientchain.genesis_hash")?; + if !check_hash_string(&hash) { + return Err(Error::from(CError::InputError(GenHash, hash))); + } + if conf_rs.get_str("clientchain.chain")?.len() == 0 { + return Err(Error::from(CError::InputError( + MissingArgument, + "clientchain.chain".into(), + ))); + } + if conf_rs.get_str("clientchain.payment_asset")?.len() == 0 { + return Err(Error::from(CError::InputError( + MissingArgument, + "clientchain.payment_asset".into(), + ))); + } + Ok(conf_rs.try_into()?) } } diff --git a/src/coordinator.rs b/src/coordinator.rs index f494506..3e583e2 100644 --- a/src/coordinator.rs +++ b/src/coordinator.rs @@ -6,15 +6,15 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::{thread, time}; -use bitcoin_hashes::{hex::FromHex, sha256d}; +use bitcoin::hashes::{hex::FromHex, sha256d}; use futures::sync::oneshot; use crate::challenger::ChallengeResponse; -use crate::clientchain::{ClientChain, RpcClientChain}; use crate::config::Config; use crate::error::Result; -use crate::service::{RpcService, Service}; -use crate::storage::{MongoStorage, Storage}; +use crate::interfaces::clientchain::{ClientChain, RpcClientChain}; +use crate::interfaces::service::{RpcService, Service}; +use crate::interfaces::storage::{MongoStorage, Storage}; /// Run coordinator main method pub fn run(config: Config) -> Result<()> { @@ -26,18 +26,21 @@ pub fn run(config: Config) -> Result<()> { let genesis_hash = sha256d::Hash::from_hex(&config.clientchain.genesis_hash)?; let _ = ::api::run_api_server(&config.api, storage.clone()); + let (req_send, req_recv): (Sender, Receiver) = channel(); + let _ = ::payments::run_payments(config.clientchain.clone(), storage.clone(), req_recv)?; // This loop runs continuously fetching and running challenge requests, // generating challenge responses and fails on any errors that occur loop { if let Some(request_id) = run_request(&config, &service, &clientchain, storage.clone(), genesis_hash)? { // if challenge request succeeds print responses - println! {"***** Responses *****"} - let resp = storage.get_responses(request_id).unwrap(); - println! {"{}", serde_json::to_string_pretty(&resp).unwrap()}; + req_send.send(request_id).unwrap(); + info! {"***** Response *****"} + let resp = storage.get_response(request_id)?.unwrap(); + info! {"{}", serde_json::to_string_pretty(&resp).unwrap()}; } - info! {"Sleeping for 10 sec..."} - thread::sleep(time::Duration::from_secs(10)) + info! {"Sleeping for {} sec...", config.block_time} + thread::sleep(time::Duration::from_secs(config.block_time)) } } @@ -52,10 +55,17 @@ pub fn run_request( genesis_hash: sha256d::Hash, ) -> Result> { match ::challenger::fetch_next(service, &genesis_hash)? { - Some(challenge) => { - // first attempt to store the challenge state information - // on requests and winning bids and exit if that fails - storage.save_challenge_state(&challenge)?; + Some(mut challenge) => { + // First attempt to store the challenge state information + // on requests and winning bids and exit if it fails. + // If already set update challenge state with correct version from storage + ::challenger::update_challenge_request_state( + clientchain, + storage.clone(), + &mut challenge, + config.block_time, + config.clientchain.block_time, + )?; // create a challenge state mutex to share between challenger and listener let shared_challenge = Arc::new(Mutex::new(challenge)); @@ -74,10 +84,10 @@ pub fn run_request( shared_challenge.clone(), &verify_rx, storage.clone(), - time::Duration::from_secs(config.verify_duration), + time::Duration::from_secs(5 * config.block_time), time::Duration::from_secs(config.challenge_duration), config.challenge_frequency, - time::Duration::from_secs(10), + time::Duration::from_secs(config.block_time / 2), )?; // stop listener service diff --git a/src/error.rs b/src/error.rs index ba7b19a..b62ebe4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,11 +6,13 @@ use std::error; use std::fmt; use std::result; -use bitcoin_hashes::Error as HashesError; +use bitcoin::hashes::hex::Error as HashesHexError; +use bitcoin::hashes::Error as HashesError; +use bitcoin::secp256k1::Error as Secp256k1Error; use config_rs::ConfigError; use mongodb::Error as MongoDbError; +use ocean::AddressError; use ocean_rpc::Error as OceanRpcError; -use secp256k1::Error as Secp256k1Error; /// Crate specific Result for crate specific Errors pub type Result = result::Result; @@ -20,6 +22,8 @@ pub type Result = result::Result; pub enum CError { /// Missing bids for a specific request error MissingBids, + /// Challenge was not successfully verified + UnverifiedChallenge, /// Listener receiver disconnected error ReceiverDisconnected, /// Missing unspent for challenge asset. Takes parameters asset label and @@ -44,15 +48,16 @@ pub enum InputErrorType { PrivKey, /// Invalid genesis hash string GenHash, + /// Missing input argument + MissingArgument, } impl InputErrorType { fn as_str(&self) -> &'static str { match *self { - InputErrorType::PrivKey => "Invalid private key input - must be base58check string of length 52.", - InputErrorType::GenHash => { - "Invalid client chain genesis hash input - must be hexadecimal string of length 64." - } + InputErrorType::PrivKey => "Private key input - must be base58check string of length 52", + InputErrorType::GenHash => "Chain genesis hash input must be hexadecimal string of length 64", + InputErrorType::MissingArgument => "Argument missing", } } } @@ -61,9 +66,7 @@ impl fmt::Display for CError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { CError::Generic(ref e) => write!(f, "generic Error: {}", e), - CError::InputError(ref error, ref value) => { - write!(f, "Input Error: {} \nProblem value: {}", error.as_str(), value) - } + CError::InputError(ref error, ref value) => write!(f, "Input Error: {} (value: {})", error.as_str(), value), CError::MissingUnspent(ref asset, ref chain) => { write!(f, "No unspent found for {} asset on {} chain", asset, chain) } @@ -77,6 +80,7 @@ impl error::Error for CError { match *self { CError::Generic(_) => "Generic error", CError::MissingBids => "No bids found", + CError::UnverifiedChallenge => "Challenge not successfully verified", CError::ReceiverDisconnected => "Challenge response receiver disconnected", CError::MissingUnspent(_, _) => "No unspent found for asset", CError::InputError(_, _) => "Input parameter error", @@ -96,6 +100,10 @@ pub enum Error { OceanRpc(OceanRpcError), /// Bitcoin hashes error BitcoinHashes(HashesError), + /// Bitcoin hex hashes error + BitcoinHashesHex(HashesHexError), + /// Ocean address error + OceanAddress(AddressError), /// Secp256k1 error Secp256k1(Secp256k1Error), /// Mongodb error @@ -124,12 +132,24 @@ impl From for Error { } } +impl From for Error { + fn from(e: HashesHexError) -> Error { + Error::BitcoinHashesHex(e) + } +} + impl From for Error { fn from(e: Secp256k1Error) -> Error { Error::Secp256k1(e) } } +impl From for Error { + fn from(e: AddressError) -> Error { + Error::OceanAddress(e) + } +} + impl From for Error { fn from(e: MongoDbError) -> Error { Error::MongoDb(e) @@ -147,7 +167,9 @@ impl fmt::Display for Error { match *self { Error::OceanRpc(ref e) => write!(f, "ocean rpc error: {}", e), Error::BitcoinHashes(ref e) => write!(f, "bitcoin hashes error: {}", e), + Error::BitcoinHashesHex(ref e) => write!(f, "bitcoin hashes hex error: {}", e), Error::Secp256k1(ref e) => write!(f, "secp256k1 error: {}", e), + Error::OceanAddress(ref e) => write!(f, "ocean address error: {}", e), Error::MongoDb(ref e) => write!(f, "mongodb error: {}", e), Error::Config(ref e) => write!(f, "config error: {}", e), Error::Coordinator(ref e) => write!(f, "coordinator error: {}", e), @@ -160,7 +182,9 @@ impl error::Error for Error { match *self { Error::OceanRpc(ref e) => Some(e), Error::BitcoinHashes(ref e) => Some(e), + Error::BitcoinHashesHex(ref e) => Some(e), Error::Secp256k1(ref e) => Some(e), + Error::OceanAddress(ref e) => Some(e), Error::MongoDb(ref e) => Some(e), Error::Config(ref e) => Some(e), Error::Coordinator(_) => None, diff --git a/src/request.rs b/src/interfaces/bid.rs similarity index 52% rename from src/request.rs rename to src/interfaces/bid.rs index 316900a..0af7328 100644 --- a/src/request.rs +++ b/src/interfaces/bid.rs @@ -1,46 +1,14 @@ -//! # Request +//! # Bid //! -//! Service request models for client requests and bids +//! Service request models for bids and bid payments use std::collections::HashSet; -use bitcoin_hashes::sha256d; -use ocean_rpc::json::{GetRequestBidsResultBid, GetRequestsResult}; -use secp256k1::key::PublicKey; +use bitcoin::{hashes::sha256d, secp256k1::PublicKey, Amount}; +use ocean::Address; +use ocean_rpc::json::GetRequestBidsResultBid; use serde::{Serialize, Serializer}; -/// Request struct storing info on client request and modelling data that need -/// to be stored -#[derive(Debug, PartialEq, Clone, Serialize)] -pub struct Request { - /// Ocean transaction ID of the request transaction - pub txid: sha256d::Hash, - /// Request start block height - pub start_blockheight: u32, - /// Request end block height - pub end_blockheight: u32, - /// Genesis blockhash of client issuing request - pub genesis_blockhash: sha256d::Hash, - /// Fee percentage for Guardnodes set by client - pub fee_percentage: u32, - /// Num of Guardnode tickets set by client - pub num_tickets: u32, -} - -impl Request { - /// Return an instance of Request from an ocean json rpc GetRequestsResult - pub fn from_json(res: &GetRequestsResult) -> Self { - Request { - txid: res.txid, - start_blockheight: res.start_block_height, - end_blockheight: res.end_block_height, - genesis_blockhash: res.genesis_block, - fee_percentage: res.fee_percentage, - num_tickets: res.num_tickets, - } - } -} - /// Bid struct storing successful bids and modelling data that need to be stored #[derive(Clone, Debug, PartialEq, Hash, Eq, Serialize)] pub struct Bid { @@ -49,6 +17,8 @@ pub struct Bid { /// Bid owner verification public key #[serde(serialize_with = "serialize_pubkey")] pub pubkey: PublicKey, + /// Bid payment optional + pub payment: Option, } impl Bid { @@ -56,11 +26,25 @@ impl Bid { pub fn from_json(res: &GetRequestBidsResultBid) -> Self { Bid { txid: res.txid, - pubkey: res.fee_pub_key, + pubkey: res.fee_pub_key.key, + payment: None, } } } +/// Bid payment struct holding information for fee payments received by bid +/// owners +#[derive(Clone, Debug, PartialEq, Hash, Eq, Serialize)] +pub struct BidPayment { + /// Bid payment transaction id; optional as might not be set yet + pub txid: Option, + /// Bid pay to address + pub address: Address, + /// Bid amount expected + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub amount: Amount, +} + /// Type defining a set of Bids pub type BidSet = HashSet; @@ -79,7 +63,7 @@ mod tests { use std::str::FromStr; - use bitcoin_hashes::hex::FromHex; + use bitcoin::hashes::hex::FromHex; #[test] fn serialize_pubkey_test() { @@ -88,11 +72,12 @@ mod tests { let bid = Bid { txid: sha256d::Hash::from_hex(txid_hex).unwrap(), pubkey: PublicKey::from_str(pubkey_hex).unwrap(), + payment: None, }; let serialized = serde_json::to_string(&bid); assert_eq!( - format!(r#"{{"txid":"{}","pubkey":"{}"}}"#, txid_hex, pubkey_hex), + format!(r#"{{"txid":"{}","pubkey":"{}","payment":null}}"#, txid_hex, pubkey_hex), serialized.unwrap() ); } diff --git a/src/clientchain.rs b/src/interfaces/clientchain.rs similarity index 83% rename from src/clientchain.rs rename to src/interfaces/clientchain.rs index 912ae5a..9bfb93a 100644 --- a/src/clientchain.rs +++ b/src/interfaces/clientchain.rs @@ -4,16 +4,16 @@ use std::collections::HashMap; -use bitcoin_hashes::{hex::FromHex, sha256d}; +use bitcoin::hashes::{hex::FromHex, sha256d}; use ocean_rpc::{json, RpcApi}; use crate::config::ClientChainConfig; use crate::error::{CError, Error, Result}; -use crate::ocean::OceanClient; +use crate::util::ocean::OceanClient; /// Method that returns the first unspent output for given asset /// or an error if the client wallet does not have any unspent/funds -pub fn get_first_unspent(client: &OceanClient, asset: &str) -> Result { +pub fn get_first_unspent(client: &OceanClient, asset: &str) -> Result { // Check asset is held by the wallet and return unspent tx let unspent = client.list_unspent(None, None, None, None, Some(asset))?; if unspent.is_empty() { @@ -33,6 +33,8 @@ pub trait ClientChain { fn send_challenge(&self) -> Result; /// Verify challenge transaction has been included in the chain fn verify_challenge(&self, txid: &sha256d::Hash) -> Result; + /// Get height of client chain + fn get_blockheight(&self) -> Result; } /// Rpc implementation of Service using an underlying ocean rpc connection @@ -85,26 +87,21 @@ impl<'a> ClientChain for RpcClientChain<'a> { }]; let mut outs = HashMap::new(); - let _ = outs.insert( - unspent.address.clone(), - (unspent.amount.into_inner() / 100000000) as f64, - ); + let _ = outs.insert(unspent.address.to_string(), unspent.amount); let mut outs_assets = HashMap::new(); - let _ = outs_assets.insert(unspent.address.clone(), unspent.asset.to_string()); + let _ = outs_assets.insert(unspent.address.to_string(), unspent.asset); let tx_hex = self .client - .create_raw_transaction_hex(&utxos, Some(&outs), Some(&outs_assets), None)?; + .create_raw_transaction_hex(&utxos, &outs, Some(&outs_assets), None)?; // sign the transaction and send via the client rpc - let tx_signed = - self.client - .sign_raw_transaction((&Vec::::from_hex(&tx_hex)? as &[u8]).into(), None, None, None)?; + let tx_signed = self + .client + .sign_raw_transaction(&Vec::::from_hex(&tx_hex)? as &[u8], None, None, None)?; - Ok(sha256d::Hash::from_hex( - &self.client.send_raw_transaction(&tx_signed.hex)?, - )?) + Ok(self.client.send_raw_transaction(&tx_signed.hex)?) } /// Verify challenge transaction has been included in the chain @@ -124,4 +121,9 @@ impl<'a> ClientChain for RpcClientChain<'a> { } Ok(false) } + + /// Return block count of chain + fn get_blockheight(&self) -> Result { + Ok(self.client.get_block_count()? as u32) + } } diff --git a/src/interfaces/mod.rs b/src/interfaces/mod.rs new file mode 100644 index 0000000..a75dcf4 --- /dev/null +++ b/src/interfaces/mod.rs @@ -0,0 +1,10 @@ +//! # Interfaces +//! +//! Interfaces used by the coordinator library + +pub mod bid; +pub mod clientchain; +pub mod request; +pub mod response; +pub mod service; +pub mod storage; diff --git a/src/interfaces/request.rs b/src/interfaces/request.rs new file mode 100644 index 0000000..76b959f --- /dev/null +++ b/src/interfaces/request.rs @@ -0,0 +1,49 @@ +//! # Request +//! +//! Service request models for client requests + +use bitcoin::hashes::sha256d; + +use ocean_rpc::json::GetRequestsResult; +use serde::Serialize; + +/// Request struct storing info on client request and modelling data that need +/// to be stored +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct Request { + /// Ocean transaction ID of the request transaction + pub txid: sha256d::Hash, + /// Request start block height + pub start_blockheight: u32, + /// Request end block height + pub end_blockheight: u32, + /// Genesis blockhash of client issuing request + pub genesis_blockhash: sha256d::Hash, + /// Fee percentage for Guardnodes set by client + pub fee_percentage: u32, + /// Num of Guardnode tickets set by client + pub num_tickets: u32, + /// Request client chain start block height + pub start_blockheight_clientchain: u32, + /// Request client chain end block height + pub end_blockheight_clientchain: u32, + /// Payment complete flag for request + pub is_payment_complete: bool, +} + +impl Request { + /// Return an instance of Request from an ocean json rpc GetRequestsResult + pub fn from_json(res: &GetRequestsResult) -> Self { + Request { + txid: res.txid, + start_blockheight: res.start_block_height, + end_blockheight: res.end_block_height, + genesis_blockhash: res.genesis_block, + fee_percentage: res.fee_percentage, + num_tickets: res.num_tickets, + start_blockheight_clientchain: 0, + end_blockheight_clientchain: 0, + is_payment_complete: false, + } + } +} diff --git a/src/interfaces/response.rs b/src/interfaces/response.rs new file mode 100644 index 0000000..f031694 --- /dev/null +++ b/src/interfaces/response.rs @@ -0,0 +1,39 @@ +//! # Response +//! +//! Response model for service challenge responses + +use std::collections::HashMap; + +use crate::challenger::ChallengeResponseIds; +use bitcoin::hashes::sha256d; +use serde::Serialize; + +/// Response struct that models responses to service challenges +/// by keeping track of the total number of challengers and the +/// number of challenges that each bid owner responded to +#[derive(Debug, Serialize, PartialEq)] +pub struct Response { + /// Total number of challenges + pub num_challenges: u32, + /// Number of responses per bid txid + pub bid_responses: HashMap, +} + +impl Response { + /// Create new Response instance + pub fn new() -> Response { + Response { + num_challenges: 0, + bid_responses: HashMap::new(), + } + } + + /// Update Response struct from challenge response ids + pub fn update(&mut self, responses: &ChallengeResponseIds) { + self.num_challenges += 1; + for txid in responses.iter() { + let bid_entry = self.bid_responses.entry(*txid).or_insert(0); + *bid_entry += 1; + } + } +} diff --git a/src/service.rs b/src/interfaces/service.rs similarity index 95% rename from src/service.rs rename to src/interfaces/service.rs index bb31319..6cffe87 100644 --- a/src/service.rs +++ b/src/interfaces/service.rs @@ -2,13 +2,16 @@ //! //! Service chain interface and implementations -use bitcoin_hashes::sha256d; +use bitcoin::hashes::sha256d; use ocean_rpc::RpcApi; use crate::config::ServiceConfig; use crate::error::Result; -use crate::ocean::OceanClient; -use crate::request::{Bid, BidSet, Request}; +use crate::interfaces::{ + bid::{Bid, BidSet}, + request::Request, +}; +use crate::util::ocean::OceanClient; /// Service trait defining functionality for interfacing with service chain pub trait Service { diff --git a/src/interfaces/storage.rs b/src/interfaces/storage.rs new file mode 100644 index 0000000..7602b2e --- /dev/null +++ b/src/interfaces/storage.rs @@ -0,0 +1,318 @@ +//! Storage +//! +//! Storage interface and implementations + +use std::mem::drop; +use std::sync::{Mutex, MutexGuard}; + +use bitcoin::hashes::sha256d; +use mongodb::db::{Database, ThreadedDatabase}; +use mongodb::{coll::options::FindOptions, Client, ThreadedClient}; +use util::doc_format::*; + +use crate::challenger::{ChallengeResponseIds, ChallengeState}; +use crate::config::StorageConfig; +use crate::error::{Error::MongoDb, Result}; +use crate::interfaces::response::Response; +use crate::interfaces::{ + bid::{Bid, BidSet}, + request::Request, +}; + +/// Storage trait defining required functionality for objects that store request +/// and challenge information +pub trait Storage { + /// Store the state of a challenge request + fn save_challenge_state(&self, challenge: &ChallengeState) -> Result<()>; + /// Update request in storage + fn update_request(&self, request: &Request) -> Result<()>; + /// Update bid in storage + fn update_bid(&self, request_hash: sha256d::Hash, bid: &Bid) -> Result<()>; + /// Store response for a specific challenge request + fn save_response(&self, request_hash: sha256d::Hash, ids: &ChallengeResponseIds) -> Result<()>; + /// Get challenge response for a specific request + fn get_response(&self, request_hash: sha256d::Hash) -> Result>; + /// Get all bids for a specific request + fn get_bids(&self, request_hash: sha256d::Hash) -> Result; + /// Get all the requests, with an optional flag to return payment complete + /// only + fn get_requests(&self, complete: Option) -> Result>; + /// Get request for a specific request txid + fn get_request(&self, request_hash: sha256d::Hash) -> Result>; +} + +/// Database implementation of Storage trait +pub struct MongoStorage { + db: Mutex, + config: StorageConfig, +} + +impl MongoStorage { + /// Create DbStorage instance + pub fn new(storage_config: StorageConfig) -> Result { + let uri = &format!("mongodb://{}/{}", storage_config.host, storage_config.name); + let client = Client::with_uri(&uri)?; + + let db = client.db("coordinator"); + if let Some(ref user) = storage_config.user { + if let Some(ref pass) = storage_config.pass { + db.auth(user, pass)?; + } + } + + // Specify collections Indexes + if let Err(e) = db.collection("Request").create_index(doc! ("txid":1), None) { + return Err(MongoDb(e)); + } + if let Err(e) = db.collection("Bid").create_index(doc! ("request_id":1), None) { + return Err(MongoDb(e)); + } + if let Err(e) = db.collection("Response").create_index(doc! ("request_id":1), None) { + return Err(MongoDb(e)); + } + + Ok(MongoStorage { + db: Mutex::new(db), + config: storage_config, + }) + } + + /// Do db authentication using user/pass from config + fn auth(&self, db_locked: &MutexGuard) -> Result<()> { + match db_locked.list_collections(None) { + // only do authentication if connectivity check fails + Err(_) => { + if let Some(ref user) = self.config.user { + if let Some(ref pass) = self.config.pass { + db_locked.auth(user, pass)?; + } + } + } + _ => (), + } + Ok(()) + } +} + +impl Storage for MongoStorage { + /// Store the state of a challenge request + fn save_challenge_state(&self, challenge: &ChallengeState) -> Result<()> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let request_id; + let coll = db_locked.collection("Request"); + let filter = doc! {"txid"=>challenge.request.txid.to_string()}; + match coll.find_one(Some(filter), None)? { + Some(res) => { + request_id = res.get("_id").unwrap().clone(); + } + None => { + request_id = coll + .insert_one(request_to_doc(&challenge.request), None)? + .inserted_id + .unwrap(); + } + } + + let coll = db_locked.collection("Bid"); + for bid in challenge.bids.iter() { + let doc = bid_to_doc(&request_id, bid); + match coll.find_one(Some(doc.clone()), None)? { + Some(_) => (), + None => { + let _ = coll.insert_one(doc, None)?; + } + } + } + Ok(()) + } + + /// Update entry in Request collection with given Request object + fn update_request(&self, request: &Request) -> Result<()> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + let coll = db_locked.collection("Request"); + let filter = doc! {"txid"=>&request.txid.clone().to_string()}; + let update = doc! {"$set" => request_to_doc(&request)}; + let _ = coll.update_one(filter, update, None)?; + Ok(()) + } + + /// Update entry in Bid collection with given Bid object + fn update_bid(&self, request_hash: sha256d::Hash, bid: &Bid) -> Result<()> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let request_id = db_locked + .collection("Request") + .find_one( + Some(doc! { + "txid": request_hash.to_string(), + }), + None, + )? + .unwrap() + .get("_id") + .unwrap() + .clone(); + + let coll = db_locked.collection("Bid"); + let filter = doc! {"request_id": request_id.clone()}; + let update = doc! {"$set" => bid_to_doc(&request_id, &bid)}; + let _ = coll.update_one(filter, update, None)?; + Ok(()) + } + + /// Store response for a specific challenge request + fn save_response(&self, request_hash: sha256d::Hash, ids: &ChallengeResponseIds) -> Result<()> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let request_id = db_locked + .collection("Request") + .find_one( + Some(doc! { + "txid": request_hash.to_string(), + }), + None, + )? + .unwrap() + .get("_id") + .unwrap() + .clone(); + + let coll = db_locked.collection("Response"); + let filter = doc! {"request_id": request_id.clone()}; + match coll.find_one(Some(filter.clone()), None)? { + Some(res) => { + let mut resp = doc_to_response(&res); + resp.update(ids); + let update = doc! {"$set" => response_to_doc(&request_id, &resp)}; + let _ = coll.update_one(filter, update, None)?; + } + None => { + let mut resp = Response::new(); + resp.update(ids); + let _ = coll.insert_one(response_to_doc(&request_id, &resp), None)?; + } + } + Ok(()) + } + + /// Get challenge response for a specific request + fn get_response(&self, request_hash: sha256d::Hash) -> Result> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let mut resp_aggr = db_locked.collection("Request").aggregate( + [ + doc! { + "$lookup": { + "from": "Response", + "localField": "_id", + "foreignField": "request_id", + "as": "response" + } + }, + doc! { + "$match": { + "txid": request_hash.to_string() + }, + }, + doc! { + "$unwind": { + "path": "$response" + } + }, + ] + .to_vec(), + None, + )?; + drop(db_locked); // drop immediately on get requests + + if let Some(resp) = resp_aggr.next() { + return Ok(Some(doc_to_response(&resp?.get_document("response").unwrap()))); + } + Ok(None) + } + + /// Get all bids for a specific request + fn get_bids(&self, request_hash: sha256d::Hash) -> Result { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let mut resp_aggr = db_locked.collection("Request").aggregate( + [ + doc! { + "$lookup": { + "from": "Bid", + "localField": "_id", + "foreignField": "request_id", + "as": "bids" + } + }, + doc! { + "$match": { + "txid": request_hash.to_string() + }, + }, + ] + .to_vec(), + None, + )?; + drop(db_locked); // drop immediately on get requests + + let mut all_bids = BidSet::new(); + if let Some(resp) = resp_aggr.next() { + for bid in resp?.get_array("bids").unwrap().iter() { + let _ = all_bids.insert(doc_to_bid(bid.as_document().unwrap())); + } + } + Ok(all_bids) + } + + /// Get all the requests, with an optional flag to return payment complete + /// only + fn get_requests(&self, complete: Option) -> Result> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let mut options = FindOptions::new(); + options.sort = Some(doc! { "_id" : 1 }); // sort ascending, latest request is last + let filter = if let Some(is_complete) = complete { + Some(doc! { "is_payment_complete": is_complete }) + } else { + None + }; + let resps = db_locked.collection("Request").find(filter, Some(options))?; + drop(db_locked); // drop immediately on get requests + + let mut requests = vec![]; + for resp in resps { + if let Ok(req) = resp { + requests.push(doc_to_request(&req)) + } + } + Ok(requests) + } + + /// Get request for a specific request txid + fn get_request(&self, request_hash: sha256d::Hash) -> Result> { + let db_locked = self.db.lock().unwrap(); + self.auth(&db_locked)?; + + let request = db_locked.collection("Request").find_one( + Some(doc! { + "txid": request_hash.to_string(), + }), + None, + )?; + drop(db_locked); // drop immediately on get requests + + match request { + Some(doc) => Ok(Some(doc_to_request(&doc))), + None => Ok(None), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d6812fa..34c7106 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! # Coordinator Library //! -//! Basic functionality required by Coordinator daemon +//! Core functionality of the coordinator library // Coding conventions #![deny(non_upper_case_globals)] @@ -20,13 +20,11 @@ extern crate log; extern crate base64; extern crate bitcoin; -extern crate bitcoin_hashes; extern crate config as config_rs; extern crate futures; extern crate hyper; extern crate ocean_rpc; -extern crate rust_ocean as _ocean; -extern crate secp256k1; +extern crate rust_ocean as ocean; extern crate serde as serde; extern crate serde_json; #[macro_use] @@ -35,17 +33,11 @@ extern crate jsonrpc_http_server; pub mod api; pub mod challenger; -pub mod clientchain; pub mod config; pub mod coordinator; pub mod error; pub mod listener; -pub mod ocean; -pub mod request; -pub mod service; -pub mod storage; -/// utilities -pub mod util { - pub mod checks; - pub mod testing; -} +pub mod payments; + +pub mod interfaces; +pub mod util; diff --git a/src/listener.rs b/src/listener.rs index 0cc59c1..6352e59 100644 --- a/src/listener.rs +++ b/src/listener.rs @@ -9,18 +9,18 @@ use std::sync::{Arc, Mutex}; use std::thread; use bitcoin::consensus::serialize; -use bitcoin_hashes::{hex::FromHex, sha256d}; +use bitcoin::hashes::{hex::FromHex, sha256d}; +use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, Signature}; use futures::future; use futures::sync::oneshot; use hyper::rt::{self, Future, Stream}; use hyper::service::service_fn; use hyper::{Body, Method, Request, Response, Server, StatusCode}; -use secp256k1::{Message, PublicKey, Secp256k1, Signature}; use serde_json::{self, Value}; use crate::challenger::{ChallengeResponse, ChallengeState}; use crate::error::Result; -use crate::request::Bid; +use crate::interfaces::bid::Bid; /// Messsage type for challenge proofs sent by guardnodes #[derive(Debug)] @@ -43,7 +43,11 @@ impl ChallengeProof { Ok(ChallengeProof { hash, sig, - bid: Bid { txid, pubkey }, + bid: Bid { + txid, + pubkey, + payment: None, + }, }) } @@ -177,8 +181,8 @@ mod tests { use std::sync::mpsc::{channel, Receiver, TryRecvError}; - use bitcoin_hashes::hex::ToHex; - use secp256k1::SecretKey; + use bitcoin::hashes::hex::ToHex; + use bitcoin::secp256k1::SecretKey; use crate::util::testing::{gen_challenge_state_with_challenge, gen_dummy_hash}; @@ -204,7 +208,7 @@ mod tests { "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c" }"#; let proof = ChallengeProof::from_json(serde_json::from_str::(data).unwrap()); - assert!(proof.err().unwrap().to_string().contains("bitcoin hashes error")); + assert!(proof.err().unwrap().to_string().contains("bitcoin hashes hex error")); // bad pubkey let data = r#" @@ -226,7 +230,7 @@ mod tests { "sig": "304402201742daea5ec3b7306b9164be862fc1659cc830032180b8b17beffe02645860d602201039eba402d22e630308e6af05da8dd4f05b51b7d672ca5fc9e3b0a57776365c" }"#; let proof = ChallengeProof::from_json(serde_json::from_str::(data).unwrap()); - assert!(proof.err().unwrap().to_string().contains("bitcoin hashes error")); + assert!(proof.err().unwrap().to_string().contains("bitcoin hashes hex error")); // bad sig let data = r#" @@ -258,6 +262,7 @@ mod tests { bid: Bid { txid: bid_txid, pubkey: bid_pubkey, + payment: None, }, }; @@ -274,6 +279,7 @@ mod tests { bid: Bid { txid: bid_txid, pubkey: bid_pubkey, + payment: None, }, }; @@ -414,6 +420,7 @@ mod tests { Bid { txid: bid_txid, pubkey: bid_pubkey, + payment: None }, )) ); // check receiver not empty @@ -662,6 +669,7 @@ mod tests { Bid { txid: bid_txid, pubkey: bid_pubkey, + payment: None }, )) ); // check receiver not empty diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 3bc2708..0000000 --- a/src/main.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! # Main -//! -//! Main daemon entry - -#[macro_use] -extern crate log; -extern crate coordinator; -extern crate env_logger; - -use std::env; -use std::process; - -fn main() { - // Fetch config which is set from default values in config - // and any values overriden by the corresponding env variable - let config = coordinator::config::Config::new().unwrap(); - - // To see results set RUST_LOG to one of the following: - // info, warning, debug, error, coordinator(for all) - env::set_var("RUST_LOG", &config.log_level); - env::set_var("RUST_BACKTRACE", "1"); - // Init env logger with value set from config - env_logger::init(); - - if let Err(e) = coordinator::coordinator::run(config) { - error!("daemon failure: {}", e); - process::exit(1); - } -} diff --git a/src/payments.rs b/src/payments.rs new file mode 100644 index 0000000..7d9ba3e --- /dev/null +++ b/src/payments.rs @@ -0,0 +1,278 @@ +//! Payments +//! +//! TODO: Add description + +use std::str::FromStr; +use std::sync::mpsc::{Receiver, RecvError}; +use std::sync::Arc; +use std::thread; + +use bitcoin::hashes::sha256d; +use bitcoin::Amount; +use bitcoin::PublicKey; +use ocean::{Address, AddressParams}; +use ocean_rpc::RpcApi; + +use crate::config::ClientChainConfig; +use crate::error::{CError, Error, Result}; +use crate::interfaces::{ + bid::{Bid, BidPayment}, + request::Request, + response::Response, + storage::Storage, +}; +use crate::util::ocean::OceanClient; + +/// Get addr params from chain name +pub fn get_chain_addr_params(chain: &String) -> &'static AddressParams { + match chain.to_lowercase().as_ref() { + "ocean_main" => return &AddressParams::OCEAN, + "gold_main" => return &AddressParams::GOLD, + _ => &AddressParams::ELEMENTS, + } +} + +/// Function that calculates all the fees accumulated in the duration of a +/// service request in the clientchain +fn calculate_fees(request: &Request, client: &OceanClient) -> Result { + let mut fee_sum = Amount::ZERO; + for i in request.start_blockheight_clientchain..request.end_blockheight_clientchain { + let block = client.get_block_info(&client.get_block_hash(i.into())?)?; + let tx = client.get_raw_transaction_verbose(&block.tx[0], None)?; // coinbase tx + assert!(tx.is_coinbase() == true); + for txout in tx.vout { + match txout.assetlabel { + Some(label) => { + // any other label is a policy asset + if label == "CBT" { + fee_sum += txout.value; + } + } + None => fee_sum += txout.value, + } + } + } + Ok(fee_sum) +} + +/// Function that calculates the fee amount to be received per bid given total +/// fees, fee percentage and bid number +fn calculate_bid_payment(fees_amount: &Amount, fee_percentage: u64, num_bids: u64) -> Result { + let total_amount = *fees_amount * fee_percentage / 100; + Ok(total_amount / num_bids) // amount per bid +} + +/// Payment Struct holding data and logic required to pay bids at the end of the +/// service request +pub struct Payments { + /// Thread safe storage instance + pub storage: Arc, + /// Client config required for fee payments + pub config: ClientChainConfig, + /// Ocean rpc connectivity to client chain + pub client: OceanClient, + /// Clientchain address params required for fee payments + pub addr_params: &'static AddressParams, +} + +impl Payments { + /// TODO: implement payments + fn complete_bid_payments(&self, _bids: &mut Vec) -> Result<()> { + // pay + // set bid txid + Ok(()) + } + + /// Process bid payments method handles calculating the payment to be + /// received per bid and on which address, and updates the corresponding + /// payment info in Storage + fn process_bid_payments(&self, bids: &mut Vec, bid_payment: &Amount, response: &Response) -> Result<()> { + for bid in bids { + if let Some(bid_resp) = response.bid_responses.get(&bid.txid) { + // correct bid payment by calculating the performance + // base on successful responses / total responses + let bid_payment_corrected = *bid_payment * (*bid_resp).into() / response.num_challenges.into(); + let bid_pay_to_addr = Address::p2pkh( + &PublicKey { + key: bid.pubkey, + compressed: true, + }, + None, + self.addr_params, + ); + + bid.payment = Some(BidPayment { + amount: bid_payment_corrected, + address: bid_pay_to_addr, + txid: None, + }); + } + } + Ok(()) + } + + /// Method that handles payments for a single request, fetching bid + /// information, calculating fees, updating payment information and doing + /// payments + fn do_request_payment(&self, request: &mut Request) -> Result<()> { + // skip requests that have not finished + if request.end_blockheight_clientchain == 0 + || (self.client.get_block_count()? as u32) < request.end_blockheight_clientchain + { + warn! {"Skipping unfinished request: {}", request.txid}; + } + + // fetch bids and responses + let bids_set = self.storage.get_bids(request.txid)?; + if bids_set.len() > 0 { + let mut bids: Vec = bids_set.iter().map(|val| val.clone()).collect(); + if let Some(resp) = self.storage.get_response(request.txid)? { + let fees_amount = calculate_fees(request, &self.client)?; + info! {"Total fees: {}", fees_amount}; + let bid_payment_amount = + calculate_bid_payment(&fees_amount, request.fee_percentage.into(), bids.len() as u64)?; + self.process_bid_payments(&mut bids, &bid_payment_amount, &resp)?; + self.complete_bid_payments(&mut bids)?; + } + + // update bids with payment information + for bid in bids { + self.storage.update_bid(request.txid, &bid)?; + } + } + + // update request with payment complete + request.is_payment_complete = true; + self.storage.update_request(request)?; + Ok(()) + } + + /// Main Request payments method; first checks for any incomplete requests + /// and then listens for new requests on the receiver channel + fn do_request_payments(&self, req_recv: Receiver) -> Result<()> { + // Look for incomplete requests + let incomplete_requests = self.storage.get_requests(Some(false))?; + for mut req in incomplete_requests { + info! {"Found incomplete request: {} ", req.txid}; + let _ = self.do_request_payment(&mut req)?; + } + + // Wait for new requests + loop { + match req_recv.recv() { + Ok(resp) => { + let mut req = self.storage.get_request(resp)?.unwrap(); + info! {"New request: {}", req.txid}; + let _ = self.do_request_payment(&mut req)?; + } + Err(RecvError) => { + return Err(Error::from(CError::ReceiverDisconnected)); + } + } + } + } + + /// Return new Payments instance that requires clientchain config for + /// various payment info and rpc calls to calculate payment fees and do the + /// payments as well as a thread-safe reference to a Storage instance for + /// getting request information and updating payment details + pub fn new(clientchain_config: ClientChainConfig, storage: Arc) -> Result { + let client = OceanClient::new( + clientchain_config.host.clone(), + Some(clientchain_config.user.clone()), + Some(clientchain_config.pass.clone()), + )?; + + // Check if payment addr/key are set and import the key for payment funds + let addr_params = get_chain_addr_params(&clientchain_config.chain); + if let Some(addr) = &clientchain_config.payment_addr { + let ocean_addr = Address::from_str(&addr)?; + if *ocean_addr.params != *addr_params { + warn!("payment addr and chain config addr param mismatch"); + } else if let Some(key) = &clientchain_config.payment_key { + let addr_unspent = client.list_unspent(None, None, Some(&[ocean_addr]), None, None)?; + if addr_unspent.len() == 0 { + client.import_priv_key(key, None, Some(true))?; + } + } else { + warn!("payment key missing"); + } + } else { + warn!("payment addr missing"); + } + + Ok(Payments { + storage: storage, + config: clientchain_config, + client: client, + addr_params: addr_params, + }) + } +} + +/// Run payments daemon in a separate thread with a Payments instance receiving +/// information on finished requests via a Receiver channel +pub fn run_payments( + clientchain_config: ClientChainConfig, + storage: Arc, + req_recv: Receiver, +) -> Result> { + let payments = Payments::new(clientchain_config, storage)?; + Ok(thread::spawn(move || { + if let Err(err) = payments.do_request_payments(req_recv) { + error! {"payments error: {}", err}; + } + })) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn calculate_bid_payment_test() { + assert_eq!( + 1.125, + calculate_bid_payment(&Amount::from_btc(6.0).unwrap(), 75, 4) + .unwrap() + .as_btc() + ); + assert_eq!( + 0.24, + calculate_bid_payment(&Amount::from_btc(4.0).unwrap(), 30, 5) + .unwrap() + .as_btc() + ); + assert_eq!( + 1.0, + calculate_bid_payment(&Amount::from_btc(100.0).unwrap(), 1, 1) + .unwrap() + .as_btc() + ); + assert_eq!( + 0.0, + calculate_bid_payment(&Amount::from_btc(0.0).unwrap(), 1, 1) + .unwrap() + .as_btc() + ); + assert_eq!( + 0.0, + calculate_bid_payment(&Amount::from_btc(4.0).unwrap(), 0, 5) + .unwrap() + .as_btc() + ); + } + + #[test] + fn get_chain_addr_params_test() { + assert_eq!( + &AddressParams::OCEAN, + get_chain_addr_params(&String::from("ocean_main")) + ); + assert_eq!(&AddressParams::GOLD, get_chain_addr_params(&String::from("gold_main"))); + assert_eq!( + &AddressParams::ELEMENTS, + get_chain_addr_params(&String::from("supersilverhazechain")) + ); + } +} diff --git a/src/storage.rs b/src/storage.rs deleted file mode 100644 index 88fe94e..0000000 --- a/src/storage.rs +++ /dev/null @@ -1,395 +0,0 @@ -//! Storage -//! -//! Storage interface and implementations - -use std::mem::drop; -use std::str::FromStr; -use std::sync::{Mutex, MutexGuard}; - -use bitcoin_hashes::{hex::FromHex, sha256d}; -use mongodb::db::{Database, ThreadedDatabase}; -use mongodb::ordered::OrderedDocument; -use mongodb::{coll::options::FindOptions, Bson, Client, ThreadedClient}; -use secp256k1::key::PublicKey; - -use crate::challenger::{ChallengeResponseIds, ChallengeState}; -use crate::config::StorageConfig; -use crate::error::Result; -use crate::request::{Bid, BidSet, Request}; - -/// Storage trait defining required functionality for objects that store request -/// and challenge information -pub trait Storage { - /// Store the state of a challenge request - fn save_challenge_state(&self, challenge: &ChallengeState) -> Result<()>; - /// Store responses for a specific challenge request - fn save_response(&self, request_hash: sha256d::Hash, ids: &ChallengeResponseIds) -> Result<()>; - /// Get all challenge responses for a specific request - fn get_responses(&self, request_hash: sha256d::Hash) -> Result>; - /// Get all bids for a specific request - fn get_bids(&self, request_hash: sha256d::Hash) -> Result; - /// Get all the requests - fn get_requests(&self) -> Result>; - /// Get request for a specific request txid - fn get_request(&self, request_hash: sha256d::Hash) -> Result>; -} - -/// Database implementation of Storage trait -pub struct MongoStorage { - db: Mutex, - config: StorageConfig, -} - -impl MongoStorage { - /// Create DbStorage instance - pub fn new(storage_config: StorageConfig) -> Result { - let uri = &format!("mongodb://{}/{}", storage_config.host, storage_config.name); - let client = Client::with_uri(&uri)?; - - let db = client.db("coordinator"); - if let Some(ref user) = storage_config.user { - if let Some(ref pass) = storage_config.pass { - db.auth(user, pass)?; - } - } - - Ok(MongoStorage { - db: Mutex::new(db), - config: storage_config, - }) - } - - /// Do db authentication using user/pass from config - fn auth(&self, db_locked: &MutexGuard) -> Result<()> { - match db_locked.list_collections(None) { - // only do authentication if connectivity check fails - Err(_) => { - if let Some(ref user) = self.config.user { - if let Some(ref pass) = self.config.pass { - db_locked.auth(user, pass)?; - } - } - } - _ => (), - } - Ok(()) - } -} - -impl Storage for MongoStorage { - /// Store the state of a challenge request - fn save_challenge_state(&self, challenge: &ChallengeState) -> Result<()> { - let db_locked = self.db.lock().unwrap(); - self.auth(&db_locked)?; - - let request_id; - let coll = db_locked.collection("Request"); - let doc = request_to_doc(&challenge.request); - match coll.find_one(Some(doc.clone()), None)? { - Some(res) => request_id = res.get("_id").unwrap().clone(), - None => { - request_id = coll.insert_one(doc, None)?.inserted_id.unwrap(); - } - } - - let coll = db_locked.collection("Bid"); - for bid in challenge.bids.iter() { - let doc = bid_to_doc(&request_id, bid); - match coll.find_one(Some(doc.clone()), None)? { - Some(_) => (), - None => { - let _ = coll.insert_one(doc, None)?; - } - } - } - Ok(()) - } - - /// Store responses for a specific challenge request - fn save_response(&self, request_hash: sha256d::Hash, ids: &ChallengeResponseIds) -> Result<()> { - let db_locked = self.db.lock().unwrap(); - self.auth(&db_locked)?; - - let request = db_locked - .collection("Request") - .find_one( - Some(doc! { - "txid": request_hash.to_string(), - }), - None, - )? - .unwrap(); - - let _ = db_locked - .collection("Response") - .insert_one(challenge_responses_to_doc(request.get("_id").unwrap(), ids), None)?; - Ok(()) - } - - /// Get all challenge responses for a specific request - fn get_responses(&self, request_hash: sha256d::Hash) -> Result> { - let db_locked = self.db.lock().unwrap(); - self.auth(&db_locked)?; - - let mut resp_aggr = db_locked.collection("Request").aggregate( - [ - doc! { - "$lookup": { - "from": "Response", - "localField": "_id", - "foreignField": "request_id", - "as": "challenges" - } - }, - doc! { - "$match": { - "txid": request_hash.to_string() - }, - }, - ] - .to_vec(), - None, - )?; - drop(db_locked); // drop immediately on get requests - - let mut all_resps: Vec = Vec::new(); - if let Some(resp) = resp_aggr.next() { - for challenge in resp?.get_array("challenges").unwrap().iter() { - all_resps.push(doc_to_challenge_responses(challenge.as_document().unwrap())) - } - } - Ok(all_resps) - } - - /// Get all bids for a specific request - fn get_bids(&self, request_hash: sha256d::Hash) -> Result { - let db_locked = self.db.lock().unwrap(); - self.auth(&db_locked)?; - - let mut resp_aggr = db_locked.collection("Request").aggregate( - [ - doc! { - "$lookup": { - "from": "Bid", - "localField": "_id", - "foreignField": "request_id", - "as": "bids" - } - }, - doc! { - "$match": { - "txid": request_hash.to_string() - }, - }, - ] - .to_vec(), - None, - )?; - drop(db_locked); // drop immediately on get requests - - let mut all_bids = BidSet::new(); - if let Some(resp) = resp_aggr.next() { - for bid in resp?.get_array("bids").unwrap().iter() { - let _ = all_bids.insert(doc_to_bid(bid.as_document().unwrap())); - } - } - Ok(all_bids) - } - - /// Get all the requests - fn get_requests(&self) -> Result> { - let db_locked = self.db.lock().unwrap(); - self.auth(&db_locked)?; - - let mut options = FindOptions::new(); - options.sort = Some(doc! { "_id" : 1 }); // sort ascending, latest request is last - let resps = db_locked.collection("Request").find(None, Some(options))?; - drop(db_locked); // drop immediately on get requests - - let mut requests = vec![]; - for resp in resps { - if let Ok(req) = resp { - requests.push(doc_to_request(&req)) - } - } - Ok(requests) - } - - /// Get request for a specific request txid - fn get_request(&self, request_hash: sha256d::Hash) -> Result> { - let db_locked = self.db.lock().unwrap(); - self.auth(&db_locked)?; - - let request = db_locked.collection("Request").find_one( - Some(doc! { - "txid": request_hash.to_string(), - }), - None, - )?; - drop(db_locked); // drop immediately on get requests - - match request { - Some(doc) => Ok(Some(doc_to_request(&doc))), - None => Ok(None), - } - } -} - -/// Util method that generates a Request document from a request -pub fn request_to_doc(request: &Request) -> OrderedDocument { - doc! { - "txid": request.txid.to_string(), - "start_blockheight": request.start_blockheight, - "end_blockheight": request.end_blockheight, - "genesis_blockhash": request.genesis_blockhash.to_string(), - "fee_percentage": request.fee_percentage, - "num_tickets": request.num_tickets - } -} - -/// Util method that generates a request from a Request document -pub fn doc_to_request(doc: &OrderedDocument) -> Request { - Request { - txid: sha256d::Hash::from_hex(doc.get("txid").unwrap().as_str().unwrap()).unwrap(), - start_blockheight: doc.get("start_blockheight").unwrap().as_i32().unwrap() as u32, - end_blockheight: doc.get("end_blockheight").unwrap().as_i32().unwrap() as u32, - genesis_blockhash: sha256d::Hash::from_hex(doc.get("genesis_blockhash").unwrap().as_str().unwrap()).unwrap(), - fee_percentage: doc.get("fee_percentage").unwrap().as_i32().unwrap() as u32, - num_tickets: doc.get("num_tickets").unwrap().as_i32().unwrap() as u32, - } -} - -/// Util method that generates a Bid document from a request bid -pub fn bid_to_doc(request_id: &Bson, bid: &Bid) -> OrderedDocument { - doc! { - "request_id": request_id.clone(), - "txid": bid.txid.to_string(), - "pubkey": bid.pubkey.to_string() - } -} - -/// Util method that generates a request bid from a Bid document -pub fn doc_to_bid(doc: &OrderedDocument) -> Bid { - Bid { - txid: sha256d::Hash::from_hex(doc.get("txid").unwrap().as_str().unwrap()).unwrap(), - pubkey: PublicKey::from_str(doc.get("pubkey").unwrap().as_str().unwrap()).unwrap(), - } -} - -/// Util method that generates a Response document from challenge responses -pub fn challenge_responses_to_doc(request_id: &Bson, responses: &ChallengeResponseIds) -> OrderedDocument { - let bids = responses - .iter() - .map(|x| Bson::String(x.to_string())) - .collect::>(); - doc! { - "request_id": request_id.clone(), - "bid_txids": bids - } -} - -/// Util method that generates challenge responses from a Response document -pub fn doc_to_challenge_responses(doc: &OrderedDocument) -> ChallengeResponseIds { - doc.get_array("bid_txids") - .unwrap() - .iter() - .map(|x| sha256d::Hash::from_hex(x.as_str().unwrap()).unwrap()) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - use mongodb::oid::ObjectId; - - use crate::util::testing::gen_dummy_hash; - - #[test] - fn request_doc_test() { - let request_hash = gen_dummy_hash(9); - let genesis_hash = "1100000000000000000000000000000000000000000000000000000000000022"; - let request = Request { - txid: request_hash, - start_blockheight: 2, - end_blockheight: 5, - genesis_blockhash: sha256d::Hash::from_hex(genesis_hash).unwrap(), - fee_percentage: 5, - num_tickets: 10, - }; - - let doc = request_to_doc(&request); - assert_eq!( - doc! { - "txid": request_hash.to_string(), - "start_blockheight": 2, - "end_blockheight": 5, - "genesis_blockhash": genesis_hash, - "fee_percentage": 5, - "num_tickets": 10 - }, - doc - ); - assert_eq!(request, doc_to_request(&doc)); - } - - #[test] - fn bid_doc_test() { - let id = ObjectId::new().unwrap(); - let pubkey_hex = "026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"; - let hash = gen_dummy_hash(1); - let bid = Bid { - txid: hash, - pubkey: PublicKey::from_str(pubkey_hex).unwrap(), - }; - - let doc = bid_to_doc(&Bson::ObjectId(id.clone()), &bid); - assert_eq!( - doc! { - "request_id": id.clone(), - "txid": hash.to_string(), - "pubkey": pubkey_hex - }, - doc - ); - assert_eq!(bid, doc_to_bid(&doc)); - } - - #[test] - fn challenge_responses_doc_test() { - let id = ObjectId::new().unwrap(); - let mut ids = ChallengeResponseIds::new(); - - let doc = challenge_responses_to_doc(&Bson::ObjectId(id.clone()), &ids); - assert_eq!( - doc! { - "request_id": id.clone(), - "bid_txids": [] - }, - doc - ); - assert_eq!(ids, doc_to_challenge_responses(&doc)); - - let _ = ids.insert(gen_dummy_hash(0)); - let doc = challenge_responses_to_doc(&Bson::ObjectId(id.clone()), &ids); - assert_eq!( - doc! { - "request_id": id.clone(), - "bid_txids": [gen_dummy_hash(0).to_string()] - }, - doc - ); - assert_eq!(ids, doc_to_challenge_responses(&doc)); - - let _ = ids.insert(gen_dummy_hash(1)); - let _ = ids.insert(gen_dummy_hash(2)); - let _ = ids.insert(gen_dummy_hash(3)); - let doc = challenge_responses_to_doc(&Bson::ObjectId(id.clone()), &ids); - assert_eq!(&id, doc.get("request_id").unwrap().as_object_id().unwrap()); - for id in doc.get_array("bid_txids").unwrap().iter() { - assert!(ids.contains(&sha256d::Hash::from_hex(id.as_str().unwrap()).unwrap())); - } - assert_eq!(4, doc.get_array("bid_txids").unwrap().len()); - assert_eq!(ids, doc_to_challenge_responses(&doc)); - } -} diff --git a/src/util/checks.rs b/src/util/checks.rs index 8dfd270..56edeb0 100644 --- a/src/util/checks.rs +++ b/src/util/checks.rs @@ -9,6 +9,7 @@ fn is_base58_char(char: &char) -> bool { _ => false, } } + /// Check for correct priv key input string format pub fn check_privkey_string(str: &String) -> bool { if str.len() == 52 && str.chars().all(|x| is_base58_char(&x)) { @@ -16,6 +17,7 @@ pub fn check_privkey_string(str: &String) -> bool { } return false; } + /// Check for correct hash input string format pub fn check_hash_string(str: &String) -> bool { if str.len() == 64 && str.chars().all(|x| x.is_ascii_hexdigit()) { diff --git a/src/util/doc_format.rs b/src/util/doc_format.rs new file mode 100644 index 0000000..f346934 --- /dev/null +++ b/src/util/doc_format.rs @@ -0,0 +1,285 @@ +//! doc_format +//! +//! doc format is used to store items in the db. +//! File contains methods to convert to/from document format. + +use std::collections::HashMap; +use std::str::FromStr; + +use bitcoin::hashes::{hex::FromHex, sha256d}; +use bitcoin::secp256k1::PublicKey; +use bitcoin::Amount; +use mongodb::{ordered::OrderedDocument, Bson}; +use ocean::Address; + +use crate::interfaces::response::Response; +use crate::interfaces::{ + bid::{Bid, BidPayment}, + request::Request, +}; + +/// Util method that generates a Request document from a request +pub fn request_to_doc(request: &Request) -> OrderedDocument { + doc! { + "txid": request.txid.to_string(), + "start_blockheight": request.start_blockheight, + "end_blockheight": request.end_blockheight, + "genesis_blockhash": request.genesis_blockhash.to_string(), + "fee_percentage": request.fee_percentage, + "num_tickets": request.num_tickets, + "start_blockheight_clientchain": request.start_blockheight_clientchain, + "end_blockheight_clientchain": request.end_blockheight_clientchain, + "is_payment_complete": request.is_payment_complete, + } +} + +/// Util method that generates a request from a Request document +pub fn doc_to_request(doc: &OrderedDocument) -> Request { + Request { + txid: sha256d::Hash::from_hex(doc.get("txid").unwrap().as_str().unwrap()).unwrap(), + start_blockheight: doc.get("start_blockheight").unwrap().as_i32().unwrap() as u32, + end_blockheight: doc.get("end_blockheight").unwrap().as_i32().unwrap() as u32, + genesis_blockhash: sha256d::Hash::from_hex(doc.get("genesis_blockhash").unwrap().as_str().unwrap()).unwrap(), + fee_percentage: doc.get("fee_percentage").unwrap().as_i32().unwrap() as u32, + num_tickets: doc.get("num_tickets").unwrap().as_i32().unwrap() as u32, + start_blockheight_clientchain: doc.get("start_blockheight_clientchain").unwrap().as_i32().unwrap() as u32, + end_blockheight_clientchain: doc.get("end_blockheight_clientchain").unwrap().as_i32().unwrap() as u32, + is_payment_complete: doc.get("is_payment_complete").unwrap().as_bool().unwrap(), + } +} + +/// Util method that generates a Bid document from a request bid +pub fn bid_to_doc(request_id: &Bson, bid: &Bid) -> OrderedDocument { + let mut bid_doc = doc! { + "request_id": request_id.clone(), + "txid": bid.txid.to_string(), + "pubkey": bid.pubkey.to_string(), + }; + if let Some(payment) = &bid.payment { + let mut bid_payment_doc = doc! { + "address": payment.address.to_string(), + "amount": payment.amount.as_btc(), + }; + if let Some(txid) = payment.txid { + let _ = bid_payment_doc.insert("txid", txid.to_string()); + } + let _ = bid_doc.insert("payment", bid_payment_doc); + } + bid_doc +} + +/// Util method that generates a request bid from a Bid document +pub fn doc_to_bid(doc: &OrderedDocument) -> Bid { + let mut payment: Option = None; + if let Some(doc_payment) = doc.get("payment") { + let doc_doc_payment = doc_payment.as_document().unwrap(); + let mut payment_txid: Option = None; + if let Some(doc_payment_txid) = doc_doc_payment.get("txid") { + payment_txid = Some(sha256d::Hash::from_hex(doc_payment_txid.as_str().unwrap()).unwrap()) + } + payment = Some(BidPayment { + txid: payment_txid, + address: Address::from_str(doc_doc_payment.get("address").unwrap().as_str().unwrap()).unwrap(), + amount: Amount::from_btc(doc_doc_payment.get("amount").unwrap().as_f64().unwrap()).unwrap(), + }); + } + Bid { + txid: sha256d::Hash::from_hex(doc.get("txid").unwrap().as_str().unwrap()).unwrap(), + pubkey: PublicKey::from_str(doc.get("pubkey").unwrap().as_str().unwrap()).unwrap(), + payment: payment, + } +} + +/// Util method that generates a Response document from request response +pub fn response_to_doc(request_id: &Bson, response: &Response) -> OrderedDocument { + let bid_resps_doc: OrderedDocument = response + .bid_responses + .iter() + .map(|(key, val)| (key.to_string(), Bson::I32(*val as i32))) + .collect(); + doc! { + "request_id": request_id.clone(), + "num_challenges": response.num_challenges, + "bid_responses": bid_resps_doc + } +} + +/// Util method that generates request response from a Response document +pub fn doc_to_response(doc: &OrderedDocument) -> Response { + let bid_resps: HashMap = doc + .get("bid_responses") + .unwrap() + .as_document() + .unwrap() + .iter() + .map(|(key, val)| { + ( + sha256d::Hash::from_hex(key.as_str()).unwrap(), + val.as_i32().unwrap() as u32, + ) + }) + .collect(); + Response { + num_challenges: doc.get("num_challenges").unwrap().as_i32().unwrap() as u32, + bid_responses: bid_resps, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use mongodb::oid::ObjectId; + + use crate::challenger::ChallengeResponseIds; + use crate::util::testing::gen_dummy_hash; + + #[test] + fn request_doc_test() { + let request_hash = gen_dummy_hash(9); + let genesis_hash = "1100000000000000000000000000000000000000000000000000000000000022"; + let request = Request { + txid: request_hash, + start_blockheight: 2, + end_blockheight: 5, + genesis_blockhash: sha256d::Hash::from_hex(genesis_hash).unwrap(), + fee_percentage: 5, + num_tickets: 10, + start_blockheight_clientchain: 0, + end_blockheight_clientchain: 0, + is_payment_complete: false, + }; + + let doc = request_to_doc(&request); + assert_eq!( + doc! { + "txid": request_hash.to_string(), + "start_blockheight": 2, + "end_blockheight": 5, + "genesis_blockhash": genesis_hash, + "fee_percentage": 5, + "num_tickets": 10, + "start_blockheight_clientchain":0, + "end_blockheight_clientchain":0, + "is_payment_complete": false, + }, + doc + ); + assert_eq!(request, doc_to_request(&doc)); + } + + #[test] + fn bid_doc_test() { + let id = ObjectId::new().unwrap(); + let pubkey_hex = "026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"; + let hash = gen_dummy_hash(1); + let mut bid = Bid { + txid: hash, + pubkey: PublicKey::from_str(pubkey_hex).unwrap(), + payment: None, + }; + + let doc = bid_to_doc(&Bson::ObjectId(id.clone()), &bid); + assert_eq!( + doc! { + "request_id": id.clone(), + "txid": hash.to_string(), + "pubkey": pubkey_hex + }, + doc + ); + assert_eq!(bid, doc_to_bid(&doc)); + + let addr = "1HXfr2qBwT4qGZYn8FczNy68rw5dwG8trc"; + let amount = 56.123; + let mut bid_payment = BidPayment { + txid: None, + address: Address::from_str(addr).unwrap(), + amount: Amount::from_btc(amount).unwrap(), + }; + bid.payment = Some(bid_payment.clone()); + let doc = bid_to_doc(&Bson::ObjectId(id.clone()), &bid); + assert_eq!( + doc! { + "request_id": id.clone(), + "txid": hash.to_string(), + "pubkey": pubkey_hex, + "payment": doc!{ + "address": addr, + "amount": amount + } + }, + doc + ); + assert_eq!(bid, doc_to_bid(&doc)); + + let payment_txid = gen_dummy_hash(123); + bid_payment.txid = Some(payment_txid); + bid.payment = Some(bid_payment.clone()); + let doc = bid_to_doc(&Bson::ObjectId(id.clone()), &bid); + assert_eq!( + doc! { + "request_id": id.clone(), + "txid": hash.to_string(), + "pubkey": pubkey_hex, + "payment": doc!{ + "address": addr, + "amount": amount, + "txid": payment_txid.to_string() + } + }, + doc + ); + assert_eq!(bid, doc_to_bid(&doc)); + } + + #[test] + fn response_doc_test() { + let id = ObjectId::new().unwrap(); + let mut ids = ChallengeResponseIds::new(); + let mut resp = Response::new(); + + let doc = response_to_doc(&Bson::ObjectId(id.clone()), &resp); + assert_eq!( + doc! { + "request_id": id.clone(), + "num_challenges": 0, + "bid_responses": doc! {} + }, + doc + ); + assert_eq!(resp, doc_to_response(&doc)); + + let hash0 = gen_dummy_hash(0); + let _ = ids.insert(hash0); + resp.update(&ids); + let doc = response_to_doc(&Bson::ObjectId(id.clone()), &resp); + assert_eq!( + doc! { + "request_id": id.clone(), + "num_challenges": 1, + "bid_responses": doc! { gen_dummy_hash(0).to_string(): 1 } + }, + doc + ); + assert_eq!(resp, doc_to_response(&doc)); + + let _ = ids.insert(gen_dummy_hash(1)); + let _ = ids.insert(gen_dummy_hash(2)); + let _ = ids.insert(gen_dummy_hash(3)); + resp.update(&ids); + let doc = response_to_doc(&Bson::ObjectId(id.clone()), &resp); + assert_eq!(&id, doc.get("request_id").unwrap().as_object_id().unwrap()); + assert_eq!(2, doc.get("num_challenges").unwrap().as_i32().unwrap()); + for (key, val) in doc.get_document("bid_responses").unwrap().iter() { + if sha256d::Hash::from_hex(key.as_str()).unwrap() == hash0 { + assert_eq!(2, val.as_i32().unwrap()); + } else { + assert_eq!(1, val.as_i32().unwrap()); + } + assert!(ids.contains(&sha256d::Hash::from_hex(key.as_str()).unwrap())); + } + assert_eq!(4, doc.get_document("bid_responses").unwrap().len()); + assert_eq!(resp, doc_to_response(&doc)); + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..0d18852 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,8 @@ +//! # Util +//! +//! Util functionality required by the coordinator library + +pub mod checks; +pub mod doc_format; +pub mod ocean; +pub mod testing; diff --git a/src/ocean.rs b/src/util/ocean.rs similarity index 69% rename from src/ocean.rs rename to src/util/ocean.rs index fad8aae..c9d38aa 100644 --- a/src/ocean.rs +++ b/src/util/ocean.rs @@ -2,7 +2,7 @@ //! //! Ocean node communication implementations -use ocean_rpc::{Client, RpcApi}; +use ocean_rpc::{Auth, Client, RpcApi}; use crate::error::Result; @@ -15,17 +15,23 @@ pub struct OceanClient { impl OceanClient { /// Create an OceanClient with underlying rpc client connectivity pub fn new(url: String, user: Option, pass: Option) -> Result { + let mut auth = Auth::None; + if let Some(ref _user) = user { + if let Some(ref _pass) = pass { + auth = Auth::UserPass(_user.clone(), _pass.clone()); + } + } Ok(OceanClient { - client: Client::new(format!("http://{}", url), user, pass), + client: Client::new(format!("http://{}", url), auth)?, }) } } /// Interval between retry attempts of rpc client -pub const CLIENT_INTERVAL: u64 = 10; +pub const OCEAN_CLIENT_RETRY_INTERVAL: u64 = 10; /// Number of retry attemps for rpc client calls -pub const CLIENT_RETRY_ATTEMPTS: u8 = 5; +pub const OCEAN_CLIENT_RETRY_ATTEMPTS: u8 = 5; impl RpcApi for OceanClient { fn call serde::de::Deserialize<'b>>( @@ -33,12 +39,12 @@ impl RpcApi for OceanClient { cmd: &str, args: &[serde_json::Value], ) -> ocean_rpc::Result { - for _ in 0..CLIENT_RETRY_ATTEMPTS { + for _ in 0..OCEAN_CLIENT_RETRY_ATTEMPTS { match self.client.call(cmd, args) { Ok(ret) => return Ok(ret), Err(ocean_rpc::Error::JsonRpc(e)) => { warn!("rpc error: {}, retrying...", e); - ::std::thread::sleep(::std::time::Duration::from_millis(CLIENT_INTERVAL)); + ::std::thread::sleep(::std::time::Duration::from_millis(OCEAN_CLIENT_RETRY_INTERVAL)); continue; } Err(e) => return Err(e), diff --git a/src/util/testing.rs b/src/util/testing.rs index 9597a80..77c059c 100644 --- a/src/util/testing.rs +++ b/src/util/testing.rs @@ -2,18 +2,23 @@ //! //! Colleciton of helper functions used in tests module -use bitcoin_hashes::{hex::FromHex, sha256d, Hash}; +use bitcoin::hashes::{hex::FromHex, sha256d, Hash}; +use bitcoin::secp256k1::PublicKey; use mongodb::ordered::OrderedDocument; use mongodb::Bson; -use secp256k1::PublicKey; use std::cell::RefCell; use std::str::FromStr; +use util::doc_format::*; use crate::challenger::{ChallengeResponseIds, ChallengeState}; -use crate::clientchain::ClientChain; -use crate::request::{Bid, BidSet, Request as ServiceRequest}; -use crate::service::Service; -use crate::storage::*; +use crate::interfaces::clientchain::ClientChain; +use crate::interfaces::response::Response; +use crate::interfaces::service::Service; +use crate::interfaces::storage::*; +use crate::interfaces::{ + bid::{Bid, BidSet}, + request::Request as ServiceRequest, +}; use crate::error::*; @@ -31,11 +36,15 @@ pub fn gen_challenge_state(request_hash: &sha256d::Hash) -> ChallengeState { genesis_blockhash: gen_dummy_hash(0), fee_percentage: 5, num_tickets: 10, + start_blockheight_clientchain: 0, + end_blockheight_clientchain: 0, + is_payment_complete: false, }; let mut bids = BidSet::new(); let _ = bids.insert(Bid { txid: sha256d::Hash::from_hex("1234567890000000000000000000000000000000000000000000000000000000").unwrap(), pubkey: PublicKey::from_str("026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3").unwrap(), + payment: None, }); ChallengeState { request, @@ -56,12 +65,16 @@ pub fn gen_challenge_state_with_challenge( genesis_blockhash: *request_hash, fee_percentage: 5, num_tickets: 10, + start_blockheight_clientchain: 0, + end_blockheight_clientchain: 0, + is_payment_complete: false, }; let mut bids = BidSet::new(); let _ = bids.insert(Bid { txid: sha256d::Hash::from_hex("1234567890000000000000000000000000000000000000000000000000000000").unwrap(), // pubkey corresponding to SecretKey::from_slice(&[0xaa; 32]) pubkey: PublicKey::from_str("026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3").unwrap(), + payment: None, }); ChallengeState { request, @@ -79,7 +92,7 @@ pub struct MockClientChain { /// bool pub return_false: bool, /// Mock client chain blockheight - pub height: RefCell, + pub height: RefCell, } impl MockClientChain { @@ -113,6 +126,11 @@ impl ClientChain for MockClientChain { } Ok(true) } + + /// Get block count dummy + fn get_blockheight(&self) -> Result { + Ok(self.height.clone().into_inner()) + } } /// Mock implementation of Service using some mock logic for testing @@ -124,7 +142,7 @@ pub struct MockService { /// Option pub return_none: bool, /// Current active request - pub request: ServiceRequest, + pub request: RefCell, /// Mock service chain blockheight - incremented by default on /// get_blockheight pub height: RefCell, @@ -143,12 +161,15 @@ impl MockService { .unwrap(), fee_percentage: 5, num_tickets: 10, + start_blockheight_clientchain: 0, + end_blockheight_clientchain: 0, + is_payment_complete: false, }; MockService { return_err: false, return_none: false, - request, + request: RefCell::new(request), height: RefCell::new(0), } } @@ -169,9 +190,9 @@ impl Service for MockService { return Err(Error::from(CError::Generic("get_request failed".to_owned()))); } - let mut dummy_req = self.request.clone(); + let mut dummy_req = self.request.borrow_mut(); dummy_req.genesis_blockhash = *hash; - Ok(Some(dummy_req)) + Ok(Some(dummy_req.clone())) } /// Try get active request bids, by transaction hash, from service chain @@ -187,16 +208,19 @@ impl Service for MockService { txid: sha256d::Hash::from_hex("1234567890000000000000000000000000000000000000000000000000000000").unwrap(), // pubkey corresponding to SecretKey::from_slice(&[0xaa; 32]) pubkey: PublicKey::from_str("026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3").unwrap(), + payment: None, }); let _ = bid_set.insert(Bid { txid: sha256d::Hash::from_hex("0000000001234567890000000000000000000000000000000000000000000000").unwrap(), // pubkey corresponding to SecretKey::from_slice(&[0xbb; 32]) pubkey: PublicKey::from_str("0268680737c76dabb801cb2204f57dbe4e4579e4f710cd67dc1b4227592c81e9b5").unwrap(), + payment: None, }); let _ = bid_set.insert(Bid { txid: sha256d::Hash::from_hex("0000000000000000001234567890000000000000000000000000000000000000").unwrap(), // pubkey corresponding to SecretKey::from_slice(&[0xcc; 32]) pubkey: PublicKey::from_str("02b95c249d84f417e3e395a127425428b540671cc15881eb828c17b722a53fc599").unwrap(), + payment: None, }); Ok(Some(bid_set)) } @@ -245,7 +269,15 @@ impl Storage for MockStorage { if self.return_err { return Err(Error::from(CError::Generic("save_challenge_state failed".to_owned()))); } - self.requests.borrow_mut().push(request_to_doc(&challenge.request)); + // do not add request if already exists + if !self + .requests + .borrow_mut() + .iter() + .any(|request| request.get("txid").unwrap().as_str().unwrap() == &challenge.request.txid.to_string()) + { + self.requests.borrow_mut().push(request_to_doc(&challenge.request)); + } for bid in challenge.bids.iter() { self.bids .borrow_mut() @@ -254,26 +286,52 @@ impl Storage for MockStorage { Ok(()) } - /// Store responses for a specific challenge request + /// update request in mock storage + fn update_request(&self, request_update: &ServiceRequest) -> Result<()> { + for request in self.requests.borrow_mut().iter_mut() { + if request.get("txid").unwrap().as_str().unwrap() == &request_update.txid.to_string() { + *request = request_to_doc(&request_update); + } + } + Ok(()) + } + + /// update bid in mock storage + fn update_bid(&self, _request_hash: sha256d::Hash, _bid: &Bid) -> Result<()> { + Ok(()) + } + + /// Store response for a specific challenge request fn save_response(&self, request_hash: sha256d::Hash, ids: &ChallengeResponseIds) -> Result<()> { if self.return_err { return Err(Error::from(CError::Generic("save_response failed".to_owned()))); } + + for resp_doc in self.challenge_responses.borrow_mut().iter_mut() { + if resp_doc.get("request_id").unwrap().as_str().unwrap() == &request_hash.to_string() { + let mut resp = doc_to_response(resp_doc); + resp.update(&ids); + *resp_doc = response_to_doc(&Bson::String(request_hash.to_string()), &resp); + return Ok(()); + } + } + + let mut resp = Response::new(); + resp.update(&ids); self.challenge_responses .borrow_mut() - .push(challenge_responses_to_doc(&Bson::String(request_hash.to_string()), ids)); + .push(response_to_doc(&Bson::String(request_hash.to_string()), &resp)); Ok(()) } - /// Get all challenge responses for a specific request - fn get_responses(&self, request_hash: sha256d::Hash) -> Result> { - let mut challenge_responses = vec![]; + /// Get challenge response for a specific request + fn get_response(&self, request_hash: sha256d::Hash) -> Result> { for doc in self.challenge_responses.borrow().to_vec().iter() { if doc.get("request_id").unwrap().as_str().unwrap() == request_hash.to_string() { - challenge_responses.push(doc_to_challenge_responses(doc)); + return Ok(Some(doc_to_response(doc))); } } - Ok(challenge_responses) + Ok(None) } /// Get all bids for a specific request @@ -287,8 +345,9 @@ impl Storage for MockStorage { Ok(bids) } - /// Get all the requests - fn get_requests(&self) -> Result> { + /// Get all the requests, with an optional flag to return payment complete + /// only + fn get_requests(&self, _complete: Option) -> Result> { let mut requests = vec![]; for doc in self.requests.borrow().to_vec().iter() { requests.push(doc_to_request(doc))