diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 61b29619e0..e57cef9e41 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -3,9 +3,9 @@ name: Build, Test, Clippy on: workflow_dispatch: push: - branches: [ master ] + branches: [ master, exchange_rate_oracle ] pull_request: - branches: [ master ] + branches: [ master, exchange_rate_oracle ] env: CARGO_TERM_COLOR: always diff --git a/Cargo.lock b/Cargo.lock index 3830cf5063..1ef233c7ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2050,6 +2050,7 @@ dependencies = [ "sp-keyring", "sp-runtime", "substrate-api-client", + "substrate-fixed 0.5.6", "thiserror 1.0.29", "tokio", "ws", @@ -2097,6 +2098,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "ita-exchange-oracle" +version = "0.8.0" +dependencies = [ + "itc-rest-client", + "log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.130", + "serde_json 1.0.67", + "sgx_tstd", + "thiserror 1.0.29", + "thiserror 1.0.9", + "url 2.1.1", + "url 2.2.2", +] + [[package]] name = "ita-stf" version = "0.8.0" @@ -3910,7 +3926,7 @@ dependencies = [ "sp-io 4.0.0-dev (git+https://github.com/paritytech/substrate.git?branch=master)", "sp-runtime", "sp-std", - "substrate-fixed", + "substrate-fixed 0.5.7", "test-utils", ] @@ -6380,6 +6396,15 @@ dependencies = [ "sp-keystore", ] +[[package]] +name = "substrate-fixed" +version = "0.5.6" +source = "git+https://github.com/encointer/substrate-fixed?tag=v0.5.6#b33d186888c60f38adafcfc0ec3a21aab263aef1" +dependencies = [ + "parity-scale-codec", + "typenum 1.14.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "substrate-fixed" version = "0.5.7" diff --git a/Cargo.toml b/Cargo.toml index 23adad12f6..15046e71a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "app-libs/exchange-oracle", "app-libs/stf", "cli", "core/direct-rpc-server", @@ -38,9 +39,11 @@ members = [ "sidechain/validateer-fetch", ] -#[patch."https://github.com/integritee-network/pallet-teerex.git"] -#pallet-teerex = { path = "../pallet-teerex" } +#[patch."https://github.com/integritee-network/integritee-node"] +#my-node-runtime = { path = "../integritee-node/runtime", package = "integritee-node-runtime"} +#[patch."https://github.com/integritee-network/pallets.git"] +#pallet-teerex = { path = "../pallets/teerex" } #[patch."https://github.com/scs/substrate-api-client"] #substrate-api-client = { path = "../substrate-api-client" } diff --git a/app-libs/exchange-oracle/Cargo.toml b/app-libs/exchange-oracle/Cargo.toml new file mode 100644 index 0000000000..01a149dd90 --- /dev/null +++ b/app-libs/exchange-oracle/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "ita-exchange-oracle" +version = "0.8.0" +authors = ["Integritee AG "] +edition = "2018" + +[features] +default = ["std"] +std = [ + "itc-rest-client/std", + "log/std", + "serde/std", + "serde_json/std", + "thiserror", + "url", +] +sgx = [ + "itc-rest-client/sgx", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", +] + +[dependencies] + +# std dependencies +thiserror = { version = "1.0.26", optional = true } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +sgx_tstd = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true} +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# internal dependencies +itc-rest-client = { path = "../../core/rest-client", default-features = false } + diff --git a/app-libs/exchange-oracle/src/coingecko.rs b/app-libs/exchange-oracle/src/coingecko.rs new file mode 100644 index 0000000000..1ed00a0e0b --- /dev/null +++ b/app-libs/exchange-oracle/src/coingecko.rs @@ -0,0 +1,131 @@ +/* + Copyright 2021 Integritee AG and 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. + +*/ +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{error::Error, GetExchangeRate}; +use itc_rest_client::{http_client::HttpClient, rest_client::RestClient, RestGet, RestPath}; +use log::*; +use serde::{Deserialize, Serialize}; +use std::{ + string::{String, ToString}, + time::Duration, + vec::Vec, +}; +use url::Url; + +const COINGECKO_URL: &str = "https://api.coingecko.com"; +const COINGECKO_PARAM_CURRENCY: &str = "vs_currency"; +const COINGECKO_PARAM_COIN: &str = "ids"; +const COINGECKO_PATH: &str = "api/v3/coins/markets"; +const COINGECKO_TIMEOUT: Duration = Duration::from_secs(3u64); + +/// REST client to make requests to CoinGecko. +pub struct CoinGeckoClient { + client: RestClient, +} +impl CoinGeckoClient { + pub fn new(baseurl: Url) -> Self { + let http_client = HttpClient::new(true, Some(COINGECKO_TIMEOUT), None, None); + let rest_client = RestClient::new(http_client, baseurl); + CoinGeckoClient { client: rest_client } + } + pub fn base_url() -> Result { + Url::parse(COINGECKO_URL).map_err(|e| Error::Other(format!("{:?}", e).into())) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CoinGeckoMarketStruct { + id: String, + symbol: String, + name: String, + current_price: Option, + last_updated: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CoinGeckoMarket(pub Vec); + +impl RestPath for CoinGeckoMarket { + fn get_path(path: String) -> Result { + Ok(path) + } +} + +impl GetExchangeRate for CoinGeckoClient { + fn get_exchange_rate(&mut self, coin: &str, currency: &str) -> Result { + let response = self + .client + .get_with::( + COINGECKO_PATH.to_string(), + &[(COINGECKO_PARAM_CURRENCY, currency), (COINGECKO_PARAM_COIN, coin)], + ) + .map_err(Error::RestClient)?; + let list = response.0; + if list.is_empty() { + error!("Got no market data from coinGecko. Check params {},{}", currency, coin); + return Err(Error::NoValidData) + } + match list[0].current_price { + Some(r) => Ok(r), + None => { + error!("Failed to get the exchange rate of {} to {}", currency, coin); + Err(Error::EmptyExchangeRate) + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_exchange_rate_for_undefined_coins_fails() { + let url = CoinGeckoClient::base_url().unwrap(); + let mut coingecko_client = CoinGeckoClient::new(url); + let result = coingecko_client.get_exchange_rate("invalid_coin", "usd"); + assert_matches!(result, Err(Error::NoValidData)); + } + + #[test] + fn get_exchange_rate_for_undefined_currency_fails() { + let url = CoinGeckoClient::base_url().unwrap(); + let mut coingecko_client = CoinGeckoClient::new(url); + let result = coingecko_client.get_exchange_rate("polkadot", "ch"); + assert_matches!(result, Err(Error::RestClient(_))); + } + + #[test] + fn get_exchange_rate_from_coingecko_works() { + let url = CoinGeckoClient::base_url().unwrap(); + let mut coingecko_client = CoinGeckoClient::new(url); + let dot_usd = coingecko_client.get_exchange_rate("polkadot", "usd").unwrap(); + assert!(dot_usd > 0f32); + let bit_usd = coingecko_client.get_exchange_rate("bitcoin", "usd").unwrap(); + assert!(bit_usd > 0f32); + let dot_chf = coingecko_client.get_exchange_rate("polkadot", "chf").unwrap(); + assert!(dot_chf > 0f32); + let bit_chf = coingecko_client.get_exchange_rate("bitcoin", "chf").unwrap(); + assert!(bit_chf > 0f32); + assert_eq!( + (dot_usd * 100000. / bit_usd).round() / 100000., + (dot_chf * 100000. / bit_chf).round() / 100000. + ); + } +} diff --git a/app-libs/exchange-oracle/src/error.rs b/app-libs/exchange-oracle/src/error.rs new file mode 100644 index 0000000000..96b21faddb --- /dev/null +++ b/app-libs/exchange-oracle/src/error.rs @@ -0,0 +1,32 @@ +/* + Copyright 2021 Integritee AG and 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. + +*/ +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; +use std::boxed::Box; + +/// Exchange rate error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Rest client error")] + RestClient(itc_rest_client::error::Error), + #[error("Other error")] + Other(Box), + #[error("Could not retrieve any data")] + NoValidData, + #[error("Value for exchange rate is null")] + EmptyExchangeRate, +} diff --git a/app-libs/exchange-oracle/src/lib.rs b/app-libs/exchange-oracle/src/lib.rs new file mode 100644 index 0000000000..4f0c13c0d5 --- /dev/null +++ b/app-libs/exchange-oracle/src/lib.rs @@ -0,0 +1,47 @@ +/* + Copyright 2021 Integritee AG and 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. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +use crate::error::Error; + +pub mod coingecko; +pub mod error; + +pub trait GetExchangeRate { + /// Get the cryptocurrency/fiat_currency exchange rate + fn get_exchange_rate( + &mut self, + cryptocurrency: &str, + fiat_currency: &str, + ) -> Result; +} diff --git a/core-primitives/api-client-extensions/src/lib.rs b/core-primitives/api-client-extensions/src/lib.rs index c258866e68..b60a5598bd 100644 --- a/core-primitives/api-client-extensions/src/lib.rs +++ b/core-primitives/api-client-extensions/src/lib.rs @@ -4,10 +4,12 @@ use substrate_api_client::ApiClientError; pub mod account; pub mod chain; +pub mod pallet_teeracle; pub mod pallet_teerex; pub use account::*; pub use chain::*; +pub use pallet_teeracle::*; pub use pallet_teerex::*; pub type ApiResult = Result; diff --git a/core-primitives/api-client-extensions/src/pallet_teeracle.rs b/core-primitives/api-client-extensions/src/pallet_teeracle.rs new file mode 100644 index 0000000000..8075822482 --- /dev/null +++ b/core-primitives/api-client-extensions/src/pallet_teeracle.rs @@ -0,0 +1 @@ +pub const TEERACLE: &str = "Teeracle"; diff --git a/core-primitives/enclave-api/ffi/src/lib.rs b/core-primitives/enclave-api/ffi/src/lib.rs index 784d6e4560..1eb5ca1d00 100644 --- a/core-primitives/enclave-api/ffi/src/lib.rs +++ b/core-primitives/enclave-api/ffi/src/lib.rs @@ -116,6 +116,17 @@ extern "C" { unchecked_extrinsic_size: u32, ) -> sgx_status_t; + pub fn update_market_data_xt( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + genesis_hash: *const u8, + genesis_hash_size: u32, + currency: *const u8, + currency_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, + ) -> sgx_status_t; + pub fn run_key_provisioning_server( eid: sgx_enclave_id_t, retval: *mut sgx_status_t, diff --git a/core-primitives/enclave-api/src/lib.rs b/core-primitives/enclave-api/src/lib.rs index b69716029a..d1efc8f615 100644 --- a/core-primitives/enclave-api/src/lib.rs +++ b/core-primitives/enclave-api/src/lib.rs @@ -22,6 +22,7 @@ pub mod enclave_test; pub mod error; pub mod remote_attestation; pub mod sidechain; +pub mod teeracle_api; pub mod teerex_api; pub mod utils; diff --git a/core-primitives/enclave-api/src/teeracle_api.rs b/core-primitives/enclave-api/src/teeracle_api.rs new file mode 100644 index 0000000000..cf0c328780 --- /dev/null +++ b/core-primitives/enclave-api/src/teeracle_api.rs @@ -0,0 +1,56 @@ +/* + Copyright 2021 Integritee AG and 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 crate::{error::Error, Enclave, EnclaveResult}; +use codec::Encode; +use frame_support::{ensure, sp_runtime::app_crypto::sp_core::H256}; +use itp_enclave_api_ffi as ffi; +use sgx_types::*; + +pub trait TeeracleApi: Send + Sync + 'static { + /// update the currency market data for the token oracle. + fn update_market_data_xt(&self, genesis_hash: H256, currency: &str) -> EnclaveResult>; +} + +impl TeeracleApi for Enclave { + fn update_market_data_xt(&self, genesis_hash: H256, currency: &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 curr = currency.encode(); + let gen = genesis_hash.as_bytes().to_vec(); + + let res = unsafe { + ffi::update_market_data_xt( + self.eid, + &mut retval, + gen.as_ptr(), + gen.len() as u32, + curr.as_ptr(), + curr.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/core-primitives/settings/src/lib.rs b/core-primitives/settings/src/lib.rs index 8e8134e389..534b224a34 100644 --- a/core-primitives/settings/src/lib.rs +++ b/core-primitives/settings/src/lib.rs @@ -91,6 +91,7 @@ pub mod enclave { /// Settings concerning the node pub mod node { + use core::time::Duration; // you may have to update these indices upon new builds of the runtime // you can get the index from metadata, counting modules starting with zero pub static TEEREX_MODULE: u8 = 50u8; @@ -104,4 +105,9 @@ pub mod node { pub static RUNTIME_SPEC_VERSION: u32 = 3; pub static RUNTIME_TRANSACTION_VERSION: u32 = 1; pub static UNSHIELD: u8 = 6u8; + + pub static TEERACLE_MODULE: u8 = 52u8; + pub static UPDATE_EXCHANGE_RATE: u8 = 0u8; + // Send extrinsic to update market exchange rate on the parentchain once per day + pub static MARKET_DATA_UPDATE_INTERVAL: Duration = Duration::from_secs(86400); } diff --git a/enclave-runtime/Cargo.lock b/enclave-runtime/Cargo.lock index 1dbe85807a..4242bb4717 100644 --- a/enclave-runtime/Cargo.lock +++ b/enclave-runtime/Cargo.lock @@ -116,6 +116,12 @@ name = "base-x" version = "0.2.6" source = "git+https://github.com/whalelephant/base-x-rs?branch=no_std#906c9ac59282ff5a2eec86efd25d50ad9927b147" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "base64" version = "0.13.0" @@ -468,6 +474,7 @@ dependencies = [ "hex", "httparse", "ipfs-unixfs", + "ita-exchange-oracle", "ita-stf", "itc-direct-rpc-server", "itc-light-client", @@ -522,6 +529,7 @@ dependencies = [ "sp-utils", "sp-version", "substrate-api-client", + "substrate-fixed", "tiny-keccak", "webpki", "webpki-roots 0.21.0 (git+https://github.com/mesalock-linux/webpki-roots?branch=mesalock_sgx)", @@ -1021,6 +1029,18 @@ dependencies = [ "sgx_tstd", ] +[[package]] +name = "http_req" +version = "0.7.2" +source = "git+https://github.com/mesalock-linux/http_req-sgx?tag=sgx_1.1.3#5d0f7474c73b70d2dbdda69c1db2a2e31aee6eb8" +dependencies = [ + "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?branch=mesalock_sgx)", + "sgx_tstd", + "unicase", + "webpki", + "webpki-roots 0.21.0 (git+https://github.com/mesalock-linux/webpki-roots?branch=mesalock_sgx)", +] + [[package]] name = "httparse" version = "1.4.1" @@ -1116,6 +1136,19 @@ dependencies = [ "sha2 0.9.8", ] +[[package]] +name = "ita-exchange-oracle" +version = "0.8.0" +dependencies = [ + "itc-rest-client", + "log 0.4.14 (git+https://github.com/mesalock-linux/log-sgx)", + "serde 1.0.130", + "serde_json 1.0.67", + "sgx_tstd", + "thiserror 1.0.9", + "url", +] + [[package]] name = "ita-stf" version = "0.8.0" @@ -1182,6 +1215,22 @@ dependencies = [ "thiserror 1.0.9", ] +[[package]] +name = "itc-rest-client" +version = "0.8.0" +dependencies = [ + "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "http", + "http_req", + "log 0.4.14 (git+https://github.com/mesalock-linux/log-sgx)", + "serde 1.0.130", + "serde_json 1.0.67", + "sgx_tstd", + "sgx_types", + "thiserror 1.0.9", + "url", +] + [[package]] name = "itc-tls-websocket-server" version = "0.8.0" @@ -3381,6 +3430,15 @@ dependencies = [ "sp-std", ] +[[package]] +name = "substrate-fixed" +version = "0.5.6" +source = "git+https://github.com/encointer/substrate-fixed?tag=v0.5.6#b33d186888c60f38adafcfc0ec3a21aab263aef1" +dependencies = [ + "parity-scale-codec", + "typenum", +] + [[package]] name = "subtle" version = "1.0.0" @@ -3610,6 +3668,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "git+https://github.com/mesalock-linux/unicase-sgx#0b0519348572927118af47af3da4da9ffdca8ec6" +dependencies = [ + "sgx_tstd", + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.4" diff --git a/enclave-runtime/Cargo.toml b/enclave-runtime/Cargo.toml index d3b4c6d191..d682b392c8 100644 --- a/enclave-runtime/Cargo.toml +++ b/enclave-runtime/Cargo.toml @@ -52,6 +52,7 @@ sp-io = { default-features = false, features = ["disable_oom", "disable_panic_ha substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client", branch = "master" } sgx-externalities = { default-features = false, git = "https://github.com/integritee-network/sgx-runtime", branch = "master" } jsonrpc-core = { default-features = false, git = "https://github.com/scs/jsonrpc", branch = "no_std" } +substrate-fixed = { package = "substrate-fixed", git = "https://github.com/encointer/substrate-fixed",tag = "v0.5.6"} # mesalock linked-hash-map = { git = "https://github.com/mesalock-linux/linked-hash-map-sgx" } @@ -96,6 +97,7 @@ itp-teerex-storage = { path = "../core-primitives/teerex-storage", default-featu itp-test = { path = "../core-primitives/test", default-features = false, optional = true } itp-types = { path = "../core-primitives/types", default-features = false, features = ["sgx"] } its-sidechain = { path = "../sidechain/sidechain-crate", default-features = false, features = ["sgx"] } +ita-exchange-oracle = { path = "../app-libs/exchange-oracle", default-features = false, features = ["sgx"] } # substrate deps frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "master" } diff --git a/enclave-runtime/Enclave.edl b/enclave-runtime/Enclave.edl index b5801c02c5..7a6c8b88d2 100644 --- a/enclave-runtime/Enclave.edl +++ b/enclave-runtime/Enclave.edl @@ -87,6 +87,12 @@ enclave { [out, size=unchecked_extrinsic_size] uint8_t* unchecked_extrinsic, uint32_t unchecked_extrinsic_size ); + public sgx_status_t update_market_data_xt( + [in, size=genesis_hash_size] uint8_t* genesis_hash, uint32_t genesis_hash_size, + [in, size=currency_size] uint8_t* currency, uint32_t currency_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, int skip_ra); diff --git a/enclave-runtime/src/lib.rs b/enclave-runtime/src/lib.rs index 85cba9f4d1..044c807588 100644 --- a/enclave-runtime/src/lib.rs +++ b/enclave-runtime/src/lib.rs @@ -45,6 +45,7 @@ use crate::{ use base58::ToBase58; use beefy_merkle_tree::{merkle_root, Keccak256}; use codec::{alloc::string::String, Decode, Encode}; +use ita_exchange_oracle::{coingecko::CoinGeckoClient, GetExchangeRate}; use ita_stf::{AccountId, Getter, ShardIdentifier, Stf, TrustedCallSigned}; use itc_direct_rpc_server::{ create_determine_watch, rpc_connection_registry::ConnectionRegistry, @@ -59,7 +60,8 @@ use itp_nonce_cache::{MutateNonce, Nonce, GLOBAL_NONCE_CACHE}; use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveOnChainOCallApi}; use itp_settings::node::{ CALL_WORKER, PROCESSED_PARENTCHAIN_BLOCK, REGISTER_ENCLAVE, RUNTIME_SPEC_VERSION, - RUNTIME_TRANSACTION_VERSION, SHIELD_FUNDS, TEEREX_MODULE, + RUNTIME_TRANSACTION_VERSION, SHIELD_FUNDS, TEERACLE_MODULE, TEEREX_MODULE, + UPDATE_EXCHANGE_RATE, }; use itp_sgx_crypto::{aes, ed25519, rsa3072, Ed25519Seal, Rsa3072Seal, ShieldingCrypto}; use itp_sgx_io as io; @@ -83,11 +85,13 @@ use sp_finality_grandpa::VersionedAuthorityList; use sp_runtime::{ generic::SignedBlock as SignedBlockG, traits::{Block as BlockT, Header as HeaderT}, + OpaqueExtrinsic, }; use std::{slice, sync::Arc, vec::Vec}; use substrate_api_client::{ compose_extrinsic_offline, extrinsic::xt_primitives::UncheckedExtrinsicV4, }; +use substrate_fixed::types::U32F32; mod attestation; mod ipfs; @@ -578,6 +582,79 @@ where Ok(()) } +/// For now get the DOT/currency exchange rate from coingecko API. +#[no_mangle] +pub unsafe extern "C" fn update_market_data_xt( + genesis_hash: *const u8, + genesis_hash_size: u32, + currency_ptr: *const u8, + currency_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 currency_slice = slice::from_raw_parts(currency_ptr, currency_size as usize); + let currency: String = Decode::decode(&mut currency_slice).unwrap(); + + let extrinsics = match update_market_data_internal(genesis_hash, currency) { + Ok(xts) => xts, + Err(_) => return sgx_status_t::SGX_ERROR_UNEXPECTED, + }; + + // Only one extrinsic to send over the node api directly. + let extrinsic = match extrinsics.get(0) { + Some(xt) => xt, + None => return sgx_status_t::SGX_ERROR_UNEXPECTED, + }; + + let extrinsic_slice = + slice::from_raw_parts_mut(unchecked_extrinsic, unchecked_extrinsic_size as usize); + + // Save created extrinsic as slice in the return value unchecked_extrinsic. + write_slice_and_whitespace_pad(extrinsic_slice, extrinsic.encode()); + sgx_status_t::SGX_SUCCESS +} + +fn update_market_data_internal(genesis_hash: H256, curr: String) -> Result> { + let signer = Ed25519Seal::unseal()?; + + let extrinsics_factory = + ExtrinsicsFactory::new(genesis_hash, signer, GLOBAL_NONCE_CACHE.clone()); + + // For now hardcoded polkadot + let coin = "polkadot"; + + // Get the exchange rate + let url = match CoinGeckoClient::base_url() { + Ok(u) => u, + Err(e) => return Err(Error::Other(e.into())), + }; + + let mut coingecko_client = CoinGeckoClient::new(url); + let rate = match coingecko_client.get_exchange_rate(coin, &curr) { + Ok(r) => r, + Err(e) => { + error!("[-] Failed to get the newest exchange rate from coingecko. {:?}", e); + return Err(Error::Other(e.into())) + }, + }; + + println!( + "Update the exchange rate: 1 DOT = {:?} {}", + Some(U32F32::from_num(rate)).unwrap(), + curr.to_uppercase() + ); + let call = OpaqueCall::from_tuple(&( + [TEERACLE_MODULE, UPDATE_EXCHANGE_RATE], + curr.encode(), + Some(U32F32::from_num(rate)), + )); + let extrinsics = extrinsics_factory.create_extrinsics(vec![call].as_slice())?; + Ok(extrinsics) +} + /// Creates a processed_parentchain_block extrinsic for a given parentchain block hash and the merkle executed extrinsics. /// /// Calculates the merkle root of the extrinsics. In case no extrinsics are supplied, the root will be a hash filled with zeros. diff --git a/service/Cargo.toml b/service/Cargo.toml index f51d2c33d4..9cdac7a06d 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -50,9 +50,11 @@ itp-enclave-api = { path = "../core-primitives/enclave-api" } its-primitives = { path = "../sidechain/primitives"} ita-stf = { path = "../app-libs/stf" } + # scs / integritee substrate-api-client = { git = "https://github.com/scs/substrate-api-client", branch = "master" } my-node-runtime = { package = "integritee-node-runtime", git = "https://github.com/integritee-network/integritee-node", branch = "master" } +substrate-fixed = { package = "substrate-fixed", git = "https://github.com/encointer/substrate-fixed", tag = "v0.5.6" } # Substrate dependencies sp-runtime = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "master" } diff --git a/service/src/main.rs b/service/src/main.rs index f9e3254acf..294bff8dbd 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -44,6 +44,7 @@ use itp_enclave_api::{ enclave_base::EnclaveBase, remote_attestation::{RemoteAttestation, TlsRemoteAttestation}, sidechain::Sidechain, + teeracle_api::TeeracleApi, teerex_api::TeerexApi, }; use itp_settings::{ @@ -249,6 +250,7 @@ fn start_worker( + RemoteAttestation + TlsRemoteAttestation + TeerexApi + + TeeracleApi + Clone, D: BlockPruner + Sync + Send + 'static, { @@ -371,7 +373,7 @@ fn start_worker( //------------------------------------------------------------------------- // start execution of trusted getters - let trusted_getters_enclave_api = enclave; + let trusted_getters_enclave_api = enclave.clone(); thread::Builder::new() .name("trusted_getters_execution".to_owned()) .spawn(move || { @@ -393,6 +395,16 @@ fn start_worker( .unwrap(); // ------------------------------------------------------------------------ + // start update exchange rate loop + let api5 = node_api.clone(); + let market_enclave_api = enclave; + thread::Builder::new() + .name("update_market_data".to_owned()) + .spawn(move || { + start_interval_market_update(&api5, market_enclave_api.as_ref()); + }) + .unwrap(); + // ------------------------------------------------------------------------ // subscribe to events and react on firing println!("*** Subscribing to events"); let (sender, receiver) = channel(); @@ -443,6 +455,50 @@ fn start_interval_trusted_getter_execution(enclave_api: &E) { ); } +/// Send extrinsic to chain according to the market data update interval in the settings +/// with the current market data (for now only exchange rate). +fn start_interval_market_update( + api: &Api, + enclave_api: &E, +) { + use itp_settings::node::MARKET_DATA_UPDATE_INTERVAL; + + schedule_on_repeating_intervals( + || { + execute_update_market(api, enclave_api); + }, + MARKET_DATA_UPDATE_INTERVAL, + ); +} + +fn execute_update_market( + node_api: &Api, + enclave: &E, +) { + // Get market data for usd (hardcoded) + let updated_extrinsic = match enclave.update_market_data_xt(node_api.genesis_hash, "usd") { + Err(e) => { + error!("{:?}", e); + return + }, + Ok(r) => r, + }; + + let mut hex_encoded_extrinsic = hex::encode(updated_extrinsic); + hex_encoded_extrinsic.insert_str(0, "0x"); + + // Send the extrinsic to the parentchain and wait for InBlock confirmation. + println!("[>] Update the exchange rate (send the extrinsic)"); + let extrinsic_hash = match node_api.send_extrinsic(hex_encoded_extrinsic, XtStatus::InBlock) { + Err(e) => { + error!("{:?}: ", e); + return + }, + Ok(r) => r, + }; + println!("[<] Extrinsic got included into a block. Hash: {:?}\n", extrinsic_hash); +} + /// Schedules a task on perpetually looping intervals. /// /// In case the task takes longer than is scheduled by the interval duration, @@ -567,6 +623,22 @@ fn print_events(events: Events, _sender: Sender) { }, } }, + /* + /// FIXME: to add as soon as worker, node, and pallet are compatible (based on the same substrate version) + Event::Teeracle(re) => { + debug!("{:?}", re); + match &re { + my_node_runtime::pallet_teeracle::RawEvent::ExchangeRateUpdated( + currency, + new_value, + ) => { + println!("[+] Received ExchangeRateUpdated event"); + println!(" Currency: {:?}", str::from_utf8(¤cy).unwrap()); + println!(" Exchange rate: {}", new_value.to_string()); + }, + } + }, + */ _ => { trace!("Ignoring event {:?}", evr); },