sui_rpc_store/schema/
transactions.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! `tx_seq` → `StoredTransaction`.
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::signature::GenericSignature;
11use sui_types::transaction::TransactionData;
12
13use crate::proto::StoredTransaction;
14use crate::schema::primitives::U64Be;
15
16pub const NAME: &str = "transactions";
17
18pub type Key = U64Be;
19pub type Value = Protobuf<StoredTransaction>;
20
21pub fn options(resolver: &sui_consistent_store::CfOptionsResolver) -> rocksdb::Options {
22    resolver.options(NAME)
23}
24
25/// Build a `StoredTransaction` row from a transaction's data and
26/// signatures.
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(transaction: &TransactionData, signatures: &[GenericSignature]) -> Value {
32    let transaction_bcs = bcs::to_bytes(transaction).expect("bcs encode TransactionData");
33    let signatures_bcs = bcs::to_bytes(signatures).expect("bcs encode Vec<GenericSignature>");
34    Protobuf(StoredTransaction {
35        transaction_bcs: transaction_bcs.into(),
36        signatures_bcs: signatures_bcs.into(),
37    })
38}
39
40impl<R: Reader> super::RpcStoreSchema<R> {
41    /// Look up the transaction data and signatures at the given
42    /// assigned `tx_seq`.
43    ///
44    /// Callers resolving from a `TransactionDigest` should first
45    /// translate through `tx_seq_by_digest`.
46    pub fn get_transaction(
47        &self,
48        tx_seq: u64,
49    ) -> Result<Option<(TransactionData, Vec<GenericSignature>)>, Error> {
50        let Some(stored) = self.transactions.get(&U64Be(tx_seq))? else {
51            return Ok(None);
52        };
53        let stored = stored.into_inner();
54        let transaction: TransactionData = bcs::from_bytes(&stored.transaction_bcs)
55            .map_err(|e| DecodeError::with_source("bcs decode TransactionData", e))?;
56        let signatures: Vec<GenericSignature> = bcs::from_bytes(&stored.signatures_bcs)
57            .map_err(|e| DecodeError::with_source("bcs decode Vec<GenericSignature>", e))?;
58        Ok(Some((transaction, signatures)))
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use sui_consistent_store::Db;
65    use sui_consistent_store::DbOptions;
66    use sui_types::base_types::FullObjectRef;
67    use sui_types::base_types::SuiAddress;
68    use sui_types::base_types::random_object_ref;
69
70    use super::*;
71    use crate::RpcStoreSchema;
72
73    fn fresh_db() -> (tempfile::TempDir, sui_consistent_store::Db, RpcStoreSchema) {
74        let dir = tempfile::tempdir().unwrap();
75        let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
76        (dir, db, schema)
77    }
78
79    fn dummy_data() -> TransactionData {
80        TransactionData::new_transfer(
81            SuiAddress::ZERO,
82            FullObjectRef::from_fastpath_ref(random_object_ref()),
83            SuiAddress::ZERO,
84            random_object_ref(),
85            1_000_000,
86            1_000,
87        )
88    }
89
90    #[test]
91    fn get_returns_none_for_unknown_seq() {
92        let (_dir, _db, schema) = fresh_db();
93        assert!(schema.get_transaction(7).unwrap().is_none());
94    }
95
96    #[test]
97    fn store_then_get_round_trips() {
98        let (_dir, db, schema) = fresh_db();
99        let data = dummy_data();
100        let sigs: Vec<GenericSignature> = vec![];
101        let expected_data_bcs = bcs::to_bytes(&data).unwrap();
102        let expected_sigs_bcs = bcs::to_bytes(&sigs).unwrap();
103
104        let mut batch = db.batch();
105        batch
106            .put(&schema.transactions, &U64Be(42), &store(&data, &sigs))
107            .unwrap();
108        batch.commit().unwrap();
109
110        let (read_data, read_sigs) = schema
111            .get_transaction(42)
112            .unwrap()
113            .expect("transaction present");
114        assert_eq!(bcs::to_bytes(&read_data).unwrap(), expected_data_bcs);
115        assert_eq!(bcs::to_bytes(&read_sigs).unwrap(), expected_sigs_bcs);
116    }
117
118    #[test]
119    fn overwrite_replaces_previous() {
120        let (_dir, db, schema) = fresh_db();
121        let first = dummy_data();
122        let later = dummy_data();
123        let later_bcs = bcs::to_bytes(&later).unwrap();
124
125        let mut batch = db.batch();
126        batch
127            .put(&schema.transactions, &U64Be(42), &store(&first, &[]))
128            .unwrap();
129        batch
130            .put(&schema.transactions, &U64Be(42), &store(&later, &[]))
131            .unwrap();
132        batch.commit().unwrap();
133
134        let (read_data, _) = schema
135            .get_transaction(42)
136            .unwrap()
137            .expect("transaction present");
138        assert_eq!(bcs::to_bytes(&read_data).unwrap(), later_bcs);
139    }
140}