Skip to main content

Two-Phase Transfers

A two-phase transfer moves funds in stages:

  1. Reserve funds (pending)
  2. Resolve funds (post, void, or expire)

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 to AMOUNT_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 than AMOUNT_MAX), exceeds_pending_transfer_amount is returned.
Client < 0.16.0
  • If the posted amount is 0, the full pending transfer's amount is posted.
  • If the posted amount is nonzero, then only this amount is posted, and the remainder is restored to its original accounts. It must be less than or equal to the pending transfer's amount.

Additionally, when flags.post_pending_transfer is 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:

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 timeouts 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:

  1. Initially, before any transfers
  2. After a pending transfer
  3. And after the pending transfer is posted or voided

Post Full Pending Amount

Account AAccount BTransfers
debitscredits
pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
wxyz----
123 + wx123 + yzAB123pending
w123 + xy123 + zAB123post_pending_transfer

Post Partial Pending Amount

Account AAccount BTransfers
debitscredits
pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
wxyz----
123 + wx123 + yzAB123pending
w100 + xy100 + zAB100post_pending_transfer

Void Pending Transfer

Account AAccount BTransfers
debitscredits
pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
wxyz----
123 + wx123 + yzAB123pending
wxyzAB123void_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.