Two-Phase Transfers
A two-phase transfer moves funds in stages:
The name "two-phase transfer" is a reference to the two-phase commit protocol for distributed transactions.
Reserve Funds (Pending Transfer)
A pending transfer, denoted by flags.pending
,
reserves its amount
in the debit/credit accounts'
debits_pending
/credits_pending
fields, respectively. Pending transfers leave the debits_posted
/credits_posted
unmodified.
Resolve Funds
Pending transfers can be posted, voided, or they may time out.
Post-Pending Transfer
A post-pending transfer, denoted by
flags.post_pending_transfer
, causes a
pending transfer to "post", transferring some or all of the pending transfer's reserved amount to
its destination.
- If the posted
amount
is less than the pending transfer's amount, then only this amount is posted, and the remainder is restored to its original accounts. - If the posted
amount
is equal to the pending transfer's amount or equal toAMOUNT_MAX
(2^128 - 1
), the full pending transfer's amount is posted. - If the posted
amount
is greater than the pending transfer's amount (but less thanAMOUNT_MAX
),exceeds_pending_transfer_amount
is returned.
Client < 0.16.0
Additionally, when flags.post_pending_transfer
is set:
pending_id
must reference a pending transfer.flags.void_pending_transfer
must not be set.
The following fields may either be zero or they must match the value of the pending transfer's field:
Void-Pending Transfer
A void-pending transfer, denoted by
flags.void_pending_transfer
, restores
the pending amount its original accounts. Additionally, when this field is set:
pending_id
must reference a pending transfer.flags.post_pending_transfer
must not be set.
The following fields may either be zero or they must match the value of the pending transfer's field:
Expire Pending Transfer
A pending transfer may optionally be created with a timeout. If the timeout interval passes before the transfer is either posted or voided, the transfer expires and the full amount is returned to the original account.
Note that timeout
s are given as intervals, specified in seconds, rather than as absolute
timestamps. For more details on why, read the page about Time in TigerBeetle.
Errors
A pending transfer can only be posted or voided once. It cannot be posted twice or voided then posted, etc.
Attempting to resolve a pending transfer more than once will return the applicable error result:
Interaction with Account Invariants
The pending transfer's amount is reserved in a way that the second step in a two-phase transfer will
never cause the accounts' configured balance invariants
(credits_must_not_exceed_debits
or
debits_must_not_exceed_credits
)
to be broken, whether the second step is a post or void.
Pessimistic Pending Transfers
If an account with
debits_must_not_exceed_credits
has credits_posted = 100
and debits_posted = 70
and a pending transfer is started causing the
account to have debits_pending = 50
, the pending transfer will fail. It will not wait to get to
posted status to fail.
All Transfers Are Immutable
To reiterate, completing a two-phase transfer (by either marking it void or posted) does not involve modifying the pending transfer. Instead you create a new transfer.
The first transfer that is marked pending will always have its pending flag set.
The second transfer will have a
post_pending_transfer
or
void_pending_transfer
flag set and a
pending_id
field set to the
id
of the first transfer. The
id
of the second transfer will be unique, not the same
id
as the initial pending transfer.
Examples
The following examples show the state of two accounts in three steps:
- Initially, before any transfers
- After a pending transfer
- And after the pending transfer is posted or voided
Post Full Pending Amount
Account A | Account B | Transfers | |||||
---|---|---|---|---|---|---|---|
debits | credits | ||||||
pending | posted | pending | posted | debit_account_id | credit_account_id | amount | flags |
w | x | y | z | - | - | - | - |
123 + w | x | 123 + y | z | A | B | 123 | pending |
w | 123 + x | y | 123 + z | A | B | 123 | post_pending_transfer |
Post Partial Pending Amount
Account A | Account B | Transfers | |||||
---|---|---|---|---|---|---|---|
debits | credits | ||||||
pending | posted | pending | posted | debit_account_id | credit_account_id | amount | flags |
w | x | y | z | - | - | - | - |
123 + w | x | 123 + y | z | A | B | 123 | pending |
w | 100 + x | y | 100 + z | A | B | 100 | post_pending_transfer |
Void Pending Transfer
Account A | Account B | Transfers | |||||
---|---|---|---|---|---|---|---|
debits | credits | ||||||
pending | posted | pending | posted | debit_account_id | credit_account_id | amount | flags |
w | x | y | z | - | - | - | - |
123 + w | x | 123 + y | z | A | B | 123 | pending |
w | x | y | z | A | B | 123 | void_pending_transfer |
Client Documentation
Read more about how two-phase transfers work with each client.
Client Samples
Or take a look at how it works with real code.