Skip to content

Commit

Permalink
docs: move two-phase transfers out of reference and into guide
Browse files Browse the repository at this point in the history
  • Loading branch information
emschwartz committed Mar 4, 2024
1 parent 6884113 commit 2f0b8b6
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 129 deletions.
111 changes: 57 additions & 54 deletions docs/design/two-phase-transfers.md
@@ -1,69 +1,72 @@
# Two-phase transfers
# Two-Phase Transfers

A two-phase transfer moves funds in stages:

1. Reserve funds ([pending](#reserve-funds-pending-transfer))
2. Resolve funds ([post](#post-pending-transfer), [void](#void-pending-transfer), or [timeout](#timeout))

The name "two-phase transfer" is a reference to the [two-phase commit
protocol for distributed
transactions](https://en.wikipedia.org/wiki/Two-phase_commit_protocol).

Single-phase transfers post funds to accounts immediately when they
are created. That is, they increase the
[`debits_posted`](../reference/accounts.md#debits_posted) and
[`credits_posted`](../reference/accounts.md#credits_posted) fields on
respective accounts.
## Reserve Funds (Pending Transfer)

A pending transfer, denoted by [`flags.pending`](../reference/transfers.md#flagspending),
reserves its `amount` in the debit/credit accounts'
[`debits_pending`](../reference/accounts.md#debits_pending)/[`credits_pending`](../reference/accounts.md#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.

In contrast to single-phase transfers, a two-phase transfer moves
funds in stages:
### Post-Pending Transfer

1. First, the pending transfer reserves funds. While reserved, they
cannot be used by either the payer or payee. Only the
[`debits_pending`](../reference/accounts.md#debits_pending) and
[`credits_pending`](../reference/accounts.md#credits_pending) fields are
increased on the relevant accounts at this point.
A post-pending transfer, denoted by [`flags.post_pending_transfer`](../reference/transfers.md#flagspost_pending_transfer),
causes a pending transfer to "post", transferring some or all of the pending transfer's reserved amount to its destination.

2. Later, the application creates another transfer — with either of
the following flags:
* [`post_pending_transfer`](../reference/transfers.md#flagspost_pending_transfer): Move all (or part) of the reserved funds to the pending transfer's destination.
* [`void_pending_transfer`](../reference/transfers.md#flagsvoid_pending_transfer): Revert all of the reserved funds to the original account.
* If the posted [`amount`](../reference/transfers.md#amount) is 0, the full pending transfer's amount is
posted.
* If the posted [`amount`](../reference/transfers.md#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.

## Resolving a two-phase transfer
Additionally, when `flags.post_pending_transfer` is set:

When the pending transfer is resolved (posted or voided), the
[`debits_pending`](../reference/accounts.md#debits_pending) and
[`credits_pending`](../reference/accounts.md#credits_pending) fields
on the respective accounts are decreased by the
[`amount`](../reference/transfers.md#amount) of the **pending** transfer.
* [`pending_id`](../reference/transfers.md#pending_id) must reference a [pending transfer](#reserve-funds-pending-transfer).
* [`flags.void_pending_transfer`](../reference/transfers.md#flagsvoid_pending_transfer) must not be set.

### Post
The following fields may either be zero or they must match the
value of the pending transfer's field:

When the pending transfer is posted,
[`debits_posted`](../reference/accounts.md#debits_posted) and
[`credits_posted`](../reference/accounts.md#credits_posted) fields on
the respective accounts are increased by the **posting** transfer's
[`amount`](../reference/transfers.md#amount) (which cannot exceed the
pending amount, but need not equal the pending amount either).
* [`debit_account_id`](../reference/transfers.md#debit_account_id)
* [`credit_account_id`](../reference/transfers.md#credit_account_id)
* [`ledger`](../reference/transfers.md#ledger)
* [`code`](../reference/transfers.md#code)

#### Post partial pending `amount`
### Void-Pending Transfer

Although an initial [`amount`](../reference/transfers.md#amount) is
reserved when a pending transfer is created, you can set the
[`amount`](../reference/transfers.md#amount) field to that amount *or*
smaller than that initial amount when posting a pending transfer.
A void-pending transfer, denoted by [`flags.void_pending_transfer`](../reference/transfers.md#flagsvoid_pending_transfer),
restores the pending amount its original accounts.
Additionally, when this field is set:

In the event that you post less than the amount you initially
reserved, the rest of the amount not posted reverts back to the
original account.
* [`pending_id`](../reference/transfers.md#pending_id) must reference a [pending transfer](#reserve-funds-pending-transfer).
* [`flags.post_pending_transfer`](../reference/transfers.md#flagspost_pending_transfer) must not be set.

### Void
The following fields may either be zero or they must match the
value of the pending transfer's field:

When the pending transfer is voided,
[`debits_posted`](../reference/accounts.md#debits_posted) and
[`credits_posted`](../reference/accounts.md#credits_posted) are not
modified.
* [`debit_account_id`](../reference/transfers.md#debit_account_id)
* [`credit_account_id`](../reference/transfers.md#credit_account_id)
* [`ledger`](../reference/transfers.md#ledger)
* [`code`](../reference/transfers.md#code)

### Timeout

If a pending transfer is created with a [timeout](../reference/transfers.md#timeout) (which is optional),
then if it has not been posted or voided by the time the timeout
expires, the full amount will be voided.
A pending transfer may optionally be created with a
[timeout](../reference/transfers.md#timeout). If the timeout interval passes
before the transfer is either posted or voided, the full amount will be voided.

### Errors

Expand All @@ -75,7 +78,7 @@ Attempting to resolve a pending transfer more than once will return the applicab
- [`pending_transfer_already_voided`](../reference/operations/create_transfers.md#pending_transfer_already_voided)
- [`pending_transfer_expired`](../reference/operations/create_transfers.md#pending_transfer_expired)

## Interaction with account invariants
## 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
Expand All @@ -85,7 +88,7 @@ or
[`debits_must_not_exceed_credits`](../reference/accounts.md#flagsdebits_must_not_exceed_credits))
to be broken, whether the second step is a post or void.

### Pessimistic pending transfers
### Pessimistic Pending Transfers

If an account with
[`debits_must_not_exceed_credits`](../reference/accounts.md#flagsdebits_must_not_exceed_credits)
Expand All @@ -94,7 +97,7 @@ 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
## All Transfers Are Immutable

To reiterate, completing a two-phase transfer (by either marking it
void or posted) does not involve modifying the pending
Expand All @@ -121,7 +124,7 @@ The following examples show the state of two accounts in three steps:
2. After a pending transfer
3. And after the pending transfer is posted or voided

### Post full pending amount
### Post Full Pending Amount

| Account `A` | | Account `B` | | Transfers | | | |
|------------:|-----------:|------------:|-----------:|:---------------------|:----------------------|-----------:|:------------------------|
Expand All @@ -131,7 +134,7 @@ The following examples show the state of two accounts in three steps:
| 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
### Post Partial Pending Amount

| Account `A` | | Account `B` | | Transfers | | | |
|------------:|-----------:|------------:|-----------:|:---------------------|:----------------------|-----------:|:------------------------|
Expand All @@ -141,7 +144,7 @@ The following examples show the state of two accounts in three steps:
| 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
### Void Pending Transfer

| Account `A` | | Account `B` | | Transfers | | | |
|------------:|-----------:|------------:|-----------:|:---------------------|:----------------------|-----------:|:------------------------|
Expand All @@ -151,7 +154,7 @@ The following examples show the state of two accounts in three steps:
| 123 + `w` | `x` | 123 + `y` | `z` | `A` | `B` | 123 | `pending` |
| `w` | `x` | `y` | `z` | `A` | `B` | 123 | `void_pending_transfer` |

## Client documentation
## Client Documentation

Read more about how two-phase transfers work with each client.

Expand All @@ -160,7 +163,7 @@ Read more about how two-phase transfers work with each client.
* [Java](/src/clients/java/README.md#two-phase-transfers)
* [Node](/src/clients/node/README.md#two-phase-transfers)

## Client samples
## Client Samples

Or take a look at how it works with real code.

Expand Down
79 changes: 4 additions & 75 deletions docs/reference/transfers.md
Expand Up @@ -16,6 +16,10 @@ 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](../design/two-phase-transfers.md).

Fields used by each mode of transfer:

| Field | Single-Phase | Pending | Post-Pending | Void-Pending |
Expand Down Expand Up @@ -43,81 +47,6 @@ TigerBeetle uses the same data structures internally and
externally. This means that sometimes you need to set temporary values
for fields that TigerBeetle, not you (the user), are responsible.

### Single-phase transfer

Single-phase transfers post funds to accounts immediately when they are created.

### Two-phase transfer

A pending transfer followed by a post-pending transfer, void-pending transfer, or a timeout is
called a "two-phase transfer". Unlike a single-phase transfer, a two-phase transfer moves funds in
stages:

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

Attempting to resolve a pending transfer more than once will return the applicable error result:
- [`pending_transfer_already_posted`](../reference/operations/create_transfers.md#pending_transfer_already_posted)
- [`pending_transfer_already_voided`](../reference/operations/create_transfers.md#pending_transfer_already_voided)
- [`pending_transfer_expired`](../reference/operations/create_transfers.md#pending_transfer_expired)

#### Pending transfer

A pending transfer, denoted by [`flags.pending`](#flagspending),
reserves its `amount` in the debit/credit accounts'
[`debits_pending`](./accounts.md#debits_pending)/[`credits_pending`](./accounts.md#credits_pending)
fields respectively, leaving `debits_posted`/`credits_posted` unmodified.

#### Post-pending transfer

A post-pending transfer, denoted by [`flags.post_pending_transfer`](#flagspost_pending_transfer),
causes the corresponding pending transfer (referenced by [`pending_id`](#pending_id)) to "post",
transferring some or all of the pending transfer's reserved amount to its destination, and restoring
(voiding) the remainder (if any) to its origin accounts.

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

* `pending_id` must reference a [pending transfer](#pending-transfer).
* `flags.void_pending_transfer` must not be set.

And the following fields may either be zero, otherwise must match the
value of the pending transfer's field:

* `debit_account_id`
* `credit_account_id`
* `ledger`
* `code`

#### Void-pending transfer

A void-pending transfer, denoted by [`flags.void_pending_transfer`](#flagsvoid_pending_transfer),
causes the pending transfer (referenced by [`pending_id`](#pending_id)) to void. The pending amount
is restored to its original accounts.
Additionally, when this field is set:

* `pending_id` must reference a [pending transfer](#pending-transfer).
* `flags.post_pending_transfer` must not be set.

And the following fields may either be zero, otherwise must match the
value of the pending transfer's field:

* `debit_account_id`
* `credit_account_id`
* `amount`
* `ledger`
* `code`

#### Read more

See the [Two-phase transfers](../design/two-phase-transfers.md) guide
for details, examples, and sample code.

## Fields

### `id`
Expand Down

0 comments on commit 2f0b8b6

Please sign in to comment.