From 75a290bf439474c7010c40cb76799b4e7f1824fa Mon Sep 17 00:00:00 2001 From: patrick Date: Fri, 11 Mar 2022 12:27:51 +0000 Subject: [PATCH 1/9] * refactor config for rest-server wallet decoupling --- sui/src/config.rs | 133 +++++++--------------- sui/src/keystore.rs | 37 +++--- sui/src/rest_server.rs | 138 +++++++++++----------- sui/src/sui.rs | 9 +- sui/src/sui_commands.rs | 154 ++++++++++++------------- sui/src/unit_tests/cli_tests.rs | 196 ++++++++++++++++++-------------- sui/src/wallet.rs | 9 +- sui/src/wallet_commands.rs | 19 +++- 8 files changed, 349 insertions(+), 346 deletions(-) diff --git a/sui/src/config.rs b/sui/src/config.rs index 79d69232b6d21..6c591b535e300 100644 --- a/sui/src/config.rs +++ b/sui/src/config.rs @@ -2,27 +2,29 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::gateway::{EmbeddedGatewayConfig, GatewayType}; -use crate::keystore::KeystoreType; -use anyhow::anyhow; -use once_cell::sync::Lazy; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use serde_with::hex::Hex; -use serde_with::serde_as; use std::fmt::Write; use std::fmt::{Display, Formatter}; use std::fs::{self, File}; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::sync::Mutex; + +use once_cell::sync::Lazy; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use serde_with::hex::Hex; +use serde_with::serde_as; +use tracing::log::trace; + use sui_framework::DEFAULT_FRAMEWORK_PATH; use sui_network::network::PortAllocator; use sui_network::transport; use sui_types::base_types::*; use sui_types::crypto::{get_key_pair, KeyPair}; -use tracing::log::trace; + +use crate::gateway::GatewayType; +use crate::keystore::KeystoreType; const DEFAULT_WEIGHT: usize = 1; const DEFAULT_GAS_AMOUNT: u64 = 100000; @@ -107,33 +109,17 @@ pub struct WalletConfig { pub accounts: Vec, pub keystore: KeystoreType, pub gateway: GatewayType, - - #[serde(skip)] - config_path: PathBuf, } -impl Config for WalletConfig { - fn create(path: &Path) -> Result { - let working_dir = path - .parent() - .ok_or_else(|| anyhow!("Cannot determine parent directory."))?; - Ok(WalletConfig { - accounts: Vec::new(), - keystore: KeystoreType::File(working_dir.join("wallet.ks")), - gateway: GatewayType::Embedded(EmbeddedGatewayConfig { - db_folder_path: working_dir.join("client_db"), - ..Default::default() - }), - config_path: path.to_path_buf(), - }) - } +impl Config for WalletConfig {} - fn set_config_path(&mut self, path: &Path) { - self.config_path = path.to_path_buf(); - } - - fn config_path(&self) -> &Path { - &self.config_path +impl Default for WalletConfig { + fn default() -> Self { + Self { + accounts: Vec::new(), + keystore: KeystoreType::File(PathBuf::from("./wallet.key")), + gateway: GatewayType::Embedded(Default::default()), + } } } @@ -141,8 +127,8 @@ impl Display for WalletConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut writer = String::new(); - writeln!(writer, "Config path : {:?}", self.config_path)?; writeln!(writer, "Managed addresses : {}", self.accounts.len())?; + writeln!(writer, "{}", self.keystore)?; write!(writer, "{}", self.gateway)?; write!(f, "{}", writer) @@ -154,30 +140,21 @@ pub struct NetworkConfig { pub authorities: Vec, pub buffer_size: usize, pub loaded_move_packages: Vec<(PathBuf, ObjectID)>, - #[serde(skip)] - config_path: PathBuf, } -impl Config for NetworkConfig { - fn create(path: &Path) -> Result { - Ok(Self { +impl Config for NetworkConfig {} + +impl Default for NetworkConfig { + fn default() -> Self { + Self { authorities: vec![], - buffer_size: transport::DEFAULT_MAX_DATAGRAM_SIZE.to_string().parse()?, + buffer_size: transport::DEFAULT_MAX_DATAGRAM_SIZE, loaded_move_packages: vec![], - config_path: path.to_path_buf(), - }) - } - - fn set_config_path(&mut self, path: &Path) { - self.config_path = path.to_path_buf(); - } - - fn config_path(&self) -> &Path { - &self.config_path + } } } -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize)] #[serde(default)] pub struct GenesisConfig { pub authorities: Vec, @@ -187,8 +164,6 @@ pub struct GenesisConfig { pub sui_framework_lib_path: PathBuf, #[serde(default = "default_move_framework_lib")] pub move_framework_lib_path: PathBuf, - #[serde(skip)] - config_path: PathBuf, } #[derive(Serialize, Deserialize, Default)] @@ -230,9 +205,9 @@ const DEFAULT_NUMBER_OF_ACCOUNT: usize = 5; const DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT: usize = 5; impl GenesisConfig { - pub fn default_genesis(path: &Path) -> Result { + pub fn default_genesis(working_dir: &Path) -> Result { GenesisConfig::custom_genesis( - path, + working_dir, DEFAULT_NUMBER_OF_AUTHORITIES, DEFAULT_NUMBER_OF_ACCOUNT, DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT, @@ -240,14 +215,11 @@ impl GenesisConfig { } pub fn custom_genesis( - path: &Path, + working_dir: &Path, num_authorities: usize, num_accounts: usize, num_objects_per_account: usize, ) -> Result { - let working_dir = path - .parent() - .ok_or_else(|| anyhow!("Cannot resolve file path."))?; let mut authorities = Vec::new(); for _ in 0..num_authorities { // Get default authority config from deserialization logic. @@ -274,38 +246,28 @@ impl GenesisConfig { Ok(Self { authorities, accounts, - move_packages: vec![], - sui_framework_lib_path: default_sui_framework_lib(), - move_framework_lib_path: default_move_framework_lib(), - config_path: path.to_path_buf(), + ..Default::default() }) } } -impl Config for GenesisConfig { - fn create(path: &Path) -> Result { - Ok(Self { +impl Config for GenesisConfig {} + +impl Default for GenesisConfig { + fn default() -> Self { + Self { authorities: vec![], accounts: vec![], - config_path: path.to_path_buf(), move_packages: vec![], sui_framework_lib_path: default_sui_framework_lib(), move_framework_lib_path: default_move_framework_lib(), - }) - } - - fn set_config_path(&mut self, path: &Path) { - self.config_path = path.to_path_buf() - } - - fn config_path(&self) -> &Path { - &self.config_path + } } } pub trait Config where - Self: DeserializeOwned + Serialize, + Self: DeserializeOwned + Serialize + Default, { fn read_or_create(path: &Path) -> Result { let path_buf = PathBuf::from(path); @@ -313,7 +275,7 @@ where Self::read(path)? } else { trace!("Config file not found, creating new config '{:?}'", path); - let new_config = Self::create(path)?; + let new_config = Self::default(); new_config.write(path)?; new_config }) @@ -322,9 +284,7 @@ where fn read(path: &Path) -> Result { trace!("Reading config from '{:?}'", path); let reader = BufReader::new(File::open(path)?); - let mut config: Self = serde_json::from_reader(reader)?; - config.set_config_path(path); - Ok(config) + Ok(serde_json::from_reader(reader)?) } fn write(&self, path: &Path) -> Result<(), anyhow::Error> { @@ -333,13 +293,4 @@ where fs::write(path, config).expect("Unable to write to config file"); Ok(()) } - - fn save(&self) -> Result<(), anyhow::Error> { - self.write(self.config_path()) - } - - fn create(path: &Path) -> Result; - - fn set_config_path(&mut self, path: &Path); - fn config_path(&self) -> &Path; } diff --git a/sui/src/keystore.rs b/sui/src/keystore.rs index 339ac3a296620..b1e20afd08f43 100644 --- a/sui/src/keystore.rs +++ b/sui/src/keystore.rs @@ -1,15 +1,19 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use ed25519_dalek::ed25519::signature; -use ed25519_dalek::{ed25519, Signer}; -use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use std::fmt::Write; +use std::fmt::{Display, Formatter}; use std::fs; use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; + +use ed25519_dalek::ed25519::signature; +use ed25519_dalek::{ed25519, Signer}; +use serde::{Deserialize, Serialize}; + use sui_types::base_types::SuiAddress; use sui_types::crypto::{get_key_pair, KeyPair, Signature}; @@ -33,10 +37,22 @@ impl KeystoreType { } } -#[derive(Serialize, Deserialize)] +impl Display for KeystoreType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut writer = String::new(); + match self { + KeystoreType::File(path) => { + writeln!(writer, "Keystore Type : File")?; + write!(writer, "Keystore Path : {:?}", path)?; + write!(f, "{}", writer) + } + } + } +} + +#[derive(Serialize, Deserialize, Default)] pub struct SuiKeystore { keys: BTreeMap, - path: PathBuf, } impl Keystore for SuiKeystore { @@ -53,7 +69,6 @@ impl Keystore for SuiKeystore { fn add_random_key(&mut self) -> Result { let (address, keypair) = get_key_pair(); self.keys.insert(address, keypair); - self.save()?; Ok(address) } } @@ -72,20 +87,16 @@ impl SuiKeystore { .map(|key| (SuiAddress::from(key.public_key_bytes()), key)) .collect(); - Ok(Self { - keys, - path: path.to_path_buf(), - }) + Ok(Self { keys }) } - pub fn save(&self) -> Result<(), anyhow::Error> { + pub fn save(&self, path: &Path) -> Result<(), anyhow::Error> { let store = serde_json::to_string_pretty(&self.keys.values().collect::>()).unwrap(); - Ok(fs::write(&self.path, store)?) + Ok(fs::write(path, store)?) } pub fn add_key(&mut self, address: SuiAddress, keypair: KeyPair) -> Result<(), anyhow::Error> { self.keys.insert(address, keypair); - self.save()?; Ok(()) } } diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index 8c2b7b0e864eb..fbc351bdc4d48 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -1,42 +1,42 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; +use std::fs; +use std::net::{Ipv4Addr, SocketAddr}; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; + use dropshot::{endpoint, Query, TypedBody}; use dropshot::{ ApiDescription, ConfigDropshot, ConfigLogging, ConfigLoggingLevel, HttpError, HttpResponseOk, HttpResponseUpdatedNoContent, HttpServerStarter, RequestContext, }; +use futures::stream::{futures_unordered::FuturesUnordered, StreamExt as _}; use hyper::StatusCode; use move_core_types::identifier::Identifier; use move_core_types::parser::parse_type_tag; use move_core_types::value::MoveStructLayout; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use serde_json::json; -use sui::config::{Config, GenesisConfig, NetworkConfig, WalletConfig}; +use tokio::task::{self, JoinHandle}; +use tracing::{error, info}; + +use sui::config::{AuthorityInfo, Config, GenesisConfig, NetworkConfig, WalletConfig}; +use sui::gateway::{EmbeddedGatewayConfig, GatewayType}; +use sui::keystore::KeystoreType; use sui::sui_commands; use sui::sui_json::{resolve_move_function_args, SuiJsonValue}; use sui::wallet_commands::{SimpleTransactionSigner, WalletContext}; use sui_types::base_types::*; use sui_types::committee::Committee; - -use futures::stream::{futures_unordered::FuturesUnordered, StreamExt as _}; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use std::net::{Ipv4Addr, SocketAddr}; -use std::path::PathBuf; -use std::str::FromStr; use sui_types::event::Event; use sui_types::messages::{ExecutionStatus, TransactionEffects}; use sui_types::move_package::resolve_and_type_check; use sui_types::object::Object as SuiObject; use sui_types::object::ObjectRead; -use tokio::task::{self, JoinHandle}; -use tracing::{error, info}; - -use std::sync::{Arc, Mutex}; -use sui::gateway::{EmbeddedGatewayConfig, GatewayType}; const REST_SERVER_PORT: u16 = 5000; const REST_SERVER_ADDR_IPV4: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); @@ -96,7 +96,6 @@ async fn main() -> Result<(), String> { */ struct ServerContext { documentation: serde_json::Value, - genesis_config_path: String, wallet_config_path: String, network_config_path: String, authority_db_path: String, @@ -111,7 +110,6 @@ impl ServerContext { pub fn new(documentation: serde_json::Value) -> ServerContext { ServerContext { documentation, - genesis_config_path: String::from("genesis.conf"), wallet_config_path: String::from("wallet.conf"), network_config_path: String::from("./network.conf"), authority_db_path: String::from("./authorities_db"), @@ -199,17 +197,17 @@ async fn genesis( rqctx: Arc>, ) -> Result, HttpError> { let server_context = rqctx.context(); - let genesis_config_path = &server_context.genesis_config_path; + //let genesis_config_path = &server_context.genesis_config_path; let network_config_path = &server_context.network_config_path; - let wallet_config_path = &server_context.wallet_config_path; + //let wallet_config_path = &server_context.wallet_config_path; - let mut network_config = NetworkConfig::read_or_create(&PathBuf::from(network_config_path)) - .map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to read network config: {error}"), - ) - })?; + let network_config_path = PathBuf::from(network_config_path); + let network_config = NetworkConfig::read_or_create(&network_config_path).map_err(|error| { + custom_http_error( + StatusCode::CONFLICT, + format!("Unable to read network config: {error}"), + ) + })?; if !network_config.authorities.is_empty() { return Err(custom_http_error( @@ -218,46 +216,56 @@ async fn genesis( )); } - let working_dir = network_config.config_path().parent().unwrap().to_owned(); - let genesis_conf = GenesisConfig::default_genesis(&working_dir.join(genesis_config_path)) - .map_err(|error| { + let working_dir = network_config_path.parent().unwrap().to_owned(); + let genesis_conf = GenesisConfig::default_genesis(&working_dir).map_err(|error| { + custom_http_error( + StatusCode::CONFLICT, + format!("Unable to create default genesis configuration: {error}"), + ) + })?; + + let (network_config, accounts, keystore) = + sui_commands::genesis(genesis_conf).await.map_err(|err| { custom_http_error( - StatusCode::CONFLICT, - format!("Unable to create default genesis configuration: {error}"), + StatusCode::FAILED_DEPENDENCY, + format!("Genesis error: {:?}", err), ) })?; - let wallet_path = working_dir.join(wallet_config_path); + let keystore_path = working_dir.join("wallet.key"); + keystore.save(&keystore_path).map_err(|err| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Genesis error: {:?}", err), + ) + })?; // TODO: Rest service should use `ClientAddressManager` directly instead of using the wallet context. - let mut wallet_config = - WalletConfig::create(&working_dir.join(wallet_path)).map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Wallet config was unable to be created: {error}"), - ) - })?; // Need to use a random id because rocksdb locks on current process which // means even if the directory is deleted the lock will remain causing an // IO Error when a restart is attempted. let client_db_path = format!("client_db_{:?}", ObjectID::random()); + *server_context.client_db_path.lock().unwrap() = client_db_path.clone(); - if let GatewayType::Embedded(config) = wallet_config.gateway { - wallet_config.gateway = GatewayType::Embedded(EmbeddedGatewayConfig { - db_folder_path: working_dir.join(&client_db_path), - ..config + let authorities = network_config + .authorities + .iter() + .map(|info| AuthorityInfo { + name: *info.key_pair.public_key_bytes(), + host: info.host.clone(), + base_port: info.port, }) - } + .collect(); - *server_context.client_db_path.lock().unwrap() = client_db_path; - - sui_commands::genesis(&mut network_config, genesis_conf, &mut wallet_config) - .await - .map_err(|err| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Genesis error: {:?}", err), - ) - })?; + let wallet_config = WalletConfig { + accounts, + keystore: KeystoreType::File(keystore_path), + gateway: GatewayType::Embedded(EmbeddedGatewayConfig { + authorities, + db_folder_path: working_dir.join(&client_db_path), + ..Default::default() + }), + }; + //let wallet_path = working_dir.join(wallet_config_path); Ok(HttpResponseOk(GenesisResponse { wallet_config: json!(wallet_config), @@ -269,7 +277,7 @@ async fn genesis( Start servers with the specified configurations from the genesis endpoint. Note: This is a temporary endpoint that will no longer be needed once the -network has been started on testnet or mainnet. +network has been started on testnet or main-net. */ #[endpoint { method = POST, @@ -358,18 +366,16 @@ async fn sui_start( })); } - let wallet_config_path = &server_context.wallet_config_path; - - let wallet_config = - WalletConfig::read_or_create(&PathBuf::from(wallet_config_path)).map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to read wallet config: {error}"), - ) - })?; + let wallet_config_path = PathBuf::from(&server_context.wallet_config_path); + let wallet_config = WalletConfig::read_or_create(&wallet_config_path).map_err(|error| { + custom_http_error( + StatusCode::CONFLICT, + format!("Unable to read wallet config: {error}"), + ) + })?; let addresses = wallet_config.accounts.clone(); - let mut wallet_context = WalletContext::new(wallet_config).map_err(|error| { + let mut wallet_context = WalletContext::new(&wallet_config_path).map_err(|error| { custom_http_error( StatusCode::CONFLICT, format!("Can't create new wallet context: {error}"), diff --git a/sui/src/sui.rs b/sui/src/sui.rs index 5f948fe3de0f2..7d6c884b811da 100644 --- a/sui/src/sui.rs +++ b/sui/src/sui.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 extern crate core; -use std::path::PathBuf; use structopt::StructOpt; -use sui::config::{Config, NetworkConfig}; use sui::sui_commands::SuiCommand; use tracing::info; @@ -25,8 +23,6 @@ mod cli_tests; struct SuiOpt { #[structopt(subcommand)] command: SuiCommand, - #[structopt(long, default_value = "./network.conf")] - config: PathBuf, } #[tokio::main] @@ -61,8 +57,5 @@ async fn main() -> Result<(), anyhow::Error> { } let options: SuiOpt = SuiOpt::from_args(); - let network_conf_path = options.config; - let mut config = NetworkConfig::read_or_create(&network_conf_path)?; - - options.command.execute(&mut config).await + options.command.execute().await } diff --git a/sui/src/sui_commands.rs b/sui/src/sui_commands.rs index 3c12c0ff265b7..a8b7bbc1aa72d 100644 --- a/sui/src/sui_commands.rs +++ b/sui/src/sui_commands.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::anyhow; @@ -16,7 +16,7 @@ use sui_adapter::adapter::generate_package_id; use sui_adapter::genesis; use sui_core::authority::{AuthorityState, AuthorityStore}; use sui_core::authority_server::AuthorityServer; -use sui_types::base_types::{SequenceNumber, TxContext}; +use sui_types::base_types::{SequenceNumber, SuiAddress, TxContext}; use sui_types::committee::Committee; use sui_types::error::SuiResult; use sui_types::object::Object; @@ -25,47 +25,90 @@ use crate::config::{ AuthorityInfo, AuthorityPrivateInfo, Config, GenesisConfig, NetworkConfig, WalletConfig, }; use crate::gateway::{EmbeddedGatewayConfig, GatewayType}; -use crate::keystore::KeystoreType; +use crate::keystore::{Keystore, KeystoreType, SuiKeystore}; #[derive(StructOpt)] #[structopt(rename_all = "kebab-case")] pub enum SuiCommand { /// Start sui network. #[structopt(name = "start")] - Start, + Start { + #[structopt(long, default_value = "./network.conf")] + config: PathBuf, + }, #[structopt(name = "genesis")] Genesis { + #[structopt(long, default_value = ".")] + working_dir: PathBuf, #[structopt(long)] config: Option, }, } impl SuiCommand { - pub async fn execute(&self, config: &mut NetworkConfig) -> Result<(), anyhow::Error> { + pub async fn execute(&self) -> Result<(), anyhow::Error> { match self { - SuiCommand::Start => start_network(config).await, - SuiCommand::Genesis { config: path } => { - // Network config has been created by this point, safe to unwrap. - let working_dir = config.config_path().parent().unwrap(); + SuiCommand::Start { config } => start_network(config).await, + SuiCommand::Genesis { + working_dir, + config: path, + } => { + let network_path = working_dir.join("network.conf"); + let wallet_path = working_dir.join("wallet.conf"); + let keystore_path = working_dir.join("wallet.key"); + let db_folder_path = working_dir.join("client_db"); + + if let Ok(config) = NetworkConfig::read(&network_path) { + if !config.authorities.is_empty() { + return Err(anyhow!("Cannot run genesis on a existing network, please delete network config file and try again.")); + } + } + let genesis_conf = if let Some(path) = path { + // unwrap is ok here because the path exist GenesisConfig::read(path)? } else { - GenesisConfig::default_genesis(&working_dir.join("genesis.conf"))? + GenesisConfig::default_genesis(working_dir)? }; - let wallet_path = working_dir.join("wallet.conf"); - let mut wallet_config = WalletConfig::create(&wallet_path)?; - wallet_config.keystore = KeystoreType::File(working_dir.join("wallet.key")); - wallet_config.gateway = GatewayType::Embedded(EmbeddedGatewayConfig { - db_folder_path: working_dir.join("client_db"), - ..Default::default() - }); - genesis(config, genesis_conf, &mut wallet_config).await + + let (network_config, accounts, keystore) = genesis(genesis_conf).await?; + info!("Network genesis completed."); + network_config.write(&network_path)?; + info!("Network config file is stored in {:?}.", network_path); + + keystore.save(&keystore_path)?; + info!("Wallet keystore is stored in {:?}.", keystore_path); + + let authorities = network_config + .authorities + .iter() + .map(|info| AuthorityInfo { + name: *info.key_pair.public_key_bytes(), + host: info.host.clone(), + base_port: info.port, + }) + .collect(); + + let wallet_config = WalletConfig { + accounts, + keystore: KeystoreType::File(keystore_path), + gateway: GatewayType::Embedded(EmbeddedGatewayConfig { + db_folder_path, + authorities, + ..Default::default() + }), + }; + + wallet_config.write(&wallet_path)?; + info!("Wallet config file is stored in {:?}.", wallet_path); + Ok(()) } } } } -async fn start_network(config: &NetworkConfig) -> Result<(), anyhow::Error> { +async fn start_network(config_path: &Path) -> Result<(), anyhow::Error> { + let config = NetworkConfig::read(config_path)?; if config.authorities.is_empty() { return Err(anyhow!( "No authority configured for the network, please run genesis." @@ -109,57 +152,37 @@ async fn start_network(config: &NetworkConfig) -> Result<(), anyhow::Error> { } pub async fn genesis( - config: &mut NetworkConfig, genesis_conf: GenesisConfig, - wallet_config: &mut WalletConfig, -) -> Result<(), anyhow::Error> { - if !config.authorities.is_empty() { - return Err(anyhow!("Cannot run genesis on a existing network, please delete network config file and try again.")); - } - - let mut voting_right = BTreeMap::new(); - let mut authority_info = Vec::new(); - +) -> Result<(NetworkConfig, Vec, SuiKeystore), anyhow::Error> { info!( "Creating {} new authorities...", genesis_conf.authorities.len() ); + let mut network_config = NetworkConfig::default(); + let mut voting_right = BTreeMap::new(); + for authority in genesis_conf.authorities { voting_right.insert(*authority.key_pair.public_key_bytes(), authority.stake); - authority_info.push(AuthorityInfo { - name: *authority.key_pair.public_key_bytes(), - host: authority.host.clone(), - base_port: authority.port, - }); - config.authorities.push(authority); + network_config.authorities.push(authority); } - let mut new_addresses = Vec::new(); + let mut addresses = Vec::new(); let mut preload_modules: Vec> = Vec::new(); let mut preload_objects = Vec::new(); - let new_account_count = genesis_conf - .accounts - .iter() - .filter(|acc| acc.address.is_none()) - .count(); - - info!( - "Creating {} account(s) and gas objects...", - new_account_count - ); - - let mut keystore = wallet_config.keystore.init()?; + info!("Creating accounts and gas objects...",); + let mut keystore = SuiKeystore::default(); for account in genesis_conf.accounts { let address = if let Some(address) = account.address { address } else { - let address = keystore.add_random_key()?; - new_addresses.push(address); - address + keystore.add_random_key()? }; + + addresses.push(address); + for object_conf in account.gas_objects { let new_object = Object::with_id_owner_gas_coin_object_for_testing( object_conf.object_id, @@ -203,46 +226,25 @@ pub async fn genesis( info!("Loaded package [{}] from {:?}.", package_id, path); // Writing package id to network.conf for user to retrieve later. - config.loaded_move_packages.push((path, package_id)); + network_config.loaded_move_packages.push((path, package_id)); preload_modules.push(modules) } } let committee = Committee::new(voting_right); - for authority in &config.authorities { + for authority in &network_config.authorities { make_server_with_genesis_ctx( authority, &committee, preload_modules.clone(), &preload_objects, - config.buffer_size, + network_config.buffer_size, &mut genesis_ctx.clone(), ) .await?; } - if let GatewayType::Embedded(config) = &wallet_config.gateway { - wallet_config.gateway = GatewayType::Embedded(EmbeddedGatewayConfig { - db_folder_path: config.db_folder_path.clone(), - authorities: authority_info, - ..Default::default() - }); - } - - wallet_config.accounts = new_addresses; - - info!("Network genesis completed."); - config.save()?; - info!( - "Network config file is stored in {:?}.", - config.config_path() - ); - wallet_config.save()?; - info!( - "Wallet config file is stored in {:?}.", - wallet_config.config_path() - ); - Ok(()) + Ok((network_config, addresses, keystore)) } pub async fn make_server( diff --git a/sui/src/unit_tests/cli_tests.rs b/sui/src/unit_tests/cli_tests.rs index d545679574d6f..976dc30e9061e 100644 --- a/sui/src/unit_tests/cli_tests.rs +++ b/sui/src/unit_tests/cli_tests.rs @@ -1,18 +1,24 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use super::*; -use move_core_types::identifier::Identifier; -use serde_json::{json, Value}; + use std::fs::read_dir; use std::ops::Add; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str; use std::time::Duration; + +use move_core_types::identifier::Identifier; +use serde_json::{json, Value}; +use tempfile::TempDir; +use tokio::task; +use tokio::task::JoinHandle; +use tracing_test::traced_test; + use sui::config::{ - AccountConfig, AuthorityPrivateInfo, GenesisConfig, NetworkConfig, ObjectConfig, WalletConfig, - AUTHORITIES_DB_NAME, + AccountConfig, AuthorityPrivateInfo, Config, GenesisConfig, NetworkConfig, ObjectConfig, + WalletConfig, AUTHORITIES_DB_NAME, }; -use sui::gateway::GatewayType; +use sui::gateway::{EmbeddedGatewayConfig, GatewayType}; use sui::keystore::{KeystoreType, SuiKeystore}; use sui::sui_json::SuiJsonValue; use sui::wallet_commands::{WalletCommandResult, WalletCommands, WalletContext}; @@ -21,10 +27,8 @@ use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress}; use sui_types::crypto::get_key_pair; use sui_types::messages::TransactionEffects; use sui_types::object::{Object, ObjectRead, GAS_VALUE_FOR_TESTING}; -use tempfile::TempDir; -use tokio::task; -use tokio::task::JoinHandle; -use tracing_test::traced_test; + +use super::*; const TEST_DATA_DIR: &str = "src/unit_tests/data/"; const AIRDROP_SOURCE_CONTRACT_ADDRESS: &str = "bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"; @@ -48,20 +52,24 @@ macro_rules! retry_assert { #[traced_test] #[tokio::test] async fn test_genesis() -> Result<(), anyhow::Error> { - let working_dir = tempfile::tempdir()?; - let mut config = NetworkConfig::read_or_create(&working_dir.path().join("network.conf"))?; + let temp_dir = tempfile::tempdir()?; + let working_dir = temp_dir.path(); + let config = working_dir.join("network.conf"); // Start network without authorities - let start = SuiCommand::Start.execute(&mut config).await; + let start = SuiCommand::Start { config }.execute().await; assert!(matches!(start, Err(..))); // Genesis - SuiCommand::Genesis { config: None } - .execute(&mut config) - .await?; + SuiCommand::Genesis { + working_dir: working_dir.to_path_buf(), + config: None, + } + .execute() + .await?; assert!(logs_contain("Network genesis completed.")); // Get all the new file names - let files = read_dir(working_dir.path())? + let files = read_dir(working_dir)? .flat_map(|r| r.map(|file| file.file_name().to_str().unwrap().to_owned())) .collect::>(); @@ -72,15 +80,15 @@ async fn test_genesis() -> Result<(), anyhow::Error> { assert!(files.contains(&"wallet.key".to_string())); // Check network.conf - let network_conf = NetworkConfig::read_or_create(&working_dir.path().join("network.conf"))?; + let network_conf = NetworkConfig::read_or_create(&working_dir.join("network.conf"))?; assert_eq!(4, network_conf.authorities.len()); // Check wallet.conf - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; + let wallet_conf = WalletConfig::read_or_create(&working_dir.join("wallet.conf"))?; if let GatewayType::Embedded(config) = wallet_conf.gateway { assert_eq!(4, config.authorities.len()); - assert_eq!(working_dir.path().join("client_db"), config.db_folder_path); + assert_eq!(working_dir.join("client_db"), config.db_folder_path); } else { panic!() } @@ -88,21 +96,32 @@ async fn test_genesis() -> Result<(), anyhow::Error> { assert_eq!(5, wallet_conf.accounts.len()); // Genesis 2nd time should fail - let result = SuiCommand::Genesis { config: None } - .execute(&mut config) - .await; + let result = SuiCommand::Genesis { + working_dir: working_dir.to_path_buf(), + config: None, + } + .execute() + .await; assert!(matches!(result, Err(..))); - working_dir.close()?; + temp_dir.close()?; Ok(()) } #[traced_test] #[tokio::test] async fn test_addresses_command() -> Result<(), anyhow::Error> { - let working_dir = tempfile::tempdir()?; - - let mut wallet_config = WalletConfig::create(&working_dir.path().join("wallet.conf"))?; + let temp_dir = tempfile::tempdir()?; + let working_dir = temp_dir.path(); + + let mut wallet_config = WalletConfig { + accounts: vec![], + keystore: KeystoreType::File(working_dir.join("wallet.key")), + gateway: GatewayType::Embedded(EmbeddedGatewayConfig { + db_folder_path: working_dir.join("client_db"), + ..Default::default() + }), + }; // Add 3 accounts for _ in 0..3 { @@ -111,7 +130,10 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { address }); } - let mut context = WalletContext::new(wallet_config)?; + let wallet_conf_path = working_dir.join("wallet.conf"); + wallet_config.write(&wallet_conf_path)?; + + let mut context = WalletContext::new(&wallet_conf_path)?; // Print all addresses WalletCommands::Addresses @@ -196,15 +218,20 @@ async fn airdrop_get_wallet_context_with_oracle( 244, 88, 202, 115, 9, 82, 3, 184, 18, 236, 128, 199, 22, 37, 255, 146, 103, 34, 0, 240, 255, 163, 60, 174, ]); - let mut wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; + let wallet_conf_path = working_dir.path().join("wallet.conf"); + let mut wallet_conf = WalletConfig::read_or_create(&wallet_conf_path)?; let path = match &wallet_conf.keystore { KeystoreType::File(path) => path, _ => panic!("Unexpected KeystoreType"), }; let mut store = SuiKeystore::load_or_create(path)?; store.add_key(oracle_address, keypair)?; - Vec::push(&mut wallet_conf.accounts, oracle_address); - Ok((oracle_address, WalletContext::new(wallet_conf)?)) + store.save(path)?; + + wallet_conf.accounts.push(oracle_address); + wallet_conf.write(&wallet_conf_path)?; + + Ok((oracle_address, WalletContext::new(&wallet_conf_path)?)) } async fn airdrop_get_oracle_object( @@ -290,9 +317,8 @@ async fn test_objects_command() -> Result<(), anyhow::Error> { ); // Create Wallet context. - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; - let address = *wallet_conf.accounts.first().unwrap(); - let mut context = WalletContext::new(wallet_conf)?; + let mut context = WalletContext::new(&working_dir.path().join("wallet.conf"))?; + let address = context.config.accounts.first().cloned().unwrap(); // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address } @@ -323,8 +349,7 @@ async fn test_custom_genesis() -> Result<(), anyhow::Error> { let working_dir = tempfile::tempdir()?; // Create and save genesis config file // Create 4 authorities, 1 account with 1 gas object with custom id - let genesis_path = working_dir.path().join("genesis.conf"); - let mut config = GenesisConfig::default_genesis(&genesis_path)?; + let mut config = GenesisConfig::default_genesis(working_dir.path())?; config.accounts.clear(); let object_id = ObjectID::random(); config.accounts.push(AccountConfig { @@ -344,11 +369,11 @@ async fn test_custom_genesis() -> Result<(), anyhow::Error> { ); // Wallet config - let wallet_conf = WalletConfig::read(&working_dir.path().join("wallet.conf"))?; - assert_eq!(1, wallet_conf.accounts.len()); - let address = *wallet_conf.accounts.first().unwrap(); - let mut context = WalletContext::new(wallet_conf)?; + let mut context = WalletContext::new(&working_dir.path().join("wallet.conf"))?; + assert_eq!(1, context.config.accounts.len()); + let address = context.config.accounts.first().cloned().unwrap(); + // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address } .execute(&mut context) @@ -383,12 +408,13 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er 105, 254, 59, 163, ]); - let working_dir = tempfile::tempdir()?; + let temp_dir = tempfile::tempdir()?; + let working_dir = temp_dir.path(); // Create and save genesis config file // Create 4 authorities and 1 account - let genesis_path = working_dir.path().join("genesis.conf"); + let genesis_path = working_dir.join("genesis.conf"); let num_authorities = 4; - let mut config = GenesisConfig::custom_genesis(&genesis_path, num_authorities, 0, 0)?; + let mut config = GenesisConfig::custom_genesis(working_dir, num_authorities, 0, 0)?; config.accounts.clear(); config.accounts.push(AccountConfig { address: Some(address), @@ -400,21 +426,19 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er config .move_packages .push(PathBuf::from(TEST_DATA_DIR).join("custom_genesis_package_2")); - config.save()?; - - // Create empty network config for genesis - let mut config = NetworkConfig::read_or_create(&working_dir.path().join("network.conf"))?; + config.write(&genesis_path)?; // Genesis SuiCommand::Genesis { + working_dir: working_dir.to_path_buf(), config: Some(genesis_path), } - .execute(&mut config) + .execute() .await?; assert!(logs_contain("Loading 2 Move packages")); // Checks network config contains package ids - let network_conf = NetworkConfig::read(&working_dir.path().join("network.conf"))?; + let network_conf = NetworkConfig::read(&working_dir.join("network.conf"))?; assert_eq!(2, network_conf.loaded_move_packages.len()); // Make sure we log out package id @@ -423,7 +447,8 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er } // Start network - let network = task::spawn(async move { SuiCommand::Start.execute(&mut config).await }); + let config = working_dir.join("network.conf"); + let network = task::spawn(async move { SuiCommand::Start { config }.execute().await }); // Wait for authorities to come alive. retry_assert!( @@ -432,9 +457,11 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er ); // Create Wallet context. - let mut wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; + let wallet_conf_path = working_dir.join("wallet.conf"); + let mut wallet_conf = WalletConfig::read_or_create(&wallet_conf_path)?; wallet_conf.accounts = vec![address]; - let mut context = WalletContext::new(wallet_conf)?; + wallet_conf.write(&wallet_conf_path)?; + let mut context = WalletContext::new(&wallet_conf_path)?; // Make sure init() is executed correctly for custom_genesis_package_2::M1 let move_objects = get_move_objects(&mut context, address).await?; @@ -460,9 +487,9 @@ async fn test_object_info_get_command() -> Result<(), anyhow::Error> { ); // Create Wallet context. - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; - let address = *wallet_conf.accounts.first().unwrap(); - let mut context = WalletContext::new(wallet_conf)?; + let wallet_conf = working_dir.path().join("wallet.conf"); + let mut context = WalletContext::new(&wallet_conf)?; + let address = context.config.accounts.first().cloned().unwrap(); // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address } @@ -503,11 +530,10 @@ async fn test_gas_command() -> Result<(), anyhow::Error> { ); // Create Wallet context. - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; - let address = *wallet_conf.accounts.first().unwrap(); - let recipient = *wallet_conf.accounts.get(1).unwrap(); - - let mut context = WalletContext::new(wallet_conf)?; + let wallet_conf = working_dir.path().join("wallet.conf"); + let mut context = WalletContext::new(&wallet_conf)?; + let address = context.config.accounts.first().cloned().unwrap(); + let recipient = context.config.accounts.get(1).cloned().unwrap(); // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address } @@ -666,12 +692,12 @@ async fn start_network( starting_port: u16, genesis: Option, ) -> Result>, anyhow::Error> { - let network_conf_path = &working_dir.join("network.conf"); - let genesis_conf_path = &working_dir.join("genesis.conf"); + let working_dir = working_dir.to_path_buf(); + let network_conf_path = working_dir.join("network.conf"); + let genesis_conf_path = working_dir.join("genesis.conf"); - let mut config = NetworkConfig::read_or_create(network_conf_path)?; let mut port_allocator = PortAllocator::new(starting_port); - let mut genesis_config = genesis.unwrap_or(GenesisConfig::default_genesis(genesis_conf_path)?); + let mut genesis_config = genesis.unwrap_or(GenesisConfig::default_genesis(&working_dir)?); let authorities = genesis_config .authorities .iter() @@ -684,18 +710,23 @@ async fn start_network( }) .collect(); genesis_config.authorities = authorities; - genesis_config.save()?; + genesis_config.write(&genesis_conf_path)?; SuiCommand::Genesis { - config: Some(genesis_conf_path.to_path_buf()), + working_dir, + config: Some(genesis_conf_path), } - .execute(&mut config) + .execute() .await?; - let mut config = NetworkConfig::read(network_conf_path)?; - // Start network - let network = task::spawn(async move { SuiCommand::Start.execute(&mut config).await }); + let network = task::spawn(async move { + SuiCommand::Start { + config: network_conf_path, + } + .execute() + .await + }); Ok(network) } @@ -713,11 +744,11 @@ async fn test_move_call_args_linter_command() -> Result<(), anyhow::Error> { ); // Create Wallet context. - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; - let address1 = *wallet_conf.accounts.first().unwrap(); - let address2 = *wallet_conf.accounts.get(1).unwrap(); + let wallet_conf = working_dir.path().join("wallet.conf"); - let mut context = WalletContext::new(wallet_conf)?; + let mut context = WalletContext::new(&wallet_conf)?; + let address1 = context.config.accounts.first().cloned().unwrap(); + let address2 = context.config.accounts.get(1).cloned().unwrap(); // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address: address1 } @@ -897,9 +928,9 @@ async fn test_package_publish_command() -> Result<(), anyhow::Error> { ); // Create Wallet context. - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; - let address = *wallet_conf.accounts.first().unwrap(); - let mut context = WalletContext::new(wallet_conf)?; + let wallet_conf = working_dir.path().join("wallet.conf"); + let mut context = WalletContext::new(&wallet_conf)?; + let address = context.config.accounts.first().cloned().unwrap(); // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address } @@ -981,12 +1012,11 @@ async fn test_native_transfer() -> Result<(), anyhow::Error> { ); // Create Wallet context. - let wallet_conf = WalletConfig::read_or_create(&working_dir.path().join("wallet.conf"))?; - let address = *wallet_conf.accounts.first().unwrap(); - let recipient = *wallet_conf.accounts.get(1).unwrap(); - - let mut context = WalletContext::new(wallet_conf)?; + let wallet_conf = working_dir.path().join("wallet.conf"); + let mut context = WalletContext::new(&wallet_conf)?; + let address = context.config.accounts.first().cloned().unwrap(); + let recipient = context.config.accounts.get(1).cloned().unwrap(); // Sync client to retrieve objects from the network. WalletCommands::SyncClientState { address } .execute(&mut context) diff --git a/sui/src/wallet.rs b/sui/src/wallet.rs index 0f1c758478d5b..ab75cfa98cc3a 100644 --- a/sui/src/wallet.rs +++ b/sui/src/wallet.rs @@ -9,6 +9,7 @@ use async_trait::async_trait; use colored::Colorize; use structopt::clap::{App, AppSettings}; use structopt::StructOpt; +use sui::shell::{install_shell_plugins, AsyncHandler, CommandStructure, Shell}; use tracing::error; use tracing_subscriber::EnvFilter; @@ -65,13 +66,11 @@ async fn main() -> Result<(), anyhow::Error> { app = app.unset_setting(AppSettings::NoBinaryName); let options: ClientOpt = ClientOpt::from_clap(&app.get_matches()); let wallet_conf_path = options.config; - let config = - WalletConfig::read_or_create(&wallet_conf_path).expect("Unable to read wallet config"); - let addresses = config.accounts.clone(); - let mut context = WalletContext::new(config)?; + + let mut context = WalletContext::new(&wallet_conf_path)?; // Sync all accounts on start up. - for address in addresses { + for address in context.config.accounts.clone() { WalletCommands::SyncClientState { address } .execute(&mut context) .await?; diff --git a/sui/src/wallet_commands.rs b/sui/src/wallet_commands.rs index be544624097c8..eb05340ff1dec 100644 --- a/sui/src/wallet_commands.rs +++ b/sui/src/wallet_commands.rs @@ -1,9 +1,10 @@ +use core::fmt; // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use core::fmt; +use std::fmt::Write; use std::fmt::{Debug, Display, Formatter, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::{Arc, RwLock}; use std::time::Instant; @@ -35,6 +36,13 @@ use sui_types::move_package::resolve_and_type_check; use sui_types::object::ObjectRead; use sui_types::object::ObjectRead::Exists; +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +use crate::config::{Config, WalletConfig}; +use crate::keystore::Keystore; +use crate::sui_json::{resolve_move_function_args, SuiJsonValue}; +use sui_types::object::ObjectRead::Exists; + use crate::config::{Config, WalletConfig}; use crate::keystore::Keystore; use crate::sui_json::{resolve_move_function_args, SuiJsonValue}; @@ -363,7 +371,7 @@ impl WalletCommands { WalletCommands::NewAddress => { let address = context.keystore.write().unwrap().add_random_key()?; context.config.accounts.push(address); - context.config.save()?; + context.config.write(&context.config_path)?; WalletCommandResult::NewAddress(address) } WalletCommands::Gas { address } => { @@ -437,16 +445,19 @@ impl WalletCommands { pub struct WalletContext { pub config: WalletConfig, + pub config_path: PathBuf, pub keystore: Arc>>, pub gateway: GatewayClient, } impl WalletContext { - pub fn new(config: WalletConfig) -> Result { + pub fn new(config_path: &Path) -> Result { + let config = WalletConfig::read_or_create(config_path)?; let keystore = Arc::new(RwLock::new(config.keystore.init()?)); let gateway = config.gateway.init(); let context = Self { config, + config_path: config_path.to_path_buf(), keystore, gateway, }; From c1f84f0cdd87b9525f7aa0c24dd248812a9f8783 Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 13 Mar 2022 22:33:57 +0000 Subject: [PATCH 2/9] refactored rest_server to not use wallet configs and context, configs in rest_server are now transient and won't be persisted to disk, it is already deleting the configs when stop endpoint is call prior the changes. --- sui/src/config.rs | 75 ++++-- sui/src/keystore.rs | 4 + sui/src/rest_server.rs | 413 ++++++++++++-------------------- sui/src/sui_commands.rs | 27 +-- sui/src/unit_tests/cli_tests.rs | 44 ++-- sui/src/wallet.rs | 5 +- sui/src/wallet_commands.rs | 23 +- sui_core/src/gateway_state.rs | 22 +- 8 files changed, 272 insertions(+), 341 deletions(-) diff --git a/sui/src/config.rs b/sui/src/config.rs index 6c591b535e300..db49acec8ddb1 100644 --- a/sui/src/config.rs +++ b/sui/src/config.rs @@ -6,6 +6,7 @@ use std::fmt::Write; use std::fmt::{Display, Formatter}; use std::fs::{self, File}; use std::io::BufReader; +use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::sync::Mutex; @@ -111,8 +112,6 @@ pub struct WalletConfig { pub gateway: GatewayType, } -impl Config for WalletConfig {} - impl Default for WalletConfig { fn default() -> Self { Self { @@ -142,8 +141,6 @@ pub struct NetworkConfig { pub loaded_move_packages: Vec<(PathBuf, ObjectID)>, } -impl Config for NetworkConfig {} - impl Default for NetworkConfig { fn default() -> Self { Self { @@ -154,6 +151,19 @@ impl Default for NetworkConfig { } } +impl NetworkConfig { + pub fn get_authority_infos(&self) -> Vec { + self.authorities + .iter() + .map(|info| AuthorityInfo { + name: *info.key_pair.public_key_bytes(), + host: info.host.clone(), + base_port: info.port, + }) + .collect() + } +} + #[derive(Serialize, Deserialize)] #[serde(default)] pub struct GenesisConfig { @@ -251,8 +261,6 @@ impl GenesisConfig { } } -impl Config for GenesisConfig {} - impl Default for GenesisConfig { fn default() -> Self { Self { @@ -265,32 +273,63 @@ impl Default for GenesisConfig { } } -pub trait Config +pub struct PersistedConfig { + inner: C, + path: PathBuf, +} + +impl PersistedConfig where - Self: DeserializeOwned + Serialize + Default, + C: DeserializeOwned + Serialize, { - fn read_or_create(path: &Path) -> Result { - let path_buf = PathBuf::from(path); + pub fn from_config(config: C, path: &Path) -> Self { + Self { + inner: config, + path: path.to_path_buf(), + } + } + + pub fn read_or_else(path: &Path, default: F) -> Result + where + F: FnOnce() -> Result, + { + let path_buf = path.to_path_buf(); Ok(if path_buf.exists() { Self::read(path)? } else { trace!("Config file not found, creating new config '{:?}'", path); - let new_config = Self::default(); - new_config.write(path)?; - new_config + Self::from_config(default()?, path) }) } - fn read(path: &Path) -> Result { + pub fn read_config(path: &Path) -> Result { trace!("Reading config from '{:?}'", path); let reader = BufReader::new(File::open(path)?); Ok(serde_json::from_reader(reader)?) } - fn write(&self, path: &Path) -> Result<(), anyhow::Error> { - trace!("Writing config to '{:?}'", path); - let config = serde_json::to_string_pretty(self).unwrap(); - fs::write(path, config).expect("Unable to write to config file"); + pub fn read(path: &Path) -> Result { + Ok(Self::from_config(Self::read_config(path)?, path)) + } + + pub fn save(&self) -> Result<(), anyhow::Error> { + trace!("Writing config to '{:?}'", &self.path); + let config = serde_json::to_string_pretty(&self.inner)?; + fs::write(&self.path, config)?; Ok(()) } } + +impl Deref for PersistedConfig { + type Target = C; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for PersistedConfig { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/sui/src/keystore.rs b/sui/src/keystore.rs index b1e20afd08f43..2431d1c853e69 100644 --- a/sui/src/keystore.rs +++ b/sui/src/keystore.rs @@ -99,6 +99,10 @@ impl SuiKeystore { self.keys.insert(address, keypair); Ok(()) } + + pub fn addresses(&self) -> Vec { + self.keys.keys().cloned().collect() + } } pub struct SuiKeystoreSigner { diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index fbc351bdc4d48..60d3d2dd780fa 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; use std::fs; use std::net::{Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use dropshot::{endpoint, Query, TypedBody}; use dropshot::{ @@ -24,12 +25,13 @@ use serde_json::json; use tokio::task::{self, JoinHandle}; use tracing::{error, info}; -use sui::config::{AuthorityInfo, Config, GenesisConfig, NetworkConfig, WalletConfig}; +use sui::config::{GenesisConfig, NetworkConfig}; use sui::gateway::{EmbeddedGatewayConfig, GatewayType}; -use sui::keystore::KeystoreType; +use sui::keystore::Keystore; use sui::sui_commands; use sui::sui_json::{resolve_move_function_args, SuiJsonValue}; -use sui::wallet_commands::{SimpleTransactionSigner, WalletContext}; +use sui::wallet_commands::SimpleTransactionSigner; +use sui_core::gateway_state::GatewayClient; use sui_types::base_types::*; use sui_types::committee::Committee; use sui_types::event::Event; @@ -96,28 +98,46 @@ async fn main() -> Result<(), String> { */ struct ServerContext { documentation: serde_json::Value, - wallet_config_path: String, - network_config_path: String, - authority_db_path: String, - client_db_path: Arc>, + server_state: Arc>>, +} + +struct ServerState { + config: NetworkConfig, + gateway: GatewayClient, + keystore: Arc>>, + addresses: Vec, + working_dir: PathBuf, // Server handles that will be used to restart authorities. - authority_handles: Arc>>>, - // Used to manage addresses for client. - wallet_context: Arc>>, + authority_handles: Vec>, +} + +impl Debug for ServerState { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "ServerState") + } } impl ServerContext { pub fn new(documentation: serde_json::Value) -> ServerContext { ServerContext { documentation, - wallet_config_path: String::from("wallet.conf"), - network_config_path: String::from("./network.conf"), - authority_db_path: String::from("./authorities_db"), - client_db_path: Arc::new(Mutex::new(String::new())), - authority_handles: Arc::new(Mutex::new(Vec::new())), - wallet_context: Arc::new(Mutex::new(None)), + server_state: Arc::new(Mutex::new(None)), } } + + fn take_server_state(&self) -> Result { + let mut state = self.server_state.lock().unwrap(); + state.take().ok_or_else(|| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + "Server state does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." + .to_string(), + ) + }) + } + fn return_server_state(&self, state: ServerState) { + *self.server_state.lock().unwrap() = Some(state); + } } /** @@ -196,27 +216,16 @@ network has been started on testnet or mainnet. async fn genesis( rqctx: Arc>, ) -> Result, HttpError> { - let server_context = rqctx.context(); - //let genesis_config_path = &server_context.genesis_config_path; - let network_config_path = &server_context.network_config_path; - //let wallet_config_path = &server_context.wallet_config_path; + let context = rqctx.context(); + let working_dir = PathBuf::from(".").join(format!("{}", ObjectID::random())); - let network_config_path = PathBuf::from(network_config_path); - let network_config = NetworkConfig::read_or_create(&network_config_path).map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to read network config: {error}"), - ) - })?; - - if !network_config.authorities.is_empty() { + if context.server_state.lock().unwrap().is_some() { return Err(custom_http_error( StatusCode::CONFLICT, String::from("Cannot run genesis on a existing network, please make a POST request to the `sui/stop` endpoint to reset."), )); } - let working_dir = network_config_path.parent().unwrap().to_owned(); let genesis_conf = GenesisConfig::default_genesis(&working_dir).map_err(|error| { custom_http_error( StatusCode::CONFLICT, @@ -232,45 +241,33 @@ async fn genesis( ) })?; - let keystore_path = working_dir.join("wallet.key"); - keystore.save(&keystore_path).map_err(|err| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Genesis error: {:?}", err), - ) - })?; - // TODO: Rest service should use `ClientAddressManager` directly instead of using the wallet context. + let authorities = network_config.get_authority_infos(); // Need to use a random id because rocksdb locks on current process which // means even if the directory is deleted the lock will remain causing an // IO Error when a restart is attempted. - let client_db_path = format!("client_db_{:?}", ObjectID::random()); - *server_context.client_db_path.lock().unwrap() = client_db_path.clone(); + let gateway = GatewayType::Embedded(EmbeddedGatewayConfig { + authorities, + db_folder_path: working_dir.join("client_db"), + ..Default::default() + }); - let authorities = network_config - .authorities - .iter() - .map(|info| AuthorityInfo { - name: *info.key_pair.public_key_bytes(), - host: info.host.clone(), - base_port: info.port, - }) - .collect(); - - let wallet_config = WalletConfig { - accounts, - keystore: KeystoreType::File(keystore_path), - gateway: GatewayType::Embedded(EmbeddedGatewayConfig { - authorities, - db_folder_path: working_dir.join(&client_db_path), - ..Default::default() - }), + let response = HttpResponseOk(GenesisResponse { + wallet_config: json!(accounts), + network_config: json!(network_config), + }); + + let state = ServerState { + config: network_config, + gateway: gateway.init(), + keystore: Arc::new(RwLock::new(Box::new(keystore))), + addresses: accounts, + working_dir: working_dir.to_path_buf(), + authority_handles: vec![], }; - //let wallet_path = working_dir.join(wallet_config_path); - Ok(HttpResponseOk(GenesisResponse { - wallet_config: json!(wallet_config), - network_config: json!(network_config), - })) + *context.server_state.lock().unwrap() = Some(state); + + Ok(response) } /** @@ -287,35 +284,20 @@ network has been started on testnet or main-net. async fn sui_start( rqctx: Arc>, ) -> Result, HttpError> { - let server_context = rqctx.context(); - let network_config_path = &server_context.network_config_path; - - let network_config = NetworkConfig::read_or_create(&PathBuf::from(network_config_path)) - .map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to read network config: {error}"), - ) - })?; + let context = rqctx.context(); - if network_config.authorities.is_empty() { + let mut state = context.take_server_state()?; + if !state.authority_handles.is_empty() { + context.return_server_state(state); return Err(custom_http_error( - StatusCode::CONFLICT, - String::from("No authority configured for the network, please make a POST request to the `sui/genesis` endpoint."), + StatusCode::FORBIDDEN, + String::from("Sui network is already running."), )); } - { - if !(*server_context.authority_handles.lock().unwrap()).is_empty() { - return Err(custom_http_error( - StatusCode::FORBIDDEN, - String::from("Sui network is already running."), - )); - } - } - let committee = Committee::new( - network_config + state + .config .authorities .iter() .map(|info| (*info.key_pair.public_key_bytes(), info.stake)) @@ -323,21 +305,16 @@ async fn sui_start( ); let mut handles = FuturesUnordered::new(); - for authority in &network_config.authorities { - let server = sui_commands::make_server( - authority, - &committee, - vec![], - &[], - network_config.buffer_size, - ) - .await - .map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to make server: {error}"), - ) - })?; + for authority in &state.config.authorities { + let server = + sui_commands::make_server(authority, &committee, vec![], &[], state.config.buffer_size) + .await + .map_err(|error| { + custom_http_error( + StatusCode::CONFLICT, + format!("Unable to make server: {error}"), + ) + })?; handles.push(async move { match server.spawn().await { Ok(server) => Ok(server), @@ -355,38 +332,17 @@ async fn sui_start( info!("Started {} authorities", num_authorities); while let Some(spawned_server) = handles.next().await { - server_context - .authority_handles - .lock() - .unwrap() - .push(task::spawn(async { - if let Err(err) = spawned_server.unwrap().join().await { - error!("Server ended with an error: {}", err); - } - })); + state.authority_handles.push(task::spawn(async { + if let Err(err) = spawned_server.unwrap().join().await { + error!("Server ended with an error: {}", err); + } + })); } - let wallet_config_path = PathBuf::from(&server_context.wallet_config_path); - let wallet_config = WalletConfig::read_or_create(&wallet_config_path).map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to read wallet config: {error}"), - ) - })?; - - let addresses = wallet_config.accounts.clone(); - let mut wallet_context = WalletContext::new(&wallet_config_path).map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Can't create new wallet context: {error}"), - ) - })?; - - // Sync all accounts. - for address in addresses.iter() { - wallet_context + for address in state.addresses.clone() { + state .gateway - .sync_account_state(*address) + .sync_account_state(address) .await .map_err(|err| { custom_http_error( @@ -395,9 +351,7 @@ async fn sui_start( ) })?; } - - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - + context.return_server_state(state); Ok(HttpResponseOk(format!( "Started {} authorities", num_authorities @@ -419,17 +373,12 @@ async fn sui_stop( rqctx: Arc>, ) -> Result { let server_context = rqctx.context(); + let state = server_context.take_server_state()?; - for authority_handle in &*server_context.authority_handles.lock().unwrap() { + for authority_handle in state.authority_handles { authority_handle.abort(); } - (*server_context.authority_handles.lock().unwrap()).clear(); - - fs::remove_dir_all(server_context.client_db_path.lock().unwrap().clone()).ok(); - fs::remove_dir_all(&server_context.authority_db_path).ok(); - fs::remove_file(&server_context.network_config_path).ok(); - fs::remove_file(&server_context.wallet_config_path).ok(); - + fs::remove_dir_all(state.working_dir).ok(); Ok(HttpResponseUpdatedNoContent()) } @@ -455,26 +404,20 @@ async fn get_addresses( rqctx: Arc>, ) -> Result, HttpError> { let server_context = rqctx.context(); - // TODO: Find a better way to utilize wallet context here that does not require 'take()' - let mut wallet_context = - get_wallet_context(server_context.wallet_context.lock().unwrap().take())?; - - let addresses: Vec = wallet_context.config.accounts.clone(); - + let mut state = server_context.take_server_state()?; // TODO: Speed up sync operations by kicking them off concurrently. // Also need to investigate if this should be an automatic sync or manually triggered. - for address in addresses.iter() { - if let Err(err) = wallet_context.gateway.sync_account_state(*address).await { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + let addresses = state.addresses.clone(); + for address in &addresses { + if let Err(err) = state.gateway.sync_account_state(*address).await { + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Can't create client state: {err}"), )); } } - - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - + server_context.return_server_state(state); Ok(HttpResponseOk(GetAddressResponse { addresses: addresses .into_iter() @@ -533,14 +476,7 @@ async fn get_objects( let get_objects_params = query.into_inner(); let address = get_objects_params.address; - let wallet_context = &mut *server_context.wallet_context.lock().unwrap(); - let wallet_context = wallet_context.as_mut().ok_or_else(|| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - "Wallet Context does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." - .to_string(), - ) - })?; + let mut state = server_context.take_server_state()?; let address = &decode_bytes_hex(address.as_str()).map_err(|error| { custom_http_error( @@ -549,8 +485,9 @@ async fn get_objects( ) })?; - let object_refs = wallet_context.gateway.get_owned_objects(*address); + let object_refs = state.gateway.get_owned_objects(*address); + server_context.return_server_state(state); Ok(HttpResponseOk(GetObjectsResponse { objects: object_refs .iter() @@ -602,13 +539,11 @@ async fn object_schema( let server_context = rqctx.context(); let object_info_params = query.into_inner(); - // TODO: Find a better way to utilize wallet context here that does not require 'take()' - let wallet_context = get_wallet_context(server_context.wallet_context.lock().unwrap().take())?; + let state = server_context.take_server_state()?; let object_id = match ObjectID::try_from(object_info_params.object_id) { Ok(object_id) => object_id, Err(error) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("{error}"), @@ -616,24 +551,24 @@ async fn object_schema( } }; - let layout = match wallet_context.gateway.get_object_info(object_id).await { + let layout = match state.gateway.get_object_info(object_id).await { Ok(ObjectRead::Exists(_, _, layout)) => layout, Ok(ObjectRead::Deleted(_)) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Object ({object_id}) was deleted."), )); } Ok(ObjectRead::NotExists(_)) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Object ({object_id}) does not exist."), )); } Err(error) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Error while getting object info: {:?}", error), @@ -641,6 +576,7 @@ async fn object_schema( } }; + server_context.return_server_state(state); match serde_json::to_value(layout) { Ok(schema) => Ok(HttpResponseOk(ObjectSchemaResponse { schema })), Err(e) => Err(custom_http_error( @@ -699,13 +635,12 @@ async fn object_info( let server_context = rqctx.context(); let object_info_params = query.into_inner(); - // TODO: Find a better way to utilize wallet context here that does not require 'take()' - let wallet_context = get_wallet_context(server_context.wallet_context.lock().unwrap().take())?; + let state = server_context.take_server_state()?; let object_id = match ObjectID::try_from(object_info_params.object_id) { Ok(object_id) => object_id, Err(error) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("{error}"), @@ -713,18 +648,11 @@ async fn object_info( } }; - let (object, layout) = match get_object_info(&wallet_context, object_id).await { - Ok((_, object, layout)) => (object, layout), - Err(error) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - return Err(error); - } - }; + let (_, object, layout) = get_object_info(&state, object_id).await?; let object_data = object.to_json(&layout).unwrap_or_else(|_| json!("")); - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - + server_context.return_server_state(state); Ok(HttpResponseOk(ObjectInfoResponse { owner: format!("{:?}", object.owner), version: format!("{:?}", object.version().value()), @@ -795,6 +723,8 @@ async fn transfer_object( request: TypedBody, ) -> Result, HttpError> { let server_context = rqctx.context(); + let mut state = server_context.take_server_state()?; + let transfer_order_params = request.into_inner(); let to_address = decode_bytes_hex(transfer_order_params.to_address.as_str()).map_err(|error| { @@ -814,15 +744,11 @@ async fn transfer_object( ) })?; - // TODO: Find a better way to utilize wallet context here that does not require 'take()' - let mut wallet_context = - get_wallet_context(server_context.wallet_context.lock().unwrap().take())?; - let tx_signer = Box::pin(SimpleTransactionSigner { - keystore: wallet_context.keystore.clone(), + keystore: state.keystore.clone(), }); - let (cert, effects, gas_used) = match wallet_context + let (cert, effects, gas_used) = match state .gateway .transfer_coin(owner, object_id, gas_object_id, to_address, tx_signer) .await @@ -833,7 +759,7 @@ async fn transfer_object( // ExecutionStatus::Success ExecutionStatus::Success { gas_used, .. } => gas_used, ExecutionStatus::Failure { gas_used, error } => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!( @@ -846,7 +772,7 @@ async fn transfer_object( (cert, effects, gas_used) } Err(err) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + server_context.return_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Transfer error: {err}"), @@ -854,16 +780,9 @@ async fn transfer_object( } }; - let object_effects_summary = match get_object_effects(&wallet_context, effects).await { - Ok(effects) => effects, - Err(err) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - return Err(err); - } - }; - - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + let object_effects_summary = get_object_effects(&state, effects).await?; + server_context.return_server_state(state); Ok(HttpResponseOk(TransactionResponse { gas_used, object_effects_summary: json!(object_effects_summary), @@ -970,19 +889,13 @@ async fn call( let server_context = rqctx.context(); let call_params = request.into_inner(); - let mut wallet_context = - get_wallet_context(server_context.wallet_context.lock().unwrap().take())?; + let mut state = server_context.take_server_state()?; - let transaction_response = match handle_move_call(call_params, &mut wallet_context).await { - Ok(transaction_response) => transaction_response, - Err(err) => { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - return Err(custom_http_error(StatusCode::BAD_REQUEST, format!("{err}"))); - } - }; - - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + let transaction_response = handle_move_call(call_params, &mut state) + .await + .map_err(|err| custom_http_error(StatusCode::BAD_REQUEST, format!("{err}")))?; + server_context.return_server_state(state); Ok(HttpResponseOk(transaction_response)) } @@ -1012,6 +925,9 @@ async fn sync( ) -> Result { let server_context = rqctx.context(); let sync_params = request.into_inner(); + + let mut state = server_context.take_server_state()?; + let address = decode_bytes_hex(sync_params.address.as_str()).map_err(|error| { custom_http_error( StatusCode::FAILED_DEPENDENCY, @@ -1019,33 +935,30 @@ async fn sync( ) })?; - // TODO: Find a better way to utilize wallet context here that does not require 'take()' - let mut wallet_context = - get_wallet_context(server_context.wallet_context.lock().unwrap().take())?; - - // Attempt to create a new account state, but continue if it already exists. - if let Err(err) = wallet_context.gateway.sync_account_state(address).await { - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Can't create client state: {err}"), - )); - } - - *server_context.wallet_context.lock().unwrap() = Some(wallet_context); + state + .gateway + .sync_account_state(address) + .await + .map_err(|err| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Can't create client state: {err}"), + ) + })?; + server_context.return_server_state(state); Ok(HttpResponseUpdatedNoContent()) } async fn get_object_effects( - wallet_context: &WalletContext, + state: &ServerState, transaction_effects: TransactionEffects, ) -> Result>>, HttpError> { let mut object_effects_summary = HashMap::new(); object_effects_summary.insert( String::from("created_objects"), get_obj_ref_effects( - wallet_context, + state, transaction_effects .created .into_iter() @@ -1057,7 +970,7 @@ async fn get_object_effects( object_effects_summary.insert( String::from("mutated_objects"), get_obj_ref_effects( - wallet_context, + state, transaction_effects .mutated .into_iter() @@ -1069,7 +982,7 @@ async fn get_object_effects( object_effects_summary.insert( String::from("unwrapped_objects"), get_obj_ref_effects( - wallet_context, + state, transaction_effects .unwrapped .into_iter() @@ -1080,11 +993,11 @@ async fn get_object_effects( ); object_effects_summary.insert( String::from("deleted_objects"), - get_obj_ref_effects(wallet_context, transaction_effects.deleted).await?, + get_obj_ref_effects(state, transaction_effects.deleted).await?, ); object_effects_summary.insert( String::from("wrapped_objects"), - get_obj_ref_effects(wallet_context, transaction_effects.wrapped).await?, + get_obj_ref_effects(state, transaction_effects.wrapped).await?, ); object_effects_summary.insert( String::from("events"), @@ -1105,12 +1018,12 @@ fn get_events(events: Vec) -> Result>, HttpEr } async fn get_obj_ref_effects( - wallet_context: &WalletContext, + state: &ServerState, object_refs: Vec, ) -> Result>, HttpError> { let mut effects = Vec::new(); for (object_id, sequence_number, object_digest) in object_refs { - let effect = get_effect(wallet_context, object_id, sequence_number, object_digest) + let effect = get_effect(state, object_id, sequence_number, object_digest) .await .map_err(|error| error)?; effects.push(effect); @@ -1119,13 +1032,13 @@ async fn get_obj_ref_effects( } async fn get_effect( - wallet_context: &WalletContext, + state: &ServerState, object_id: ObjectID, sequence_number: SequenceNumber, object_digest: ObjectDigest, ) -> Result, HttpError> { let mut effect = HashMap::new(); - let object = match get_object_info(wallet_context, object_id).await { + let object = match get_object_info(state, object_id).await { Ok((_, object, _)) => object, Err(error) => { return Err(error); @@ -1145,11 +1058,10 @@ async fn get_effect( } async fn get_object_info( - wallet_context: &WalletContext, + state: &ServerState, object_id: ObjectID, ) -> Result<(ObjectRef, SuiObject, Option), HttpError> { - let (object_ref, object, layout) = match wallet_context.gateway.get_object_info(object_id).await - { + let (object_ref, object, layout) = match state.gateway.get_object_info(object_id).await { Ok(ObjectRead::Exists(object_ref, object, layout)) => (object_ref, object, layout), Ok(ObjectRead::Deleted(_)) => { return Err(custom_http_error( @@ -1175,7 +1087,7 @@ async fn get_object_info( async fn handle_move_call( call_params: CallRequest, - wallet_context: &mut WalletContext, + state: &mut ServerState, ) -> Result { let module = Identifier::from_str(&call_params.module.to_owned())?; let function = Identifier::from_str(&call_params.function.to_owned())?; @@ -1192,8 +1104,7 @@ async fn handle_move_call( let sender: SuiAddress = decode_bytes_hex(call_params.sender.as_str())?; - let (package_object_ref, package_object, _) = - get_object_info(wallet_context, package_object_id).await?; + let (package_object_ref, package_object, _) = get_object_info(state, package_object_id).await?; // Extract the input args let (object_ids, pure_args) = @@ -1204,7 +1115,7 @@ async fn handle_move_call( // Fetch all the objects needed for this call let mut input_objs = vec![]; for obj_id in object_ids.clone() { - let (_, object, _) = get_object_info(wallet_context, obj_id).await?; + let (_, object, _) = get_object_info(state, obj_id).await?; input_objs.push(object); } @@ -1219,20 +1130,20 @@ async fn handle_move_call( )?; // Fetch the object info for the gas obj - let (gas_obj_ref, _, _) = get_object_info(wallet_context, gas_object_id).await?; + let (gas_obj_ref, _, _) = get_object_info(state, gas_object_id).await?; // Fetch the objects for the object args let mut object_args_refs = Vec::new(); for obj_id in object_ids { - let (object_ref, _, _) = get_object_info(wallet_context, obj_id).await?; + let (object_ref, _, _) = get_object_info(state, obj_id).await?; object_args_refs.push(object_ref); } let tx_signer = Box::pin(SimpleTransactionSigner { - keystore: wallet_context.keystore.clone(), + keystore: state.keystore.clone(), }); - let (cert, effects, gas_used) = match wallet_context + let (cert, effects, gas_used) = match state .gateway .move_call( sender, @@ -1267,7 +1178,7 @@ async fn handle_move_call( } }; - let object_effects_summary = get_object_effects(wallet_context, effects).await?; + let object_effects_summary = get_object_effects(state, effects).await?; Ok(TransactionResponse { gas_used, @@ -1276,16 +1187,6 @@ async fn handle_move_call( }) } -fn get_wallet_context(wallet_context: Option) -> Result { - wallet_context.ok_or_else(|| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - "Wallet Context does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." - .to_string(), - ) - }) -} - fn custom_http_error(status_code: http::StatusCode, message: String) -> HttpError { HttpError::for_client_error(None, status_code, message) } diff --git a/sui/src/sui_commands.rs b/sui/src/sui_commands.rs index a8b7bbc1aa72d..b6a81bf6b1562 100644 --- a/sui/src/sui_commands.rs +++ b/sui/src/sui_commands.rs @@ -22,7 +22,7 @@ use sui_types::error::SuiResult; use sui_types::object::Object; use crate::config::{ - AuthorityInfo, AuthorityPrivateInfo, Config, GenesisConfig, NetworkConfig, WalletConfig, + AuthorityPrivateInfo, GenesisConfig, NetworkConfig, PersistedConfig, WalletConfig, }; use crate::gateway::{EmbeddedGatewayConfig, GatewayType}; use crate::keystore::{Keystore, KeystoreType, SuiKeystore}; @@ -58,48 +58,39 @@ impl SuiCommand { let keystore_path = working_dir.join("wallet.key"); let db_folder_path = working_dir.join("client_db"); - if let Ok(config) = NetworkConfig::read(&network_path) { + if let Ok(config) = PersistedConfig::::read(&network_path) { if !config.authorities.is_empty() { return Err(anyhow!("Cannot run genesis on a existing network, please delete network config file and try again.")); } } let genesis_conf = if let Some(path) = path { - // unwrap is ok here because the path exist - GenesisConfig::read(path)? + PersistedConfig::read_config(path)? } else { GenesisConfig::default_genesis(working_dir)? }; let (network_config, accounts, keystore) = genesis(genesis_conf).await?; info!("Network genesis completed."); - network_config.write(&network_path)?; + let network_config = PersistedConfig::from_config(network_config, &network_path); + network_config.save()?; info!("Network config file is stored in {:?}.", network_path); keystore.save(&keystore_path)?; info!("Wallet keystore is stored in {:?}.", keystore_path); - let authorities = network_config - .authorities - .iter() - .map(|info| AuthorityInfo { - name: *info.key_pair.public_key_bytes(), - host: info.host.clone(), - base_port: info.port, - }) - .collect(); - let wallet_config = WalletConfig { accounts, keystore: KeystoreType::File(keystore_path), gateway: GatewayType::Embedded(EmbeddedGatewayConfig { db_folder_path, - authorities, + authorities: network_config.get_authority_infos(), ..Default::default() }), }; - wallet_config.write(&wallet_path)?; + let wallet_config = PersistedConfig::from_config(wallet_config, &wallet_path); + wallet_config.save()?; info!("Wallet config file is stored in {:?}.", wallet_path); Ok(()) } @@ -108,7 +99,7 @@ impl SuiCommand { } async fn start_network(config_path: &Path) -> Result<(), anyhow::Error> { - let config = NetworkConfig::read(config_path)?; + let config: NetworkConfig = PersistedConfig::read_config(config_path)?; if config.authorities.is_empty() { return Err(anyhow!( "No authority configured for the network, please run genesis." diff --git a/sui/src/unit_tests/cli_tests.rs b/sui/src/unit_tests/cli_tests.rs index 976dc30e9061e..3400109b21a1b 100644 --- a/sui/src/unit_tests/cli_tests.rs +++ b/sui/src/unit_tests/cli_tests.rs @@ -15,8 +15,8 @@ use tokio::task::JoinHandle; use tracing_test::traced_test; use sui::config::{ - AccountConfig, AuthorityPrivateInfo, Config, GenesisConfig, NetworkConfig, ObjectConfig, - WalletConfig, AUTHORITIES_DB_NAME, + AccountConfig, AuthorityPrivateInfo, GenesisConfig, NetworkConfig, ObjectConfig, + PersistedConfig, WalletConfig, AUTHORITIES_DB_NAME, }; use sui::gateway::{EmbeddedGatewayConfig, GatewayType}; use sui::keystore::{KeystoreType, SuiKeystore}; @@ -80,13 +80,13 @@ async fn test_genesis() -> Result<(), anyhow::Error> { assert!(files.contains(&"wallet.key".to_string())); // Check network.conf - let network_conf = NetworkConfig::read_or_create(&working_dir.join("network.conf"))?; + let network_conf = PersistedConfig::::read(&working_dir.join("network.conf"))?; assert_eq!(4, network_conf.authorities.len()); // Check wallet.conf - let wallet_conf = WalletConfig::read_or_create(&working_dir.join("wallet.conf"))?; + let wallet_conf = PersistedConfig::::read(&working_dir.join("wallet.conf"))?; - if let GatewayType::Embedded(config) = wallet_conf.gateway { + if let GatewayType::Embedded(config) = &wallet_conf.gateway { assert_eq!(4, config.authorities.len()); assert_eq!(working_dir.join("client_db"), config.db_folder_path); } else { @@ -114,7 +114,7 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { let temp_dir = tempfile::tempdir()?; let working_dir = temp_dir.path(); - let mut wallet_config = WalletConfig { + let wallet_config = WalletConfig { accounts: vec![], keystore: KeystoreType::File(working_dir.join("wallet.key")), gateway: GatewayType::Embedded(EmbeddedGatewayConfig { @@ -122,6 +122,8 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { ..Default::default() }), }; + let wallet_conf_path = working_dir.join("wallet.conf"); + let mut wallet_config = PersistedConfig::from_config(wallet_config, &wallet_conf_path); // Add 3 accounts for _ in 0..3 { @@ -130,8 +132,7 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { address }); } - let wallet_conf_path = working_dir.join("wallet.conf"); - wallet_config.write(&wallet_conf_path)?; + wallet_config.save()?; let mut context = WalletContext::new(&wallet_conf_path)?; @@ -142,7 +143,7 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { .print(true); // Check log output contains all addresses - for address in context.config.accounts { + for address in &context.config.accounts { assert!(logs_contain(&*format!("{}", address))); } @@ -155,7 +156,7 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { async fn test_cross_chain_airdrop() -> Result<(), anyhow::Error> { let working_dir = tempfile::tempdir()?; - let network = start_network(working_dir.path(), 10101, None).await?; + let network = start_network(working_dir.path(), 10800, None).await?; // Wait for authorities to come alive. retry_assert!( logs_contain("Listening to TCP traffic on 127.0.0.1"), @@ -219,7 +220,8 @@ async fn airdrop_get_wallet_context_with_oracle( 255, 163, 60, 174, ]); let wallet_conf_path = working_dir.path().join("wallet.conf"); - let mut wallet_conf = WalletConfig::read_or_create(&wallet_conf_path)?; + let mut wallet_conf = + PersistedConfig::read_or_else(&wallet_conf_path, || Ok(WalletConfig::default()))?; let path = match &wallet_conf.keystore { KeystoreType::File(path) => path, _ => panic!("Unexpected KeystoreType"), @@ -229,7 +231,7 @@ async fn airdrop_get_wallet_context_with_oracle( store.save(path)?; wallet_conf.accounts.push(oracle_address); - wallet_conf.write(&wallet_conf_path)?; + wallet_conf.save()?; Ok((oracle_address, WalletContext::new(&wallet_conf_path)?)) } @@ -414,7 +416,9 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er // Create 4 authorities and 1 account let genesis_path = working_dir.join("genesis.conf"); let num_authorities = 4; - let mut config = GenesisConfig::custom_genesis(working_dir, num_authorities, 0, 0)?; + let config = GenesisConfig::custom_genesis(working_dir, num_authorities, 0, 0)?; + let mut config = PersistedConfig::from_config(config, &genesis_path); + config.accounts.clear(); config.accounts.push(AccountConfig { address: Some(address), @@ -426,7 +430,7 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er config .move_packages .push(PathBuf::from(TEST_DATA_DIR).join("custom_genesis_package_2")); - config.write(&genesis_path)?; + config.save()?; // Genesis SuiCommand::Genesis { @@ -438,11 +442,11 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er assert!(logs_contain("Loading 2 Move packages")); // Checks network config contains package ids - let network_conf = NetworkConfig::read(&working_dir.join("network.conf"))?; + let network_conf = PersistedConfig::::read(&working_dir.join("network.conf"))?; assert_eq!(2, network_conf.loaded_move_packages.len()); // Make sure we log out package id - for (_, id) in network_conf.loaded_move_packages { + for (_, id) in &network_conf.loaded_move_packages { assert!(logs_contain(&*format!("{}", id))); } @@ -458,9 +462,9 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er // Create Wallet context. let wallet_conf_path = working_dir.join("wallet.conf"); - let mut wallet_conf = WalletConfig::read_or_create(&wallet_conf_path)?; + let mut wallet_conf = PersistedConfig::::read(&wallet_conf_path)?; wallet_conf.accounts = vec![address]; - wallet_conf.write(&wallet_conf_path)?; + wallet_conf.save()?; let mut context = WalletContext::new(&wallet_conf_path)?; // Make sure init() is executed correctly for custom_genesis_package_2::M1 @@ -710,7 +714,9 @@ async fn start_network( }) .collect(); genesis_config.authorities = authorities; - genesis_config.write(&genesis_conf_path)?; + + let genesis_config = PersistedConfig::from_config(genesis_config, &genesis_conf_path); + genesis_config.save()?; SuiCommand::Genesis { working_dir, diff --git a/sui/src/wallet.rs b/sui/src/wallet.rs index ab75cfa98cc3a..26a2851695143 100644 --- a/sui/src/wallet.rs +++ b/sui/src/wallet.rs @@ -3,17 +3,16 @@ use std::io::Write; use std::io::{stderr, stdout}; +use std::ops::Deref; use std::path::PathBuf; use async_trait::async_trait; use colored::Colorize; use structopt::clap::{App, AppSettings}; use structopt::StructOpt; -use sui::shell::{install_shell_plugins, AsyncHandler, CommandStructure, Shell}; use tracing::error; use tracing_subscriber::EnvFilter; -use sui::config::{Config, WalletConfig}; use sui::shell::{ install_shell_plugins, AsyncHandler, CacheKey, CommandStructure, CompletionCache, Shell, }; @@ -88,7 +87,7 @@ async fn main() -> Result<(), anyhow::Error> { .unwrap_or_else(|| app.p.meta.version.unwrap_or("unknown")); writeln!(out, "--- sui wallet {} ---", version)?; writeln!(out)?; - writeln!(out, "{}", context.config)?; + writeln!(out, "{}", context.config.deref())?; writeln!(out, "Welcome to the Sui interactive shell.")?; writeln!(out)?; diff --git a/sui/src/wallet_commands.rs b/sui/src/wallet_commands.rs index eb05340ff1dec..41ab1fb80a026 100644 --- a/sui/src/wallet_commands.rs +++ b/sui/src/wallet_commands.rs @@ -1,10 +1,8 @@ -use core::fmt; // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - -use std::fmt::Write; +use core::fmt; use std::fmt::{Debug, Display, Formatter, Write}; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::{Arc, RwLock}; use std::time::Instant; @@ -36,14 +34,7 @@ use sui_types::move_package::resolve_and_type_check; use sui_types::object::ObjectRead; use sui_types::object::ObjectRead::Exists; -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 -use crate::config::{Config, WalletConfig}; -use crate::keystore::Keystore; -use crate::sui_json::{resolve_move_function_args, SuiJsonValue}; -use sui_types::object::ObjectRead::Exists; - -use crate::config::{Config, WalletConfig}; +use crate::config::{PersistedConfig, WalletConfig}; use crate::keystore::Keystore; use crate::sui_json::{resolve_move_function_args, SuiJsonValue}; @@ -371,7 +362,7 @@ impl WalletCommands { WalletCommands::NewAddress => { let address = context.keystore.write().unwrap().add_random_key()?; context.config.accounts.push(address); - context.config.write(&context.config_path)?; + context.config.save()?; WalletCommandResult::NewAddress(address) } WalletCommands::Gas { address } => { @@ -444,20 +435,18 @@ impl WalletCommands { } pub struct WalletContext { - pub config: WalletConfig, - pub config_path: PathBuf, + pub config: PersistedConfig, pub keystore: Arc>>, pub gateway: GatewayClient, } impl WalletContext { pub fn new(config_path: &Path) -> Result { - let config = WalletConfig::read_or_create(config_path)?; + let config = PersistedConfig::read_or_else(config_path, || Ok(WalletConfig::default()))?; let keystore = Arc::new(RwLock::new(config.keystore.init()?)); let gateway = config.gateway.init(); let context = Self { config, - config_path: config_path.to_path_buf(), keystore, gateway, }; diff --git a/sui_core/src/gateway_state.rs b/sui_core/src/gateway_state.rs index ff269ad6ce2e0..1224f97d654c1 100644 --- a/sui_core/src/gateway_state.rs +++ b/sui_core/src/gateway_state.rs @@ -2,13 +2,23 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::{authority_aggregator::AuthorityAggregator, authority_client::AuthorityAPI}; +use std::collections::btree_map::Entry; +use std::path::PathBuf; +use std::time::Duration; +use std::{ + collections::{BTreeMap, BTreeSet, HashSet}, + pin::Pin, +}; + use async_trait::async_trait; use futures::future; use itertools::Itertools; use move_core_types::identifier::Identifier; use move_core_types::language_storage::TypeTag; use move_core_types::value::MoveStructLayout; +use typed_store::rocks::open_cf; +use typed_store::Map; + use sui_types::crypto::Signature; use sui_types::error::SuiResult; use sui_types::{ @@ -21,16 +31,8 @@ use sui_types::{ object::{Object, ObjectRead, Owner}, SUI_FRAMEWORK_ADDRESS, }; -use typed_store::rocks::open_cf; -use typed_store::Map; -use std::collections::btree_map::Entry; -use std::path::PathBuf; -use std::time::Duration; -use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, - pin::Pin, -}; +use crate::{authority_aggregator::AuthorityAggregator, authority_client::AuthorityAPI}; use self::gateway_responses::*; From 1c3566d461c7e926bfef384648e5e402477715f8 Mon Sep 17 00:00:00 2001 From: patrick Date: Mon, 14 Mar 2022 11:36:32 +0000 Subject: [PATCH 3/9] encode addresses to hex --- sui/src/rest_server.rs | 51 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index 60d3d2dd780fa..bd4a160c3cb12 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -135,7 +135,7 @@ impl ServerContext { ) }) } - fn return_server_state(&self, state: ServerState) { + fn set_server_state(&self, state: ServerState) { *self.server_state.lock().unwrap() = Some(state); } } @@ -194,7 +194,7 @@ provided genesis configuration. #[serde(rename_all = "camelCase")] struct GenesisResponse { /** List of managed addresses and the list of authorities */ - wallet_config: serde_json::Value, + addresses: serde_json::Value, /** Information about authorities and the list of loaded move packages. */ network_config: serde_json::Value, } @@ -251,10 +251,9 @@ async fn genesis( ..Default::default() }); - let response = HttpResponseOk(GenesisResponse { - wallet_config: json!(accounts), - network_config: json!(network_config), - }); + let addresses = accounts.iter().map(encode_bytes_hex).collect::>(); + let addresses_json = json!(addresses); + let network_config_json = json!(network_config); let state = ServerState { config: network_config, @@ -264,10 +263,12 @@ async fn genesis( working_dir: working_dir.to_path_buf(), authority_handles: vec![], }; + context.set_server_state(state); - *context.server_state.lock().unwrap() = Some(state); - - Ok(response) + Ok(HttpResponseOk(GenesisResponse { + addresses: addresses_json, + network_config: network_config_json, + })) } /** @@ -288,7 +289,7 @@ async fn sui_start( let mut state = context.take_server_state()?; if !state.authority_handles.is_empty() { - context.return_server_state(state); + context.set_server_state(state); return Err(custom_http_error( StatusCode::FORBIDDEN, String::from("Sui network is already running."), @@ -351,7 +352,7 @@ async fn sui_start( ) })?; } - context.return_server_state(state); + context.set_server_state(state); Ok(HttpResponseOk(format!( "Started {} authorities", num_authorities @@ -410,14 +411,14 @@ async fn get_addresses( let addresses = state.addresses.clone(); for address in &addresses { if let Err(err) = state.gateway.sync_account_state(*address).await { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Can't create client state: {err}"), )); } } - server_context.return_server_state(state); + server_context.set_server_state(state); Ok(HttpResponseOk(GetAddressResponse { addresses: addresses .into_iter() @@ -487,7 +488,7 @@ async fn get_objects( let object_refs = state.gateway.get_owned_objects(*address); - server_context.return_server_state(state); + server_context.set_server_state(state); Ok(HttpResponseOk(GetObjectsResponse { objects: object_refs .iter() @@ -554,21 +555,21 @@ async fn object_schema( let layout = match state.gateway.get_object_info(object_id).await { Ok(ObjectRead::Exists(_, _, layout)) => layout, Ok(ObjectRead::Deleted(_)) => { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Object ({object_id}) was deleted."), )); } Ok(ObjectRead::NotExists(_)) => { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Object ({object_id}) does not exist."), )); } Err(error) => { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Error while getting object info: {:?}", error), @@ -576,7 +577,7 @@ async fn object_schema( } }; - server_context.return_server_state(state); + server_context.set_server_state(state); match serde_json::to_value(layout) { Ok(schema) => Ok(HttpResponseOk(ObjectSchemaResponse { schema })), Err(e) => Err(custom_http_error( @@ -640,7 +641,7 @@ async fn object_info( let object_id = match ObjectID::try_from(object_info_params.object_id) { Ok(object_id) => object_id, Err(error) => { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("{error}"), @@ -652,7 +653,7 @@ async fn object_info( let object_data = object.to_json(&layout).unwrap_or_else(|_| json!("")); - server_context.return_server_state(state); + server_context.set_server_state(state); Ok(HttpResponseOk(ObjectInfoResponse { owner: format!("{:?}", object.owner), version: format!("{:?}", object.version().value()), @@ -759,7 +760,7 @@ async fn transfer_object( // ExecutionStatus::Success ExecutionStatus::Success { gas_used, .. } => gas_used, ExecutionStatus::Failure { gas_used, error } => { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!( @@ -772,7 +773,7 @@ async fn transfer_object( (cert, effects, gas_used) } Err(err) => { - server_context.return_server_state(state); + server_context.set_server_state(state); return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, format!("Transfer error: {err}"), @@ -782,7 +783,7 @@ async fn transfer_object( let object_effects_summary = get_object_effects(&state, effects).await?; - server_context.return_server_state(state); + server_context.set_server_state(state); Ok(HttpResponseOk(TransactionResponse { gas_used, object_effects_summary: json!(object_effects_summary), @@ -895,7 +896,7 @@ async fn call( .await .map_err(|err| custom_http_error(StatusCode::BAD_REQUEST, format!("{err}")))?; - server_context.return_server_state(state); + server_context.set_server_state(state); Ok(HttpResponseOk(transaction_response)) } @@ -946,7 +947,7 @@ async fn sync( ) })?; - server_context.return_server_state(state); + server_context.set_server_state(state); Ok(HttpResponseUpdatedNoContent()) } From dc7d065c6f0230de675eac673c68b05160a5ce56 Mon Sep 17 00:00:00 2001 From: patrick Date: Mon, 14 Mar 2022 12:08:16 +0000 Subject: [PATCH 4/9] refactor config --- sui/src/config.rs | 81 +++++++++------------------------ sui/src/sui_commands.rs | 17 ++++--- sui/src/unit_tests/cli_tests.rs | 15 +++--- sui/src/wallet_commands.rs | 5 +- 4 files changed, 44 insertions(+), 74 deletions(-) diff --git a/sui/src/config.rs b/sui/src/config.rs index db49acec8ddb1..a5d6acd144d4c 100644 --- a/sui/src/config.rs +++ b/sui/src/config.rs @@ -20,7 +20,6 @@ use tracing::log::trace; use sui_framework::DEFAULT_FRAMEWORK_PATH; use sui_network::network::PortAllocator; -use sui_network::transport; use sui_types::base_types::*; use sui_types::crypto::{get_key_pair, KeyPair}; @@ -112,15 +111,7 @@ pub struct WalletConfig { pub gateway: GatewayType, } -impl Default for WalletConfig { - fn default() -> Self { - Self { - accounts: Vec::new(), - keystore: KeystoreType::File(PathBuf::from("./wallet.key")), - gateway: GatewayType::Embedded(Default::default()), - } - } -} +impl Config for WalletConfig {} impl Display for WalletConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -141,15 +132,7 @@ pub struct NetworkConfig { pub loaded_move_packages: Vec<(PathBuf, ObjectID)>, } -impl Default for NetworkConfig { - fn default() -> Self { - Self { - authorities: vec![], - buffer_size: transport::DEFAULT_MAX_DATAGRAM_SIZE, - loaded_move_packages: vec![], - } - } -} +impl Config for NetworkConfig {} impl NetworkConfig { pub fn get_authority_infos(&self) -> Vec { @@ -170,12 +153,12 @@ pub struct GenesisConfig { pub authorities: Vec, pub accounts: Vec, pub move_packages: Vec, - #[serde(default = "default_sui_framework_lib")] pub sui_framework_lib_path: PathBuf, - #[serde(default = "default_move_framework_lib")] pub move_framework_lib_path: PathBuf, } +impl Config for GenesisConfig {} + #[derive(Serialize, Deserialize, Default)] #[serde(default)] pub struct AccountConfig { @@ -200,16 +183,6 @@ fn default_gas_value() -> u64 { DEFAULT_GAS_AMOUNT } -fn default_sui_framework_lib() -> PathBuf { - PathBuf::from(DEFAULT_FRAMEWORK_PATH) -} - -fn default_move_framework_lib() -> PathBuf { - PathBuf::from(DEFAULT_FRAMEWORK_PATH) - .join("deps") - .join("move-stdlib") -} - const DEFAULT_NUMBER_OF_AUTHORITIES: usize = 4; const DEFAULT_NUMBER_OF_ACCOUNT: usize = 5; const DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT: usize = 5; @@ -267,8 +240,22 @@ impl Default for GenesisConfig { authorities: vec![], accounts: vec![], move_packages: vec![], - sui_framework_lib_path: default_sui_framework_lib(), - move_framework_lib_path: default_move_framework_lib(), + sui_framework_lib_path: PathBuf::from(DEFAULT_FRAMEWORK_PATH), + move_framework_lib_path: PathBuf::from(DEFAULT_FRAMEWORK_PATH) + .join("deps") + .join("move-stdlib"), + } + } +} + +pub trait Config +where + Self: DeserializeOwned + Serialize, +{ + fn persisted(self, path: &Path) -> PersistedConfig { + PersistedConfig { + inner: self, + path: path.to_path_buf(), } } } @@ -280,38 +267,14 @@ pub struct PersistedConfig { impl PersistedConfig where - C: DeserializeOwned + Serialize, + C: Config, { - pub fn from_config(config: C, path: &Path) -> Self { - Self { - inner: config, - path: path.to_path_buf(), - } - } - - pub fn read_or_else(path: &Path, default: F) -> Result - where - F: FnOnce() -> Result, - { - let path_buf = path.to_path_buf(); - Ok(if path_buf.exists() { - Self::read(path)? - } else { - trace!("Config file not found, creating new config '{:?}'", path); - Self::from_config(default()?, path) - }) - } - - pub fn read_config(path: &Path) -> Result { + pub fn read(path: &Path) -> Result { trace!("Reading config from '{:?}'", path); let reader = BufReader::new(File::open(path)?); Ok(serde_json::from_reader(reader)?) } - pub fn read(path: &Path) -> Result { - Ok(Self::from_config(Self::read_config(path)?, path)) - } - pub fn save(&self) -> Result<(), anyhow::Error> { trace!("Writing config to '{:?}'", &self.path); let config = serde_json::to_string_pretty(&self.inner)?; diff --git a/sui/src/sui_commands.rs b/sui/src/sui_commands.rs index b6a81bf6b1562..0b70cd580b0d6 100644 --- a/sui/src/sui_commands.rs +++ b/sui/src/sui_commands.rs @@ -16,13 +16,14 @@ use sui_adapter::adapter::generate_package_id; use sui_adapter::genesis; use sui_core::authority::{AuthorityState, AuthorityStore}; use sui_core::authority_server::AuthorityServer; +use sui_network::transport::DEFAULT_MAX_DATAGRAM_SIZE; use sui_types::base_types::{SequenceNumber, SuiAddress, TxContext}; use sui_types::committee::Committee; use sui_types::error::SuiResult; use sui_types::object::Object; use crate::config::{ - AuthorityPrivateInfo, GenesisConfig, NetworkConfig, PersistedConfig, WalletConfig, + AuthorityPrivateInfo, Config, GenesisConfig, NetworkConfig, PersistedConfig, WalletConfig, }; use crate::gateway::{EmbeddedGatewayConfig, GatewayType}; use crate::keystore::{Keystore, KeystoreType, SuiKeystore}; @@ -65,14 +66,14 @@ impl SuiCommand { } let genesis_conf = if let Some(path) = path { - PersistedConfig::read_config(path)? + PersistedConfig::read(path)? } else { GenesisConfig::default_genesis(working_dir)? }; let (network_config, accounts, keystore) = genesis(genesis_conf).await?; info!("Network genesis completed."); - let network_config = PersistedConfig::from_config(network_config, &network_path); + let network_config = network_config.persisted(&network_path); network_config.save()?; info!("Network config file is stored in {:?}.", network_path); @@ -89,7 +90,7 @@ impl SuiCommand { }), }; - let wallet_config = PersistedConfig::from_config(wallet_config, &wallet_path); + let wallet_config = wallet_config.persisted(&wallet_path); wallet_config.save()?; info!("Wallet config file is stored in {:?}.", wallet_path); Ok(()) @@ -99,7 +100,7 @@ impl SuiCommand { } async fn start_network(config_path: &Path) -> Result<(), anyhow::Error> { - let config: NetworkConfig = PersistedConfig::read_config(config_path)?; + let config: NetworkConfig = PersistedConfig::read(config_path)?; if config.authorities.is_empty() { return Err(anyhow!( "No authority configured for the network, please run genesis." @@ -150,7 +151,11 @@ pub async fn genesis( genesis_conf.authorities.len() ); - let mut network_config = NetworkConfig::default(); + let mut network_config = NetworkConfig { + authorities: vec![], + buffer_size: DEFAULT_MAX_DATAGRAM_SIZE, + loaded_move_packages: vec![], + }; let mut voting_right = BTreeMap::new(); for authority in genesis_conf.authorities { diff --git a/sui/src/unit_tests/cli_tests.rs b/sui/src/unit_tests/cli_tests.rs index 3400109b21a1b..32b9f6a9dcea4 100644 --- a/sui/src/unit_tests/cli_tests.rs +++ b/sui/src/unit_tests/cli_tests.rs @@ -15,7 +15,7 @@ use tokio::task::JoinHandle; use tracing_test::traced_test; use sui::config::{ - AccountConfig, AuthorityPrivateInfo, GenesisConfig, NetworkConfig, ObjectConfig, + AccountConfig, AuthorityPrivateInfo, Config, GenesisConfig, NetworkConfig, ObjectConfig, PersistedConfig, WalletConfig, AUTHORITIES_DB_NAME, }; use sui::gateway::{EmbeddedGatewayConfig, GatewayType}; @@ -123,7 +123,7 @@ async fn test_addresses_command() -> Result<(), anyhow::Error> { }), }; let wallet_conf_path = working_dir.join("wallet.conf"); - let mut wallet_config = PersistedConfig::from_config(wallet_config, &wallet_conf_path); + let mut wallet_config = wallet_config.persisted(&wallet_conf_path); // Add 3 accounts for _ in 0..3 { @@ -220,8 +220,8 @@ async fn airdrop_get_wallet_context_with_oracle( 255, 163, 60, 174, ]); let wallet_conf_path = working_dir.path().join("wallet.conf"); - let mut wallet_conf = - PersistedConfig::read_or_else(&wallet_conf_path, || Ok(WalletConfig::default()))?; + let wallet_conf: WalletConfig = PersistedConfig::read(&wallet_conf_path)?; + let mut wallet_conf = wallet_conf.persisted(&wallet_conf_path); let path = match &wallet_conf.keystore { KeystoreType::File(path) => path, _ => panic!("Unexpected KeystoreType"), @@ -417,7 +417,7 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er let genesis_path = working_dir.join("genesis.conf"); let num_authorities = 4; let config = GenesisConfig::custom_genesis(working_dir, num_authorities, 0, 0)?; - let mut config = PersistedConfig::from_config(config, &genesis_path); + let mut config = config.persisted(&genesis_path); config.accounts.clear(); config.accounts.push(AccountConfig { @@ -462,7 +462,8 @@ async fn test_custom_genesis_with_custom_move_package() -> Result<(), anyhow::Er // Create Wallet context. let wallet_conf_path = working_dir.join("wallet.conf"); - let mut wallet_conf = PersistedConfig::::read(&wallet_conf_path)?; + let wallet_conf: WalletConfig = PersistedConfig::read(&wallet_conf_path)?; + let mut wallet_conf = wallet_conf.persisted(&wallet_conf_path); wallet_conf.accounts = vec![address]; wallet_conf.save()?; let mut context = WalletContext::new(&wallet_conf_path)?; @@ -715,7 +716,7 @@ async fn start_network( .collect(); genesis_config.authorities = authorities; - let genesis_config = PersistedConfig::from_config(genesis_config, &genesis_conf_path); + let genesis_config = genesis_config.persisted(&genesis_conf_path); genesis_config.save()?; SuiCommand::Genesis { diff --git a/sui/src/wallet_commands.rs b/sui/src/wallet_commands.rs index 41ab1fb80a026..bb26c418d235d 100644 --- a/sui/src/wallet_commands.rs +++ b/sui/src/wallet_commands.rs @@ -34,7 +34,7 @@ use sui_types::move_package::resolve_and_type_check; use sui_types::object::ObjectRead; use sui_types::object::ObjectRead::Exists; -use crate::config::{PersistedConfig, WalletConfig}; +use crate::config::{Config, PersistedConfig, WalletConfig}; use crate::keystore::Keystore; use crate::sui_json::{resolve_move_function_args, SuiJsonValue}; @@ -442,7 +442,8 @@ pub struct WalletContext { impl WalletContext { pub fn new(config_path: &Path) -> Result { - let config = PersistedConfig::read_or_else(config_path, || Ok(WalletConfig::default()))?; + let config: WalletConfig = PersistedConfig::read(config_path)?; + let config = config.persisted(config_path); let keystore = Arc::new(RwLock::new(config.keystore.init()?)); let gateway = config.gateway.init(); let context = Self { From 01f2fe65e6cfdfa8df00d4f880a27b83a28d3e9f Mon Sep 17 00:00:00 2001 From: patrick Date: Mon, 14 Mar 2022 15:03:01 +0000 Subject: [PATCH 5/9] add comments and revert import reordering --- sui/src/rest_server.rs | 7 ++++++- sui_core/src/gateway_state.rs | 22 ++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index bd4a160c3cb12..32be1755731a6 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -98,6 +98,7 @@ async fn main() -> Result<(), String> { */ struct ServerContext { documentation: serde_json::Value, + // ServerState is created after genesis. server_state: Arc>>, } @@ -124,7 +125,9 @@ impl ServerContext { server_state: Arc::new(Mutex::new(None)), } } - + // TODO: Can this work without take? + // Take is required here because dropshot's `HttpHandlerFunc` is Send + Sync + 'static + // and we cannot use &mut reference on the server_state object. fn take_server_state(&self) -> Result { let mut state = self.server_state.lock().unwrap(); state.take().ok_or_else(|| { @@ -135,6 +138,8 @@ impl ServerContext { ) }) } + // This is to return ownership of ServerState after take() + // TODO: Anyway to make this automatic? fn set_server_state(&self, state: ServerState) { *self.server_state.lock().unwrap() = Some(state); } diff --git a/sui_core/src/gateway_state.rs b/sui_core/src/gateway_state.rs index 1224f97d654c1..ff269ad6ce2e0 100644 --- a/sui_core/src/gateway_state.rs +++ b/sui_core/src/gateway_state.rs @@ -2,23 +2,13 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::btree_map::Entry; -use std::path::PathBuf; -use std::time::Duration; -use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, - pin::Pin, -}; - +use crate::{authority_aggregator::AuthorityAggregator, authority_client::AuthorityAPI}; use async_trait::async_trait; use futures::future; use itertools::Itertools; use move_core_types::identifier::Identifier; use move_core_types::language_storage::TypeTag; use move_core_types::value::MoveStructLayout; -use typed_store::rocks::open_cf; -use typed_store::Map; - use sui_types::crypto::Signature; use sui_types::error::SuiResult; use sui_types::{ @@ -31,8 +21,16 @@ use sui_types::{ object::{Object, ObjectRead, Owner}, SUI_FRAMEWORK_ADDRESS, }; +use typed_store::rocks::open_cf; +use typed_store::Map; -use crate::{authority_aggregator::AuthorityAggregator, authority_client::AuthorityAPI}; +use std::collections::btree_map::Entry; +use std::path::PathBuf; +use std::time::Duration; +use std::{ + collections::{BTreeMap, BTreeSet, HashSet}, + pin::Pin, +}; use self::gateway_responses::*; From 5acc5e6b8da859a1c8df47c8051e12e3d72ed048 Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 15 Mar 2022 13:05:52 +0000 Subject: [PATCH 6/9] added `with_state` and `with_state_no_param` functions to help manage take and return of server state object --- sui/src/internal.rs | 416 ++++++++++++++++++++++++++++++++++++ sui/src/rest_server.rs | 469 +++++------------------------------------ 2 files changed, 469 insertions(+), 416 deletions(-) create mode 100644 sui/src/internal.rs diff --git a/sui/src/internal.rs b/sui/src/internal.rs new file mode 100644 index 0000000000000..277f112d14e3d --- /dev/null +++ b/sui/src/internal.rs @@ -0,0 +1,416 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +use super::*; + +pub fn sync( + state: &mut ServerState, + sync_params: SyncRequest, +) -> AsyncResult { + Box::pin(async move { + let address = decode_bytes_hex(sync_params.address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode to address from hex {error}"), + ) + })?; + + state + .gateway + .sync_account_state(address) + .await + .map_err(|err| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Can't create client state: {err}"), + ) + })?; + Ok(HttpResponseUpdatedNoContent()) + }) +} + +pub fn get_addresses( + state: &mut ServerState, +) -> AsyncResult, HttpError> { + Box::pin(async { + // TODO: Speed up sync operations by kicking them off concurrently. + // Also need to investigate if this should be an automatic sync or manually triggered. + let addresses = state.addresses.clone(); + for address in &addresses { + if let Err(err) = state.gateway.sync_account_state(*address).await { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Can't create client state: {err}"), + )); + } + } + Ok(HttpResponseOk(GetAddressResponse { + addresses: addresses + .into_iter() + .map(|address| format!("{}", address)) + .collect(), + })) + }) +} + +pub fn get_objects( + state: &mut ServerState, + get_objects_params: GetObjectsRequest, +) -> AsyncResult, HttpError> { + Box::pin(async { + let address = get_objects_params.address; + let address = &decode_bytes_hex(address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode address from hex {error}"), + ) + })?; + + let object_refs = state.gateway.get_owned_objects(*address); + Ok(HttpResponseOk(GetObjectsResponse { + objects: object_refs + .iter() + .map(|(object_id, sequence_number, object_digest)| Object { + object_id: object_id.to_string(), + version: format!("{:?}", sequence_number), + object_digest: format!("{:?}", object_digest), + }) + .collect::>(), + })) + }) +} + +pub fn object_schema( + state: &mut ServerState, + object_info_params: GetObjectSchemaRequest, +) -> AsyncResult, HttpError> { + Box::pin(async { + let object_id = match ObjectID::try_from(object_info_params.object_id) { + Ok(object_id) => object_id, + Err(error) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("{error}"), + )); + } + }; + + let layout = match state.gateway.get_object_info(object_id).await { + Ok(ObjectRead::Exists(_, _, layout)) => layout, + Ok(ObjectRead::Deleted(_)) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Object ({object_id}) was deleted."), + )); + } + Ok(ObjectRead::NotExists(_)) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Object ({object_id}) does not exist."), + )); + } + Err(error) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Error while getting object info: {:?}", error), + )); + } + }; + + match serde_json::to_value(layout) { + Ok(schema) => Ok(HttpResponseOk(ObjectSchemaResponse { schema })), + Err(e) => Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Error while getting object info: {:?}", e), + )), + } + }) +} + +pub fn object_info( + state: &mut ServerState, + object_info_params: GetObjectInfoRequest, +) -> AsyncResult, HttpError> { + Box::pin(async move { + let object_id = match ObjectID::try_from(object_info_params.object_id) { + Ok(object_id) => object_id, + Err(error) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("{error}"), + )); + } + }; + + let (_, object, layout) = get_object_info(state, object_id).await?; + let object_data = object.to_json(&layout).unwrap_or_else(|_| json!("")); + Ok(HttpResponseOk(ObjectInfoResponse { + owner: format!("{:?}", object.owner), + version: format!("{:?}", object.version().value()), + id: format!("{:?}", object.id()), + readonly: format!("{:?}", object.is_read_only()), + obj_type: object + .data + .type_() + .map_or("Unknown Type".to_owned(), |type_| format!("{}", type_)), + data: object_data, + })) + }) +} + +pub fn transfer_object( + state: &mut ServerState, + transfer_order_params: TransferTransactionRequest, +) -> AsyncResult, HttpError> { + Box::pin(async move { + let to_address = + decode_bytes_hex(transfer_order_params.to_address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode to address from hex {error}"), + ) + })?; + let object_id = ObjectID::try_from(transfer_order_params.object_id).map_err(|error| { + custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")) + })?; + let gas_object_id = + ObjectID::try_from(transfer_order_params.gas_object_id).map_err(|error| { + custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")) + })?; + let owner = + decode_bytes_hex(transfer_order_params.from_address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode address from hex {error}"), + ) + })?; + + let tx_signer = Box::pin(SimpleTransactionSigner { + keystore: state.keystore.clone(), + }); + + let (cert, effects, gas_used) = match state + .gateway + .transfer_coin(owner, object_id, gas_object_id, to_address, tx_signer) + .await + { + Ok((cert, effects)) => { + let gas_used = match effects.status { + ExecutionStatus::Success { gas_used } => gas_used, + ExecutionStatus::Failure { gas_used, error } => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!( + "Error transferring object: {:#?}, gas used {}", + error, gas_used + ), + )); + } + }; + (cert, effects, gas_used) + } + Err(err) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Transfer error: {err}"), + )); + } + }; + + let object_effects_summary = get_object_effects(state, effects).await?; + + Ok(HttpResponseOk(TransactionResponse { + gas_used, + object_effects_summary: json!(object_effects_summary), + certificate: json!(cert), + })) + }) +} + +pub fn call( + state: &mut ServerState, + call_params: CallRequest, +) -> AsyncResult, HttpError> { + Box::pin(async { + let transaction_response = handle_move_call(call_params, state) + .await + .map_err(|err| custom_http_error(StatusCode::BAD_REQUEST, format!("{err}")))?; + Ok(HttpResponseOk(transaction_response)) + }) +} + +async fn handle_move_call( + call_params: CallRequest, + state: &mut ServerState, +) -> Result { + let module = Identifier::from_str(&call_params.module.to_owned())?; + let function = Identifier::from_str(&call_params.function.to_owned())?; + let args = call_params.args; + let type_args = call_params + .type_args + .unwrap_or_default() + .iter() + .map(|type_arg| parse_type_tag(type_arg)) + .collect::, _>>()?; + let gas_budget = call_params.gas_budget; + let gas_object_id = ObjectID::try_from(call_params.gas_object_id)?; + let package_object_id = ObjectID::from_hex_literal(&call_params.package_object_id)?; + + let sender: SuiAddress = decode_bytes_hex(call_params.sender.as_str())?; + + let (package_object_ref, package_object, _) = get_object_info(state, package_object_id).await?; + + // Extract the input args + let (object_ids, pure_args) = + resolve_move_function_args(&package_object, module.clone(), function.clone(), args)?; + + info!("Resolved fn to: \n {:?} & {:?}", object_ids, pure_args); + + // Fetch all the objects needed for this call + let mut input_objs = vec![]; + for obj_id in object_ids.clone() { + let (_, object, _) = get_object_info(state, obj_id).await?; + input_objs.push(object); + } + + // Pass in the objects for a deeper check + resolve_and_type_check( + package_object.clone(), + &module, + &function, + &type_args, + input_objs, + pure_args.clone(), + )?; + + // Fetch the object info for the gas obj + let (gas_obj_ref, _, _) = get_object_info(state, gas_object_id).await?; + + // Fetch the objects for the object args + let mut object_args_refs = Vec::new(); + for obj_id in object_ids { + let (object_ref, _, _) = get_object_info(state, obj_id).await?; + object_args_refs.push(object_ref); + } + + let tx_signer = Box::pin(SimpleTransactionSigner { + keystore: state.keystore.clone(), + }); + + let (cert, effects, gas_used) = match state + .gateway + .move_call( + sender, + package_object_ref, + module.to_owned(), + function.to_owned(), + type_args.clone(), + gas_obj_ref, + object_args_refs, + // TODO: Populate shared object args. sui/issue#719 + vec![], + pure_args, + gas_budget, + tx_signer, + ) + .await + { + Ok((cert, effects)) => { + let gas_used = match effects.status { + ExecutionStatus::Success { gas_used } => gas_used, + ExecutionStatus::Failure { gas_used, error } => { + let context = format!("Error calling move function, gas used {gas_used}"); + return Err(anyhow::Error::new(error).context(context)); + } + }; + (cert, effects, gas_used) + } + Err(err) => { + return Err(err); + } + }; + + let object_effects_summary = get_object_effects(state, effects).await?; + + Ok(TransactionResponse { + gas_used, + object_effects_summary: json!(object_effects_summary), + certificate: json!(cert), + }) +} + +pub fn sui_start(state: &mut ServerState) -> AsyncResult, HttpError> { + Box::pin(async { + if !state.authority_handles.is_empty() { + return Err(custom_http_error( + StatusCode::FORBIDDEN, + String::from("Sui network is already running."), + )); + } + + let committee = Committee::new( + state + .config + .authorities + .iter() + .map(|info| (*info.key_pair.public_key_bytes(), info.stake)) + .collect(), + ); + let mut handles = FuturesUnordered::new(); + + for authority in &state.config.authorities { + let server = sui_commands::make_server( + authority, + &committee, + vec![], + &[], + state.config.buffer_size, + ) + .await + .map_err(|error| { + custom_http_error( + StatusCode::CONFLICT, + format!("Unable to make server: {error}"), + ) + })?; + handles.push(async move { + match server.spawn().await { + Ok(server) => Ok(server), + Err(err) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Failed to start server: {}", err), + )); + } + } + }) + } + + let num_authorities = handles.len(); + info!("Started {} authorities", num_authorities); + + while let Some(spawned_server) = handles.next().await { + state.authority_handles.push(task::spawn(async { + if let Err(err) = spawned_server.unwrap().join().await { + error!("Server ended with an error: {}", err); + } + })); + } + + for address in state.addresses.clone() { + state + .gateway + .sync_account_state(address) + .await + .map_err(|err| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Sync error: {:?}", err), + ) + })?; + } + Ok(HttpResponseOk(format!( + "Started {} authorities", + num_authorities + ))) + }) +} diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index 32be1755731a6..e800f58982ac8 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -20,6 +20,7 @@ use move_core_types::identifier::Identifier; use move_core_types::parser::parse_type_tag; use move_core_types::value::MoveStructLayout; use schemars::JsonSchema; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::task::{self, JoinHandle}; @@ -31,6 +32,7 @@ use sui::keystore::Keystore; use sui::sui_commands; use sui::sui_json::{resolve_move_function_args, SuiJsonValue}; use sui::wallet_commands::SimpleTransactionSigner; +use sui_core::authority_aggregator::AsyncResult; use sui_core::gateway_state::GatewayClient; use sui_types::base_types::*; use sui_types::committee::Committee; @@ -40,6 +42,8 @@ use sui_types::move_package::resolve_and_type_check; use sui_types::object::Object as SuiObject; use sui_types::object::ObjectRead; +mod internal; + const REST_SERVER_PORT: u16 = 5000; const REST_SERVER_ADDR_IPV4: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); @@ -102,7 +106,7 @@ struct ServerContext { server_state: Arc>>, } -struct ServerState { +pub struct ServerState { config: NetworkConfig, gateway: GatewayClient, keystore: Arc>>, @@ -222,6 +226,7 @@ async fn genesis( rqctx: Arc>, ) -> Result, HttpError> { let context = rqctx.context(); + // Using a new working dir for genesis, this directory will be deleted when stop end point is called. let working_dir = PathBuf::from(".").join(format!("{}", ObjectID::random())); if context.server_state.lock().unwrap().is_some() { @@ -247,9 +252,6 @@ async fn genesis( })?; let authorities = network_config.get_authority_infos(); - // Need to use a random id because rocksdb locks on current process which - // means even if the directory is deleted the lock will remain causing an - // IO Error when a restart is attempted. let gateway = GatewayType::Embedded(EmbeddedGatewayConfig { authorities, db_folder_path: working_dir.join("client_db"), @@ -290,78 +292,7 @@ network has been started on testnet or main-net. async fn sui_start( rqctx: Arc>, ) -> Result, HttpError> { - let context = rqctx.context(); - - let mut state = context.take_server_state()?; - if !state.authority_handles.is_empty() { - context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FORBIDDEN, - String::from("Sui network is already running."), - )); - } - - let committee = Committee::new( - state - .config - .authorities - .iter() - .map(|info| (*info.key_pair.public_key_bytes(), info.stake)) - .collect(), - ); - let mut handles = FuturesUnordered::new(); - - for authority in &state.config.authorities { - let server = - sui_commands::make_server(authority, &committee, vec![], &[], state.config.buffer_size) - .await - .map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to make server: {error}"), - ) - })?; - handles.push(async move { - match server.spawn().await { - Ok(server) => Ok(server), - Err(err) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Failed to start server: {}", err), - )); - } - } - }) - } - - let num_authorities = handles.len(); - info!("Started {} authorities", num_authorities); - - while let Some(spawned_server) = handles.next().await { - state.authority_handles.push(task::spawn(async { - if let Err(err) = spawned_server.unwrap().join().await { - error!("Server ended with an error: {}", err); - } - })); - } - - for address in state.addresses.clone() { - state - .gateway - .sync_account_state(address) - .await - .map_err(|err| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Sync error: {:?}", err), - ) - })?; - } - context.set_server_state(state); - Ok(HttpResponseOk(format!( - "Started {} authorities", - num_authorities - ))) + with_state_no_param(rqctx, internal::sui_start).await } /** @@ -379,11 +310,13 @@ async fn sui_stop( rqctx: Arc>, ) -> Result { let server_context = rqctx.context(); + // Taking state object without returning ownership let state = server_context.take_server_state()?; for authority_handle in state.authority_handles { authority_handle.abort(); } + // Delete everything from working dir fs::remove_dir_all(state.working_dir).ok(); Ok(HttpResponseUpdatedNoContent()) } @@ -393,7 +326,7 @@ Response containing the managed addresses for this client. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct GetAddressResponse { +pub struct GetAddressResponse { /** Vector of hex codes as strings representing the managed addresses */ addresses: Vec, } @@ -409,27 +342,7 @@ Retrieve all managed addresses for this client. async fn get_addresses( rqctx: Arc>, ) -> Result, HttpError> { - let server_context = rqctx.context(); - let mut state = server_context.take_server_state()?; - // TODO: Speed up sync operations by kicking them off concurrently. - // Also need to investigate if this should be an automatic sync or manually triggered. - let addresses = state.addresses.clone(); - for address in &addresses { - if let Err(err) = state.gateway.sync_account_state(*address).await { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Can't create client state: {err}"), - )); - } - } - server_context.set_server_state(state); - Ok(HttpResponseOk(GetAddressResponse { - addresses: addresses - .into_iter() - .map(|address| format!("{}", address)) - .collect(), - })) + with_state_no_param(rqctx, internal::get_addresses).await } /** @@ -437,7 +350,7 @@ Request containing the address of which objecst are to be retrieved. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct GetObjectsRequest { +pub struct GetObjectsRequest { /** Required; Hex code as string representing the address */ address: String, } @@ -461,7 +374,7 @@ Returns the list of objects owned by an address. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct GetObjectsResponse { +pub struct GetObjectsResponse { objects: Vec, } @@ -477,33 +390,7 @@ async fn get_objects( rqctx: Arc>, query: Query, ) -> Result, HttpError> { - let server_context = rqctx.context(); - - let get_objects_params = query.into_inner(); - let address = get_objects_params.address; - - let mut state = server_context.take_server_state()?; - - let address = &decode_bytes_hex(address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode address from hex {error}"), - ) - })?; - - let object_refs = state.gateway.get_owned_objects(*address); - - server_context.set_server_state(state); - Ok(HttpResponseOk(GetObjectsResponse { - objects: object_refs - .iter() - .map(|(object_id, sequence_number, object_digest)| Object { - object_id: object_id.to_string(), - version: format!("{:?}", sequence_number), - object_digest: format!("{:?}", object_digest), - }) - .collect::>(), - })) + with_state(rqctx, query.into_inner(), internal::get_objects).await } /** @@ -514,7 +401,7 @@ otherwise we look for it in the shared object store. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct GetObjectSchemaRequest { +pub struct GetObjectSchemaRequest { /** Required; Hex code as string representing the object id */ object_id: String, } @@ -525,7 +412,7 @@ is returned. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct ObjectSchemaResponse { +pub struct ObjectSchemaResponse { /** JSON representation of the object schema */ schema: serde_json::Value, } @@ -542,54 +429,7 @@ async fn object_schema( rqctx: Arc>, query: Query, ) -> Result, HttpError> { - let server_context = rqctx.context(); - let object_info_params = query.into_inner(); - - let state = server_context.take_server_state()?; - - let object_id = match ObjectID::try_from(object_info_params.object_id) { - Ok(object_id) => object_id, - Err(error) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("{error}"), - )); - } - }; - - let layout = match state.gateway.get_object_info(object_id).await { - Ok(ObjectRead::Exists(_, _, layout)) => layout, - Ok(ObjectRead::Deleted(_)) => { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Object ({object_id}) was deleted."), - )); - } - Ok(ObjectRead::NotExists(_)) => { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Object ({object_id}) does not exist."), - )); - } - Err(error) => { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Error while getting object info: {:?}", error), - )); - } - }; - - server_context.set_server_state(state); - match serde_json::to_value(layout) { - Ok(schema) => Ok(HttpResponseOk(ObjectSchemaResponse { schema })), - Err(e) => Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Error while getting object info: {:?}", e), - )), - } + with_state(rqctx, query.into_inner(), internal::object_schema).await } /** @@ -600,7 +440,7 @@ otherwise we look for it in the shared object store. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct GetObjectInfoRequest { +pub struct GetObjectInfoRequest { /** Required; Hex code as string representing the object id */ object_id: String, } @@ -611,7 +451,7 @@ is returned. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct ObjectInfoResponse { +pub struct ObjectInfoResponse { /** Hex code as string representing the owner's address */ owner: String, /** Sequence number of the object */ @@ -638,38 +478,7 @@ async fn object_info( rqctx: Arc>, query: Query, ) -> Result, HttpError> { - let server_context = rqctx.context(); - let object_info_params = query.into_inner(); - - let state = server_context.take_server_state()?; - - let object_id = match ObjectID::try_from(object_info_params.object_id) { - Ok(object_id) => object_id, - Err(error) => { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("{error}"), - )); - } - }; - - let (_, object, layout) = get_object_info(&state, object_id).await?; - - let object_data = object.to_json(&layout).unwrap_or_else(|_| json!("")); - - server_context.set_server_state(state); - Ok(HttpResponseOk(ObjectInfoResponse { - owner: format!("{:?}", object.owner), - version: format!("{:?}", object.version().value()), - id: format!("{:?}", object.id()), - readonly: format!("{:?}", object.is_read_only()), - obj_type: object - .data - .type_() - .map_or("Unknown Type".to_owned(), |type_| format!("{}", type_)), - data: object_data, - })) + with_state(rqctx, query.into_inner(), internal::object_info).await } /** @@ -677,7 +486,7 @@ Request containing the information needed to execute a transfer transaction. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct TransferTransactionRequest { +pub struct TransferTransactionRequest { /** Required; Hex code as string representing the address to be sent from */ from_address: String, /** Required; Hex code as string representing the object id */ @@ -694,7 +503,7 @@ associated with the transaction that verifies the transaction. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct TransactionResponse { +pub struct TransactionResponse { /** Integer representing the actual cost of the transaction */ gas_used: u64, /** JSON representation of the list of resulting effects on the object */ @@ -728,72 +537,7 @@ async fn transfer_object( rqctx: Arc>, request: TypedBody, ) -> Result, HttpError> { - let server_context = rqctx.context(); - let mut state = server_context.take_server_state()?; - - let transfer_order_params = request.into_inner(); - let to_address = - decode_bytes_hex(transfer_order_params.to_address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode to address from hex {error}"), - ) - })?; - let object_id = ObjectID::try_from(transfer_order_params.object_id) - .map_err(|error| custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")))?; - let gas_object_id = ObjectID::try_from(transfer_order_params.gas_object_id) - .map_err(|error| custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")))?; - let owner = decode_bytes_hex(transfer_order_params.from_address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode address from hex {error}"), - ) - })?; - - let tx_signer = Box::pin(SimpleTransactionSigner { - keystore: state.keystore.clone(), - }); - - let (cert, effects, gas_used) = match state - .gateway - .transfer_coin(owner, object_id, gas_object_id, to_address, tx_signer) - .await - { - Ok((cert, effects)) => { - let gas_used = match effects.status { - // TODO: handle the actual return value stored in - // ExecutionStatus::Success - ExecutionStatus::Success { gas_used, .. } => gas_used, - ExecutionStatus::Failure { gas_used, error } => { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!( - "Error transferring object: {:#?}, gas used {}", - error, gas_used - ), - )); - } - }; - (cert, effects, gas_used) - } - Err(err) => { - server_context.set_server_state(state); - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Transfer error: {err}"), - )); - } - }; - - let object_effects_summary = get_object_effects(&state, effects).await?; - - server_context.set_server_state(state); - Ok(HttpResponseOk(TransactionResponse { - gas_used, - object_effects_summary: json!(object_effects_summary), - certificate: json!(cert), - })) + with_state(rqctx, request.into_inner(), internal::transfer_object).await } /** @@ -845,7 +589,7 @@ Request containing the information required to execute a move module. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct CallRequest { +pub struct CallRequest { /** Required; Hex code as string representing the sender's address */ sender: String, /** Required; Hex code as string representing Move module location */ @@ -892,17 +636,7 @@ async fn call( rqctx: Arc>, request: TypedBody, ) -> Result, HttpError> { - let server_context = rqctx.context(); - let call_params = request.into_inner(); - - let mut state = server_context.take_server_state()?; - - let transaction_response = handle_move_call(call_params, &mut state) - .await - .map_err(|err| custom_http_error(StatusCode::BAD_REQUEST, format!("{err}")))?; - - server_context.set_server_state(state); - Ok(HttpResponseOk(transaction_response)) + with_state(rqctx, request.into_inner(), internal::call).await } /** @@ -911,7 +645,7 @@ Request containing the address that requires a sync. // TODO: This call may not be required. Sync should not need to be triggered by user. #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -struct SyncRequest { +pub struct SyncRequest { /** Required; Hex code as string representing the address */ address: String, } @@ -926,34 +660,39 @@ on all objects owned by each address that is managed by this client state. tags = [ "wallet" ], }] async fn sync( - rqctx: Arc>, + ctx: Arc>, request: TypedBody, ) -> Result { - let server_context = rqctx.context(); - let sync_params = request.into_inner(); + with_state(ctx, request.into_inner(), internal::sync).await +} +async fn with_state( + rqctx: Arc>, + params: S, + func: F, +) -> Result +where + F: Fn(&mut ServerState, S) -> AsyncResult, +{ + let server_context = rqctx.context(); let mut state = server_context.take_server_state()?; + let result = func(&mut state, params).await; + server_context.set_server_state(state); + result +} - let address = decode_bytes_hex(sync_params.address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode to address from hex {error}"), - ) - })?; - - state - .gateway - .sync_account_state(address) - .await - .map_err(|err| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Can't create client state: {err}"), - ) - })?; - +async fn with_state_no_param( + ctx: Arc>, + func: F, +) -> Result +where + F: Fn(&mut ServerState) -> AsyncResult, +{ + let server_context = ctx.context(); + let mut state = server_context.take_server_state()?; + let result = func(&mut state).await; server_context.set_server_state(state); - Ok(HttpResponseUpdatedNoContent()) + result } async fn get_object_effects( @@ -1091,108 +830,6 @@ async fn get_object_info( Ok((object_ref, object, layout)) } -async fn handle_move_call( - call_params: CallRequest, - state: &mut ServerState, -) -> Result { - let module = Identifier::from_str(&call_params.module.to_owned())?; - let function = Identifier::from_str(&call_params.function.to_owned())?; - let args = call_params.args; - let type_args = call_params - .type_args - .unwrap_or_default() - .iter() - .map(|type_arg| parse_type_tag(type_arg)) - .collect::, _>>()?; - let gas_budget = call_params.gas_budget; - let gas_object_id = ObjectID::try_from(call_params.gas_object_id)?; - let package_object_id = ObjectID::from_hex_literal(&call_params.package_object_id)?; - - let sender: SuiAddress = decode_bytes_hex(call_params.sender.as_str())?; - - let (package_object_ref, package_object, _) = get_object_info(state, package_object_id).await?; - - // Extract the input args - let (object_ids, pure_args) = - resolve_move_function_args(&package_object, module.clone(), function.clone(), args)?; - - info!("Resolved fn to: \n {:?} & {:?}", object_ids, pure_args); - - // Fetch all the objects needed for this call - let mut input_objs = vec![]; - for obj_id in object_ids.clone() { - let (_, object, _) = get_object_info(state, obj_id).await?; - input_objs.push(object); - } - - // Pass in the objects for a deeper check - resolve_and_type_check( - package_object.clone(), - &module, - &function, - &type_args, - input_objs, - pure_args.clone(), - )?; - - // Fetch the object info for the gas obj - let (gas_obj_ref, _, _) = get_object_info(state, gas_object_id).await?; - - // Fetch the objects for the object args - let mut object_args_refs = Vec::new(); - for obj_id in object_ids { - let (object_ref, _, _) = get_object_info(state, obj_id).await?; - object_args_refs.push(object_ref); - } - - let tx_signer = Box::pin(SimpleTransactionSigner { - keystore: state.keystore.clone(), - }); - - let (cert, effects, gas_used) = match state - .gateway - .move_call( - sender, - package_object_ref, - module.to_owned(), - function.to_owned(), - type_args.clone(), - gas_obj_ref, - object_args_refs, - // TODO: Populate shared object args. sui/issue#719 - vec![], - pure_args, - gas_budget, - tx_signer, - ) - .await - { - Ok((cert, effects)) => { - let gas_used = match effects.status { - // TODO: handle the actual return value stored in - // ExecutionStatus::Success - ExecutionStatus::Success { gas_used, .. } => gas_used, - ExecutionStatus::Failure { gas_used, error } => { - let context = format!("Error calling move function, gas used {gas_used}"); - return Err(anyhow::Error::new(error).context(context)); - } - }; - (cert, effects, gas_used) - } - Err(err) => { - return Err(err); - } - }; - - let object_effects_summary = get_object_effects(state, effects).await?; - - Ok(TransactionResponse { - gas_used, - object_effects_summary: json!(object_effects_summary), - certificate: json!(cert), - }) -} - fn custom_http_error(status_code: http::StatusCode, message: String) -> HttpError { HttpError::for_client_error(None, status_code, message) } From e028ef0ff3768e26749033cc4c41ffde181bd86c Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 15 Mar 2022 15:51:44 +0000 Subject: [PATCH 7/9] remove take and remove hacky with_state functions added concurrent request test for rest server --- sui/src/internal.rs | 416 ------------------- sui/src/rest_server.rs | 525 +++++++++++++++++++----- sui/src/unit_tests/rest_server_tests.rs | 81 ++++ 3 files changed, 514 insertions(+), 508 deletions(-) delete mode 100644 sui/src/internal.rs create mode 100644 sui/src/unit_tests/rest_server_tests.rs diff --git a/sui/src/internal.rs b/sui/src/internal.rs deleted file mode 100644 index 277f112d14e3d..0000000000000 --- a/sui/src/internal.rs +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 -use super::*; - -pub fn sync( - state: &mut ServerState, - sync_params: SyncRequest, -) -> AsyncResult { - Box::pin(async move { - let address = decode_bytes_hex(sync_params.address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode to address from hex {error}"), - ) - })?; - - state - .gateway - .sync_account_state(address) - .await - .map_err(|err| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Can't create client state: {err}"), - ) - })?; - Ok(HttpResponseUpdatedNoContent()) - }) -} - -pub fn get_addresses( - state: &mut ServerState, -) -> AsyncResult, HttpError> { - Box::pin(async { - // TODO: Speed up sync operations by kicking them off concurrently. - // Also need to investigate if this should be an automatic sync or manually triggered. - let addresses = state.addresses.clone(); - for address in &addresses { - if let Err(err) = state.gateway.sync_account_state(*address).await { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Can't create client state: {err}"), - )); - } - } - Ok(HttpResponseOk(GetAddressResponse { - addresses: addresses - .into_iter() - .map(|address| format!("{}", address)) - .collect(), - })) - }) -} - -pub fn get_objects( - state: &mut ServerState, - get_objects_params: GetObjectsRequest, -) -> AsyncResult, HttpError> { - Box::pin(async { - let address = get_objects_params.address; - let address = &decode_bytes_hex(address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode address from hex {error}"), - ) - })?; - - let object_refs = state.gateway.get_owned_objects(*address); - Ok(HttpResponseOk(GetObjectsResponse { - objects: object_refs - .iter() - .map(|(object_id, sequence_number, object_digest)| Object { - object_id: object_id.to_string(), - version: format!("{:?}", sequence_number), - object_digest: format!("{:?}", object_digest), - }) - .collect::>(), - })) - }) -} - -pub fn object_schema( - state: &mut ServerState, - object_info_params: GetObjectSchemaRequest, -) -> AsyncResult, HttpError> { - Box::pin(async { - let object_id = match ObjectID::try_from(object_info_params.object_id) { - Ok(object_id) => object_id, - Err(error) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("{error}"), - )); - } - }; - - let layout = match state.gateway.get_object_info(object_id).await { - Ok(ObjectRead::Exists(_, _, layout)) => layout, - Ok(ObjectRead::Deleted(_)) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Object ({object_id}) was deleted."), - )); - } - Ok(ObjectRead::NotExists(_)) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Object ({object_id}) does not exist."), - )); - } - Err(error) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Error while getting object info: {:?}", error), - )); - } - }; - - match serde_json::to_value(layout) { - Ok(schema) => Ok(HttpResponseOk(ObjectSchemaResponse { schema })), - Err(e) => Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Error while getting object info: {:?}", e), - )), - } - }) -} - -pub fn object_info( - state: &mut ServerState, - object_info_params: GetObjectInfoRequest, -) -> AsyncResult, HttpError> { - Box::pin(async move { - let object_id = match ObjectID::try_from(object_info_params.object_id) { - Ok(object_id) => object_id, - Err(error) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("{error}"), - )); - } - }; - - let (_, object, layout) = get_object_info(state, object_id).await?; - let object_data = object.to_json(&layout).unwrap_or_else(|_| json!("")); - Ok(HttpResponseOk(ObjectInfoResponse { - owner: format!("{:?}", object.owner), - version: format!("{:?}", object.version().value()), - id: format!("{:?}", object.id()), - readonly: format!("{:?}", object.is_read_only()), - obj_type: object - .data - .type_() - .map_or("Unknown Type".to_owned(), |type_| format!("{}", type_)), - data: object_data, - })) - }) -} - -pub fn transfer_object( - state: &mut ServerState, - transfer_order_params: TransferTransactionRequest, -) -> AsyncResult, HttpError> { - Box::pin(async move { - let to_address = - decode_bytes_hex(transfer_order_params.to_address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode to address from hex {error}"), - ) - })?; - let object_id = ObjectID::try_from(transfer_order_params.object_id).map_err(|error| { - custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")) - })?; - let gas_object_id = - ObjectID::try_from(transfer_order_params.gas_object_id).map_err(|error| { - custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")) - })?; - let owner = - decode_bytes_hex(transfer_order_params.from_address.as_str()).map_err(|error| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Could not decode address from hex {error}"), - ) - })?; - - let tx_signer = Box::pin(SimpleTransactionSigner { - keystore: state.keystore.clone(), - }); - - let (cert, effects, gas_used) = match state - .gateway - .transfer_coin(owner, object_id, gas_object_id, to_address, tx_signer) - .await - { - Ok((cert, effects)) => { - let gas_used = match effects.status { - ExecutionStatus::Success { gas_used } => gas_used, - ExecutionStatus::Failure { gas_used, error } => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!( - "Error transferring object: {:#?}, gas used {}", - error, gas_used - ), - )); - } - }; - (cert, effects, gas_used) - } - Err(err) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Transfer error: {err}"), - )); - } - }; - - let object_effects_summary = get_object_effects(state, effects).await?; - - Ok(HttpResponseOk(TransactionResponse { - gas_used, - object_effects_summary: json!(object_effects_summary), - certificate: json!(cert), - })) - }) -} - -pub fn call( - state: &mut ServerState, - call_params: CallRequest, -) -> AsyncResult, HttpError> { - Box::pin(async { - let transaction_response = handle_move_call(call_params, state) - .await - .map_err(|err| custom_http_error(StatusCode::BAD_REQUEST, format!("{err}")))?; - Ok(HttpResponseOk(transaction_response)) - }) -} - -async fn handle_move_call( - call_params: CallRequest, - state: &mut ServerState, -) -> Result { - let module = Identifier::from_str(&call_params.module.to_owned())?; - let function = Identifier::from_str(&call_params.function.to_owned())?; - let args = call_params.args; - let type_args = call_params - .type_args - .unwrap_or_default() - .iter() - .map(|type_arg| parse_type_tag(type_arg)) - .collect::, _>>()?; - let gas_budget = call_params.gas_budget; - let gas_object_id = ObjectID::try_from(call_params.gas_object_id)?; - let package_object_id = ObjectID::from_hex_literal(&call_params.package_object_id)?; - - let sender: SuiAddress = decode_bytes_hex(call_params.sender.as_str())?; - - let (package_object_ref, package_object, _) = get_object_info(state, package_object_id).await?; - - // Extract the input args - let (object_ids, pure_args) = - resolve_move_function_args(&package_object, module.clone(), function.clone(), args)?; - - info!("Resolved fn to: \n {:?} & {:?}", object_ids, pure_args); - - // Fetch all the objects needed for this call - let mut input_objs = vec![]; - for obj_id in object_ids.clone() { - let (_, object, _) = get_object_info(state, obj_id).await?; - input_objs.push(object); - } - - // Pass in the objects for a deeper check - resolve_and_type_check( - package_object.clone(), - &module, - &function, - &type_args, - input_objs, - pure_args.clone(), - )?; - - // Fetch the object info for the gas obj - let (gas_obj_ref, _, _) = get_object_info(state, gas_object_id).await?; - - // Fetch the objects for the object args - let mut object_args_refs = Vec::new(); - for obj_id in object_ids { - let (object_ref, _, _) = get_object_info(state, obj_id).await?; - object_args_refs.push(object_ref); - } - - let tx_signer = Box::pin(SimpleTransactionSigner { - keystore: state.keystore.clone(), - }); - - let (cert, effects, gas_used) = match state - .gateway - .move_call( - sender, - package_object_ref, - module.to_owned(), - function.to_owned(), - type_args.clone(), - gas_obj_ref, - object_args_refs, - // TODO: Populate shared object args. sui/issue#719 - vec![], - pure_args, - gas_budget, - tx_signer, - ) - .await - { - Ok((cert, effects)) => { - let gas_used = match effects.status { - ExecutionStatus::Success { gas_used } => gas_used, - ExecutionStatus::Failure { gas_used, error } => { - let context = format!("Error calling move function, gas used {gas_used}"); - return Err(anyhow::Error::new(error).context(context)); - } - }; - (cert, effects, gas_used) - } - Err(err) => { - return Err(err); - } - }; - - let object_effects_summary = get_object_effects(state, effects).await?; - - Ok(TransactionResponse { - gas_used, - object_effects_summary: json!(object_effects_summary), - certificate: json!(cert), - }) -} - -pub fn sui_start(state: &mut ServerState) -> AsyncResult, HttpError> { - Box::pin(async { - if !state.authority_handles.is_empty() { - return Err(custom_http_error( - StatusCode::FORBIDDEN, - String::from("Sui network is already running."), - )); - } - - let committee = Committee::new( - state - .config - .authorities - .iter() - .map(|info| (*info.key_pair.public_key_bytes(), info.stake)) - .collect(), - ); - let mut handles = FuturesUnordered::new(); - - for authority in &state.config.authorities { - let server = sui_commands::make_server( - authority, - &committee, - vec![], - &[], - state.config.buffer_size, - ) - .await - .map_err(|error| { - custom_http_error( - StatusCode::CONFLICT, - format!("Unable to make server: {error}"), - ) - })?; - handles.push(async move { - match server.spawn().await { - Ok(server) => Ok(server), - Err(err) => { - return Err(custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Failed to start server: {}", err), - )); - } - } - }) - } - - let num_authorities = handles.len(); - info!("Started {} authorities", num_authorities); - - while let Some(spawned_server) = handles.next().await { - state.authority_handles.push(task::spawn(async { - if let Err(err) = spawned_server.unwrap().join().await { - error!("Server ended with an error: {}", err); - } - })); - } - - for address in state.addresses.clone() { - state - .gateway - .sync_account_state(address) - .await - .map_err(|err| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - format!("Sync error: {:?}", err), - ) - })?; - } - Ok(HttpResponseOk(format!( - "Started {} authorities", - num_authorities - ))) - }) -} diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index e800f58982ac8..d9d44921df4c4 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -7,20 +7,20 @@ use std::fs; use std::net::{Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, RwLock}; use dropshot::{endpoint, Query, TypedBody}; use dropshot::{ ApiDescription, ConfigDropshot, ConfigLogging, ConfigLoggingLevel, HttpError, HttpResponseOk, HttpResponseUpdatedNoContent, HttpServerStarter, RequestContext, }; +use futures::lock::Mutex; use futures::stream::{futures_unordered::FuturesUnordered, StreamExt as _}; use hyper::StatusCode; use move_core_types::identifier::Identifier; use move_core_types::parser::parse_type_tag; use move_core_types::value::MoveStructLayout; use schemars::JsonSchema; -use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::task::{self, JoinHandle}; @@ -32,7 +32,6 @@ use sui::keystore::Keystore; use sui::sui_commands; use sui::sui_json::{resolve_move_function_args, SuiJsonValue}; use sui::wallet_commands::SimpleTransactionSigner; -use sui_core::authority_aggregator::AsyncResult; use sui_core::gateway_state::GatewayClient; use sui_types::base_types::*; use sui_types::committee::Committee; @@ -42,11 +41,13 @@ use sui_types::move_package::resolve_and_type_check; use sui_types::object::Object as SuiObject; use sui_types::object::ObjectRead; -mod internal; - const REST_SERVER_PORT: u16 = 5000; const REST_SERVER_ADDR_IPV4: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); +#[path = "unit_tests/rest_server_tests.rs"] +#[cfg(test)] +mod rest_server_tests; + #[tokio::main] async fn main() -> Result<(), String> { let config_dropshot: ConfigDropshot = ConfigDropshot { @@ -63,6 +64,23 @@ async fn main() -> Result<(), String> { tracing_subscriber::fmt::init(); + let api = create_api(); + + let documentation = api + .openapi("Sui API", "0.1") + .json() + .map_err(|e| e.to_string())?; + + let api_context = ServerContext::new(documentation); + + let server = HttpServerStarter::new(&config_dropshot, api, api_context, &log) + .map_err(|error| format!("failed to create server: {}", error))? + .start(); + + server.await +} + +fn create_api() -> ApiDescription { let mut api = ApiDescription::new(); // [DOCS] @@ -83,18 +101,7 @@ async fn main() -> Result<(), String> { api.register(call).unwrap(); api.register(sync).unwrap(); - let documentation = api - .openapi("Sui API", "0.1") - .json() - .map_err(|e| e.to_string())?; - - let api_context = ServerContext::new(documentation); - - let server = HttpServerStarter::new(&config_dropshot, api, api_context, &log) - .map_err(|error| format!("failed to create server: {}", error))? - .start(); - - server.await + api } /** @@ -129,24 +136,14 @@ impl ServerContext { server_state: Arc::new(Mutex::new(None)), } } - // TODO: Can this work without take? - // Take is required here because dropshot's `HttpHandlerFunc` is Send + Sync + 'static - // and we cannot use &mut reference on the server_state object. - fn take_server_state(&self) -> Result { - let mut state = self.server_state.lock().unwrap(); - state.take().ok_or_else(|| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - "Server state does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." - .to_string(), - ) - }) - } - // This is to return ownership of ServerState after take() - // TODO: Anyway to make this automatic? - fn set_server_state(&self, state: ServerState) { - *self.server_state.lock().unwrap() = Some(state); - } +} + +fn server_state_error() -> HttpError { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + "Server state does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." + .to_string(), + ) } /** @@ -229,7 +226,7 @@ async fn genesis( // Using a new working dir for genesis, this directory will be deleted when stop end point is called. let working_dir = PathBuf::from(".").join(format!("{}", ObjectID::random())); - if context.server_state.lock().unwrap().is_some() { + if context.server_state.lock().await.is_some() { return Err(custom_http_error( StatusCode::CONFLICT, String::from("Cannot run genesis on a existing network, please make a POST request to the `sui/stop` endpoint to reset."), @@ -270,7 +267,8 @@ async fn genesis( working_dir: working_dir.to_path_buf(), authority_handles: vec![], }; - context.set_server_state(state); + + *context.server_state.lock().await = Some(state); Ok(HttpResponseOk(GenesisResponse { addresses: addresses_json, @@ -290,9 +288,78 @@ network has been started on testnet or main-net. tags = [ "debug" ], }] async fn sui_start( - rqctx: Arc>, + ctx: Arc>, ) -> Result, HttpError> { - with_state_no_param(rqctx, internal::sui_start).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + + if !state.authority_handles.is_empty() { + return Err(custom_http_error( + StatusCode::FORBIDDEN, + String::from("Sui network is already running."), + )); + } + + let committee = Committee::new( + state + .config + .authorities + .iter() + .map(|info| (*info.key_pair.public_key_bytes(), info.stake)) + .collect(), + ); + let mut handles = FuturesUnordered::new(); + + for authority in &state.config.authorities { + let server = + sui_commands::make_server(authority, &committee, vec![], &[], state.config.buffer_size) + .await + .map_err(|error| { + custom_http_error( + StatusCode::CONFLICT, + format!("Unable to make server: {error}"), + ) + })?; + handles.push(async move { + match server.spawn().await { + Ok(server) => Ok(server), + Err(err) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Failed to start server: {}", err), + )); + } + } + }) + } + + let num_authorities = handles.len(); + info!("Started {} authorities", num_authorities); + + while let Some(spawned_server) = handles.next().await { + state.authority_handles.push(task::spawn(async { + if let Err(err) = spawned_server.unwrap().join().await { + error!("Server ended with an error: {}", err); + } + })); + } + + for address in state.addresses.clone() { + state + .gateway + .sync_account_state(address) + .await + .map_err(|err| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Sync error: {:?}", err), + ) + })?; + } + Ok(HttpResponseOk(format!( + "Started {} authorities", + num_authorities + ))) } /** @@ -311,13 +378,20 @@ async fn sui_stop( ) -> Result { let server_context = rqctx.context(); // Taking state object without returning ownership - let state = server_context.take_server_state()?; + let mut state = server_context.server_state.lock().await; + let state = state.as_mut().ok_or_else(|| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + "Server state does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." + .to_string(), + ) + })?; - for authority_handle in state.authority_handles { + for authority_handle in &state.authority_handles { authority_handle.abort(); } // Delete everything from working dir - fs::remove_dir_all(state.working_dir).ok(); + fs::remove_dir_all(&state.working_dir).ok(); Ok(HttpResponseUpdatedNoContent()) } @@ -340,9 +414,28 @@ Retrieve all managed addresses for this client. tags = [ "wallet" ], }] async fn get_addresses( - rqctx: Arc>, + ctx: Arc>, ) -> Result, HttpError> { - with_state_no_param(rqctx, internal::get_addresses).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + + // TODO: Speed up sync operations by kicking them off concurrently. + // Also need to investigate if this should be an automatic sync or manually triggered. + let addresses = state.addresses.clone(); + for address in &addresses { + if let Err(err) = state.gateway.sync_account_state(*address).await { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Can't create client state: {err}"), + )); + } + } + Ok(HttpResponseOk(GetAddressResponse { + addresses: addresses + .into_iter() + .map(|address| format!("{}", address)) + .collect(), + })) } /** @@ -350,7 +443,7 @@ Request containing the address of which objecst are to be retrieved. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct GetObjectsRequest { +struct GetObjectsRequest { /** Required; Hex code as string representing the address */ address: String, } @@ -374,7 +467,7 @@ Returns the list of objects owned by an address. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct GetObjectsResponse { +struct GetObjectsResponse { objects: Vec, } @@ -387,10 +480,31 @@ Returns list of objects owned by an address. tags = [ "wallet" ], }] async fn get_objects( - rqctx: Arc>, + ctx: Arc>, query: Query, ) -> Result, HttpError> { - with_state(rqctx, query.into_inner(), internal::get_objects).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + let get_objects_params = query.into_inner(); + let address = get_objects_params.address; + let address = &decode_bytes_hex(address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode address from hex {error}"), + ) + })?; + + let object_refs = state.gateway.get_owned_objects(*address); + Ok(HttpResponseOk(GetObjectsResponse { + objects: object_refs + .iter() + .map(|(object_id, sequence_number, object_digest)| Object { + object_id: object_id.to_string(), + version: format!("{:?}", sequence_number), + object_digest: format!("{:?}", object_digest), + }) + .collect::>(), + })) } /** @@ -401,7 +515,7 @@ otherwise we look for it in the shared object store. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct GetObjectSchemaRequest { +struct GetObjectSchemaRequest { /** Required; Hex code as string representing the object id */ object_id: String, } @@ -412,7 +526,7 @@ is returned. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct ObjectSchemaResponse { +struct ObjectSchemaResponse { /** JSON representation of the object schema */ schema: serde_json::Value, } @@ -426,10 +540,52 @@ Returns the schema for a specified object. tags = [ "wallet" ], }] async fn object_schema( - rqctx: Arc>, + ctx: Arc>, query: Query, ) -> Result, HttpError> { - with_state(rqctx, query.into_inner(), internal::object_schema).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + let object_info_params = query.into_inner(); + + let object_id = match ObjectID::try_from(object_info_params.object_id) { + Ok(object_id) => object_id, + Err(error) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("{error}"), + )); + } + }; + + let layout = match state.gateway.get_object_info(object_id).await { + Ok(ObjectRead::Exists(_, _, layout)) => layout, + Ok(ObjectRead::Deleted(_)) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Object ({object_id}) was deleted."), + )); + } + Ok(ObjectRead::NotExists(_)) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Object ({object_id}) does not exist."), + )); + } + Err(error) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Error while getting object info: {:?}", error), + )); + } + }; + + match serde_json::to_value(layout) { + Ok(schema) => Ok(HttpResponseOk(ObjectSchemaResponse { schema })), + Err(e) => Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Error while getting object info: {:?}", e), + )), + } } /** @@ -440,7 +596,7 @@ otherwise we look for it in the shared object store. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct GetObjectInfoRequest { +struct GetObjectInfoRequest { /** Required; Hex code as string representing the object id */ object_id: String, } @@ -451,7 +607,7 @@ is returned. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct ObjectInfoResponse { +struct ObjectInfoResponse { /** Hex code as string representing the owner's address */ owner: String, /** Sequence number of the object */ @@ -475,10 +631,36 @@ Returns the object information for a specified object. tags = [ "wallet" ], }] async fn object_info( - rqctx: Arc>, + ctx: Arc>, query: Query, ) -> Result, HttpError> { - with_state(rqctx, query.into_inner(), internal::object_info).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + + let object_info_params = query.into_inner(); + let object_id = match ObjectID::try_from(object_info_params.object_id) { + Ok(object_id) => object_id, + Err(error) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("{error}"), + )); + } + }; + + let (_, object, layout) = get_object_info(state, object_id).await?; + let object_data = object.to_json(&layout).unwrap_or_else(|_| json!("")); + Ok(HttpResponseOk(ObjectInfoResponse { + owner: format!("{:?}", object.owner), + version: format!("{:?}", object.version().value()), + id: format!("{:?}", object.id()), + readonly: format!("{:?}", object.is_read_only()), + obj_type: object + .data + .type_() + .map_or("Unknown Type".to_owned(), |type_| format!("{}", type_)), + data: object_data, + })) } /** @@ -486,7 +668,7 @@ Request containing the information needed to execute a transfer transaction. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct TransferTransactionRequest { +struct TransferTransactionRequest { /** Required; Hex code as string representing the address to be sent from */ from_address: String, /** Required; Hex code as string representing the object id */ @@ -503,7 +685,7 @@ associated with the transaction that verifies the transaction. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct TransactionResponse { +struct TransactionResponse { /** Integer representing the actual cost of the transaction */ gas_used: u64, /** JSON representation of the list of resulting effects on the object */ @@ -534,10 +716,70 @@ Example TransferTransactionRequest tags = [ "wallet" ], }] async fn transfer_object( - rqctx: Arc>, + ctx: Arc>, request: TypedBody, ) -> Result, HttpError> { - with_state(rqctx, request.into_inner(), internal::transfer_object).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + let transfer_order_params = request.into_inner(); + + let to_address = + decode_bytes_hex(transfer_order_params.to_address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode to address from hex {error}"), + ) + })?; + let object_id = ObjectID::try_from(transfer_order_params.object_id) + .map_err(|error| custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")))?; + let gas_object_id = ObjectID::try_from(transfer_order_params.gas_object_id) + .map_err(|error| custom_http_error(StatusCode::FAILED_DEPENDENCY, format!("{error}")))?; + let owner = decode_bytes_hex(transfer_order_params.from_address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode address from hex {error}"), + ) + })?; + + let tx_signer = Box::pin(SimpleTransactionSigner { + keystore: state.keystore.clone(), + }); + + let (cert, effects, gas_used) = match state + .gateway + .transfer_coin(owner, object_id, gas_object_id, to_address, tx_signer) + .await + { + Ok((cert, effects)) => { + let gas_used = match effects.status { + ExecutionStatus::Success { gas_used } => gas_used, + ExecutionStatus::Failure { gas_used, error } => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!( + "Error transferring object: {:#?}, gas used {}", + error, gas_used + ), + )); + } + }; + (cert, effects, gas_used) + } + Err(err) => { + return Err(custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Transfer error: {err}"), + )); + } + }; + + let object_effects_summary = get_object_effects(state, effects).await?; + + Ok(HttpResponseOk(TransactionResponse { + gas_used, + object_effects_summary: json!(object_effects_summary), + certificate: json!(cert), + })) } /** @@ -589,7 +831,7 @@ Request containing the information required to execute a move module. */ #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct CallRequest { +struct CallRequest { /** Required; Hex code as string representing the sender's address */ sender: String, /** Required; Hex code as string representing Move module location */ @@ -633,10 +875,17 @@ Example CallRequest tags = [ "wallet" ], }] async fn call( - rqctx: Arc>, + ctx: Arc>, request: TypedBody, ) -> Result, HttpError> { - with_state(rqctx, request.into_inner(), internal::call).await + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; + + let call_params = request.into_inner(); + let transaction_response = handle_move_call(call_params, state) + .await + .map_err(|err| custom_http_error(StatusCode::BAD_REQUEST, format!("{err}")))?; + Ok(HttpResponseOk(transaction_response)) } /** @@ -645,7 +894,7 @@ Request containing the address that requires a sync. // TODO: This call may not be required. Sync should not need to be triggered by user. #[derive(Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct SyncRequest { +struct SyncRequest { /** Required; Hex code as string representing the address */ address: String, } @@ -663,36 +912,28 @@ async fn sync( ctx: Arc>, request: TypedBody, ) -> Result { - with_state(ctx, request.into_inner(), internal::sync).await -} + let sync_params = request.into_inner(); + let mut state = ctx.context().server_state.lock().await; + let state = state.as_mut().ok_or_else(server_state_error)?; -async fn with_state( - rqctx: Arc>, - params: S, - func: F, -) -> Result -where - F: Fn(&mut ServerState, S) -> AsyncResult, -{ - let server_context = rqctx.context(); - let mut state = server_context.take_server_state()?; - let result = func(&mut state, params).await; - server_context.set_server_state(state); - result -} + let address = decode_bytes_hex(sync_params.address.as_str()).map_err(|error| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Could not decode to address from hex {error}"), + ) + })?; -async fn with_state_no_param( - ctx: Arc>, - func: F, -) -> Result -where - F: Fn(&mut ServerState) -> AsyncResult, -{ - let server_context = ctx.context(); - let mut state = server_context.take_server_state()?; - let result = func(&mut state).await; - server_context.set_server_state(state); - result + state + .gateway + .sync_account_state(address) + .await + .map_err(|err| { + custom_http_error( + StatusCode::FAILED_DEPENDENCY, + format!("Can't create client state: {err}"), + ) + })?; + Ok(HttpResponseUpdatedNoContent()) } async fn get_object_effects( @@ -830,6 +1071,106 @@ async fn get_object_info( Ok((object_ref, object, layout)) } +async fn handle_move_call( + call_params: CallRequest, + state: &mut ServerState, +) -> Result { + let module = Identifier::from_str(&call_params.module.to_owned())?; + let function = Identifier::from_str(&call_params.function.to_owned())?; + let args = call_params.args; + let type_args = call_params + .type_args + .unwrap_or_default() + .iter() + .map(|type_arg| parse_type_tag(type_arg)) + .collect::, _>>()?; + let gas_budget = call_params.gas_budget; + let gas_object_id = ObjectID::try_from(call_params.gas_object_id)?; + let package_object_id = ObjectID::from_hex_literal(&call_params.package_object_id)?; + + let sender: SuiAddress = decode_bytes_hex(call_params.sender.as_str())?; + + let (package_object_ref, package_object, _) = get_object_info(state, package_object_id).await?; + + // Extract the input args + let (object_ids, pure_args) = + resolve_move_function_args(&package_object, module.clone(), function.clone(), args)?; + + info!("Resolved fn to: \n {:?} & {:?}", object_ids, pure_args); + + // Fetch all the objects needed for this call + let mut input_objs = vec![]; + for obj_id in object_ids.clone() { + let (_, object, _) = get_object_info(state, obj_id).await?; + input_objs.push(object); + } + + // Pass in the objects for a deeper check + resolve_and_type_check( + package_object.clone(), + &module, + &function, + &type_args, + input_objs, + pure_args.clone(), + )?; + + // Fetch the object info for the gas obj + let (gas_obj_ref, _, _) = get_object_info(state, gas_object_id).await?; + + // Fetch the objects for the object args + let mut object_args_refs = Vec::new(); + for obj_id in object_ids { + let (object_ref, _, _) = get_object_info(state, obj_id).await?; + object_args_refs.push(object_ref); + } + + let tx_signer = Box::pin(SimpleTransactionSigner { + keystore: state.keystore.clone(), + }); + + let (cert, effects, gas_used) = match state + .gateway + .move_call( + sender, + package_object_ref, + module.to_owned(), + function.to_owned(), + type_args.clone(), + gas_obj_ref, + object_args_refs, + // TODO: Populate shared object args. sui/issue#719 + vec![], + pure_args, + gas_budget, + tx_signer, + ) + .await + { + Ok((cert, effects)) => { + let gas_used = match effects.status { + ExecutionStatus::Success { gas_used } => gas_used, + ExecutionStatus::Failure { gas_used, error } => { + let context = format!("Error calling move function, gas used {gas_used}"); + return Err(anyhow::Error::new(error).context(context)); + } + }; + (cert, effects, gas_used) + } + Err(err) => { + return Err(err); + } + }; + + let object_effects_summary = get_object_effects(state, effects).await?; + + Ok(TransactionResponse { + gas_used, + object_effects_summary: json!(object_effects_summary), + certificate: json!(cert), + }) +} + fn custom_http_error(status_code: http::StatusCode, message: String) -> HttpError { HttpError::for_client_error(None, status_code, message) } diff --git a/sui/src/unit_tests/rest_server_tests.rs b/sui/src/unit_tests/rest_server_tests.rs new file mode 100644 index 0000000000000..0b3c7132672d3 --- /dev/null +++ b/sui/src/unit_tests/rest_server_tests.rs @@ -0,0 +1,81 @@ +use anyhow::anyhow; +use dropshot::test_util::{LogContext, TestContext}; +use dropshot::{ConfigDropshot, ConfigLogging, ConfigLoggingLevel}; +use futures::future::try_join_all; +use http::{Method, StatusCode}; + +use crate::{create_api, ServerContext}; + +#[tokio::test] +async fn test_concurrency() -> Result<(), anyhow::Error> { + let api = create_api(); + + let config_dropshot: ConfigDropshot = Default::default(); + let log_config = ConfigLogging::StderrTerminal { + level: ConfigLoggingLevel::Debug, + }; + let logctx = LogContext::new("test_name", &log_config); + + let log = log_config + .to_logger("rest_server") + .map_err(|error| anyhow!("failed to create logger: {}", error))?; + + let documentation = api.openapi("Sui API", "0.1").json()?; + + let api_context = ServerContext::new(documentation); + let testctx = TestContext::new(api, api_context, &config_dropshot, Some(logctx), log); + + testctx + .client_testctx + .make_request( + Method::POST, + "/sui/genesis", + None as Option<()>, + StatusCode::OK, + ) + .await + .expect("expected success"); + + testctx + .client_testctx + .make_request( + Method::POST, + "/sui/start", + None as Option<()>, + StatusCode::OK, + ) + .await + .expect("expected success"); + + let task = (0..10).map(|_| { + testctx.client_testctx.make_request( + Method::GET, + "/addresses", + None as Option<()>, + StatusCode::OK, + ) + }); + + let task = task + .into_iter() + .map(|request| async { request.await }) + .collect::>(); + + let result = try_join_all(task).await.map_err(|e| anyhow!(e.message)); + + // Clean up + testctx + .client_testctx + .make_request( + Method::POST, + "/sui/stop", + None as Option<()>, + StatusCode::NO_CONTENT, + ) + .await + .expect("expected success"); + + result?; + + Ok(()) +} From cb25c08f5dc9ce82408387792830a6264fa42175 Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 15 Mar 2022 16:22:55 +0000 Subject: [PATCH 8/9] add license --- sui/src/unit_tests/rest_server_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sui/src/unit_tests/rest_server_tests.rs b/sui/src/unit_tests/rest_server_tests.rs index 0b3c7132672d3..78e5c080ed157 100644 --- a/sui/src/unit_tests/rest_server_tests.rs +++ b/sui/src/unit_tests/rest_server_tests.rs @@ -1,3 +1,5 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 use anyhow::anyhow; use dropshot::test_util::{LogContext, TestContext}; use dropshot::{ConfigDropshot, ConfigLogging, ConfigLoggingLevel}; From 2c27da94544ab7849b662cac840dbe1b28b63abd Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 16 Mar 2022 01:20:37 +0000 Subject: [PATCH 9/9] address PR comments --- sui/src/rest_server.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sui/src/rest_server.rs b/sui/src/rest_server.rs index d9d44921df4c4..6ad49fe797f7d 100644 --- a/sui/src/rest_server.rs +++ b/sui/src/rest_server.rs @@ -114,10 +114,12 @@ struct ServerContext { } pub struct ServerState { - config: NetworkConfig, gateway: GatewayClient, - keystore: Arc>>, + // The fields below are for genesis and starting demo network. + // TODO: Remove these fields when we fully transform rest_server into GatewayServer. addresses: Vec, + config: NetworkConfig, + keystore: Arc>>, working_dir: PathBuf, // Server handles that will be used to restart authorities. authority_handles: Vec>, @@ -379,13 +381,7 @@ async fn sui_stop( let server_context = rqctx.context(); // Taking state object without returning ownership let mut state = server_context.server_state.lock().await; - let state = state.as_mut().ok_or_else(|| { - custom_http_error( - StatusCode::FAILED_DEPENDENCY, - "Server state does not exist. Please make a POST request to `sui/genesis/` and `sui/start/` to bootstrap the network." - .to_string(), - ) - })?; + let state = state.as_mut().ok_or_else(server_state_error)?; for authority_handle in &state.authority_handles { authority_handle.abort(); @@ -752,7 +748,9 @@ async fn transfer_object( { Ok((cert, effects)) => { let gas_used = match effects.status { - ExecutionStatus::Success { gas_used } => gas_used, + // TODO: handle the actual return value stored in + // ExecutionStatus::Success + ExecutionStatus::Success { gas_used, .. } => gas_used, ExecutionStatus::Failure { gas_used, error } => { return Err(custom_http_error( StatusCode::FAILED_DEPENDENCY, @@ -1149,7 +1147,9 @@ async fn handle_move_call( { Ok((cert, effects)) => { let gas_used = match effects.status { - ExecutionStatus::Success { gas_used } => gas_used, + // TODO: handle the actual return value stored in + // ExecutionStatus::Success + ExecutionStatus::Success { gas_used, .. } => gas_used, ExecutionStatus::Failure { gas_used, error } => { let context = format!("Error calling move function, gas used {gas_used}"); return Err(anyhow::Error::new(error).context(context));