diff --git a/README.md b/README.md index 6c8e2f776d96c..f6effce1fc351 100644 --- a/README.md +++ b/README.md @@ -16,42 +16,47 @@ FastPay allows a set of distributed authorities, some of which are Byzantine, to ```bash cargo build --release cd target/release -rm -f *.json *.txt +rm -f *.json *.toml +rm -rf db* -# Create configuration files for 4 authorities with 4 shards each. +# Create DB dirs and configuration files for 4 authorities. # * Private server states are stored in `server*.json`. # * `committee.json` is the public description of the FastPay committee. for I in 1 2 3 4 do - ./server --server server"$I".json generate --host 127.0.0.1 --port 9"$I"00 --shards 4 >> committee.json + mkdir ./db"$I" + ./server --server server"$I".json generate --host 127.0.0.1 --port 9"$I"00 --database-path ./db"$I" >> committee.json done -# Create configuration files for 1000 user accounts. +# Create configuration files for 100 user accounts, with 4 gas objects per account and 200 value each. # * Private account states are stored in one local wallet `accounts.json`. -# * `initial_accounts.txt` is used to mint the corresponding initial balances at startup on the server side. -./client --committee committee.json --accounts accounts.json create_accounts 1000 --initial-funding 100 >> initial_accounts.txt - +# * `initial_accounts.toml` is used to mint the corresponding initially randomly generated (for now) objects at startup on the server side. +./client --committee committee.json --accounts accounts.json create-accounts --num 100 \ +--gas-objs-per-account 4 --value-per-per-obj 200 initial_accounts.toml # Start servers for I in 1 2 3 4 do - for J in $(seq 0 3) - do - ./server --server server"$I".json run --shard "$J" --initial-accounts initial_accounts.txt --committee committee.json & - done - done - -# Query (locally cached) balance for first and last user account -ACCOUNT1="`head -n 1 initial_accounts.txt | awk -F: '{ print $1 }'`" -ACCOUNT2="`tail -n -1 initial_accounts.txt | awk -F: '{ print $1 }'`" -./client --committee committee.json --accounts accounts.json query_balance "$ACCOUNT1" -./client --committee committee.json --accounts accounts.json query_balance "$ACCOUNT2" - -# Transfer 10 units -./client --committee committee.json --accounts accounts.json transfer 10 --from "$ACCOUNT1" --to "$ACCOUNT2" - -# Query balances again -./client --committee committee.json --accounts accounts.json query_balance "$ACCOUNT1" -./client --committee committee.json --accounts accounts.json query_balance "$ACCOUNT2" + ./server --server server"$I".json run --initial-accounts initial_accounts.toml --committee committee.json & +done + +# Query account addresses +./client --committee committee.json --accounts accounts.json query-accounts-addrs + +# Query (locally cached) object info for first and last user account +ACCOUNT1=`./client --committee committee.json --accounts accounts.json query-accounts-addrs | head -n 1` +ACCOUNT2=`./client --committee committee.json --accounts accounts.json query-accounts-addrs | tail -n -1` +./client --committee committee.json --accounts accounts.json query-objects "$ACCOUNT1" +./client --committee committee.json --accounts accounts.json query-objects "$ACCOUNT2" + +# Get the first ObjectId for Account1 +ACCOUNT1_OBJECT1=`./client --committee committee.json --accounts accounts.json query-objects "$ACCOUNT1" | head -n 1 | awk -F: '{ print $1 }'` + +# Transfer object by ObjectID +./client --committee committee.json --accounts accounts.json transfer "$ACCOUNT1_OBJECT1" --from "$ACCOUNT1" --to "$ACCOUNT2" + +# Query objects again +./client --committee committee.json --accounts accounts.json query-objects "$ACCOUNT1" +./client --committee committee.json --accounts accounts.json query-objects "$ACCOUNT2" # Launch local benchmark using all user accounts ./client --committee committee.json --accounts accounts.json benchmark diff --git a/fastpay/Cargo.toml b/fastpay/Cargo.toml index 498cd6843a44a..e50fcbaf56ea7 100644 --- a/fastpay/Cargo.toml +++ b/fastpay/Cargo.toml @@ -18,6 +18,8 @@ serde_json = "1.0.57" structopt = "0.3" tempfile = "3.2.0" tokio = { version = "0.2.22", features = ["full"] } +rand = "0.7.3" +toml = "0.5.8" strum = "0.23.0" strum_macros = "0.23" num_cpus = "1.13.1" diff --git a/fastpay/src/client.rs b/fastpay/src/client.rs index 0b49c338a62c9..62cad16cfc5cc 100644 --- a/fastpay/src/client.rs +++ b/fastpay/src/client.rs @@ -275,7 +275,8 @@ fn deserialize_response(response: &[u8]) -> Option { #[derive(StructOpt)] #[structopt( name = "FastPay Client", - about = "A Byzantine fault tolerant payments sidechain with low-latency finality and high throughput" + about = "A Byzantine fault tolerant payments sidechain with low-latency finality and high throughput", + rename_all = "kebab-case" )] struct ClientOpt { /// Sets the file storing the state of our user accounts (an empty one will be created if missing) @@ -298,12 +299,13 @@ struct ClientOpt { #[structopt(long, default_value = transport::DEFAULT_MAX_DATAGRAM_SIZE)] buffer_size: usize, - /// Subcommands. Acceptable values are transfer, query_balance, benchmark, and create_accounts. + /// Subcommands. Acceptable values are transfer, query_objects, benchmark, and create_accounts. #[structopt(subcommand)] cmd: ClientCommands, } #[derive(StructOpt)] +#[structopt(rename_all = "kebab-case")] enum ClientCommands { /// Transfer funds #[structopt(name = "transfer")] @@ -320,9 +322,13 @@ enum ClientCommands { object_id: String, }, - /// Obtain the spendable balance - #[structopt(name = "query_balance")] - QueryBalance { + /// Obtain the Account Addresses + #[structopt(name = "query-accounts-addrs")] + QueryAccountAddresses {}, + + /// Obtain the Object Info + #[structopt(name = "query-objects")] + QueryObjects { /// Address of the account address: String, }, @@ -343,15 +349,25 @@ enum ClientCommands { server_configs: Option>, }, - /// Create new user accounts and print the public keys - #[structopt(name = "create_accounts")] + /// Create new user accounts with randomly generated object IDs + #[structopt(name = "create-accounts")] CreateAccounts { - /// known initial balance of the account - #[structopt(long)] - initial_objects: Option>, - /// Number of additional accounts to create + #[structopt(long, default_value = "1000")] num: u32, + + /// Number of objects per account + #[structopt(long, default_value = "1000")] + gas_objs_per_account: u32, + + /// Gas value per object + #[structopt(long, default_value = "1000")] + #[allow(dead_code)] + value_per_per_obj: u32, + + /// Initial state config file path + #[structopt(name = "init-state-cfg")] + initial_state_config_path: String, }, } @@ -421,7 +437,17 @@ fn main() { }); } - ClientCommands::QueryBalance { address } => { + ClientCommands::QueryAccountAddresses {} => { + let addr_strings: Vec<_> = accounts_config + .addresses() + .into_iter() + .map(|addr| format!("{:?}", addr).trim_end_matches('=').to_string()) + .collect(); + let addr_text = addr_strings.join("\n"); + println!("{}", addr_text); + } + + ClientCommands::QueryObjects { address } => { let user_address = decode_address(&address).expect("Failed to decode address"); let mut rt = Runtime::new().unwrap(); @@ -434,15 +460,17 @@ fn main() { send_timeout, recv_timeout, ); - info!("Starting balance query"); - let time_start = Instant::now(); - let time_total = time_start.elapsed().as_micros(); - info!("Balance confirmed after {} us", time_total); + + let objects_ids = client_state.object_ids(); + accounts_config.update_from_state(&client_state); accounts_config .write(accounts_config_path) .expect("Unable to write user accounts"); - info!("Saved client account state"); + + for (obj_id, seq_num) in objects_ids { + println!("{:#x}: {:?}", obj_id, seq_num); + } }); } @@ -474,7 +502,7 @@ fn main() { deserialize_response(&buf[..]).and_then(|info| info.pending_confirmation) }) .collect(); - warn!("Received {} valid votes.", votes.len()); + info!("Received {} valid votes.", votes.len()); warn!("Starting benchmark phase 2 (confirmation orders)"); let certificates = if let Some(files) = server_configs { @@ -524,24 +552,33 @@ fn main() { } ClientCommands::CreateAccounts { - initial_objects, num, + gas_objs_per_account, + // TODO: Integrate gas logic with https://github.com/MystenLabs/fastnft/pull/97 + value_per_per_obj: _, + initial_state_config_path, } => { let num_accounts: u32 = num; - let object_ids = match initial_objects { - Some(object_ids) => object_ids - .into_iter() - .map(|string| ObjectID::from_hex_literal(&string).unwrap()) - .collect(), - - None => Vec::new(), - }; + let mut init_state_cfg: InitialStateConfig = InitialStateConfig::new(); for _ in 0..num_accounts { - let account = UserAccount::new(object_ids.clone()); - println!("{}:{:?}", encode_address(&account.address), object_ids); + let mut obj_ids = Vec::new(); + + for _ in 0..gas_objs_per_account { + obj_ids.push(ObjectID::random()); + } + let account = UserAccount::new(obj_ids.clone()); + + init_state_cfg.config.push(InitialStateConfigEntry { + address: account.address, + object_ids: obj_ids, + }); + accounts_config.insert(account); } + init_state_cfg + .write(initial_state_config_path.as_str()) + .expect("Unable to write to initial state config file"); accounts_config .write(accounts_config_path) .expect("Unable to write user accounts"); diff --git a/fastpay/src/config.rs b/fastpay/src/config.rs index 2d2e78f666306..1f6fc003dd337 100644 --- a/fastpay/src/config.rs +++ b/fastpay/src/config.rs @@ -11,8 +11,8 @@ use fastx_types::{ use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, - fs::{self, File, OpenOptions}, - io::{BufRead, BufReader, BufWriter, Write}, + fs::{self, read_to_string, File, OpenOptions}, + io::{BufReader, BufWriter, Write}, }; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -140,6 +140,10 @@ impl AccountsConfig { self.accounts.values_mut() } + pub fn addresses(&mut self) -> impl Iterator { + self.accounts.keys() + } + pub fn update_from_state(&mut self, state: &ClientState) { let account = self .accounts @@ -197,34 +201,37 @@ impl AccountsConfig { } } +#[derive(Serialize, Deserialize)] +pub struct InitialStateConfigEntry { + pub address: FastPayAddress, + pub object_ids: Vec, +} +#[derive(Serialize, Deserialize)] pub struct InitialStateConfig { - pub accounts: Vec<(FastPayAddress, ObjectID)>, + pub config: Vec, } impl InitialStateConfig { + pub fn new() -> Self { + Self { config: Vec::new() } + } + pub fn read(path: &str) -> Result { - let file = File::open(path)?; - let reader = BufReader::new(file); - let mut accounts = Vec::new(); - for line in reader.lines() { - let line = line?; - let elements = line.split(':').collect::>(); - if elements.len() != 2 { - anyhow::bail!("expecting two columns separated with ':'") - } - let address = decode_address(elements[0])?; - let object_id = ObjectID::from_hex_literal(elements[1])?; - accounts.push((address, object_id)); - } - Ok(Self { accounts }) + let raw_data: String = read_to_string(path)?.parse()?; + + Ok(toml::from_str(&raw_data)?) } pub fn write(&self, path: &str) -> Result<(), std::io::Error> { - let file = OpenOptions::new().create(true).write(true).open(path)?; - let mut writer = BufWriter::new(file); - for (address, object_id) in &self.accounts { - writeln!(writer, "{}:{}", encode_address(address), object_id,)?; - } + let config = toml::to_string(self).unwrap(); + + fs::write(path, config).expect("Unable to write to initial config file"); Ok(()) } } + +impl Default for InitialStateConfig { + fn default() -> Self { + Self::new() + } +} diff --git a/fastpay/src/server.rs b/fastpay/src/server.rs index fbe49dad3ece4..198a61e483e6e 100644 --- a/fastpay/src/server.rs +++ b/fastpay/src/server.rs @@ -40,10 +40,14 @@ fn make_server( // Load initial states let mut rt = Runtime::new().unwrap(); rt.block_on(async { - for (address, object_id) in &initial_accounts_config.accounts { - let mut client = Object::with_id_for_testing(*object_id); - client.transfer(*address); - state.insert_object(client).await; + for initial_state_cfg_entry in &initial_accounts_config.config { + let address = &initial_state_cfg_entry.address; + for object_id in &initial_state_cfg_entry.object_ids { + let object = Object::with_id_owner_for_testing(*object_id, *address); + + state.init_order_lock(object.to_object_reference()).await; + state.insert_object(object).await; + } } });