Skip to content

Commit

Permalink
[workloads] add adversarial workload
Browse files Browse the repository at this point in the history
A new workload type that tries to push against system limits. For starters, I added logic for generating as many objects of the max size as possible without going over a limit. Some other ideas for extensions to this workload:

- MaxReads (by creating a bunch of shared objects in the module init for adversarial, then taking them all as input)
- MaxDynamicFields (by reading a bunch of dynamic fields at runtime)
- MaxEffects (by creating a bunch of small objects)
- MaxEvents (max out VM's event size limit)

Hopefully this will be useful for simtests/stress-testing.
  • Loading branch information
sblackshear committed Mar 16, 2023
1 parent 724d3e1 commit 7c0ad78
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 31 deletions.
72 changes: 66 additions & 6 deletions crates/sui-benchmark/src/in_memory_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,49 @@

use std::{collections::BTreeMap, sync::Arc};

use move_core_types::{identifier::Identifier, language_storage::TypeTag};
use sui_types::{
base_types::{ObjectID, ObjectRef, SuiAddress},
crypto::AccountKeyPair,
messages::{TransactionData, TransactionDataAPI, VerifiedTransaction},
messages::{CallArg, TransactionData, TransactionDataAPI, VerifiedTransaction},
object::Owner,
utils::to_sender_signed_transaction,
};

use crate::ExecutionEffects;
use crate::{workloads::Gas, ExecutionEffects};

/// A Sui account and all of the objects it owns
#[derive(Debug)]
pub struct SuiAccount {
key: Arc<AccountKeyPair>,
/// object this account uses to pay for gas
pub gas: ObjectRef,
/// objects owned by this account. does not include `gas`
owned: BTreeMap<ObjectID, ObjectRef>,
// TODO: optional type info
}

impl SuiAccount {
pub fn new(key: Arc<AccountKeyPair>, objs: Vec<ObjectRef>) -> Self {
pub fn new(key: Arc<AccountKeyPair>, gas: ObjectRef, objs: Vec<ObjectRef>) -> Self {
let owned = objs.into_iter().map(|obj| (obj.0, obj)).collect();
SuiAccount { key, owned }
SuiAccount { key, gas, owned }
}

/// Update the state associated with `obj`, adding it if it doesn't exist
pub fn add_or_update(&mut self, obj: ObjectRef) -> Option<ObjectRef> {
self.owned.insert(obj.0, obj)
if self.gas.0 == obj.0 {
let old_gas = self.gas;
self.gas = obj;
Some(old_gas)
} else {
self.owned.insert(obj.0, obj)
}
}

/// Delete `id` and return the old value
pub fn delete(&mut self, id: &ObjectID) -> Option<ObjectRef> {
debug_assert!(self.gas.0 != *id, "Deleting gas object");

self.owned.remove(id)
}
}
Expand All @@ -45,13 +57,22 @@ pub struct InMemoryWallet {
}

impl InMemoryWallet {
pub fn new(gas: &Gas) -> Self {
let mut wallet = InMemoryWallet {
accounts: BTreeMap::new(),
};
wallet.add_account(gas.1, gas.2.clone(), gas.0, Vec::new());
wallet
}

pub fn add_account(
&mut self,
addr: SuiAddress,
key: Arc<AccountKeyPair>,
gas: ObjectRef,
objs: Vec<ObjectRef>,
) {
self.accounts.insert(addr, SuiAccount::new(key, objs));
self.accounts.insert(addr, SuiAccount::new(key, gas, objs));
}

/// Apply updates from `effects` to `self`
Expand All @@ -76,6 +97,18 @@ impl InMemoryWallet {
} // else, tx sender is not an account we can spend from, we don't care
}

pub fn account_mut(&mut self, addr: &SuiAddress) -> Option<&mut SuiAccount> {
self.accounts.get_mut(addr)
}

pub fn account(&self, addr: &SuiAddress) -> Option<&SuiAccount> {
self.accounts.get(addr)
}

pub fn gas(&self, addr: &SuiAddress) -> Option<&ObjectRef> {
self.accounts.get(addr).map(|a| &a.gas)
}

pub fn owned_object(&self, addr: &SuiAddress, id: &ObjectID) -> Option<&ObjectRef> {
self.accounts.get(addr).and_then(|a| a.owned.get(id))
}
Expand All @@ -89,6 +122,33 @@ impl InMemoryWallet {
to_sender_signed_transaction(data, self.accounts.get(&sender).unwrap().key.as_ref())
}

pub fn move_call(
&self,
sender: SuiAddress,
package: ObjectID,
module: &str,
function: &str,
type_arguments: Vec<TypeTag>,
arguments: Vec<CallArg>,
gas_budget: u64,
gas_price: u64,
) -> VerifiedTransaction {
let account = self.account(&sender).unwrap();
let data = TransactionData::new_move_call(
sender,
package,
Identifier::new(module).unwrap(),
Identifier::new(function).unwrap(),
type_arguments,
account.gas,
arguments,
gas_budget,
gas_price,
)
.unwrap();
to_sender_signed_transaction(data, account.key.as_ref())
}

pub fn keypair(&self, addr: &SuiAddress) -> Option<Arc<AccountKeyPair>> {
self.accounts.get(addr).map(|a| a.key.clone())
}
Expand Down
11 changes: 11 additions & 0 deletions crates/sui-benchmark/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ impl ExecutionEffects {
Owner::ObjectOwner(_) | Owner::Shared { .. } | Owner::Immutable => unreachable!(), // owner of gas object is always an address
}
}

pub fn is_ok(&self) -> bool {
match self {
ExecutionEffects::CertifiedTransactionEffects(certified_effects, ..) => {
certified_effects.data().status().is_ok()
}
ExecutionEffects::SuiTransactionEffects(sui_tx_effects) => {
sui_tx_effects.status().is_ok()
}
}
}
}

#[async_trait]
Expand Down
24 changes: 16 additions & 8 deletions crates/sui-benchmark/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,11 @@ pub enum RunSpec {
// will likely change in future to support
// more representative workloads.
Bench {
// ----- workloads ----
// relative weight of shared counter
// transaction in the benchmark workload
#[clap(long, default_value = "0")]
shared_counter: u32,
// 100 for max hotness i.e all requests target
// just the same shared counter, 0 for no hotness
// i.e. all requests target a different shared
// counter. The way total number of counters to
// create is computed roughly as:
// total_shared_counters = max(1, qps * (1.0 - hotness/100.0))
#[clap(long, default_value = "50")]
shared_counter_hotness_factor: u32,
// relative weight of transfer object
// transactions in the benchmark workload
#[clap(long, default_value = "1")]
Expand All @@ -152,9 +145,24 @@ pub enum RunSpec {
// relative weight of batch payment transactions in the benchmark workload
#[clap(long, default_value = "0")]
batch_payment: u32,
// relative weight of adversarial transactions in the benchmark workload
#[clap(long, default_value = "0")]
adversarial: u32,

// --- workload-specific options --- (TODO: use subcommands or similar)
// 100 for max hotness i.e all requests target
// just the same shared counter, 0 for no hotness
// i.e. all requests target a different shared
// counter. The way total number of counters to
// create is computed roughly as:
// total_shared_counters = max(1, qps * (1.0 - hotness/100.0))
#[clap(long, default_value = "50")]
shared_counter_hotness_factor: u32,
// batch size use for batch payment workload
#[clap(long, default_value = "15")]
batch_payment_size: u32,

// --- generic options ---
// Target qps
#[clap(long, default_value = "1000", global = true)]
target_qps: u64,
Expand Down
Loading

0 comments on commit 7c0ad78

Please sign in to comment.