sui_rpc_store/schema/
tx_seq_by_digest.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! `TransactionDigest` → `tx_seq`.
5//!
6//! One half of the digest <-> sequence bijection. The inverse lives
7//! in [`super::tx_metadata_by_seq`].
8//!
9//! Both `Key` and `Value` are thin newtypes (a 32-byte digest and a
10//! varint-encoded `u64`), so no `store` helper is provided —
11//! indexer pipelines stage writes directly via
12//! `batch.put(&schema.tx_seq_by_digest, &Key(digest), &U64Varint(seq))`.
13
14use bytes::Buf;
15use bytes::BufMut;
16use sui_consistent_store::Decode;
17use sui_consistent_store::Encode;
18use sui_consistent_store::error::DecodeError;
19use sui_consistent_store::error::EncodeError;
20use sui_consistent_store::error::Error;
21use sui_consistent_store::reader::Reader;
22use sui_types::digests::TransactionDigest;
23
24use crate::schema::primitives::U64Varint;
25
26pub const NAME: &str = "tx_seq_by_digest";
27
28/// Wrapper around `TransactionDigest` whose encoding is the raw 32
29/// bytes.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub struct Key(pub TransactionDigest);
32
33pub type Value = U64Varint;
34
35impl Encode for Key {
36    fn encode_into<B: BufMut>(&self, buf: &mut B) -> Result<(), EncodeError> {
37        buf.put_slice(self.0.inner());
38        Ok(())
39    }
40}
41
42impl Decode for Key {
43    fn decode<B: Buf>(buf: &mut B) -> Result<Self, DecodeError> {
44        if buf.remaining() != 32 {
45            return Err(DecodeError::msg(format!(
46                "expected 32 bytes for {NAME} Key, got {}",
47                buf.remaining(),
48            )));
49        }
50        let mut bytes = [0u8; 32];
51        buf.copy_to_slice(&mut bytes);
52        Ok(Key(TransactionDigest::new(bytes)))
53    }
54}
55
56pub fn options(resolver: &sui_consistent_store::CfOptionsResolver) -> rocksdb::Options {
57    resolver.options(NAME)
58}
59
60impl<R: Reader> super::RpcStoreSchema<R> {
61    /// Look up the sequence number of the transaction identified
62    /// by `digest`.
63    pub fn get_tx_seq_by_digest(&self, digest: &TransactionDigest) -> Result<Option<u64>, Error> {
64        Ok(self.tx_seq_by_digest.get(&Key(*digest))?.map(|v| v.0))
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use sui_consistent_store::Db;
71    use sui_consistent_store::DbOptions;
72
73    use super::*;
74    use crate::RpcStoreSchema;
75
76    fn fresh_db() -> (tempfile::TempDir, sui_consistent_store::Db, RpcStoreSchema) {
77        let dir = tempfile::tempdir().unwrap();
78        let (db, schema) = Db::open::<RpcStoreSchema>(dir.path(), DbOptions::default()).unwrap();
79        (dir, db, schema)
80    }
81
82    #[test]
83    fn get_returns_none_for_unknown_digest() {
84        let (_dir, _db, schema) = fresh_db();
85        let digest = TransactionDigest::random();
86        assert!(schema.get_tx_seq_by_digest(&digest).unwrap().is_none());
87    }
88
89    #[test]
90    fn put_then_get_round_trips() {
91        let (_dir, db, schema) = fresh_db();
92        let digest = TransactionDigest::random();
93
94        let mut batch = db.batch();
95        batch
96            .put(&schema.tx_seq_by_digest, &Key(digest), &U64Varint(42))
97            .unwrap();
98        batch.commit().unwrap();
99
100        let seq = schema
101            .get_tx_seq_by_digest(&digest)
102            .unwrap()
103            .expect("digest present");
104        assert_eq!(seq, 42);
105    }
106
107    #[test]
108    fn distinct_digests_dont_collide() {
109        let (_dir, db, schema) = fresh_db();
110        let d1 = TransactionDigest::random();
111        let d2 = TransactionDigest::random();
112
113        let mut batch = db.batch();
114        batch
115            .put(&schema.tx_seq_by_digest, &Key(d1), &U64Varint(1))
116            .unwrap();
117        batch
118            .put(&schema.tx_seq_by_digest, &Key(d2), &U64Varint(2))
119            .unwrap();
120        batch.commit().unwrap();
121
122        assert_eq!(schema.get_tx_seq_by_digest(&d1).unwrap(), Some(1));
123        assert_eq!(schema.get_tx_seq_by_digest(&d2).unwrap(), Some(2));
124    }
125}