sui_rpc_store/schema/
effects.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! `tx_seq` → `StoredEffects`.
5
6use 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
25/// Build a `StoredEffects` row from a transaction's effects and
26/// the set of objects loaded but not modified during execution.
27///
28/// BCS-encode failures here would indicate either OOM or a bug in
29/// the types' `Serialize` impls; we panic rather than thread a
30/// `Result` through every call site.
31pub 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    /// Look up the effects produced by the transaction at the
42    /// given assigned `tx_seq`, along with the set of objects
43    /// loaded during execution but not modified by the tx.
44    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}