Skip to content

Commit

Permalink
feat: add two-phase transfer exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
emschwartz committed Jun 20, 2024
1 parent 70783db commit 77ca3dc
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ You might also be interested in reading more in the [TigerBeetle docs](https://d
- Linked transfers
- Multi-debit, multi-credit transfers
- Currency exchange
- Two-phase transfers (pending, posting, voiding, timeouts, and partial amounts)
36 changes: 36 additions & 0 deletions exercises/024_two_phase_transfers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
source ./tools/tb_function.sh

# Sometimes, you want to synchronize a transfer in TigerBeetle with one
# in an external system. This could be a national payment system, a
# bank transfer, or cryptocurrency.

# Two-phase transfers enable you to reserve funds and then execute the
# transfer or cancel it depending on the outcome of the external system.

tb "create_accounts id=2400 code=10 ledger=240,
id=2401 code=10 ledger=240;"

# Let's create two pending transfers using the `pending` flag:
tb "create_transfers id=24000 debit_account_id=2400 credit_account_id=2401 amount=200 ledger=240 code=10 flags=pending,
id=24001 debit_account_id=2401 credit_account_id=2400 amount=100 ledger=240 code=10 flags=pending;"

# If we look up the account balance, we can see the amounts appear as `debits_pending` and `credits_pending`:
tb "lookup_accounts id=2400, id=2401;"

# Now we can execute or "post" the transfer:
tb "create_transfers id=24000 pending_id=24000 flags=post_pending_transfer;"

# Uh oh! That doesn't work. The second transfer is a separate transfer so it needs its own unique ID.

# Can you fix this so that the second transfer is voided?
# Hint: we need to use the `void_pending_transfer` flag.
tb "create_transfers id=24003 pending_id=??? flags=???;"

# Finally, we can look up the accounts again to see their final balances:
tb "lookup_accounts id=2400, id=2401;"

# Note that we don't need to specify the debit_account_id, credit_account_id, amount, ledger, or code
# for the transfers that post or void pending transfers.
# We could if we wanted to -- but they need to be the same as in the pending transfer
# (with the exception of the `amount`, which we'll explain in another exercise).
36 changes: 36 additions & 0 deletions exercises/025_two_phase_transfers_timeout.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/bash
source ./tools/tb_function.sh

# In the previous exercise, we learned about two-phase transfers.
# Sometimes, however, you don't want to keep funds reserved for too long.

# Pending transfers allow you to specify an optional timeout to prevent liquidity from
# being locked up indefinitely.

tb "create_accounts id=2500 code=10 ledger=250,
id=2501 code=10 ledger=250;"

# Let's create two pending transfers, both of which have a timeout:
tb "create_transfers id=25000 debit_account_id=2500 credit_account_id=2501 amount=200 ledger=250 code=10 flags=pending timeout=1,
id=25001 debit_account_id=2500 credit_account_id=2501 amount=100 ledger=250 code=10 flags=pending timeout=86400;"

# Note that timeouts are set in seconds from when the pending transfer is received by the TigerBeetle primary,
# rather than as absolute timestamps.

sleep 2

# Now, we'll try to post both transfers.
# Can you fix this request to post both transfers?
output=$(tb "create_transfers id=25002 pending_id=??? flags=post_pending_transfer,
id=25003 pending_id=25001 flags=???;")
echo "$output"

# Notice that the first transfer returns an error because the pending transfer already timed out.

# We can also check the account balances to see that only the second transfer was posted:
tb "lookup_accounts id=2500, id=2501;"

if [[ $output != *"Failed to create transfer (0): tigerbeetle.CreateTransferResult.pending_transfer_expired."* ]]; then
echo "The first transfer should have timed out!"
exit 1
fi
33 changes: 33 additions & 0 deletions exercises/026_two_phase_transfers_partial.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
source ./tools/tb_function.sh

# In the previous two exercises, we created and posted some two-phase transfers.

# There is one more detail related to two-phase transfers that we haven't covered yet: partial amounts.

tb "create_accounts id=2600 code=10 ledger=260,
id=2601 code=10 ledger=260;"

# Let's create 3 pending transfers:
tb "create_transfers id=26000 debit_account_id=2600 credit_account_id=2601 amount=500 ledger=260 code=10 flags=pending,
id=26001 debit_account_id=2600 credit_account_id=2601 amount=300 ledger=260 code=10 flags=pending,
id=26002 debit_account_id=2600 credit_account_id=2601 amount=100 ledger=260 code=10 flags=pending;"

# Now, we'll post them -- but we're going to specify different amounts:
output=$(tb "create_transfers id=26003 pending_id=26000 amount=0 flags=post_pending_transfer,
id=26004 pending_id=26001 amount=200 flags=post_pending_transfer,
id=26005 pending_id=26002 amount=200 flags=post_pending_transfer;")

# Uh oh! That didn't work!
# The first transfer is actually okay -- specifying an amount of 0 means the full amount is posted.
# The second is also okay, because the amount is less than the pending transfer.
# But that third transfer...
# Can you fix it?

# Once it posts, you can check the account balances to see the results.
tb "lookup_accounts id=2600, id=2601;"

if [[ $output == *"Failed to create transfer (2): tigerbeetle.CreateTransferResult.exceeds_pending_transfer_amount."* ]]; then
echo "The third transfer should have been posted!"
exit 1
fi
20 changes: 20 additions & 0 deletions patches/024_two_phase_transfers.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/exercises/024_two_phase_transfers.sh b/exercises/024_two_phase_transfers.sh
index a340abd..8171019 100755
--- a/exercises/024_two_phase_transfers.sh
+++ b/exercises/024_two_phase_transfers.sh
@@ -19,13 +19,13 @@ tb "create_transfers id=24000 debit_account_id=2400 credit_account_id=2401 amoun
tb "lookup_accounts id=2400, id=2401;"

# Now we can execute or "post" the transfer:
-tb "create_transfers id=24000 pending_id=24000 flags=post_pending_transfer;"
+tb "create_transfers id=24002 pending_id=24000 flags=post_pending_transfer;"

# Uh oh! That doesn't work. The second transfer is a separate transfer so it needs its own unique ID.

# Can you fix this so that the second transfer is voided?
# Hint: we need to use the `void_pending_transfer` flag.
-tb "create_transfers id=24003 pending_id=??? flags=???;"
+tb "create_transfers id=24003 pending_id=24001 flags=void_pending_transfer;"

# Finally, we can look up the accounts again to see their final balances:
tb "lookup_accounts id=2400, id=2401;"
15 changes: 15 additions & 0 deletions patches/025_two_phase_transfers_timeout.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
diff --git a/exercises/025_two_phase_transfers_timeout.sh b/exercises/025_two_phase_transfers_timeout.sh
index 9c66a93..8fa425e 100755
--- a/exercises/025_two_phase_transfers_timeout.sh
+++ b/exercises/025_two_phase_transfers_timeout.sh
@@ -21,8 +21,8 @@ sleep 2

# Now, we'll try to post both transfers.
# Can you fix this request to post both transfers?
-output=$(tb "create_transfers id=25002 pending_id=??? flags=post_pending_transfer,
- id=25003 pending_id=25001 flags=???;")
+output=$(tb "create_transfers id=25002 pending_id=25000 flags=post_pending_transfer,
+ id=25003 pending_id=25001 flags=post_pending_transfer;")
echo "$output"

# Notice that the first transfer returns an error because the pending transfer already timed out.
13 changes: 13 additions & 0 deletions patches/026_two_phase_transfers_partial.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/exercises/026_two_phase_transfers_partial.sh b/exercises/026_two_phase_transfers_partial.sh
index f3dcc0a..555d557 100755
--- a/exercises/026_two_phase_transfers_partial.sh
+++ b/exercises/026_two_phase_transfers_partial.sh
@@ -16,7 +16,7 @@ tb "create_transfers id=26000 debit_account_id=2600 credit_account_id=2601 amoun
# Now, we'll post them -- but we're going to specify different amounts:
output=$(tb "create_transfers id=26003 pending_id=26000 amount=0 flags=post_pending_transfer,
id=26004 pending_id=26001 amount=200 flags=post_pending_transfer,
- id=26005 pending_id=26002 amount=200 flags=post_pending_transfer;")
+ id=26005 pending_id=26002 amount=100 flags=post_pending_transfer;")

# Uh oh! That didn't work!
# The first transfer is actually okay -- specifying an amount of 0 means the full amount is posted.
4 changes: 2 additions & 2 deletions tools/tb_function.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ function tb() {
# You can start the REPL by leaving off the --command argument
# but we want to run specific requests in these exercises, so we'll
# pass the request as an argument to this function.
output=$(./tigerbeetle repl --cluster=0 --addresses=3000 --command="$1" 2>&1)
local output=$(./tigerbeetle repl --cluster=0 --addresses=3000 --command="$1" 2>&1)

prefixed_output=$(echo "$output" | sed "s/^/${bold}[Client]${normal} /")
local prefixed_output=$(echo "$output" | sed "s/^/${bold}[Client]${normal} /")
echo "$prefixed_output"

# For efficiency, TigerBeetle only returns error responses.
Expand Down

0 comments on commit 77ca3dc

Please sign in to comment.