diff --git a/CHANGELOG.md b/CHANGELOG.md index e4d4fbaed..c5d4bfdbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ runtime ### Added - Protocol message versioning ([#1140](https://github.com/entropyxyz/entropy-core/pull/1140)) +- CLI command to get oracle headings ([#1170](https://github.com/entropyxyz/entropy-core/pull/1170)) - Add TSS endpoint to get TDX quote ([#1173](https://github.com/entropyxyz/entropy-core/pull/1173)) ### Changed diff --git a/Cargo.lock b/Cargo.lock index d44be9ea4..989a41f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2522,6 +2522,7 @@ dependencies = [ "hex", "js-sys", "num", + "parity-scale-codec", "rand", "rand_core 0.6.4", "reqwest", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 1b1ac87a0..869bc5dd1 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -19,18 +19,19 @@ futures ="0.3" sp-core ={ version="31.0.0", default-features=false, features=["full_crypto", "serde"] } tracing ="0.1.37" rand ={ version="0.8", default-features=false } +anyhow ="1.0.93" # Present when "full-client" feature is active -blake2 ={ version="0.10.4", optional=true } -rand_core ={ version="0.6.4", optional=true } -serde_json ={ version="1.0", optional=true } -x25519-dalek ={ version="2.0.1", features=["static_secrets"], optional=true } -entropy-protocol={ version="0.3.0", path="../protocol", optional=true, default-features=false } -reqwest ={ version="0.12.9", features=["json", "stream"], optional=true } -base64 ={ version="0.22.0", optional=true } -synedrion ={ version="0.2.0-beta.0", optional=true } -hex ={ version="0.4.3", optional=true } -anyhow ="1.0.93" +blake2 ={ version="0.10.4", optional=true } +rand_core ={ version="0.6.4", optional=true } +serde_json ={ version="1.0", optional=true } +x25519-dalek ={ version="2.0.1", features=["static_secrets"], optional=true } +entropy-protocol ={ version="0.3.0", path="../protocol", optional=true, default-features=false } +reqwest ={ version="0.12.9", features=["json", "stream"], optional=true } +base64 ={ version="0.22.0", optional=true } +synedrion ={ version="0.2.0-beta.0", optional=true } +hex ={ version="0.4.3", optional=true } +parity-scale-codec={ version="3.6.3", default-features=false, optional=true } # Only for the browser js-sys={ version="0.3.72", optional=true } @@ -64,6 +65,7 @@ full-client=[ "dep:base64", "dep:synedrion", "dep:hex", + "dep:parity-scale-codec", ] full-client-native=["full-client", "entropy-protocol/server"] full-client-wasm=["full-client", "entropy-protocol/wasm", "dep:js-sys"] diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 9061fd270..062de590a 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -21,6 +21,7 @@ pub use crate::{ }; use anyhow::anyhow; pub use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams}; +use parity_scale_codec::Decode; use rand::Rng; use std::str::FromStr; pub use synedrion::KeyShare; @@ -444,3 +445,21 @@ pub async fn request_attestation( ) -> Result<[u8; 32], ClientError> { Ok(user::request_attestation(api, rpc, attestee).await?) } + +/// Get oracle data headings +/// This is useful for program developers to know what oracle data is available +pub async fn get_oracle_headings( + api: &OnlineClient, + _rpc: &LegacyRpcMethods, +) -> Result, ClientError> { + let storage_address = entropy::storage().oracle().oracle_data_iter(); + let mut iter = api.storage().at_latest().await?.iter(storage_address).await?; + let mut headings = Vec::new(); + while let Some(Ok(kv)) = iter.next().await { + // Key is: storage_address || 128 bit hash || key + let mut input = &kv.key_bytes[32 + 16 + 1..]; + let heading = String::decode(&mut input)?; + headings.push(heading); + } + Ok(headings) +} diff --git a/crates/client/src/errors.rs b/crates/client/src/errors.rs index 9cd514608..c2f4ab241 100644 --- a/crates/client/src/errors.rs +++ b/crates/client/src/errors.rs @@ -119,6 +119,8 @@ pub enum ClientError { BadVerifyingKeyLength, #[error("There are no validators which can act as a relay node for signature requests")] NoNonSigningValidators, + #[error("Scale decode: {0}")] + Codec(#[from] parity_scale_codec::Error), #[error("Attestation request: {0}")] AttestationRequest(#[from] AttestationRequestError), } diff --git a/crates/client/src/tests.rs b/crates/client/src/tests.rs index db3ab5e94..1fa9c290f 100644 --- a/crates/client/src/tests.rs +++ b/crates/client/src/tests.rs @@ -11,8 +11,8 @@ use crate::{ }, get_api, get_rpc, EntropyConfig, }, - change_endpoint, change_threshold_accounts, register, remove_program, request_attestation, - store_program, + change_endpoint, change_threshold_accounts, get_oracle_headings, register, remove_program, + request_attestation, store_program, substrate::query_chain, update_programs, }; @@ -274,3 +274,22 @@ async fn test_remove_program_reference_counter() { // We can now remove the program because no-one is using it remove_program(&api, &rpc, &program_owner, program_pointer).await.unwrap(); } + +#[tokio::test] +#[serial] +async fn test_get_oracle_headings() { + let force_authoring = true; + let substrate_context = &test_node_process_testing_state(force_authoring).await[0]; + let api = get_api(&substrate_context.ws_url).await.unwrap(); + let rpc = get_rpc(&substrate_context.ws_url).await.unwrap(); + + let mut current_block = 0; + while current_block < 2 { + let finalized_head = rpc.chain_get_finalized_head().await.unwrap(); + current_block = rpc.chain_get_header(Some(finalized_head)).await.unwrap().unwrap().number; + } + + let headings = get_oracle_headings(&api, &rpc).await.unwrap(); + + assert_eq!(headings, vec!["block_number_entropy".to_string()]); +} diff --git a/crates/test-cli/src/lib.rs b/crates/test-cli/src/lib.rs index 5a64df243..8b76fe5f8 100644 --- a/crates/test-cli/src/lib.rs +++ b/crates/test-cli/src/lib.rs @@ -27,9 +27,9 @@ use entropy_client::{ EntropyConfig, }, client::{ - change_endpoint, change_threshold_accounts, get_accounts, get_api, get_programs, get_rpc, - jumpstart_network, register, remove_program, sign, store_program, update_programs, - VERIFYING_KEY_LENGTH, + change_endpoint, change_threshold_accounts, get_accounts, get_api, get_oracle_headings, + get_programs, get_rpc, jumpstart_network, register, remove_program, sign, store_program, + update_programs, VERIFYING_KEY_LENGTH, }, }; pub use entropy_shared::PROGRAM_VERSION_NUMBER; @@ -189,6 +189,10 @@ enum CliCommand { #[arg(short, long)] mnemonic_option: Option, }, + /// Get headings of oracle data + /// + /// This is useful for program developers to know what oracle data is available. + GetOracleHeadings, /// Request a TDX quote from a TSS server and write it to a file. GetTdxQuote { /// The socket address of the TS server, eg: `127.0.0.1:3002` @@ -568,6 +572,10 @@ pub async fn run_command( Ok("Succesfully jumpstarted network.".to_string()) } }, + CliCommand::GetOracleHeadings => { + let headings = get_oracle_headings(&api, &rpc).await?; + Ok(serde_json::to_string_pretty(&headings)?) + }, CliCommand::GetTdxQuote { tss_endpoint, output_filename } => { let quote_bytes = reqwest::get(format!("http://{}/attest", tss_endpoint)).await?.bytes().await?;