From 7eb0760fcd34d9299616df33c424fea500d902c6 Mon Sep 17 00:00:00 2001 From: ade <93547199+oxade@users.noreply.github.com> Date: Sat, 15 Jan 2022 15:42:45 -0500 Subject: [PATCH] Feature/support move calls from client (#157) * Support move Calls from client and added object fetching from authorities --- README.md | 20 +- fastpay/Cargo.toml | 8 + fastpay/src/client.rs | 191 +++++++++++++-- fastpay/src/config.rs | 80 +++++- fastpay_core/clippy.toml | 1 + fastpay_core/src/authority.rs | 7 +- fastpay_core/src/client.rs | 229 +++++++++++++++++- fastx_types/Cargo.toml | 2 + fastx_types/src/base_types.rs | 56 ++++- fastx_types/src/error.rs | 4 + fastx_types/src/messages.rs | 7 +- fastx_types/src/object.rs | 6 +- fastx_types/src/unit_tests/serialize_tests.rs | 22 +- 13 files changed, 562 insertions(+), 71 deletions(-) create mode 100644 fastpay_core/clippy.toml diff --git a/README.md b/README.md index f6effce1fc351..c311a6e4e0d69 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,11 @@ do ./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 4 gas objects per account and 200 value each. +# 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 4 --value-per-per-obj 200 initial_accounts.toml +--gas-objs-per-account 10 --value-per-per-obj 2000000 initial_accounts.toml # Start servers for I in 1 2 3 4 do @@ -45,18 +45,18 @@ done # 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" +./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 "$ACCOUNT1" | head -n 1 | awk -F: '{ print $1 }'` - +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" --from "$ACCOUNT1" --to "$ACCOUNT2" - +./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 "$ACCOUNT1" -./client --committee committee.json --accounts accounts.json query-objects "$ACCOUNT2" +./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 diff --git a/fastpay/Cargo.toml b/fastpay/Cargo.toml index 6713bb7861e5d..30b53909581f8 100644 --- a/fastpay/Cargo.toml +++ b/fastpay/Cargo.toml @@ -24,12 +24,20 @@ toml = "0.5.8" strum = "0.23.0" strum_macros = "0.23.1" num_cpus = "1.13.1" +base64 = "0.13.0" +ed25519-dalek = { version = "1.0.1", features = ["batch", "serde"] } rocksdb = "0.17.0" +hex = "0.4.3" +bcs = "0.1.3" fastpay_core = { path = "../fastpay_core" } fastx-adapter = { path = "../fastx_programmability/adapter" } fastx-types = { path = "../fastx_types" } +move-package = { git = "https://github.com/diem/move", rev="98ed299a7e3a9223019c9bdf4dd92fea9faef860" } +move-core-types = { git = "https://github.com/diem/move", rev="98ed299a7e3a9223019c9bdf4dd92fea9faef860" } +move-bytecode-verifier = { git = "https://github.com/diem/move", rev="98ed299a7e3a9223019c9bdf4dd92fea9faef860" } + [[bin]] name = "client" path = "src/client.rs" diff --git a/fastpay/src/client.rs b/fastpay/src/client.rs index 88ae273888fce..8a61423e1455c 100644 --- a/fastpay/src/client.rs +++ b/fastpay/src/client.rs @@ -6,6 +6,7 @@ use fastpay::{config::*, network, transport}; use fastpay_core::client::*; use fastx_types::{base_types::*, committee::Committee, messages::*, serialize::*}; +use move_core_types::transaction_argument::convert_txn_args; use bytes::Bytes; use futures::stream::StreamExt; @@ -283,6 +284,33 @@ fn deserialize_response(response: &[u8]) -> Option { } } } +fn find_cached_owner_by_object_id( + account_config: &AccountsConfig, + object_id: ObjectID, +) -> Option<&PublicKeyBytes> { + account_config + .find_account(&object_id) + .map(|acc| &acc.address) +} + +fn show_object_effects(order_effects: OrderEffects) { + 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( @@ -319,22 +347,30 @@ struct ClientOpt { #[derive(StructOpt)] #[structopt(rename_all = "kebab-case")] enum ClientCommands { + /// Get obj info + #[structopt(name = "get-obj-info")] + GetObjInfo { obj_id: ObjectID }, + + /// Call Move + #[structopt(name = "call")] + Call { path: String }, + /// Transfer funds #[structopt(name = "transfer")] Transfer { /// Sending address (must be one of our accounts) - #[structopt(long)] - from: String, + #[structopt(long, parse(try_from_str = parse_public_key_bytes))] + from: PublicKeyBytes, /// Recipient address - #[structopt(long)] - to: String, + #[structopt(long, parse(try_from_str = parse_public_key_bytes))] + to: PublicKeyBytes, /// Object to transfer, in 20 bytes Hex string - object_id: String, + object_id: ObjectID, /// ID of the gas object for gas payment, in 20 bytes Hex string - gas_object_id: String, + gas_object_id: ObjectID, }, /// Obtain the Account Addresses @@ -345,7 +381,8 @@ enum ClientCommands { #[structopt(name = "query-objects")] QueryObjects { /// Address of the account - address: String, + #[structopt(long, parse(try_from_str = parse_public_key_bytes))] + address: PublicKeyBytes, }, /// Send one transfer per account in bulk mode @@ -389,7 +426,6 @@ enum ClientCommands { fn main() { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); 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; @@ -402,23 +438,136 @@ fn main() { CommitteeConfig::read(committee_config_path).expect("Unable to read committee config file"); match options.cmd { + ClientCommands::GetObjInfo { obj_id } => { + // 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; + // Fetch the object ref + let mut client_state = make_client_state( + &accounts_config, + &committee_config, + account, + buffer_size, + send_timeout, + recv_timeout, + ); + let rt = Runtime::new().unwrap(); + rt.block_on(async move { + // Fetch the object info for the object + let obj_info_req = ObjectInfoRequest { + object_id: obj_id, + request_sequence_number: None, + request_received_transfers_excluding_first_nth: None, + }; + let obj_info = client_state.get_object_info(obj_info_req).await.unwrap(); + println!("Owner: {:#?}", obj_info.object.owner); + println!("Version: {:#?}", obj_info.object.version().value()); + println!("ID: {:#?}", obj_info.object.id()); + println!("Readonly: {:#?}", obj_info.object.is_read_only()); + println!( + "Type: {:#?}", + obj_info + .object + .data + .type_() + .map_or("Type Unwrap Failed".to_owned(), |type_| type_ + .module + .as_ident_str() + .to_string()) + ); + }); + } + + ClientCommands::Call { path } => { + let config = MoveCallConfig::read(&path).unwrap(); + // 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( + &accounts_config, + &committee_config, + *owner, + buffer_size, + send_timeout, + recv_timeout, + ); + + let rt = Runtime::new().unwrap(); + rt.block_on(async move { + // Fetch the object info for the package + let package_obj_info_req = ObjectInfoRequest { + object_id: config.package_obj_id, + request_sequence_number: None, + request_received_transfers_excluding_first_nth: None, + }; + let package_obj_info = client_state + .get_object_info(package_obj_info_req) + .await + .unwrap(); + let package_obj_ref = package_obj_info.object.to_object_reference(); + + // Fetch the object info for the gas obj + let gas_obj_info_req = ObjectInfoRequest { + object_id: config.gas_object_id, + request_sequence_number: None, + request_received_transfers_excluding_first_nth: None, + }; + + let gas_obj_info = client_state + .get_object_info(gas_obj_info_req) + .await + .unwrap(); + let gas_obj_ref = gas_obj_info.object.to_object_reference(); + + // 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_req = ObjectInfoRequest { + object_id: obj_id, + request_sequence_number: None, + request_received_transfers_excluding_first_nth: None, + }; + + let obj_info = client_state.get_object_info(obj_info_req).await.unwrap(); + object_args_refs.push(obj_info.object.to_object_reference()); + } + + 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 { from, to, object_id, gas_object_id, } => { - let sender = decode_address(&from).expect("Failed to decode sender's address"); - let recipient = decode_address(&to).expect("Failed to decode recipient's address"); - let object_id = ObjectID::from_hex_literal(&object_id).unwrap(); - let gas_object_id = ObjectID::from_hex_literal(&gas_object_id).unwrap(); - let rt = Runtime::new().unwrap(); rt.block_on(async move { let mut client_state = make_client_state( &accounts_config, &committee_config, - sender, + from, buffer_size, send_timeout, recv_timeout, @@ -426,7 +575,7 @@ fn main() { info!("Starting transfer"); let time_start = Instant::now(); let cert = client_state - .transfer_object(object_id, gas_object_id, recipient) + .transfer_object(object_id, gas_object_id, to) .await .unwrap(); let time_total = time_start.elapsed().as_micros(); @@ -437,7 +586,7 @@ fn main() { let mut recipient_client_state = make_client_state( &accounts_config, &committee_config, - recipient, + to, buffer_size, send_timeout, recv_timeout, @@ -455,21 +604,19 @@ fn main() { let addr_strings: Vec<_> = accounts_config .addresses() .into_iter() - .map(|addr| format!("{:?}", addr).trim_end_matches('=').to_string()) + .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 user_address = decode_address(&address).expect("Failed to decode address"); - let rt = Runtime::new().unwrap(); rt.block_on(async move { let client_state = make_client_state( &accounts_config, &committee_config, - user_address, + address, buffer_size, send_timeout, recv_timeout, @@ -483,7 +630,7 @@ fn main() { .expect("Unable to write user accounts"); for (obj_id, seq_num) in objects_ids { - println!("{:#x}: {:?}", obj_id, seq_num); + println!("{}: {:?}", obj_id, seq_num); } }); } @@ -543,7 +690,7 @@ fn main() { .iter() .fold(0, |acc, buf| match deserialize_response(&buf[..]) { Some(info) => { - confirmed.insert(info.object_id); + confirmed.insert(info.object.id()); acc + 1 } None => acc, diff --git a/fastpay/src/config.rs b/fastpay/src/config.rs index 5e5815fdc1e44..cfbb59b389b75 100644 --- a/fastpay/src/config.rs +++ b/fastpay/src/config.rs @@ -8,6 +8,8 @@ use fastx_types::{ messages::{Address, CertifiedOrder, OrderKind}, }; +use move_core_types::language_storage::TypeTag; +use move_core_types::{identifier::Identifier, transaction_argument::TransactionArgument}; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, @@ -15,13 +17,12 @@ use std::{ io::{BufReader, BufWriter, Write}, iter::FromIterator, }; - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AuthorityConfig { pub network_protocol: NetworkProtocol, #[serde( - serialize_with = "address_as_base64", - deserialize_with = "address_from_base64" + serialize_with = "address_as_hex", + deserialize_with = "address_from_hex" )] pub address: FastPayAddress, pub host: String, @@ -94,8 +95,8 @@ impl CommitteeConfig { #[derive(Serialize, Deserialize)] pub struct UserAccount { #[serde( - serialize_with = "address_as_base64", - deserialize_with = "address_from_base64" + serialize_with = "address_as_hex", + deserialize_with = "address_from_hex" )] pub address: FastPayAddress, pub key: KeyPair, @@ -124,6 +125,66 @@ impl UserAccount { } } +pub fn transaction_args_from_str<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::de::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + + let tokens = s.split(','); + + let result: Result, _> = tokens + .map(|tok| move_core_types::parser::parse_transaction_argument(tok.trim())) + .collect(); + result.map_err(serde::de::Error::custom) +} +#[derive(Serialize, Deserialize)] +pub struct MoveCallConfig { + /// Object ID of the package, which contains the module + pub package_obj_id: ObjectID, + /// The name of the module in the package + pub module: Identifier, + /// Function name in module + pub function: Identifier, + /// Function name in module + pub type_args: Vec, + /// Object args object IDs + pub object_args_ids: Vec, + + /// Pure arguments to the functions, which conform to move_core_types::transaction_argument + /// Special case formatting rules: + /// Use one string with CSV token embedded, for example "54u8,0x43" + /// When specifying FastX addresses, specify as vector. Example x\"01FE4E6F9F57935C5150A486B5B78AC2B94E2C5CD9352C132691D99B3E8E095C\" + #[serde(deserialize_with = "transaction_args_from_str")] + pub pure_args: Vec, + /// ID of the gas object for gas payment, in 20 bytes Hex string + pub gas_object_id: ObjectID, + /// Gas budget for this call + pub gas_budget: u64, +} + +impl MoveCallConfig { + pub fn read(path: &str) -> Result { + let file = OpenOptions::new() + .create(true) + .write(true) + .read(true) + .open(path)?; + let reader = BufReader::new(file); + Ok(serde_json::from_reader(reader)?) + } + + pub fn write(&self, path: &str) -> Result<(), std::io::Error> { + let file = OpenOptions::new().write(true).open(path)?; + let mut writer = BufWriter::new(file); + serde_json::to_writer(&mut writer, self)?; + writer.write_all(b"\n")?; + Ok(()) + } +} + pub struct AccountsConfig { accounts: BTreeMap, } @@ -141,6 +202,15 @@ impl AccountsConfig { self.accounts.len() } + pub fn nth_account(&self, n: usize) -> Option<&UserAccount> { + self.accounts.values().nth(n) + } + + pub fn find_account(&self, object_id: &ObjectID) -> Option<&UserAccount> { + self.accounts + .values() + .find(|acc| acc.object_ids.contains_key(object_id)) + } pub fn accounts_mut(&mut self) -> impl Iterator { self.accounts.values_mut() } diff --git a/fastpay_core/clippy.toml b/fastpay_core/clippy.toml new file mode 100644 index 0000000000000..da879ebe9c749 --- /dev/null +++ b/fastpay_core/clippy.toml @@ -0,0 +1 @@ +too-many-arguments-threshold = 20 \ No newline at end of file diff --git a/fastpay_core/src/authority.rs b/fastpay_core/src/authority.rs index 63db31600fbc6..2bcf7dfd93241 100644 --- a/fastpay_core/src/authority.rs +++ b/fastpay_core/src/authority.rs @@ -398,7 +398,7 @@ impl AuthorityState { self._database.get_order_info(transaction_digest) } - /// Make an information summary of an object to help clients + /// Make an info summary of an object, and include the raw object for clients async fn make_object_info( &self, object_id: ObjectID, @@ -411,12 +411,9 @@ impl AuthorityState { .or::(Ok(None))?; Ok(ObjectInfoResponse { - object_id: object.id(), - owner: object.owner, - next_sequence_number: object.version(), requested_certificate, pending_confirmation: lock, - requested_received_transfers: Vec::new(), + object, }) } diff --git a/fastpay_core/src/client.rs b/fastpay_core/src/client.rs index 58c4d8ef18ac0..0d6821de3a5cb 100644 --- a/fastpay_core/src/client.rs +++ b/fastpay_core/src/client.rs @@ -7,6 +7,8 @@ use fastx_types::{ base_types::*, committee::Committee, error::FastPayError, fp_ensure, messages::*, }; use futures::{future, StreamExt, TryFutureExt}; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::TypeTag; use rand::seq::SliceRandom; use std::collections::{btree_map, BTreeMap, BTreeSet, HashMap}; use std::time::Duration; @@ -98,10 +100,28 @@ pub trait Client { /// Get all object we own. fn get_owned_objects(&self) -> AsyncResult<'_, Vec, anyhow::Error>; + + /// Call move functions in the module in the given package, with args supplied + fn move_call( + &mut self, + package_object_ref: ObjectRef, + module: Identifier, + function: Identifier, + type_arguments: Vec, + gas_object_ref: ObjectRef, + object_arguments: Vec, + pure_arguments: Vec>, + gas_budget: u64, + ) -> AsyncResult<'_, (CertifiedOrder, OrderEffects), anyhow::Error>; + + /// Get the object information + fn get_object_info( + &mut self, + object_info_req: ObjectInfoRequest, + ) -> AsyncResult<'_, ObjectInfoResponse, anyhow::Error>; } impl ClientState { - #[allow(clippy::too_many_arguments)] pub fn new( address: FastPayAddress, secret: KeyPair, @@ -264,7 +284,7 @@ where let fut = client.handle_object_info_request(request.clone()); async move { match fut.await { - Ok(info) => Some((*name, info.next_sequence_number)), + Ok(info) => Some((*name, info.object.version())), _ => None, } } @@ -294,7 +314,7 @@ where let fut = client.handle_object_info_request(request.clone()); async move { match fut.await { - Ok(info) => Some((*name, Some((info.owner, info.next_sequence_number)))), + Ok(info) => Some((*name, Some((info.object.owner, info.object.version())))), _ => None, } } @@ -398,7 +418,7 @@ where }; let response = client.handle_object_info_request(request).await?; - let current_sequence_number = response.next_sequence_number; + let current_sequence_number = response.object.version(); // Download each missing certificate in reverse order using the downloader. let mut missing_certificates = Vec::new(); let mut number = target_sequence_number.decrement(); @@ -649,6 +669,178 @@ where } Err(FastPayError::ErrorWhileRequestingInformation) } + + fn update_objects_from_order_info( + &mut self, + order_info_resp: OrderInfoResponse, + ) -> Result<(), FastPayError> { + // TODO: use the digest and mutated objects + // https://github.com/MystenLabs/fastnft/issues/175 + if let Some(v) = order_info_resp.signed_effects { + for (obj_id, _, _) in v.effects.deleted { + self.object_ids.remove(&obj_id); + } + Ok(()) + } else { + Err(FastPayError::ErrorWhileRequestingInformation) + } + } + /// TODO/TBD: Formalize how to handle failed transaction orders in FastX + /// https://github.com/MystenLabs/fastnft/issues/174 + async fn communicate_transaction_order( + &mut self, + order: Order, + ) -> Result { + let committee = self.committee.clone(); + + let votes = self + .communicate_with_quorum(|name, client| { + let order = order.clone(); + let committee = &committee; + Box::pin(async move { + let result = client.handle_order(order).await; + let s_order = result + .as_ref() + .map(|order_info_resp| order_info_resp.signed_order.as_ref()); + if let Ok(Some(signed_order)) = s_order { + fp_ensure!( + signed_order.authority == name, + FastPayError::ErrorWhileProcessingTransactionOrder + ); + signed_order.check(committee)?; + Ok(signed_order.clone()) + } else { + Err(FastPayError::ErrorWhileProcessingTransactionOrder) + } + }) + }) + .await?; + + let certificate = CertifiedOrder { + order: order.clone(), + signatures: votes + .iter() + .map(|vote| (vote.authority, vote.signature)) + .collect(), + }; + Ok(certificate) + } + + /// TODO/TBD: Formalize how to handle failed transaction orders in FastX + /// https://github.com/MystenLabs/fastnft/issues/174 + async fn communicate_confirmation_order( + &mut self, + cert_order: &CertifiedOrder, + ) -> Result { + let committee = self.committee.clone(); + + let votes = self + .communicate_with_quorum(|name, client| { + let certified_order = ConfirmationOrder { + certificate: cert_order.clone(), + }; + let committee = &committee; + Box::pin(async move { + let result = client.handle_confirmation_order(certified_order).await; + + if let Ok(Some(signed_order)) = result + .as_ref() + .map(|order_info_resp| order_info_resp.signed_order.as_ref()) + { + fp_ensure!( + signed_order.authority == name, + FastPayError::ErrorWhileProcessingConfirmationOrder + ); + signed_order.check(committee)?; + result + } else { + Err(FastPayError::ErrorWhileProcessingConfirmationOrder) + } + }) + }) + .await?; + + votes + .get(0) + .cloned() + .ok_or_else(|| anyhow::anyhow!("No valid confirmation order votes")) + } + + /// Execute call order + /// Need improvement and decoupling from transfer logic + /// TODO: https://github.com/MystenLabs/fastnft/issues/173 + async fn execute_call( + &mut self, + order: Order, + ) -> Result<(CertifiedOrder, OrderEffects), anyhow::Error> { + // Transaction order + let new_certificate = self.communicate_transaction_order(order).await?; + + // TODO: update_certificates relies on orders having sequence numbers/object IDs , which fails for calls with obj args + // https://github.com/MystenLabs/fastnft/issues/173 + + // Confirmation + let order_info = self + .communicate_confirmation_order(&new_certificate) + .await?; + + // Update local object view + self.update_objects_from_order_info(order_info.clone())?; + + let cert = order_info + .certified_order + .ok_or(FastPayError::ErrorWhileProcessingTransferOrder)?; + let effects = order_info + .signed_effects + .ok_or(FastPayError::ErrorWhileProcessingTransferOrder)? + .effects; + + Ok((cert, effects)) + } + + async fn call( + &mut self, + package_object_ref: ObjectRef, + module: Identifier, + function: Identifier, + type_arguments: Vec, + gas_object_ref: ObjectRef, + object_arguments: Vec, + pure_arguments: Vec>, + gas_budget: u64, + ) -> Result<(CertifiedOrder, OrderEffects), anyhow::Error> { + let move_call_order = Order::new_move_call( + self.address, + package_object_ref, + module, + function, + type_arguments, + gas_object_ref, + object_arguments, + pure_arguments, + gas_budget, + &self.secret, + ); + + Ok(self.execute_call(move_call_order).await?) + } + + async fn get_object_info_execute( + &mut self, + object_info_req: ObjectInfoRequest, + ) -> Result { + let votes = self + .communicate_with_quorum(|_, client| { + let req = object_info_req.clone(); + Box::pin(async move { client.handle_object_info_request(req).await }) + }) + .await?; + + votes + .get(0) + .cloned() + .ok_or_else(|| anyhow::anyhow!("No valid confirmation order votes")) + } } impl Client for ClientState @@ -777,4 +969,33 @@ where fn get_owned_objects(&self) -> AsyncResult<'_, Vec, anyhow::Error> { Box::pin(async move { Ok(self.object_ids.keys().copied().collect()) }) } + + fn move_call( + &mut self, + package_object_ref: ObjectRef, + module: Identifier, + function: Identifier, + type_arguments: Vec, + gas_object_ref: ObjectRef, + object_arguments: Vec, + pure_arguments: Vec>, + gas_budget: u64, + ) -> AsyncResult<'_, (CertifiedOrder, OrderEffects), anyhow::Error> { + Box::pin(self.call( + package_object_ref, + module, + function, + type_arguments, + gas_object_ref, + object_arguments, + pure_arguments, + gas_budget, + )) + } + fn get_object_info( + &mut self, + object_info_req: ObjectInfoRequest, + ) -> AsyncResult<'_, ObjectInfoResponse, anyhow::Error> { + Box::pin(self.get_object_info_execute(object_info_req)) + } } diff --git a/fastx_types/Cargo.toml b/fastx_types/Cargo.toml index 9a828bd71ceba..77b921e55b31b 100644 --- a/fastx_types/Cargo.toml +++ b/fastx_types/Cargo.toml @@ -18,6 +18,8 @@ ed25519-dalek = { version = "1.0.1", features = ["batch", "serde"] } serde-name = "0.2.0" sha3 = "0.9" thiserror = "1.0.30" +hex = "0.4.3" + move-binary-format = { git = "https://github.com/diem/move", rev="98ed299a7e3a9223019c9bdf4dd92fea9faef860" } move-core-types = { git = "https://github.com/diem/move", rev="98ed299a7e3a9223019c9bdf4dd92fea9faef860" } diff --git a/fastx_types/src/base_types.rs b/fastx_types/src/base_types.rs index 8b13a3f1161d8..6bc090d6151b6 100644 --- a/fastx_types/src/base_types.rs +++ b/fastx_types/src/base_types.rs @@ -186,6 +186,60 @@ pub fn get_key_pair() -> (FastPayAddress, KeyPair) { (PublicKeyBytes(keypair.public.to_bytes()), KeyPair(keypair)) } +pub fn address_as_hex(key: &PublicKeyBytes, serializer: S) -> Result +where + S: serde::ser::Serializer, +{ + serializer.serialize_str(&encode_address_hex(key)) +} + +pub fn address_from_hex<'de, D>(deserializer: D) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + let value = decode_address_hex(&s).map_err(serde::de::Error::custom)?; + Ok(value) +} + +pub fn encode_address_hex(key: &PublicKeyBytes) -> String { + hex::encode(&key.0[..]) +} + +pub fn decode_address_hex(s: &str) -> Result { + let value = hex::decode(s)?; + let mut address = [0u8; dalek::PUBLIC_KEY_LENGTH]; + address.copy_from_slice(&value[..dalek::PUBLIC_KEY_LENGTH]); + Ok(PublicKeyBytes(address)) +} + +impl std::fmt::LowerHex for PublicKeyBytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + write!(f, "0x")?; + } + + for byte in &self.0 { + write!(f, "{:02x}", byte)?; + } + + Ok(()) + } +} + +impl std::fmt::UpperHex for PublicKeyBytes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() { + write!(f, "0x")?; + } + + for byte in &self.0 { + write!(f, "{:02X}", byte)?; + } + + Ok(()) + } +} pub fn address_as_base64(key: &PublicKeyBytes, serializer: S) -> Result where S: serde::ser::Serializer, @@ -268,7 +322,7 @@ impl std::fmt::Debug for Signature { impl std::fmt::Debug for PublicKeyBytes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let s = base64::encode(&self.0); + let s = hex::encode(&self.0); write!(f, "{}", s)?; Ok(()) } diff --git a/fastx_types/src/error.rs b/fastx_types/src/error.rs index 980323c813a22..cfe2dd9e1ab58 100644 --- a/fastx_types/src/error.rs +++ b/fastx_types/src/error.rs @@ -52,6 +52,10 @@ pub enum FastPayError { ConflictingOrder { pending_confirmation: Order }, #[error("Transfer order was processed but no signature was produced by authority")] ErrorWhileProcessingTransferOrder, + #[error("Transaction order processing not properly executed by authority")] + ErrorWhileProcessingTransactionOrder, + #[error("Invalid response when processing confirmation order by authority")] + ErrorWhileProcessingConfirmationOrder, #[error("An invalid answer was returned by the authority while requesting a certificate")] ErrorWhileRequestingCertificate, #[error("An invalid answer was returned by the authority while requesting information")] diff --git a/fastx_types/src/messages.rs b/fastx_types/src/messages.rs index a38b50b7ea2e7..b6f5ae880962f 100644 --- a/fastx_types/src/messages.rs +++ b/fastx_types/src/messages.rs @@ -1,6 +1,8 @@ // Copyright (c) Facebook, Inc. and its affiliates. // SPDX-License-Identifier: Apache-2.0 +use crate::object::Object; + use super::{base_types::*, committee::Committee, error::*}; #[cfg(test)] @@ -107,12 +109,9 @@ pub struct AccountInfoResponse { #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub struct ObjectInfoResponse { - pub object_id: ObjectID, - pub owner: FastPayAddress, - pub next_sequence_number: SequenceNumber, pub requested_certificate: Option, pub pending_confirmation: Option, - pub requested_received_transfers: Vec, + pub object: Object, } #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] diff --git a/fastx_types/src/object.rs b/fastx_types/src/object.rs index d400eabfeab9a..34d526733cdf5 100644 --- a/fastx_types/src/object.rs +++ b/fastx_types/src/object.rs @@ -17,7 +17,7 @@ use crate::{ gas_coin::GasCoin, }; -#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)] +#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] pub struct MoveObject { pub type_: StructTag, contents: Vec, @@ -119,7 +119,7 @@ impl MoveObject { // TODO: Make MovePackage a NewType so that we can implement functions on it. pub type MovePackage = BTreeMap>; -#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)] +#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] #[allow(clippy::large_enum_variant)] pub enum Data { /// An object whose governing logic lives in a published Move module @@ -163,7 +163,7 @@ impl Data { } } -#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)] +#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] pub struct Object { /// The meat of the object pub data: Data, diff --git a/fastx_types/src/unit_tests/serialize_tests.rs b/fastx_types/src/unit_tests/serialize_tests.rs index 222334f24aa73..1cf5b33d4a4f7 100644 --- a/fastx_types/src/unit_tests/serialize_tests.rs +++ b/fastx_types/src/unit_tests/serialize_tests.rs @@ -3,7 +3,7 @@ #![allow(clippy::same_item_push)] // get_key_pair returns random elements use super::*; -use crate::base_types::*; +use crate::{base_types::*, object::Object}; use std::time::Instant; #[test] @@ -215,36 +215,24 @@ fn test_info_response() { } let resp1 = ObjectInfoResponse { - object_id: dbg_object_id(0x20), - owner: dbg_addr(0x20), - next_sequence_number: SequenceNumber::new(), + object: Object::with_id_owner_for_testing(dbg_object_id(0x20), dbg_addr(0x20)), pending_confirmation: None, requested_certificate: None, - requested_received_transfers: Vec::new(), }; let resp2 = ObjectInfoResponse { - object_id: dbg_object_id(0x20), - owner: dbg_addr(0x20), - next_sequence_number: SequenceNumber::new(), + object: Object::with_id_owner_for_testing(dbg_object_id(0x20), dbg_addr(0x20)), pending_confirmation: Some(vote.clone()), requested_certificate: None, - requested_received_transfers: Vec::new(), }; let resp3 = ObjectInfoResponse { - object_id: dbg_object_id(0x20), - owner: dbg_addr(0x20), - next_sequence_number: SequenceNumber::new(), + object: Object::with_id_owner_for_testing(dbg_object_id(0x20), dbg_addr(0x20)), pending_confirmation: None, requested_certificate: Some(cert.clone()), - requested_received_transfers: Vec::new(), }; let resp4 = ObjectInfoResponse { - object_id: dbg_object_id(0x20), - owner: dbg_addr(0x20), - next_sequence_number: SequenceNumber::new(), + object: Object::with_id_owner_for_testing(dbg_object_id(0x20), dbg_addr(0x20)), pending_confirmation: Some(vote), requested_certificate: Some(cert), - requested_received_transfers: Vec::new(), }; for resp in [resp1, resp2, resp3, resp4].iter() {