tb_rs/protocol/
types.rs

1//! TigerBeetle protocol data types.
2//!
3//! These types match the exact byte layout of the TigerBeetle wire protocol.
4//! All types use `#[repr(C)]` to ensure C-compatible memory layout.
5
6use bitflags::bitflags;
7
8/// TigerBeetle Account (128 bytes).
9///
10/// Accounts are the fundamental unit of accounting in TigerBeetle.
11/// They track debits and credits with pending and posted balances.
12#[repr(C)]
13#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
14pub struct Account {
15    /// Unique identifier for the account.
16    pub id: u128,
17    /// Sum of pending debit transfers.
18    pub debits_pending: u128,
19    /// Sum of posted debit transfers.
20    pub debits_posted: u128,
21    /// Sum of pending credit transfers.
22    pub credits_pending: u128,
23    /// Sum of posted credit transfers.
24    pub credits_posted: u128,
25    /// Opaque user data for external linking (128-bit indexed).
26    pub user_data_128: u128,
27    /// Opaque user data for external linking (64-bit indexed).
28    pub user_data_64: u64,
29    /// Opaque user data for external linking (32-bit indexed).
30    pub user_data_32: u32,
31    /// Reserved for accounting policy primitives.
32    pub reserved: u32,
33    /// The ledger this account belongs to.
34    pub ledger: u32,
35    /// Chart of accounts code describing the account type.
36    pub code: u16,
37    /// Account flags.
38    pub flags: AccountFlags,
39    /// Timestamp when the account was created (set by server).
40    pub timestamp: u64,
41}
42
43const _: () = assert!(std::mem::size_of::<Account>() == 128);
44
45bitflags! {
46    /// Flags for Account configuration.
47    #[repr(transparent)]
48    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
49    pub struct AccountFlags: u16 {
50        /// Link this account with the next in a chain.
51        const LINKED = 1 << 0;
52        /// Enforce that debits do not exceed credits.
53        const DEBITS_MUST_NOT_EXCEED_CREDITS = 1 << 1;
54        /// Enforce that credits do not exceed debits.
55        const CREDITS_MUST_NOT_EXCEED_DEBITS = 1 << 2;
56        /// Enable balance history for this account.
57        const HISTORY = 1 << 3;
58        /// Mark this account as imported (for data migration).
59        const IMPORTED = 1 << 4;
60        /// Mark this account as closed.
61        const CLOSED = 1 << 5;
62    }
63}
64
65/// TigerBeetle Transfer (128 bytes).
66///
67/// Transfers move value between accounts by debiting one and crediting another.
68#[repr(C)]
69#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
70pub struct Transfer {
71    /// Unique identifier for the transfer.
72    pub id: u128,
73    /// Account ID to debit.
74    pub debit_account_id: u128,
75    /// Account ID to credit.
76    pub credit_account_id: u128,
77    /// Amount to transfer.
78    pub amount: u128,
79    /// ID of pending transfer to post or void (0 if not applicable).
80    pub pending_id: u128,
81    /// Opaque user data for external linking (128-bit indexed).
82    pub user_data_128: u128,
83    /// Opaque user data for external linking (64-bit indexed).
84    pub user_data_64: u64,
85    /// Opaque user data for external linking (32-bit indexed).
86    pub user_data_32: u32,
87    /// Timeout in seconds for pending transfers.
88    pub timeout: u32,
89    /// The ledger this transfer operates on.
90    pub ledger: u32,
91    /// Chart of accounts code describing the transfer type.
92    pub code: u16,
93    /// Transfer flags.
94    pub flags: TransferFlags,
95    /// Timestamp when the transfer was created (set by server).
96    pub timestamp: u64,
97}
98
99const _: () = assert!(std::mem::size_of::<Transfer>() == 128);
100
101bitflags! {
102    /// Flags for Transfer configuration.
103    #[repr(transparent)]
104    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
105    pub struct TransferFlags: u16 {
106        /// Link this transfer with the next in a chain.
107        const LINKED = 1 << 0;
108        /// Create a pending (two-phase) transfer.
109        const PENDING = 1 << 1;
110        /// Post a pending transfer.
111        const POST_PENDING_TRANSFER = 1 << 2;
112        /// Void a pending transfer.
113        const VOID_PENDING_TRANSFER = 1 << 3;
114        /// Balance the debit side.
115        const BALANCING_DEBIT = 1 << 4;
116        /// Balance the credit side.
117        const BALANCING_CREDIT = 1 << 5;
118        /// Close the debit account after this transfer.
119        const CLOSING_DEBIT = 1 << 6;
120        /// Close the credit account after this transfer.
121        const CLOSING_CREDIT = 1 << 7;
122        /// Mark this transfer as imported (for data migration).
123        const IMPORTED = 1 << 8;
124    }
125}
126
127/// Account balance at a point in time (128 bytes).
128///
129/// Used for historical balance queries.
130#[repr(C)]
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub struct AccountBalance {
133    /// Pending debits at this timestamp.
134    pub debits_pending: u128,
135    /// Posted debits at this timestamp.
136    pub debits_posted: u128,
137    /// Pending credits at this timestamp.
138    pub credits_pending: u128,
139    /// Posted credits at this timestamp.
140    pub credits_posted: u128,
141    /// Timestamp of this balance snapshot.
142    pub timestamp: u64,
143    /// Reserved for future use.
144    pub reserved: [u8; 56],
145}
146
147impl Default for AccountBalance {
148    fn default() -> Self {
149        Self {
150            debits_pending: 0,
151            debits_posted: 0,
152            credits_pending: 0,
153            credits_posted: 0,
154            timestamp: 0,
155            reserved: [0; 56],
156        }
157    }
158}
159
160const _: () = assert!(std::mem::size_of::<AccountBalance>() == 128);
161
162/// Filter for account-related queries (128 bytes).
163#[repr(C)]
164#[derive(Clone, Copy, Debug)]
165pub struct AccountFilter {
166    /// Account ID to query.
167    pub account_id: u128,
168    /// Filter by user_data_128 (0 for no filter).
169    pub user_data_128: u128,
170    /// Filter by user_data_64 (0 for no filter).
171    pub user_data_64: u64,
172    /// Filter by user_data_32 (0 for no filter).
173    pub user_data_32: u32,
174    /// Filter by code (0 for no filter).
175    pub code: u16,
176    /// Reserved for future use.
177    pub reserved: [u8; 58],
178    /// Minimum timestamp (inclusive, 0 for no filter).
179    pub timestamp_min: u64,
180    /// Maximum timestamp (inclusive, 0 for no filter).
181    pub timestamp_max: u64,
182    /// Maximum number of results.
183    pub limit: u32,
184    /// Query flags.
185    pub flags: AccountFilterFlags,
186}
187
188impl Default for AccountFilter {
189    fn default() -> Self {
190        Self {
191            account_id: 0,
192            user_data_128: 0,
193            user_data_64: 0,
194            user_data_32: 0,
195            code: 0,
196            reserved: [0; 58],
197            timestamp_min: 0,
198            timestamp_max: 0,
199            limit: 0,
200            flags: AccountFilterFlags::empty(),
201        }
202    }
203}
204
205const _: () = assert!(std::mem::size_of::<AccountFilter>() == 128);
206
207bitflags! {
208    /// Flags for AccountFilter queries.
209    #[repr(transparent)]
210    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
211    pub struct AccountFilterFlags: u32 {
212        /// Include debit transfers.
213        const DEBITS = 1 << 0;
214        /// Include credit transfers.
215        const CREDITS = 1 << 1;
216        /// Return results in reverse order.
217        const REVERSED = 1 << 2;
218    }
219}
220
221/// Filter for general queries (64 bytes).
222#[repr(C)]
223#[derive(Clone, Copy, Debug, Default)]
224pub struct QueryFilter {
225    /// Filter by user_data_128 (0 for no filter).
226    pub user_data_128: u128,
227    /// Filter by user_data_64 (0 for no filter).
228    pub user_data_64: u64,
229    /// Filter by user_data_32 (0 for no filter).
230    pub user_data_32: u32,
231    /// Filter by ledger (0 for no filter).
232    pub ledger: u32,
233    /// Filter by code (0 for no filter).
234    pub code: u16,
235    /// Reserved for future use.
236    pub reserved: [u8; 6],
237    /// Minimum timestamp (inclusive, 0 for no filter).
238    pub timestamp_min: u64,
239    /// Maximum timestamp (inclusive, 0 for no filter).
240    pub timestamp_max: u64,
241    /// Maximum number of results.
242    pub limit: u32,
243    /// Query flags.
244    pub flags: QueryFilterFlags,
245}
246
247const _: () = assert!(std::mem::size_of::<QueryFilter>() == 64);
248
249bitflags! {
250    /// Flags for QueryFilter queries.
251    #[repr(transparent)]
252    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
253    pub struct QueryFilterFlags: u32 {
254        /// Return results in reverse order.
255        const REVERSED = 1 << 0;
256    }
257}
258
259/// Result of a create_accounts operation (8 bytes).
260#[repr(C)]
261#[derive(Clone, Copy, Debug)]
262pub struct CreateAccountsResult {
263    /// Index of the account in the request batch.
264    pub index: u32,
265    /// Result code for this account.
266    pub result: CreateAccountResult,
267}
268
269const _: () = assert!(std::mem::size_of::<CreateAccountsResult>() == 8);
270
271/// Result of a create_transfers operation (8 bytes).
272#[repr(C)]
273#[derive(Clone, Copy, Debug)]
274pub struct CreateTransfersResult {
275    /// Index of the transfer in the request batch.
276    pub index: u32,
277    /// Result code for this transfer.
278    pub result: CreateTransferResult,
279}
280
281const _: () = assert!(std::mem::size_of::<CreateTransfersResult>() == 8);
282
283/// Register request body (256 bytes).
284#[repr(C)]
285#[derive(Clone, Copy, Debug)]
286pub struct RegisterRequest {
287    /// Batch size limit (0 for clients, >0 for prepares).
288    pub batch_size_limit: u32,
289    /// Reserved for future use.
290    pub reserved: [u8; 252],
291}
292
293impl Default for RegisterRequest {
294    fn default() -> Self {
295        Self {
296            batch_size_limit: 0,
297            reserved: [0; 252],
298        }
299    }
300}
301
302const _: () = assert!(std::mem::size_of::<RegisterRequest>() == 256);
303
304/// Register result body (64 bytes).
305#[repr(C)]
306#[derive(Clone, Copy, Debug)]
307pub struct RegisterResult {
308    /// Maximum body size for requests.
309    pub batch_size_limit: u32,
310    /// Reserved for future use.
311    pub reserved: [u8; 60],
312}
313
314impl Default for RegisterResult {
315    fn default() -> Self {
316        Self {
317            batch_size_limit: 0,
318            reserved: [0; 60],
319        }
320    }
321}
322
323const _: () = assert!(std::mem::size_of::<RegisterResult>() == 64);
324
325/// Create account result codes.
326///
327/// These match the exact values from the TigerBeetle protocol.
328#[repr(u32)]
329#[derive(Clone, Copy, Debug, Eq, PartialEq)]
330pub enum CreateAccountResult {
331    /// Account created successfully.
332    Ok = 0,
333    /// A linked event in the batch failed, so this event was not applied.
334    LinkedEventFailed = 1,
335    /// A linked event chain was not closed properly.
336    LinkedEventChainOpen = 2,
337    /// The timestamp field must be zero (server assigns timestamps).
338    TimestampMustBeZero = 3,
339    /// A reserved field was set to a non-zero value.
340    ReservedField = 4,
341    /// A reserved flag was set.
342    ReservedFlag = 5,
343    /// Account ID must not be zero.
344    IdMustNotBeZero = 6,
345    /// Account ID must not be `u128::MAX`.
346    IdMustNotBeIntMax = 7,
347    /// Mutually exclusive flags were set together.
348    FlagsAreMutuallyExclusive = 8,
349    /// `debits_pending` must be zero on creation.
350    DebitsPendingMustBeZero = 9,
351    /// `debits_posted` must be zero on creation.
352    DebitsPostedMustBeZero = 10,
353    /// `credits_pending` must be zero on creation.
354    CreditsPendingMustBeZero = 11,
355    /// `credits_posted` must be zero on creation.
356    CreditsPostedMustBeZero = 12,
357    /// Ledger must not be zero.
358    LedgerMustNotBeZero = 13,
359    /// Code must not be zero.
360    CodeMustNotBeZero = 14,
361    /// Account exists with different flags.
362    ExistsWithDifferentFlags = 15,
363    /// Account exists with different `user_data_128`.
364    ExistsWithDifferentUserData128 = 16,
365    /// Account exists with different `user_data_64`.
366    ExistsWithDifferentUserData64 = 17,
367    /// Account exists with different `user_data_32`.
368    ExistsWithDifferentUserData32 = 18,
369    /// Account exists with different ledger.
370    ExistsWithDifferentLedger = 19,
371    /// Account exists with different code.
372    ExistsWithDifferentCode = 20,
373    /// Account already exists (idempotent success).
374    Exists = 21,
375    /// Expected an imported event but `IMPORTED` flag not set.
376    ImportedEventExpected = 22,
377    /// `IMPORTED` flag set but not in import mode.
378    ImportedEventNotExpected = 23,
379    /// Imported event timestamp is out of valid range.
380    ImportedEventTimestampOutOfRange = 24,
381    /// Imported event timestamp must not advance beyond current.
382    ImportedEventTimestampMustNotAdvance = 25,
383    /// Imported event timestamp must not regress.
384    ImportedEventTimestampMustNotRegress = 26,
385}
386
387/// Create transfer result codes.
388///
389/// These match the exact values from the TigerBeetle protocol.
390#[repr(u32)]
391#[derive(Clone, Copy, Debug, Eq, PartialEq)]
392pub enum CreateTransferResult {
393    /// Transfer created successfully.
394    Ok = 0,
395    /// A linked event in the batch failed, so this event was not applied.
396    LinkedEventFailed = 1,
397    /// A linked event chain was not closed properly.
398    LinkedEventChainOpen = 2,
399    /// The timestamp field must be zero (server assigns timestamps).
400    TimestampMustBeZero = 3,
401    /// A reserved flag was set.
402    ReservedFlag = 4,
403    /// Transfer ID must not be zero.
404    IdMustNotBeZero = 5,
405    /// Transfer ID must not be `u128::MAX`.
406    IdMustNotBeIntMax = 6,
407    /// Mutually exclusive flags were set together.
408    FlagsAreMutuallyExclusive = 7,
409    /// Debit account ID must not be zero.
410    DebitAccountIdMustNotBeZero = 8,
411    /// Debit account ID must not be `u128::MAX`.
412    DebitAccountIdMustNotBeIntMax = 9,
413    /// Credit account ID must not be zero.
414    CreditAccountIdMustNotBeZero = 10,
415    /// Credit account ID must not be `u128::MAX`.
416    CreditAccountIdMustNotBeIntMax = 11,
417    /// Debit and credit accounts must be different.
418    AccountsMustBeDifferent = 12,
419    /// `pending_id` must be zero for non-posting/voiding transfers.
420    PendingIdMustBeZero = 13,
421    /// `pending_id` must not be zero for posting/voiding transfers.
422    PendingIdMustNotBeZero = 14,
423    /// `pending_id` must not be `u128::MAX`.
424    PendingIdMustNotBeIntMax = 15,
425    /// `pending_id` must be different from the transfer ID.
426    PendingIdMustBeDifferent = 16,
427    /// Timeout is only valid for pending transfers.
428    TimeoutReservedForPendingTransfer = 17,
429    // 18 is deprecated (amount_must_not_be_zero)
430    /// Ledger must not be zero.
431    LedgerMustNotBeZero = 19,
432    /// Code must not be zero.
433    CodeMustNotBeZero = 20,
434    /// Debit account not found.
435    DebitAccountNotFound = 21,
436    /// Credit account not found.
437    CreditAccountNotFound = 22,
438    /// Debit and credit accounts must have the same ledger.
439    AccountsMustHaveTheSameLedger = 23,
440    /// Transfer ledger must match the accounts' ledger.
441    TransferMustHaveTheSameLedgerAsAccounts = 24,
442    /// Referenced pending transfer not found.
443    PendingTransferNotFound = 25,
444    /// Referenced transfer is not pending.
445    PendingTransferNotPending = 26,
446    /// Pending transfer has different debit account.
447    PendingTransferHasDifferentDebitAccountId = 27,
448    /// Pending transfer has different credit account.
449    PendingTransferHasDifferentCreditAccountId = 28,
450    /// Pending transfer has different ledger.
451    PendingTransferHasDifferentLedger = 29,
452    /// Pending transfer has different code.
453    PendingTransferHasDifferentCode = 30,
454    /// Post amount exceeds pending transfer amount.
455    ExceedsPendingTransferAmount = 31,
456    /// Pending transfer has different amount (for void).
457    PendingTransferHasDifferentAmount = 32,
458    /// Pending transfer was already posted.
459    PendingTransferAlreadyPosted = 33,
460    /// Pending transfer was already voided.
461    PendingTransferAlreadyVoided = 34,
462    /// Pending transfer has expired.
463    PendingTransferExpired = 35,
464    /// Transfer exists with different flags.
465    ExistsWithDifferentFlags = 36,
466    /// Transfer exists with different debit account.
467    ExistsWithDifferentDebitAccountId = 37,
468    /// Transfer exists with different credit account.
469    ExistsWithDifferentCreditAccountId = 38,
470    /// Transfer exists with different amount.
471    ExistsWithDifferentAmount = 39,
472    /// Transfer exists with different `pending_id`.
473    ExistsWithDifferentPendingId = 40,
474    /// Transfer exists with different `user_data_128`.
475    ExistsWithDifferentUserData128 = 41,
476    /// Transfer exists with different `user_data_64`.
477    ExistsWithDifferentUserData64 = 42,
478    /// Transfer exists with different `user_data_32`.
479    ExistsWithDifferentUserData32 = 43,
480    /// Transfer exists with different timeout.
481    ExistsWithDifferentTimeout = 44,
482    /// Transfer exists with different code.
483    ExistsWithDifferentCode = 45,
484    /// Transfer already exists (idempotent success).
485    Exists = 46,
486    /// Transfer would overflow debit account's `debits_pending`.
487    OverflowsDebitsPending = 47,
488    /// Transfer would overflow credit account's `credits_pending`.
489    OverflowsCreditsPending = 48,
490    /// Transfer would overflow debit account's `debits_posted`.
491    OverflowsDebitsPosted = 49,
492    /// Transfer would overflow credit account's `credits_posted`.
493    OverflowsCreditsPosted = 50,
494    /// Transfer would overflow debit account's total debits.
495    OverflowsDebits = 51,
496    /// Transfer would overflow credit account's total credits.
497    OverflowsCredits = 52,
498    /// Transfer timeout would overflow.
499    OverflowsTimeout = 53,
500    /// Transfer exceeds credit account's available credits.
501    ExceedsCredits = 54,
502    /// Transfer exceeds debit account's available debits.
503    ExceedsDebits = 55,
504    /// Expected an imported event but `IMPORTED` flag not set.
505    ImportedEventExpected = 56,
506    /// `IMPORTED` flag set but not in import mode.
507    ImportedEventNotExpected = 57,
508    /// Imported event timestamp is out of valid range.
509    ImportedEventTimestampOutOfRange = 58,
510    /// Imported event timestamp must not advance beyond current.
511    ImportedEventTimestampMustNotAdvance = 59,
512    /// Imported event timestamp must not regress.
513    ImportedEventTimestampMustNotRegress = 60,
514    /// Imported event timestamp must postdate the debit account.
515    ImportedEventTimestampMustPostdateDebitAccount = 61,
516    /// Imported event timestamp must postdate the credit account.
517    ImportedEventTimestampMustPostdateCreditAccount = 62,
518    /// Imported event timeout must be zero.
519    ImportedEventTimeoutMustBeZero = 63,
520    /// Closing transfer must reference a pending transfer.
521    ClosingTransferMustBePending = 64,
522    /// Debit account is already closed.
523    DebitAccountAlreadyClosed = 65,
524    /// Credit account is already closed.
525    CreditAccountAlreadyClosed = 66,
526    /// Transfer exists with different ledger.
527    ExistsWithDifferentLedger = 67,
528    /// This ID was previously used in a failed transfer.
529    IdAlreadyFailed = 68,
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    #[test]
537    fn test_account_size() {
538        assert_eq!(std::mem::size_of::<Account>(), 128);
539        assert_eq!(std::mem::align_of::<Account>(), 16);
540    }
541
542    #[test]
543    fn test_transfer_size() {
544        assert_eq!(std::mem::size_of::<Transfer>(), 128);
545        assert_eq!(std::mem::align_of::<Transfer>(), 16);
546    }
547
548    #[test]
549    fn test_account_balance_size() {
550        assert_eq!(std::mem::size_of::<AccountBalance>(), 128);
551    }
552
553    #[test]
554    fn test_account_filter_size() {
555        assert_eq!(std::mem::size_of::<AccountFilter>(), 128);
556    }
557
558    #[test]
559    fn test_query_filter_size() {
560        assert_eq!(std::mem::size_of::<QueryFilter>(), 64);
561    }
562
563    #[test]
564    fn test_create_accounts_result_size() {
565        assert_eq!(std::mem::size_of::<CreateAccountsResult>(), 8);
566    }
567
568    #[test]
569    fn test_create_transfers_result_size() {
570        assert_eq!(std::mem::size_of::<CreateTransfersResult>(), 8);
571    }
572
573    #[test]
574    fn test_register_request_size() {
575        assert_eq!(std::mem::size_of::<RegisterRequest>(), 256);
576    }
577
578    #[test]
579    fn test_register_result_size() {
580        assert_eq!(std::mem::size_of::<RegisterResult>(), 64);
581    }
582
583    #[test]
584    fn test_account_flags() {
585        let flags = AccountFlags::LINKED | AccountFlags::HISTORY;
586        assert_eq!(flags.bits(), 0b1001);
587    }
588
589    #[test]
590    fn test_transfer_flags() {
591        let flags = TransferFlags::PENDING | TransferFlags::LINKED;
592        assert_eq!(flags.bits(), 0b11);
593    }
594}