diff --git a/Cargo.lock b/Cargo.lock index 76c7cce134..f9405a7ea7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -344,6 +344,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "beef" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -453,6 +462,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bstr" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +dependencies = [ + "memchr 2.4.0", +] + [[package]] name = "build-helper" version = "0.1.1" @@ -734,6 +752,15 @@ dependencies = [ "subtle 2.4.0", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct", +] + [[package]] name = "ctor" version = "0.1.20" @@ -933,6 +960,19 @@ dependencies = [ "termcolor 1.0.5", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime 2.1.0", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.5.4", + "termcolor 1.1.2", +] + [[package]] name = "environmental" version = "1.1.3" @@ -1423,6 +1463,19 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick 0.7.18", + "bstr", + "fnv", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.5.4", +] + [[package]] name = "gloo-timers" version = "0.2.1" @@ -1643,6 +1696,12 @@ dependencies = [ "sgx_tstd", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.13.10" @@ -1703,6 +1762,23 @@ dependencies = [ "hyper 0.14.9", ] +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "ct-logs", + "futures-util", + "hyper 0.14.9", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls", + "rustls-native-certs", + "tokio 1.7.1", + "tokio-rustls", + "webpki 0.21.4", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1729,7 +1805,7 @@ dependencies = [ "sp-core", "sp-io 3.0.0", "sp-std", - "webpki", + "webpki 0.21.0", ] [[package]] @@ -1975,6 +2051,157 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonrpsee" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "316a89048d2ea5530ab5502aa31e1128f6429b524a37e4c0bc54903bcdf3d342" +dependencies = [ + "jsonrpsee-http-client", + "jsonrpsee-http-server", + "jsonrpsee-proc-macros", + "jsonrpsee-types", + "jsonrpsee-utils", + "jsonrpsee-ws-client", + "jsonrpsee-ws-server", +] + +[[package]] +name = "jsonrpsee-http-client" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7275601ba6f9f6feaa82d3c66b51e34d190e75f1cf23d5c40f7801f3a7610a6" +dependencies = [ + "async-trait", + "fnv", + "hyper 0.14.9", + "hyper-rustls", + "jsonrpsee-types", + "jsonrpsee-utils", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", + "thiserror", + "url 2.2.2", +] + +[[package]] +name = "jsonrpsee-http-server" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22372378f63f7d16de453e786afc740fca5ee80bd260be024a616b6ac2cefe5" +dependencies = [ + "futures-channel", + "futures-util", + "globset", + "hyper 0.14.9", + "jsonrpsee-types", + "jsonrpsee-utils", + "lazy_static", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", + "socket2", + "thiserror", + "tokio 1.7.1", + "unicase", +] + +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4c85cfa6767333f3e5f3b2f2f765dad2727b0033ee270ae07c599bf43ed5ae" +dependencies = [ + "Inflector", + "proc-macro-crate 1.0.0", + "proc-macro2", + "quote 1.0.9", + "syn 1.0.73", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cf7bd4e93b3b56e59131de7f24afbea871faf914e97bcdd942c86927ab0172" +dependencies = [ + "async-trait", + "beef", + "futures-channel", + "futures-util", + "hyper 0.14.9", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", + "soketto", + "thiserror", +] + +[[package]] +name = "jsonrpsee-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47554ecaacb479285da68799d9b6afc258c32b332cc8b85829c6a9304ee98776" +dependencies = [ + "futures-channel", + "futures-util", + "hyper 0.14.9", + "jsonrpsee-types", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.11.1", + "rand 0.8.4", + "rustc-hash", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ec51150965544e1a4468f372bdab8545243a1b045d4ab272023aac74c60de32" +dependencies = [ + "async-trait", + "fnv", + "futures 0.3.15", + "jsonrpsee-types", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 1.0.7", + "rustls", + "rustls-native-certs", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio 1.7.1", + "tokio-rustls", + "tokio-util 0.6.7", + "url 2.2.2", +] + +[[package]] +name = "jsonrpsee-ws-server" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b512c3c679a89d20f97802f69188a2d01f6234491b7513076e21e8424efccafe" +dependencies = [ + "futures-channel", + "futures-util", + "jsonrpsee-types", + "jsonrpsee-utils", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hash", + "serde", + "serde_json", + "soketto", + "thiserror", + "tokio 1.7.1", + "tokio-stream", + "tokio-util 0.6.7", +] + [[package]] name = "keccak" version = "0.1.0" @@ -3835,6 +4062,31 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.16.20", + "sct", + "webpki 0.21.4", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "ruzstd" version = "0.2.2" @@ -3965,6 +4217,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring 0.16.20", + "untrusted", +] + [[package]] name = "secrecy" version = "0.7.0" @@ -4289,6 +4551,19 @@ dependencies = [ "opaque-debug 0.2.3", ] +[[package]] +name = "sha-1" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + [[package]] name = "sha1" version = "0.5.0" @@ -4436,6 +4711,21 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "soketto" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4919971d141dbadaa0e82b5d369e2d7666c98e4625046140615ca363e50d4daa" +dependencies = [ + "base64 0.13.0", + "bytes 1.0.1", + "futures 0.3.15", + "httparse", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.8.4", + "sha-1 0.9.6", +] + [[package]] name = "sp-api" version = "3.0.0" @@ -5098,7 +5388,7 @@ dependencies = [ [[package]] name = "substrate-api-client" version = "0.6.0" -source = "git+https://github.com/scs/substrate-api-client?branch=master#731a3a5c3a16ed3ce7f1f6b50593a2cdfd08a39f" +source = "git+https://github.com/scs/substrate-api-client?branch=master#3c8c04cd785b383dfbacb48a374503e644d13a08" dependencies = [ "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "frame-metadata", @@ -5136,7 +5426,7 @@ dependencies = [ [[package]] name = "substrate-client-keystore" version = "0.6.0" -source = "git+https://github.com/scs/substrate-api-client?branch=master#731a3a5c3a16ed3ce7f1f6b50593a2cdfd08a39f" +source = "git+https://github.com/scs/substrate-api-client?branch=master#3c8c04cd785b383dfbacb48a374503e644d13a08" dependencies = [ "async-trait", "hex 0.4.3", @@ -5230,6 +5520,24 @@ dependencies = [ "ws 0.7.9", ] +[[package]] +name = "substratee-enclave-api" +version = "0.8.0" +dependencies = [ + "frame-support", + "parity-scale-codec 2.1.3", + "sgx_types", + "substratee-enclave-api-ffi", + "thiserror", +] + +[[package]] +name = "substratee-enclave-api-ffi" +version = "0.1.0" +dependencies = [ + "sgx_types", +] + [[package]] name = "substratee-node-primitives" version = "0.8.0" @@ -5295,6 +5603,7 @@ dependencies = [ "pallet-balances", "parity-scale-codec 2.1.3", "sc-keystore", + "serde", "sgx-externalities", "sgx-runtime", "sgx_tstd", @@ -5313,20 +5622,25 @@ dependencies = [ name = "substratee-worker" version = "0.8.0" dependencies = [ + "anyhow", + "async-trait", "base58", "cid", "clap", "dirs 1.0.5", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "frame-support", "frame-system", "futures 0.3.15", "hex 0.3.2", "ipfs-api", + "jsonrpsee", "lazy_static", "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", "multihash 0.8.0", "pallet-balances", "parity-scale-codec 2.1.3", + "parking_lot 0.11.1", "primitive-types 0.9.0", "rust-crypto", "serde", @@ -5342,13 +5656,16 @@ dependencies = [ "sp-runtime", "substrate-api-client", "substratee-api-client-extensions", + "substratee-enclave-api", "substratee-node-primitives", "substratee-node-runtime", "substratee-settings", "substratee-stf", "substratee-worker-api", "substratee-worker-primitives", - "tokio 0.2.25", + "substratee-worker-rpc-server", + "thiserror", + "tokio 1.7.1", "ws 0.7.9", ] @@ -5381,6 +5698,22 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "substratee-worker-rpc-server" +version = "0.8.0" +dependencies = [ + "anyhow", + "env_logger 0.8.4", + "jsonrpsee", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-scale-codec 2.1.3", + "serde_json", + "sp-core", + "substratee-enclave-api", + "substratee-worker-primitives", + "tokio 1.7.1", +] + [[package]] name = "subtle" version = "1.0.0" @@ -5607,7 +5940,6 @@ dependencies = [ "futures-core", "memchr 2.4.0", "pin-project-lite 0.1.12", - "tokio-macros", ] [[package]] @@ -5621,15 +5953,20 @@ dependencies = [ "libc", "memchr 2.4.0", "mio 0.7.13", + "num_cpus", + "once_cell 1.8.0", + "parking_lot 0.11.1", "pin-project-lite 0.2.6", + "signal-hook-registry", + "tokio-macros", "winapi 0.3.9", ] [[package]] name = "tokio-macros" -version = "0.2.6" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -5646,6 +5983,28 @@ dependencies = [ "tokio 1.7.1", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio 1.7.1", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-stream" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066" +dependencies = [ + "futures-core", + "pin-project-lite 0.2.6", + "tokio 1.7.1", +] + [[package]] name = "tokio-util" version = "0.3.1" @@ -5668,6 +6027,7 @@ checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" dependencies = [ "bytes 1.0.1", "futures-core", + "futures-io", "futures-sink", "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project-lite 0.2.6", @@ -5810,7 +6170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59" dependencies = [ "cfg-if 0.1.10", - "rand 0.6.5", + "rand 0.3.23", "static_assertions", ] @@ -6162,6 +6522,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring 0.16.20", + "untrusted", +] + [[package]] name = "wepoll-ffi" version = "0.1.2" @@ -6267,7 +6637,7 @@ dependencies = [ "mio-extras", "openssl", "rand 0.7.3", - "sha-1", + "sha-1 0.8.2", "slab", "url 2.2.2", ] diff --git a/Cargo.toml b/Cargo.toml index 1f1aa586cf..d05443bcd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,17 @@ [workspace] +resolver = "2" members = [ "client", "primitives/api-client-extensions", - "primitives/settings", + "primitives/enclave-api", + "primitives/enclave-api/ffi", "primitives/node", + "primitives/settings", "primitives/worker", "stf", "worker", - "worker/worker-api", + "worker/rpc/client", + "worker/rpc/server", # "enclave", ] diff --git a/client/Cargo.toml b/client/Cargo.toml index 8b64ce09c8..418bdc68c0 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -80,7 +80,7 @@ path = "../primitives/api-client-extensions" path = "../stf" [dependencies.substratee-worker-api] -path = "../worker/worker-api" +path = "../worker/rpc/client" [dependencies.sp-keyring] git = "https://github.com/paritytech/substrate.git" diff --git a/client/src/main.rs b/client/src/main.rs index b9e0cf2f29..3d36348892 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -64,7 +64,7 @@ use substrate_api_client::{ use substrate_client_keystore::{LocalKeystore, KeystoreExt}; use substratee_stf::{ShardIdentifier, TrustedCallSigned, TrustedOperation}; -use substratee_worker_api::direct_client::DirectApi as DirectWorkerApi; +use substratee_worker_api::direct_client::{DirectClient as DirectWorkerApi, DirectApi}; use substratee_api_client_extensions::SubstrateeRegistryApi; use substratee_worker_primitives::{DirectRequestStatus, RpcRequest, RpcResponse, RpcReturnValue}; @@ -311,7 +311,7 @@ fn main() { println!(" AccountId: {}", enclave.pubkey.to_ss58check()); println!(" MRENCLAVE: {}", enclave.mr_enclave.to_base58()); println!(" RA timestamp: {}", timestamp); - println!(" URL: {}", String::from_utf8(enclave.url).unwrap()); + println!(" URL: {}", enclave.url); } Ok(()) }), diff --git a/enclave/Cargo.lock b/enclave/Cargo.lock index 76446c073f..b195497697 100644 --- a/enclave/Cargo.lock +++ b/enclave/Cargo.lock @@ -973,6 +973,12 @@ dependencies = [ "sgx_tstd", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "jsonrpc-core" version = "16.1.0" @@ -982,7 +988,7 @@ dependencies = [ "log", "serde 1.0.118", "serde_derive", - "serde_json", + "serde_json 1.0.60", "sp-std", ] @@ -1867,12 +1873,23 @@ name = "serde_json" version = "1.0.60" source = "git+https://github.com/mesalock-linux/serde-json-sgx#380893814ad2a057758d825bab798aa117f7362a" dependencies = [ - "itoa", + "itoa 0.4.5", "ryu", "serde 1.0.118", "sgx_tstd", ] +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa 0.4.7", + "ryu", + "serde 1.0.126", +] + [[package]] name = "sgx-externalities" version = "0.3.0" @@ -2639,7 +2656,7 @@ dependencies = [ "rustls", "serde 1.0.118", "serde_derive", - "serde_json", + "serde_json 1.0.60", "sgx-externalities", "sgx_rand", "sgx_serialize", @@ -2677,6 +2694,7 @@ dependencies = [ "chrono 0.4.19", "parity-scale-codec", "primitive-types", + "serde_json 1.0.64", "sgx_tstd", "sp-core", "sp-runtime", diff --git a/enclave/Enclave.edl b/enclave/Enclave.edl index c5a04eb671..16395ec999 100644 --- a/enclave/Enclave.edl +++ b/enclave/Enclave.edl @@ -67,6 +67,13 @@ enclave { [out, size=unchecked_extrinsic_size] uint8_t* unchecked_extrinsic, uint32_t unchecked_extrinsic_size ); + public sgx_status_t mock_register_enclave_xt( + [in, size=genesis_hash_size] uint8_t* genesis_hash, uint32_t genesis_hash_size, + [in] uint32_t* nonce, + [in, size=w_url_size] uint8_t* w_url, uint32_t w_url_size, + [out, size=unchecked_extrinsic_size] uint8_t* unchecked_extrinsic, uint32_t unchecked_extrinsic_size + ); + public sgx_status_t dump_ra_to_disk(); public sgx_status_t run_key_provisioning_server(int fd, sgx_quote_sign_type_t quote_type); diff --git a/enclave/src/lib.rs b/enclave/src/lib.rs index 2d85da4b20..b3cd79e87b 100644 --- a/enclave/src/lib.rs +++ b/enclave/src/lib.rs @@ -37,7 +37,7 @@ use sgx_types::{sgx_epid_group_id_t, sgx_status_t, sgx_target_info_t, SgxResult} use substrate_api_client::compose_extrinsic_offline; use substratee_node_primitives::{CallWorkerFn, ShieldFundsFn}; use substratee_worker_primitives::block::{ - Block as SidechainBlock, SignedBlock as SignedSidechainBlock, StatePayload, + Block as SidechainBlock, SignedBlock as SignedSidechainBlock, }; use substratee_worker_primitives::BlockHash; @@ -56,7 +56,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::untrusted::time::SystemTimeEx; use utils::write_slice_and_whitespace_pad; -use crate::utils::UnwrapOrSgxErrorUnexpected; +use crate::utils::{UnwrapOrSgxErrorUnexpected, hash_from_slice}; use chain_relay::{ storage_proof::{StorageProof, StorageProofChecker}, Block, Header, LightValidation, @@ -69,7 +69,7 @@ use sgx_externalities::SgxExternalitiesTypeTrait; use substratee_stf::sgx::{shards_key_hash, storage_hashes_to_update_per_shard, OpaqueCall}; use substratee_stf::State as StfState; use substratee_stf::{ - AccountId, Getter, ShardIdentifier, Stf, TrustedCall, TrustedCallSigned, TrustedGetterSigned, + AccountId, Getter, ShardIdentifier, Stf, TrustedCall, TrustedCallSigned, TrustedGetterSigned, StatePayload }; use rpc::author::{hash::TrustedOperationOrHash, Author, AuthorApi}; @@ -92,8 +92,9 @@ pub mod tests; pub mod tls_ra; pub mod top_pool; -use substratee_settings::node::{BLOCK_CONFIRMED, CALL_CONFIRMED, RUNTIME_SPEC_VERSION, RUNTIME_TRANSACTION_VERSION, SUBSTRATEE_REGISTRY_MODULE, CALL_WORKER, SHIELD_FUNDS}; +use substratee_settings::node::{BLOCK_CONFIRMED, CALL_CONFIRMED, RUNTIME_SPEC_VERSION, RUNTIME_TRANSACTION_VERSION, SUBSTRATEE_REGISTRY_MODULE, CALL_WORKER, SHIELD_FUNDS, REGISTER_ENCLAVE}; use substratee_settings::enclave::{CALL_TIMEOUT, GETTER_TIMEOUT}; +use codec::alloc::string::String; pub const CERTEXPIRYDAYS: i64 = 90i64; @@ -190,6 +191,44 @@ pub unsafe extern "C" fn get_ecc_signing_pubkey(pubkey: *mut u8, pubkey_size: u3 sgx_status_t::SGX_SUCCESS } + +#[no_mangle] +pub unsafe extern "C" fn mock_register_enclave_xt( + genesis_hash: *const u8, + genesis_hash_size: u32, + nonce: *const u32, + w_url: *const u8, + w_url_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, +) -> sgx_status_t { + let genesis_hash_slice = slice::from_raw_parts(genesis_hash, genesis_hash_size as usize); + let genesis_hash = hash_from_slice(genesis_hash_slice); + + let mut url_slice = slice::from_raw_parts(w_url, w_url_size as usize); + let url: String = Decode::decode(&mut url_slice).unwrap(); + let extrinsic_slice = + slice::from_raw_parts_mut(unchecked_extrinsic, unchecked_extrinsic_size as usize); + + let signer = ed25519::unseal_pair().unwrap(); + + let call = [SUBSTRATEE_REGISTRY_MODULE, REGISTER_ENCLAVE]; + + let xt = compose_extrinsic_offline!( + signer, + (call, Vec::::new(), url.clone()), + *nonce, + Era::Immortal, + genesis_hash, + genesis_hash, + RUNTIME_SPEC_VERSION, + RUNTIME_TRANSACTION_VERSION + ).encode(); + + write_slice_and_whitespace_pad(extrinsic_slice, xt); + sgx_status_t::SGX_SUCCESS +} + fn create_extrinsics( validator: LightValidation, calls_buffer: Vec, diff --git a/enclave/src/rpc/worker_api_direct.rs b/enclave/src/rpc/worker_api_direct.rs index 79a2246db1..4cad4a48e6 100644 --- a/enclave/src/rpc/worker_api_direct.rs +++ b/enclave/src/rpc/worker_api_direct.rs @@ -57,7 +57,7 @@ use chain_relay::Block; use substratee_node_primitives::Request; use substratee_worker_primitives::RpcReturnValue; -use substratee_worker_primitives::{DirectRequestStatus, TrustedOperationStatus}; +use substratee_worker_primitives::{DirectRequestStatus, TrustedOperationStatus, block::SignedBlock}; use crate::rsa3072; use crate::utils::write_slice_and_whitespace_pad; @@ -357,6 +357,24 @@ fn init_io_handler() -> IoHandler { Ok(Value::String(format!("hello, {}", parsed))) }); + let sidechain_import_import_name: &str = "sidechain_importBlock"; + rpc_methods_vec.push(sidechain_import_import_name); + io.add_sync_method(sidechain_import_import_name, |sidechain_blocks: Params| { + // Todo: actually do something with the block, i.e., apply the state diff. + // However, this requires importing the stf here. So, before doing that and increase + // the size of init_io_handler even more, we should think about how we can do that more + // modularized. + debug!("sidechain_importBlock rpc. Params: {:?}", sidechain_blocks); + let block_vec: Vec = sidechain_blocks.parse()?; + let blocks: Vec = Decode::decode(&mut block_vec.as_slice()) + .map_err(|_| jsonrpc_core::error::Error::invalid_params_with_details( + "Could not decode Vec", + block_vec + ))?; + info!("sidechain_importBlock. Blocks: {:?}", blocks); + Ok(Value::String("ok".to_owned())) + }); + // returns all rpcs methods let rpc_methods_string: String = convert_vec_to_string(rpc_methods_vec); io.add_sync_method("rpc_methods", move |_: Params| { @@ -449,3 +467,42 @@ pub fn send_state(hash: H, value_opt: Option>) -> Result<(), Ok(()) } + +pub mod tests { + use super::init_io_handler; + use super::alloc::string::ToString; + use std::string::String; + + fn rpc_response(result: T) -> String { + format!(r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, result.to_string()) + } + + pub fn sidechain_import_block_is_ok() { + let io = init_io_handler(); + let enclave_req = r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params":[4,0,0,0,0,0,0,0,0,228,0,145,188,97,251,138,131,108,29,6,107,10,152,67,29,148,190,114,167,223,169,197,163,93,228,76,169,171,80,15,209,101,11,211,96,0,0,0,0,83,52,167,255,37,229,185,231,38,66,122,3,55,139,5,190,125,85,94,177,190,99,22,149,92,97,154,30,142,89,24,144,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,136,220,52,23,213,5,142,196,180,80,62,12,18,234,26,10,137,190,32,15,233,137,34,66,61,67,52,1,79,166,176,238,0,0,0,175,124,84,84,32,238,162,224,130,203,26,66,7,121,44,59,196,200,100,31,173,226,165,106,187,135,223,149,30,46,191,95,116,203,205,102,100,85,82,74,158,197,166,218,181,130,119,127,162,134,227,129,118,85,123,76,21,113,90,1,160,77,110,15],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + assert_eq!(response_string, rpc_response("\"ok\"")); + } + + pub fn sidechain_import_block_returns_invalid_param_err() { + let io = init_io_handler(); + let enclave_req = r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params":["SophisticatedInvalidParam"],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + let err_msg = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params: invalid type: string \"SophisticatedInvalidParam\", expected u8."},"id":1}"#; + assert_eq!(response_string, err_msg); + } + + pub fn sidechain_import_block_returns_decode_err() { + let io = init_io_handler(); + let enclave_req = r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params":[2],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + let err_msg = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid parameters: Could not decode Vec","data":"[2]"},"id":1}"#; + assert_eq!(response_string, err_msg); + } +} \ No newline at end of file diff --git a/enclave/src/tests.rs b/enclave/src/tests.rs index 8951c44ab3..7145ff57bc 100644 --- a/enclave/src/tests.rs +++ b/enclave/src/tests.rs @@ -29,7 +29,6 @@ use sgx_tunittest::*; use sgx_types::{sgx_status_t, size_t}; use substrate_api_client::utils::storage_key; -use substratee_worker_primitives::block::StatePayload; use codec::{Decode, Encode}; use sp_core::{crypto::Pair, hashing::blake2_256, H256}; @@ -49,9 +48,9 @@ use chain_relay::{Block, Header}; use sp_runtime::traits::Header as HeaderT; use sgx_externalities::SgxExternalitiesTypeTrait; -use substratee_stf::sgx::AccountInfo; +use substratee_stf::AccountInfo; use substratee_stf::StateTypeDiff as StfStateTypeDiff; -use substratee_stf::{ShardIdentifier, Stf, TrustedCall}; +use substratee_stf::{ShardIdentifier, Stf, TrustedCall, StatePayload}; use substratee_stf::{TrustedGetter, TrustedOperation}; use substratee_settings::enclave::{GETTER_TIMEOUT}; @@ -115,6 +114,12 @@ pub extern "C" fn test_main_entrance() -> size_t { test_executing_call_updates_account_nonce, test_invalid_nonce_call_is_not_executed, test_non_root_shielding_call_is_not_executed, + substratee_stf::sgx::tests::apply_state_diff_works, + substratee_stf::sgx::tests::apply_state_diff_returns_storage_hash_mismatch_err, + substratee_stf::sgx::tests::apply_state_diff_returns_invalid_storage_diff_err, + rpc::worker_api_direct::tests::sidechain_import_block_is_ok, + rpc::worker_api_direct::tests::sidechain_import_block_returns_invalid_param_err, + rpc::worker_api_direct::tests::sidechain_import_block_returns_decode_err, // these unit tests (?) need an ipfs node running.. //ipfs::test_creates_ipfs_content_struct_works, //ipfs::test_verification_ok_for_correct_content, diff --git a/local-setup/launch.py b/local-setup/launch.py index 28e563596e..1c7d869ec8 100755 --- a/local-setup/launch.py +++ b/local-setup/launch.py @@ -4,6 +4,7 @@ """ import signal from subprocess import Popen, STDOUT +from time import sleep from typing import Union, IO from py.worker import Worker @@ -32,17 +33,19 @@ def setup_worker(work_dir: str, std_err: Union[None, int, IO]): def main(processes): print('Starting substraTee-node-process in background') processes.append( - Popen([node_bin, '--tmp', '--dev', '-lruntime=debug'], stdout=node_log, stderr=STDOUT, bufsize=1) + Popen([node_bin, '--tmp', '--dev', '-lruntime=info'], stdout=node_log, stderr=STDOUT, bufsize=1) ) w1 = setup_worker(w1_working_dir, worker1_log) w2 = setup_worker(w2_working_dir, worker2_log) print('Starting worker 1 in background') - processes.append(w1.run_in_background(log_file=worker1_log, flags=['-P', '2001'], subcommand_flags=['--skip-ra'])) - print('Starting worker 2 in background') - processes.append(w2.run_in_background(log_file=worker2_log, subcommand_flags=['--skip-ra'])) + processes.append(w1.run_in_background(log_file=worker1_log, flags=['-P', '2000'], subcommand_flags=['--skip-ra'])) + # sleep to prevent nonce clash when bootstrapping the enclave's account + sleep(6) + print('Starting worker 2 in background') + processes.append(w2.run_in_background(log_file=worker2_log, flags=['-P', '3000'], subcommand_flags=['--skip-ra'])) # keep script alive until terminated signal.pause() diff --git a/local-setup/py/__pycache__/__init__.cpython-36.pyc b/local-setup/py/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000..e7e03581cf Binary files /dev/null and b/local-setup/py/__pycache__/__init__.cpython-36.pyc differ diff --git a/local-setup/py/__pycache__/helpers.cpython-36.pyc b/local-setup/py/__pycache__/helpers.cpython-36.pyc new file mode 100644 index 0000000000..2fa744dd81 Binary files /dev/null and b/local-setup/py/__pycache__/helpers.cpython-36.pyc differ diff --git a/local-setup/py/__pycache__/worker.cpython-36.pyc b/local-setup/py/__pycache__/worker.cpython-36.pyc new file mode 100644 index 0000000000..f0d2ca812d Binary files /dev/null and b/local-setup/py/__pycache__/worker.cpython-36.pyc differ diff --git a/local-setup/py/worker.py b/local-setup/py/worker.py index 1cbe92fc90..9729f4cd6c 100644 --- a/local-setup/py/worker.py +++ b/local-setup/py/worker.py @@ -1,3 +1,4 @@ +import os import pathlib import shutil import subprocess @@ -108,16 +109,18 @@ def purge_shard(self, shard=None): if not shard: shard = self.mrenclave() + print(f'Purging shard: {shard}') + if not self.shard_exists(shard): print('The shard to be purged does not exist.') else: shutil.rmtree(self._shard_path(shard)) - @staticmethod - def purge_chain_relay_db(): - db = pathlib.Path('./chain_relay_db.bin') - if db.exists(): - db.unlink() + def purge_chain_relay_db(self): + print(f'purging chain_relay_db') + for db in pathlib.Path(self.cwd).glob('chain_relay_db.bin*'): + print(f'remove: {db}') + db.unlink() def mrenclave(self): """ Returns the mrenclave and caches it. """ @@ -140,8 +143,13 @@ def run_in_background(self, log_file: TextIO, flags: [str] = None, subcommand_fl :return: process handle for the spawned background process. """ + + # Todo: make this configurable + env = dict(os.environ, RUST_LOG='debug,ws=warn,sp_io=warn,substrate_api_client=info') + return Popen( self._assemble_cmd(flags=flags, subcommand_flags=subcommand_flags), + env=env, stdout=log_file, stderr=STDOUT, bufsize=1, diff --git a/primitives/enclave-api/Cargo.toml b/primitives/enclave-api/Cargo.toml new file mode 100644 index 0000000000..c69a2aa752 --- /dev/null +++ b/primitives/enclave-api/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "substratee-enclave-api" +version = "0.8.0" +authors = ["clangenbacher "] +edition = "2018" + +[dependencies] +thiserror = "1.0.25" +codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } + +sgx_types = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +frame-support = { version = "3.0.0", git = "https://github.com/paritytech/substrate.git", branch = "master" } + +substratee-enclave-api-ffi = { path = "ffi" } diff --git a/primitives/enclave-api/ffi/Cargo.toml b/primitives/enclave-api/ffi/Cargo.toml new file mode 100644 index 0000000000..808504da5a --- /dev/null +++ b/primitives/enclave-api/ffi/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "substratee-enclave-api-ffi" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sgx_types = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git" } \ No newline at end of file diff --git a/primitives/enclave-api/ffi/src/lib.rs b/primitives/enclave-api/ffi/src/lib.rs new file mode 100644 index 0000000000..93dc5e3bb0 --- /dev/null +++ b/primitives/enclave-api/ffi/src/lib.rs @@ -0,0 +1,27 @@ +///! FFI's that call into the enclave. These functions need to be added to the +/// enclave edl file and be implemented within the enclave. + +use sgx_types::{sgx_enclave_id_t, sgx_status_t}; + +extern "C" { + pub fn call_rpc_methods( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + request: *const u8, + request_len: u32, + response: *mut u8, + response_len: u32, + ) -> sgx_status_t; + + pub fn mock_register_enclave_xt( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + genesis_hash: *const u8, + genesis_hash_size: u32, + nonce: *const u32, + w_url: *const u8, + w_url_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, + ) -> sgx_status_t; +} \ No newline at end of file diff --git a/primitives/enclave-api/src/error.rs b/primitives/enclave-api/src/error.rs new file mode 100644 index 0000000000..085cbf1ed5 --- /dev/null +++ b/primitives/enclave-api/src/error.rs @@ -0,0 +1,11 @@ +use codec::{Error as CodecError}; +use sgx_types::sgx_status_t; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Codec(#[from] CodecError), + #[error("Enclave Error: {0}")] + Sgx(sgx_status_t) +} + diff --git a/primitives/enclave-api/src/lib.rs b/primitives/enclave-api/src/lib.rs new file mode 100644 index 0000000000..9e0c837465 --- /dev/null +++ b/primitives/enclave-api/src/lib.rs @@ -0,0 +1,109 @@ +//! Some definitions and traits that facilitate interaction with the enclave. +//! +//! This serves as a proof of concept on how we could design the interface between the worker and +//! the enclave. +//! +//! Design principle here should be to keep the traits as slim as possible - because then the +//! worker can also define slim interfaces with less demanding trait bounds. +//! +//! This can further be simplified once https://github.com/integritee-network/worker/issues/254 +//! is implemented. Then we can replace the several ffi:: and the boilerplate code +//! around it with a simple `fn ecall(call: CallEnum) -> Result`, which wraps one single +//! ffi function. +//! + +use sgx_types::{sgx_enclave_id_t, sgx_status_t}; +use frame_support::ensure; + +use crate::error::Error; +use codec::Encode; +use frame_support::sp_runtime::app_crypto::sp_core::H256; + +use substratee_enclave_api_ffi as ffi; + +pub mod error; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Enclave { + eid: sgx_enclave_id_t +} + +impl Enclave { + pub fn new(eid: sgx_enclave_id_t) -> Self { + Enclave { eid } + } +} + +pub type EnclaveResult = Result; + +pub trait EnclaveApi: Send + Sync + 'static { + // Todo: Vec shall be replaced by D: Decode, E: Encode but this is currently + // not compatible with the direct_api_server... + fn rpc(&self, request: Vec) -> EnclaveResult>; +} + + +pub trait TeeRexApi : Send + Sync + 'static { + /// Register enclave xt with an empty attestation report. + fn mock_register_xt(&self, genesis_hash: H256, nonce: u32, w_url: &str) -> EnclaveResult>; +} + +impl EnclaveApi for Enclave { + fn rpc(&self, request: Vec) -> EnclaveResult> { + let mut retval = sgx_status_t::SGX_SUCCESS; + let response_len = 8192; + let mut response: Vec = vec![0u8; response_len as usize]; + + let res = unsafe { + ffi::call_rpc_methods( + self.eid, + &mut retval, + request.as_ptr(), + request.len() as u32, + response.as_mut_ptr(), + response_len + ) + }; + + ensure!(res == sgx_status_t::SGX_SUCCESS, Error::Sgx(res)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(response) + } +} + +impl TeeRexApi for Enclave { + fn mock_register_xt( + &self, + genesis_hash: H256, + nonce: u32, + w_url: &str, + ) -> EnclaveResult> { + let mut retval = sgx_status_t::SGX_SUCCESS; + let response_len = 8192; + let mut response: Vec = vec![0u8; response_len as usize]; + + let url = w_url.encode(); + let gen = genesis_hash.as_bytes().to_vec(); + + let res = unsafe { + ffi::mock_register_enclave_xt( + self.eid, + &mut retval, + gen.as_ptr(), + gen.len() as u32, + &nonce, + url.as_ptr(), + url.len() as u32, + response.as_mut_ptr(), + response_len + ) + }; + + ensure!(res == sgx_status_t::SGX_SUCCESS, Error::Sgx(res)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(response) + } +} + diff --git a/primitives/node/src/lib.rs b/primitives/node/src/lib.rs index e060e4e3be..2880668c2c 100644 --- a/primitives/node/src/lib.rs +++ b/primitives/node/src/lib.rs @@ -7,11 +7,17 @@ use sgx_tstd as std; use sp_core::H256; use std::vec::Vec; +/// Substrate runtimes provide no string type. Hence, for arbitrary data of varying length the +/// `Vec` is used. In the polkadot-js the typedef `Text` is used to automatically +/// utf8 decode bytes into a string. +#[cfg(not(feature = "std"))] +pub type PalletString = Vec; + #[cfg(feature = "std")] -pub type SignedBlock = sp_runtime::generic::SignedBlock; +pub type PalletString = String; #[cfg(feature = "std")] -pub use my_node_runtime::substratee_registry::Enclave as EnclaveGen; +pub type SignedBlock = sp_runtime::generic::SignedBlock; pub use sp_core::crypto::AccountId32 as AccountId; @@ -25,10 +31,32 @@ pub struct Request { pub cyphertext: Vec, } -#[cfg(feature = "std")] -pub type Enclave = EnclaveGen>; pub type IpfsHash = [u8; 46]; pub type SubstrateeConfirmCallFn = ([u8; 2], ShardIdentifier, H256, Vec); pub type ShieldFundsFn = ([u8; 2], Vec, u128, ShardIdentifier); pub type CallWorkerFn = ([u8; 2], Request); + +// Todo: move this improved enclave definition into a primitives crate in the substratee-registry repo. +#[derive(Encode, Decode, Default, Clone, PartialEq, sp_core::RuntimeDebug)] +pub struct EnclaveGen { + pub pubkey: AccountId, + // FIXME: this is redundant information + pub mr_enclave: [u8; 32], + pub timestamp: u64, + // unix epoch in milliseconds + pub url: PalletString, // utf8 encoded url +} + +impl EnclaveGen { + pub fn new(pubkey: AccountId, mr_enclave: [u8; 32], timestamp: u64, url: PalletString) -> Self { + Self { + pubkey, + mr_enclave, + timestamp, + url, + } + } +} + +pub type Enclave = EnclaveGen; diff --git a/primitives/worker/Cargo.toml b/primitives/worker/Cargo.toml index 5ef04f47f4..24f2283ebe 100644 --- a/primitives/worker/Cargo.toml +++ b/primitives/worker/Cargo.toml @@ -5,11 +5,11 @@ authors = ["bhaerdi "] edition = "2018" [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive", "full"] } primitive-types = { version = "0.9", default-features = false, features = ["codec"] } serde = { version = "1.0", optional = true} serde_derive = { version = "1.0", optional = true} -serde_json = { version = "1.0", optional = true} +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } chrono = { version = "0.4.19", default-features = false, features = ["alloc"]} [dependencies.sgx_tstd] @@ -38,7 +38,7 @@ std = [ 'codec/std', 'chrono/std', 'serde', 'serde_derive', - 'serde_json', + 'serde_json/std', 'sp-runtime/std', 'sp-core/std', 'primitive-types/std' diff --git a/primitives/worker/src/block.rs b/primitives/worker/src/block.rs index 120cabbae4..f07d95fad6 100644 --- a/primitives/worker/src/block.rs +++ b/primitives/worker/src/block.rs @@ -31,38 +31,6 @@ pub struct SignedBlock { signature: Signature, } -/// payload of block that needs to be encrypted -#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct StatePayload { - state_hash_apriori: H256, - state_hash_aposteriori: H256, - /// encoded state update - state_update: Vec, -} - -impl StatePayload { - /// get hash of state before block execution - pub fn state_hash_apriori(&self) -> H256 { - self.state_hash_apriori - } - /// get hash of state after block execution - pub fn state_hash_aposteriori(&self) -> H256 { - self.state_hash_aposteriori - } - /// get encoded state update reference - pub fn state_update(&self) -> &Vec { - &self.state_update - } - pub fn new(apriori: H256, aposteriori: H256, update: Vec) -> StatePayload { - StatePayload { - state_hash_apriori: apriori, - state_hash_aposteriori: aposteriori, - state_update: update, - } - } -} - /// simplified block structure for relay chain submission as an extrinsic #[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -184,26 +152,6 @@ mod tests { use std::thread; use std::time::Duration; - #[test] - fn new_payload_works() { - // given - let state_hash_apriori = H256::random(); - let state_hash_aposteriori = H256::random(); - let state_update: Vec = vec![]; - - // when - let payload = StatePayload::new( - state_hash_apriori.clone(), - state_hash_aposteriori.clone(), - state_update.clone(), - ); - - // then - assert_eq!(state_hash_apriori, payload.state_hash_apriori()); - assert_eq!(state_hash_aposteriori, payload.state_hash_aposteriori()); - assert_eq!(state_update, *payload.state_update()); - } - #[test] fn construct_block_works() { // given diff --git a/primitives/worker/src/lib.rs b/primitives/worker/src/lib.rs index 09d057eefa..05536a1207 100644 --- a/primitives/worker/src/lib.rs +++ b/primitives/worker/src/lib.rs @@ -15,6 +15,8 @@ pub type BlockHash = H256; pub type BlockNumber = u64; pub type ShardIdentifier = H256; +use std::string::String; + //use sp_core::ed25519::Signature; #[derive(Debug, Clone, PartialEq, Encode, Decode)] @@ -73,16 +75,18 @@ impl RpcReturnValue { } } -#[cfg(feature = "std")] -#[derive(Encode, Decode, Serialize, Deserialize)] +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +// Todo: result should not be Vec, but `T: Serialize` pub struct RpcResponse { pub jsonrpc: String, pub result: Vec, // encoded RpcReturnValue pub id: u32, } -#[cfg(feature = "std")] -#[derive(Encode, Decode, Serialize)] +#[derive(Encode, Decode)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +// Todo: params should not be Vec, but `T: Serialize` pub struct RpcRequest { pub jsonrpc: String, pub method: String, diff --git a/stf/Cargo.toml b/stf/Cargo.toml index 50afa765d2..27a51273bc 100644 --- a/stf/Cargo.toml +++ b/stf/Cargo.toml @@ -22,12 +22,13 @@ std = [ "log", "base58", "sc-keystore", + "serde", "system/std", "sp-core/std", "hex", "substrate-api-client", "substrate-client-keystore", - "my-node-runtime" + "my-node-runtime", ] [dependencies] @@ -39,6 +40,7 @@ log = { version = "0.4", optional = true } base58 = { version = "0.1", optional = true } derive_more = { version = "0.99.5", optional = true } hex = { version = "0.4.2", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } [dependencies.sgx_tstd] git = "https://github.com/apache/teaclave-sgx-sdk.git" diff --git a/stf/src/lib.rs b/stf/src/lib.rs index d3509ff5bc..fc05611ce9 100644 --- a/stf/src/lib.rs +++ b/stf/src/lib.rs @@ -20,6 +20,7 @@ #![feature(rustc_attrs)] #![feature(core_intrinsics)] #![feature(derive_eq)] +#![feature(trait_alias)] #![cfg_attr(all(not(target_env = "sgx"), not(feature = "std")), no_std)] #![cfg_attr(target_env = "sgx", feature(rustc_private))] @@ -28,15 +29,14 @@ extern crate alloc; #[cfg(feature = "std")] extern crate clap; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + use codec::{Compact, Decode, Encode}; #[cfg(feature = "std")] use my_node_runtime::Balance; #[cfg(feature = "std")] pub use my_node_runtime::Index; -#[cfg(feature = "sgx")] -use sgx_runtime::Balance; -#[cfg(feature = "sgx")] -pub use sgx_runtime::Index; use sp_core::crypto::AccountId32; //use sp_core::{Encode, Decode}; @@ -91,17 +91,12 @@ impl From for KeyPair { #[cfg(feature = "sgx")] pub mod sgx; +#[cfg(feature = "sgx")] +pub use sgx::types::*; + #[cfg(feature = "std")] pub mod cli; -#[cfg(feature = "sgx")] -//pub type State = sp_io::SgxExternalitiesType; -pub type StateType = sgx_externalities::SgxExternalitiesType; -#[cfg(feature = "sgx")] -pub type State = sgx_externalities::SgxExternalities; -#[cfg(feature = "sgx")] -pub type StateTypeDiff = sgx_externalities::SgxExternalitiesDiffType; - #[derive(Encode, Decode, Clone, core::fmt::Debug)] #[allow(non_camel_case_types)] pub enum TrustedOperation { @@ -284,8 +279,44 @@ pub struct TrustedReturnValue { impl TrustedReturnValue */ + +#[cfg(feature = "sgx")] +use sgx_tstd as std; + #[cfg(feature = "sgx")] -pub struct Stf {} +use std::vec::Vec; + +/// payload of block that needs to be encrypted +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct StatePayload { + state_hash_apriori: H256, + state_hash_aposteriori: H256, + /// encoded state update + state_update: Vec, +} + +impl StatePayload { + /// get hash of state before block execution + pub fn state_hash_apriori(&self) -> H256 { + self.state_hash_apriori + } + /// get hash of state after block execution + pub fn state_hash_aposteriori(&self) -> H256 { + self.state_hash_aposteriori + } + /// get encoded state update reference + pub fn state_update(&self) -> &Vec { + &self.state_update + } + pub fn new(apriori: H256, aposteriori: H256, update: Vec) -> StatePayload { + StatePayload { + state_hash_apriori: apriori, + state_hash_aposteriori: aposteriori, + state_update: update, + } + } +} #[cfg(test)] mod tests { @@ -313,4 +344,24 @@ mod tests { assert!(signed_call.verify_signature(&mrenclave, &shard)); } + + #[test] + fn new_payload_works() { + // given + let state_hash_apriori = H256::random(); + let state_hash_aposteriori = H256::random(); + let state_update: Vec = vec![]; + + // when + let payload = StatePayload::new( + state_hash_apriori.clone(), + state_hash_aposteriori.clone(), + state_update.clone(), + ); + + // then + assert_eq!(state_hash_apriori, payload.state_hash_apriori()); + assert_eq!(state_hash_aposteriori, payload.state_hash_aposteriori()); + assert_eq!(state_update, *payload.state_update()); + } } diff --git a/stf/src/sgx.rs b/stf/src/sgx.rs index 63f50158c4..455ca0cca0 100644 --- a/stf/src/sgx.rs +++ b/stf/src/sgx.rs @@ -8,17 +8,19 @@ use derive_more::Display; use log_sgx::*; use sgx_runtime::{Balance, BlockNumber as L1BlockNumer, Runtime}; use sp_core::crypto::AccountId32; -use sp_core::Pair; -use sp_core::H256 as Hash; -use sp_io::hashing::blake2_256; -use sp_io::SgxExternalitiesTrait; +use sp_core::{Pair, H256 as Hash}; +use sp_io::{hashing::blake2_256, SgxExternalitiesTrait}; +use sgx_externalities::SgxExternalitiesTypeTrait; use sp_runtime::MultiAddress; use substratee_worker_primitives::BlockNumber; -use support::metadata::StorageHasher; -use support::traits::UnfilteredDispatchable; +use support::{ + ensure, + metadata::StorageHasher, + traits::UnfilteredDispatchable +}; use crate::{ - AccountId, Getter, Index, PublicGetter, ShardIdentifier, State, Stf, TrustedCall, + AccountId, Getter, Index, PublicGetter, ShardIdentifier, TrustedCall, StatePayload, TrustedCallSigned, TrustedGetter, SUBSRATEE_REGISTRY_MODULE, UNSHIELD, }; @@ -32,8 +34,25 @@ impl Encode for OpaqueCall { } } -pub type AccountData = balances::AccountData; -pub type AccountInfo = system::AccountInfo; +pub trait StfTrait = SgxExternalitiesTrait + StateHash + Clone + Send + Sync; + +pub trait StateHash { + fn hash(&self) -> Hash; +} + +pub mod types { + pub use sgx_runtime::{Balance, Index}; + pub type AccountData = balances::AccountData; + pub type AccountInfo = system::AccountInfo; + + pub type StateType = sgx_externalities::SgxExternalitiesType; + pub type State = sgx_externalities::SgxExternalities; + pub type StateTypeDiff = sgx_externalities::SgxExternalitiesDiffType; + pub struct Stf; +} + +use types::*; + const ALICE_ENCODED: [u8; 32] = [ 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, @@ -120,7 +139,7 @@ impl Stf { ext } - pub fn update_storage(ext: &mut State, map_update: &HashMap, Option>>) { + pub fn update_storage(ext: &mut impl SgxExternalitiesTrait, map_update: &HashMap, Option>>) { ext.execute_with(|| { map_update.iter().for_each(|(k, v)| { match v { @@ -207,7 +226,7 @@ impl Stf { ext: &mut State, call: TrustedCallSigned, calls: &mut Vec, - ) -> Result<(), StfError> { + ) -> StfResult<()> { let call_hash = blake2_256(&call.encode()); ext.execute_with(|| { let sender = call.call.account().clone(); @@ -353,7 +372,7 @@ impl Stf { }) } - fn ensure_root(account: AccountId) -> Result<(), StfError> { + fn ensure_root(account: AccountId) -> StfResult<()> { if sp_io::storage::get(&storage_value_key("Sudo", "Key")).unwrap() == account.encode() { Ok(()) } else { @@ -361,7 +380,7 @@ impl Stf { } } - fn shield_funds(account: AccountId, amount: u128) -> Result<(), StfError> { + fn shield_funds(account: AccountId, amount: u128) -> StfResult<()> { match get_account_info(&account) { Some(account_info) => sgx_runtime::BalancesCall::::set_balance( MultiAddress::Id(account), @@ -381,7 +400,7 @@ impl Stf { Ok(()) } - fn unshield_funds(account: AccountId, amount: u128) -> Result<(), StfError> { + fn unshield_funds(account: AccountId, amount: u128) -> StfResult<()> { match get_account_info(&account) { Some(account_info) => { if account_info.data.free < amount { @@ -412,6 +431,17 @@ impl Stf { key_hashes } + pub fn apply_state_diff(ext: &mut impl StfTrait, state_payload: &mut StatePayload) -> StfResult<()> { + // Todo: how do we ensure that the apriori state hash matches? + ensure!(ext.hash() == state_payload.state_hash_apriori(), StfError::StorageHashMismatch); + let mut ext2 = ext.clone(); + Self::update_storage(&mut ext2, &StateTypeDiff::decode(state_payload.state_update.clone())); + ensure!(ext2.hash() == state_payload.state_hash_aposteriori(), StfError::InvalidStorageDiff); + *ext = ext2; + ext.prune_state_diff(); + Ok(()) + } + pub fn get_storage_hashes_to_update_for_getter(getter: &Getter) -> Vec> { debug!( "No specific storage updates needed for getter. Returning those for on block: {:?}", @@ -466,7 +496,7 @@ fn get_account_info(who: &AccountId) -> Option { } } -fn validate_nonce(who: &AccountId, nonce: Index) -> Result<(), StfError> { +fn validate_nonce(who: &AccountId, nonce: Index) -> StfResult<()> { // validate let expected_nonce = get_account_info(who).map_or_else(|| 0, |acc| acc.nonce); if expected_nonce == nonce { @@ -548,11 +578,13 @@ fn key_hash(key: &K, hasher: &StorageHasher) -> Vec { } } -#[derive(Debug, Display)] +pub type StfResult = Result; + +#[derive(Debug, Display, PartialEq, Eq)] pub enum StfError { #[display(fmt = "Insufficient privileges {:?}, are you sure you are root?", _0)] MissingPrivileges(AccountId), - #[display(fmt = "Error dispatching runtime call")] + #[display(fmt = "Error dispatching runtime call. {:?}", _0)] Dispatch(String), #[display(fmt = "Not enough funds to perform operation")] MissingFunds, @@ -560,4 +592,71 @@ pub enum StfError { InexistentAccount(AccountId), #[display(fmt = "Invalid Nonce {:?}", _0)] InvalidNonce(Index), + StorageHashMismatch, + InvalidStorageDiff, +} + +// this must be pub to be able to test it in the enclave. In the future this should be testable +// with cargo test. See: https://github.com/scs/substraTEE-worker/issues/272. +pub mod tests { + use super::{Stf, StfTrait, StateHash, State, StatePayload}; + use support::{assert_ok, assert_err}; + use sp_core::H256; + use sp_runtime::traits::{BlakeTwo256, Hash}; + use sgx_externalities::{SgxExternalitiesTypeTrait}; + use crate::sgx::StfError; + + impl StateHash for State { + fn hash(&self) -> H256 { + BlakeTwo256::hash(self.state.clone().encode().as_slice()) + } + } + + pub fn apply_state_diff_works() { + let mut state1 = State::new(); + let mut state2 = State::new(); + + let apriori = state1.hash(); + state1.insert(b"Hello".to_vec(), b"World".to_vec()); + let aposteriori = state1.hash(); + + let mut state_update = StatePayload::new(apriori, aposteriori, state1.state_diff.clone().encode()); + + assert_ok!(Stf::apply_state_diff(&mut state2, &mut state_update)); + assert_eq!(state2.hash(), aposteriori); + assert_eq!(*state2.get(b"Hello").unwrap(), b"World".to_vec()); + assert!(state2.state_diff.is_empty()); + } + + pub fn apply_state_diff_returns_storage_hash_mismatch_err() { + let mut state1 = State::new(); + let mut state2 = State::new(); + + let apriori = H256::from([1; 32]); + state1.insert(b"Hello".to_vec(), b"World".to_vec()); + let aposteriori = state1.hash(); + + let mut state_update = StatePayload::new(apriori, aposteriori, state1.state_diff.clone().encode()); + + assert_err!(Stf::apply_state_diff(&mut state2, &mut state_update), StfError::StorageHashMismatch); + // todo: Derive `Eq` on State + assert_eq!(state2.hash(), State::new().hash()); + assert!(state2.state_diff.is_empty()); + } + + pub fn apply_state_diff_returns_invalid_storage_diff_err() { + let mut state1 = State::new(); + let mut state2 = State::new(); + + let apriori = state1.hash(); + state1.insert(b"Hello".to_vec(), b"World".to_vec()); + let aposteriori = H256::from([1; 32]); + + let mut state_update = StatePayload::new(apriori, aposteriori, state1.state_diff.clone().encode()); + + assert_err!(Stf::apply_state_diff(&mut state2, &mut state_update), StfError::InvalidStorageDiff); + // todo: Derive `Eq` on State + assert_eq!(state2.hash(), State::new().hash()); + assert!(state2.state_diff.is_empty()); + } } diff --git a/worker/Cargo.toml b/worker/Cargo.toml index 610e58f4ab..264d28d192 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -14,16 +14,20 @@ base58 = "0.1" rust-crypto = "0.2" clap = { version = "2.33", features = [ "yaml" ] } lazy_static = "1.4.0" +parking_lot = "0.11.1" +thiserror = "1.0" dirs = "1.0.2" serde = "1.0" serde_json = "1.0" serde_derive = "1.0" +jsonrpsee = { version = "0.2.0", features = ["client", "ws-server", "macros"]} +async-trait = "0.1.50" +tokio = { version = "1.6.1", features = ["full"] } # ipfs ipfs-api = "0.11.0" futures = "0.3" -tokio = { version = "0.2", features = ["macros"] } multihash = "0.8" cid = "<0.3.1" sha2 = { version = "0.7", default-features = false } @@ -51,7 +55,13 @@ path = "../primitives/worker" path = "../primitives/api-client-extensions" [dependencies.substratee-worker-api] -path = "worker-api" +path = "rpc/client" + +[dependencies.substratee-worker-rpc-server] +path = "rpc/server" + +[dependencies.substratee-enclave-api] +path = "../primitives/enclave-api" [dependencies.my-node-runtime] git = "https://github.com/scs/substraTEE-node" @@ -94,3 +104,7 @@ path = "../stf" [features] default = [] production = ['substratee-settings/production'] + +[dev-dependencies] +anyhow = "1.0.40" +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" } \ No newline at end of file diff --git a/worker/worker-api/Cargo.toml b/worker/rpc/client/Cargo.toml similarity index 93% rename from worker/worker-api/Cargo.toml rename to worker/rpc/client/Cargo.toml index 34b0eb51dd..3747f48099 100644 --- a/worker/worker-api/Cargo.toml +++ b/worker/rpc/client/Cargo.toml @@ -13,4 +13,4 @@ sgx_crypto_helper = { rev = "v1.1.3", git = "https://github.com/apache/teaclave- codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } [dependencies.substratee-worker-primitives] -path = "../../primitives/worker" \ No newline at end of file +path = "../../../primitives/worker" \ No newline at end of file diff --git a/worker/worker-api/src/direct_client.rs b/worker/rpc/client/src/direct_client.rs similarity index 85% rename from worker/worker-api/src/direct_client.rs rename to worker/rpc/client/src/direct_client.rs index 5cfae54447..098bf88e6c 100644 --- a/worker/worker-api/src/direct_client.rs +++ b/worker/rpc/client/src/direct_client.rs @@ -1,3 +1,9 @@ +///! Interface for direct access to a workers rpc. +/// +/// This should be replaced with the `jsonrpsee::WsClient`. It is async an removes a lot of +/// boilerplate code. Example usage in worker/worker.rs. +/// + use log::*; use std::sync::mpsc::channel; use std::sync::mpsc::Sender as MpscSender; @@ -11,14 +17,14 @@ use substratee_worker_primitives::{DirectRequestStatus, RpcRequest, RpcResponse, use sgx_crypto_helper::rsa3072::Rsa3072PubKey; -pub struct DirectWsClient { +pub struct WsClient { pub out: Sender, pub request: String, pub result: MpscSender, pub do_watch: bool, } -impl Handler for DirectWsClient { +impl Handler for WsClient { fn on_open(&mut self, _: Handshake) -> ClientResult<()> { debug!("sending request: {:?}", self.request.clone()); match self.out.send(self.request.clone()) { @@ -38,11 +44,16 @@ impl Handler for DirectWsClient { } #[derive(Clone)] -pub struct DirectApi { +pub struct DirectClient { url: String, } -impl DirectApi { +pub trait DirectApi { + fn watch(&self, request: String, sender: MpscSender) -> Result<(), ()>; + fn get_rsa_pubkey(&self) -> Result; +} + +impl DirectClient { pub fn new(url: String) -> Self { Self { url } } @@ -55,7 +66,7 @@ impl DirectApi { info!("[WorkerApi Direct]: Sending request: {:?}", request); let client = thread::spawn(move || { - match connect(url, |out| DirectWsClient { + match connect(url, |out| WsClient { out, request: request.clone(), result: port_in.clone(), @@ -77,14 +88,17 @@ impl DirectApi { } } } +} + +impl DirectApi for DirectClient { /// server connection with more than one response #[allow(clippy::result_unit_err)] - pub fn watch(&self, request: String, sender: MpscSender) -> Result<(), ()> { + fn watch(&self, request: String, sender: MpscSender) -> Result<(), ()> { let url = self.url.clone(); info!("[WorkerApi Direct]: Sending request: {:?}", request); thread::spawn(move || { - match connect(url, |out| DirectWsClient { + match connect(url, |out| WsClient { out, request: request.clone(), result: sender.clone(), @@ -99,7 +113,7 @@ impl DirectApi { Ok(()) } - pub fn get_rsa_pubkey(&self) -> Result { + fn get_rsa_pubkey(&self) -> Result { // compose jsonrpc call let method = "author_getShieldingKey".to_owned(); let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call(method, vec![]); @@ -144,4 +158,4 @@ impl DirectApi { info!("[+] Got RSA public key of enclave"); Ok(shielding_pubkey) } -} +} \ No newline at end of file diff --git a/worker/worker-api/src/lib.rs b/worker/rpc/client/src/lib.rs similarity index 100% rename from worker/worker-api/src/lib.rs rename to worker/rpc/client/src/lib.rs diff --git a/worker/rpc/server/Cargo.toml b/worker/rpc/server/Cargo.toml new file mode 100644 index 0000000000..650647cec5 --- /dev/null +++ b/worker/rpc/server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "substratee-worker-rpc-server" +version = "0.8.0" +authors = ["Supercomputing Systems AG "] +edition = "2018" +resolver = "2" + +[dependencies] +anyhow = "1.0.40" +log = "0.4.14" +jsonrpsee = { version = "0.2.0-alpha.7", features = ["full"] } +serde_json = "1.0.64" +tokio = { version = "1.6.1", features = ["full"] } +parity-scale-codec = "2.1.3" + +substratee-enclave-api = { path = "../../../primitives/enclave-api" } +substratee-worker-primitives = { path = "../../../primitives/worker" } + +[features] +default = ["std"] +std = [] + +[dev-dependencies] +env_logger = { version = "*" } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" } \ No newline at end of file diff --git a/worker/rpc/server/src/lib.rs b/worker/rpc/server/src/lib.rs new file mode 100644 index 0000000000..88a6e794eb --- /dev/null +++ b/worker/rpc/server/src/lib.rs @@ -0,0 +1,66 @@ +/* + Copyright 2019 Supercomputing Systems AG + + 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. + +*/ + +use std::net::SocketAddr; + +use jsonrpsee::{ + types::error::CallError, + ws_server::{RpcModule, WsServerBuilder}, +}; +use log::debug; +use parity_scale_codec::Encode; +use tokio::net::ToSocketAddrs; + +use substratee_enclave_api::EnclaveApi; +use substratee_worker_primitives::block::SignedBlock; +use substratee_worker_primitives::RpcRequest; + +#[cfg(test)] +mod tests; +#[cfg(test)] +mod mock; + +pub async fn run_server( + addr: impl ToSocketAddrs, + enclave: Enclave, +) -> anyhow::Result +where + Enclave: EnclaveApi, +{ + let mut server = WsServerBuilder::default().build(addr).await?; + + let mut module = RpcModule::new(enclave); + + module.register_method("sidechain_importBlock", |params, enclave| { + debug!("sidechain_importBlock params: {:?}", params); + + let enclave_req = RpcRequest::compose_jsonrpc_call( + "sidechain_importBlock".into(), + params.one::>()?.encode(), + ); + + enclave + .rpc(enclave_req.as_bytes().to_vec()) + .map_err(|e| CallError::Failed(e.into())) + })?; + + server.register_module(module).unwrap(); + + let socket_addr = server.local_addr()?; + tokio::spawn(async move { server.start().await }); + Ok(socket_addr) +} diff --git a/worker/rpc/server/src/mock.rs b/worker/rpc/server/src/mock.rs new file mode 100644 index 0000000000..ce5bbbd732 --- /dev/null +++ b/worker/rpc/server/src/mock.rs @@ -0,0 +1,45 @@ +use parity_scale_codec::Encode; + +use substratee_enclave_api::{EnclaveApi, EnclaveResult}; +use substratee_worker_primitives::block::{SignedBlock, Block}; +use substratee_worker_primitives::{ShardIdentifier, RpcResponse}; +use sp_core::crypto::AccountId32; + +pub struct TestEnclave; + +impl EnclaveApi for TestEnclave { + fn rpc(&self, _request: Vec) -> EnclaveResult> { + Ok(RpcResponse { + jsonrpc: "mock_response".into(), + result: "null".encode(), + id: 1 + }.encode()) + } +} + +// todo: this is a duplicate that is also defined in the worker. We should extract an independent +// test-utils crate because here we don't want to depend on the worker itself. +pub fn test_sidechain_block() -> SignedBlock { + use sp_core::{H256, Pair}; + + let signer_pair = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); + let author: AccountId32 = signer_pair.public().into(); + let block_number: u64 = 0; + let parent_hash = H256::random(); + let layer_one_head = H256::random(); + let signed_top_hashes = vec![]; + let encrypted_payload: Vec = vec![]; + let shard = ShardIdentifier::default(); + + // when + let block = Block::construct_block( + author, + block_number, + parent_hash.clone(), + layer_one_head.clone(), + shard.clone(), + signed_top_hashes.clone(), + encrypted_payload.clone(), + ); + block.sign(&signer_pair) +} \ No newline at end of file diff --git a/worker/rpc/server/src/tests.rs b/worker/rpc/server/src/tests.rs new file mode 100644 index 0000000000..fa1bffdecd --- /dev/null +++ b/worker/rpc/server/src/tests.rs @@ -0,0 +1,34 @@ +use log::info; + +use super::*; +use parity_scale_codec::Decode; +use jsonrpsee::{ + types::{to_json_value, traits::Client}, + ws_client::WsClientBuilder, +}; + +use mock::{test_sidechain_block, TestEnclave}; +use substratee_worker_primitives::RpcResponse; + +fn init() { + let _ = env_logger::builder().is_test(true).try_init(); +} + +#[tokio::test] +async fn test_client_calls() { + init(); + let addr = run_server("127.0.0.1:0", TestEnclave).await.unwrap(); + info!("ServerAddress: {:?}", addr); + + let url = format!("ws://{}", addr); + let client = WsClientBuilder::default().build(&url).await.unwrap(); + let response: Vec = client + .request( + "sidechain_importBlock", + vec![to_json_value(vec![test_sidechain_block()]).unwrap()].into(), + ) + .await + .unwrap(); + + assert!(RpcResponse::decode(&mut response.as_slice()).is_ok()); +} diff --git a/worker/src/config.rs b/worker/src/config.rs index c25673f97d..624b24389f 100644 --- a/worker/src/config.rs +++ b/worker/src/config.rs @@ -14,7 +14,23 @@ pub struct Config { } impl Config { - pub fn node_url(&self) -> String { + pub fn new( + node_ip: String, + node_port: String, + worker_ip: String, + worker_rpc_port: String, + worker_mu_ra_port: String) -> Self { + Self { + node_ip, + node_port, + worker_ip, + worker_rpc_port, + worker_mu_ra_port, + ext_api_url: None, + } + } + + pub fn node_url(&self) -> String { format!("{}:{}", self.node_ip, self.node_port) } @@ -33,17 +49,16 @@ impl Config { impl From<&ArgMatches<'_>> for Config { fn from(m: &ArgMatches<'_>) -> Self { - Self { - node_ip: m.value_of("node-server").unwrap_or("ws://127.0.0.1").into(), - node_port: m.value_of("node-port").unwrap_or("9944").into(), - worker_ip: if m.is_present("ws-external") { + Self::new( + m.value_of("node-server").unwrap_or("ws://127.0.0.1").into(), + m.value_of("node-port").unwrap_or("9944").into(), + if m.is_present("ws-external") { "0.0.0.0".into() } else { "127.0.0.1".into() }, - worker_rpc_port: m.value_of("worker-rpc-port").unwrap_or("2000").into(), - worker_mu_ra_port: m.value_of("mu-ra-port").unwrap_or("3443").into(), - ext_api_url: None, - } + m.value_of("worker-rpc-port").unwrap_or("2000").into(), + m.value_of("mu-ra-port").unwrap_or("3443").into(), + ) } } \ No newline at end of file diff --git a/worker/src/error.rs b/worker/src/error.rs new file mode 100644 index 0000000000..31a33d4fe7 --- /dev/null +++ b/worker/src/error.rs @@ -0,0 +1,18 @@ +use codec::{Error as CodecError}; +use substrate_api_client::ApiClientError; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Codec(#[from] CodecError), + #[error("{0}")] + ApiClientError(#[from] ApiClientError), + #[error("{0}")] + JsonRpSeeClient(#[from] jsonrpsee::types::Error), + #[error("{0}")] + Serialization(#[from] serde_json::Error), + #[error("{0}")] + FromUtf8Error(#[from] std::string::FromUtf8Error), + #[error("Custom Error: {0}")] + Custom(Box), +} \ No newline at end of file diff --git a/worker/src/main.rs b/worker/src/main.rs index 32860e513e..be5511054b 100644 --- a/worker/src/main.rs +++ b/worker/src/main.rs @@ -34,8 +34,9 @@ use codec::{Decode, Encode}; use lazy_static::lazy_static; use log::*; use my_node_runtime::{ - substratee_registry::ShardIdentifier, Event, Hash, Header, UncheckedExtrinsic + substratee_registry::ShardIdentifier, Event, Hash, Header }; +use parking_lot::RwLock; use sp_core::{ crypto::{AccountId32, Ss58Codec}, sr25519, @@ -58,19 +59,26 @@ use std::time::{Duration, SystemTime}; use substratee_api_client_extensions::{AccountApi, ChainApi}; use substratee_worker_primitives::block::SignedBlock as SignedSidechainBlock; use substratee_node_primitives::SignedBlock; +use substratee_enclave_api::{Enclave, TeeRexApi}; +use substratee_worker_api::direct_client::DirectClient; + use config::Config; -use utils::extract_shard; use substratee_settings::files::{ - SIGNING_KEY_FILE, SHIELDING_KEY_FILE, ENCLAVE_FILE, - RA_SPID_FILE, RA_API_KEY_FILE, SHARDS_PATH, ENCRYPTED_STATE_FILE + SIGNING_KEY_FILE, SHIELDING_KEY_FILE, SHARDS_PATH, ENCRYPTED_STATE_FILE }; +use worker::{Worker as WorkerGen}; +use crate::utils::{extract_shard, hex_encode, check_files, write_slice_and_whitespace_pad}; +use crate::worker::{WorkerT, worker_url_into_async_rpc_url}; + mod enclave; mod ipfs; mod tests; mod config; mod utils; +mod worker; +mod error; /// how many blocks will be synced before storing the chain db to disk const BLOCK_SYNC_BATCH_SIZE: u32 = 1000; @@ -78,6 +86,15 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); /// start block production every ... ms const BLOCK_PRODUCTION_INTERVAL: u64 = 1000; +type Worker = WorkerGen, Enclave, DirectClient>; + +lazy_static! { + // todo: replace with &str, but use &str in api-client first + static ref NODE_URL: Mutex = Mutex::new("".to_string()); + static ref WORKER: RwLock> = RwLock::new(None); + static ref TOKIO_HANDLE: Mutex> = Default::default(); +} + fn main() { // Setup logging env_logger::init(); @@ -86,7 +103,6 @@ fn main() { let matches = App::from_yaml(yml).get_matches(); let mut config = Config::from(&matches); - println!("Worker Config: {:?}", config); *NODE_URL.lock().unwrap() = config.node_url(); @@ -100,7 +116,8 @@ fn main() { .map(ToString::to_string) .unwrap_or_else(|| format!("ws://127.0.0.1:{}", config.worker_rpc_port)) ); - println!("Advertising worker api at {}", config.ext_api_url.as_ref().unwrap()); + + println!("Worker Config: {:?}", config); let skip_ra = smatches.is_present("skip-ra"); worker( config.clone(), @@ -225,6 +242,18 @@ fn worker( let mrenclave = enclave_mrenclave(enclave.geteid()).unwrap(); println!("MRENCLAVE={}", mrenclave.to_base58()); let eid = enclave.geteid(); + + let rt = tokio::runtime::Runtime::new().unwrap(); + *TOKIO_HANDLE.lock().unwrap() = Some(rt.handle().clone()); + *WORKER.write() = Some( + Worker::new( + config.clone(), + Api::new(config.node_url()).map(|api| api.set_signer(AccountKeyring::Alice.pair())).unwrap(), + Enclave::new(eid), + DirectClient::new(config.worker_url()), + ) + ); + // ------------------------------------------------------------------------ // let new workers call us for key provisioning println!("MU-RA server listening on ws://{}", config.mu_ra_url()); @@ -242,6 +271,18 @@ fn worker( println!("rpc worker server listening on ws://{}", config.worker_url()); start_worker_api_direct_server( config.worker_url(), eid); + // listen for sidechain_block import request. Later the `start_worker_api_direct_server` + // should be merged into this one. + let enclave = Enclave::new(eid); + let url = worker_url_into_async_rpc_url(&config.worker_url()).unwrap(); + + let handle = TOKIO_HANDLE.lock().unwrap().as_ref().unwrap().clone(); + handle.spawn(async move { + substratee_worker_rpc_server::run_server( + &url, + enclave, + ).await.unwrap() + }); // ------------------------------------------------------------------------ // start the substrate-api-client to communicate with the node let mut api = Api::new(NODE_URL.lock().unwrap().clone()) @@ -255,28 +296,24 @@ fn worker( // ------------------------------------------------------------------------ // perform a remote attestation and get an unchecked extrinsic back - if skip_ra { - println!("[!] skipping remote attestation. will not register this enclave on chain"); - } else { - // get enclaves's account nonce - let nonce = api.get_nonce_of(&tee_accountid).unwrap(); - info!("Enclave nonce = {:?}", nonce); - - let uxt = - enclave_perform_ra(eid, genesis_hash, nonce, config.ext_api_url.unwrap().as_bytes().to_vec()).unwrap(); + // get enclaves's account nonce + let nonce = api.get_nonce_of(&tee_accountid).unwrap(); + info!("Enclave nonce = {:?}", nonce); - let ue = UncheckedExtrinsic::decode(&mut uxt.as_slice()).unwrap(); + let uxt = if skip_ra { + println!("[!] skipping remote attestation. Registering enclave without attestation report."); + enclave.mock_register_xt(api.genesis_hash, nonce, &config.ext_api_url.unwrap()).unwrap() + } else { + enclave_perform_ra(eid, genesis_hash, nonce, config.ext_api_url.unwrap().as_bytes().to_vec()).unwrap() + }; - debug!("RA extrinsic: {:?}", ue); + let mut xthex = hex::encode(uxt); + xthex.insert_str(0, "0x"); - let mut _xthex = hex::encode(ue.encode()); - _xthex.insert_str(0, "0x"); - - // send the extrinsic and wait for confirmation - println!("[>] Register the enclave (send the extrinsic)"); - let tx_hash = api.send_extrinsic(_xthex, XtStatus::InBlock).unwrap(); - println!("[<] Extrinsic got finalized. Hash: {:?}\n", tx_hash); - } + // send the extrinsic and wait for confirmation + println!("[>] Register the enclave (send the extrinsic)"); + let tx_hash = api.send_extrinsic(xthex, XtStatus::Finalized).unwrap(); + println!("[<] Extrinsic got finalized. Hash: {:?}\n", tx_hash); let latest_head = init_chain_relay(eid, &api); println!("*** [+] Finished syncing chain relay\n"); @@ -545,12 +582,6 @@ pub fn produce_blocks( curr_head.block.header } -fn hex_encode(data: Vec) -> String { - let mut hex_str = hex::encode(data); - hex_str.insert_str(0, "0x"); - hex_str -} - fn init_shard(shard: &ShardIdentifier) { let path = format!("{}/{}", SHARDS_PATH, shard.encode().to_base58()); println!("initializing shard at {}", path); @@ -615,27 +646,6 @@ fn ensure_account_has_funds(api: &mut Api, accountid: &AccountId3 } } -pub fn check_files() { - debug!("*** Check files"); - let files = vec![ - ENCLAVE_FILE, - SHIELDING_KEY_FILE, - SIGNING_KEY_FILE, - RA_SPID_FILE, - RA_API_KEY_FILE, - ]; - for f in files.iter() { - if !Path::new(f).exists() { - panic!("file doesn't exist: {}", f); - } - } -} - -lazy_static! { - // todo: replace with &str, but use &str in api-client first - static ref NODE_URL: Mutex = Mutex::new("".to_string()); -} - /// # Safety /// /// FFI are always unsafe @@ -730,22 +740,22 @@ pub unsafe extern "C" fn ocall_send_block_and_confirmation( vec![] } }; + println! {"Received blocks: {:?}", signed_blocks}; + + let w = WORKER.read(); + + // make it sync, as sgx ffi does not support async/await + let handle = TOKIO_HANDLE.lock().unwrap().as_ref().unwrap().clone(); + if let Err(e) = handle.block_on(w.as_ref().unwrap().gossip_blocks(signed_blocks)) { + error!("Error gossiping blocks: {:?}", e); + // Fixme: returning an error here results in a `HeaderAncestryMismatch` error. + // status = sgx_status_t::SGX_ERROR_UNEXPECTED; + }; // TODO: M8.3: Store blocks - // TODO: M8.3: broadcast blocks status } -pub fn write_slice_and_whitespace_pad(writable: &mut [u8], data: Vec) { - if data.len() > writable.len() { - panic!("not enough bytes in output buffer for return value"); - } - let (left, right) = writable.split_at_mut(data.len()); - left.clone_from_slice(&data); - // fill the right side with whitespace - right.iter_mut().for_each(|x| *x = 0x20); -} - #[derive(Encode, Decode, Clone, Debug, PartialEq)] pub enum WorkerRequest { ChainStorage(Vec, Option), // (storage_key, at_block) diff --git a/worker/src/tests/commons.rs b/worker/src/tests/commons.rs index 260329de07..1a05adc10d 100644 --- a/worker/src/tests/commons.rs +++ b/worker/src/tests/commons.rs @@ -32,6 +32,11 @@ use crate::{enclave_account, ensure_account_has_funds}; use substrate_api_client::Api; use substratee_stf::{Index, KeyPair, ShardIdentifier, TrustedCall, TrustedGetter, Getter}; +#[cfg(test)] +use crate::config::Config; +#[cfg(test)] +use substratee_worker_primitives::block::{SignedBlock, Block}; + #[derive(Debug, Serialize, Deserialize)] pub struct Message { pub account: String, @@ -132,3 +137,44 @@ pub fn get_nonce(api: &Api, who: &AccountId32) -> u32 { 0 } } + +#[cfg(test)] +pub fn test_sidechain_block() -> SignedBlock { + use sp_core::{H256, Pair}; + + + let signer_pair = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); + let author: AccountId32 = signer_pair.public().into(); + let block_number: u64 = 0; + let parent_hash = H256::random(); + let layer_one_head = H256::random(); + let signed_top_hashes = vec![]; + let encrypted_payload: Vec = vec![]; + let shard = ShardIdentifier::default(); + + // when + let block = Block::construct_block( + author, + block_number, + parent_hash.clone(), + layer_one_head.clone(), + shard.clone(), + signed_top_hashes.clone(), + encrypted_payload.clone(), + ); + block.sign(&signer_pair) +} + +/// Local Worker config. Fields are the default values except for +/// the worker's rpc server. +#[cfg(test)] +pub fn local_worker_config(worker_url: String) -> Config { + let mut url = worker_url.split(":"); + Config::new( + Default::default(), + Default::default(), + url.next().unwrap().into(), + url.next().unwrap().into(), + Default::default(), + ) +} \ No newline at end of file diff --git a/worker/src/tests/mock.rs b/worker/src/tests/mock.rs new file mode 100644 index 0000000000..e20e7a7dc5 --- /dev/null +++ b/worker/src/tests/mock.rs @@ -0,0 +1,45 @@ +use substratee_api_client_extensions::{SubstrateeRegistryApi, ApiResult}; +use substratee_node_primitives::{Enclave, ShardIdentifier}; + +pub struct TestNodeApi; + +pub const W1_URL: &str = "127.0.0.1:2222"; +pub const W2_URL: &str = "127.0.0.1:3333"; + +pub fn enclaves() -> Vec { + vec![ + Enclave::new( + [0;32].into(), + [1;32].into(), + 1, + format!("ws://{}", W1_URL), + ), + Enclave::new( + [2;32].into(), + [3;32].into(), + 2, + format!("ws://{}", W2_URL), + ), + ] +} + +impl SubstrateeRegistryApi for TestNodeApi { + + fn enclave(&self, index: u64) -> ApiResult> { + Ok(Some(enclaves().remove(index as usize))) + } + fn enclave_count(&self) -> ApiResult { + unreachable!() + } + + fn all_enclaves(&self) -> ApiResult> { + Ok(enclaves()) + } + + fn worker_for_shard(&self, _: &ShardIdentifier) -> ApiResult> { + unreachable!() + } + fn latest_ipfs_hash(&self, _: &ShardIdentifier) -> ApiResult> { + unreachable!() + } +} \ No newline at end of file diff --git a/worker/src/tests/mod.rs b/worker/src/tests/mod.rs index 8369af6b49..99be5402a2 100644 --- a/worker/src/tests/mod.rs +++ b/worker/src/tests/mod.rs @@ -25,6 +25,10 @@ use self::integration_tests::*; pub mod commons; pub mod ecalls; pub mod integration_tests; +pub mod mock; + +#[cfg(test)] +pub mod worker; pub fn run_enclave_tests(matches: &ArgMatches, port: &str) { println!("*** Starting Test enclave"); diff --git a/worker/src/tests/worker.rs b/worker/src/tests/worker.rs new file mode 100644 index 0000000000..29c3ae2aa9 --- /dev/null +++ b/worker/src/tests/worker.rs @@ -0,0 +1,34 @@ +use lazy_static::lazy_static; +use parking_lot::RwLock; +use substratee_api_client_extensions::SubstrateeRegistryApi; + +use crate::config::Config; +use crate::tests::commons::local_worker_config; +use crate::tests::mock::{enclaves, TestNodeApi, W2_URL}; +use crate::worker::Worker as WorkerGen; + +type TestWorker = WorkerGen; + +lazy_static! { + static ref WORKER: RwLock> = RwLock::new(None); +} + +#[test] +fn worker_rw_lock_works() { + { + let mut w = WORKER.write(); + *w = Some(TestWorker::new( + local_worker_config(W2_URL.into()), + TestNodeApi, + (), + (), + )); + } + + let w = WORKER.read(); + // call some random function to see how the worker needs to be called. + assert_eq!( + w.as_ref().unwrap().node_api().all_enclaves().unwrap(), + enclaves() + ) +} diff --git a/worker/src/utils.rs b/worker/src/utils.rs index 0f00af5198..f168f5258f 100644 --- a/worker/src/utils.rs +++ b/worker/src/utils.rs @@ -1,8 +1,9 @@ use substratee_stf::ShardIdentifier; use clap::ArgMatches; use base58::{FromBase58, ToBase58}; -use log::info; +use log::{info, debug}; use crate::enclave::api::{enclave_init, enclave_mrenclave}; +use std::path::Path; pub fn extract_shard(m: &ArgMatches<'_>) -> ShardIdentifier { match m.value_of("shard") { @@ -22,4 +23,41 @@ pub fn extract_shard(m: &ArgMatches<'_>) -> ShardIdentifier { ShardIdentifier::from_slice(&mrenclave[..]) } } +} + +pub fn hex_encode(data: Vec) -> String { + let mut hex_str = hex::encode(data); + hex_str.insert_str(0, "0x"); + hex_str +} + +pub fn write_slice_and_whitespace_pad(writable: &mut [u8], data: Vec) { + if data.len() > writable.len() { + panic!("not enough bytes in output buffer for return value"); + } + let (left, right) = writable.split_at_mut(data.len()); + left.clone_from_slice(&data); + // fill the right side with whitespace + right.iter_mut().for_each(|x| *x = 0x20); +} + + +pub fn check_files() { + use substratee_settings::files::{ + SIGNING_KEY_FILE, SHIELDING_KEY_FILE, ENCLAVE_FILE, + RA_SPID_FILE, RA_API_KEY_FILE, + }; + debug!("*** Check files"); + let files = vec![ + ENCLAVE_FILE, + SHIELDING_KEY_FILE, + SIGNING_KEY_FILE, + RA_SPID_FILE, + RA_API_KEY_FILE, + ]; + for f in files.iter() { + if !Path::new(f).exists() { + panic!("file doesn't exist: {}", f); + } + } } \ No newline at end of file diff --git a/worker/src/worker.rs b/worker/src/worker.rs new file mode 100644 index 0000000000..936bcfc2c2 --- /dev/null +++ b/worker/src/worker.rs @@ -0,0 +1,170 @@ + +///! Substratee worker. Inspiration for this design came from parity's substrate Client. +/// +/// This should serve as a proof of concept for a potential refactoring design. Ultimately, everything +/// from the main.rs should be covered by the worker struct here - hidden and split across +/// multiple traits. + +use async_trait::async_trait; +use jsonrpsee::{ + types::{to_json_value, traits::Client}, + ws_client::WsClientBuilder, +}; +use log::info; +use std::num::ParseIntError; + +use substratee_api_client_extensions::SubstrateeRegistryApi; +use substratee_worker_primitives::block::SignedBlock as SignedSidechainBlock; +use substratee_node_primitives::Enclave as EnclaveMetadata; + +use crate::config::Config; +use crate::error::Error; + +pub type WorkerResult = Result; + +// don't put any trait bounds here. It is good practise to only enforce them where needed. This +// also serves a guide when traits should be split into subtraits. +pub struct Worker { + config: Config, + node_api: NodeApi, // todo: Depending on system design, all the api fields should be Arc + // unused yet, but will be used when more methods are migrated to the worker + _enclave_api: Enclave, + _worker_api_direct: WorkerApiDirect, +} + +impl Worker { + pub fn new( + config: Config, + node_api: NodeApi, + _enclave_api: Enclave, + _worker_api_direct: WorkerApiDirect, + ) -> Self { + Self { + config, + node_api, + _enclave_api, + _worker_api_direct, + } + } + + // will soon be used. + #[allow(dead_code)] + pub fn node_api(&self) -> &NodeApi { + &self.node_api + } +} + +#[async_trait] +pub trait WorkerT { + // fn send_confirmations(&self, confirms: Vec>) -> WorkerResult<()>; + async fn gossip_blocks(&self, blocks: Vec) -> WorkerResult<()>; + fn peers(&self) -> WorkerResult>; +} + +#[async_trait] +impl WorkerT + for Worker +where + NodeApi: SubstrateeRegistryApi + Send + Sync, + Enclave: Send + Sync, + WorkerApiDirect: Send + Sync, +{ + async fn gossip_blocks(&self, blocks: Vec) -> WorkerResult<()> { + let peers = self.peers()?; + info!("Gossiping sidechain blocks to peers: {:?}", peers); + + for p in peers.iter() { + // Todo: once the two direct servers are merged, remove this. + let url = worker_url_into_async_rpc_url(&p.url)?; + info!("Gossiping block to peer with address: {:?}", url); + let client = WsClientBuilder::default().build(&url).await?; + let response: String = client + .request::>( + "sidechain_importBlock", + vec![to_json_value(blocks.clone())?].into(), + ) + .await + .map(String::from_utf8)??; + info!("sidechain_importBlock response: {:?}", response); + } + Ok(()) + } + + fn peers(&self) -> WorkerResult> { + let mut peers = self.node_api.all_enclaves()?; + peers.retain(|e| e.url.trim_start_matches("ws://") != self.config.worker_url()); + Ok(peers) + } +} + +/// Temporary method that transforms the workers rpc port of the direct api defined in rpc/direct_client +/// to the new version in rpc/server. Remove this, when all the methods have been migrated to the new one +/// in rpc/server. +pub fn worker_url_into_async_rpc_url(url: &str) -> WorkerResult { + // [Option("ws"), //ip, port] + let mut url_vec: Vec<&str> = url.split(":").collect(); + match url_vec.len() { + 3 | 2 => (), + _ => Err(Error::Custom("Invalid worker url format".into()))?, + }; + + let ip = if url_vec.len() == 3 { + format!("{}:{}", url_vec.remove(0), url_vec.remove(0)) + } else { + url_vec.remove(0).into() + }; + + let port: i32 = url_vec.remove(0).parse().map_err(|e:ParseIntError| Error::Custom(e.into()))?; + + Ok(format!("{}:{}", ip, (port + 1))) +} + +#[cfg(test)] +mod tests { + use jsonrpsee::{ws_server::WsServerBuilder, RpcModule}; + use log::debug; + use std::net::SocketAddr; + use frame_support::assert_ok; + use substratee_worker_primitives::block::SignedBlock as SignedSidechainBlock; + use tokio::net::ToSocketAddrs; + + use crate::tests::{ + commons::{local_worker_config, test_sidechain_block}, + mock::{TestNodeApi, W1_URL, W2_URL}, + }; + use crate::worker::{Worker, WorkerT, worker_url_into_async_rpc_url}; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + async fn run_server(addr: impl ToSocketAddrs) -> anyhow::Result { + let mut server = WsServerBuilder::default().build(addr).await?; + let mut module = RpcModule::new(()); + + module.register_method("sidechain_importBlock", |params, _| { + debug!("sidechain_importBlock params: {:?}", params); + let _blocks: Vec = params.one()?; + Ok("ok".as_bytes().to_vec()) + })?; + + server.register_module(module).unwrap(); + + let socket_addr = server.local_addr()?; + tokio::spawn(async move { server.start().await }); + Ok(socket_addr) + } + + #[tokio::test] + async fn gossip_blocks_works() { + init(); + run_server(worker_url_into_async_rpc_url(W2_URL).unwrap()).await.unwrap(); + + let worker = Worker::new(local_worker_config(W1_URL.into()), TestNodeApi, (), ()); + + let resp = worker + .gossip_blocks(vec![test_sidechain_block()]) + .await; + assert_ok!(resp); + } +}