Skip to content

Commit 129fcca

Browse files
committed
[mfp] allow partial failures in soft bundles (#23910)
## Description Soft bundles can include transactions from multiple sources. Permitting partial failure seems necessary, where some transactions may fail without invalidating the entire request. Otherwise, a single source submitting invalid transactions can deterministically cause the bundle request to fail. When partial failures are allowed, each transaction must handle the situations in which some transactions in the bundle have been rejected or already executed at submission. This is the case in existing soft bundle usages pre-MFP. ## Test plan CI
1 parent e44db8c commit 129fcca

File tree

2 files changed

+21
-36
lines changed

2 files changed

+21
-36
lines changed

crates/sui-core/src/authority_server.rs

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,10 @@ impl ValidatorService {
628628
);
629629
}
630630

631+
// NOTE: for soft bundle requests, the system tries to sequence the transactions in the same order
632+
// if they use the same gas price. But this is only done with best effort.
633+
// Transactions in a soft bundle can be individually rejected or deferred, without affecting
634+
// other transactions in the same bundle.
631635
let is_soft_bundle_request = submit_type == SubmitTxType::SoftBundle;
632636

633637
let max_num_transactions = if is_soft_bundle_request {
@@ -701,10 +705,6 @@ impl ValidatorService {
701705
.num_rejected_tx_during_overload
702706
.with_label_values(&[error.as_ref()])
703707
.inc();
704-
// Avoid breaking the soft bundle if one transaction is rejected.
705-
if is_soft_bundle_request {
706-
return Err(error.into());
707-
}
708708
results[idx] = Some(SubmitTxResult::Rejected { error });
709709
continue;
710710
}
@@ -738,15 +738,6 @@ impl ValidatorService {
738738
{
739739
let effects_digest = effects.digest();
740740
if let Ok(executed_data) = self.complete_executed_data(effects, None).await {
741-
// Avoid breaking the soft bundle if one transaction has already been executed.
742-
if is_soft_bundle_request {
743-
return Err(SuiError::UserInputError {
744-
error: UserInputError::AlreadyExecutedInSoftBundleError {
745-
digest: *tx_digest,
746-
},
747-
}
748-
.into());
749-
}
750741
let executed_result = SubmitTxResult::Executed {
751742
effects_digest,
752743
details: Some(executed_data),
@@ -785,15 +776,6 @@ impl ValidatorService {
785776
let effects_digest = effects.digest();
786777
if let Ok(executed_data) = self.complete_executed_data(effects, None).await
787778
{
788-
// Avoid breaking the soft bundle if one transaction has already been executed.
789-
if is_soft_bundle_request {
790-
return Err(SuiError::UserInputError {
791-
error: UserInputError::AlreadyExecutedInSoftBundleError {
792-
digest: *tx_digest,
793-
},
794-
}
795-
.into());
796-
}
797779
let executed_result = SubmitTxResult::Executed {
798780
effects_digest,
799781
details: Some(executed_data),
@@ -803,11 +785,7 @@ impl ValidatorService {
803785
continue;
804786
}
805787
}
806-
// Avoid breaking the soft bundle if one transaction is rejected.
807-
if is_soft_bundle_request {
808-
return Err(e.into());
809-
}
810-
// Otherwise, record the error for the transaction.
788+
// If the transaction has not been executed, record the error for the transaction.
811789
results[idx] = Some(SubmitTxResult::Rejected { error: e });
812790
continue;
813791
}

crates/sui-core/src/unit_tests/submit_transaction_tests.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -577,20 +577,27 @@ async fn test_submit_soft_bundle_transactions_with_already_executed() {
577577
};
578578

579579
// Submit request with batched transactions, using grpc client directly.
580-
let error = test_context
580+
let raw_response = test_context
581581
.client
582582
.client()
583583
.unwrap()
584584
.submit_transaction(request)
585585
.await
586-
.unwrap_err()
587-
.into();
586+
.unwrap()
587+
.into_inner();
588588

589-
// Verify the error is AlreadyExecutedInSoftBundleError.
590-
assert!(matches!(
591-
error,
592-
SuiError::UserInputError {
593-
error: UserInputError::AlreadyExecutedInSoftBundleError { .. }
589+
// First should be already executed, second should be submitted
590+
match &raw_response.results[0].inner {
591+
Some(sui_types::messages_grpc::RawValidatorSubmitStatus::Executed(_)) => {
592+
// Expected: first transaction was already executed
594593
}
595-
));
594+
_ => panic!("Expected Executed status for first transaction"),
595+
}
596+
597+
match &raw_response.results[1].inner {
598+
Some(sui_types::messages_grpc::RawValidatorSubmitStatus::Submitted(_)) => {
599+
// Expected: second transaction was submitted to consensus
600+
}
601+
_ => panic!("Expected Submitted status for second transaction"),
602+
}
596603
}

0 commit comments

Comments
 (0)