diff --git a/.travis.yml b/.travis.yml index 1ec306dd..a0d395c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,8 @@ env: matrix: include: + - os: linux + env: TEST_SUITE=integration - os: linux env: TEST_SUITE=config-libwallet-apiwallet - os: linux diff --git a/Cargo.lock b/Cargo.lock index c0c7c649..6dc328f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,6 +153,11 @@ dependencies = [ "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bufstream" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "build_const" version = "0.2.1" @@ -414,6 +419,11 @@ name = "dtoa" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "either" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "encode_unicode" version = "0.3.5" @@ -546,7 +556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "grin_api" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -596,7 +606,7 @@ dependencies = [ [[package]] name = "grin_chain" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -618,7 +628,7 @@ dependencies = [ [[package]] name = "grin_core" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -641,10 +651,42 @@ dependencies = [ "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "grin_integration" +version = "1.1.0" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "grin_api 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_apiwallet 1.1.0", + "grin_chain 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_core 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_keychain 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_libwallet 1.1.0", + "grin_p2p 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_refwallet 1.1.0", + "grin_servers 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_store 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_util 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_wallet_config 1.1.0", + "http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lmdb-zero 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (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.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "grin_keychain" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -687,7 +729,7 @@ dependencies = [ [[package]] name = "grin_p2p" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -708,7 +750,7 @@ dependencies = [ [[package]] name = "grin_pool" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -770,10 +812,41 @@ dependencies = [ "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "grin_servers" +version = "1.0.1" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" +dependencies = [ + "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "grin_api 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_chain 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_core 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_keychain 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_p2p 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_pool 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_store 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_util 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_wallet 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-staticfile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lmdb-zero 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (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.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "grin_store" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "croaring 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -793,7 +866,7 @@ dependencies = [ [[package]] name = "grin_util" version = "1.0.1" -source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#ee4eed71ea7e379f5c7e2ca08179c516c2d4be45" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" dependencies = [ "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -811,6 +884,39 @@ dependencies = [ "zip 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "grin_wallet" +version = "1.0.1" +source = "git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0#cbac14c1353ae011ea9452196fcb26fd64e65141" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "grin_api 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_chain 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_core 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_keychain 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_store 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "grin_util 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)", + "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-retry 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "grin_wallet" version = "1.1.0" @@ -943,6 +1049,19 @@ dependencies = [ "webpki-roots 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hyper-staticfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.1.5" @@ -967,11 +1086,31 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itoa" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "jsonrpc-core" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1076,6 +1215,14 @@ dependencies = [ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.4.6" @@ -1335,7 +1482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2605,6 +2752,7 @@ dependencies = [ "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" +"checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" "checksum built 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61f5aae2fa15b68fbcf0cbab64e659a55d10e9bacc55d3470ef77ae73030d755" "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" @@ -2636,6 +2784,7 @@ dependencies = [ "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" +"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd" "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" @@ -2660,8 +2809,10 @@ dependencies = [ "checksum grin_p2p 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)" = "" "checksum grin_pool 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)" = "" "checksum grin_secp256k1zkp 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "223095ed6108a42855ab2ce368d2056cfd384705f81c494f6d88ab3383161562" +"checksum grin_servers 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)" = "" "checksum grin_store 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)" = "" "checksum grin_util 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)" = "" +"checksum grin_wallet 1.0.1 (git+https://github.com/mimblewimble/grin?branch=milestone/1.1.0)" = "" "checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e" "checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a" "checksum http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1a10e5b573b9a0146545010f50772b9e8b1dd0a256564cc4307694c68832a2f5" @@ -2669,10 +2820,13 @@ dependencies = [ "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum hyper 0.12.19 (registry+https://github.com/rust-lang/crates.io-index)" = "f1ebec079129e43af5e234ef36ee3d7e6085687d145b7ea653b262d16c6b65f1" "checksum hyper-rustls 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "68f2aa6b1681795bf4da8063f718cd23145aa0c9a5143d9787b345aa60d38ee4" +"checksum hyper-staticfile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4080cb44b9c1e4c6dfd6f7ee85a9c3439777ec9c59df32f944836d3de58ac35e" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf83704f4e79979a424d1082dd2c1e52683058056c9280efa19ac5f6bc9033c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" @@ -2686,6 +2840,7 @@ dependencies = [ "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" "checksum lmdb-zero 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "13416eee745b087c22934f35f1f24da22da41ba2a5ce197143d168ce055cc58d" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum log-mdc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" "checksum log4rs 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "25e0fc8737a634116a2deb38d821e4400ed16ce9dcb0d628a978d399260f5902" diff --git a/Cargo.toml b/Cargo.toml index a9f054af..9e681f54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ name = "grin-wallet" path = "src/bin/grin-wallet.rs" [workspace] -members = ["apiwallet", "config", "libwallet", "refwallet"] +members = ["apiwallet", "config", "integration", "libwallet", "refwallet"] [dependencies] clap = { version = "2.31", features = ["yaml"] } diff --git a/integration/Cargo.toml b/integration/Cargo.toml new file mode 100644 index 00000000..350e47e5 --- /dev/null +++ b/integration/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "grin_integration" +version = "1.1.0" +authors = ["Grin Developers "] +description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." +license = "Apache-2.0" +repository = "https://github.com/mimblewimble/grin" +keywords = [ "crypto", "grin", "mimblewimble" ] +workspace = ".." +edition = "2018" + +[dependencies] +hyper = "0.12" +futures = "0.1" +http = "0.1" +itertools = "0.7" +lmdb-zero = "0.4.4" +rand = "0.5" +serde = "1" +log = "0.4" +serde_derive = "1" +serde_json = "1" +chrono = "0.4.4" +tokio = "0.1.11" +blake2-rfc = "0.2" +bufstream = "0.1" + +grin_apiwallet = { path = "../apiwallet", version = "1.1.0" } +grin_libwallet = { path = "../libwallet", version = "1.1.0" } +grin_refwallet = { path = "../refwallet", version = "1.1.0" } +grin_wallet_config = { path = "../config", version = "1.1.0" } + +grin_core = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_util = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_api = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_store = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_p2p = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } +grin_servers = { git = "https://github.com/mimblewimble/grin", branch = "milestone/1.1.0" } diff --git a/integration/src/lib.rs b/integration/src/lib.rs new file mode 100644 index 00000000..3507bb55 --- /dev/null +++ b/integration/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright 2019 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Grin integration test crate + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] diff --git a/integration/tests/api.rs b/integration/tests/api.rs new file mode 100644 index 00000000..de856e82 --- /dev/null +++ b/integration/tests/api.rs @@ -0,0 +1,485 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate log; + +mod framework; + +use self::core::global::{self, ChainTypes}; +use self::util::init_test_logger; +use self::util::Mutex; +use crate::framework::{LocalServerContainer, LocalServerContainerConfig}; +use grin_api as api; +use grin_core as core; +use grin_p2p as p2p; +use grin_util as util; +use std::sync::Arc; +use std::{thread, time}; + +#[test] +fn simple_server_wallet() { + init_test_logger(); + info!("starting simple_server_wallet"); + let _test_name_dir = "test_servers"; + core::global::set_mining_mode(core::global::ChainTypes::AutomatedTesting); + + // Run a separate coinbase wallet for coinbase transactions + let coinbase_dir = "coinbase_wallet_api"; + framework::clean_all_output(coinbase_dir); + let mut coinbase_config = LocalServerContainerConfig::default(); + coinbase_config.name = String::from(coinbase_dir); + coinbase_config.wallet_validating_node_url = String::from("http://127.0.0.1:40001"); + coinbase_config.wallet_port = 50002; + let coinbase_wallet = Arc::new(Mutex::new( + LocalServerContainer::new(coinbase_config).unwrap(), + )); + + let _ = thread::spawn(move || { + let mut w = coinbase_wallet.lock(); + w.run_wallet(0); + }); + + // Wait for the wallet to start + thread::sleep(time::Duration::from_millis(1000)); + + let api_server_one_dir = "api_server_one"; + framework::clean_all_output(api_server_one_dir); + let mut server_config = LocalServerContainerConfig::default(); + server_config.name = String::from(api_server_one_dir); + server_config.p2p_server_port = 40000; + server_config.api_server_port = 40001; + server_config.start_miner = true; + server_config.start_wallet = false; + server_config.coinbase_wallet_address = + String::from(format!("http://{}:{}", server_config.base_addr, 50002)); + let mut server_one = LocalServerContainer::new(server_config.clone()).unwrap(); + + // Spawn server and let it run for a bit + let _ = thread::spawn(move || server_one.run_server(120)); + + //Wait for chain to build + thread::sleep(time::Duration::from_millis(5000)); + + // Starting tests + let base_addr = server_config.base_addr; + let api_server_port = server_config.api_server_port; + + warn!("Testing chain handler"); + let tip = get_tip(&base_addr, api_server_port); + assert!(tip.is_ok()); + + warn!("Testing status handler"); + let status = get_status(&base_addr, api_server_port); + assert!(status.is_ok()); + + // Be sure that at least a block is mined by Travis + let mut current_tip = get_tip(&base_addr, api_server_port).unwrap(); + while current_tip.height == 0 { + thread::sleep(time::Duration::from_millis(1000)); + current_tip = get_tip(&base_addr, api_server_port).unwrap(); + } + + warn!("Testing block handler"); + let last_block_by_height = get_block_by_height(&base_addr, api_server_port, current_tip.height); + assert!(last_block_by_height.is_ok()); + let last_block_by_height_compact = + get_block_by_height_compact(&base_addr, api_server_port, current_tip.height); + assert!(last_block_by_height_compact.is_ok()); + + let block_hash = current_tip.last_block_pushed; + let last_block_by_hash = get_block_by_hash(&base_addr, api_server_port, &block_hash); + assert!(last_block_by_hash.is_ok()); + let last_block_by_hash_compact = + get_block_by_hash_compact(&base_addr, api_server_port, &block_hash); + assert!(last_block_by_hash_compact.is_ok()); + + warn!("Testing chain output handler"); + let start_height = 0; + let end_height = current_tip.height; + let outputs_by_height = + get_outputs_by_height(&base_addr, api_server_port, start_height, end_height); + assert!(outputs_by_height.is_ok()); + let ids = get_ids_from_block_outputs(outputs_by_height.unwrap()); + let outputs_by_ids1 = get_outputs_by_ids1(&base_addr, api_server_port, ids.clone()); + assert!(outputs_by_ids1.is_ok()); + let outputs_by_ids2 = get_outputs_by_ids2(&base_addr, api_server_port, ids.clone()); + assert!(outputs_by_ids2.is_ok()); + + warn!("Testing txhashset handler"); + let roots = get_txhashset_roots(&base_addr, api_server_port); + assert!(roots.is_ok()); + let last_10_outputs = get_txhashset_lastoutputs(&base_addr, api_server_port, 0); + assert!(last_10_outputs.is_ok()); + let last_5_outputs = get_txhashset_lastoutputs(&base_addr, api_server_port, 5); + assert!(last_5_outputs.is_ok()); + let last_10_rangeproofs = get_txhashset_lastrangeproofs(&base_addr, api_server_port, 0); + assert!(last_10_rangeproofs.is_ok()); + let last_5_rangeproofs = get_txhashset_lastrangeproofs(&base_addr, api_server_port, 5); + assert!(last_5_rangeproofs.is_ok()); + let last_10_kernels = get_txhashset_lastkernels(&base_addr, api_server_port, 0); + assert!(last_10_kernels.is_ok()); + let last_5_kernels = get_txhashset_lastkernels(&base_addr, api_server_port, 5); + assert!(last_5_kernels.is_ok()); + + //let some more mining happen, make sure nothing pukes + thread::sleep(time::Duration::from_millis(5000)); +} + +/// Creates 2 servers and test P2P API +#[test] +fn test_p2p() { + init_test_logger(); + info!("starting test_p2p"); + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let _test_name_dir = "test_servers"; + + // Spawn server and let it run for a bit + let server_one_dir = "p2p_server_one"; + framework::clean_all_output(server_one_dir); + let mut server_config_one = LocalServerContainerConfig::default(); + server_config_one.name = String::from(server_one_dir); + server_config_one.p2p_server_port = 40002; + server_config_one.api_server_port = 40003; + server_config_one.start_miner = false; + server_config_one.start_wallet = false; + server_config_one.is_seeding = true; + let mut server_one = LocalServerContainer::new(server_config_one.clone()).unwrap(); + let _ = thread::spawn(move || server_one.run_server(120)); + + thread::sleep(time::Duration::from_millis(1000)); + + // Spawn server and let it run for a bit + let server_two_dir = "p2p_server_two"; + framework::clean_all_output(server_two_dir); + let mut server_config_two = LocalServerContainerConfig::default(); + server_config_two.name = String::from(server_two_dir); + server_config_two.p2p_server_port = 40004; + server_config_two.api_server_port = 40005; + server_config_two.start_miner = false; + server_config_two.start_wallet = false; + server_config_two.is_seeding = false; + let mut server_two = LocalServerContainer::new(server_config_two.clone()).unwrap(); + server_two.add_peer(format!( + "{}:{}", + server_config_one.base_addr, server_config_one.p2p_server_port + )); + let _ = thread::spawn(move || server_two.run_server(120)); + + // Let them do the handshake + thread::sleep(time::Duration::from_millis(2000)); + + // Starting tests + warn!("Starting P2P Tests"); + let base_addr = server_config_one.base_addr; + let api_server_port = server_config_one.api_server_port; + + // Check that peer all is also working + let mut peers_all = get_all_peers(&base_addr, api_server_port); + assert!(peers_all.is_ok()); + let pall = peers_all.unwrap(); + assert_eq!(pall.len(), 2); + + // Check that when we get peer connected the peer is here + let peers_connected = get_connected_peers(&base_addr, api_server_port); + assert!(peers_connected.is_ok()); + let pc = peers_connected.unwrap(); + assert_eq!(pc.len(), 1); + + // Check that the peer status is Healthy + let addr = format!( + "{}:{}", + server_config_two.base_addr, server_config_two.p2p_server_port + ); + let peer = get_peer(&base_addr, api_server_port, &addr); + assert!(peer.is_ok()); + assert_eq!(peer.unwrap().flags, p2p::State::Healthy); + + // Ban the peer + let ban_result = ban_peer(&base_addr, api_server_port, &addr); + assert!(ban_result.is_ok()); + thread::sleep(time::Duration::from_millis(2000)); + + // Check its status is banned with get peer + let peer = get_peer(&base_addr, api_server_port, &addr); + assert!(peer.is_ok()); + assert_eq!(peer.unwrap().flags, p2p::State::Banned); + + // Check from peer all + peers_all = get_all_peers(&base_addr, api_server_port); + assert!(peers_all.is_ok()); + assert_eq!(peers_all.unwrap().len(), 2); + + // Unban + let unban_result = unban_peer(&base_addr, api_server_port, &addr); + assert!(unban_result.is_ok()); + + // Check from peer connected + let peers_connected = get_connected_peers(&base_addr, api_server_port); + assert!(peers_connected.is_ok()); + assert_eq!(peers_connected.unwrap().len(), 0); + + // Check its status is healthy with get peer + let peer = get_peer(&base_addr, api_server_port, &addr); + assert!(peer.is_ok()); + assert_eq!(peer.unwrap().flags, p2p::State::Healthy); +} + +// Tip handler function +fn get_tip(base_addr: &String, api_server_port: u16) -> Result { + let url = format!("http://{}:{}/v1/chain", base_addr, api_server_port); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +// Status handler function +fn get_status(base_addr: &String, api_server_port: u16) -> Result { + let url = format!("http://{}:{}/v1/status", base_addr, api_server_port); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +// Block handler functions +fn get_block_by_height( + base_addr: &String, + api_server_port: u16, + height: u64, +) -> Result { + let url = format!( + "http://{}:{}/v1/blocks/{}", + base_addr, api_server_port, height + ); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_block_by_height_compact( + base_addr: &String, + api_server_port: u16, + height: u64, +) -> Result { + let url = format!( + "http://{}:{}/v1/blocks/{}?compact", + base_addr, api_server_port, height + ); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_block_by_hash( + base_addr: &String, + api_server_port: u16, + block_hash: &String, +) -> Result { + let url = format!( + "http://{}:{}/v1/blocks/{}", + base_addr, api_server_port, block_hash + ); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_block_by_hash_compact( + base_addr: &String, + api_server_port: u16, + block_hash: &String, +) -> Result { + let url = format!( + "http://{}:{}/v1/blocks/{}?compact", + base_addr, api_server_port, block_hash + ); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +// Chain output handler functions +fn get_outputs_by_ids1( + base_addr: &String, + api_server_port: u16, + ids: Vec, +) -> Result, Error> { + let url = format!( + "http://{}:{}/v1/chain/outputs/byids?id={}", + base_addr, + api_server_port, + ids.join(",") + ); + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_outputs_by_ids2( + base_addr: &String, + api_server_port: u16, + ids: Vec, +) -> Result, Error> { + let mut ids_string: String = String::from(""); + for id in ids { + ids_string = ids_string + "?id=" + &id; + } + let ids_string = String::from(&ids_string[1..ids_string.len()]); + let url = format!( + "http://{}:{}/v1/chain/outputs/byids?{}", + base_addr, api_server_port, ids_string + ); + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_outputs_by_height( + base_addr: &String, + api_server_port: u16, + start_height: u64, + end_height: u64, +) -> Result, Error> { + let url = format!( + "http://{}:{}/v1/chain/outputs/byheight?start_height={}&end_height={}", + base_addr, api_server_port, start_height, end_height + ); + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +// TxHashSet handler functions +fn get_txhashset_roots(base_addr: &String, api_server_port: u16) -> Result { + let url = format!( + "http://{}:{}/v1/txhashset/roots", + base_addr, api_server_port + ); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_txhashset_lastoutputs( + base_addr: &String, + api_server_port: u16, + n: u64, +) -> Result, Error> { + let url: String; + if n == 0 { + url = format!( + "http://{}:{}/v1/txhashset/lastoutputs", + base_addr, api_server_port + ); + } else { + url = format!( + "http://{}:{}/v1/txhashset/lastoutputs?n={}", + base_addr, api_server_port, n + ); + } + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_txhashset_lastrangeproofs( + base_addr: &String, + api_server_port: u16, + n: u64, +) -> Result, Error> { + let url: String; + if n == 0 { + url = format!( + "http://{}:{}/v1/txhashset/lastrangeproofs", + base_addr, api_server_port + ); + } else { + url = format!( + "http://{}:{}/v1/txhashset/lastrangeproofs?n={}", + base_addr, api_server_port, n + ); + } + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +fn get_txhashset_lastkernels( + base_addr: &String, + api_server_port: u16, + n: u64, +) -> Result, Error> { + let url: String; + if n == 0 { + url = format!( + "http://{}:{}/v1/txhashset/lastkernels", + base_addr, api_server_port + ); + } else { + url = format!( + "http://{}:{}/v1/txhashset/lastkernels?n={}", + base_addr, api_server_port, n + ); + } + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +// Helper function to get a vec of commitment output ids from a vec of block +// outputs +fn get_ids_from_block_outputs(block_outputs: Vec) -> Vec { + let mut ids: Vec = Vec::new(); + for block_output in block_outputs { + let outputs = &block_output.outputs; + for output in outputs { + ids.push(util::to_hex(output.clone().commit.0.to_vec())); + } + } + ids.into_iter().take(100).collect() +} + +pub fn ban_peer(base_addr: &String, api_server_port: u16, peer_addr: &String) -> Result<(), Error> { + let url = format!( + "http://{}:{}/v1/peers/{}/ban", + base_addr, api_server_port, peer_addr + ); + api::client::post_no_ret(url.as_str(), None, &"").map_err(|e| Error::API(e)) +} + +pub fn unban_peer( + base_addr: &String, + api_server_port: u16, + peer_addr: &String, +) -> Result<(), Error> { + let url = format!( + "http://{}:{}/v1/peers/{}/unban", + base_addr, api_server_port, peer_addr + ); + api::client::post_no_ret(url.as_str(), None, &"").map_err(|e| Error::API(e)) +} + +pub fn get_peer( + base_addr: &String, + api_server_port: u16, + peer_addr: &String, +) -> Result { + let url = format!( + "http://{}:{}/v1/peers/{}", + base_addr, api_server_port, peer_addr + ); + api::client::get::(url.as_str(), None).map_err(|e| Error::API(e)) +} + +pub fn get_connected_peers( + base_addr: &String, + api_server_port: u16, +) -> Result, Error> { + let url = format!( + "http://{}:{}/v1/peers/connected", + base_addr, api_server_port + ); + api::client::get::>(url.as_str(), None) + .map_err(|e| Error::API(e)) +} + +pub fn get_all_peers( + base_addr: &String, + api_server_port: u16, +) -> Result, Error> { + let url = format!("http://{}:{}/v1/peers/all", base_addr, api_server_port); + api::client::get::>(url.as_str(), None).map_err(|e| Error::API(e)) +} + +/// Error type wrapping underlying module errors. +#[derive(Debug)] +pub enum Error { + /// Error originating from HTTP API calls. + API(api::Error), +} diff --git a/integration/tests/dandelion.rs b/integration/tests/dandelion.rs new file mode 100644 index 00000000..aebc30fd --- /dev/null +++ b/integration/tests/dandelion.rs @@ -0,0 +1,157 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate log; + +mod framework; + +use self::util::Mutex; +use crate::framework::{LocalServerContainer, LocalServerContainerConfig}; +use grin_core as core; +use grin_util as util; +use std::sync::Arc; +use std::{thread, time}; + +/// Start 1 node mining, 1 non mining node and two wallets. +/// Then send a transaction from one wallet to another and propagate it a stem +/// transaction but without stem relay and check if the transaction is still +/// broadcasted. +#[test] +#[ignore] +fn test_dandelion_timeout() { + let test_name_dir = "test_dandelion_timeout"; + core::global::set_mining_mode(core::global::ChainTypes::AutomatedTesting); + framework::clean_all_output(test_name_dir); + let mut log_config = util::LoggingConfig::default(); + //log_config.stdout_log_level = util::LogLevel::Trace; + log_config.stdout_log_level = util::LogLevel::Info; + //init_logger(Some(log_config)); + util::init_test_logger(); + + // Run a separate coinbase wallet for coinbase transactions + let mut coinbase_config = LocalServerContainerConfig::default(); + coinbase_config.name = String::from("coinbase_wallet"); + coinbase_config.wallet_validating_node_url = String::from("http://127.0.0.1:30001"); + coinbase_config.wallet_port = 10002; + let coinbase_wallet = Arc::new(Mutex::new( + LocalServerContainer::new(coinbase_config).unwrap(), + )); + let coinbase_wallet_config = { coinbase_wallet.lock().wallet_config.clone() }; + + let coinbase_seed = LocalServerContainer::get_wallet_seed(&coinbase_wallet_config); + + let _ = thread::spawn(move || { + let mut w = coinbase_wallet.lock(); + w.run_wallet(0); + }); + + let mut recp_config = LocalServerContainerConfig::default(); + recp_config.name = String::from("target_wallet"); + recp_config.wallet_validating_node_url = String::from("http://127.0.0.1:30001"); + recp_config.wallet_port = 20002; + let target_wallet = Arc::new(Mutex::new(LocalServerContainer::new(recp_config).unwrap())); + let target_wallet_cloned = target_wallet.clone(); + let recp_wallet_config = { target_wallet.lock().wallet_config.clone() }; + + let recp_seed = LocalServerContainer::get_wallet_seed(&recp_wallet_config); + //Start up a second wallet, to receive + let _ = thread::spawn(move || { + let mut w = target_wallet_cloned.lock(); + w.run_wallet(0); + }); + + // Spawn server and let it run for a bit + let mut server_one_config = LocalServerContainerConfig::default(); + server_one_config.name = String::from("server_one"); + server_one_config.p2p_server_port = 30000; + server_one_config.api_server_port = 30001; + server_one_config.start_miner = true; + server_one_config.start_wallet = false; + server_one_config.is_seeding = false; + server_one_config.coinbase_wallet_address = + String::from(format!("http://{}:{}", server_one_config.base_addr, 10002)); + let mut server_one = LocalServerContainer::new(server_one_config).unwrap(); + + let mut server_two_config = LocalServerContainerConfig::default(); + server_two_config.name = String::from("server_two"); + server_two_config.p2p_server_port = 40000; + server_two_config.api_server_port = 40001; + server_two_config.start_miner = false; + server_two_config.start_wallet = false; + server_two_config.is_seeding = true; + let mut server_two = LocalServerContainer::new(server_two_config.clone()).unwrap(); + + server_one.add_peer(format!( + "{}:{}", + server_two_config.base_addr, server_two_config.p2p_server_port + )); + + // Spawn servers and let them run for a bit + let _ = thread::spawn(move || { + server_two.run_server(120); + }); + + // Wait for the first server to start + thread::sleep(time::Duration::from_millis(5000)); + + let _ = thread::spawn(move || { + server_one.run_server(120); + }); + + // Let them do a handshake and properly update their peer relay + thread::sleep(time::Duration::from_millis(30000)); + + //Wait until we have some funds to send + let mut coinbase_info = + LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + let mut slept_time = 0; + while coinbase_info.amount_currently_spendable < 100000000000 { + thread::sleep(time::Duration::from_millis(500)); + slept_time += 500; + if slept_time > 10000 { + panic!("Coinbase not confirming in time"); + } + coinbase_info = + LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + } + + warn!("Sending 50 Grins to recipient wallet"); + + // Sending stem transaction + LocalServerContainer::send_amount_to( + &coinbase_wallet_config, + "50.00", + 1, + "not_all", + "http://127.0.0.1:20002", + false, + ); + + let coinbase_info = + LocalServerContainer::get_wallet_info(&coinbase_wallet_config, &coinbase_seed); + println!("Coinbase wallet info: {:?}", coinbase_info); + + let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); + + // The transaction should be waiting in the node stempool thus cannot be mined. + println!("Recipient wallet info: {:?}", recipient_info); + assert!(recipient_info.amount_awaiting_confirmation == 50000000000); + + // Wait for stem timeout + thread::sleep(time::Duration::from_millis(35000)); + println!("Recipient wallet info: {:?}", recipient_info); + let recipient_info = LocalServerContainer::get_wallet_info(&recp_wallet_config, &recp_seed); + assert!(recipient_info.amount_currently_spendable == 50000000000); +} diff --git a/integration/tests/framework.rs b/integration/tests/framework.rs new file mode 100644 index 00000000..13cd2e58 --- /dev/null +++ b/integration/tests/framework.rs @@ -0,0 +1,678 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate grin_apiwallet as apiwallet; +extern crate grin_libwallet as libwallet; +extern crate grin_refwallet as wallet; +extern crate grin_wallet_config as wallet_config; + +use self::keychain::Keychain; +use self::util::Mutex; +use self::wallet::{HTTPNodeClient, HTTPWalletCommAdapter, LMDBBackend}; +use self::wallet_config::WalletConfig; +use blake2_rfc as blake2; +use grin_api as api; +use grin_core as core; +use grin_keychain as keychain; +use grin_p2p as p2p; +use grin_servers as servers; +use grin_util as util; +use std::default::Default; +use std::ops::Deref; +use std::sync::Arc; +use std::{fs, thread, time}; + +/// Just removes all results from previous runs +pub fn clean_all_output(test_name_dir: &str) { + let target_dir = format!("target/tmp/{}", test_name_dir); + if let Err(e) = fs::remove_dir_all(target_dir) { + println!("can't remove output from previous test :{}, may be ok", e); + } +} + +/// Errors that can be returned by LocalServerContainer +#[derive(Debug)] +#[allow(dead_code)] +pub enum Error { + Internal(String), + Argument(String), + NotFound, +} + +/// All-in-one server configuration struct, for convenience +/// +#[derive(Clone)] +pub struct LocalServerContainerConfig { + // user friendly name for the server, also denotes what dir + // the data files will appear in + pub name: String, + + // Base IP address + pub base_addr: String, + + // Port the server (p2p) is running on + pub p2p_server_port: u16, + + // Port the API server is running on + pub api_server_port: u16, + + // Port the wallet server is running on + pub wallet_port: u16, + + // Port the wallet owner API is running on + pub owner_port: u16, + + // Whether to include the foreign API endpoints in the owner API + pub owner_api_include_foreign: bool, + + // Whether we're going to mine + pub start_miner: bool, + + // time in millis by which to artificially slow down the mining loop + // in this container + pub miner_slowdown_in_millis: u64, + + // Whether we're going to run a wallet as well, + // can use same server instance as a validating node for convenience + pub start_wallet: bool, + + // address of a server to use as a seed + pub seed_addr: String, + + // keep track of whether this server is supposed to be seeding + pub is_seeding: bool, + + // Whether to burn mining rewards + pub burn_mining_rewards: bool, + + // full address to send coinbase rewards to + pub coinbase_wallet_address: String, + + // When running a wallet, the address to check inputs and send + // finalised transactions to, + pub wallet_validating_node_url: String, +} + +/// Default server config +impl Default for LocalServerContainerConfig { + fn default() -> LocalServerContainerConfig { + LocalServerContainerConfig { + name: String::from("test_host"), + base_addr: String::from("127.0.0.1"), + api_server_port: 13413, + p2p_server_port: 13414, + wallet_port: 13415, + owner_port: 13420, + owner_api_include_foreign: false, + seed_addr: String::from(""), + is_seeding: false, + start_miner: false, + start_wallet: false, + burn_mining_rewards: false, + coinbase_wallet_address: String::from(""), + wallet_validating_node_url: String::from(""), + miner_slowdown_in_millis: 0, + } + } +} + +/// A top-level container to hold everything that might be running +/// on a server, i.e. server, wallet in send or receive mode + +#[allow(dead_code)] +pub struct LocalServerContainer { + // Configuration + config: LocalServerContainerConfig, + + // Structure of references to the + // internal server data + pub p2p_server_stats: Option, + + // The API server instance + api_server: Option, + + // whether the server is running + pub server_is_running: bool, + + // Whether the server is mining + pub server_is_mining: bool, + + // Whether the server is also running a wallet + // Not used if running wallet without server + pub wallet_is_running: bool, + + // the list of peers to connect to + pub peer_list: Vec, + + // base directory for the server instance + pub working_dir: String, + + // Wallet configuration + pub wallet_config: WalletConfig, +} + +impl LocalServerContainer { + /// Create a new local server container with defaults, with the given name + /// all related files will be created in the directory + /// target/tmp/{name} + + pub fn new(config: LocalServerContainerConfig) -> Result { + let working_dir = format!("target/tmp/{}", config.name); + let mut wallet_config = WalletConfig::default(); + + wallet_config.api_listen_port = config.wallet_port; + wallet_config.check_node_api_http_addr = config.wallet_validating_node_url.clone(); + wallet_config.owner_api_include_foreign = Some(config.owner_api_include_foreign); + wallet_config.data_file_dir = working_dir.clone(); + Ok(LocalServerContainer { + config: config, + p2p_server_stats: None, + api_server: None, + server_is_running: false, + server_is_mining: false, + wallet_is_running: false, + working_dir: working_dir, + peer_list: Vec::new(), + wallet_config: wallet_config, + }) + } + + pub fn run_server(&mut self, duration_in_seconds: u64) -> servers::Server { + let api_addr = format!("{}:{}", self.config.base_addr, self.config.api_server_port); + + let mut seeding_type = p2p::Seeding::None; + let mut seeds = Vec::new(); + + if self.config.seed_addr.len() > 0 { + seeding_type = p2p::Seeding::List; + seeds = vec![self.config.seed_addr.to_string()]; + } + + let s = servers::Server::new(servers::ServerConfig { + api_http_addr: api_addr, + api_secret_path: None, + db_root: format!("{}/.grin", self.working_dir), + p2p_config: p2p::P2PConfig { + port: self.config.p2p_server_port, + seeds: Some(seeds), + seeding_type: seeding_type, + ..p2p::P2PConfig::default() + }, + chain_type: core::global::ChainTypes::AutomatedTesting, + skip_sync_wait: Some(true), + stratum_mining_config: None, + ..Default::default() + }) + .unwrap(); + + self.p2p_server_stats = Some(s.get_server_stats().unwrap()); + + let mut wallet_url = None; + + if self.config.start_wallet == true { + self.run_wallet(duration_in_seconds + 5); + // give a second to start wallet before continuing + thread::sleep(time::Duration::from_millis(1000)); + wallet_url = Some(format!( + "http://{}:{}", + self.config.base_addr, self.config.wallet_port + )); + } + + if self.config.start_miner == true { + println!( + "starting test Miner on port {}", + self.config.p2p_server_port + ); + s.start_test_miner(wallet_url, s.stop_state.clone()); + } + + for p in &mut self.peer_list { + println!("{} connecting to peer: {}", self.config.p2p_server_port, p); + let _ = s.connect_peer(p.parse().unwrap()); + } + + if self.wallet_is_running { + self.stop_wallet(); + } + + s + } + + /// Make a wallet for use in test endpoints (run_wallet and run_owner). + fn make_wallet_for_tests( + &mut self, + ) -> Arc>> { + // URL on which to start the wallet listener (i.e. api server) + let _url = format!("{}:{}", self.config.base_addr, self.config.wallet_port); + + // Just use the name of the server for a seed for now + let seed = format!("{}", self.config.name); + + let _seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); + + println!( + "Starting the Grin wallet receiving daemon on {} ", + self.config.wallet_port + ); + + self.wallet_config = WalletConfig::default(); + + self.wallet_config.api_listen_port = self.config.wallet_port; + self.wallet_config.check_node_api_http_addr = + self.config.wallet_validating_node_url.clone(); + self.wallet_config.data_file_dir = self.working_dir.clone(); + self.wallet_config.owner_api_include_foreign = Some(self.config.owner_api_include_foreign); + + let _ = fs::create_dir_all(self.wallet_config.clone().data_file_dir); + let r = wallet::WalletSeed::init_file(&self.wallet_config, 32, None, ""); + + let client_n = HTTPNodeClient::new(&self.wallet_config.check_node_api_http_addr, None); + + if let Err(_e) = r { + //panic!("Error initializing wallet seed: {}", e); + } + + let wallet: LMDBBackend = + LMDBBackend::new(self.wallet_config.clone(), "", client_n).unwrap_or_else(|e| { + panic!( + "Error creating wallet: {:?} Config: {:?}", + e, self.wallet_config + ) + }); + + Arc::new(Mutex::new(wallet)) + } + + /// Starts a wallet daemon to receive + pub fn run_wallet(&mut self, _duration_in_mills: u64) { + let wallet = self.make_wallet_for_tests(); + + wallet::controller::foreign_listener(wallet, &self.wallet_config.api_listen_addr(), None) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, self.wallet_config + ) + }); + + self.wallet_is_running = true; + } + + /// Starts a wallet owner daemon + #[allow(dead_code)] + pub fn run_owner(&mut self) { + let wallet = self.make_wallet_for_tests(); + + // WalletConfig doesn't allow changing the owner API path, so we build + // the path ourselves + let owner_listen_addr = format!("127.0.0.1:{}", self.config.owner_port); + + wallet::controller::owner_listener( + wallet, + &owner_listen_addr, + None, + None, + self.wallet_config.owner_api_include_foreign.clone(), + ) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet owner listener: {:?} Config: {:?}", + e, self.wallet_config + ) + }); + } + + #[allow(dead_code)] + pub fn get_wallet_seed(config: &WalletConfig) -> wallet::WalletSeed { + let _ = fs::create_dir_all(config.clone().data_file_dir); + wallet::WalletSeed::init_file(config, 32, None, "").unwrap(); + let wallet_seed = + wallet::WalletSeed::from_file(config, "").expect("Failed to read wallet seed file."); + wallet_seed + } + + #[allow(dead_code)] + pub fn get_wallet_info( + config: &WalletConfig, + wallet_seed: &wallet::WalletSeed, + ) -> wallet::WalletInfo { + let keychain: keychain::ExtKeychain = wallet_seed + .derive_keychain(false) + .expect("Failed to derive keychain from seed file and passphrase."); + let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None); + let mut wallet = LMDBBackend::new(config.clone(), "", client_n) + .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); + wallet.keychain = Some(keychain); + let parent_id = keychain::ExtKeychain::derive_key_id(2, 0, 0, 0, 0); + let _ = + libwallet::internal::updater::refresh_outputs(&mut wallet, &parent_id, false); + libwallet::internal::updater::retrieve_info(&mut wallet, &parent_id, 1).unwrap() + } + + #[allow(dead_code)] + pub fn send_amount_to( + config: &WalletConfig, + amount: &str, + minimum_confirmations: u64, + selection_strategy: &str, + dest: &str, + _fluff: bool, + ) { + let amount = core::core::amount_from_hr_string(amount) + .expect("Could not parse amount as a number with optional decimal point."); + + let wallet_seed = + wallet::WalletSeed::from_file(config, "").expect("Failed to read wallet seed file."); + + let keychain: keychain::ExtKeychain = wallet_seed + .derive_keychain(false) + .expect("Failed to derive keychain from seed file and passphrase."); + + let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None); + let client_w = HTTPWalletCommAdapter::new(); + + let max_outputs = 500; + let change_outputs = 1; + + let mut wallet = LMDBBackend::new(config.clone(), "", client_n) + .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); + wallet.keychain = Some(keychain); + let _ = wallet::controller::owner_single_use(Arc::new(Mutex::new(wallet)), |api| { + let (mut slate, lock_fn) = api.initiate_tx( + None, + amount, + minimum_confirmations, + max_outputs, + change_outputs, + selection_strategy == "all", + None, + )?; + slate = client_w.send_tx_sync(dest, &slate)?; + api.finalize_tx(&mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; + println!( + "Tx sent: {} grin to {} (strategy '{}')", + core::core::amount_to_hr_string(amount, false), + dest, + selection_strategy, + ); + Ok(()) + }) + .unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, config)); + } + + /// Stops the running wallet server + pub fn stop_wallet(&mut self) { + println!("Stop wallet!"); + let api_server = self.api_server.as_mut().unwrap(); + api_server.stop(); + } + + /// Adds a peer to this server to connect to upon running + + #[allow(dead_code)] + pub fn add_peer(&mut self, addr: String) { + self.peer_list.push(addr); + } +} + +/// Configuration values for container pool + +pub struct LocalServerContainerPoolConfig { + // Base name to append to all the servers in this pool + pub base_name: String, + + // Base http address for all of the servers in this pool + pub base_http_addr: String, + + // Base port server for all of the servers in this pool + // Increment the number by 1 for each new server + pub base_p2p_port: u16, + + // Base api port for all of the servers in this pool + // Increment this number by 1 for each new server + pub base_api_port: u16, + + // Base wallet port for this server + // + pub base_wallet_port: u16, + + // Base wallet owner port for this server + // + pub base_owner_port: u16, + + // How long the servers in the pool are going to run + pub run_length_in_seconds: u64, +} + +/// Default server config +/// +impl Default for LocalServerContainerPoolConfig { + fn default() -> LocalServerContainerPoolConfig { + LocalServerContainerPoolConfig { + base_name: String::from("test_pool"), + base_http_addr: String::from("127.0.0.1"), + base_p2p_port: 10000, + base_api_port: 11000, + base_wallet_port: 12000, + base_owner_port: 13000, + run_length_in_seconds: 30, + } + } +} + +/// A convenience pool for running many servers simultaneously +/// without necessarily having to configure each one manually + +#[allow(dead_code)] +pub struct LocalServerContainerPool { + // configuration + pub config: LocalServerContainerPoolConfig, + + // keep ahold of all the created servers thread-safely + server_containers: Vec, + + // Keep track of what the last ports a server was opened on + next_p2p_port: u16, + + next_api_port: u16, + + next_wallet_port: u16, + + next_owner_port: u16, + + // keep track of whether a seed exists, and pause a bit if so + is_seeding: bool, +} + +#[allow(dead_code)] +impl LocalServerContainerPool { + pub fn new(config: LocalServerContainerPoolConfig) -> LocalServerContainerPool { + (LocalServerContainerPool { + next_api_port: config.base_api_port, + next_p2p_port: config.base_p2p_port, + next_wallet_port: config.base_wallet_port, + next_owner_port: config.base_owner_port, + config: config, + server_containers: Vec::new(), + is_seeding: false, + }) + } + + /// adds a single server on the next available port + /// overriding passed-in values as necessary. Config object is an OUT value + /// with + /// ports/addresses filled in + /// + + #[allow(dead_code)] + pub fn create_server(&mut self, server_config: &mut LocalServerContainerConfig) { + // If we're calling it this way, need to override these + server_config.p2p_server_port = self.next_p2p_port; + server_config.api_server_port = self.next_api_port; + server_config.wallet_port = self.next_wallet_port; + server_config.owner_port = self.next_owner_port; + + server_config.name = String::from(format!( + "{}/{}-{}", + self.config.base_name, self.config.base_name, server_config.p2p_server_port + )); + + // Use self as coinbase wallet + server_config.coinbase_wallet_address = String::from(format!( + "http://{}:{}", + server_config.base_addr, server_config.wallet_port + )); + + self.next_p2p_port += 1; + self.next_api_port += 1; + self.next_wallet_port += 1; + self.next_owner_port += 1; + + if server_config.is_seeding { + self.is_seeding = true; + } + + let _server_address = format!( + "{}:{}", + server_config.base_addr, server_config.p2p_server_port + ); + + let server_container = LocalServerContainer::new(server_config.clone()).unwrap(); + // self.server_containers.push(server_arc); + + // Create a future that runs the server for however many seconds + // collect them all and run them in the run_all_servers + let _run_time = self.config.run_length_in_seconds; + + self.server_containers.push(server_container); + } + + /// adds n servers, ready to run + /// + /// + #[allow(dead_code)] + pub fn create_servers(&mut self, number: u16) { + for _ in 0..number { + // self.create_server(); + } + } + + /// runs all servers, and returns a vector of references to the servers + /// once they've all been run + /// + + #[allow(dead_code)] + pub fn run_all_servers(self) -> Arc>> { + let run_length = self.config.run_length_in_seconds; + let mut handles = vec![]; + + // return handles to all of the servers, wrapped in mutexes, handles, etc + let return_containers = Arc::new(Mutex::new(Vec::new())); + + let is_seeding = self.is_seeding.clone(); + + for mut s in self.server_containers { + let return_container_ref = return_containers.clone(); + let handle = thread::spawn(move || { + if is_seeding && !s.config.is_seeding { + // there's a seed and we're not it, so hang around longer and give the seed + // a chance to start + thread::sleep(time::Duration::from_millis(2000)); + } + let server_ref = s.run_server(run_length); + return_container_ref.lock().push(server_ref); + }); + // Not a big fan of sleeping hack here, but there appears to be a + // concurrency issue when creating files in rocksdb that causes + // failure if we don't pause a bit before starting the next server + thread::sleep(time::Duration::from_millis(500)); + handles.push(handle); + } + + for handle in handles { + match handle.join() { + Ok(_) => {} + Err(e) => { + println!("Error starting server thread: {:?}", e); + panic!(e); + } + } + } + + // return a much simplified version of the results + return_containers.clone() + } + + #[allow(dead_code)] + pub fn connect_all_peers(&mut self) { + // just pull out all currently active servers, build a list, + // and feed into all servers + let mut server_addresses: Vec = Vec::new(); + for s in &self.server_containers { + let server_address = format!("{}:{}", s.config.base_addr, s.config.p2p_server_port); + server_addresses.push(server_address); + } + + for a in server_addresses { + for s in &mut self.server_containers { + if format!("{}:{}", s.config.base_addr, s.config.p2p_server_port) != a { + s.add_peer(a.clone()); + } + } + } + } +} + +#[allow(dead_code)] +pub fn stop_all_servers(servers: Arc>>) { + let locked_servs = servers.lock(); + for s in locked_servs.deref() { + s.stop(); + } +} + +/// Create and return a ServerConfig +#[allow(dead_code)] +pub fn config(n: u16, test_name_dir: &str, seed_n: u16) -> servers::ServerConfig { + servers::ServerConfig { + api_http_addr: format!("127.0.0.1:{}", 20000 + n), + api_secret_path: None, + db_root: format!("target/tmp/{}/grin-sync-{}", test_name_dir, n), + p2p_config: p2p::P2PConfig { + port: 10000 + n, + seeding_type: p2p::Seeding::List, + seeds: Some(vec![format!("127.0.0.1:{}", 10000 + seed_n)]), + ..p2p::P2PConfig::default() + }, + chain_type: core::global::ChainTypes::AutomatedTesting, + archive_mode: Some(true), + skip_sync_wait: Some(true), + ..Default::default() + } +} + +/// return stratum mining config +#[allow(dead_code)] +pub fn stratum_config() -> servers::common::types::StratumServerConfig { + servers::common::types::StratumServerConfig { + enable_stratum_server: Some(true), + stratum_server_addr: Some(String::from("127.0.0.1:13416")), + attempt_time_per_block: 60, + minimum_share_difficulty: 1, + wallet_listener_url: String::from("http://127.0.0.1:13415"), + burn_reward: false, + } +} diff --git a/integration/tests/simulnet.rs b/integration/tests/simulnet.rs new file mode 100644 index 00000000..3db92107 --- /dev/null +++ b/integration/tests/simulnet.rs @@ -0,0 +1,1006 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate grin_apiwallet as apiwallet; +extern crate grin_libwallet as libwallet; +extern crate grin_refwallet as wallet; +extern crate grin_wallet_config as wallet_config; +#[macro_use] +extern crate log; + +mod framework; + +use self::core::core::hash::Hashed; +use self::core::global::{self, ChainTypes}; +use self::libwallet::types::{WalletBackend, WalletInst}; +use self::util::{Mutex, StopState}; +use self::wallet::controller; +use self::wallet::lmdb_wallet::LMDBBackend; +use self::wallet::{HTTPNodeClient, HTTPWalletCommAdapter}; +use self::wallet_config::WalletConfig; +use apiwallet::{APIForeign, APIOwner}; +use grin_api as api; +use grin_core as core; +use grin_keychain as keychain; +use grin_p2p as p2p; +use grin_servers as servers; +use grin_util as util; +use std::cmp; +use std::default::Default; +use std::process::exit; +use std::sync::Arc; +use std::{thread, time}; + +use crate::framework::{ + config, stop_all_servers, LocalServerContainerConfig, LocalServerContainerPool, + LocalServerContainerPoolConfig, +}; + +/// Testing the frameworks by starting a fresh server, creating a genesis +/// Block and mining into a wallet for a bit +#[test] +fn basic_genesis_mine() { + util::init_test_logger(); + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "genesis_mine"; + framework::clean_all_output(test_name_dir); + + // Create a server pool + let mut pool_config = LocalServerContainerPoolConfig::default(); + pool_config.base_name = String::from(test_name_dir); + pool_config.run_length_in_seconds = 10; + + pool_config.base_api_port = 30000; + pool_config.base_p2p_port = 31000; + pool_config.base_wallet_port = 32000; + + let mut pool = LocalServerContainerPool::new(pool_config); + + // Create a server to add into the pool + let mut server_config = LocalServerContainerConfig::default(); + server_config.start_miner = true; + server_config.start_wallet = false; + server_config.burn_mining_rewards = true; + + pool.create_server(&mut server_config); + let servers = pool.run_all_servers(); + stop_all_servers(servers); +} + +/// Creates 5 servers, first being a seed and check that through peer address +/// messages they all end up connected. +#[test] +fn simulate_seeding() { + util::init_test_logger(); + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "simulate_seeding"; + framework::clean_all_output(test_name_dir); + + // Create a server pool + let mut pool_config = LocalServerContainerPoolConfig::default(); + pool_config.base_name = test_name_dir.to_string(); + pool_config.run_length_in_seconds = 30; + + // have to use different ports because of tests being run in parallel + pool_config.base_api_port = 30020; + pool_config.base_p2p_port = 31020; + pool_config.base_wallet_port = 32020; + + let mut pool = LocalServerContainerPool::new(pool_config); + + // Create a first seed server to add into the pool + let mut server_config = LocalServerContainerConfig::default(); + // server_config.start_miner = true; + server_config.start_wallet = false; + server_config.burn_mining_rewards = true; + server_config.is_seeding = true; + + pool.create_server(&mut server_config); + + // wait the seed server fully start up before start remaining servers + thread::sleep(time::Duration::from_millis(1_000)); + + // point next servers at first seed + server_config.is_seeding = false; + server_config.seed_addr = format!( + "{}:{}", + server_config.base_addr, server_config.p2p_server_port + ); + + for _ in 0..4 { + pool.create_server(&mut server_config); + } + + let servers = pool.run_all_servers(); + thread::sleep(time::Duration::from_secs(5)); + + // Check they all end up connected. + let url = format!( + "http://{}:{}/v1/peers/connected", + &server_config.base_addr, 30020 + ); + let peers_all = api::client::get::>(url.as_str(), None); + assert!(peers_all.is_ok()); + assert_eq!(peers_all.unwrap().len(), 4); + + stop_all_servers(servers); + + // wait servers fully stop before start next automated test + thread::sleep(time::Duration::from_millis(1_000)); +} + +/// Create 1 server, start it mining, then connect 4 other peers mining and +/// using the first as a seed. Meant to test the evolution of mining difficulty with miners +/// running at different rates. +/// +/// TODO: Just going to comment this out as an automatically run test for the time +/// being, As it's more for actively testing and hurts CI a lot +#[ignore] +#[test] +fn simulate_parallel_mining() { + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "simulate_parallel_mining"; + // framework::clean_all_output(test_name_dir); + + // Create a server pool + let mut pool_config = LocalServerContainerPoolConfig::default(); + pool_config.base_name = test_name_dir.to_string(); + pool_config.run_length_in_seconds = 60; + // have to use different ports because of tests being run in parallel + pool_config.base_api_port = 30040; + pool_config.base_p2p_port = 31040; + pool_config.base_wallet_port = 32040; + + let mut pool = LocalServerContainerPool::new(pool_config); + + // Create a first seed server to add into the pool + let mut server_config = LocalServerContainerConfig::default(); + server_config.start_miner = true; + server_config.start_wallet = true; + server_config.is_seeding = true; + + pool.create_server(&mut server_config); + + // point next servers at first seed + server_config.is_seeding = false; + server_config.seed_addr = format!( + "{}:{}", + server_config.base_addr, server_config.p2p_server_port + ); + + // And create 4 more, then let them run for a while + for i in 1..4 { + // fudge in some slowdown + server_config.miner_slowdown_in_millis = i * 2; + pool.create_server(&mut server_config); + } + + // pool.connect_all_peers(); + + let servers = pool.run_all_servers(); + stop_all_servers(servers); + + // Check mining difficulty here?, though I'd think it's more valuable + // to simply output it. Can at least see the evolution of the difficulty target + // in the debug log output for now +} + +// TODO: Convert these tests to newer framework format +/// Create a network of 5 servers and mine a block, verifying that the block +/// gets propagated to all. +#[test] +fn simulate_block_propagation() { + util::init_test_logger(); + + // we actually set the chain_type in the ServerConfig below + // TODO - avoid needing to set it in two places? + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "grin-prop"; + framework::clean_all_output(test_name_dir); + + // instantiates 5 servers on different ports + let mut servers = vec![]; + for n in 0..5 { + let s = servers::Server::new(framework::config(10 * n, test_name_dir, 0)).unwrap(); + servers.push(s); + thread::sleep(time::Duration::from_millis(100)); + } + + // start mining + let stop = Arc::new(Mutex::new(StopState::new())); + servers[0].start_test_miner(None, stop.clone()); + + // monitor for a change of head on a different server and check whether + // chain height has changed + let mut success = false; + let mut time_spent = 0; + loop { + let mut count = 0; + for n in 0..5 { + if servers[n].head().height > 3 { + count += 1; + } + } + if count == 5 { + success = true; + break; + } + thread::sleep(time::Duration::from_millis(1_000)); + time_spent += 1; + if time_spent >= 30 { + info!("simulate_block_propagation - fail on timeout",); + break; + } + + // stop mining after 8s + if time_spent == 8 { + servers[0].stop_test_miner(stop.clone()); + } + } + for n in 0..5 { + servers[n].stop(); + } + assert_eq!(true, success); + + // wait servers fully stop before start next automated test + thread::sleep(time::Duration::from_millis(1_000)); +} + +/// Creates 2 different disconnected servers, mine a few blocks on one, connect +/// them and check that the 2nd gets all the blocks +#[test] +fn simulate_full_sync() { + util::init_test_logger(); + + // we actually set the chain_type in the ServerConfig below + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "grin-sync"; + framework::clean_all_output(test_name_dir); + + let s1 = servers::Server::new(framework::config(1000, "grin-sync", 1000)).unwrap(); + // mine a few blocks on server 1 + let stop = Arc::new(Mutex::new(StopState::new())); + s1.start_test_miner(None, stop.clone()); + thread::sleep(time::Duration::from_secs(8)); + s1.stop_test_miner(stop); + + let s2 = servers::Server::new(framework::config(1001, "grin-sync", 1000)).unwrap(); + + // Get the current header from s1. + let s1_header = s1.chain.head_header().unwrap(); + info!( + "simulate_full_sync - s1 header head: {} at {}", + s1_header.hash(), + s1_header.height + ); + + // Wait for s2 to sync up to and including the header from s1. + let mut time_spent = 0; + while s2.head().height < s1_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + time_spent += 1; + if time_spent >= 30 { + info!( + "sync fail. s2.head().height: {}, s1_header.height: {}", + s2.head().height, + s1_header.height + ); + break; + } + } + + // Confirm both s1 and s2 see a consistent header at that height. + let s2_header = s2.chain.get_block_header(&s1_header.hash()).unwrap(); + assert_eq!(s1_header, s2_header); + + // Stop our servers cleanly. + s1.stop(); + s2.stop(); + + // wait servers fully stop before start next automated test + thread::sleep(time::Duration::from_millis(1_000)); +} + +/// Creates 2 different disconnected servers, mine a few blocks on one, connect +/// them and check that the 2nd gets all using fast sync algo +#[test] +fn simulate_fast_sync() { + util::init_test_logger(); + + // we actually set the chain_type in the ServerConfig below + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "grin-fast"; + framework::clean_all_output(test_name_dir); + + // start s1 and mine enough blocks to get beyond the fast sync horizon + let s1 = servers::Server::new(framework::config(2000, "grin-fast", 2000)).unwrap(); + let stop = Arc::new(Mutex::new(StopState::new())); + s1.start_test_miner(None, stop.clone()); + + while s1.head().height < 20 { + thread::sleep(time::Duration::from_millis(1_000)); + } + s1.stop_test_miner(stop); + + let mut conf = config(2001, "grin-fast", 2000); + conf.archive_mode = Some(false); + + let s2 = servers::Server::new(conf).unwrap(); + + // Get the current header from s1. + let s1_header = s1.chain.head_header().unwrap(); + + // Wait for s2 to sync up to and including the header from s1. + let mut total_wait = 0; + while s2.head().height < s1_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 30 { + error!( + "simulate_fast_sync test fail on timeout! s2 height: {}, s1 height: {}", + s2.head().height, + s1_header.height, + ); + break; + } + } + + // Confirm both s1 and s2 see a consistent header at that height. + let s2_header = s2.chain.get_block_header(&s1_header.hash()).unwrap(); + assert_eq!(s1_header, s2_header); + + // Stop our servers cleanly. + s1.stop(); + s2.stop(); + + // wait servers fully stop before start next automated test + thread::sleep(time::Duration::from_millis(1_000)); +} + +/// Preparation: +/// Creates 6 disconnected servers: A, B, C, D, E and F, mine 80 blocks on A, +/// Compact server A. +/// Connect all servers, check all get state_sync_threshold full blocks using fast sync. +/// Disconnect all servers from each other. +/// +/// Test case 1: nodes that just synced is able to handle forks of up to state_sync_threshold +/// Mine state_sync_threshold-7 blocks on A +/// Mine state_sync_threshold-1 blocks on C (long fork), connect C to server A +/// check server A can sync to C without txhashset download. +/// +/// Test case 2: nodes with history in between state_sync_threshold and cut_through_horizon will +/// be able to handle forks larger than state_sync_threshold but not as large as cut_through_horizon. +/// Mine 20 blocks on A (then A has 59 blocks in local chain) +/// Mine cut_through_horizon-1 blocks on D (longer fork), connect D to servers A, then fork point +/// is at A's body head.height - 39, and 20 < 39 < 70. +/// check server A can sync without txhashset download. +/// +/// Test case 3: nodes that have enough history is able to handle forks of up to cut_through_horizon +/// Mine cut_through_horizon+10 blocks on E, connect E to servers A and B +/// check server A can sync to E without txhashset download. +/// check server B can sync to E but need txhashset download. +/// +/// Test case 4: nodes which had a success state sync can have a new state sync if needed. +/// Mine cut_through_horizon+20 blocks on F (longer fork than E), connect F to servers B +/// check server B can sync to F with txhashset download. +/// +/// Test case 5: normal sync (not a fork) should not trigger a txhashset download +/// Mine cut_through_horizon-10 blocks on F, connect F to servers B +/// check server B can sync to F without txhashset download. +/// +/// Test case 6: far behind sync (not a fork) should trigger a txhashset download +/// Mine cut_through_horizon+1 blocks on F, connect F to servers B +/// check server B can sync to F with txhashset download. +/// +/// +#[ignore] +#[test] +fn simulate_long_fork() { + util::init_test_logger(); + println!("starting simulate_long_fork"); + + // we actually set the chain_type in the ServerConfig below + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "grin-long-fork"; + framework::clean_all_output(test_name_dir); + + let s = long_fork_test_preparation(); + for si in &s { + si.pause(); + } + thread::sleep(time::Duration::from_millis(1_000)); + + long_fork_test_case_1(&s); + thread::sleep(time::Duration::from_millis(1_000)); + + long_fork_test_case_2(&s); + thread::sleep(time::Duration::from_millis(1_000)); + + long_fork_test_case_3(&s); + thread::sleep(time::Duration::from_millis(1_000)); + + long_fork_test_case_4(&s); + thread::sleep(time::Duration::from_millis(1_000)); + + long_fork_test_case_5(&s); + + // Clean up + for si in &s { + si.stop(); + } + + // wait servers fully stop before start next automated test + thread::sleep(time::Duration::from_millis(1_000)); +} + +fn long_fork_test_preparation() -> Vec { + println!("preparation: mine 80 blocks, create 6 servers and sync all of them"); + + let mut s: Vec = vec![]; + + // start server A and mine 80 blocks to get beyond the fast sync horizon + let mut conf = framework::config(2100, "grin-long-fork", 2100); + conf.archive_mode = Some(false); + conf.api_secret_path = None; + let s0 = servers::Server::new(conf).unwrap(); + thread::sleep(time::Duration::from_millis(1_000)); + s.push(s0); + let stop = Arc::new(Mutex::new(StopState::new())); + s[0].start_test_miner(None, stop.clone()); + + while s[0].head().height < global::cut_through_horizon() as u64 + 10 { + thread::sleep(time::Duration::from_millis(1_000)); + } + s[0].stop_test_miner(stop); + thread::sleep(time::Duration::from_millis(1_000)); + + // Get the current header from s0. + let s0_header = s[0].chain.head().unwrap(); + + // check the tail after compacting + let _ = s[0].chain.compact(); + let s0_tail = s[0].chain.tail().unwrap(); + assert_eq!( + s0_header.height - global::cut_through_horizon() as u64, + s0_tail.height + ); + + for i in 1..6 { + let mut conf = config(2100 + i, "grin-long-fork", 2100); + conf.archive_mode = Some(false); + conf.api_secret_path = None; + let si = servers::Server::new(conf).unwrap(); + s.push(si); + } + thread::sleep(time::Duration::from_millis(1_000)); + + // Wait for s[1..5] to sync up to and including the header from s0. + let mut total_wait = 0; + let mut min_height = 0; + while min_height < s0_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 60 { + println!( + "simulate_long_fork (preparation) test fail on timeout! minimum height: {}, s0 height: {}", + min_height, + s0_header.height, + ); + exit(1); + } + min_height = s0_header.height; + for i in 1..6 { + min_height = cmp::min(s[i].head().height, min_height); + } + } + + // Confirm both s0 and s1 see a consistent header at that height. + let s1_header = s[1].chain.head().unwrap(); + assert_eq!(s0_header, s1_header); + println!( + "preparation done. all 5 servers head.height: {}", + s0_header.height + ); + + // Wait for peers fully connection + let mut total_wait = 0; + let mut min_peers = 0; + while min_peers < 4 { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 60 { + println!( + "simulate_long_fork (preparation) test fail on timeout! minimum connected peers: {}", + min_peers, + ); + exit(1); + } + min_peers = 4; + for i in 0..5 { + let peers_connected = get_connected_peers(&"127.0.0.1".to_owned(), 22100 + i); + min_peers = cmp::min(min_peers, peers_connected.len()); + } + } + + return s; +} + +fn long_fork_test_mining(blocks: u64, n: u16, s: &servers::Server) { + // Get the current header from node. + let sn_header = s.chain.head().unwrap(); + + // Mining + let stop = Arc::new(Mutex::new(StopState::new())); + s.start_test_miner(None, stop.clone()); + + while s.head().height < sn_header.height + blocks { + thread::sleep(time::Duration::from_millis(1)); + } + s.stop_test_miner(stop); + thread::sleep(time::Duration::from_millis(1_000)); + println!( + "{} blocks mined on s{}. s{}.height: {} (old height: {})", + s.head().height - sn_header.height, + n, + n, + s.head().height, + sn_header.height, + ); + + let _ = s.chain.compact(); + let sn_header = s.chain.head().unwrap(); + let sn_tail = s.chain.tail().unwrap(); + println!( + "after compacting, s{}.head().height: {}, s{}.tail().height: {}", + n, sn_header.height, n, sn_tail.height, + ); +} + +fn long_fork_test_case_1(s: &Vec) { + println!("\ntest case 1 start"); + + // Mine state_sync_threshold-7 blocks on s0 + long_fork_test_mining(global::state_sync_threshold() as u64 - 7, 0, &s[0]); + + // Mine state_sync_threshold-1 blocks on s2 (long fork), a fork with more work than s0 chain + long_fork_test_mining(global::state_sync_threshold() as u64 - 1, 2, &s[2]); + + let s2_header = s[2].chain.head().unwrap(); + let s0_header = s[0].chain.head().unwrap(); + let s0_tail = s[0].chain.tail().unwrap(); + println!( + "test case 1: s0 start syncing with s2... s0.head().height: {}, s2.head().height: {}", + s0_header.height, s2_header.height, + ); + s[0].resume(); + s[2].resume(); + + // Check server s0 can sync to s2 without txhashset download. + let mut total_wait = 0; + while s[0].head().height < s2_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 1: test fail on timeout! s0 height: {}, s2 height: {}", + s[0].head().height, + s2_header.height, + ); + exit(1); + } + } + let s0_tail_new = s[0].chain.tail().unwrap(); + assert_eq!(s0_tail_new.height, s0_tail.height); + println!( + "test case 1: s0.head().height: {}, s2_header.height: {}", + s[0].head().height, + s2_header.height, + ); + assert_eq!(s[0].head().last_block_h, s2_header.last_block_h); + + s[0].pause(); + s[2].stop(); + println!("test case 1 passed") +} + +fn long_fork_test_case_2(s: &Vec) { + println!("\ntest case 2 start"); + + // Mine 20 blocks on s0 + long_fork_test_mining(20, 0, &s[0]); + + // Mine cut_through_horizon-1 blocks on s3 (longer fork) + long_fork_test_mining(global::cut_through_horizon() as u64 - 1, 3, &s[3]); + let s3_header = s[3].chain.head().unwrap(); + let s0_header = s[0].chain.head().unwrap(); + let s0_tail = s[0].chain.tail().unwrap(); + println!( + "test case 2: s0 start syncing with s3. s0.head().height: {}, s3.head().height: {}", + s0_header.height, s3_header.height, + ); + s[0].resume(); + s[3].resume(); + + // Check server s0 can sync to s3 without txhashset download. + let mut total_wait = 0; + while s[0].head().height < s3_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 2: test fail on timeout! s0 height: {}, s3 height: {}", + s[0].head().height, + s3_header.height, + ); + exit(1); + } + } + let s0_tail_new = s[0].chain.tail().unwrap(); + assert_eq!(s0_tail_new.height, s0_tail.height); + assert_eq!(s[0].head().hash(), s3_header.hash()); + + let _ = s[0].chain.compact(); + let s0_header = s[0].chain.head().unwrap(); + let s0_tail = s[0].chain.tail().unwrap(); + println!( + "test case 2: after compacting, s0.head().height: {}, s0.tail().height: {}", + s0_header.height, s0_tail.height, + ); + + s[0].pause(); + s[3].stop(); + println!("test case 2 passed") +} + +fn long_fork_test_case_3(s: &Vec) { + println!("\ntest case 3 start"); + + // Mine cut_through_horizon+1 blocks on s4 + long_fork_test_mining(global::cut_through_horizon() as u64 + 10, 4, &s[4]); + + let s4_header = s[4].chain.head().unwrap(); + let s0_header = s[0].chain.head().unwrap(); + let s0_tail = s[0].chain.tail().unwrap(); + let s1_header = s[1].chain.head().unwrap(); + let s1_tail = s[1].chain.tail().unwrap(); + println!( + "test case 3: s0/1 start syncing with s4. s0.head().height: {}, s0.tail().height: {}, s1.head().height: {}, s1.tail().height: {}, s4.head().height: {}", + s0_header.height, s0_tail.height, + s1_header.height, s1_tail.height, + s4_header.height, + ); + s[0].resume(); + s[4].resume(); + + // Check server s0 can sync to s4. + let mut total_wait = 0; + while s[0].head().height < s4_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 3: test fail on timeout! s0 height: {}, s4 height: {}", + s[0].head().height, + s4_header.height, + ); + exit(1); + } + } + assert_eq!(s[0].head().hash(), s4_header.hash()); + + s[0].stop(); + s[1].resume(); + + // Check server s1 can sync to s4 but with txhashset download. + let mut total_wait = 0; + while s[1].head().height < s4_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 3: test fail on timeout! s1 height: {}, s4 height: {}", + s[1].head().height, + s4_header.height, + ); + exit(1); + } + } + let s1_tail_new = s[1].chain.tail().unwrap(); + println!( + "test case 3: s[1].tail().height: {}, old height: {}", + s1_tail_new.height, s1_tail.height + ); + assert_ne!(s1_tail_new.height, s1_tail.height); + assert_eq!(s[1].head().hash(), s4_header.hash()); + + s[1].pause(); + s[4].pause(); + println!("test case 3 passed") +} + +fn long_fork_test_case_4(s: &Vec) { + println!("\ntest case 4 start"); + + let _ = s[1].chain.compact(); + + // Mine cut_through_horizon+20 blocks on s5 (longer fork than s4) + long_fork_test_mining(global::cut_through_horizon() as u64 + 20, 5, &s[5]); + + let s5_header = s[5].chain.head().unwrap(); + let s1_header = s[1].chain.head().unwrap(); + let s1_tail = s[1].chain.tail().unwrap(); + println!( + "test case 4: s1 start syncing with s5. s1.head().height: {}, s1.tail().height: {}, s5.head().height: {}", + s1_header.height, s1_tail.height, + s5_header.height, + ); + s[1].resume(); + s[5].resume(); + + // Check server s1 can sync to s5 with a new txhashset download. + let mut total_wait = 0; + while s[1].head().height < s5_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 4: test fail on timeout! s1 height: {}, s5 height: {}", + s[1].head().height, + s5_header.height, + ); + exit(1); + } + } + let s1_tail_new = s[1].chain.tail().unwrap(); + println!( + "test case 4: s[1].tail().height: {}, old height: {}", + s1_tail_new.height, s1_tail.height + ); + assert_ne!(s1_tail_new.height, s1_tail.height); + assert_eq!(s[1].head().hash(), s5_header.hash()); + + s[1].pause(); + s[5].pause(); + + println!("test case 4 passed") +} + +fn long_fork_test_case_5(s: &Vec) { + println!("\ntest case 5 start"); + + let _ = s[1].chain.compact(); + + // Mine cut_through_horizon-10 blocks on s5 + long_fork_test_mining(global::cut_through_horizon() as u64 - 10, 5, &s[5]); + + let s5_header = s[5].chain.head().unwrap(); + let s1_header = s[1].chain.head().unwrap(); + let s1_tail = s[1].chain.tail().unwrap(); + println!( + "test case 5: s1 start syncing with s5. s1.head().height: {}, s1.tail().height: {}, s5.head().height: {}", + s1_header.height, s1_tail.height, + s5_header.height, + ); + s[1].resume(); + s[5].resume(); + + // Check server s1 can sync to s5 without a txhashset download (normal body sync) + let mut total_wait = 0; + while s[1].head().height < s5_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 5: test fail on timeout! s1 height: {}, s5 height: {}", + s[1].head().height, + s5_header.height, + ); + exit(1); + } + } + let s1_tail_new = s[1].chain.tail().unwrap(); + println!( + "test case 5: s[1].tail().height: {}, old height: {}", + s1_tail_new.height, s1_tail.height + ); + assert_eq!(s1_tail_new.height, s1_tail.height); + assert_eq!(s[1].head().hash(), s5_header.hash()); + + s[1].pause(); + s[5].pause(); + + println!("test case 5 passed") +} + +#[allow(dead_code)] +fn long_fork_test_case_6(s: &Vec) { + println!("\ntest case 6 start"); + + let _ = s[1].chain.compact(); + + // Mine cut_through_horizon+1 blocks on s5 + long_fork_test_mining(global::cut_through_horizon() as u64 + 1, 5, &s[5]); + + let s5_header = s[5].chain.head().unwrap(); + let s1_header = s[1].chain.head().unwrap(); + let s1_tail = s[1].chain.tail().unwrap(); + println!( + "test case 6: s1 start syncing with s5. s1.head().height: {}, s1.tail().height: {}, s5.head().height: {}", + s1_header.height, s1_tail.height, + s5_header.height, + ); + s[1].resume(); + s[5].resume(); + + // Check server s1 can sync to s5 without a txhashset download (normal body sync) + let mut total_wait = 0; + while s[1].head().height < s5_header.height { + thread::sleep(time::Duration::from_millis(1_000)); + total_wait += 1; + if total_wait >= 120 { + println!( + "test case 6: test fail on timeout! s1 height: {}, s5 height: {}", + s[1].head().height, + s5_header.height, + ); + exit(1); + } + } + let s1_tail_new = s[1].chain.tail().unwrap(); + println!( + "test case 6: s[1].tail().height: {}, old height: {}", + s1_tail_new.height, s1_tail.height + ); + assert_eq!(s1_tail_new.height, s1_tail.height); + assert_eq!(s[1].head().hash(), s5_header.hash()); + + s[1].pause(); + s[5].pause(); + + println!("test case 6 passed") +} + +pub fn create_wallet( + dir: &str, + client_n: HTTPNodeClient, +) -> Arc>> { + let mut wallet_config = WalletConfig::default(); + wallet_config.data_file_dir = String::from(dir); + let _ = wallet::WalletSeed::init_file(&wallet_config, 32, None, ""); + let mut wallet: LMDBBackend = + LMDBBackend::new(wallet_config.clone(), "", client_n).unwrap_or_else(|e| { + panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config) + }); + wallet.open_with_credentials().unwrap_or_else(|e| { + panic!( + "Error initializing wallet: {:?} Config: {:?}", + e, wallet_config + ) + }); + Arc::new(Mutex::new(wallet)) +} + +/// Intended to replicate https://github.com/mimblewimble/grin/issues/1325 +#[ignore] +#[test] +fn replicate_tx_fluff_failure() { + util::init_test_logger(); + global::set_mining_mode(ChainTypes::UserTesting); + framework::clean_all_output("tx_fluff"); + + // Create Wallet 1 (Mining Input) and start it listening + // Wallet 1 post to another node, just for fun + let client1 = HTTPNodeClient::new("http://127.0.0.1:23003", None); + let client1_w = HTTPWalletCommAdapter::new(); + let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); + let _wallet1_handle = thread::spawn(move || { + controller::foreign_listener(wallet1, "127.0.0.1:33000", None) + .unwrap_or_else(|e| panic!("Error creating wallet1 listener: {:?}", e,)); + }); + + // Create Wallet 2 (Recipient) and launch + let client2 = HTTPNodeClient::new("http://127.0.0.1:23001", None); + let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); + let _wallet2_handle = thread::spawn(move || { + controller::foreign_listener(wallet2, "127.0.0.1:33001", None) + .unwrap_or_else(|e| panic!("Error creating wallet2 listener: {:?}", e,)); + }); + + // Server 1 (mines into wallet 1) + let mut s1_config = framework::config(3000, "tx_fluff", 3000); + s1_config.test_miner_wallet_url = Some("http://127.0.0.1:33000".to_owned()); + s1_config.dandelion_config.embargo_secs = Some(10); + s1_config.dandelion_config.patience_secs = Some(1); + s1_config.dandelion_config.relay_secs = Some(1); + let s1 = servers::Server::new(s1_config.clone()).unwrap(); + // Mine off of server 1 + s1.start_test_miner(s1_config.test_miner_wallet_url, s1.stop_state.clone()); + thread::sleep(time::Duration::from_secs(5)); + + // Server 2 (another node) + let mut s2_config = framework::config(3001, "tx_fluff", 3001); + s2_config.p2p_config.seeds = Some(vec!["127.0.0.1:13000".to_owned()]); + s2_config.dandelion_config.embargo_secs = Some(10); + s2_config.dandelion_config.patience_secs = Some(1); + s2_config.dandelion_config.relay_secs = Some(1); + let _s2 = servers::Server::new(s2_config.clone()).unwrap(); + + let dl_nodes = 5; + + for i in 0..dl_nodes { + // (create some stem nodes) + let mut s_config = framework::config(3002 + i, "tx_fluff", 3002 + i); + s_config.p2p_config.seeds = Some(vec!["127.0.0.1:13000".to_owned()]); + s_config.dandelion_config.embargo_secs = Some(10); + s_config.dandelion_config.patience_secs = Some(1); + s_config.dandelion_config.relay_secs = Some(1); + let _ = servers::Server::new(s_config.clone()).unwrap(); + } + + thread::sleep(time::Duration::from_secs(10)); + + // get another instance of wallet1 (to update contents and perform a send) + let wallet1 = create_wallet("target/tmp/tx_fluff/wallet1", client1.clone()); + + let amount = 30_000_000_000; + let dest = "http://127.0.0.1:33001"; + + wallet::controller::owner_single_use(wallet1, |api| { + let (mut slate, lock_fn) = api.initiate_tx( + None, amount, // amount + 2, // minimum confirmations + 500, // max outputs + 1000, // num change outputs + true, // select all outputs + None, + )?; + slate = client1_w.send_tx_sync(dest, &slate)?; + api.finalize_tx(&mut slate)?; + api.tx_lock_outputs(&slate, lock_fn)?; + api.post_tx(&slate.tx, false)?; + Ok(()) + }) + .unwrap(); + + // Give some time for propagation and mining + thread::sleep(time::Duration::from_secs(200)); + + // get another instance of wallet (to check contents) + let wallet2 = create_wallet("target/tmp/tx_fluff/wallet2", client2.clone()); + + wallet::controller::owner_single_use(wallet2, |api| { + let res = api.retrieve_summary_info(true, 1).unwrap(); + assert_eq!(res.1.amount_currently_spendable, amount); + Ok(()) + }) + .unwrap(); +} + +fn get_connected_peers( + base_addr: &String, + api_server_port: u16, +) -> Vec { + let url = format!( + "http://{}:{}/v1/peers/connected", + base_addr, api_server_port + ); + api::client::get::>(url.as_str(), None).unwrap() +} diff --git a/integration/tests/stratum.rs b/integration/tests/stratum.rs new file mode 100644 index 00000000..938dcf5e --- /dev/null +++ b/integration/tests/stratum.rs @@ -0,0 +1,177 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[macro_use] +extern crate log; + +mod framework; + +use self::core::global::{self, ChainTypes}; +use crate::framework::{config, stratum_config}; +use bufstream::BufStream; +use grin_core as core; +use grin_servers as servers; +use grin_util as util; +use grin_util::{Mutex, StopState}; +use serde_json::Value; +use std::io::prelude::{BufRead, Write}; +use std::net::TcpStream; +use std::process; +use std::sync::Arc; +use std::{thread, time}; + +// Create a grin server, and a stratum server. +// Simulate a few JSONRpc requests and verify the results. +// Validate disconnected workers +// Validate broadcasting new jobs +#[test] +fn basic_stratum_server() { + util::init_test_logger(); + global::set_mining_mode(ChainTypes::AutomatedTesting); + + let test_name_dir = "stratum_server"; + framework::clean_all_output(test_name_dir); + + // Create a server + let s = servers::Server::new(config(4000, test_name_dir, 0)).unwrap(); + + // Get mining config with stratumserver enabled + let mut stratum_cfg = stratum_config(); + stratum_cfg.burn_reward = true; + stratum_cfg.attempt_time_per_block = 999; + stratum_cfg.enable_stratum_server = Some(true); + stratum_cfg.stratum_server_addr = Some(String::from("127.0.0.1:11101")); + + // Start stratum server + s.start_stratum_server(stratum_cfg); + + // Wait for stratum server to start and + // Verify stratum server accepts connections + loop { + if let Ok(_stream) = TcpStream::connect("127.0.0.1:11101") { + break; + } else { + thread::sleep(time::Duration::from_millis(500)); + } + // As this stream falls out of scope it will be disconnected + } + info!("stratum server connected"); + + // Create a few new worker connections + let mut workers = vec![]; + for _n in 0..5 { + let w = TcpStream::connect("127.0.0.1:11101").unwrap(); + w.set_nonblocking(true) + .expect("Failed to set TcpStream to non-blocking"); + let stream = BufStream::new(w); + workers.push(stream); + } + assert!(workers.len() == 5); + info!("workers length verification ok"); + + // Simulate a worker lost connection + workers.remove(4); + + // Swallow the genesis block + thread::sleep(time::Duration::from_secs(5)); // Wait for the server to broadcast + let mut response = String::new(); + for n in 0..workers.len() { + let _result = workers[n].read_line(&mut response); + } + + // Verify a few stratum JSONRpc commands + // getjobtemplate - expected block template result + let mut response = String::new(); + let job_req = "{\"id\": \"Stratum\", \"jsonrpc\": \"2.0\", \"method\": \"getjobtemplate\"}\n"; + workers[2].write(job_req.as_bytes()).unwrap(); + workers[2].flush().unwrap(); + thread::sleep(time::Duration::from_secs(1)); // Wait for the server to reply + match workers[2].read_line(&mut response) { + Ok(_) => { + let r: Value = serde_json::from_str(&response).unwrap(); + assert_eq!(r["error"], serde_json::Value::Null); + assert_ne!(r["result"], serde_json::Value::Null); + } + Err(_e) => { + assert!(false); + } + } + info!("a few stratum JSONRpc commands verification ok"); + + // keepalive - expected "ok" result + let mut response = String::new(); + let job_req = "{\"id\":\"3\",\"jsonrpc\":\"2.0\",\"method\":\"keepalive\"}\n"; + let ok_resp = "{\"id\":\"3\",\"jsonrpc\":\"2.0\",\"method\":\"keepalive\",\"result\":\"ok\",\"error\":null}\n"; + workers[2].write(job_req.as_bytes()).unwrap(); + workers[2].flush().unwrap(); + thread::sleep(time::Duration::from_secs(1)); // Wait for the server to reply + let _st = workers[2].read_line(&mut response); + assert_eq!(response.as_str(), ok_resp); + info!("keepalive test ok"); + + // "doesnotexist" - error expected + let mut response = String::new(); + let job_req = "{\"id\":\"4\",\"jsonrpc\":\"2.0\",\"method\":\"doesnotexist\"}\n"; + let ok_resp = "{\"id\":\"4\",\"jsonrpc\":\"2.0\",\"method\":\"doesnotexist\",\"result\":null,\"error\":{\"code\":-32601,\"message\":\"Method not found\"}}\n"; + workers[3].write(job_req.as_bytes()).unwrap(); + workers[3].flush().unwrap(); + thread::sleep(time::Duration::from_secs(1)); // Wait for the server to reply + let _st = workers[3].read_line(&mut response); + assert_eq!(response.as_str(), ok_resp); + info!("worker doesnotexist test ok"); + + // Verify stratum server and worker stats + let stats = s.get_server_stats().unwrap(); + assert_eq!(stats.stratum_stats.block_height, 1); // just 1 genesis block + assert_eq!(stats.stratum_stats.num_workers, 4); // 5 - 1 = 4 + assert_eq!(stats.stratum_stats.worker_stats[5].is_connected, false); // worker was removed + assert_eq!(stats.stratum_stats.worker_stats[1].is_connected, true); + info!("stratum server and worker stats verification ok"); + + // Start mining blocks + let stop = Arc::new(Mutex::new(StopState::new())); + s.start_test_miner(None, stop.clone()); + info!("test miner started"); + + // This test is supposed to complete in 3 seconds, + // so let's set a timeout on 10s to avoid infinite waiting happened in Travis-CI. + let _handler = thread::spawn(|| { + thread::sleep(time::Duration::from_secs(10)); + error!("basic_stratum_server test fail on timeout!"); + thread::sleep(time::Duration::from_millis(100)); + process::exit(1); + }); + + // Simulate a worker lost connection + workers.remove(1); + + // Wait for a few mined blocks + thread::sleep(time::Duration::from_secs(3)); + s.stop_test_miner(stop); + + // Verify blocks are being broadcast to workers + let expected = String::from("job"); + let mut jobtemplate = String::new(); + let _st = workers[2].read_line(&mut jobtemplate); + let job_template: Value = serde_json::from_str(&jobtemplate).unwrap(); + assert_eq!(job_template["method"], expected); + info!("blocks broadcasting to workers test ok"); + + // Verify stratum server and worker stats + let stats = s.get_server_stats().unwrap(); + assert_eq!(stats.stratum_stats.num_workers, 3); // 5 - 2 = 3 + assert_eq!(stats.stratum_stats.worker_stats[2].is_connected, false); // worker was removed + assert_ne!(stats.stratum_stats.block_height, 1); + info!("basic_stratum_server test done and ok."); +}