From dae1ad264ce46672a3b782292a7384f90a58f39d Mon Sep 17 00:00:00 2001 From: ade <93547199+oxade@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:32:52 -0500 Subject: [PATCH] Bench for Move based transfer with Gas (#190) * Bench for Move based transfer with ObjectBasics --- fastpay/src/bench.rs | 87 ++++++++++++++++--- fastpay_core/src/authority.rs | 16 ++-- .../src/unit_tests/authority_tests.rs | 12 ++- fastx_programmability/adapter/src/adapter.rs | 19 ++-- .../adapter/src/unit_tests/adapter_tests.rs | 2 + .../framework/sources/GAS.move | 7 ++ fastx_types/src/object.rs | 81 +++++++++++++++++ 7 files changed, 196 insertions(+), 28 deletions(-) diff --git a/fastpay/src/bench.rs b/fastpay/src/bench.rs index 00864a6d84465..a2e490346b309 100644 --- a/fastpay/src/bench.rs +++ b/fastpay/src/bench.rs @@ -6,9 +6,13 @@ use bytes::Bytes; use fastpay::{network, transport}; use fastpay_core::authority::*; +use fastx_types::FASTX_FRAMEWORK_ADDRESS; use fastx_types::{base_types::*, committee::*, messages::*, object::Object, serialize::*}; use futures::stream::StreamExt; use log::*; +use move_core_types::ident_str; +use rand::rngs::StdRng; +use rand::Rng; use std::time::{Duration, Instant}; use structopt::StructOpt; use tokio::runtime::Runtime; @@ -67,6 +71,9 @@ struct ClientServerBenchmark { /// Number of database cpus #[structopt(long, default_value = "1")] db_cpus: usize, + /// Use Move orders + #[structopt(long)] + use_move: bool, } #[derive(Debug, Clone, PartialEq, EnumString)] enum BenchmarkType { @@ -156,24 +163,34 @@ impl ClientServerBenchmark { store, ) .await; + let mut rnd = ::seed_from_u64(0); for _ in 0..self.num_accounts { let keypair = get_key_pair(); let object_id: ObjectID = ObjectID::random(); + let object = if self.use_move { + Object::with_id_owner_gas_coin_object_for_testing( + ObjectID::random(), + SequenceNumber::new(), + keypair.0, + rnd.gen::(), + ) + } else { + Object::with_id_owner_for_testing(object_id, keypair.0) + }; - let object = Object::with_id_owner_for_testing(object_id, keypair.0); assert!(object.version() == SequenceNumber::from(0)); let object_ref = object.to_object_reference(); state.init_order_lock(object_ref).await; + account_objects.push((keypair.0, object.clone(), keypair.1)); state.insert_object(object).await; - account_objects.push((keypair.0, object_ref, keypair.1)); let gas_object_id = ObjectID::random(); let gas_object = Object::with_id_owner_for_testing(gas_object_id, keypair.0); assert!(gas_object.version() == SequenceNumber::from(0)); let gas_object_ref = gas_object.to_object_reference(); state.init_order_lock(gas_object_ref).await; + gas_objects.push(gas_object.clone()); state.insert_object(gas_object).await; - gas_objects.push(gas_object_ref); } state }); @@ -182,15 +199,42 @@ impl ClientServerBenchmark { // Make one transaction per account (transfer order + confirmation). let mut orders: Vec = Vec::new(); let mut next_recipient = get_key_pair().0; - for ((pubx, object_ref, secx), gas_payment) in account_objects.iter().zip(gas_objects) { - let transfer = Transfer { - object_ref: *object_ref, - sender: *pubx, - recipient: Address::FastPay(next_recipient), - gas_payment, + for ((account_addr, object, secret), gas_obj) in account_objects.iter().zip(gas_objects) { + let object_ref = object.to_object_reference(); + let gas_object_ref = gas_obj.to_object_reference(); + + let order = if self.use_move { + // TODO: authority should not require seq# or digets for package in Move calls. Use dummy values + let framework_obj_ref = ( + FASTX_FRAMEWORK_ADDRESS, + SequenceNumber::new(), + ObjectDigest::new([0; 32]), + ); + + Order::new_move_call( + *account_addr, + framework_obj_ref, + ident_str!("GAS").to_owned(), + ident_str!("transfer").to_owned(), + Vec::new(), + gas_object_ref, + vec![object_ref], + vec![bcs::to_bytes(&next_recipient.to_vec()).unwrap()], + 1000, + secret, + ) + } else { + let transfer = Transfer { + sender: *account_addr, + recipient: Address::FastPay(next_recipient), + object_ref, + gas_payment: gas_object_ref, + }; + Order::new_transfer(transfer, secret) }; - next_recipient = *pubx; - let order = Order::new_transfer(transfer, secx); + + // Set the next recipient to current + next_recipient = *account_addr; // Serialize order let serialized_order = serialize_order(&order); @@ -210,10 +254,10 @@ impl ClientServerBenchmark { let serialized_certificate = serialize_cert(&certificate); assert!(!serialized_certificate.is_empty()); - if self.benchmark_type != BenchmarkType::OrdersOnly { + if self.benchmark_type != BenchmarkType::CertsOnly { orders.push(serialized_order.into()); } - if self.benchmark_type != BenchmarkType::CertsOnly { + if self.benchmark_type != BenchmarkType::OrdersOnly { orders.push(serialized_certificate.into()); } } @@ -261,6 +305,23 @@ impl ClientServerBenchmark { let responses = mass_client.run(orders, connections).concat().await; info!("Received {} responses.", responses.len(),); + // Check the responses for errors + for resp in &responses { + let reply_message = deserialize_message(&resp[..]); + match reply_message { + Ok(SerializedMessage::OrderResp(res)) => { + if let Some(e) = res.signed_effects { + if e.effects.status != ExecutionStatus::Success { + info!("Execution Error {:?}", e.effects.status); + } + } + } + Err(err) => { + info!("Received Error {:?}", err); + } + _ => {} + }; + } } else { // Use actual client core let client = network::Client::new( diff --git a/fastpay_core/src/authority.rs b/fastpay_core/src/authority.rs index d17b1d94d7f6a..f39716975147c 100644 --- a/fastpay_core/src/authority.rs +++ b/fastpay_core/src/authority.rs @@ -43,7 +43,8 @@ pub struct AuthorityState { pub secret: KeyPair, /// Move native functions that are available to invoke - native_functions: NativeFunctionTable, + _native_functions: NativeFunctionTable, + move_vm: Arc, /// The database _database: Arc, } @@ -239,8 +240,9 @@ impl AuthorityState { let gas_object = inputs.pop().unwrap(); let package = inputs.pop().unwrap(); adapter::execute( + &self.move_vm, &mut temporary_store, - self.native_functions.clone(), + self._native_functions.clone(), package, &c.module, &c.function, @@ -257,7 +259,7 @@ impl AuthorityState { let gas_object = inputs.pop().unwrap(); adapter::publish( &mut temporary_store, - self.native_functions.clone(), + self._native_functions.clone(), m.modules, m.sender, &mut tx_ctx, @@ -331,7 +333,9 @@ impl AuthorityState { committee, name, secret, - native_functions, + _native_functions: native_functions.clone(), + move_vm: adapter::new_move_vm(native_functions) + .expect("We defined natives to not fail here"), _database: store, }; @@ -354,11 +358,13 @@ impl AuthorityState { secret: KeyPair, store: Arc, ) -> Self { + let native_functions = NativeFunctionTable::new(); AuthorityState { committee, name, secret, - native_functions: NativeFunctionTable::new(), + _native_functions: native_functions.clone(), + move_vm: adapter::new_move_vm(native_functions).expect("Only fails due to natives."), _database: store, } } diff --git a/fastpay_core/src/unit_tests/authority_tests.rs b/fastpay_core/src/unit_tests/authority_tests.rs index 6bec528224e52..8798af2eb958f 100644 --- a/fastpay_core/src/unit_tests/authority_tests.rs +++ b/fastpay_core/src/unit_tests/authority_tests.rs @@ -386,7 +386,8 @@ async fn test_handle_move_order() { genesis_package_objects.push(gas_payment_object); let mut authority_state = init_state_with_objects(genesis_package_objects).await; - authority_state.native_functions = native_functions; + authority_state._native_functions = native_functions.clone(); + authority_state.move_vm = adapter::new_move_vm(native_functions).unwrap(); let function = ident_str!("create").to_owned(); let order = Order::new_move_call( @@ -413,8 +414,12 @@ async fn test_handle_move_order() { // Check that effects are reported assert!(res.signed_effects.is_some()); + assert_eq!( + res.signed_effects.as_ref().unwrap().effects.status, + ExecutionStatus::Success + ); let mutated = res.signed_effects.unwrap().effects.mutated; - assert!(mutated.len() == 2); + assert_eq!(mutated.len(), 2); let created_object_id = mutated[0].0; // res.object_id; // check that order actually created an object with the expected ID, owner, sequence number @@ -465,7 +470,8 @@ async fn test_handle_move_order_insufficient_budget() { genesis_package_objects.push(gas_payment_object); let mut authority_state = init_state_with_objects(genesis_package_objects).await; - authority_state.native_functions = native_functions; + authority_state._native_functions = native_functions.clone(); + authority_state.move_vm = adapter::new_move_vm(native_functions).unwrap(); let function = ident_str!("create").to_owned(); let order = Order::new_move_call( diff --git a/fastx_programmability/adapter/src/adapter.rs b/fastx_programmability/adapter/src/adapter.rs index f445f362b8e9b..26f68885ddb85 100644 --- a/fastx_programmability/adapter/src/adapter.rs +++ b/fastx_programmability/adapter/src/adapter.rs @@ -30,22 +30,29 @@ use move_core_types::{ language_storage::{ModuleId, StructTag, TypeTag}, resolver::{ModuleResolver, ResourceResolver}, }; -use move_vm_runtime::{ - move_vm::MoveVM, native_functions::NativeFunctionTable, session::ExecutionResult, -}; -use std::{borrow::Borrow, collections::BTreeMap, convert::TryFrom, fmt::Debug}; +use move_vm_runtime::{native_functions::NativeFunctionTable, session::ExecutionResult}; +use std::{borrow::Borrow, collections::BTreeMap, convert::TryFrom, fmt::Debug, sync::Arc}; + +pub use move_vm_runtime::move_vm::MoveVM; #[cfg(test)] #[path = "unit_tests/adapter_tests.rs"] mod adapter_tests; +pub fn new_move_vm(natives: NativeFunctionTable) -> Result, FastPayError> { + Ok(Arc::new( + MoveVM::new(natives).map_err(|_| FastPayError::ExecutionInvariantViolation)?, + )) +} + /// Execute `module::function(object_args ++ pure_args)` as a call from `sender` with the given `gas_budget`. /// Execution will read from/write to the store in `state_view`. /// If `gas_budget` is None, runtime metering is disabled and execution may diverge. #[allow(clippy::too_many_arguments)] pub fn execute + ModuleResolver + Storage>( + vm: &MoveVM, state_view: &mut S, - natives: NativeFunctionTable, + _natives: NativeFunctionTable, package_object: Object, module: &Identifier, function: &Identifier, @@ -71,8 +78,6 @@ pub fn execute + ModuleResolver FastPayResult { let package = storage.find_package(module_name).unwrap(); + let vm = adapter::new_move_vm(native_functions.clone()).expect("No errors"); adapter::execute( + &vm, storage, native_functions.clone(), package, diff --git a/fastx_programmability/framework/sources/GAS.move b/fastx_programmability/framework/sources/GAS.move index 4d2b20506689a..7e9d7ba8f6ac6 100644 --- a/fastx_programmability/framework/sources/GAS.move +++ b/fastx_programmability/framework/sources/GAS.move @@ -2,6 +2,7 @@ module FastX::GAS { use FastX::Coin; use FastX::Transfer; + use FastX::Address; use FastX::TxContext::{Self, TxContext}; /// Name of the coin @@ -16,4 +17,10 @@ module FastX::GAS { let treasury_cap = Coin::create_currency(GAS{}, ctx); Transfer::transfer(treasury_cap, TxContext::get_signer_address(ctx)) } + + /// Transfer to a recipient + public fun transfer(c: Coin::Coin, recipient: vector, _ctx: &mut TxContext) { + Coin::transfer(c, Address::new(recipient)) + } + } diff --git a/fastx_types/src/object.rs b/fastx_types/src/object.rs index fd39988228379..8332e76234a7f 100644 --- a/fastx_types/src/object.rs +++ b/fastx_types/src/object.rs @@ -1,6 +1,7 @@ // Copyright (c) Mysten Labs // SPDX-License-Identifier: Apache-2.0 +use move_core_types::ident_str; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; @@ -8,6 +9,8 @@ use std::convert::{TryFrom, TryInto}; use move_binary_format::CompiledModule; use move_core_types::{account_address::AccountAddress, language_storage::StructTag}; +use crate::id::ID; +use crate::FASTX_FRAMEWORK_ADDRESS; use crate::{ base_types::{ sha3_hash, BcsSignable, FastPayAddress, ObjectDigest, ObjectID, ObjectRef, SequenceNumber, @@ -16,7 +19,12 @@ use crate::{ gas_coin::GasCoin, }; +pub const OBJECT_BASICS_MODULE_NAME: &move_core_types::identifier::IdentStr = + ident_str!("ObjectBasics"); +pub const OBJECT_BASICS_OBJECT_TYPE_NAME: &move_core_types::identifier::IdentStr = + ident_str!("Object"); pub const GAS_VALUE_FOR_TESTING: u64 = 100000_u64; + #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] pub struct MoveObject { pub type_: StructTag, @@ -24,6 +32,20 @@ pub struct MoveObject { read_only: bool, } +/// ObjectBasics in the Framework uses an object of the following format +#[derive(Debug, Deserialize, Serialize)] +pub struct ObjectBasicsObject { + pub id: ID, + pub value: u64, +} + +/// Coin in the Framework uses an object of the following format +#[derive(Debug, Deserialize, Serialize)] +pub struct CoinObject { + pub id: ID, + pub value: u64, +} + /// Byte encoding of a 64 byte unsigned integer in BCS type BcsU64 = [u8; 8]; /// Index marking the end of the object's ID + the beginning of its version @@ -291,4 +313,63 @@ impl Object { // For testing, we provide sufficient gas by default. Self::with_id_owner_gas_for_testing(id, SequenceNumber::new(), owner, GAS_VALUE_FOR_TESTING) } + + /// Create ObjectBasics object for use in Move object operation + pub fn with_id_owner_object_basics_object_for_testing( + id: ObjectID, + version: SequenceNumber, + owner: FastPayAddress, + value: u64, + ) -> Self { + // Check ObjectBasics.move in Framework for details + // Create struct tag for ObjectBasics object + let struct_tag = StructTag { + address: FASTX_FRAMEWORK_ADDRESS, + name: OBJECT_BASICS_OBJECT_TYPE_NAME.to_owned(), + module: OBJECT_BASICS_MODULE_NAME.to_owned(), + type_params: Vec::new(), + }; + + // An object in ObjectBasics is a struct of an ID and a u64 value + let obj = ObjectBasicsObject { + id: ID::new(id, version), + value, + }; + + let data = Data::Move(MoveObject { + type_: struct_tag, + contents: bcs::to_bytes(&obj).unwrap(), + read_only: false, + }); + Self { + owner, + data, + previous_transaction: TransactionDigest::genesis(), + } + } + + /// Create Coin object for use in Move object operation + pub fn with_id_owner_gas_coin_object_for_testing( + id: ObjectID, + version: SequenceNumber, + owner: FastPayAddress, + value: u64, + ) -> Self { + // An object in Coin.move is a struct of an ID and a u64 value + let obj = CoinObject { + id: ID::new(id, version), + value, + }; + + let data = Data::Move(MoveObject { + type_: GasCoin::type_(), + contents: bcs::to_bytes(&obj).unwrap(), + read_only: false, + }); + Self { + owner, + data, + previous_transaction: TransactionDigest::genesis(), + } + } }