From dafd1a9296a73fcb4727c9b754cf1b19a0916f4a Mon Sep 17 00:00:00 2001 From: boneyard93501 <4523011+boneyard93501@users.noreply.github.com> Date: Fri, 3 Dec 2021 01:08:04 -0600 Subject: [PATCH] Add section 5: Decentralized Oracles With Fluence And Aqua (#35) * init quickstart-5 * update consensus calc, tests * update serivce, aqua * update service and deploymnt * update aqua, air * add seq version * update aquq * update examples * add fold, no fold to aqua * update par * update service, aqua with err value * update tests with err_val * add connect check, remove peer.connect funcs * update Readme * move body to gitbook --- quickstart/5-oracle-service/Cargo.toml | 26 ++ quickstart/5-oracle-service/Readme.md | 3 + .../5-oracle-service/aqua/ts_oracle.aqua | 88 +++++ .../5-oracle-service/configs/Config.toml | 6 + .../configs/ts_oracle_cfg.json | 5 + .../5-oracle-service/deployed_service.data | 15 + quickstart/5-oracle-service/scripts/build.sh | 6 + quickstart/5-oracle-service/scripts/deploy.sh | 9 + quickstart/5-oracle-service/src/main.rs | 301 ++++++++++++++++++ 9 files changed, 459 insertions(+) create mode 100644 quickstart/5-oracle-service/Cargo.toml create mode 100644 quickstart/5-oracle-service/Readme.md create mode 100644 quickstart/5-oracle-service/aqua/ts_oracle.aqua create mode 100644 quickstart/5-oracle-service/configs/Config.toml create mode 100644 quickstart/5-oracle-service/configs/ts_oracle_cfg.json create mode 100644 quickstart/5-oracle-service/deployed_service.data create mode 100755 quickstart/5-oracle-service/scripts/build.sh create mode 100755 quickstart/5-oracle-service/scripts/deploy.sh create mode 100644 quickstart/5-oracle-service/src/main.rs diff --git a/quickstart/5-oracle-service/Cargo.toml b/quickstart/5-oracle-service/Cargo.toml new file mode 100644 index 0000000..2e9bdca --- /dev/null +++ b/quickstart/5-oracle-service/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "quickstart-5-timestamp-oracle" +version = "0.1.0" +authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"] +edition = "2018" +description = "ts-cons, a Marine wasi module" +license = "Apache-2.0" + +[[bin]] +name = "ts_oracle" +path = "src/main.rs" + +[dependencies] +marine-rs-sdk = { version = "0.6.14", features = ["logger"] } +log = "0.4.14" +# picorand = "0.1.1" +nanorand = { version = "0.6.1", default-features = false, features = [ + "wyrand", +] } + +[dev-dependencies] +marine-rs-sdk-test = "0.4.0" + +[dev] +[profile.release] +opt-level = "s" diff --git a/quickstart/5-oracle-service/Readme.md b/quickstart/5-oracle-service/Readme.md new file mode 100644 index 0000000..c75e270 --- /dev/null +++ b/quickstart/5-oracle-service/Readme.md @@ -0,0 +1,3 @@ +# 5. Decentralized Oracles With Fluence And Aqua + +See the gitbook [documentation]( https://doc.fluence.dev/docs/quick-start/5.-decentralized-oracles-with-fluence-and-aqua) for the tutorial. \ No newline at end of file diff --git a/quickstart/5-oracle-service/aqua/ts_oracle.aqua b/quickstart/5-oracle-service/aqua/ts_oracle.aqua new file mode 100644 index 0000000..acc228c --- /dev/null +++ b/quickstart/5-oracle-service/aqua/ts_oracle.aqua @@ -0,0 +1,88 @@ + +alias PeerId : string +alias Base58String : string +alias Hash : string + +-- const N_KNEIGHBORS = 19 + +data Contact: + peer_id: string + addresses: []string + +data Info: + external_addresses: []string + +service Op("op"): + string_to_b58(s: string) -> Base58String + concat_strings(a: string, b: string) -> string + +service MyOp("op"): + identity(u: u64) + identity_string(s:string) + +service MyOpBool("op"): + identity(b: bool) + +service Kademlia("kad"): + neighborhood(key: Base58String, already_hashed: ?bool, count: ?u32) -> []PeerId + merge(target: Base58String, left: []string, right: []string, count: ?u32) -> []string + +service Peer("peer"): + is_connected(peer: PeerId) -> bool + connect(id: PeerId, multiaddrs: ?[]string) -> bool + get_contact(peer: PeerId) -> Contact + identify() -> Info + timestamp_ms() -> u64 + timestamp_sec() -> u64 + timeout(duration_ms: u64, message: string) -> string + +data Consensus: + n: u32 + consensus_ts: u64 + consensus: bool + support: u32 + err_str: string + +data Oracle: + n: u32 + avg: f64 + err_str: string + +service TSOracle("service-id"): + ts_frequency(timestamps: []u64, tolerance: u32, threshold: f64, err_value:u64) -> Consensus + +func ts_oracle_with_consensus(tolerance: u32, threshold: f64, err_value:u64, node:string, oracle_service_id:string)-> Consensus, []string: + rtt = 1000 -- in ms + res: *u64 + msg = "timeout" + dead_peers: *string + on node: + k <- Op.string_to_b58(node) + nodes <- Kademlia.neighborhood(k, nil, nil) + for n <- nodes par: + status: *string + on n: + res <- Peer.timestamp_ms() + status <<- "success" + par status <- Peer.timeout(rtt, msg) + if status! != "success": + res <<- err_value + dead_peers <<- n + + MyOp.identity(res!19) + TSOracle oracle_service_id + consensus <- TSOracle.ts_frequency(res, tolerance, threshold, err_value) + <- consensus, dead_peers + +func ts_oracle_with_bool(tolerance: u32, threshold: f64, err_value:u64, node:string, oracle_service_id:string)-> []bool, []string: + res: *bool + peers: *string + dead_peers: *string + on node: + k <- Op.string_to_b58(node) + nodes <- Kademlia.neighborhood(k, nil, nil) + for n <- nodes par: + res <- Peer.connect(n, nil) + peers <<- n + MyOpBool.identity(res!19) + <- res, peers \ No newline at end of file diff --git a/quickstart/5-oracle-service/configs/Config.toml b/quickstart/5-oracle-service/configs/Config.toml new file mode 100644 index 0000000..29f996a --- /dev/null +++ b/quickstart/5-oracle-service/configs/Config.toml @@ -0,0 +1,6 @@ +modules_dir = "artifacts/" + +[[module]] +name = "ts_oracle" +mem_pages_count = 20 +logger_enabled = true diff --git a/quickstart/5-oracle-service/configs/ts_oracle_cfg.json b/quickstart/5-oracle-service/configs/ts_oracle_cfg.json new file mode 100644 index 0000000..cd9ce20 --- /dev/null +++ b/quickstart/5-oracle-service/configs/ts_oracle_cfg.json @@ -0,0 +1,5 @@ +{ + "name": "ts_oracle", + "mem_pages_count": 5, + "logger_enabled": false +} diff --git a/quickstart/5-oracle-service/deployed_service.data b/quickstart/5-oracle-service/deployed_service.data new file mode 100644 index 0000000..3825b57 --- /dev/null +++ b/quickstart/5-oracle-service/deployed_service.data @@ -0,0 +1,15 @@ +client seed: Ek6bAgs1ymxZhfnH98WNeQ8dEEUuRJ5B8aN7mfgvYSMt +client peerId: 12D3KooWLMsHa9CY5efWkBokbryrpUkE7onTFGK6oTvTuovVUtDp +relay peerId: 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi +service id: ec347996-ebd6-4630-8e45-46c2dfcaa3f5 +service created successfully +client seed: FXcSUpuwDPftAMVX5qWiAkuf3f6uSqf7zujw6uM2PzqE +client peerId: 12D3KooWSA6zFEWoVazQGR3nfRJCVfZbWuP9UgrTjZZaf62FbtCh +relay peerId: 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi +service id: 754db64e-fe14-49ff-8e08-1a1f7082781f +service created successfully +client seed: 7UNmJPMWdLmrwAtGrpJXNrrcK7tEZHCjvKbGdSzEizEr +client peerId: 12D3KooWBeEsUnMV9MZ6QGMfaxcTvw8mGFEMGDe7rhKN8RQv1Gs8 +relay peerId: 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi +service id: 61a86f67-ffc2-4dea-8746-fd4f04d9c75b +service created successfully diff --git a/quickstart/5-oracle-service/scripts/build.sh b/quickstart/5-oracle-service/scripts/build.sh new file mode 100755 index 0000000..7ca1588 --- /dev/null +++ b/quickstart/5-oracle-service/scripts/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash -o errexit -o nounset -o pipefail + +mkdir -p artifacts +rm -f artifacts/*.wasm +marine build --release +cp target/wasm32-wasi/release/ts_oracle.wasm artifacts/ diff --git a/quickstart/5-oracle-service/scripts/deploy.sh b/quickstart/5-oracle-service/scripts/deploy.sh new file mode 100755 index 0000000..ca84776 --- /dev/null +++ b/quickstart/5-oracle-service/scripts/deploy.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash -o errexit -o nounset -o pipefail + +fldist --node-id 12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi \ + new_service \ + --ms artifacts/ts_oracle.wasm:configs/ts_oracle_cfg.json \ + --name ts-oracle \ + --verbose \ +>> \ +deployed_service.data \ No newline at end of file diff --git a/quickstart/5-oracle-service/src/main.rs b/quickstart/5-oracle-service/src/main.rs new file mode 100644 index 0000000..caeea49 --- /dev/null +++ b/quickstart/5-oracle-service/src/main.rs @@ -0,0 +1,301 @@ +/* + * Copyright 2021 Fluence Labs Limited + * + * 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 marine_rs_sdk::marine; +use marine_rs_sdk::module_manifest; +use marine_rs_sdk::WasmLoggerBuilder; +// use picorand::{PicoRandGenerate, WyRand, RNG}; +use nanorand::{Rng, WyRand}; + +module_manifest!(); + +pub fn main() { + WasmLoggerBuilder::new().build().unwrap(); +} + +#[marine] +#[derive(Default, Debug)] +pub struct Oracle { + pub n: u32, + pub avg: f64, + pub err_str: String, +} +#[marine] +#[derive(Default, Debug)] +pub struct Consensus { + pub n: u32, + pub consensus_ts: u64, + pub consensus: bool, + pub support: u32, + pub err_str: String, +} + +#[marine] +pub fn ts_avg(timestamps: Vec, min_points: u32) -> Oracle { + if min_points as usize == 0 { + let err_str = "Need to process at least one point".to_string(); + return Oracle { + err_str, + n: timestamps.len() as u32, + ..<_>::default() + }; + } + + if timestamps.len() < min_points as usize { + return Oracle { + err_str: format!( + "Expected at least {} points but only got {}.", + min_points, + timestamps.len(), + ), + n: timestamps.len() as u32, + ..<_>::default() + }; + } + let avg = timestamps.iter().sum::() as f64 / timestamps.len() as f64; + Oracle { + n: timestamps.len() as u32, + avg, + ..<_>::default() + } +} + +#[marine] +fn ts_frequency( + mut timestamps: Vec, + tolerance: u32, + threshold: f64, + err_value: u64, +) -> Consensus { + timestamps.retain(|&ts| ts != err_value); + if timestamps.len() == 0 { + return Consensus { + err_str: "Array must have at least one element".to_string(), + ..<_>::default() + }; + } + + if timestamps.len() == 1 { + return Consensus { + n: 1, + consensus_ts: timestamps[0], + consensus: true, + support: 1, + ..<_>::default() + }; + } + + if threshold < 0f64 || threshold > 1f64 { + return Consensus { + err_str: "Threshold needs to be between [0.0,1.0]".to_string(), + ..<_>::default() + }; + } + + let rnd_seed: u64 = timestamps.iter().sum(); + let mut rng = WyRand::new_seed(rnd_seed); + let rnd_idx = rng.generate_range(0..timestamps.len()); + let consensus_ts = timestamps.swap_remove(rnd_idx); + let mut support: u32 = 0; + for ts in timestamps.iter() { + if ts <= &(consensus_ts + tolerance as u64) && ts >= &(consensus_ts - tolerance as u64) { + support += 1; + } + } + + let mut consensus = false; + if (support as f64 / timestamps.len() as f64) >= threshold { + consensus = true; + } + + Consensus { + n: timestamps.len() as u32, + consensus_ts, + consensus, + support, + err_str: "".to_string(), + } +} + +#[cfg(test)] +mod tests { + use marine_rs_sdk_test::marine_test; + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn test_mean_good(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![1u64, 2u64, 3u64]; + let n = 3; + let res = ts_consensus.ts_avg(data, n); + assert_eq!(res.avg, 2f64); + assert_eq!(res.err_str.len(), 0) + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn test_mean_fail(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![1u64, 2u64, 3u64]; + let n = 0; + let res = ts_consensus.ts_avg(data, n); + assert_eq!( + res.err_str, + "Need to process at least one point".to_string() + ); + + let data = vec![1u64]; + let n = 3; + let res = ts_consensus.ts_avg(data, n); + assert!(res.err_str.contains("Expected at least")); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn test_err_val(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![0, 1, 1, 1]; + let tolerance = 3u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.consensus_ts, 1); + assert_eq!(res.n, data.len() as u32 - 2); + assert_eq!(res.err_str.len(), 0); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_good_consensus(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![ + 1636961969u64, + 1636961970u64, + 1636961969u64, + 1636961968u64, + 1636961969u64, + 1636961971u64, + ]; + let tolerance = 3u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.n, (data.len() - 1) as u32); + assert_eq!(res.support, (data.len() - 1) as u32); + assert_eq!(res.err_str.len(), 0); + assert!(res.consensus); + assert!(data.contains(&res.consensus_ts)); + } + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_good_no_support(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![ + 1636961969u64, + 1636961970u64, + 1636961969u64, + 1636961968u64, + 1636961969u64, + 1636961971u64, + ]; + let tolerance = 0u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.n, (data.len() - 1) as u32); + assert_eq!(res.support, 0u32); + assert_eq!(res.err_str.len(), 0); + assert!(data.contains(&res.consensus_ts)); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_good_consensus_true( + ts_consensus: marine_test_env::ts_oracle::ModuleInterface, + ) { + let data = vec![ + 1636961969u64, + 1636961970u64, + 1636961969u64, + 1636961968u64, + 1636961969u64, + 1636961969u64, + 1636961971u64, + ]; + let tolerance = 3u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.n, (data.len() - 1) as u32); + assert_eq!(res.support, (data.len() - 1) as u32); + assert_eq!(res.err_str.len(), 0); + assert!(data.contains(&res.consensus_ts)); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_good_consensus_false( + ts_consensus: marine_test_env::ts_oracle::ModuleInterface, + ) { + let data = vec![ + 1636961969u64, + 1636961970u64, + 1636961969u64, + 1636961968u64, + 1636961969u64, + 1636961971u64, + ]; + let tolerance = 0u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.n, (data.len() - 1) as u32); + assert_eq!(res.support, 0u32); + assert_eq!(res.err_str.len(), 0); + assert!(!res.consensus); + assert!(data.contains(&res.consensus_ts)); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_good_no_consensus(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![1636961965u64, 1636961969u64, 1636961972u64]; + let tolerance = 1u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.n, (data.len() - 1) as u32); + assert_eq!(res.support, 0 as u32); + assert_eq!(res.err_str.len(), 0); + assert!(data.contains(&res.consensus_ts)); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_good_one(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![1636961969u64]; + let tolerance = 3u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data.clone(), tolerance, threshold, err_val); + assert_eq!(res.consensus_ts, data[0]); + assert_eq!(res.support, data.len() as u32); + assert_eq!(res.n, data.len() as u32); + assert_eq!(res.err_str.len(), 0usize); + } + + #[marine_test(config_path = "../configs/Config.toml", modules_dir = "../artifacts")] + fn ts_validation_bad_empty(ts_consensus: marine_test_env::ts_oracle::ModuleInterface) { + let data = vec![]; + let tolerance = 3u32; + let threshold: f64 = 0.66; + let err_val: u64 = 0; + let res = ts_consensus.ts_frequency(data, tolerance, threshold, err_val); + assert_eq!(res.n, 0); + assert_eq!(res.support, 0); + assert_eq!(res.consensus_ts, 0); + assert_eq!( + res.err_str, + "Array must have at least one element".to_string() + ); + } +}