Skip to content

Commit b38bca8

Browse files
mystenmarkclaude
andauthored
Add ObjectBalanceOverdraw benchmark operation and instrumentation (#25227)
## Summary - Add new `ObjectBalanceOverdraw` operation to composite benchmark workloads that tests balance pool overdraw scenarios - Track pool balance in `AccountState` for accurate overdraw calculations - Add `assert_reachable!` instrumentation for pending object funds scenarios to monitor code path coverage ## Test plan - [ ] Run composite benchmark workloads with the new operation enabled - [ ] Verify `ObjectBalanceOverdraw` withdraws 50-100% of pool balance as expected 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3ffa8ca commit b38bca8

File tree

4 files changed

+109
-1
lines changed

4 files changed

+109
-1
lines changed

crates/sui-benchmark/src/workloads/composite/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rand::seq::SliceRandom;
1717

1818
use crate::drivers::Interval;
1919
use crate::system_state_observer::SystemStateObserver;
20+
use crate::workloads::composite::operations::ObjectBalanceOverdraw;
2021
use crate::workloads::payload::{BatchExecutionResults, BatchedTransactionStatus, Payload};
2122
use crate::workloads::workload::{
2223
ESTIMATED_COMPUTATION_COST, MAX_GAS_FOR_TESTING, STORAGE_COST_PER_COUNTER, Workload,
@@ -296,6 +297,7 @@ impl CompositeWorkloadConfig {
296297
probabilities.insert(TestCoinObjectWithdraw::FLAG, 0.1);
297298
probabilities.insert(AddressBalanceOverdraw::FLAG, 0.1);
298299
probabilities.insert(AccumulatorBalanceRead::FLAG, 0.3);
300+
probabilities.insert(ObjectBalanceOverdraw::FLAG, 0.1);
299301
Self {
300302
probabilities,
301303
alias_tx_probability: 0.3,
@@ -884,7 +886,8 @@ impl Payload for CompositePayload {
884886
self.current_batch_num_conflicting_transactions = 0;
885887
let mut transactions = Vec::with_capacity(batch_size + 1);
886888

887-
let account_state = AccountState::new(sender, &self.fullnode_proxies).await;
889+
let account_state =
890+
AccountState::new(sender, &self.fullnode_proxies, self.pool.balance_pool).await;
888891

889892
let mut used_gas = vec![];
890893

@@ -1052,12 +1055,14 @@ impl Payload for CompositePayload {
10521055
pub struct AccountState {
10531056
pub sender: SuiAddress,
10541057
pub sui_balance: u64,
1058+
pub pool_balance: u64,
10551059
}
10561060

10571061
impl AccountState {
10581062
pub async fn new(
10591063
sender: SuiAddress,
10601064
fullnode_proxies: &Vec<Arc<dyn ValidatorProxy + Sync + Send>>,
1065+
balance_pool: Option<(ObjectID, SequenceNumber)>,
10611066
) -> Self {
10621067
let mut retries = 0;
10631068
while retries < 3 {
@@ -1069,14 +1074,25 @@ impl AccountState {
10691074
continue;
10701075
};
10711076
assert_reachable!("successfully got sui balance for address");
1077+
let pool_balance = if let Some((pool_id, _)) = balance_pool {
1078+
let pool_address: SuiAddress = pool_id.into();
1079+
proxy
1080+
.get_sui_address_balance(pool_address)
1081+
.await
1082+
.unwrap_or(0)
1083+
} else {
1084+
0
1085+
};
10721086
return Self {
10731087
sender,
10741088
sui_balance,
1089+
pool_balance,
10751090
};
10761091
}
10771092
Self {
10781093
sender,
10791094
sui_balance: 0,
1095+
pool_balance: 0,
10801096
}
10811097
}
10821098
}

crates/sui-benchmark/src/workloads/composite/operations.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub const ALL_OPERATIONS: &[OperationDescriptor] = &[
5454
TestCoinObjectWithdraw::DESCRIPTOR,
5555
AddressBalanceOverdraw::DESCRIPTOR,
5656
AccumulatorBalanceRead::DESCRIPTOR,
57+
ObjectBalanceOverdraw::DESCRIPTOR,
5758
];
5859

5960
pub fn describe_flags(flags: u32) -> String {
@@ -992,3 +993,89 @@ impl Operation for AccumulatorBalanceRead {
992993
);
993994
}
994995
}
996+
997+
pub struct ObjectBalanceOverdraw;
998+
999+
impl ObjectBalanceOverdraw {
1000+
pub const FLAG: u32 = 1 << 16;
1001+
pub const DESCRIPTOR: OperationDescriptor = OperationDescriptor {
1002+
name: "ObjectBalanceOverdraw",
1003+
flag: Self::FLAG,
1004+
factory: || Box::new(ObjectBalanceOverdraw),
1005+
};
1006+
}
1007+
1008+
impl Operation for ObjectBalanceOverdraw {
1009+
fn name(&self) -> &'static str {
1010+
"object_balance_overdraw"
1011+
}
1012+
1013+
fn operation_flag(&self) -> u32 {
1014+
Self::FLAG
1015+
}
1016+
1017+
fn resource_requests(&self) -> Vec<ResourceRequest> {
1018+
vec![ResourceRequest::ObjectBalance]
1019+
}
1020+
1021+
fn init_requirements(&self) -> Vec<InitRequirement> {
1022+
vec![
1023+
InitRequirement::CreateBalancePool,
1024+
InitRequirement::SeedBalancePool,
1025+
]
1026+
}
1027+
1028+
fn apply(
1029+
&self,
1030+
builder: &mut ProgrammableTransactionBuilder,
1031+
resources: &OperationResources,
1032+
account_state: &AccountState,
1033+
) {
1034+
let (pool_id, initial_shared_version) =
1035+
resources.balance_pool.expect("Balance pool not resolved");
1036+
1037+
let withdraw_amount = if account_state.pool_balance == 0 {
1038+
0
1039+
} else {
1040+
let half_balance = std::cmp::max(1, account_state.pool_balance / 2);
1041+
get_rng().gen_range(half_balance..=account_state.pool_balance)
1042+
};
1043+
1044+
let pool_arg = builder
1045+
.obj(ObjectArg::SharedObject {
1046+
id: pool_id,
1047+
initial_shared_version,
1048+
mutability: SharedObjectMutability::Mutable,
1049+
})
1050+
.unwrap();
1051+
1052+
let amount_arg = builder.pure(withdraw_amount).unwrap();
1053+
1054+
let withdrawal = builder.programmable_move_call(
1055+
resources.package_id,
1056+
Identifier::new("balance_pool").unwrap(),
1057+
Identifier::new("withdraw").unwrap(),
1058+
vec![GAS::type_tag()],
1059+
vec![pool_arg, amount_arg],
1060+
);
1061+
1062+
let balance = builder.programmable_move_call(
1063+
SUI_FRAMEWORK_PACKAGE_ID,
1064+
Identifier::new("balance").unwrap(),
1065+
Identifier::new("redeem_funds").unwrap(),
1066+
vec![GAS::type_tag()],
1067+
vec![withdrawal],
1068+
);
1069+
1070+
let coin = builder.programmable_move_call(
1071+
SUI_FRAMEWORK_PACKAGE_ID,
1072+
Identifier::new("coin").unwrap(),
1073+
Identifier::new("from_balance").unwrap(),
1074+
vec![GAS::type_tag()],
1075+
vec![balance],
1076+
);
1077+
1078+
let recipient = SuiAddress::random_for_testing_only();
1079+
builder.transfer_arg(recipient, coin);
1080+
}
1081+
}

crates/sui-core/src/accumulators/object_funds_checker/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
sync::Arc,
77
};
88

9+
use mysten_common::assert_reachable;
910
use parking_lot::RwLock;
1011
use sui_types::{
1112
SUI_ACCUMULATOR_ROOT_OBJECT_ID,
@@ -132,6 +133,7 @@ impl ObjectFundsChecker {
132133
match self.check_object_funds(object_withdraws, accumulator_version, funds_read.as_ref()) {
133134
// Sufficient funds, we can go ahead and commit the execution results as it is.
134135
ObjectFundsWithdrawStatus::SufficientFunds => {
136+
assert_reachable!("object funds sufficient");
135137
debug!("Object funds sufficient, committing effects");
136138
self.metrics
137139
.check_result
@@ -161,13 +163,15 @@ impl ObjectFundsChecker {
161163
let tx_digest = cert.digest();
162164
match receiver.await {
163165
Ok(FundsWithdrawStatus::MaybeSufficient) => {
166+
assert_reachable!("object funds maybe sufficient");
164167
// The withdraw state is now deterministically known,
165168
// so we can enqueue the transaction again and it will check again
166169
// whether it is sufficient or not in the next execution.
167170
// TODO: We should be able to optimize this by avoiding re-execution.
168171
debug!(?tx_digest, "Object funds possibly sufficient");
169172
}
170173
Ok(FundsWithdrawStatus::Insufficient) => {
174+
assert_reachable!("object funds insufficient");
171175
// Re-enqueue with insufficient funds status, so it will be executed
172176
// in the next execution and fail through early error.
173177
// FIXME: We need to also track the amount of gas that was used,

crates/sui-core/src/authority.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,7 @@ impl AuthorityState {
19711971
epoch_store,
19721972
)
19731973
{
1974+
assert_reachable!("retry object withdraw later");
19741975
return ExecutionOutput::RetryLater;
19751976
}
19761977

0 commit comments

Comments
 (0)