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.
Edit this page