diff --git a/README.md b/README.md index 0bdf75cfce092..044a6595f47e7 100644 --- a/README.md +++ b/README.md @@ -12,41 +12,56 @@ Sui extends Sui by allowing objects to be transacted. Sui allows a set of distributed authorities, some of which are Byzantine, to maintain a high-integrity and availability settlement system for pre-funded payments. It can be used to settle payments in a native unit of value (crypto-currency), or as a financial side-infrastructure to support retail payments in fiat currencies. Sui is based on Byzantine Consistent Broadcast as its core primitive, foregoing the expenses of full atomic commit channels (consensus). The resulting system has low-latency for both confirmation and payment finality. Remarkably, each authority can be sharded across many machines to allow unbounded horizontal scalability. Our experiments demonstrate intra-continental confirmation latency of less than 100ms, making Sui applicable to point of sale payments. In laboratory environments, we achieve over 80,000 transactions per second with 20 authorities---surpassing the requirements of current retail card payment networks, while significantly increasing their robustness. ## Quickstart with local Sui network and interactive wallet + ### 1. Build the binaries + ```shell cargo build --release cd target/release ``` -This will create `fastx` and `wallet` binaries in `target/release` directory. + +This will create `fastx` and `wallet` binaries in `target/release` directory. ### 2. Genesis + ```shell ./fastx genesis ``` + 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. ### 3. Starting the network -Run the following command to start the local Sui network: + +Run the following command to start the local Sui network: + ```shell ./fastx start ``` -or + +or + ```shell ./fastx start --config [config file path] ``` + The network config file path is defaulted to `./network.conf` if not specified. ### 4. Running interactive wallet + To start the interactive wallet: + ```shell ./wallet ``` + or + ```shell ./wallet --config [config file path] ``` + The wallet config file path is defaulted to `./wallet.conf` if not specified. The following commands are supported by the interactive wallet: @@ -64,12 +79,16 @@ The following commands are supported by the interactive wallet: Use `help ` to see more information on each command. ### 5. Using the wallet without interactive shell + The wallet can also be use without the interactive shell + ```shell USAGE: wallet --no-shell [SUBCOMMAND] ``` + #### Example + ```shell sui@MystenLab release % ./wallet --no-shell addresses Showing 5 results. @@ -80,69 +99,6 @@ e91c22628771f1465947fe328ed47983b1a1013afbdd1c8ded2009ec4812054d 1be661a8d7157bffbb2cf7f652d270bbefb07b0b436aa10f2c8bdedcadcc22cb ``` -## Quickstart with Sui Prototype - -```bash -cargo build --release -cd target/release -rm -f *.json *.toml -rm -rf db* -killall server - -# 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 Sui committee. -for I in 1 2 3 4 -do - 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 100 user accounts, with 10 gas objects per account and 2000000 value each. -# * Private account states are stored in one local wallet `accounts.json`. -# * `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 10 --value-per-obj 2000000 initial_accounts.toml -# Start servers -for I in 1 2 3 4 -do - ./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 --address "$ACCOUNT1" -./client --committee committee.json --accounts accounts.json query-objects --address "$ACCOUNT2" - -# Get the first ObjectId for Account1 -ACCOUNT1_OBJECT1=`./client --committee committee.json --accounts accounts.json query-objects --address "$ACCOUNT1" | head -n 1 | awk -F: '{ print $1 }'` -# Pick the last item as gas object for Account1 -ACCOUNT1_GAS_OBJECT=`./client --committee committee.json --accounts accounts.json query-objects --address "$ACCOUNT1" | tail -n -1 | awk -F: '{ print $1 }'` -# Transfer object by ObjectID -./client --committee committee.json --accounts accounts.json transfer "$ACCOUNT1_OBJECT1" "$ACCOUNT1_GAS_OBJECT" --to "$ACCOUNT2" -# Query objects again -./client --committee committee.json --accounts accounts.json query-objects --address "$ACCOUNT1" -./client --committee committee.json --accounts accounts.json query-objects --address "$ACCOUNT2" - -# Launch local benchmark using all user accounts -./client --committee committee.json --accounts accounts.json benchmark - -# Inspect state of first account -grep "$ACCOUNT1" accounts.json - -# Kill servers -kill %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16 - -# Additional local benchmark -./bench - -cd ../.. -``` - ## References * Sui is based on FastPay: [FastPay: High-Performance Byzantine Fault Tolerant Settlement](https://arxiv.org/pdf/2003.11506.pdf) diff --git a/fastpay/Cargo.toml b/fastpay/Cargo.toml index 4e5b9aaf4a95c..d07524904c878 100644 --- a/fastpay/Cargo.toml +++ b/fastpay/Cargo.toml @@ -44,14 +44,6 @@ move-package = { git = "https://github.com/diem/move", rev="1e5e36cb1fede8073c86 move-core-types = { git = "https://github.com/diem/move", rev="1e5e36cb1fede8073c8688886a0943bb5bfe40d5", features=["address20"] } move-bytecode-verifier = { git = "https://github.com/diem/move", rev="1e5e36cb1fede8073c8688886a0943bb5bfe40d5" } -[[bin]] -name = "client" -path = "src/client.rs" - -[[bin]] -name = "server" -path = "src/server.rs" - [[bin]] name = "bench" path = "src/bench.rs" diff --git a/fastpay/src/client.rs b/fastpay/src/client.rs deleted file mode 100644 index 33d1499e24e4e..0000000000000 --- a/fastpay/src/client.rs +++ /dev/null @@ -1,854 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// SPDX-License-Identifier: Apache-2.0 - -#![deny(warnings)] - -use fastpay::config::*; -use fastpay_core::{authority_client::AuthorityClient, client::*}; -use fastx_network::{network::NetworkClient, transport}; -use fastx_types::{base_types::*, committee::Committee, messages::*, serialize::*}; -use move_core_types::transaction_argument::convert_txn_args; - -use bytes::Bytes; -use fastx_types::object::{Object, ObjectRead}; -use futures::stream::StreamExt; -use std::{ - collections::{BTreeMap, HashSet}, - path::PathBuf, - time::{Duration, Instant}, -}; -use structopt::StructOpt; -use tempfile::tempdir; -use tokio::runtime::Runtime; -use tracing::{subscriber::set_global_default, *}; -use tracing_subscriber::EnvFilter; - -fn make_authority_clients( - committee_config: &CommitteeConfig, - buffer_size: usize, - send_timeout: std::time::Duration, - recv_timeout: std::time::Duration, -) -> BTreeMap { - let mut authority_clients = BTreeMap::new(); - for config in &committee_config.authorities { - let config = config.clone(); - let client = AuthorityClient::new(NetworkClient::new( - config.host, - config.base_port, - buffer_size, - send_timeout, - recv_timeout, - )); - authority_clients.insert(config.address, client); - } - authority_clients -} - -fn make_authority_mass_clients( - committee_config: &CommitteeConfig, - buffer_size: usize, - send_timeout: std::time::Duration, - recv_timeout: std::time::Duration, -) -> Vec { - let mut authority_clients = Vec::new(); - for config in &committee_config.authorities { - let client = NetworkClient::new( - config.host.clone(), - config.base_port, - buffer_size, - send_timeout, - recv_timeout, - ); - authority_clients.push(client); - } - authority_clients -} - -async fn make_client_state_and_try_sync( - path: PathBuf, - accounts: &mut AccountsConfig, - committee_config: &CommitteeConfig, - address: FastPayAddress, - buffer_size: usize, - send_timeout: std::time::Duration, - recv_timeout: std::time::Duration, -) -> ClientState { - let mut c = make_client_state( - path, - accounts, - committee_config, - address, - buffer_size, - send_timeout, - recv_timeout, - ); - - // Force a sync - let _ = c.sync_client_state(); - c -} - -fn make_client_state( - path: PathBuf, - accounts: &mut AccountsConfig, - committee_config: &CommitteeConfig, - address: FastPayAddress, - buffer_size: usize, - send_timeout: std::time::Duration, - recv_timeout: std::time::Duration, -) -> ClientState { - let account = accounts.remove(&address).expect("Unknown account"); - let committee = Committee::new(committee_config.voting_rights()); - let authority_clients = - make_authority_clients(committee_config, buffer_size, send_timeout, recv_timeout); - ClientState::new( - path, - address, - Box::pin(account.key), - committee, - authority_clients, - account.certificates.clone(), - account.object_refs, - ) - .unwrap() -} - -/// Make one transfer order per account, up to `max_orders` transfers. -fn make_benchmark_transfer_orders( - accounts_config: &mut AccountsConfig, - max_orders: usize, -) -> (Vec, Vec<(ObjectID, Bytes)>) { - let mut orders = Vec::new(); - let mut serialized_orders = Vec::new(); - // TODO: deterministic sequence of orders to recover from interrupted benchmarks. - let mut next_recipient = get_key_pair().0; - for account in accounts_config.accounts_mut() { - let gas_object_id = *account.gas_object_ids.iter().next().unwrap(); - let gas_object_ref = *account.object_refs.get(&gas_object_id).unwrap(); - let object_ref = *account - .object_refs - .iter() - .find_map(|(key, object_ref)| { - if *key != gas_object_id { - Some(object_ref) - } else { - None - } - }) - .unwrap(); - let transfer = Transfer { - object_ref, - sender: account.address, - recipient: next_recipient, - gas_payment: gas_object_ref, - }; - debug!("Preparing transfer order: {:?}", transfer); - let (object_id, seq, digest) = object_ref; - account - .object_refs - .insert(object_id, (object_id, seq.increment(), digest)); - next_recipient = account.address; - let order = Order::new_transfer(transfer.clone(), &account.key); - orders.push(order.clone()); - let serialized_order = serialize_order(&order); - serialized_orders.push((object_id, serialized_order.into())); - if serialized_orders.len() >= max_orders { - break; - } - } - (orders, serialized_orders) -} - -/// Try to make certificates from orders and server configs -fn make_benchmark_certificates_from_orders_and_server_configs( - orders: Vec, - server_config: Vec<&str>, -) -> Vec<(ObjectID, Bytes)> { - let mut keys = Vec::new(); - for file in server_config { - let server_config = AuthorityServerConfig::read(file).expect("Fail to read server config"); - keys.push((server_config.authority.address, server_config.key)); - } - let committee = Committee::new(keys.iter().map(|(k, _)| (*k, 1)).collect()); - assert!( - keys.len() >= committee.quorum_threshold(), - "Not enough server configs were provided with --server-configs" - ); - let mut serialized_certificates = Vec::new(); - for order in orders { - let mut certificate = CertifiedOrder { - order: order.clone(), - signatures: Vec::new(), - }; - for i in 0..committee.quorum_threshold() { - let (pubx, secx) = keys.get(i).unwrap(); - let sig = Signature::new(&certificate.order.kind, secx); - certificate.signatures.push((*pubx, sig)); - } - let serialized_certificate = serialize_cert(&certificate); - serialized_certificates.push((*order.object_id(), serialized_certificate.into())); - } - serialized_certificates -} - -/// Try to aggregate votes into certificates. -fn make_benchmark_certificates_from_votes( - committee_config: &CommitteeConfig, - votes: Vec, -) -> Vec<(ObjectID, Bytes)> { - let committee = Committee::new(committee_config.voting_rights()); - let mut aggregators = BTreeMap::new(); - let mut certificates = Vec::new(); - let mut done_senders = HashSet::new(); - for vote in votes { - // We aggregate votes indexed by sender. - let address = *vote.order.sender(); - let object_id = *vote.order.object_id(); - if done_senders.contains(&address) { - continue; - } - debug!( - "Processing vote on {}'s transfer by {}", - encode_address(&address), - encode_address(&vote.authority) - ); - let value = vote.order; - let aggregator = aggregators - .entry(address) - .or_insert_with(|| SignatureAggregator::try_new(value, &committee).unwrap()); - match aggregator.append(vote.authority, vote.signature) { - Ok(Some(certificate)) => { - debug!("Found certificate: {:?}", certificate); - let buf = serialize_cert(&certificate); - certificates.push((object_id, buf.into())); - done_senders.insert(address); - } - Ok(None) => { - debug!("Added one vote"); - } - Err(error) => { - error!("Failed to aggregate vote: {}", error); - } - } - } - certificates -} - -/// Broadcast a bulk of requests to each authority. -async fn mass_broadcast_orders( - phase: &'static str, - committee_config: &CommitteeConfig, - buffer_size: usize, - send_timeout: std::time::Duration, - recv_timeout: std::time::Duration, - max_in_flight: u64, - orders: Vec<(ObjectID, Bytes)>, -) -> Vec { - let time_start = Instant::now(); - info!("Broadcasting {} {} orders", orders.len(), phase); - let authority_clients = - make_authority_mass_clients(committee_config, buffer_size, send_timeout, recv_timeout); - let mut streams = Vec::new(); - for client in authority_clients { - let mut requests = Vec::new(); - for (_object_id, buf) in &orders { - requests.push(buf.clone()); - } - streams.push(client.batch_send(requests, 1, max_in_flight)); - } - let responses = futures::stream::select_all(streams).concat().await; - let time_elapsed = time_start.elapsed(); - warn!( - "Received {} responses in {} ms.", - responses.len(), - time_elapsed.as_millis() - ); - warn!( - "Estimated server throughput: {} {} orders per sec", - (orders.len() as u128) * 1_000_000 / time_elapsed.as_micros(), - phase - ); - responses -} - -fn mass_update_recipients( - accounts_config: &mut AccountsConfig, - certificates: Vec<(ObjectID, Bytes)>, -) { - for (_object_id, buf) in certificates { - if let Ok(SerializedMessage::Cert(certificate)) = deserialize_message(&buf[..]) { - accounts_config.update_for_received_transfer(*certificate); - } - } -} - -fn deserialize_response(response: &[u8]) -> Option { - match deserialize_message(response) { - Ok(SerializedMessage::OrderResp(info)) => Some(*info), - Ok(SerializedMessage::Error(error)) => { - error!("Received error value: {}", error); - None - } - Ok(_) => { - error!("Unexpected return value"); - None - } - Err(error) => { - error!( - "Unexpected error: {} while deserializing {:?}", - error, response - ); - None - } - } -} -fn find_cached_owner_by_object_id( - account_config: &AccountsConfig, - object_id: &ObjectID, -) -> Option { - account_config - .find_account(object_id) - .map(|acc| acc.address) -} - -fn show_object_effects(order_effects: OrderEffects) { - if order_effects.status != ExecutionStatus::Success { - error!("Error publishing module: {:#?}", order_effects.status); - } - if !order_effects.created.is_empty() { - println!("Created Objects:"); - for (obj, _) in order_effects.created { - println!("{:?} {:?} {:?}", obj.0, obj.1, obj.2); - } - } - if !order_effects.mutated.is_empty() { - println!("Mutated Objects:"); - for (obj, _) in order_effects.mutated { - println!("{:?} {:?} {:?}", obj.0, obj.1, obj.2); - } - } - if !order_effects.deleted.is_empty() { - println!("Deleted Objects:"); - for obj in order_effects.deleted { - println!("{:?} {:?} {:?}", obj.0, obj.1, obj.2); - } - } -} - -fn parse_public_key_bytes(src: &str) -> Result { - decode_address_hex(src) -} - -#[derive(StructOpt)] -#[structopt( - name = "FastPay Client", - 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) - #[structopt(long)] - accounts: String, - - /// Sets the file describing the public configurations of all authorities - #[structopt(long)] - committee: String, - - /// Timeout for sending queries (us) - #[structopt(long, default_value = "4000000")] - send_timeout: u64, - - /// Timeout for receiving responses (us) - #[structopt(long, default_value = "4000000")] - recv_timeout: u64, - - /// Maximum size of datagrams received and sent (bytes) - #[structopt(long, default_value = transport::DEFAULT_MAX_DATAGRAM_SIZE)] - buffer_size: usize, - - /// Subcommands. Acceptable values are transfer, query_objects, benchmark, and create_accounts. - #[structopt(subcommand)] - cmd: ClientCommands, - - /// Path to directory for client DB - #[structopt(long)] - db_path: Option, -} - -#[derive(StructOpt)] -#[structopt(rename_all = "kebab-case")] -enum ClientCommands { - /// Get obj info - #[structopt(name = "get-obj-info")] - GetObjInfo { - /// Object ID of the object to fetch - obj_id: ObjectID, - - /// Deep inspection of object - #[structopt(long)] - deep: bool, - }, - - /// Publish Move modules - #[structopt(name = "publish")] - Publish { - /// Path to directory containing a Move package - path: String, - - /// ID of the gas object for gas payment, in 20 bytes Hex string - gas_object_id: ObjectID, - }, - - /// Call Move - #[structopt(name = "call")] - Call { path: String }, - - /// Transfer funds - #[structopt(name = "transfer")] - Transfer { - /// Recipient address - #[structopt(long, parse(try_from_str = parse_public_key_bytes))] - to: PublicKeyBytes, - - /// Object to transfer, in 20 bytes Hex string - object_id: ObjectID, - - /// ID of the gas object for gas payment, in 20 bytes Hex string - gas_object_id: ObjectID, - }, - - /// Obtain the Account Addresses - #[structopt(name = "query-accounts-addrs")] - QueryAccountAddresses {}, - - /// Obtain the Object Info - #[structopt(name = "query-objects")] - QueryObjects { - /// Address of the account - #[structopt(long, parse(try_from_str = parse_public_key_bytes))] - address: PublicKeyBytes, - }, - - /// Send one transfer per account in bulk mode - #[structopt(name = "benchmark")] - Benchmark { - /// Maximum number of requests in flight - #[structopt(long, default_value = "200")] - max_in_flight: u64, - - /// Use a subset of the accounts to generate N transfers - #[structopt(long)] - max_orders: Option, - - /// Use server configuration files to generate certificates (instead of aggregating received votes). - #[structopt(long)] - server_configs: Option>, - }, - - /// Create new user accounts with randomly generated object IDs - #[structopt(name = "create-accounts")] - CreateAccounts { - /// 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")] - value_per_obj: u64, - - /// Initial state config file path - #[structopt(name = "init-state-cfg")] - initial_state_config_path: String, - }, -} - -fn main() { - let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); - let subscriber_builder = - tracing_subscriber::fmt::Subscriber::builder().with_env_filter(env_filter); - let subscriber = subscriber_builder.with_writer(std::io::stderr).finish(); - set_global_default(subscriber).expect("Failed to set subscriber"); - - let options = ClientOpt::from_args(); - let send_timeout = Duration::from_micros(options.send_timeout); - let recv_timeout = Duration::from_micros(options.recv_timeout); - let accounts_config_path = &options.accounts; - let committee_config_path = &options.committee; - let buffer_size = options.buffer_size; - let client_db_path = options - .db_path - .map_or(tempdir().unwrap().into_path(), PathBuf::from); - - let mut accounts_config = - AccountsConfig::read_or_create(accounts_config_path).expect("Unable to read user accounts"); - let committee_config = - CommitteeConfig::read(committee_config_path).expect("Unable to read committee config file"); - - match options.cmd { - ClientCommands::Publish { - path, - gas_object_id, - } => { - let rt = Runtime::new().unwrap(); - rt.block_on(async move { - // Find owner of gas object - let owner = find_cached_owner_by_object_id(&accounts_config, &gas_object_id) - .expect("Cannot find owner for gas object"); - let mut client_state = make_client_state_and_try_sync( - client_db_path, - &mut accounts_config, - &committee_config, - owner, - buffer_size, - send_timeout, - recv_timeout, - ) - .await; - - let gas_obj_ref = *client_state - .object_refs() - .get(&gas_object_id) - .expect("Gas object not found"); - - let pub_resp = client_state.publish(path, gas_obj_ref).await; - - match pub_resp { - Ok(resp) => show_object_effects(resp.1), - Err(err) => error!("{:#?}", err), - } - }); - } - - ClientCommands::GetObjInfo { obj_id, deep } => { - // Pick the first (or any) account for use in finding obj info - let account = accounts_config - .nth_account(0) - .expect("Account config is invalid") - .address; - let rt = Runtime::new().unwrap(); - rt.block_on(async move { - // Fetch the object ref - let mut client_state = make_client_state_and_try_sync( - client_db_path, - &mut accounts_config, - &committee_config, - account, - buffer_size, - send_timeout, - recv_timeout, - ) - .await; - - // Fetch the object info for the object - if let Ok(ObjectRead::Exists(_, object)) = - client_state.get_object_info(obj_id).await - { - println!("Owner: {:#?}", object.owner); - println!("Version: {:#?}", object.version().value()); - println!("ID: {:#?}", object.id()); - println!("Readonly: {:#?}", object.is_read_only()); - println!( - "Type: {:#?}", - object - .data - .type_() - .map_or("Type Unwrap Failed".to_owned(), |type_| type_ - .module - .as_ident_str() - .to_string()) - ); - if deep { - println!("Full Info: {:#?}", object); - } - } else { - panic!("Object with id {:?} not found", obj_id); - } - }); - } - - ClientCommands::Call { path } => { - let config = MoveCallConfig::read(&path).unwrap(); - - let rt = Runtime::new().unwrap(); - rt.block_on(async move { - // Find owner of gas object - let owner = find_cached_owner_by_object_id(&accounts_config, &config.gas_object_id) - .expect("Cannot find owner for gas object"); - - let mut client_state = make_client_state_and_try_sync( - client_db_path, - &mut accounts_config, - &committee_config, - owner, - buffer_size, - send_timeout, - recv_timeout, - ) - .await; - - // Fetch the object info for the package - let package_obj_ref = client_state - .get_object_info(config.package_obj_id) - .await - .unwrap() - .reference() - .unwrap(); - - // Fetch the object info for the gas obj - let gas_obj_ref = *client_state - .object_refs() - .get(&config.gas_object_id) - .expect("Gas object not found"); - - // Fetch the objects for the object args - let mut object_args_refs = Vec::new(); - for obj_id in config.object_args_ids { - // Fetch the obj ref - let obj_info_ref = client_state - .get_object_info(obj_id) - .await - .unwrap() - .reference() - .unwrap_or_else(|_| panic!("Could not find object {:?}", obj_id)); - object_args_refs.push(obj_info_ref); - } - - let pure_args = convert_txn_args(&config.pure_args); - - let call_ret = client_state - .move_call( - package_obj_ref, - config.module, - config.function, - config.type_args, - gas_obj_ref, - object_args_refs, - pure_args, - config.gas_budget, - ) - .await - .unwrap(); - println!("Cert: {:?}", call_ret.0); - show_object_effects(call_ret.1); - }); - } - - ClientCommands::Transfer { - to, - object_id, - gas_object_id, - } => { - let rt = Runtime::new().unwrap(); - rt.block_on(async move { - let owner = find_cached_owner_by_object_id(&accounts_config, &gas_object_id) - .expect("Cannot find owner for gas object"); - let mut client_state = make_client_state_and_try_sync( - client_db_path.clone(), - &mut accounts_config, - &committee_config, - owner, - buffer_size, - send_timeout, - recv_timeout, - ) - .await; - info!("Starting transfer"); - let time_start = Instant::now(); - let (cert, _) = client_state - .transfer_object(object_id, gas_object_id, to) - .await - .unwrap(); - let time_total = time_start.elapsed().as_micros(); - info!("Transfer confirmed after {} us", time_total); - println!("{:?}", cert); - accounts_config.update_from_state(&client_state); - info!("Updating recipient's local balance"); - - let client2_db_path = tempdir().unwrap().into_path(); - // TODO: client should manage multiple addresses instead of each addr having DBs - // https://github.com/MystenLabs/fastnft/issues/332 - let mut recipient_client_state = make_client_state_and_try_sync( - client2_db_path, - &mut accounts_config, - &committee_config, - to, - buffer_size, - send_timeout, - recv_timeout, - ) - .await; - recipient_client_state.sync_client_state().await.unwrap(); - accounts_config.update_from_state(&recipient_client_state); - accounts_config - .write(accounts_config_path) - .expect("Unable to write user accounts"); - info!("Saved user account states"); - }); - } - - ClientCommands::QueryAccountAddresses {} => { - let addr_strings: Vec<_> = accounts_config - .addresses() - .into_iter() - .map(|addr| format!("{:X}", addr).trim_end_matches('=').to_string()) - .collect(); - let addr_text = addr_strings.join("\n"); - println!("{}", addr_text); - } - - ClientCommands::QueryObjects { address } => { - let rt = Runtime::new().unwrap(); - rt.block_on(async move { - let client_state = make_client_state_and_try_sync( - client_db_path, - &mut accounts_config, - &committee_config, - address, - buffer_size, - send_timeout, - recv_timeout, - ) - .await; - - let object_refs = client_state.object_refs(); - - accounts_config.update_from_state(&client_state); - accounts_config - .write(accounts_config_path) - .expect("Unable to write user accounts"); - - for (obj_id, object_ref) in object_refs { - println!("{}: {:?}", obj_id, object_ref); - } - }); - } - - ClientCommands::Benchmark { - max_in_flight, - max_orders, - server_configs, - } => { - let max_orders = max_orders.unwrap_or_else(|| accounts_config.num_accounts()); - - let rt = Runtime::new().unwrap(); - rt.block_on(async move { - warn!("Starting benchmark phase 1 (transfer orders)"); - let (orders, serialize_orders) = - make_benchmark_transfer_orders(&mut accounts_config, max_orders); - let responses = mass_broadcast_orders( - "transfer", - &committee_config, - buffer_size, - send_timeout, - recv_timeout, - max_in_flight, - serialize_orders, - ) - .await; - let votes: Vec<_> = responses - .into_iter() - .filter_map(|buf| { - deserialize_response(&buf[..]).and_then(|info| info.signed_order) - }) - .collect(); - info!("Received {} valid votes.", votes.len()); - - warn!("Starting benchmark phase 2 (confirmation orders)"); - let certificates = if let Some(files) = server_configs { - warn!("Using server configs provided by --server-configs"); - let files = files.iter().map(AsRef::as_ref).collect(); - make_benchmark_certificates_from_orders_and_server_configs(orders, files) - } else { - warn!("Using committee config"); - make_benchmark_certificates_from_votes(&committee_config, votes) - }; - let responses = mass_broadcast_orders( - "confirmation", - &committee_config, - buffer_size, - send_timeout, - recv_timeout, - max_in_flight, - certificates.clone(), - ) - .await; - let mut confirmed = HashSet::new(); - let num_valid = - responses - .iter() - .fold(0, |acc, buf| match deserialize_response(&buf[..]) { - Some(OrderInfoResponse { - signed_order: Some(order), - .. - }) => { - confirmed.insert(*order.order.object_id()); - acc + 1 - } - _ => acc, - }); - warn!( - "Received {} valid confirmations for {} transfers.", - num_valid, - confirmed.len() - ); - - warn!("Updating local state of user accounts"); - // Make sure that the local balances are accurate so that future - // balance checks of the non-mass client pass. - mass_update_recipients(&mut accounts_config, certificates); - accounts_config - .write(accounts_config_path) - .expect("Unable to write user accounts"); - info!("Saved client account state"); - }); - } - - ClientCommands::CreateAccounts { - num, - gas_objs_per_account, - // TODO: Integrate gas logic with https://github.com/MystenLabs/fastnft/pull/97 - value_per_obj, - initial_state_config_path, - } => { - let num_of_addresses: u32 = num; - let mut init_state_cfg: InitialStateConfig = InitialStateConfig::new(); - - for _ in 0..num_of_addresses { - let (address, key) = get_key_pair(); - let mut objects = Vec::new(); - let mut object_refs = Vec::new(); - let mut gas_object_ids = Vec::new(); - for _ in 0..gas_objs_per_account { - let object = Object::with_id_owner_gas_for_testing( - ObjectID::random(), - SequenceNumber::default(), - address, - value_per_obj, - ); - object_refs.push(object.to_object_reference()); - gas_object_ids.push(object.id()); - objects.push(object) - } - - let account = UserAccount::new(address, key, object_refs, gas_object_ids); - - init_state_cfg.config.push(InitialStateConfigEntry { - address: account.address, - objects, - }); - - 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/server.rs b/fastpay/src/server.rs deleted file mode 100644 index 2486460e18b63..0000000000000 --- a/fastpay/src/server.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Facebook, Inc. and its affiliates. -// SPDX-License-Identifier: Apache-2.0 - -#![deny(warnings)] - -use fastpay::config::*; -use fastpay_core::{authority::*, authority_server::AuthorityServer}; -use fastx_network::transport; -use fastx_types::{base_types::*, committee::Committee}; - -use futures::future::join_all; -use std::path::Path; -use std::sync::Arc; -use structopt::StructOpt; -use tokio::runtime::Runtime; -use tracing::subscriber::set_global_default; -use tracing::*; -use tracing_subscriber::EnvFilter; - -#[allow(clippy::too_many_arguments)] -fn make_server( - local_ip_addr: &str, - server_config_path: &str, - committee_config_path: &str, - initial_accounts_config_path: &str, - buffer_size: usize, -) -> AuthorityServer { - let server_config = - AuthorityServerConfig::read(server_config_path).expect("Fail to read server config"); - let committee_config = - CommitteeConfig::read(committee_config_path).expect("Fail to read committee config"); - let initial_accounts_config = InitialStateConfig::read(initial_accounts_config_path) - .expect("Fail to read initial account config"); - - let committee = Committee::new(committee_config.voting_rights()); - - let store = Arc::new(AuthorityStore::open( - Path::new(&server_config.authority.database_path), - None, - )); - - // Load initial states - let rt = Runtime::new().unwrap(); - - let state = rt.block_on(async { - let state = AuthorityState::new_with_genesis_modules( - committee, - server_config.authority.address, - Box::pin(server_config.key), - store, - ) - .await; - for initial_state_cfg_entry in initial_accounts_config.config { - for object in initial_state_cfg_entry.objects { - state.init_order_lock(object.to_object_reference()).await; - state.insert_object(object).await; - } - } - state - }); - - AuthorityServer::new( - local_ip_addr.to_string(), - server_config.authority.base_port, - buffer_size, - state, - ) -} - -#[derive(StructOpt)] -#[structopt( - name = "FastPay Server", - about = "A byzantine fault tolerant payments sidechain with low-latency finality and high throughput" -)] -struct ServerOpt { - /// Path to the file containing the server configuration of this FastPay authority (including its secret key) - #[structopt(long)] - server: String, - - /// Subcommands. Acceptable values are run and generate. - #[structopt(subcommand)] - cmd: ServerCommands, -} - -#[derive(StructOpt)] -enum ServerCommands { - /// Runs the FastPay authority") - #[structopt(name = "run")] - Run { - /// Maximum size of datagrams received and sent (bytes) - #[structopt(long, default_value = transport::DEFAULT_MAX_DATAGRAM_SIZE)] - buffer_size: usize, - - /// Path to the file containing the public description of all authorities in this FastPay committee - #[structopt(long)] - committee: String, - - /// Path to the file describing the initial user accounts - #[structopt(long)] - initial_accounts: String, - }, - - /// Generate a new server configuration and output its public description - #[structopt(name = "generate")] - Generate { - /// Sets the public name of the host - #[structopt(long)] - host: String, - - /// Sets the port - #[structopt(long)] - port: u16, - - /// Sets the path to the database folder - #[structopt(long)] - database_path: String, - }, -} - -fn main() { - let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); - let subscriber_builder = - tracing_subscriber::fmt::Subscriber::builder().with_env_filter(env_filter); - let subscriber = subscriber_builder.with_writer(std::io::stderr).finish(); - set_global_default(subscriber).expect("Failed to set subscriber"); - - let options = ServerOpt::from_args(); - - let server_config_path = &options.server; - - match options.cmd { - ServerCommands::Run { - buffer_size, - committee, - initial_accounts, - } => { - // Run the server - - let server = make_server( - "0.0.0.0", // Allow local IP address to be different from the public one. - server_config_path, - &committee, - &initial_accounts, - buffer_size, - ); - - let rt = Runtime::new().unwrap(); - let mut handles = Vec::new(); - - handles.push(async move { - let spawned_server = match server.spawn().await { - Ok(server) => server, - Err(err) => { - error!("Failed to start server: {}", err); - return; - } - }; - if let Err(err) = spawned_server.join().await { - error!("Server ended with an error: {}", err); - } - }); - - rt.block_on(join_all(handles)); - } - - ServerCommands::Generate { - host, - port, - database_path, - } => { - let (address, key) = get_key_pair(); - let authority = AuthorityConfig { - address, - host, - base_port: port, - database_path, - }; - let server = AuthorityServerConfig { authority, key }; - server - .write(server_config_path) - .expect("Unable to write server config file"); - info!("Wrote server config file"); - server.authority.print(); - } - } -} diff --git a/fastpay/src/wallet_commands.rs b/fastpay/src/wallet_commands.rs index ae309fce1bb35..25de5c3c993f5 100644 --- a/fastpay/src/wallet_commands.rs +++ b/fastpay/src/wallet_commands.rs @@ -383,8 +383,6 @@ impl WalletContext { Box::pin(client_info.key_pair.copy()), committee, authority_clients, - BTreeMap::new(), - BTreeMap::new(), ) } diff --git a/fastpay_core/src/client.rs b/fastpay_core/src/client.rs index 50e0fedb58d31..c5e4452b8777b 100644 --- a/fastpay_core/src/client.rs +++ b/fastpay_core/src/client.rs @@ -107,19 +107,13 @@ impl ClientState { secret: StableSyncSigner, committee: Committee, authority_clients: BTreeMap, - certificates: BTreeMap, - object_refs: BTreeMap, ) -> Result { - let client_state = ClientState { + Ok(ClientState { address, secret, authorities: AuthorityAggregator::new(committee, authority_clients), store: ClientStore::new(path), - }; - - // Backfill the DB - client_state.store.populate(object_refs, certificates)?; - Ok(client_state) + }) } pub fn address(&self) -> FastPayAddress { diff --git a/fastpay_core/src/unit_tests/client_tests.rs b/fastpay_core/src/unit_tests/client_tests.rs index f048ce8c7b097..3cdf5ac553d7b 100644 --- a/fastpay_core/src/unit_tests/client_tests.rs +++ b/fastpay_core/src/unit_tests/client_tests.rs @@ -337,8 +337,6 @@ fn make_client( pb_secret, committee, authority_clients, - BTreeMap::new(), - BTreeMap::new(), ) .unwrap() }