diff --git a/Cargo.lock b/Cargo.lock index e011d40017..38e9f1f35f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3915,6 +3915,7 @@ dependencies = [ "mc-common", "mc-crypto-keys", "mc-crypto-rand", + "mc-crypto-x509-test-vectors", "mc-fog-api", "mc-fog-enclave-connection", "mc-fog-ledger-connection", @@ -3934,6 +3935,7 @@ dependencies = [ "mc-util-encodings", "mc-util-from-random", "mc-util-grpc", + "mc-util-keyfile", "mc-util-metrics", "mc-util-parse", "mc-util-serial", @@ -3943,6 +3945,8 @@ dependencies = [ "mc-watcher", "mc-watcher-api", "rand", + "pem", + "portpicker", "retry", "serde", "serde_json", @@ -3950,6 +3954,7 @@ dependencies = [ "sha2 0.10.6", "tempdir", "url", + "x509-signature", ] [[package]] diff --git a/fog/ledger/server/Cargo.toml b/fog/ledger/server/Cargo.toml index 25d173108c..73e4993883 100644 --- a/fog/ledger/server/Cargo.toml +++ b/fog/ledger/server/Cargo.toml @@ -81,6 +81,8 @@ mc-blockchain-test-utils = { path = "../../../blockchain/test-utils" } mc-common = { path = "../../../common", features = ["loggers"] } mc-crypto-keys = { path = "../../../crypto/keys" } mc-crypto-rand = { path = "../../../crypto/rand" } +mc-crypto-x509-test-vectors = { path = "../../../crypto/x509/test-vectors" } +mc-util-keyfile = { path = "../../../util/keyfile" } mc-util-test-helper = { path = "../../../util/test-helper" } mc-util-uri = { path = "../../../util/uri" } @@ -93,5 +95,8 @@ mc-fog-test-infra = { path = "../../test_infra" } # third-party tempfile = "3.4" aes-gcm = "0.10.1" +pem = "1.1" +portpicker = "0.1.1" sha2 = "0.10" tempdir = "0.3" +x509-signature = "0.5" diff --git a/fog/ledger/server/src/bin/router.rs b/fog/ledger/server/src/bin/router.rs index f366005ea9..3daa1d9e7b 100644 --- a/fog/ledger/server/src/bin/router.rs +++ b/fog/ledger/server/src/bin/router.rs @@ -14,9 +14,10 @@ use mc_fog_api::ledger_grpc::KeyImageStoreApiClient; use mc_fog_ledger_enclave::{LedgerSgxEnclave, ENCLAVE_FILE}; use mc_fog_ledger_server::{LedgerRouterConfig, LedgerRouterServer}; use mc_fog_uri::{KeyImageStoreScheme, KeyImageStoreUri}; - +use mc_ledger_db::LedgerDB; use mc_util_grpc::ConnectionUriGrpcioChannel; use mc_util_uri::UriScheme; +use mc_watcher::watcher_db::WatcherDB; fn main() { let (logger, _global_logger_guard) = @@ -78,8 +79,18 @@ fn main() { } let ledger_store_grpc_clients = Arc::new(RwLock::new(ledger_store_grpc_clients)); - let mut router_server = - LedgerRouterServer::new(config, enclave, ledger_store_grpc_clients, logger); + let ledger_db = LedgerDB::open(&config.ledger_db).expect("Could not read ledger DB"); + let watcher_db = + WatcherDB::open_ro(&config.watcher_db, logger.clone()).expect("Could not open watcher DB"); + + let mut router_server = LedgerRouterServer::new( + config, + enclave, + ledger_store_grpc_clients, + ledger_db, + watcher_db, + logger, + ); router_server.start(); loop { diff --git a/fog/ledger/server/src/config.rs b/fog/ledger/server/src/config.rs index 316f7b7d82..2dc6ce98de 100644 --- a/fog/ledger/server/src/config.rs +++ b/fog/ledger/server/src/config.rs @@ -82,6 +82,10 @@ pub struct LedgerServerConfig { #[derive(Clone, Parser, Serialize)] #[clap(version)] pub struct LedgerRouterConfig { + /// The chain id of the network we are a part of + #[clap(long, env = "MC_CHAIN_ID")] + pub chain_id: String, + /// The ID with which to respond to client attestation requests. /// /// This ID needs to match the host:port clients use in their URI when @@ -102,6 +106,25 @@ pub struct LedgerRouterConfig { #[clap(long, default_value = "3")] pub query_retries: usize, + /// Enables authenticating client requests using Authorization tokens using + /// the provided hex-encoded 32 bytes shared secret. + #[clap(long, value_parser = mc_util_parse::parse_hex::<[u8; 32]>, env = "MC_CLIENT_AUTH_TOKEN_SECRET")] + pub client_auth_token_secret: Option<[u8; 32]>, + + /// Maximal client authentication token lifetime, in seconds (only relevant + /// when --client-auth-token-secret is used. Defaults to 86400 - 24 + /// hours). + #[clap(long, default_value = "86400", value_parser = parse_duration_in_seconds, env = "MC_CLIENT_AUTH_TOKEN_MAX_LIFETIME")] + pub client_auth_token_max_lifetime: Duration, + + /// Path to ledger db (lmdb) + #[clap(long, env = "MC_LEDGER_DB")] + pub ledger_db: PathBuf, + + /// Path to watcher db (lmdb) - includes block timestamps + #[clap(long, env = "MC_WATCHER_DB")] + pub watcher_db: PathBuf, + // TODO: Add store instance uris which are of type Vec. /// The capacity to build the OMAP (ORAM hash table) with. /// About 75% of this capacity can be used. diff --git a/fog/ledger/server/src/key_image_router_server.rs b/fog/ledger/server/src/key_image_router_server.rs new file mode 100644 index 0000000000..bce08d6dd8 --- /dev/null +++ b/fog/ledger/server/src/key_image_router_server.rs @@ -0,0 +1,117 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +use futures::executor::block_on; +use mc_common::logger::{log, Logger}; +use mc_fog_api::ledger_grpc; +use mc_fog_ledger_enclave::LedgerEnclaveProxy; +use mc_fog_uri::{ConnectionUri, KeyImageStoreUri}; +use mc_util_grpc::{ConnectionUriGrpcioServer, ReadinessIndicator}; + +use crate::{ + config::LedgerRouterConfig, key_image_router_service::KeyImageRouterService, + router_admin_service::LedgerRouterAdminService, +}; + +pub struct KeyImageRouterServer { + router_server: grpcio::Server, + admin_server: grpcio::Server, + logger: Logger, +} + +impl KeyImageRouterServer { + pub fn new( + config: LedgerRouterConfig, + enclave: E, + shards: Arc>>>, + logger: Logger, + ) -> KeyImageRouterServer + where + E: LedgerEnclaveProxy, + { + let readiness_indicator = ReadinessIndicator::default(); + + let env = Arc::new( + grpcio::EnvBuilder::new() + .name_prefix("key-image-router-server".to_string()) + .build(), + ); + + // Health check service - will be used in both cases + let health_service = + mc_util_grpc::HealthService::new(Some(readiness_indicator.into()), logger.clone()) + .into_service(); + + // Build our router server. + // Init ledger router service. + let ledger_router_service = ledger_grpc::create_ledger_api(KeyImageRouterService::new( + enclave, + shards.clone(), + logger.clone(), + )); + log::debug!(logger, "Constructed Key Image Router GRPC Service"); + + // Init ledger router admin service. + let ledger_router_admin_service = ledger_grpc::create_ledger_router_admin_api( + LedgerRouterAdminService::new(shards, logger.clone()), + ); + log::debug!(logger, "Constructed Key Image Router Admin GRPC Service"); + + // Package service into grpc server + log::info!( + logger, + "Starting Key Image Router server on {}", + config.client_listen_uri.addr(), + ); + + let router_server_builder = grpcio::ServerBuilder::new(env.clone()) + .register_service(ledger_router_service) + .register_service(health_service) + .bind_using_uri(&config.client_listen_uri, logger.clone()); + let admin_server_builder = grpcio::ServerBuilder::new(env) + .register_service(ledger_router_admin_service) + .bind_using_uri(&config.admin_listen_uri, logger.clone()); + + let router_server = router_server_builder.build().unwrap(); + let admin_server = admin_server_builder.build().unwrap(); + + Self { + router_server, + admin_server, + logger, + } + } + + /// Starts the server + pub fn start(&mut self) { + self.router_server.start(); + for (host, port) in self.router_server.bind_addrs() { + log::info!(self.logger, "Router API listening on {}:{}", host, port); + } + self.admin_server.start(); + for (host, port) in self.admin_server.bind_addrs() { + log::info!( + self.logger, + "Router Admin API listening on {}:{}", + host, + port + ); + } + } + + /// Stops the server + pub fn stop(&mut self) { + block_on(self.router_server.shutdown()).expect("Could not stop router grpc server"); + block_on(self.admin_server.shutdown()).expect("Could not stop router admin grpc server"); + } +} + +impl Drop for KeyImageRouterServer { + fn drop(&mut self) { + self.stop(); + } +} diff --git a/fog/ledger/server/src/key_image_router_service.rs b/fog/ledger/server/src/key_image_router_service.rs new file mode 100644 index 0000000000..707674547a --- /dev/null +++ b/fog/ledger/server/src/key_image_router_service.rs @@ -0,0 +1,86 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; + +use futures::{FutureExt, TryFutureExt}; +use grpcio::{DuplexSink, RequestStream, RpcContext}; +use mc_common::logger::{log, Logger}; +use mc_fog_api::{ + ledger::{LedgerRequest, LedgerResponse}, + ledger_grpc::{self, LedgerApi}, +}; +use mc_fog_ledger_enclave::LedgerEnclaveProxy; +use mc_fog_uri::KeyImageStoreUri; +use mc_util_grpc::rpc_logger; +use mc_util_metrics::SVC_COUNTERS; + +use crate::router_handlers; + +#[derive(Clone)] +pub struct KeyImageRouterService +where + E: LedgerEnclaveProxy, +{ + enclave: E, + shards: Arc>>>, + query_retries: usize, + logger: Logger, +} + +impl KeyImageRouterService { + /// Creates a new LedgerRouterService that can be used by a gRPC server to + /// fulfill gRPC requests. + #[allow(dead_code)] // FIXME + pub fn new( + enclave: E, + shards: Arc>>>, + query_retries: usize, + logger: Logger, + ) -> Self { + Self { + enclave, + shards, + query_retries, + logger, + } + } +} + +impl LedgerApi for KeyImageRouterService +where + E: LedgerEnclaveProxy, +{ + fn request( + &mut self, + ctx: RpcContext, + requests: RequestStream, + responses: DuplexSink, + ) { + let _timer = SVC_COUNTERS.req(&ctx); + mc_common::logger::scoped_global_logger(&rpc_logger(&ctx, &self.logger), |logger| { + log::warn!( + self.logger, + "Streaming GRPC Ledger API only partially implemented." + ); + let logger = logger.clone(); + + let shards = self.shards.read().expect("RwLock poisoned"); + let future = router_handlers::handle_requests( + shards.values().cloned().collect(), + self.enclave.clone(), + requests, + responses, + self.query_retries, + logger.clone(), + ) + .map_err(move |err| log::error!(&logger, "failed to reply: {}", err)) + // TODO: Do more with the error than just push it to the log. + .map(|_| ()); + + ctx.spawn(future) + }); + } +} diff --git a/fog/ledger/server/src/router_handlers.rs b/fog/ledger/server/src/router_handlers.rs index 2bf217de08..c71df6064c 100644 --- a/fog/ledger/server/src/router_handlers.rs +++ b/fog/ledger/server/src/router_handlers.rs @@ -11,8 +11,8 @@ use mc_common::{ }; use mc_fog_api::{ ledger::{ - LedgerRequest, LedgerResponse, MultiKeyImageStoreRequest, MultiKeyImageStoreResponse, - MultiKeyImageStoreResponseStatus, + LedgerRequest, LedgerRequest_oneof_request_data, LedgerResponse, MultiKeyImageStoreRequest, + MultiKeyImageStoreResponse, MultiKeyImageStoreResponseStatus, }, ledger_grpc::KeyImageStoreApiClient, }; @@ -55,7 +55,7 @@ where /// Handles a client's request by performing either an authentication or a /// query. pub async fn handle_request( - mut request: LedgerRequest, + request: LedgerRequest, shard_clients: Vec>, enclave: E, query_retries: usize, @@ -64,26 +64,21 @@ pub async fn handle_request( where E: LedgerEnclaveProxy, { - if request.has_auth() { - handle_auth_request(enclave, request.take_auth(), logger) - } else if request.has_check_key_images() { - handle_query_request( - request.take_check_key_images(), - enclave, - shard_clients, - query_retries, - logger, - ) - .await - // TODO: Handle other cases here as they are added, such as the merkele - // proof service. - } else { - let rpc_status = rpc_invalid_arg_error( - "Inavlid LedgerRequest request", - "Neither the check_key_images nor auth fields were set".to_string(), - &logger, - ); - Err(rpc_status) + match request.request_data { + Some(LedgerRequest_oneof_request_data::auth(request)) => { + handle_auth_request(enclave, request, logger) + } + Some(LedgerRequest_oneof_request_data::check_key_images(request)) => { + handle_query_request(request, enclave, shard_clients, query_retries, logger).await + } + None => { + let rpc_status = rpc_invalid_arg_error( + "Inavlid LedgerRequest request", + "Neither the check_key_images nor auth fields were set".to_string(), + &logger, + ); + Err(rpc_status) + } } } diff --git a/fog/ledger/server/src/router_server.rs b/fog/ledger/server/src/router_server.rs index a7ff4a8bce..98af9eb8f0 100644 --- a/fog/ledger/server/src/router_server.rs +++ b/fog/ledger/server/src/router_server.rs @@ -6,16 +6,24 @@ use std::{ }; use futures::executor::block_on; -use mc_common::logger::{log, Logger}; +use mc_common::{ + logger::{log, Logger}, + time::SystemTimeProvider, +}; use mc_fog_api::ledger_grpc; use mc_fog_ledger_enclave::LedgerEnclaveProxy; use mc_fog_uri::{ConnectionUri, FogLedgerUri, KeyImageStoreUri}; -use mc_util_grpc::{ConnectionUriGrpcioServer, ReadinessIndicator}; +use mc_ledger_db::LedgerDB; +use mc_util_grpc::{ + AnonymousAuthenticator, Authenticator, ConnectionUriGrpcioServer, ReadinessIndicator, + TokenAuthenticator, +}; use mc_util_uri::AdminUri; +use mc_watcher::watcher_db::WatcherDB; use crate::{ config::LedgerRouterConfig, router_admin_service::LedgerRouterAdminService, - router_service::LedgerRouterService, + router_service::LedgerRouterService, BlockService, MerkleProofService, UntrustedTxOutService, }; pub struct LedgerRouterServer { @@ -31,11 +39,24 @@ impl LedgerRouterServer { config: LedgerRouterConfig, enclave: E, shards: Arc>>>, + ledger: LedgerDB, + watcher: WatcherDB, logger: Logger, ) -> LedgerRouterServer where E: LedgerEnclaveProxy, { + let client_authenticator: Arc = + if let Some(shared_secret) = config.client_auth_token_secret.as_ref() { + Arc::new(TokenAuthenticator::new( + *shared_secret, + config.client_auth_token_max_lifetime, + SystemTimeProvider::default(), + )) + } else { + Arc::new(AnonymousAuthenticator::default()) + }; + let readiness_indicator = ReadinessIndicator::default(); let env = Arc::new( @@ -52,7 +73,7 @@ impl LedgerRouterServer { // Build our router server. // Init ledger router service. let ledger_router_service = ledger_grpc::create_ledger_api(LedgerRouterService::new( - enclave, + enclave.clone(), shards.clone(), config.query_retries, logger.clone(), @@ -65,6 +86,34 @@ impl LedgerRouterServer { ); log::debug!(logger, "Constructed Ledger Router Admin GRPC Service"); + // Non-routed servers and services + // Init merkle proof service + let merkle_proof_service = + ledger_grpc::create_fog_merkle_proof_api(MerkleProofService::new( + config.chain_id.clone(), + ledger.clone(), + enclave, + client_authenticator.clone(), + logger.clone(), + )); + // Init untrusted tx out service + let untrusted_tx_out_service = + ledger_grpc::create_fog_untrusted_tx_out_api(UntrustedTxOutService::new( + config.chain_id.clone(), + ledger.clone(), + watcher.clone(), + client_authenticator.clone(), + logger.clone(), + )); + // Init block service + let block_service = ledger_grpc::create_fog_block_api(BlockService::new( + config.chain_id.clone(), + ledger, + watcher, + client_authenticator.clone(), + logger.clone(), + )); + // Package service into grpc server log::info!( logger, @@ -74,6 +123,9 @@ impl LedgerRouterServer { let router_server = grpcio::ServerBuilder::new(env.clone()) .register_service(ledger_router_service) + .register_service(merkle_proof_service) + .register_service(untrusted_tx_out_service) + .register_service(block_service) .register_service(health_service) .build_using_uri(&config.client_listen_uri, logger.clone()) .expect("Could not build Ledger Router Server"); diff --git a/fog/ledger/server/tests/store_tests.rs b/fog/ledger/server/tests/store_tests.rs new file mode 100644 index 0000000000..73bf6a42df --- /dev/null +++ b/fog/ledger/server/tests/store_tests.rs @@ -0,0 +1,332 @@ +use std::{ + collections::BTreeMap, + path::PathBuf, + str::FromStr, + sync::{Arc, Mutex}, +}; + +use itertools::Itertools; +use mc_attest_ake::{AuthResponseInput, ClientInitiate, Start, Transition}; +use mc_attest_api::attest; +use mc_attest_enclave_api::{ClientSession, EnclaveMessage, NonceSession}; +use mc_attest_net::{Client as AttestClient, RaClient}; +use mc_attest_verifier::Verifier; +use mc_blockchain_types::MAX_BLOCK_VERSION; +use mc_common::{ + logger::{test_with_logger, Logger}, + ResponderId, +}; +use mc_crypto_keys::X25519; +use mc_crypto_rand::{CryptoRng, RngCore}; +use mc_fog_ledger_enclave::{ + CheckKeyImagesResponse, KeyImageData, LedgerEnclave, LedgerSgxEnclave, ENCLAVE_FILE, +}; +use mc_fog_ledger_enclave_api::UntrustedKeyImageQueryResponse; +use mc_fog_ledger_server::{ + sharding_strategy::EpochShardingStrategy, DbPollSharedState, KeyImageClientListenUri, + KeyImageService, KeyImageStoreServer, LedgerStoreConfig, ShardingStrategy, +}; +use mc_fog_types::ledger::{CheckKeyImagesRequest, KeyImageQuery}; +use mc_fog_uri::{ConnectionUri, KeyImageStoreScheme, KeyImageStoreUri}; +use mc_ledger_db::{test_utils::recreate_ledger_db, LedgerDB}; +use mc_sgx_report_cache_untrusted::ReportCacheThread; +use mc_util_grpc::AnonymousAuthenticator; +use mc_util_metrics::{IntGauge, OpMetrics}; +use mc_util_test_helper::{Rng, RngType, SeedableRng}; +use mc_util_uri::UriScheme; +use mc_watcher::watcher_db::WatcherDB; + +use aes_gcm::Aes256Gcm; +use sha2::Sha512; +use tempdir::TempDir; +use url::Url; + +pub fn uri_for_test(port: u16) -> KeyImageStoreUri { + // If a load-balancer were set up in the middle here + // this might need to be changed to + // {KeyImageStoreScheme::SCHEME_INSECURE}://localhost:1234/? + // responder-id={test_name} + let name = format!( + "{}://localhost:{}", + KeyImageStoreScheme::SCHEME_INSECURE, + port + ); + KeyImageStoreUri::from_str(&name).unwrap() +} + +pub struct TestingContext { + pub enclave: LedgerSgxEnclave, + pub ledger: LedgerDB, + pub responder_id: ResponderId, + pub rng: R, + pub store_config: LedgerStoreConfig, + pub tempdir: TempDir, + pub tx_source_url: Url, + pub watcher: WatcherDB, + pub watcher_path: TempDir, +} + +impl TestingContext { + pub fn new( + test_name: &'static str, + logger: Logger, + port: u16, + omap_capacity: u64, + rng: R, + ) -> Self { + // Set up our directories. + let test_dir_name = format!("fog_ledger_test_{}", test_name); + let tempdir = TempDir::new(&test_dir_name).expect("Could not produce test_ledger tempdir"); + let test_path = PathBuf::from(tempdir.path()); + let user_keys_path = test_path.join(PathBuf::from("keys/")); + if !user_keys_path.exists() { + std::fs::create_dir(&user_keys_path).unwrap(); + } + + let test_uri = uri_for_test(port); + // This ID needs to match the host:port clients use in their URI when + // referencing the host node. + let responder_id = test_uri.responder_id().unwrap(); + + let enclave_path = std::env::current_exe() + .expect("Could not get the path of our executable") + // The test ends up in target/debug/deps/ + // rather than just target/debug/. So, + // we need the parent directory. + .parent() + .unwrap() + .with_file_name(ENCLAVE_FILE); + + let enclave = + LedgerSgxEnclave::new(enclave_path, &responder_id, omap_capacity, logger.clone()); + + // Make LedgerDB + let ledger_path = test_path.join(PathBuf::from("fog_ledger")); + let ledger = recreate_ledger_db(ledger_path.as_path()); + + // Set up wallet db. + let test_url_name = format!("http://{}.wallet.test.test", test_name); + let url = Url::parse(&test_url_name).unwrap(); + + let db_tmp = TempDir::new("wallet_db").expect("Could not make tempdir for wallet db"); + WatcherDB::create(db_tmp.path()).unwrap(); + let watcher = WatcherDB::open_rw(db_tmp.path(), &[url.clone()], logger).unwrap(); + + let config = LedgerStoreConfig { + chain_id: test_name.to_string(), + client_responder_id: responder_id.clone(), + client_listen_uri: test_uri, + ledger_db: ledger_path, + watcher_db: PathBuf::from(db_tmp.path()), + ias_api_key: Default::default(), + ias_spid: Default::default(), + admin_listen_uri: Default::default(), + client_auth_token_secret: None, + client_auth_token_max_lifetime: Default::default(), + omap_capacity, + sharding_strategy: ShardingStrategy::Epoch(EpochShardingStrategy::default()), + }; + + Self { + enclave, + ledger, + responder_id, + rng, + tempdir, + tx_source_url: url, + store_config: config, + watcher, + watcher_path: db_tmp, + } + } +} + +lazy_static::lazy_static! { + pub static ref TEST_OP_COUNTERS: OpMetrics = OpMetrics::new_and_registered("consensus_service"); +} + +lazy_static::lazy_static! { + pub static ref TEST_ENCLAVE_REPORT_TIMESTAMP: IntGauge = TEST_OP_COUNTERS.gauge("enclave_report_timestamp"); +} + +#[test_with_logger] +pub fn direct_key_image_store_check(logger: Logger) { + const TEST_NAME: &str = "direct_key_image_store_check"; + const PORT_START: u16 = 3223; + const OMAP_CAPACITY: u64 = 768; + + let port = PORT_START; + + let rng = RngType::from_entropy(); + let TestingContext { + enclave, + ledger, + responder_id, + mut rng, + tempdir: _tempdir, + tx_source_url: _tx_source_url, + watcher, + store_config, + watcher_path: _watcher_path, + } = TestingContext::new(TEST_NAME, logger.clone(), port, OMAP_CAPACITY, rng); + + let shared_state = Arc::new(Mutex::new(DbPollSharedState::default())); + + let client_listen_uri = store_config.client_listen_uri.clone(); + let store_service = KeyImageService::new( + KeyImageClientListenUri::Store(client_listen_uri.clone()), + store_config.chain_id.clone(), + ledger, + watcher, + enclave.clone(), //LedgerSgxEnclave is an Arc internally + shared_state.clone(), + Arc::new(AnonymousAuthenticator::default()), + logger.clone(), + ); + + let mut store_server = KeyImageStoreServer::new_from_service( + store_service, + client_listen_uri, + enclave.clone(), + EpochShardingStrategy::default(), + logger.clone(), + ); + store_server.start(); + + // Set up IAS verficiation + // This will be a SimClient in testing contexts. + let ias_client = + AttestClient::new(&store_config.ias_api_key).expect("Could not create IAS client"); + let mut report_cache_thread = Some( + ReportCacheThread::start( + enclave.clone(), + ias_client, + store_config.ias_spid, + &TEST_ENCLAVE_REPORT_TIMESTAMP, + logger.clone(), + ) + .unwrap(), + ) + .unwrap(); + + // Make GRPC client for sending requests. + + // Get the enclave to generate an auth request. + let client_auth_request = enclave.ledger_store_init(responder_id.clone()).unwrap(); + // Submit auth request and wait for the response. + let (auth_response, router_to_store_session) = + enclave.frontend_accept(client_auth_request).unwrap(); + // Finish the enclave's handshake with itself. + enclave + .ledger_store_connect(responder_id.clone(), auth_response) + .unwrap(); + println!("router_to_store_session is: {:?}", &router_to_store_session); + + // Generate a dummy key image we're going to check against. + let mut test_key_image_bytes: [u8; 32] = [0u8; 32]; + rng.fill(&mut test_key_image_bytes); + let test_key_image = KeyImageData { + key_image: test_key_image_bytes.into(), + block_index: 1, + timestamp: 255, + }; + enclave.add_key_image_data(vec![test_key_image]).unwrap(); + + // Set up the client's end of the encrypted connection. + let initiator = Start::new(responder_id.to_string()); + + let init_input = ClientInitiate::::default(); + let (initiator, auth_request_output) = initiator.try_next(&mut rng, init_input).unwrap(); + + // Authenticate our "client" with the server. + let auth_message = attest::AuthMessage::from(auth_request_output); + let (client_auth_response, client_session) = + enclave.client_accept(auth_message.into()).unwrap(); + println!("Initial client_session is {:?}", &client_session); + // We will need to double-convert, ClientAuthResponse -> AuthMessage -> + // AuthResponseOutput + let auth_message = attest::AuthMessage::from(client_auth_response); + // Initiator accepts responder's message. + let auth_response_event = AuthResponseInput::new(auth_message.into(), Verifier::default()); + // Should be a valid noise connection at this point. + let (mut noise_connection, _verification_report) = + initiator.try_next(&mut rng, auth_response_event).unwrap(); + + //Construct our request. + let key_images_request = CheckKeyImagesRequest { + queries: vec![KeyImageQuery { + key_image: test_key_image.key_image, + start_block: 1, + }], + }; + // Protobuf-encoded plaintext. + let message_encoded = mc_util_serial::encode(&key_images_request); + let ciphertext = noise_connection.encrypt(&[], &message_encoded).unwrap(); + let msg: EnclaveMessage = EnclaveMessage { + aad: vec![], + channel_id: client_session, + data: ciphertext, + }; + + // Decrypt and seal + let sealed_query = enclave.decrypt_and_seal_query(msg).unwrap(); + println!( + "Client session on sealed_query is {:?}", + &sealed_query.channel_id + ); + let mut multi_query = enclave + .create_multi_key_image_store_query_data(sealed_query.clone()) + .unwrap(); + + let query: EnclaveMessage = multi_query.pop().unwrap(); + println!("Nonce session on message is {:?}", query.channel_id); + + // Get an untrusted query + let ( + highest_processed_block_count, + last_known_block_cumulative_txo_count, + latest_block_version, + ) = { + let shared_state = shared_state.lock().expect("mutex poisoned"); + ( + shared_state.highest_processed_block_count, + shared_state.last_known_block_cumulative_txo_count, + shared_state.latest_block_version, + ) + }; + + let untrusted_kiqr = UntrustedKeyImageQueryResponse { + highest_processed_block_count, + last_known_block_cumulative_txo_count, + latest_block_version, + max_block_version: latest_block_version.max(*MAX_BLOCK_VERSION), + }; + + let result = enclave + .check_key_image_store(query, untrusted_kiqr) + .unwrap(); + + let responses_btree: BTreeMap> = + BTreeMap::from([(responder_id, result)]); + + let client_response = enclave + .collate_shard_query_responses(sealed_query, responses_btree) + .unwrap(); + + let plaintext_bytes = noise_connection + .decrypt(&client_response.aad, &client_response.data) + .unwrap(); + + let done_response: CheckKeyImagesResponse = mc_util_serial::decode(&plaintext_bytes).unwrap(); + assert_eq!(done_response.results.len(), 1); + + let mut test_results = done_response + .results + .into_iter() + .map(|result| (result.key_image, result.key_image_result_code)); + + // The key image result code for a spent key image is 1. + assert!(test_results.contains(&(test_key_image.key_image, 1))); + + report_cache_thread.stop().unwrap(); +}