Skip to content

Commit 99543bd

Browse files
authored
[epoch] Revert effects that did not reach finality (MystenLabs#2633)
1 parent 9746a53 commit 99543bd

File tree

3 files changed

+151
-2
lines changed

3 files changed

+151
-2
lines changed

crates/sui-core/src/authority/authority_store.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::iter;
12
// Copyright (c) 2022, Mysten Labs, Inc.
23
// SPDX-License-Identifier: Apache-2.0
34
use super::*;
@@ -966,6 +967,82 @@ impl<const ALL_OBJ_VER: bool, S: Eq + Serialize + for<'de> Deserialize<'de>>
966967
Ok(assigned_seq)
967968
}
968969

970+
/// This function is called at the end of epoch for each transaction that's
971+
/// executed locally on the validator but didn't make to the last checkpoint.
972+
/// The effects of the execution is reverted here.
973+
/// The following things are reverted:
974+
/// 1. Certificate and effects are deleted.
975+
/// 2. Latest parent_sync entries for each mutated object are deleted.
976+
/// 3. All new object states are deleted.
977+
/// 4. owner_index table change is reverted.
978+
pub fn revert_state_update(&self, tx_digest: &TransactionDigest) -> SuiResult {
979+
let effects = self.get_effects(tx_digest)?;
980+
let mut write_batch = self.certificates.batch();
981+
write_batch = write_batch.delete_batch(&self.certificates, iter::once(tx_digest))?;
982+
write_batch = write_batch.delete_batch(&self.effects, iter::once(tx_digest))?;
983+
984+
let all_new_refs = effects
985+
.mutated
986+
.iter()
987+
.chain(effects.created.iter())
988+
.chain(effects.unwrapped.iter())
989+
.map(|(r, _)| r)
990+
.chain(effects.deleted.iter())
991+
.chain(effects.wrapped.iter());
992+
write_batch = write_batch.delete_batch(&self.parent_sync, all_new_refs)?;
993+
994+
let all_new_object_keys = effects
995+
.mutated
996+
.iter()
997+
.chain(effects.created.iter())
998+
.chain(effects.unwrapped.iter())
999+
.map(|((id, version, _), _)| ObjectKey(*id, *version));
1000+
write_batch = write_batch.delete_batch(&self.objects, all_new_object_keys)?;
1001+
1002+
// Reverting the change to the owner_index table is most complex.
1003+
// For each newly created (i.e. created and unwrapped) object, the entry in owner_index
1004+
// needs to be deleted; for each mutated object, we need to query the object state of
1005+
// the older version, and then rewrite the entry with the old object info.
1006+
// TODO: Validators should not need to maintain owner_index.
1007+
// This is dependent on https://github.com/MystenLabs/sui/issues/2629.
1008+
let owners_to_delete = effects
1009+
.created
1010+
.iter()
1011+
.chain(effects.unwrapped.iter())
1012+
.chain(effects.mutated.iter())
1013+
.map(|((id, _, _), owner)| (*owner, *id));
1014+
write_batch = write_batch.delete_batch(&self.owner_index, owners_to_delete)?;
1015+
let mutated_objects = effects
1016+
.mutated
1017+
.iter()
1018+
.map(|(r, _)| r)
1019+
.chain(effects.deleted.iter())
1020+
.chain(effects.wrapped.iter())
1021+
.map(|(id, version, _)| {
1022+
ObjectKey(
1023+
*id,
1024+
version
1025+
.decrement()
1026+
.expect("version revert should never fail"),
1027+
)
1028+
});
1029+
let old_objects = self
1030+
.objects
1031+
.multi_get(mutated_objects)?
1032+
.into_iter()
1033+
.map(|obj_opt| {
1034+
let obj = obj_opt.expect("Older object version not found");
1035+
(
1036+
(obj.owner, obj.id()),
1037+
ObjectInfo::new(&obj.compute_object_reference(), &obj),
1038+
)
1039+
});
1040+
write_batch = write_batch.insert_batch(&self.owner_index, old_objects)?;
1041+
1042+
write_batch.write()?;
1043+
Ok(())
1044+
}
1045+
9691046
/// Returns the last entry we have for this object in the parents_sync index used
9701047
/// to facilitate client and authority sync. In turn the latest entry provides the
9711048
/// latest object_reference, and also the latest transaction that has interacted with

crates/sui-core/src/epoch/reconfiguration.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,12 @@ where
7171
next_cp,
7272
"finish_epoch_change called when there are still unprocessed transactions",
7373
);
74-
if checkpoints.extra_transactions.iter().next().is_some() {
75-
// TODO: Revert any tx that's executed but not in the checkpoint.
74+
for (tx_digest, _) in checkpoints.extra_transactions.iter() {
75+
self.state
76+
.database
77+
.revert_state_update(&tx_digest.transaction)?;
7678
}
79+
checkpoints.extra_transactions.clear()?;
7780
// drop checkpoints lock
7881
} else {
7982
unreachable!();

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,75 @@ async fn test_transfer_sui_with_amount() {
14911491
);
14921492
}
14931493

1494+
#[tokio::test]
1495+
async fn test_store_revert_state_update() {
1496+
// This test checks the correctness of revert_state_update in SuiDataStore.
1497+
let (sender, sender_key) = get_key_pair();
1498+
let (recipient, _sender_key) = get_key_pair();
1499+
let gas_object_id = ObjectID::random();
1500+
let gas_object = Object::with_id_owner_for_testing(gas_object_id, sender);
1501+
let gas_object_ref = gas_object.compute_object_reference();
1502+
let authority_state = init_state_with_objects(vec![gas_object.clone()]).await;
1503+
1504+
let tx_data = TransactionData::new_transfer_sui(
1505+
recipient,
1506+
sender,
1507+
None,
1508+
gas_object.compute_object_reference(),
1509+
MAX_GAS,
1510+
);
1511+
let signature = Signature::new(&tx_data, &sender_key);
1512+
let transaction = Transaction::new(tx_data, signature);
1513+
1514+
let certificate = init_certified_transaction(transaction, &authority_state);
1515+
let tx_digest = *certificate.digest();
1516+
authority_state
1517+
.handle_confirmation_transaction(ConfirmationTransaction { certificate })
1518+
.await
1519+
.unwrap();
1520+
1521+
authority_state
1522+
.database
1523+
.revert_state_update(&tx_digest)
1524+
.unwrap();
1525+
assert_eq!(
1526+
authority_state
1527+
.database
1528+
.get_object(&gas_object_id)
1529+
.unwrap()
1530+
.unwrap()
1531+
.owner,
1532+
Owner::AddressOwner(sender),
1533+
);
1534+
assert_eq!(
1535+
authority_state
1536+
.database
1537+
.get_latest_parent_entry(gas_object_id)
1538+
.unwrap()
1539+
.unwrap(),
1540+
(gas_object_ref, TransactionDigest::genesis()),
1541+
);
1542+
assert!(authority_state
1543+
.database
1544+
.get_owner_objects(Owner::AddressOwner(recipient))
1545+
.unwrap()
1546+
.is_empty());
1547+
assert_eq!(
1548+
authority_state
1549+
.database
1550+
.get_owner_objects(Owner::AddressOwner(sender))
1551+
.unwrap()
1552+
.len(),
1553+
1,
1554+
);
1555+
assert!(authority_state
1556+
.database
1557+
.get_certified_transaction(&tx_digest)
1558+
.unwrap()
1559+
.is_none());
1560+
assert!(authority_state.database.get_effects(&tx_digest).is_err());
1561+
}
1562+
14941563
// helpers
14951564

14961565
#[cfg(test)]

0 commit comments

Comments
 (0)