# Consumer contract
```rust
use std::path::Path;
pub const TX_ID_BYTES: usize = 12;
pub const SECTOR_BYTES: u64 = 4_096;
pub const INLINE_LIMIT: usize = 262_144;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TxId;
impl TxId {
pub const fn from_bytes(bytes: [u8; TX_ID_BYTES]) -> Self;
pub const fn as_bytes(&self) -> &[u8; TX_ID_BYTES];
pub const fn into_bytes(self) -> [u8; TX_ID_BYTES];
pub fn for_transaction(transaction: &[u8]) -> Self;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PutOutcome {
Inserted(TxId),
Duplicate(TxId),
}
#[derive(Debug)]
pub enum StoreError {
Io(std::io::Error),
AlreadyExists,
InvalidStore,
StoreFull,
IdCollision(TxId),
OutcomeUnknown(TxId),
ReopenRequired,
CorruptTransaction(TxId),
}
pub struct TransactionStore;
impl TransactionStore {
pub fn create(root: &Path) -> Result<Self, StoreError>;
pub fn open(root: &Path) -> Result<Self, StoreError>;
pub fn put(&self, transaction: &[u8]) -> Result<PutOutcome, StoreError>;
pub fn contains(&self, id: TxId) -> bool;
pub fn get(&self, id: TxId) -> Result<Option<Vec<u8>>, StoreError>;
}
impl std::fmt::Display for StoreError;
impl std::error::Error for StoreError;
impl From<std::io::Error> for StoreError;
```
`TxId` is the first 12 bytes of SHA-256 over the exact transaction. Empty transactions are valid. An already committed equal ID is compared byte for byte: equal bytes return `Duplicate`, while different bytes return `IdCollision`. Deliberately constructing different content with the same 96-bit ID, including concurrent colliding puts, is outside this non-Byzantine store's supported domain.
The private root contains `transactions.dat`, `lookup.dat`, and `payload/<first 2>/<remaining 14>.dat`, where the payload components are the URL-safe unpadded Base64 form of the 12-byte ID. `create` materializes all 4,096 possible two-character payload shard directories and synchronizes `payload/` before creating `transactions.dat`. Shard membership never changes afterward. Inputs through 262,144 bytes are stored inline. Larger inputs use one directly written payload file and one external record; the containing shard is synchronized after a new payload file is written.
Every transaction record begins at a 4,096-byte sector. An inline record is a one-byte zero kind, little-endian `u64` length, exact bytes, and zero padding through its allocated sectors. An external record is one full sector containing a one-byte kind of one and the length. `lookup.dat` is only consecutive 16-byte entries containing a 12-byte ID and little-endian `u32` sector index. It is loaded completely into an in-memory map at open. A partial final lookup entry is discarded; transaction and payload files are not scanned or repaired.
`put` accepts only fully resident bytes. It hashes first, allocates disjoint sectors under a brief counter lock, writes positionally, synchronizes data, then directly appends and synchronizes one lookup entry before publishing the map entry. Concurrent equal transactions may leave unused records or payload bytes; final publication returns `Duplicate`. No orphan is reclaimed. A lookup-publication failure returns `OutcomeUnknown` and disables later puts through that handle with `ReopenRequired`; reopening resolves complete entries. `Inserted` is returned only after payload, transaction record, and lookup entry durability.
`get` returns `None` for an absent ID. Otherwise it allocates and returns the complete transaction, hashes it once, and returns `CorruptTransaction` for an invalid record, wrong length, missing payload, or ID mismatch. `contains` is only an in-memory lookup.
`TransactionStore` is `Send + Sync`. Concurrent calls through one instance are supported. Hashing, transaction writes, payload writes, and reads are independent; only sector allocation and final lookup publication are serialized. The package starts no threads and has no queue, batching, polling, retry, flush, close, maintenance, streaming, deletion, or compaction API. `Drop` performs no durability work. Concurrent writers through separately opened instances or processes, external file mutation, network filesystems, transactions too large for memory, and adversarial hash collisions are unsupported.
`open` performs O(number of lookup entries) sequential work and memory without reading transaction bytes. `contains` has average O(1) work and no I/O. `put` and `get` perform O(transaction length) work; `get` allocates exactly the returned transaction length, while an inline put allocates its sector-padded record and an external put does not make another transaction-sized copy. Filesystem operations have no timeout and may block with the local device. The store supports at most 2^32 transaction-file sectors, or 16 TiB.