diff --git a/README.md b/README.md index a821078..cba2e1b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/exercises/024_two_phase_transfers.sh b/exercises/024_two_phase_transfers.sh new file mode 100755 index 0000000..9929f97 --- /dev/null +++ b/exercises/024_two_phase_transfers.sh @@ -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). diff --git a/exercises/025_two_phase_transfers_timeout.sh b/exercises/025_two_phase_transfers_timeout.sh new file mode 100755 index 0000000..3a51e06 --- /dev/null +++ b/exercises/025_two_phase_transfers_timeout.sh @@ -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 diff --git a/exercises/026_two_phase_transfers_partial.sh b/exercises/026_two_phase_transfers_partial.sh new file mode 100755 index 0000000..f3dcc0a --- /dev/null +++ b/exercises/026_two_phase_transfers_partial.sh @@ -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 diff --git a/patches/024_two_phase_transfers.patch b/patches/024_two_phase_transfers.patch new file mode 100644 index 0000000..dbe5812 --- /dev/null +++ b/patches/024_two_phase_transfers.patch @@ -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;" diff --git a/patches/025_two_phase_transfers_timeout.patch b/patches/025_two_phase_transfers_timeout.patch new file mode 100644 index 0000000..e772d04 --- /dev/null +++ b/patches/025_two_phase_transfers_timeout.patch @@ -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. diff --git a/patches/026_two_phase_transfers_partial.patch b/patches/026_two_phase_transfers_partial.patch new file mode 100644 index 0000000..4230dd8 --- /dev/null +++ b/patches/026_two_phase_transfers_partial.patch @@ -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. diff --git a/tools/tb_function.sh b/tools/tb_function.sh index 9e9c1d7..4eced8a 100755 --- a/tools/tb_function.sh +++ b/tools/tb_function.sh @@ -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.