diff --git a/Cargo.lock b/Cargo.lock index 5a802b0aa..343558ce8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1100,6 +1100,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "downcast" version = "0.11.0" @@ -2574,12 +2580,13 @@ dependencies = [ [[package]] name = "integritee-cli" -version = "0.16.6" +version = "0.16.7" dependencies = [ "array-bytes 6.1.0", "base58", "chrono 0.4.26", "clap 3.2.25", + "dotenv", "enclave-bridge-primitives", "env_logger 0.9.3", "hdrhistogram", @@ -2632,7 +2639,7 @@ dependencies = [ [[package]] name = "integritee-service" -version = "0.16.6" +version = "0.16.7" dependencies = [ "anyhow", "async-trait", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 288c2af89..050527989 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "integritee-cli" -version = "0.16.6" +version = "0.16.7" authors = ["Integritee AG "] edition = "2021" @@ -10,6 +10,7 @@ base58 = "0.2" chrono = "*" clap = { version = "3.1.6", features = ["derive"] } codec = { version = "3.0.0", package = "parity-scale-codec", features = ["derive"] } +dotenv = "0.15" env_logger = "0.9" hdrhistogram = "7.5.0" hex = "0.4.2" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 3d40b5a26..60ef8a2a2 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -35,6 +35,8 @@ mod benchmark; mod command_utils; #[cfg(feature = "evm")] mod evm; +mod llm_handler; +mod notes_handler; #[cfg(feature = "teeracle")] mod oracle; mod trusted_assets; diff --git a/cli/src/llm_handler.rs b/cli/src/llm_handler.rs new file mode 100644 index 000000000..4ded4343e --- /dev/null +++ b/cli/src/llm_handler.rs @@ -0,0 +1,176 @@ +/* + Copyright 2021 Integritee 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 codec::Decode; +use ita_stf::TrustedCall; +use itp_types::{AccountId, Moment}; +use log::{debug, trace, warn}; +use pallet_notes::{TimestampedTrustedNote, TrustedNote}; +use prometheus::register_counter; +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +// ChatGPT API types +#[derive(Serialize)] +struct ChatRequest<'a> { + model: &'a str, + messages: Vec>, + max_tokens: u16, +} + +#[derive(Debug, Serialize)] +struct Message<'a> { + role: &'a str, + content: &'a str, +} + +#[derive(Deserialize)] +struct ChatResponse { + choices: Vec, + usage: Option, +} + +#[derive(Deserialize)] +struct Choice { + message: MessageContent, +} + +#[derive(Deserialize)] +struct MessageContent { + content: String, +} + +#[derive(Deserialize)] +struct Usage { + prompt_tokens: u32, + completion_tokens: u32, + total_tokens: u32, +} + +pub struct LLMHandler { + api_key: String, + metrics_prompt_tokens_counter: prometheus::Counter, + metrics_completion_tokens_counter: prometheus::Counter, +} + +impl LLMHandler { + pub fn new(api_key: String) -> Self { + let metrics_prompt_tokens_counter = + register_counter!("llm_prompt_tokens_counter", "Number of used prompt tokens").unwrap(); + let metrics_completion_tokens_counter = + register_counter!("llm_completion_tokens_counter", "Number of used completion tokens") + .unwrap(); + LLMHandler { api_key, metrics_prompt_tokens_counter, metrics_completion_tokens_counter } + } + + pub async fn process_ai_prompt( + &self, + prompt: String, + system_briefing: String, + model: String, + bot_account: &AccountId, + history: Vec>, + ) -> String { + let mut messages: Vec = + vec![Message { role: "system", content: system_briefing.as_str() }]; + history.iter().for_each(|note| { + if let TrustedNote::SuccessfulTrustedCall(ref tc) = note.note { + if let Ok(TrustedCall::send_note(from, _to, msg)) = + TrustedCall::decode(&mut tc.as_slice()) + { + let msg_str = String::from_utf8(msg).unwrap_or_else(|_| { + warn!("Failed to decode message as UTF-8, using empty string"); + String::new() + }); + if *bot_account == from { + messages.push(Message { + role: "assistant", + content: Box::leak(msg_str.into_boxed_str()), + }); + } else { + messages.push(Message { + role: "user", + content: Box::leak(msg_str.into_boxed_str()), + }); + } + } else { + warn!("Failed to decode TrustedCall::send_note from note: {:?}", note); + } + } + }); + messages.push(Message { role: "user", content: prompt.as_str() }); + trace!("Sending prompt to LLM: {:?}", messages); + let request_body = ChatRequest { + model: model.as_str(), + messages, + max_tokens: 70, // Roughly ≈ 140 characters + }; + let client = Client::new(); + let mut attempts = 0; + let response = loop { + match client + .post("https://api.openai.com/v1/chat/completions") + .bearer_auth(self.api_key.clone()) + .json(&request_body) + .send() + .await + { + Ok(resp) => + if resp.status().is_success() { + break resp + } else { + warn!("Received non-success status code: {}", resp.status()); + return String::from("Error: Non-success status code received from LLM API") + }, + Err(e) => { + attempts += 1; + warn!("Failed to send request to LLM API (attempt {}): {:?}", attempts, e); + if attempts >= 3 { + return String::from( + "Error: Failed to send request to LLM API after 3 attempts", + ) + } + }, + } + }; + + debug!("Got response from LLM: {:?}", response); + + let json: ChatResponse = match response.json().await { + Ok(parsed_json) => parsed_json, + Err(e) => { + warn!("Failed to parse LLM response JSON: {:?}", e); + return String::from("Error: Failed to parse LLM response JSON") + }, + }; + if let Some(usage) = json.usage { + debug!( + "Token usage - Prompt tokens: {}, Completion tokens: {}, Total tokens: {}", + usage.prompt_tokens, usage.completion_tokens, usage.total_tokens + ); + self.metrics_prompt_tokens_counter.inc_by(usage.prompt_tokens.into()); + self.metrics_completion_tokens_counter.inc_by(usage.completion_tokens.into()); + } + + let prompt_reply = { + let content = json.choices[0].message.content.trim(); + let cropped = &content.as_bytes()[..std::cmp::min(200, content.len())]; + String::from_utf8_lossy(cropped).to_string() + }; + prompt_reply + } +} diff --git a/cli/src/notes_handler.rs b/cli/src/notes_handler.rs new file mode 100644 index 000000000..4a473371a --- /dev/null +++ b/cli/src/notes_handler.rs @@ -0,0 +1,211 @@ +/* + Copyright 2021 Integritee 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::{trusted_cli::TrustedCli, trusted_operation::perform_trusted_operation, Cli}; +use codec::Decode; +use ita_stf::{Getter, PublicGetter, TrustedCall, TrustedCallSigned, TrustedGetter}; +use itp_stf_primitives::types::{KeyPair, TrustedOperation}; +use itp_types::{AccountId, Moment}; +use log::{debug, trace, warn}; +use pallet_notes::{BucketIndex, BucketRange, TimestampedTrustedNote, TrustedNote}; +use sp_core::sr25519 as sr25519_core; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum NoteCategory { + Other, + ConversationWith(AccountId), // non-financial notes (chat messages) + //TransfersWith(AccountId), // native or assets +} + +pub struct NotesHandler<'a> { + cli: &'a Cli, + trusted_args: &'a TrustedCli, + account: AccountId, + signer: sr25519_core::Pair, + bucket_range: BucketRange, + pub last_fetched_timestamp: Moment, + pub last_fetched_bucket_index: BucketIndex, + pub conversation_counterparties: HashSet, + notes: HashMap>>, +} + +impl<'a> NotesHandler<'a> { + pub fn new( + cli: &'a Cli, + trusted_args: &'a TrustedCli, + account: AccountId, + signer: sr25519_core::Pair, + ) -> Self { + let bucket_range = + get_note_buckets_info(cli, trusted_args).expect("Failed to get note buckets info"); + let notes = HashMap::new(); + let last_fetched_timestamp = 0u64; + let last_fetched_bucket_index = 0u32; + let conversation_counterparties = HashSet::new(); + NotesHandler { + cli, + trusted_args, + account, + signer, + bucket_range, + last_fetched_timestamp, + last_fetched_bucket_index, + conversation_counterparties, + notes, + } + } + + pub fn fetch_history(&mut self) { + let first_bucket_index = + self.bucket_range.maybe_first.map(|bucket| bucket.index).unwrap_or_default(); + let last_bucket_index = + self.bucket_range.maybe_last.map(|bucket| bucket.index).unwrap_or_default(); + for bucket_index in first_bucket_index..=last_bucket_index { + let top = TrustedOperation::::get(Getter::trusted( + TrustedGetter::notes_for(self.account.clone(), bucket_index) + .sign(&KeyPair::Sr25519(Box::new(self.signer.clone()))), + )); + let maybe_notes: Option>> = + perform_trusted_operation(self.cli, self.trusted_args, &top).ok(); + if let Some(notes) = maybe_notes { + notes.iter().for_each(|note| self.store_note(note.clone())) + }; + self.last_fetched_bucket_index = bucket_index; + } + } + + pub fn update(&mut self) { + self.bucket_range = get_note_buckets_info(self.cli, self.trusted_args) + .expect("Failed to get note buckets info"); + let last_bucket_index = + self.bucket_range.maybe_last.map(|bucket| bucket.index).unwrap_or_default(); + + for bucket_index in self.last_fetched_bucket_index..=last_bucket_index { + let last_fetched_timestamp = self.last_fetched_timestamp; + let top = TrustedOperation::::get(Getter::trusted( + TrustedGetter::notes_for(self.account.clone(), bucket_index) + .sign(&KeyPair::Sr25519(Box::new(self.signer.clone()))), + )); + if let Ok(notes) = perform_trusted_operation::>>( + self.cli, + self.trusted_args, + &top, + ) { + notes + .iter() + .filter(|¬e| note.timestamp > last_fetched_timestamp) + .for_each(|note| self.store_note(note.clone())); + }; + self.last_fetched_bucket_index = bucket_index; + } + } + fn store_note(&mut self, note: TimestampedTrustedNote) { + let category = match note.note { + TrustedNote::SuccessfulTrustedCall(ref tc) => { + let call = TrustedCall::decode(&mut tc.as_slice()) + .map_err(|e| { + warn!("error decoding TrustedNote::SuccessfulTrustedCall: {:?}", e); + e + }) + .ok(); + if call.is_none() { + return + } + match call.unwrap() { + TrustedCall::send_note(from, to, msg) => { + let counterparty = + if from == self.account { to.clone() } else { from.clone() }; + self.conversation_counterparties.insert(counterparty.clone()); + trace!( + "Storing note from {:?} to {:?}: {}", + from, + to, + String::from_utf8_lossy(&msg) + ); + NoteCategory::ConversationWith(counterparty) + }, + _ => NoteCategory::Other, + } + }, + _ => NoteCategory::Other, + }; + if note.timestamp > self.last_fetched_timestamp { + self.last_fetched_timestamp = note.timestamp; + } + self.notes.entry(category).or_default().push(note); + } + + pub fn conversation_with( + &self, + counterparty: &AccountId, + maybe_since: Option, + ) -> Vec> { + let since = maybe_since.unwrap_or_default(); + if let Some(notes) = self.notes.get(&NoteCategory::ConversationWith(counterparty.clone())) { + notes.iter().filter(|note| note.timestamp >= since).cloned().collect() + } else { + Vec::new() + } + } + + pub fn unanswered_conversation_with( + &self, + counterparty: &AccountId, + maybe_since: Option, + ) -> Vec> { + let conversation = self.conversation_with(counterparty, maybe_since); + let my_last_note = conversation + .iter() + .cloned() + .filter(|note| { + if let TrustedNote::SuccessfulTrustedCall(ref tc) = note.note { + if let Ok(TrustedCall::send_note(from, _to, _msg)) = + TrustedCall::decode(&mut tc.as_slice()) + { + self.account == from + } else { + false + } + } else { + false + } + }) + .last(); + if let Some(last_note) = my_last_note { + conversation + .into_iter() + .filter(|note| note.timestamp > last_note.timestamp) + .collect() + } else { + conversation + } + } +} + +pub(crate) fn get_note_buckets_info( + cli: &Cli, + trusted_args: &TrustedCli, +) -> Option> { + let top = TrustedOperation::::get(Getter::public( + PublicGetter::note_buckets_info, + )); + let maybe_bucket_range: Option> = + perform_trusted_operation(cli, trusted_args, &top).ok(); + debug!("maybe_bucket_range: {:?}", maybe_bucket_range); + maybe_bucket_range +} diff --git a/cli/src/trusted_base_cli/commands/chatbot.rs b/cli/src/trusted_base_cli/commands/chatbot.rs new file mode 100644 index 000000000..05c7f592d --- /dev/null +++ b/cli/src/trusted_base_cli/commands/chatbot.rs @@ -0,0 +1,227 @@ +/* + Copyright 2021 Integritee 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::{ + get_basic_signing_info_from_args, + llm_handler::LLMHandler, + notes_handler::NotesHandler, + trusted_cli::TrustedCli, + trusted_command_utils::get_trusted_account_info, + trusted_operation::{perform_trusted_operation, send_direct_request}, + Cli, CliResult, CliResultOk, +}; +use chrono::Timelike; +use codec::Decode; +use dotenv::dotenv; +use ita_stf::{ + Getter, ParentchainsInfo, PublicGetter, TrustedCall, TrustedCallSigned, STF_TX_FEE_UNIT_DIVIDER, +}; +use itp_stf_primitives::{ + traits::TrustedCallSigning, + types::{KeyPair, TrustedOperation}, +}; +use log::{debug, info, warn}; +use pallet_notes::TrustedNote; +use prometheus::{ + register_counter, register_gauge, register_histogram, Encoder, HistogramOpts, TextEncoder, +}; +use std::{env, time::Duration}; +use tokio::time::sleep; +use warp::Filter; + +const LATENCY_HISTOGRAM_BUCKETS: [f64; 9] = [0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 60.0, 600.0]; + +#[derive(Parser)] +pub struct ChatbotCommand { + /// chatbot AccountId in ss58check format. must have enough funds on shard + account: String, + /// probing interval in seconds. default is 3600 (1h) + #[clap(long)] + interval: Option, + /// port to use for serving prometheus metrics. default is 9090 + #[clap(long)] + prometheus_port: Option, + /// session proxy who can sign on behalf of the account + #[clap(long)] + session_proxy: Option, +} + +impl ChatbotCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + dotenv().ok(); + let api_key = env::var("OPENAI_API_KEY").unwrap(); + let ai_model = env::var("OPENAI_MODEL").unwrap(); + let ai_briefing = env::var("OPENAI_SYSTEM_BRIEFING").unwrap(); + let (bot_account, signer, mrenclave, shard) = + get_basic_signing_info_from_args!(self.account, self.session_proxy, cli, trusted_args); + + let interval = self.interval.unwrap_or(1); + let messages_received_counter = register_counter!( + "messages_received_counter", + "Number of messages received since startup" + ) + .unwrap(); + let conversation_counterparties_gauge = register_gauge!( + "conversation_counterparties_gauege", + "Number of counterparties the bot has a conversation with" + ) + .unwrap(); + let response_time_histogram = register_histogram!(HistogramOpts::new( + "response_time_seconds_histogram", + "Histogram of response times in seconds" + ) + .buckets(LATENCY_HISTOGRAM_BUCKETS.into())) + .unwrap(); + let queue_processing_time_histogram = register_histogram!(HistogramOpts::new( + "queue_processing_time_seconds_histogram", + "Histogram of queue_processing times in seconds" + ) + .buckets(LATENCY_HISTOGRAM_BUCKETS.into())) + .unwrap(); + + let top = TrustedOperation::::get(Getter::public( + PublicGetter::parentchains_info, + )); + let parentchains_info: ParentchainsInfo = + perform_trusted_operation(cli, trusted_args, &top).unwrap(); + let decimals = parentchains_info.get_shielding_target_decimals().unwrap_or(12); + println!("Shielding target decimals: {}", decimals); + + let rt = tokio::runtime::Runtime::new().unwrap(); + + let mut notes_handler = + NotesHandler::new(cli, trusted_args, bot_account.clone(), signer.clone()); + notes_handler.fetch_history(); + info!( + "fetched existing conversation history with {} counterparties", + notes_handler.conversation_counterparties.len() + ); + + let llm_handler = LLMHandler::new(api_key); + + rt.block_on(async { + let metrics_route = warp::path("metrics").map(move || { + let encoder = TextEncoder::new(); + let metric_families = prometheus::gather(); + let mut buffer = Vec::new(); + encoder.encode(&metric_families, &mut buffer).unwrap(); + warp::http::Response::builder() + .header("Content-Type", encoder.format_type()) + .body(buffer) + }); + + tokio::spawn( + warp::serve(metrics_route) + .run(([0, 0, 0, 0], self.prometheus_port.unwrap_or(9090))), + ); + loop { + let processing_start_time = chrono::Utc::now(); + let account_info = + get_trusted_account_info(cli, trusted_args, &bot_account, &signer) + .unwrap_or_default(); + let decimal_balance_free = + account_info.data.free as f64 / 10u128.pow(decimals as u32) as f64; + let nonce = account_info.nonce; + + notes_handler.update(); + conversation_counterparties_gauge + .set(notes_handler.conversation_counterparties.len() as f64); + + for counterparty in notes_handler.conversation_counterparties.iter() { + if decimal_balance_free < 2f64 / STF_TX_FEE_UNIT_DIVIDER as f64 { + warn!("Account has insufficient funds to reply"); + continue + }; + let conversation = notes_handler.conversation_with(counterparty, None); + let unanswered_notes = + notes_handler.unanswered_conversation_with(counterparty, None); + if !unanswered_notes.is_empty() { + messages_received_counter.inc_by(f64::from(unanswered_notes.len() as u32)); + println!( + "Unanswered notes with {}: {}", + counterparty, + unanswered_notes.len() + ); + // concatenate all unanswerered notes + let prompt = unanswered_notes + .iter() + .map(|note| { + if let TrustedNote::SuccessfulTrustedCall(ref tc) = note.note { + if let Ok(TrustedCall::send_note(_, _, msg)) = + TrustedCall::decode(&mut tc.as_slice()) + { + String::from_utf8(msg) + .unwrap_or_else(|_| "Invalid UTF-8".to_string()) + } else { + "".into() + } + } else { + "".into() + } + }) + .collect::>() + .join("\n"); + let prompt_reply = llm_handler + .process_ai_prompt( + prompt, + ai_briefing.clone(), + ai_model.clone(), + &bot_account, + conversation, + ) + .await; + let top = TrustedCall::send_note( + bot_account.clone(), + counterparty.clone(), + prompt_reply.clone().into(), + ) + .sign( + &KeyPair::Sr25519(Box::new(signer.clone())), + nonce, + &mrenclave, + &shard, + ) + .into_trusted_operation(trusted_args.direct); + if send_direct_request(cli, trusted_args, &top).is_ok() { + let now = chrono::Utc::now().timestamp_millis() as f64; + let response_time_s = (now + - unanswered_notes.last().expect("can't be empty here").timestamp + as f64) / 1000.0; + println!("Sent a reply in {}s: {}", response_time_s, prompt_reply); + response_time_histogram.observe(response_time_s); + } else { + println!("Failed to send echo"); + } + } + } + let elapsed_millis = chrono::Utc::now() + .signed_duration_since(processing_start_time) + .num_milliseconds(); + queue_processing_time_histogram.observe(elapsed_millis as f64 / 1000.0); + let pause_millis = (1000 * interval).saturating_sub(elapsed_millis.max(0) as u64); + debug!("Sleeping for {}ms", pause_millis); + if processing_start_time.num_seconds_from_midnight() % 60 == 0 { + println!( + "[Heartbeat] Processed queue in {}ms, sleeping for {}ms", + elapsed_millis, pause_millis + ); + } + sleep(Duration::from_millis(pause_millis)).await; + } + }); + Ok(CliResultOk::None) + } +} diff --git a/cli/src/trusted_base_cli/commands/mod.rs b/cli/src/trusted_base_cli/commands/mod.rs index a26847db4..ff2a872b5 100644 --- a/cli/src/trusted_base_cli/commands/mod.rs +++ b/cli/src/trusted_base_cli/commands/mod.rs @@ -1,5 +1,6 @@ pub mod add_session_proxy; pub mod balance; +pub mod chatbot; pub mod get_fingerprint; pub mod get_header; pub mod get_note_buckets_info; diff --git a/cli/src/trusted_base_cli/mod.rs b/cli/src/trusted_base_cli/mod.rs index 2547a0935..d452601c9 100644 --- a/cli/src/trusted_base_cli/mod.rs +++ b/cli/src/trusted_base_cli/mod.rs @@ -20,9 +20,9 @@ use crate::trusted_base_cli::commands::set_balance::SetBalanceCommand; use crate::{ trusted_base_cli::commands::{ add_session_proxy::AddSessionProxyCommand, balance::BalanceCommand, - get_fingerprint::GetFingerprintCommand, get_header::GetSidechainHeaderCommand, - get_note_buckets_info::GetNoteBucketsInfoCommand, get_notes::GetNotesCommand, - get_parentchains_info::GetParentchainsInfoCommand, + chatbot::ChatbotCommand, get_fingerprint::GetFingerprintCommand, + get_header::GetSidechainHeaderCommand, get_note_buckets_info::GetNoteBucketsInfoCommand, + get_notes::GetNotesCommand, get_parentchains_info::GetParentchainsInfoCommand, get_session_proxies::GetSessionProxiesCommand, get_shard::GetShardCommand, get_shard_info::GetShardInfoCommand, get_shard_vault::GetShardVaultCommand, get_total_issuance::GetTotalIssuanceCommand, @@ -115,6 +115,9 @@ pub enum TrustedBaseCommand { /// run a watchdog service to probe a validateer regularly and profile timings Watchdog(WatchdogCommand), + /// run a chatbot service + Chatbot(ChatbotCommand), + /// get a version string for the enclave Version(VersionCommand), } @@ -146,6 +149,7 @@ impl TrustedBaseCommand { TrustedBaseCommand::WasteTime(cmd) => cmd.run(cli, trusted_cli), TrustedBaseCommand::SpamExtrinsics(cmd) => cmd.run(cli, trusted_cli), TrustedBaseCommand::Watchdog(cmd) => cmd.run(cli, trusted_cli), + TrustedBaseCommand::Chatbot(cmd) => cmd.run(cli, trusted_cli), TrustedBaseCommand::Version(cmd) => cmd.run(cli, trusted_cli), } } diff --git a/cli/src/trusted_command_utils.rs b/cli/src/trusted_command_utils.rs index 1e365f8dd..772fd7339 100644 --- a/cli/src/trusted_command_utils.rs +++ b/cli/src/trusted_command_utils.rs @@ -94,7 +94,7 @@ pub(crate) fn get_identifiers(cli: &Cli, trusted_args: &TrustedCli) -> ([u8; 32] let mrenclave = if let Some(ref mrenclave_arg) = trusted_args.mrenclave { mrenclave_from_base58(mrenclave_arg) } else { - warn!("no --mrenclave argument provided. Will trustfully fetch enclave fingerprint from worker rpc endpoint"); + debug!("no --mrenclave argument provided. Will trustfully fetch enclave fingerprint from worker rpc endpoint"); get_fingerprint(cli).expect("could not get fingerprint").0 }; let shard = match &trusted_args.shard { @@ -169,8 +169,11 @@ pub(crate) fn get_account_id_from_str(account: &str) -> AccountId { pub(crate) fn get_sidechain_header(cli: &Cli) -> Result { let direct_api = get_worker_api_direct(cli); let rpc_method = "chain_getHeader".to_owned(); - let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call(rpc_method, vec![]).unwrap(); - let rpc_response_str = direct_api.get(&jsonrpc_call).unwrap(); + let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call(rpc_method, vec![]) + .map_err(|e| CliError::WorkerRpcApi { msg: e.to_string() })?; + let rpc_response_str = direct_api + .get(&jsonrpc_call) + .map_err(|e| CliError::WorkerRpcApi { msg: e.to_string() })?; // Decode RPC response. let rpc_response: RpcResponse = serde_json::from_str(&rpc_response_str) .map_err(|err| CliError::WorkerRpcApi { msg: err.to_string() })?; @@ -182,7 +185,11 @@ pub(crate) fn get_sidechain_header(cli: &Cli) -> Result Result Result { let direct_api = get_worker_api_direct(cli); let rpc_method = "author_getFingerprint".to_owned(); - let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call(rpc_method, vec![]).unwrap(); - let rpc_response_str = direct_api.get(&jsonrpc_call).unwrap(); + let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call(rpc_method, vec![]) + .map_err(|e| CliError::WorkerRpcApi { msg: e.to_string() })?; + let rpc_response_str = direct_api + .get(&jsonrpc_call) + .map_err(|e| CliError::WorkerRpcApi { msg: e.to_string() })?; // Decode RPC response. let rpc_response: RpcResponse = serde_json::from_str(&rpc_response_str) .map_err(|err| CliError::WorkerRpcApi { msg: err.to_string() })?; @@ -210,7 +220,11 @@ pub(crate) fn get_fingerprint(cli: &Cli) -> Result { })?; if rpc_return_value.status == DirectRequestStatus::Error { - error!("{}", String::decode(&mut rpc_return_value.value.as_slice()).unwrap()); + error!( + "{}", + String::decode(&mut rpc_return_value.value.as_slice()) + .map_err(|e| CliError::WorkerRpcApi { msg: e.to_string() })? + ); return Err(CliError::WorkerRpcApi { msg: "rpc error".to_string() }) } diff --git a/enclave-runtime/Cargo.lock b/enclave-runtime/Cargo.lock index 51a61d8ec..726a56c3b 100644 --- a/enclave-runtime/Cargo.lock +++ b/enclave-runtime/Cargo.lock @@ -771,7 +771,7 @@ dependencies = [ [[package]] name = "enclave-runtime" -version = "0.16.6" +version = "0.16.7" dependencies = [ "array-bytes 6.2.2", "cid", diff --git a/enclave-runtime/Cargo.toml b/enclave-runtime/Cargo.toml index 3df4835de..5840f109b 100644 --- a/enclave-runtime/Cargo.toml +++ b/enclave-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enclave-runtime" -version = "0.16.6" +version = "0.16.7" authors = ["Integritee AG "] edition = "2021" diff --git a/service/Cargo.toml b/service/Cargo.toml index 27ec44595..a1b00dae9 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "integritee-service" -version = "0.16.6" +version = "0.16.7" authors = ["Integritee AG "] build = "build.rs" edition = "2021"