Transfers
A transfer
is an immutable record of a financial transaction between
two accounts.
In TigerBeetle, financial transactions are called "transfers" instead of "transactions" because the latter term is heavily overloaded in the context of databases.
Updates
Transfers cannot be modified after creation.
Modes
Transfers can either be Single-Phase, where they are executed immediately, or Two-Phase, where they are first put in a Pending state and then either Posted or Voided. For more details on the latter, see the Two-Phase Transfer guide.
Fields used by each mode of transfer:
Field | Single-Phase | Pending | Post-Pending | Void-Pending |
---|---|---|---|---|
id | required | required | required | required |
debit_account_id | required | required | optional | optional |
credit_account_id | required | required | optional | optional |
amount | required | required | optional | optional |
pending_id | none | none | required | required |
user_data_128 | optional | optional | optional | optional |
user_data_64 | optional | optional | optional | optional |
user_data_32 | optional | optional | optional | optional |
timeout | none | optional | none | none |
ledger | required | required | optional | optional |
code | required | required | optional | optional |
flags.linked | optional | optional | optional | optional |
flags.pending | false | true | false | false |
flags.post_pending_transfer | false | false | true | false |
flags.void_pending_transfer | false | false | false | true |
flags.balancing_debit | optional | optional | false | false |
flags.balancing_credit | optional | optional | false | false |
timestamp | none | none | none | none |
Fields
id
This is a unique identifier for the transaction.
Constraints:
- Type is 128-bit unsigned integer (16 bytes)
- Must not be zero or
2^128 - 1
- Must not conflict with another transfer in the cluster
See the id
section in the data modeling doc for more
recommendations on choosing an ID scheme.
Note that transfer IDs are unique for the cluster -- not the ledger. If you want to store a
relationship between multiple transfers, such as indicating that multiple transfers on different
ledgers were part of a single transaction, you should store a transaction ID in one of the
user_data
fields.
debit_account_id
This refers to the account to debit the transfer's amount
.
Constraints:
- Type is 128-bit unsigned integer (16 bytes)
- When
flags.post_pending_transfer
andflags.void_pending_transfer
are unset:- Must match an existing account
- Must not be the same as
credit_account_id
- When
flags.post_pending_transfer
orflags.void_pending_transfer
are set:- If
debit_account_id
is zero, it will be automatically set to the pending transfer'sdebit_account_id
. - If
debit_account_id
is nonzero, it must match the corresponding pending transfer'sdebit_account_id
.
- If
credit_account_id
This refers to the account to credit the transfer's amount
.
Constraints:
- Type is 128-bit unsigned integer (16 bytes)
- When
flags.post_pending_transfer
andflags.void_pending_transfer
are unset:- Must match an existing account
- Must not be the same as
debit_account_id
- When
flags.post_pending_transfer
orflags.void_pending_transfer
are set:- If
credit_account_id
is zero, it will be automatically set to the pending transfer'scredit_account_id
. - If
credit_account_id
is nonzero, it must match the corresponding pending transfer'scredit_account_id
.
- If
amount
This is how much should be debited from the debit_account_id
account
and credited to the credit_account_id
account.
- When
flags.balancing_debit
is set, this is the maximum amount that will be debited/credited, where the actual transfer amount is determined by the debit account's constraints. - When
flags.balancing_credit
is set, this is the maximum amount that will be debited/credited, where the actual transfer amount is determined by the credit account's constraints.
Constraints:
- Type is 128-bit unsigned integer (16 bytes)
- When
flags.post_pending_transfer
is set:- If
amount
is zero, it will be automatically be set to the pending transfer'samount
. - If
amount
is nonzero, it must be less than or equal to the pending transfer'samount
.
- If
- When
flags.void_pending_transfer
is set:- If
amount
is zero, it will be automatically be set to the pending transfer'samount
. - If
amount
is nonzero, it must be equal to the pending transfer'samount
.
- If
- When
flags.balancing_debit
and/orflags.balancing_credit
is set, ifamount
is zero, it will automatically be set to the maximum amount that does not violate the corresponding account limits. (Equivalent to settingamount = 2^128 - 1
). - When all of the following flags are not set,
amount
must be nonzero:flags.post_pending_transfer
flags.void_pending_transfer
flags.balancing_debit
flags.balancing_credit
Examples
- For representing fractional amounts (e.g.
$12.34
), see Fractional Amounts. - For balancing transfers, see Close Account.
pending_id
If this transfer will post or void a pending transfer, pending_id
references that pending transfer. If this is not a post or void
transfer, it must be zero.
See the section on Two-Phase Transfers for more information on how the pending_id
is used.
Constraints:
- Type is 128-bit unsigned integer (16 bytes)
- Must be zero if neither void nor pending transfer flag is set
- Must match an existing transfer's
id
if non-zero
user_data_128
This is an optional 128-bit secondary identifier to link this transfer to an external entity or event.
As an example, you might generate a TigerBeetle Time-Based Identifier that ties together a group of transfers.
For more information, see Data Modeling.
Constraints:
- Type is 128-bit unsigned integer (16 bytes)
user_data_64
This is an optional 64-bit secondary identifier to link this transfer to an external entity or event.
As an example, you might use this field store an external timestamp.
For more information, see Data Modeling.
Constraints:
- Type is 64-bit unsigned integer (8 bytes)
user_data_32
This is an optional 32-bit secondary identifier to link this transfer to an external entity or event.
As an example, you might use this field to store a timezone or locale.
For more information, see Data Modeling.
Constraints:
- Type is 32-bit unsigned integer (4 bytes)
timeout
This is the interval in seconds after a
pending
transfer's arrival at the cluster
that it may be posted or voided.
Zero denotes absence of timeout.
Non-pending transfers cannot have a timeout.
TigerBeetle makes a best-effort approach to remove pending balances of expired transfers automatically:
Transfers expire exactly at their expiry time (
timestamp
plustimeout
converted in nanoseconds).Pending balances will never be removed before its expiry.
Expired transfers cannot be manually posted or voided.
It is not guaranteed that the pending balance will be removed exactly at its expiry.
In particular, client requests may observe still-pending balances for expired transfers.
Pending balances are removed in chronological order by expiry. If multiple transfers expire at the same time, then ordered by the transfer's creation
timestamp
.If a transfer
A
has expiryE₁
and transferB
has expiryE₂
, andE₁<E₂
, if transferB
had the pending balance removed, then transferA
had the pending balance removed as well.
Constraints:
- Type is 32-bit unsigned integer (4 bytes)
- Must be zero if
flags.pending
is not set
The timeout
is an interval in seconds rather than an absolute timestamp
because this is more robust to clock skew between the cluster and the
application. (Watch this talk on
Detecting Clock Sync Failure in Highly Available Systems
on YouTube for more details.)
ledger
This is an identifier that partitions the sets of accounts that can transact with each other.
See data modeling for more details about how to think about setting up your ledgers.
Constraints:
- Type is 32-bit unsigned integer (4 bytes)
- When
flags.post_pending_transfer
orflags.void_pending_transfer
is set:- If
ledger
is zero, it will be automatically be set to the pending transfer'sledger
. - If
ledger
is nonzero, it must match theledger
value on the pending transfer'sdebit_account_id
andcredit_account_id
.
- If
- When
flags.post_pending_transfer
andflags.void_pending_transfer
are not set:ledger
must not be zero.ledger
must match theledger
value on the accounts referenced indebit_account_id
andcredit_account_id
.
code
This is a user-defined enum denoting the reason for (or category of) the transfer.
Constraints:
- Type is 16-bit unsigned integer (2 bytes)
- When
flags.post_pending_transfer
orflags.void_pending_transfer
is set:- If
code
is zero, it will be automatically be set to the pending transfer'scode
. - If
code
is nonzero, it must match the pending transfer'scode
.
- If
- When
flags.post_pending_transfer
andflags.void_pending_transfer
are not set,code
must not be zero.
flags
This specifies (optional) transfer behavior.
Constraints:
- Type is 16-bit unsigned integer (2 bytes)
- Some flags are mutually exclusive; see
flags_are_mutually_exclusive
.
flags.linked
This flag links the result of this transfer to the outcome of the next transfer in the request such that they will either succeed or fail together.
The last transfer in a chain of linked transfers does not have this flag set.
You can read more about linked events.
Examples
flags.pending
Mark the transfer as a pending transfer.
flags.post_pending_transfer
Mark the transfer as a post-pending transfer.
flags.void_pending_transfer
Mark the transfer as a void-pending transfer.
flags.balancing_debit
Transfer at most amount
— automatically transferring less than amount
as necessary
such that debit_account.debits_pending + debit_account.debits_posted ≤ debit_account.credits_posted
.
If amount
is set to 0
, transfer at most 2^64 - 1
(i.e. as much as possible).
If the highest amount transferable is 0
, returns
exceeds_credits
.
Retrying a balancing transfer will return
exists_with_different_amount
if the amount of the retry differs from the amount that was actually transferred.
The amount
of the recorded transfer is set to the actual amount that was transferred, which is
less than or equal to the amount that was passed to create_transfers
.
flags.balancing_debit
is exclusive with the flags.post_pending_transfer
/flags.void_pending_transfer
flags because posting or voiding a pending transfer will never exceed/overflow either account's limits.
flags.balancing_debit
is compatible with (and orthogonal to) flags.balancing_credit
.
Examples
flags.balancing_credit
Transfer at most amount
— automatically transferring less than amount
as necessary
such that credit_account.credits_pending + credit_account.credits_posted ≤ credit_account.debits_posted
.
If amount
is set to 0
, transfer at most 2^64 - 1
(i.e. as much as possible).
If the highest amount transferable is 0
, returns
exceeds_debits
.
Retrying a balancing transfer will return
exists_with_different_amount
if the amount of the retry differs from the amount that was actually transferred.
The amount
of the recorded transfer is set to the actual amount that was transferred, which is
less than or equal to the amount that was passed to create_transfers
.
flags.balancing_credit
is exclusive with the flags.post_pending_transfer
/flags.void_pending_transfer
flags because posting or voiding a pending transfer will never exceed/overflow either account's limits.
flags.balancing_credit
is compatible with (and orthogonal to) flags.balancing_debit
.
Examples
timestamp
This is the time the transfer was created, as nanoseconds since UNIX epoch.
It is set by TigerBeetle to the moment the transfer arrives at the cluster.
You can read more about Time in TigerBeetle.
Constraints:
- Type is 64-bit unsigned integer (8 bytes)
- Must be set to
0
by the user when theTransfer
is created
Internals
If you're curious and want to learn more, you can find the source code
for this struct in
src/tigerbeetle.zig. Search
for const Transfer = extern struct {
.
You can find the source code for creating a transfer in
src/state_machine.zig. Search
for fn create_transfer(
.