Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
bitcoinleveldb-key
Low-level internal key encoding utilities for the bitcoin-rs LevelDB port. This crate provides a faithful, byte-for-byte reproduction of LevelDB's internal key machinery, specialized for Bitcoin's storage engine.
It focuses on:
- Packing and unpacking LevelDB internal keys
(user_key, sequence_number, value_type)into an opaque byte sequence. - Correct ordering semantics via an
InternalKeyComparatorthat respects both user key order and sequence numbers. - Filter policy adaptation so that user-level Bloom filters remain valid when the DB internally stores extended keys.
- Efficient construction of lookup keys for MemTable and SSTable access.
Design overview
LevelDB (and this port) do not store raw user keys directly. Instead, they use an internal key:
internal_key := user_key || tag
where
tag := pack(sequence_number: 56 bits, value_type: 8 bits)
This design allows multiple versions of the same logical user key to coexist, with different sequence_number values and ValueType variants:
ValueType::TypeValue— key is present with a valueValueType::TypeDeletion— key is logically deleted at that sequence
The sort order for internal keys is:
- First by the user key, using a user-supplied
SliceComparator(or bytewise lexicographic fallback). - For equal user keys, by decreasing sequence number (newest entries first).
This crate encapsulates these invariants so that higher-level components (MemTable, SSTable, DB implementation) can operate safely without re-implementing the encoding logic.
The following invariants are critical and preserved here:
ValueTypeis#[repr(u8)]and the discriminants are stable on disk — they must not change.SequenceNumberis a 64‑bit integer, but only the high 56 bits are used when packed, leaving the lower 8 bits forValueType.- All encoding/decoding paths are little-endian to match LevelDB.
Crate features and components
ValueType
Represents the logical type of a versioned key. The enum values are encoded directly into the low 8 bits of the tag and therefore must remain stable.
ValueType::from_tag(tag: u8) -> Option<Self> maps raw tags back to the enum, enforcing the valid subset {0x00, 0x01}.
Key and Value traits
Minimal traits abstracting over key/value providers that expose their representation as a Slice (defined elsewhere in the bitcoinleveldb ecosystem).
These traits are intentionally narrow: they push policy into higher layers and keep the encoding layer purely about bytes.
ParsedInternalKey
pub type SequenceNumber = u64;
ParsedInternalKey is a structured view of an internal key:
user_key: the raw user key bytessequence: the sequence numberty: theValueType
Key operations:
ParsedInternalKey::new(u: &Slice, seq: &SequenceNumber, t: ValueType) -> SelfParsedInternalKey::default()— uses an empty key andVALUE_TYPE_FOR_SEEKas configured elsewhere.debug_string(&self) -> String— stable debug formatting:'<escaped_user>' @ seq : type_tag.
InternalKey
Represents a fully encoded internal key as a sequence of bytes. The encoding is not UTF‑8 by design; the use of String is purely a compatibility artifact with the original LevelDB.
Key methods:
-
InternalKey::new(user_key: &Slice, s: SequenceNumber, t: ValueType) -> Self
Encodes(user_key, s, t)into therepbuffer usingappend_internal_key. -
fn decode_from(&mut self, s: &Slice) -> bool
Decodes an internal key from raw bytes intoself.rep. Returnsfalsefor empty input. -
fn encode(&self) -> Slice
Returns aSliceview over the internal key bytes. Panics ifrepis empty. -
fn user_key(&self) -> Slice
Extracts the user key portion by stripping the trailing 8‑byte tag. -
fn set_from(&mut self, p: &ParsedInternalKey)/fn clear(&mut self)
Reset or rebuildrepfrom a parsed representation. -
fn debug_string(&self) -> String
Parses the internal representation and renders aParsedInternalKeydebug string, or(bad)<escaped_bytes>if parsing fails.
The Debug implementation for InternalKey uses debug_string() instead of raw bytes, yielding stable, human-meaningful logging.
Encoding helpers
pack_sequence_and_type
Packs seq and t into a single u64:
- High 56 bits:
seq(constrained byMAX_SEQUENCE_NUMBERelsewhere) - Low 8 bits:
t as u8(constrained byVALUE_TYPE_FOR_SEEKelsewhere)
Ensures:
seq <= MAX_SEQUENCE_NUMBERt as u64 <= VALUE_TYPE_FOR_SEEK as u64
internal_key_encoding_length
Returns user_key_len + 8. This is deterministic and matches the C++ implementation.
parse_internal_key
Given an encoded internal key, fills *result with:
user_key= all but last 8 bytessequence= high 56 bits of the decoded tagty=ValueType::from_tag(low_8_bits)
Returns false if:
- The length is
< 8. - The tag is invalid or beyond
VALUE_TYPE_FOR_SEEK.
Caller provides a non-null pointer to a ParsedInternalKey instance.
append_internal_key
Appends the encoded internal key for k onto the provided String buffer:
- Copies raw user key bytes.
- Packs and appends the (sequence, type) tag via
pack_sequence_and_typeandencode_fixed64_le.
Low-level byte utilities
These functions provide self-contained, allocation-free primitives for manipulating key bytes:
encode_fixed64_le(value: u64) -> [u8; 8]anddecode_fixed64_le(ptr: *const u8) -> u64— fixed 64‑bit little-endian encode/decode.put_varint32_vec(dst: &mut Vec<u8>, v: u32)— varint32 encoding into an existingVec<u8>.decode_varint32(src: &[u8]) -> (u32, usize)— decodes a varint32, asserting on malformed input.slice_as_bytes(s: &Slice) -> &[u8]— safe view over aSlice.bytewise_compare(a: &[u8], b: &[u8]) -> i32— lexicographic compare with LevelDB-style{-1,0,1}result.
Key-range shortening helpers, used by compaction and iterators:
-
shorten_separator_user_keys(start: &[u8], limit: &[u8]) -> Option<Vec<u8>>
Attempts to construct a minimal user key betweenstartandlimitby bumping the first differing byte when possible. -
find_short_successor_user_key(key: &mut Vec<u8>) -> bool
Makeskeya short successor by incrementing the first non-0xffbyte and truncating after it.
Debug utility:
escape_for_debug(input: &[u8]) -> String
Renders a byte string using C-style escapes for control and non-ASCII bytes.
extract_user_key
Returns the user key portion from a full internal key. Panics if the key is shorter than 8 bytes.
This is the canonical way to slice away the tag when you know you have a valid internal key.
InternalFilterPolicy
Wraps a user-provided FilterPolicy (e.g., Bloom filter) and transparently converts internal keys into user keys before delegating.
Implements:
FilterPolicyNamedCreateFilterKeyMayMatch
Key behavior:
-
create_filter(&self, keys: *const Slice, n: i32, dst: &mut Vec<u8>)- Rewrites the
keysarray in place: each entry becomes its user key by applyingextract_user_key. - Delegates to
user_policy.create_filterif provided.
- Rewrites the
-
key_may_match(&self, k: &Slice, f: &Slice) -> bool- Extracts
user_keyfrom internal keyk. - Delegates to
user_policy.key_may_match.
- Extracts
-
name(&self) -> Cow<'_, str>- If no user policy is set, returns an empty name; otherwise forwards.
InternalFilterPolicy::new(p: *const dyn FilterPolicy) -> Self constructs a wrapper around p. Passing a null pointer disables filtering.
InternalKeyComparator
Implements the precise ordering semantics for internal keys:
- Compare on user key using
user_comparatorif provided, else bytewise. - If equal, compare the packed
sequence+typetag such that higher sequence numbers sort first.
Implements:
SliceComparatorCompareNamedFindShortSuccessorFindShortestSeparator
Core methods:
compare_slices
- Extracts user key portions using
extract_user_key. - Compares user keys using
user_comparatororbytewise_compare. - If equal, decodes tags via
decode_fixed64_leand orders by descending tag (a_num > b_numyields-1).
compare_internal_key(&self, a: &InternalKey, b: &InternalKey) -> i32 is a convenience wrapper taking InternalKey objects.
Shortening functions
Used during compaction to minimize index key sizes while preserving ordering.
-
find_short_successor(&self, k: &mut Vec<u8>)- Treats
kas an internal-key byte vector. - Extracts and shortens the user key (via user comparator or
find_short_successor_user_key). - Re-appends a tag with
MAX_SEQUENCE_NUMBERandVALUE_TYPE_FOR_SEEK. - Ensures the new key is strictly greater than the original in internal-key order.
- Treats
-
find_shortest_separator(&self, start: &mut Vec<u8>, limit: &[u8])- Uses user-level separator logic to compute a shorter user key between
startandlimit. - Reconstructs a full internal key, again using
MAX_SEQUENCE_NUMBERandVALUE_TYPE_FOR_SEEKfor the tag. - Asserts the new key lies strictly between original
startandlimitin internal-key order.
- Uses user-level separator logic to compute a shorter user key between
InternalKeyComparator::new(c: *const dyn SliceComparator) -> Self creates a comparator; null_slice_comparator() can be used to trigger fallback bytewise logic without providing a concrete comparator.
The name() implementation returns a stable identifier:
"leveldb.InternalKeyComparator"
LookupKey
LookupKey assists with constructing the exact key bytes used when issuing point lookups against MemTables and internal iterators.
The layout encoded in buf is:
varint32(internal_key_len) || user_key || tag
Where internal_key_len = user_key_len + 8.
Construction:
The constructor:
- Varint-encodes the internal key length into
buf. - Appends the raw user key bytes.
- Packs
(sequence, VALUE_TYPE_FOR_SEEK)into a tag and appends it. - Sets raw pointers
start,kstart, andendinsidebuffor fast slicing.
Accessors:
-
memtable_key(&self) -> Slice
Returnsvarint_len || internal_key— used directly as the key in the MemTable. -
internal_key(&self) -> Slice
Returnsuser_key || tag, i.e., the internal key view. -
user_key(&self) -> Slice
Returns the user portion, ensuring there is room for the tag.
The Drop implementation is trivial: the backing Vec and stack storage clean themselves up; raw pointers are only references into that storage and do not own anything.
null_slice_comparator
Constructs a syntactically valid but semantically null trait-object pointer to SliceComparator.
This is never dereferenced intentionally; it exists solely to exercise and validate the "no user comparator provided" code paths. It uses transmute over (0usize, 0usize) for the vtable/data pair and must be handled with care.
Usage examples
Encoding and decoding an internal key
use ;
use Slice; // assuming this is the Slice type used in the project
Using InternalKeyComparator with a bytewise comparator
use ;
use SliceComparator; // your implementation
Building a LookupKey for MemTable access
use ;
use Slice;
Integration notes
- This crate is part of the
bitcoin-rsrepository and is intended to be used in concert with sibling crates providingSlice, filter policies, comparators, MemTable implementations, and SSTable abstractions. - The on-disk format is designed to be stable and compatible with the original LevelDB layout; do not change
ValueTypediscriminants or encoding routines if you care about read-compatibility. - Many functions use
unsafewith raw pointers (Slice, trait-object pointers, etc.). The invariants are mirroring the C++ library; ensure any external uses respect lifetimes and aliasing constraints.
Repository, license, and authors
- Repository: https://github.com/klebs6/bitcoin-rs
- Crate:
bitcoinleveldb-key - Edition: 2021
- License: MIT
- Author:
klebs <none>
If you extend or wrap this crate, preserve the encoding, comparison, and filter semantics to maintain compatibility with existing data and with canonical LevelDB behavior.