diff --git a/README.md b/README.md index 24c9d0e121daf..621c67b3d252e 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,60 @@ This will create `sui` and `wallet` binaries in `target/release` directory. ./sui genesis ``` -The genesis command creates 4 authorities, 5 user accounts each with 5 gas objects. +The genesis command creates 4 authorities, 5 user accounts each with 5 gas objects. The network configuration are stored in `network.conf` and can be used subsequently to start the network. -A `wallet.conf` will also be generated to be used by the `wallet` binary to manage the newly created accounts. +A `wallet.conf` will also be generated to be used by the `wallet` binary to manage the newly created accounts. + +### 2.1 Genesis customization + +The genesis process can be customised by providing a genesis config file. + +```shell +./sui genesis --config genesis.conf +``` +Example `genesis.conf` +```json +{ + "authorities": [ + { + "key_pair": "xWhgxF5fagohi2V9jzUToxnhJbTwbtV2qX4dbMGXR7lORTBuDBe+ppFDnnHz8L/BcYHWO76EuQzUYe5pnpLsFQ==", + "host": "127.0.0.1", + "port": 10000, + "db_path": "./authorities_db/4e45306e0c17bea691439e71f3f0bfc17181d63bbe84b90cd461ee699e92ec15", + "stake": 1 + } + ], + "accounts": [ + { + "address": "bd654f352c895d9ec14c491d3f2b4e1f98fb07323383bebe9f95ab625bff2fa0", + "gas_objects": [ + { + "object_id": "5c68ac7ba66ef69fdea0651a21b531a37bf342b7", + "gas_value": 1000 + } + ] + } + ], + "move_packages": [""], + "sui_framework_lib_path": "", + "move_framework_lib_path": "" +} +``` +All attributes in genesis.conf are optional, default value will be use if the attributes are not provided. +For example, the config shown below will create a network of 4 authorities, and pre-populate 2 gas objects for 4 accounts. +```json +{ + "authorities": [ + {},{},{},{} + ], + "accounts": [ + { "gas_objects":[{},{}] }, + { "gas_objects":[{},{}] }, + { "gas_objects":[{},{}] }, + { "gas_objects":[{},{}] } + ] +} +``` ### 3. Starting the network diff --git a/sui/Cargo.toml b/sui/Cargo.toml index 0d366555c0d05..80ee8cc89134a 100644 --- a/sui/Cargo.toml +++ b/sui/Cargo.toml @@ -45,6 +45,8 @@ move-package = { git = "https://github.com/diem/move", rev = "7683d09732dd930c58 move-core-types = { git = "https://github.com/diem/move", rev = "7683d09732dd930c581583bf5fde97fb7ac02ff7", features = ["address20"] } move-bytecode-verifier = { git = "https://github.com/diem/move", rev = "7683d09732dd930c581583bf5fde97fb7ac02ff7" } +once_cell = "1.9.0" + [dev-dependencies] tracing-test = "0.2.1" diff --git a/sui/src/config.rs b/sui/src/config.rs index f047e6814d6cd..2690614ec72d7 100644 --- a/sui/src/config.rs +++ b/sui/src/config.rs @@ -2,15 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 use sui_types::base_types::*; -use sui_types::crypto::KeyPair; +use sui_types::crypto::{get_key_pair, KeyPair}; -use crate::utils::Config; +use crate::utils::optional_address_as_hex; +use crate::utils::optional_address_from_hex; +use crate::utils::{Config, PortAllocator, DEFAULT_STARTING_PORT}; +use anyhow::anyhow; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; +use std::sync::Mutex; use std::time::Duration; +use sui_framework::DEFAULT_FRAMEWORK_PATH; use sui_network::transport; +const DEFAULT_WEIGHT: usize = 1; +const DEFAULT_GAS_AMOUNT: u64 = 100000; +pub const AUTHORITIES_DB_NAME: &str = "authorities_db"; + +static PORT_ALLOCATOR: Lazy> = + Lazy::new(|| Mutex::new(PortAllocator::new(DEFAULT_STARTING_PORT))); + #[derive(Serialize, Deserialize)] pub struct AccountInfo { #[serde(serialize_with = "bytes_as_hex", deserialize_with = "bytes_from_hex")] @@ -26,12 +40,64 @@ pub struct AuthorityInfo { pub base_port: u16, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize)] pub struct AuthorityPrivateInfo { pub key_pair: KeyPair, pub host: String, pub port: u16, pub db_path: PathBuf, + pub stake: usize, +} + +// Custom deserializer with optional default fields +impl<'de> Deserialize<'de> for AuthorityPrivateInfo { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let (_, new_key_pair) = get_key_pair(); + + let json = Value::deserialize(deserializer)?; + let key_pair = if let Some(val) = json.get("key_pair") { + KeyPair::deserialize(val).map_err(serde::de::Error::custom)? + } else { + new_key_pair + }; + let host = if let Some(val) = json.get("host") { + String::deserialize(val).map_err(serde::de::Error::custom)? + } else { + "127.0.0.1".to_string() + }; + let port = if let Some(val) = json.get("port") { + u16::deserialize(val).map_err(serde::de::Error::custom)? + } else { + PORT_ALLOCATOR + .lock() + .map_err(serde::de::Error::custom)? + .next_port() + .ok_or_else(|| serde::de::Error::custom("No available port."))? + }; + let db_path = if let Some(val) = json.get("db_path") { + PathBuf::deserialize(val).map_err(serde::de::Error::custom)? + } else { + PathBuf::from(".") + .join(AUTHORITIES_DB_NAME) + .join(encode_bytes_hex(key_pair.public_key_bytes())) + }; + let stake = if let Some(val) = json.get("stake") { + usize::deserialize(val).map_err(serde::de::Error::custom)? + } else { + DEFAULT_WEIGHT + }; + + Ok(AuthorityPrivateInfo { + key_pair, + host, + port, + db_path, + stake, + }) + } } #[derive(Serialize, Deserialize)] @@ -85,6 +151,7 @@ impl Display for WalletConfig { pub struct NetworkConfig { pub authorities: Vec, pub buffer_size: usize, + pub loaded_move_packages: Vec<(PathBuf, ObjectID)>, #[serde(skip)] config_path: PathBuf, } @@ -92,8 +159,9 @@ pub struct NetworkConfig { impl Config for NetworkConfig { fn create(path: &Path) -> Result { Ok(Self { - authorities: Vec::new(), + authorities: vec![], buffer_size: transport::DEFAULT_MAX_DATAGRAM_SIZE.to_string().parse()?, + loaded_move_packages: vec![], config_path: path.to_path_buf(), }) } @@ -106,3 +174,113 @@ impl Config for NetworkConfig { &self.config_path } } + +#[derive(Serialize, Deserialize, Default)] +#[serde(default)] +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, + #[serde(skip)] + config_path: PathBuf, +} + +#[derive(Serialize, Deserialize, Default)] +#[serde(default)] +pub struct AccountConfig { + #[serde( + skip_serializing_if = "Option::is_none", + serialize_with = "optional_address_as_hex", + deserialize_with = "optional_address_from_hex" + )] + pub address: Option, + pub gas_objects: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct ObjectConfig { + #[serde(default = "ObjectID::random")] + pub object_id: ObjectID, + #[serde(default = "default_gas_value")] + pub gas_value: u64, +} + +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; + +impl GenesisConfig { + pub fn default_genesis(path: &Path) -> Result { + let working_dir = path.parent().ok_or(anyhow!("Cannot resolve file path."))?; + let mut authorities = Vec::new(); + for _ in 0..DEFAULT_NUMBER_OF_AUTHORITIES { + // Get default authority config from deserialization logic. + let mut authority = AuthorityPrivateInfo::deserialize(Value::String(String::new()))?; + authority.db_path = working_dir + .join(AUTHORITIES_DB_NAME) + .join(encode_bytes_hex(&authority.key_pair.public_key_bytes())); + authorities.push(authority) + } + let mut accounts = Vec::new(); + for _ in 0..DEFAULT_NUMBER_OF_ACCOUNT { + let mut objects = Vec::new(); + for _ in 0..DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT { + objects.push(ObjectConfig { + object_id: ObjectID::random(), + gas_value: DEFAULT_GAS_AMOUNT, + }) + } + accounts.push(AccountConfig { + address: None, + gas_objects: objects, + }) + } + 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(), + }) + } +} + +impl Config for GenesisConfig { + fn create(path: &Path) -> Result { + Ok(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 + } +} diff --git a/sui/src/sui_commands.rs b/sui/src/sui_commands.rs index a8e29e8c5f4ff..7671a55acad7b 100644 --- a/sui/src/sui_commands.rs +++ b/sui/src/sui_commands.rs @@ -1,23 +1,25 @@ use crate::config::{ - AccountInfo, AuthorityInfo, AuthorityPrivateInfo, NetworkConfig, WalletConfig, + AccountInfo, AuthorityInfo, AuthorityPrivateInfo, GenesisConfig, NetworkConfig, WalletConfig, }; use crate::utils::Config; use anyhow::anyhow; use futures::future::join_all; +use move_package::BuildConfig; use std::collections::BTreeMap; -use std::net::TcpListener; +use std::path::PathBuf; use std::sync::Arc; use structopt::StructOpt; use sui_core::authority::{AuthorityState, AuthorityStore}; use sui_core::authority_server::AuthorityServer; -use sui_types::base_types::{encode_bytes_hex, ObjectID, SequenceNumber}; +use sui_types::base_types::{SequenceNumber, SuiAddress, TransactionDigest, TxContext}; + +use sui_adapter::adapter::generate_package_id; use sui_types::committee::Committee; use sui_types::crypto::get_key_pair; +use sui_types::error::SuiResult; use sui_types::object::Object; use tracing::{error, info}; -const DEFAULT_WEIGHT: usize = 1; - #[derive(StructOpt)] #[structopt(rename_all = "kebab-case")] pub enum SuiCommand { @@ -25,14 +27,26 @@ pub enum SuiCommand { #[structopt(name = "start")] Start, #[structopt(name = "genesis")] - Genesis, + Genesis { + #[structopt(long)] + config: Option, + }, } impl SuiCommand { pub async fn execute(&self, config: &mut NetworkConfig) -> Result<(), anyhow::Error> { match self { SuiCommand::Start => start_network(config).await, - SuiCommand::Genesis => genesis(config).await, + SuiCommand::Genesis { config: path } => { + let genesis_conf = if let Some(path) = path { + GenesisConfig::read(path)? + } else { + // Network config has been created by this point, safe to unwrap. + let working_dir = config.config_path().parent().unwrap(); + GenesisConfig::default_genesis(&working_dir.join("genesis.conf"))? + }; + genesis(config, genesis_conf).await + } } } } @@ -53,12 +67,12 @@ async fn start_network(config: &NetworkConfig) -> Result<(), anyhow::Error> { config .authorities .iter() - .map(|info| (*info.key_pair.public_key_bytes(), DEFAULT_WEIGHT)) + .map(|info| (*info.key_pair.public_key_bytes(), info.stake)) .collect(), ); for authority in &config.authorities { - let server = make_server(authority, &committee, &[], config.buffer_size).await; + let server = make_server(authority, &committee, &[], config.buffer_size).await?; handles.push(async move { let spawned_server = match server.spawn().await { Ok(server) => server, @@ -78,63 +92,122 @@ async fn start_network(config: &NetworkConfig) -> Result<(), anyhow::Error> { Ok(()) } -async fn genesis(config: &mut NetworkConfig) -> Result<(), anyhow::Error> { +async fn genesis( + config: &mut NetworkConfig, + genesis_conf: GenesisConfig, +) -> Result<(), anyhow::Error> { // We have created the config file, safe to unwrap the path here. let working_dir = &config.config_path().parent().unwrap().to_path_buf(); 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 authorities = BTreeMap::new(); + let mut voting_right = BTreeMap::new(); let mut authority_info = Vec::new(); - let mut port_allocator = PortAllocator::new(10000); - - info!("Creating new authorities..."); - let authorities_db_path = working_dir.join("authorities_db"); - for _ in 0..4 { - let (_, key_pair) = get_key_pair(); - let pub_key = *key_pair.public_key_bytes(); - let info = AuthorityPrivateInfo { - key_pair, - host: "127.0.0.1".to_string(), - port: port_allocator.next_port().expect("No free ports"), - db_path: authorities_db_path.join(encode_bytes_hex(&pub_key)), - }; + info!( + "Creating {} new authorities...", + genesis_conf.authorities.len() + ); + + for authority in genesis_conf.authorities { + voting_right.insert(*authority.key_pair.public_key_bytes(), authority.stake); authority_info.push(AuthorityInfo { - name: pub_key, - host: info.host.clone(), - base_port: info.port, + name: *authority.key_pair.public_key_bytes(), + host: authority.host.clone(), + base_port: authority.port, }); - authorities.insert(pub_key, 1); - config.authorities.push(info); + config.authorities.push(authority); } - config.save()?; - let mut new_addresses = Vec::new(); let mut preload_objects = Vec::new(); - info!("Creating test objects..."); - for _ in 0..5 { - let (address, key_pair) = get_key_pair(); - new_addresses.push(AccountInfo { address, key_pair }); - for _ in 0..5 { + 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 + ); + for account in genesis_conf.accounts { + let address = if let Some(address) = account.address { + address + } else { + let (address, key_pair) = get_key_pair(); + new_addresses.push(AccountInfo { address, key_pair }); + address + }; + for object_conf in account.gas_objects { let new_object = Object::with_id_owner_gas_coin_object_for_testing( - ObjectID::random(), + object_conf.object_id, SequenceNumber::new(), address, - 1000, + object_conf.gas_value, ); preload_objects.push(new_object); } } - let committee = Committee::new(authorities); - // Make server state to persist the objects. - let config_path = config.config_path(); + // Load Sui and Move framework lib + info!( + "Loading Sui framework lib from {:?}", + genesis_conf.sui_framework_lib_path + ); + let sui_lib = sui_framework::get_sui_framework_modules(&genesis_conf.sui_framework_lib_path)?; + let lib_object = + Object::new_package(sui_lib, SuiAddress::default(), TransactionDigest::genesis()); + preload_objects.push(lib_object); + + info!( + "Loading Move framework lib from {:?}", + genesis_conf.move_framework_lib_path + ); + let move_lib = sui_framework::get_move_stdlib_modules(&genesis_conf.move_framework_lib_path)?; + let lib_object = Object::new_package( + move_lib, + SuiAddress::default(), + TransactionDigest::genesis(), + ); + preload_objects.push(lib_object); + + // Build custom move packages + if !genesis_conf.move_packages.is_empty() { + info!( + "Loading {} Move packages from {:?}", + &genesis_conf.move_packages.len(), + &genesis_conf.move_packages + ); + + for path in genesis_conf.move_packages { + let mut modules = + sui_framework::build_move_package(&path, BuildConfig::default(), false)?; + generate_package_id( + &mut modules, + &mut TxContext::new(&SuiAddress::default(), TransactionDigest::genesis()), + )?; + + let object = + Object::new_package(modules, SuiAddress::default(), TransactionDigest::genesis()); + info!("Loaded package [{}] from {:?}.", object.id(), path); + // Writing package id to network.conf for user to retrieve later. + config.loaded_move_packages.push((path, object.id())); + preload_objects.push(object) + } + } + + let committee = Committee::new(voting_right); + + // Make server state to persist the objects and modules. + info!( + "Preloading {} objects to authorities.", + preload_objects.len() + ); for authority in &config.authorities { - make_server(authority, &committee, &preload_objects, config.buffer_size).await; + make_server(authority, &committee, &preload_objects, config.buffer_size).await?; } let wallet_path = working_dir.join("wallet.conf"); @@ -142,15 +215,18 @@ async fn genesis(config: &mut NetworkConfig) -> Result<(), anyhow::Error> { wallet_config.authorities = authority_info; wallet_config.accounts = new_addresses; wallet_config.db_folder_path = working_dir.join("client_db"); - wallet_config.save()?; info!("Network genesis completed."); - info!("Network config file is stored in {:?}.", config_path); + 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(()) } @@ -159,43 +235,26 @@ async fn make_server( committee: &Committee, pre_load_objects: &[Object], buffer_size: usize, -) -> AuthorityServer { +) -> SuiResult { let store = Arc::new(AuthorityStore::open(&authority.db_path, None)); let name = *authority.key_pair.public_key_bytes(); - let state = AuthorityState::new_with_genesis_modules( + let state = AuthorityState::new( committee.clone(), name, Box::pin(authority.key_pair.copy()), store, - ) - .await; + ); for object in pre_load_objects { state.init_order_lock(object.to_object_reference()).await; state.insert_object(object.clone()).await; } - AuthorityServer::new(authority.host.clone(), authority.port, buffer_size, state) -} - -struct PortAllocator { - next_port: u16, -} - -impl PortAllocator { - pub fn new(starting_port: u16) -> Self { - Self { - next_port: starting_port, - } - } - fn next_port(&mut self) -> Option { - for port in self.next_port..65535 { - if TcpListener::bind(("127.0.0.1", port)).is_ok() { - self.next_port = port + 1; - return Some(port); - } - } - None - } + Ok(AuthorityServer::new( + authority.host.clone(), + authority.port, + buffer_size, + state, + )) } diff --git a/sui/src/unit_tests/cli_tests.rs b/sui/src/unit_tests/cli_tests.rs index a3f24958776bb..f63098754c965 100644 --- a/sui/src/unit_tests/cli_tests.rs +++ b/sui/src/unit_tests/cli_tests.rs @@ -1,9 +1,12 @@ use super::*; use std::fs::read_dir; use std::time::Duration; -use sui::config::{AccountInfo, NetworkConfig, WalletConfig}; +use sui::config::{ + AccountConfig, AccountInfo, GenesisConfig, NetworkConfig, ObjectConfig, WalletConfig, + AUTHORITIES_DB_NAME, +}; use sui::wallet_commands::{WalletCommands, WalletContext}; -use sui_types::base_types::encode_bytes_hex; +use sui_types::base_types::{encode_bytes_hex, ObjectID}; use sui_types::crypto::get_key_pair; use tokio::task; use tracing_test::traced_test; @@ -18,7 +21,9 @@ async fn test_genesis() -> Result<(), anyhow::Error> { let start = SuiCommand::Start.execute(&mut config).await; assert!(matches!(start, Err(..))); // Genesis - SuiCommand::Genesis.execute(&mut config).await?; + SuiCommand::Genesis { config: None } + .execute(&mut config) + .await?; assert!(logs_contain("Network genesis completed.")); // Get all the new file names @@ -28,7 +33,7 @@ async fn test_genesis() -> Result<(), anyhow::Error> { assert_eq!(3, files.len()); assert!(files.contains(&"wallet.conf".to_string())); - assert!(files.contains(&"authorities_db".to_string())); + assert!(files.contains(&AUTHORITIES_DB_NAME.to_string())); assert!(files.contains(&"network.conf".to_string())); // Check network.conf @@ -45,7 +50,9 @@ async fn test_genesis() -> Result<(), anyhow::Error> { ); // Genesis 2nd time should fail - let result = SuiCommand::Genesis.execute(&mut config).await; + let result = SuiCommand::Genesis { config: None } + .execute(&mut config) + .await; assert!(matches!(result, Err(..))); working_dir.close()?; @@ -86,14 +93,18 @@ async fn test_objects_command() -> Result<(), anyhow::Error> { let working_dir = tempfile::tempdir()?; let mut config = NetworkConfig::read_or_create(&working_dir.path().join("network.conf"))?; - SuiCommand::Genesis.execute(&mut config).await?; + SuiCommand::Genesis { config: None } + .execute(&mut config) + .await?; // Start network let network = task::spawn(async move { SuiCommand::Start.execute(&mut config).await }); // Wait for authorities to come alive. - while !logs_contain("Listening to TCP traffic on 127.0.0.1") { + let mut count = 0; + while count < 50 && !logs_contain("Listening to TCP traffic on 127.0.0.1") { tokio::time::sleep(Duration::from_millis(100)).await; + count += 1; } // Create Wallet context. @@ -125,3 +136,114 @@ async fn test_objects_command() -> Result<(), anyhow::Error> { network.abort(); Ok(()) } + +#[traced_test] +#[tokio::test] +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)?; + config.accounts.clear(); + let object_id = ObjectID::random(); + config.accounts.push(AccountConfig { + address: None, + gas_objects: vec![ObjectConfig { + object_id, + gas_value: 500, + }], + }); + config.save()?; + + // Create empty network config for genesis + let mut config = NetworkConfig::read_or_create(&working_dir.path().join("network.conf"))?; + + // Genesis + SuiCommand::Genesis { + config: Some(genesis_path), + } + .execute(&mut config) + .await?; + + let mut config = NetworkConfig::read(&working_dir.path().join("network.conf"))?; + assert_eq!(4, config.authorities.len()); + + // Start network + let network = task::spawn(async move { SuiCommand::Start.execute(&mut config).await }); + + // Wait for authorities to come alive. + let mut count = 0; + while count < 50 && !logs_contain("Listening to TCP traffic on 127.0.0.1") { + tokio::time::sleep(Duration::from_millis(100)).await; + count += 1; + } + + // 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().address; + let mut context = WalletContext::new(wallet_conf)?; + // Sync client to retrieve objects from the network. + WalletCommands::SyncClientState { address } + .execute(&mut context) + .await?; + + // Print objects owned by `address` + WalletCommands::Objects { address } + .execute(&mut context) + .await?; + + // confirm the object with custom object id. + assert!(logs_contain(format!("{}", object_id).as_str())); + + network.abort(); + Ok(()) +} + +#[traced_test] +#[tokio::test] +async fn test_custom_genesis_with_custom_move_package() -> 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)?; + config.accounts.clear(); + let object_id = ObjectID::random(); + config.accounts.push(AccountConfig { + address: None, + gas_objects: vec![ObjectConfig { + object_id, + gas_value: 500, + }], + }); + config.move_packages.push( + PathBuf::from("..") + .join("sui_programmability") + .join("examples"), + ); + config.save()?; + + // Create empty network config for genesis + let mut config = NetworkConfig::read_or_create(&working_dir.path().join("network.conf"))?; + + // Genesis + SuiCommand::Genesis { + config: Some(genesis_path), + } + .execute(&mut config) + .await?; + + assert!(logs_contain("Loading 1 Move packages")); + // Checks network config contains package ids + let network_conf = NetworkConfig::read(&working_dir.path().join("network.conf"))?; + assert_eq!(1, network_conf.loaded_move_packages.len()); + + // Make sure we log out package id + for (_, id) in network_conf.loaded_move_packages { + assert!(logs_contain(&*format!("{}", id))); + } + Ok(()) +} diff --git a/sui/src/utils.rs b/sui/src/utils.rs index c6e95c4332f61..e7b757649986e 100644 --- a/sui/src/utils.rs +++ b/sui/src/utils.rs @@ -1,13 +1,17 @@ // Copyright (c) Mysten Labs // SPDX-License-Identifier: Apache-2.0 use serde::de::DeserializeOwned; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::fs; use std::fs::File; use std::io::BufReader; +use std::net::TcpListener; use std::path::{Path, PathBuf}; +use sui_types::base_types::{decode_bytes_hex, encode_bytes_hex, SuiAddress}; use tracing::log::trace; +pub const DEFAULT_STARTING_PORT: u16 = 10000; + pub trait Config where Self: DeserializeOwned + Serialize, @@ -15,11 +19,7 @@ where fn read_or_create(path: &Path) -> Result { let path_buf = PathBuf::from(path); Ok(if path_buf.exists() { - trace!("Reading config from '{:?}'", path); - let reader = BufReader::new(File::open(path_buf)?); - let mut config: Self = serde_json::from_reader(reader)?; - config.set_config_path(path); - config + Self::read(path)? } else { trace!("Config file not found, creating new config '{:?}'", path); let new_config = Self::create(path)?; @@ -28,6 +28,14 @@ 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) + } + fn write(&self, path: &Path) -> Result<(), anyhow::Error> { trace!("Writing config to '{:?}'", path); let config = serde_json::to_string_pretty(self).unwrap(); @@ -44,3 +52,47 @@ where fn set_config_path(&mut self, path: &Path); fn config_path(&self) -> &Path; } + +pub struct PortAllocator { + next_port: u16, +} + +impl PortAllocator { + pub fn new(starting_port: u16) -> Self { + Self { + next_port: starting_port, + } + } + pub fn next_port(&mut self) -> Option { + for port in self.next_port..65535 { + if TcpListener::bind(("127.0.0.1", port)).is_ok() { + self.next_port = port + 1; + return Some(port); + } + } + None + } +} + +pub fn optional_address_as_hex( + key: &Option, + serializer: S, +) -> Result +where + S: serde::ser::Serializer, +{ + serializer.serialize_str( + &*key + .map(|addr| encode_bytes_hex(&addr)) + .unwrap_or_else(|| "".to_string()), + ) +} + +pub fn optional_address_from_hex<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let value = decode_bytes_hex(&s).map_err(serde::de::Error::custom)?; + Ok(Some(value)) +} diff --git a/sui_core/src/authority.rs b/sui_core/src/authority.rs index 6ad9f4c77cb22..17c90331c0012 100644 --- a/sui_core/src/authority.rs +++ b/sui_core/src/authority.rs @@ -22,6 +22,7 @@ use sui_types::{ messages::*, object::{Data, Object}, storage::Storage, + MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS, }; #[cfg(test)] @@ -461,6 +462,25 @@ impl AuthorityState { }) } + pub fn new( + committee: Committee, + name: AuthorityName, + secret: StableSyncAuthoritySigner, + store: Arc, + ) -> Self { + let native_functions = + sui_framework::natives::all_natives(MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS); + AuthorityState { + committee, + name, + secret, + _native_functions: native_functions.clone(), + move_vm: adapter::new_move_vm(native_functions) + .expect("We defined natives to not fail here"), + _database: store, + } + } + pub async fn new_with_genesis_modules( committee: Committee, name: AuthorityName, diff --git a/sui_programmability/adapter/src/genesis.rs b/sui_programmability/adapter/src/genesis.rs index 7c7ca2f638f7b..2b2808d98862b 100644 --- a/sui_programmability/adapter/src/genesis.rs +++ b/sui_programmability/adapter/src/genesis.rs @@ -3,15 +3,19 @@ use move_vm_runtime::native_functions::NativeFunctionTable; use once_cell::sync::Lazy; +use std::path::{Path, PathBuf}; use std::sync::Mutex; -use sui_framework::{self}; +use sui_framework::{self, DEFAULT_FRAMEWORK_PATH}; +use sui_types::error::SuiResult; use sui_types::{ base_types::{SuiAddress, TransactionDigest}, object::Object, MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS, }; -static GENESIS: Lazy> = Lazy::new(|| Mutex::new(create_genesis_module_objects())); +static GENESIS: Lazy> = Lazy::new(|| { + Mutex::new(create_genesis_module_objects(&PathBuf::from(DEFAULT_FRAMEWORK_PATH)).unwrap()) +}); struct Genesis { pub objects: Vec, @@ -24,9 +28,10 @@ pub fn clone_genesis_data() -> (Vec, NativeFunctionTable) { } /// Create and return objects wrapping the genesis modules for fastX -fn create_genesis_module_objects() -> Genesis { - let sui_modules = sui_framework::get_sui_framework_modules(); - let std_modules = sui_framework::get_move_stdlib_modules(); +fn create_genesis_module_objects(lib_dir: &Path) -> SuiResult { + let sui_modules = sui_framework::get_sui_framework_modules(lib_dir)?; + let std_modules = + sui_framework::get_move_stdlib_modules(&lib_dir.join("deps").join("move-stdlib"))?; let native_functions = sui_framework::natives::all_natives(MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS); let owner = SuiAddress::default(); @@ -34,8 +39,8 @@ fn create_genesis_module_objects() -> Genesis { Object::new_package(sui_modules, owner, TransactionDigest::genesis()), Object::new_package(std_modules, owner, TransactionDigest::genesis()), ]; - Genesis { + Ok(Genesis { objects, native_functions, - } + }) } diff --git a/sui_programmability/framework/src/lib.rs b/sui_programmability/framework/src/lib.rs index a86d739a58a6d..a471ca9fea803 100644 --- a/sui_programmability/framework/src/lib.rs +++ b/sui_programmability/framework/src/lib.rs @@ -6,15 +6,20 @@ use move_core_types::{account_address::AccountAddress, ident_str}; use move_package::BuildConfig; use num_enum::TryFromPrimitive; use std::collections::HashSet; -use std::path::{Path, PathBuf}; +use std::path::Path; use sui_types::error::{SuiError, SuiResult}; use sui_verifier::verifier as sui_bytecode_verifier; +#[cfg(test)] +use std::path::PathBuf; + pub mod natives; // Move unit tests will halt after executing this many steps. This is a protection to avoid divergence const MAX_UNIT_TEST_INSTRUCTIONS: u64 = 100_000; +pub const DEFAULT_FRAMEWORK_PATH: &str = env!("CARGO_MANIFEST_DIR"); + #[derive(TryFromPrimitive)] #[repr(u8)] pub enum EventType { @@ -33,26 +38,24 @@ pub enum EventType { User, } -pub fn get_sui_framework_modules() -> Vec { - let modules = build_framework("."); - // TODO: Consider not unwrap. - verify_modules(&modules).unwrap(); - modules +pub fn get_sui_framework_modules(lib_dir: &Path) -> SuiResult> { + let modules = build_framework(lib_dir)?; + verify_modules(&modules)?; + Ok(modules) } -pub fn get_move_stdlib_modules() -> Vec { +pub fn get_move_stdlib_modules(lib_dir: &Path) -> SuiResult> { let denylist = vec![ ident_str!("Capability").to_owned(), ident_str!("Event").to_owned(), ident_str!("GUID").to_owned(), ]; - let modules: Vec = build_framework("deps/move-stdlib") + let modules: Vec = build_framework(lib_dir)? .into_iter() .filter(|m| !denylist.contains(&m.self_id().name().to_owned())) .collect(); - // TODO: Consider not unwrap. - verify_modules(&modules).unwrap(); - modules + verify_modules(&modules)?; + Ok(modules) } /// Given a `path` and a `build_config`, build the package in that path and return the compiled modules as Vec>. @@ -160,15 +163,12 @@ fn verify_modules(modules: &[CompiledModule]) -> SuiResult { // TODO(https://github.com/MystenLabs/fastnft/issues/69): Run Move linker } -fn build_framework(sub_dir: &str) -> Vec { - let mut framework_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - framework_dir.push(sub_dir); +fn build_framework(framework_dir: &Path) -> SuiResult> { let build_config = BuildConfig { dev_mode: false, ..Default::default() }; - // TODO: Consider not unwrap. - build_move_package(&framework_dir, build_config, true).unwrap() + build_move_package(framework_dir, build_config, true) } pub fn run_move_unit_tests(path: &Path) -> SuiResult { @@ -198,7 +198,7 @@ pub fn run_move_unit_tests(path: &Path) -> SuiResult { #[test] fn run_framework_move_unit_tests() { - get_sui_framework_modules(); + get_sui_framework_modules(&PathBuf::from(DEFAULT_FRAMEWORK_PATH)); run_move_unit_tests(Path::new(env!("CARGO_MANIFEST_DIR"))).unwrap(); }