sui_rpc_store/schema/
effects.rs1use sui_consistent_store::Protobuf;
7use sui_consistent_store::error::DecodeError;
8use sui_consistent_store::error::Error;
9use sui_consistent_store::reader::Reader;
10use sui_types::effects::TransactionEffects;
11use sui_types::storage::ObjectKey;
12
13use crate::proto::StoredEffects;
14use crate::schema::primitives::U64Be;
15
16pub const NAME: &str = "effects";
17
18pub type Key = U64Be;
19pub type Value = Protobuf<StoredEffects>;
20
21pub fn options(resolver: &sui_consistent_store::CfOptionsResolver) -> rocksdb::Options {
22 resolver.options(NAME)
23}
24
25pub fn store(effects: &TransactionEffects, unchanged_loaded: &[ObjectKey]) -> Value {
32 let bcs = bcs::to_bytes(effects).expect("bcs encode TransactionEffects");
33 let unchanged_loaded_bcs = bcs::to_bytes(unchanged_loaded).expect("bcs encode Vec<ObjectKey>");
34 Protobuf(StoredEffects {
35 bcs: bcs.into(),
36 unchanged_loaded_bcs: unchanged_loaded_bcs.into(),
37 })
38}
39
40impl<R: Reader> super::RpcStoreSchema<R> {
41 pub fn get_effects(
45 &self,
46 tx_seq: u64,
47 ) -> Result<Option<(TransactionEffects, Vec<ObjectKey>)>, Error> {
48 let Some(stored) = self.effects.get(&U64Be(tx_seq))? else {
49 return Ok(None);
50 };
51 let stored = stored.into_inner();
52 let effects: TransactionEffects = bcs::from_bytes(&stored.bcs)
53 .map_err(|e| DecodeError::with_source("bcs decode TransactionEffects", e))?;
54 let unchanged_loaded: Vec<ObjectKey> = bcs::from_bytes(&stored.unchanged_loaded_bcs)
55 .map_err(|e| DecodeError::with_source("bcs decode Vec<ObjectKey>", e))?;
56 Ok(Some((effects, unchanged_loaded)))
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use sui_consistent_store::Db;
63 use sui_consistent_store::DbOptions;
64 use sui_types::base_types::ObjectID;
65 use sui_types::base_types::SequenceNumber;
66
67 use super::*;
68 use crate::RpcStoreSchema;
69
70 fn fresh_db() -> (tempfile::TempDir, sui_consistent_store::Db, RpcStoreSchema) {
71 let dir = tempfile::tempdir().unwrap();
72 let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
73 (dir, db, schema)
74 }
75
76 fn dummy_unchanged_loaded() -> Vec<ObjectKey> {
77 vec![
78 ObjectKey(ObjectID::random(), SequenceNumber::from_u64(7)),
79 ObjectKey(ObjectID::random(), SequenceNumber::from_u64(13)),
80 ]
81 }
82
83 #[test]
84 fn get_returns_none_for_unknown_seq() {
85 let (_dir, _db, schema) = fresh_db();
86 assert!(schema.get_effects(7).unwrap().is_none());
87 }
88
89 #[test]
90 fn store_then_get_round_trips() {
91 let (_dir, db, schema) = fresh_db();
92 let effects = TransactionEffects::default();
93 let loaded = dummy_unchanged_loaded();
94
95 let mut batch = db.batch();
96 batch
97 .put(&schema.effects, &U64Be(42), &store(&effects, &loaded))
98 .unwrap();
99 batch.commit().unwrap();
100
101 let (read_effects, read_loaded) = schema.get_effects(42).unwrap().expect("effects present");
102 assert_eq!(read_effects, effects);
103 assert_eq!(read_loaded, loaded);
104 }
105
106 #[test]
107 fn empty_unchanged_loaded_round_trips() {
108 let (_dir, db, schema) = fresh_db();
109 let effects = TransactionEffects::default();
110
111 let mut batch = db.batch();
112 batch
113 .put(&schema.effects, &U64Be(42), &store(&effects, &[]))
114 .unwrap();
115 batch.commit().unwrap();
116
117 let (_, read_loaded) = schema.get_effects(42).unwrap().expect("effects present");
118 assert!(read_loaded.is_empty());
119 }
120
121 #[test]
122 fn overwrite_replaces_previous() {
123 let (_dir, db, schema) = fresh_db();
124 let effects = TransactionEffects::default();
125 let first = dummy_unchanged_loaded();
126 let later = dummy_unchanged_loaded();
127
128 let mut batch = db.batch();
129 batch
130 .put(&schema.effects, &U64Be(42), &store(&effects, &first))
131 .unwrap();
132 batch
133 .put(&schema.effects, &U64Be(42), &store(&effects, &later))
134 .unwrap();
135 batch.commit().unwrap();
136
137 let (_, read_loaded) = schema.get_effects(42).unwrap().expect("effects present");
138 assert_eq!(read_loaded, later);
139 }
140}