sui_rpc_store/schema/
primitives.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Key types shared across multiple `sui-rpc-store` CFs.
5//!
6//! Keys used by only one CF live in that CF's own module; only the
7//! types reused across many CFs land here.
8
9use bytes::Buf;
10use bytes::BufMut;
11use move_core_types::account_address::AccountAddress;
12use move_core_types::identifier::Identifier;
13use move_core_types::language_storage::StructTag;
14use move_core_types::language_storage::TypeTag;
15use sui_consistent_store::Decode;
16use sui_consistent_store::Encode;
17use sui_consistent_store::error::DecodeError;
18use sui_consistent_store::error::EncodeError;
19
20/// A `u64` encoded big-endian, suitable for keys whose iteration
21/// order should match numerical order. Shared across CFs keyed by
22/// transaction sequence, checkpoint sequence, and epoch id, and
23/// reused as the value type for the `tx_seq_by_digest` and
24/// `checkpoint_seq_by_digest` lookup CFs.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct U64Be(pub u64);
27
28impl Encode for U64Be {
29    fn encode_into<B: BufMut>(&self, buf: &mut B) -> Result<(), EncodeError> {
30        buf.put_slice(&self.0.to_be_bytes());
31        Ok(())
32    }
33}
34
35impl Decode for U64Be {
36    fn decode<B: Buf>(buf: &mut B) -> Result<Self, DecodeError> {
37        if buf.remaining() != 8 {
38            return Err(DecodeError::msg(format!(
39                "expected 8 bytes for U64Be, got {}",
40                buf.remaining(),
41            )));
42        }
43        Ok(U64Be(buf.get_u64()))
44    }
45}
46
47/// A `u64` encoded as a protobuf-style varint, suitable for
48/// *value* positions where on-disk size matters more than sort
49/// order. Compared to [`U64Be`]: smaller for typical values (1–5
50/// bytes for anything below `2^35`), slightly larger only near the
51/// `u64::MAX` corner (up to 10 bytes), and never sort-stable —
52/// hence values only.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub struct U64Varint(pub u64);
55
56impl Encode for U64Varint {
57    fn encode_into<B: BufMut>(&self, buf: &mut B) -> Result<(), EncodeError> {
58        prost::encoding::encode_varint(self.0, buf);
59        Ok(())
60    }
61}
62
63impl Decode for U64Varint {
64    fn decode<B: Buf>(buf: &mut B) -> Result<Self, DecodeError> {
65        let value = prost::encoding::decode_varint(buf)
66            .map_err(|e| DecodeError::with_source("decode varint", e))?;
67        if buf.has_remaining() {
68            return Err(DecodeError::msg(format!(
69                "expected exact varint length, {} bytes remain",
70                buf.remaining(),
71            )));
72        }
73        Ok(U64Varint(value))
74    }
75}
76
77/// Newtype wrapping `StructTag` with a streaming-friendly
78/// `Encode` / `Decode` pair.
79///
80/// On the wire, the bytes are **exactly** what `bcs::to_bytes`
81/// would produce for the same `StructTag` — encode delegates to
82/// `bcs::to_bytes`, and decode is a hand-rolled streaming parser
83/// that consumes one `StructTag`'s worth of bytes and leaves any
84/// trailing bytes intact. The byte-equivalence to BCS matters
85/// because the sort order of these keys is determined by the
86/// on-disk bytes; layering an extra framing prefix would change
87/// it.
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89pub struct StructTagKey(pub StructTag);
90
91impl Encode for StructTagKey {
92    fn encode_into<B: BufMut>(&self, buf: &mut B) -> Result<(), EncodeError> {
93        let bytes = bcs::to_bytes(&self.0)
94            .map_err(|e| EncodeError::with_source("bcs encode StructTag", e))?;
95        buf.put_slice(&bytes);
96        Ok(())
97    }
98}
99
100impl Decode for StructTagKey {
101    fn decode<B: Buf>(buf: &mut B) -> Result<Self, DecodeError> {
102        let tag = read_struct_tag(buf)?;
103        Ok(StructTagKey(tag))
104    }
105}
106
107/// Newtype wrapping `TypeTag` with the same byte-identical, but
108/// streaming-friendly `Encode` / `Decode` shape as
109/// [`StructTagKey`].
110#[derive(Debug, Clone, PartialEq, Eq, Hash)]
111pub struct TypeTagKey(pub TypeTag);
112
113impl Encode for TypeTagKey {
114    fn encode_into<B: BufMut>(&self, buf: &mut B) -> Result<(), EncodeError> {
115        let bytes = bcs::to_bytes(&self.0)
116            .map_err(|e| EncodeError::with_source("bcs encode TypeTag", e))?;
117        buf.put_slice(&bytes);
118        Ok(())
119    }
120}
121
122impl Decode for TypeTagKey {
123    fn decode<B: Buf>(buf: &mut B) -> Result<Self, DecodeError> {
124        let tag = read_type_tag(buf)?;
125        Ok(TypeTagKey(tag))
126    }
127}
128
129/// Read one `StructTag` from the head of `buf`, advancing past
130/// exactly its bytes.
131pub(crate) fn read_struct_tag<B: Buf>(buf: &mut B) -> Result<StructTag, DecodeError> {
132    if buf.remaining() < AccountAddress::LENGTH {
133        return Err(DecodeError::msg(format!(
134            "StructTag truncated at address: {} bytes left",
135            buf.remaining(),
136        )));
137    }
138    let mut addr = [0u8; AccountAddress::LENGTH];
139    buf.copy_to_slice(&mut addr);
140    let address = AccountAddress::new(addr);
141    let module = read_identifier(buf)?;
142    let name = read_identifier(buf)?;
143    let n_params = read_uleb128(buf)? as usize;
144    let mut type_params = Vec::with_capacity(n_params);
145    for _ in 0..n_params {
146        type_params.push(read_type_tag(buf)?);
147    }
148    Ok(StructTag {
149        address,
150        module,
151        name,
152        type_params,
153    })
154}
155
156/// Read one `TypeTag` from the head of `buf`, advancing past
157/// exactly its bytes.
158pub(crate) fn read_type_tag<B: Buf>(buf: &mut B) -> Result<TypeTag, DecodeError> {
159    let variant = read_uleb128(buf)?;
160    Ok(match variant {
161        0 => TypeTag::Bool,
162        1 => TypeTag::U8,
163        2 => TypeTag::U64,
164        3 => TypeTag::U128,
165        4 => TypeTag::Address,
166        5 => TypeTag::Signer,
167        6 => TypeTag::Vector(Box::new(read_type_tag(buf)?)),
168        7 => TypeTag::Struct(Box::new(read_struct_tag(buf)?)),
169        8 => TypeTag::U16,
170        9 => TypeTag::U32,
171        10 => TypeTag::U256,
172        v => {
173            return Err(DecodeError::msg(format!("unknown TypeTag variant: {v}",)));
174        }
175    })
176}
177
178/// Write a BCS uleb128-encoded `u32`. Always emits the minimal
179/// canonical byte sequence — the longest run of 7-bit groups
180/// needed to represent the value, with no zero-padded tail. This
181/// matches the encoding the [`read_uleb128`] decoder accepts.
182pub(crate) fn write_uleb128<B: BufMut>(value: u32, buf: &mut B) {
183    let mut v = value;
184    while v >= 0x80 {
185        buf.put_u8(((v & 0x7f) as u8) | 0x80);
186        v >>= 7;
187    }
188    buf.put_u8(v as u8);
189}
190
191/// Read a BCS uleb128-encoded `u32`. Matches the canonical
192/// encoding bcs uses (rejects non-canonical zero-padded forms).
193pub(crate) fn read_uleb128<B: Buf>(buf: &mut B) -> Result<u32, DecodeError> {
194    let mut value: u64 = 0;
195    for shift in (0..32).step_by(7) {
196        if !buf.has_remaining() {
197            return Err(DecodeError::msg("uleb128 truncated"));
198        }
199        let byte = buf.get_u8();
200        let digit = byte & 0x7f;
201        value |= u64::from(digit) << shift;
202        if digit == byte {
203            if shift > 0 && digit == 0 {
204                return Err(DecodeError::msg("non-canonical uleb128"));
205            }
206            return u32::try_from(value).map_err(|_| DecodeError::msg("uleb128 overflow"));
207        }
208    }
209    Err(DecodeError::msg("uleb128 overflow"))
210}
211
212/// Read a BCS-encoded `Identifier`: uleb128 length followed by
213/// UTF-8 bytes.
214fn read_identifier<B: Buf>(buf: &mut B) -> Result<Identifier, DecodeError> {
215    let len = read_uleb128(buf)? as usize;
216    if buf.remaining() < len {
217        return Err(DecodeError::msg(format!(
218            "Identifier truncated: need {len} bytes, have {}",
219            buf.remaining(),
220        )));
221    }
222    let bytes = buf.copy_to_bytes(len);
223    let s =
224        std::str::from_utf8(&bytes).map_err(|e| DecodeError::with_source("Identifier utf8", e))?;
225    Identifier::new(s).map_err(|e| DecodeError::with_source("Identifier", e))
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    fn ident(s: &str) -> Identifier {
233        Identifier::new(s).unwrap()
234    }
235
236    fn struct_tag(addr: u8, module: &str, name: &str, params: Vec<TypeTag>) -> StructTag {
237        StructTag {
238            address: AccountAddress::new([addr; 32]),
239            module: ident(module),
240            name: ident(name),
241            type_params: params,
242        }
243    }
244
245    /// Encode through both `StructTagKey::encode` and
246    /// `bcs::to_bytes` and assert the bytes match — the
247    /// byte-identity contract is what preserves on-disk sort order
248    /// against any consumer that historically wrote the same
249    /// `StructTag` via raw BCS.
250    fn assert_struct_tag_byte_identical(tag: &StructTag) {
251        let via_key = StructTagKey(tag.clone()).encode().unwrap();
252        let via_bcs = bcs::to_bytes(tag).unwrap();
253        assert_eq!(via_key, via_bcs);
254    }
255
256    fn round_trip_struct(tag: StructTag) {
257        assert_struct_tag_byte_identical(&tag);
258        let bytes = StructTagKey(tag.clone()).encode().unwrap();
259        let decoded = StructTagKey::decode(&mut &bytes[..]).unwrap();
260        assert_eq!(decoded.0, tag);
261    }
262
263    fn round_trip_type(tag: TypeTag) {
264        let bytes = TypeTagKey(tag.clone()).encode().unwrap();
265        assert_eq!(bytes, bcs::to_bytes(&tag).unwrap());
266        let decoded = TypeTagKey::decode(&mut &bytes[..]).unwrap();
267        assert_eq!(decoded.0, tag);
268    }
269
270    /// Streaming-decode contract: the decoder must consume exactly
271    /// one tag's bytes and leave anything after untouched.
272    #[test]
273    fn struct_tag_leaves_trailing_bytes_intact() {
274        let tag = struct_tag(2, "sui", "SUI", vec![]);
275        let mut bytes = bcs::to_bytes(&tag).unwrap();
276        let trailer = b"trailing payload";
277        bytes.extend_from_slice(trailer);
278
279        let mut cursor: &[u8] = &bytes;
280        let decoded = StructTagKey::decode(&mut cursor).unwrap();
281        assert_eq!(decoded.0, tag);
282        assert_eq!(cursor, trailer);
283    }
284
285    #[test]
286    fn struct_tag_no_type_params() {
287        round_trip_struct(struct_tag(2, "sui", "SUI", vec![]));
288    }
289
290    #[test]
291    fn struct_tag_with_primitive_param() {
292        round_trip_struct(struct_tag(2, "balance", "Balance", vec![TypeTag::U64]));
293    }
294
295    #[test]
296    fn struct_tag_with_nested_struct_param() {
297        // `Coin<0x2::sui::SUI>`.
298        round_trip_struct(struct_tag(
299            2,
300            "coin",
301            "Coin",
302            vec![TypeTag::Struct(Box::new(struct_tag(
303                2,
304                "sui",
305                "SUI",
306                vec![],
307            )))],
308        ));
309    }
310
311    #[test]
312    fn struct_tag_with_vector_of_struct_param() {
313        // `Bag<vector<0x2::sui::SUI>>`.
314        round_trip_struct(struct_tag(
315            2,
316            "bag",
317            "Bag",
318            vec![TypeTag::Vector(Box::new(TypeTag::Struct(Box::new(
319                struct_tag(2, "sui", "SUI", vec![]),
320            ))))],
321        ));
322    }
323
324    #[test]
325    fn type_tag_primitive_variants_round_trip() {
326        for tag in [
327            TypeTag::Bool,
328            TypeTag::U8,
329            TypeTag::U16,
330            TypeTag::U32,
331            TypeTag::U64,
332            TypeTag::U128,
333            TypeTag::U256,
334            TypeTag::Address,
335            TypeTag::Signer,
336        ] {
337            round_trip_type(tag);
338        }
339    }
340
341    #[test]
342    fn type_tag_nested_vectors_round_trip() {
343        round_trip_type(TypeTag::Vector(Box::new(TypeTag::Vector(Box::new(
344            TypeTag::U64,
345        )))));
346    }
347
348    #[test]
349    fn type_tag_decode_rejects_unknown_variant() {
350        // BCS uleb128 11 is one byte (`0x0b`) — past the highest
351        // defined TypeTag variant (U256 at index 10).
352        let err = TypeTagKey::decode(&mut &[0x0bu8][..]).unwrap_err();
353        assert!(
354            err.to_string().contains("unknown TypeTag variant"),
355            "unexpected error: {err}",
356        );
357    }
358}
359
360/// Zero-byte key for singleton CFs. Encodes as the empty byte
361/// string; decoding requires the input to be empty too.
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
363pub struct UnitKey;
364
365impl Encode for UnitKey {
366    fn encode_into<B: BufMut>(&self, _buf: &mut B) -> Result<(), EncodeError> {
367        Ok(())
368    }
369}
370
371impl Decode for UnitKey {
372    fn decode<B: Buf>(buf: &mut B) -> Result<Self, DecodeError> {
373        if buf.has_remaining() {
374            return Err(DecodeError::msg(format!(
375                "expected 0 bytes for UnitKey, got {}",
376                buf.remaining(),
377            )));
378        }
379        Ok(UnitKey)
380    }
381}