forked from ServiceWeaver/weaver
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ported Bank of Anthos. (ServiceWeaver#438)
This PR adds @nipun-sehrawat's port of Bank of Anthos to Service Weaver. I updated the port to work with the latest version of Service Weaver and added a README with some instructions, but otherwise didn't change much. I also added Docker instructions for running BOA.
- Loading branch information
1 parent
af2bdf7
commit fc71056
Showing
43 changed files
with
6,330 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
#!/bin/bash | ||
# Copyright 2020 Google LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http:https://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
# Create demo transactions in the ledger for the demo user accounts. | ||
# | ||
# Gerenated transactions follow a pattern of biweekly large deposits with | ||
# periodic small payments to randomly choosen accounts. | ||
# | ||
# To run, set environment variable USE_DEMO_DATA="True" | ||
|
||
set -u | ||
|
||
|
||
# skip adding transactions if not enabled | ||
if [ -z "$USE_DEMO_DATA" ] && [ "$USE_DEMO_DATA" != "True" ]; then | ||
echo "\$USE_DEMO_DATA not \"True\"; no demo transactions added" | ||
exit 0 | ||
fi | ||
|
||
|
||
# Expected environment variables | ||
readonly ENV_VARS=( | ||
"POSTGRES_DB" | ||
"POSTGRES_USER" | ||
"POSTGRES_PASSWORD" | ||
"LOCAL_ROUTING_NUM" | ||
) | ||
|
||
|
||
add_transaction() { | ||
DATE=$(date -u +"%Y-%m-%d %H:%M:%S.%3N%z" --date="@$(($6))") | ||
echo "adding demo transaction: $1 -> $2" | ||
PGPASSWORD="$POSTGRES_PASSWORD" psql -X -v ON_ERROR_STOP=1 -v fromacct="$1" -v toacct="$2" -v fromroute="$3" -v toroute="$4" -v amount="$5" --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL | ||
INSERT INTO TRANSACTIONS (FROM_ACCT, TO_ACCT, FROM_ROUTE, TO_ROUTE, AMOUNT, TIMESTAMP) | ||
VALUES (:'fromacct', :'toacct', :'fromroute', :'toroute', :'amount', '$DATE'); | ||
EOSQL | ||
} | ||
|
||
|
||
create_transactions() { | ||
PAY_PERIODS=3 | ||
DAYS_BETWEEN_PAY=14 | ||
SECONDS_IN_PAY_PERIOD=$(( 86400 * $DAYS_BETWEEN_PAY )) | ||
DEPOSIT_AMOUNT=250000 | ||
|
||
# create a UNIX timestamp in seconds since the Epoch | ||
START_TIMESTAMP=$(( $(date +%s) - $(( $(($PAY_PERIODS+1)) * $SECONDS_IN_PAY_PERIOD )) )) | ||
|
||
for i in $(seq 1 $PAY_PERIODS); do | ||
# create deposit transaction for each user | ||
for account in ${USER_ACCOUNTS[@]}; do | ||
add_transaction "$EXTERNAL_ACCOUNT" "$account" "$EXTERNAL_ROUTING" "$LOCAL_ROUTING_NUM" $DEPOSIT_AMOUNT $START_TIMESTAMP | ||
done | ||
|
||
# create 15-20 payments between users | ||
TRANSACTIONS_PER_PERIOD=$(shuf -i 15-20 -n1) | ||
for p in $(seq 1 $TRANSACTIONS_PER_PERIOD); do | ||
# randomly generate an amount between $10-$100 | ||
AMOUNT=$(shuf -i 1000-10000 -n1) | ||
|
||
# randomly select a sender and receiver | ||
SENDER_ACCOUNT=${USER_ACCOUNTS[$RANDOM % ${#USER_ACCOUNTS[@]}]} | ||
RECIPIENT_ACCOUNT=${USER_ACCOUNTS[$RANDOM % ${#USER_ACCOUNTS[@]}]} | ||
# if sender equals receiver, send to a random anonymous account | ||
if [[ "$SENDER_ACCOUNT" == "$RECIPIENT_ACCOUNT" ]]; then | ||
RECIPIENT_ACCOUNT=$(shuf -i 1000000000-9999999999 -n1) | ||
fi | ||
|
||
TIMESTAMP=$(( $START_TIMESTAMP + $(( $SECONDS_IN_PAY_PERIOD * $p / $(($TRANSACTIONS_PER_PERIOD + 1 )) )) )) | ||
|
||
add_transaction "$SENDER_ACCOUNT" "$RECIPIENT_ACCOUNT" "$LOCAL_ROUTING_NUM" "$LOCAL_ROUTING_NUM" $AMOUNT $TIMESTAMP | ||
done | ||
|
||
START_TIMESTAMP=$(( $START_TIMESTAMP + $(( $i * $SECONDS_IN_PAY_PERIOD )) )) | ||
done | ||
} | ||
|
||
|
||
create_ledger() { | ||
# Account numbers for users 'testuser', 'alice', 'bob', and 'eve'. | ||
USER_ACCOUNTS=("1011226111" "1033623433" "1055757655" "1077441377") | ||
# Numbers for external account 'External Bank' | ||
EXTERNAL_ACCOUNT="9099791699" | ||
EXTERNAL_ROUTING="808889588" | ||
|
||
create_transactions | ||
} | ||
|
||
|
||
main() { | ||
# Check environment variables are set | ||
for env_var in ${ENV_VARS[@]}; do | ||
if [[ -z "${env_var}" ]]; then | ||
echo "Error: environment variable '$env_var' not set. Aborting." | ||
exit 1 | ||
fi | ||
done | ||
|
||
create_ledger | ||
} | ||
|
||
|
||
main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# Bank of Anthos | ||
|
||
This directory contains a port of Google Cloud's [Bank of Anthos][boa] demo | ||
application. | ||
|
||
```mermaid | ||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% | ||
graph TD | ||
%% Nodes. | ||
github.com/ServiceWeaver/weaver/Main(weaver.Main) | ||
github.com/ServiceWeaver/weaver/examples/bankofanthos/balancereader/T(balancereader.T) | ||
github.com/ServiceWeaver/weaver/examples/bankofanthos/contacts/T(contacts.T) | ||
github.com/ServiceWeaver/weaver/examples/bankofanthos/ledgerwriter/T(ledgerwriter.T) | ||
github.com/ServiceWeaver/weaver/examples/bankofanthos/transactionhistory/T(transactionhistory.T) | ||
github.com/ServiceWeaver/weaver/examples/bankofanthos/userservice/T(userservice.T) | ||
%% Edges. | ||
github.com/ServiceWeaver/weaver/Main --> github.com/ServiceWeaver/weaver/examples/bankofanthos/balancereader/T | ||
github.com/ServiceWeaver/weaver/Main --> github.com/ServiceWeaver/weaver/examples/bankofanthos/contacts/T | ||
github.com/ServiceWeaver/weaver/Main --> github.com/ServiceWeaver/weaver/examples/bankofanthos/ledgerwriter/T | ||
github.com/ServiceWeaver/weaver/Main --> github.com/ServiceWeaver/weaver/examples/bankofanthos/transactionhistory/T | ||
github.com/ServiceWeaver/weaver/Main --> github.com/ServiceWeaver/weaver/examples/bankofanthos/userservice/T | ||
github.com/ServiceWeaver/weaver/examples/bankofanthos/ledgerwriter/T --> github.com/ServiceWeaver/weaver/examples/bankofanthos/balancereader/T | ||
``` | ||
|
||
## Running Locally | ||
|
||
- TODO(mwhittaker): Re-write the app to use the JWT credentials shipped with the | ||
original bank of anthos app. | ||
|
||
First, run and initialize a local [Postgres][postgres] instance. | ||
|
||
1. Create an `admin` user with password `admin`. | ||
2. Create two databases, `postgresdb` and `accountsdb`, both owned by `admin`. | ||
3. Use `postgresdb.sql` and `accountsdb.sql` to initialize the `postgresdb` and | ||
`accountsdb` databases respectively. | ||
4. Run `1_create_transactions.sh` to populate `postgresdb`. | ||
|
||
Note that these scripts were taken from [`ledger-db/initdb/`][ledger-db] and | ||
[`accounts-db/initdb/`][accounts-db]. | ||
|
||
We recommend using Docker to perform these steps: | ||
|
||
```shell | ||
# Run the Postgres instance. | ||
$ docker run \ | ||
--rm \ | ||
--detach \ | ||
--name postgres \ | ||
--env POSTGRES_PASSWORD=password \ | ||
--volume="$(realpath postgres.sh):/app/postgres.sh" \ | ||
--volume="$(realpath postgresdb.sql):/app/postgresdb.sql" \ | ||
--volume="$(realpath accountsdb.sql):/app/accountsdb.sql" \ | ||
--volume="$(realpath 1_create_transactions.sh):/app/1_create_transactions.sh" \ | ||
--publish 127.0.0.1:5432:5432 \ | ||
postgres | ||
|
||
# Wait about 10 seconds for the Postgres instance to start. Then, run the | ||
# postgres.sh script in the container. | ||
docker exec -it postgres /app/postgres.sh | ||
``` | ||
|
||
Next, create a private key and public key for JWT called `jwtRS256.key` and | ||
`jwtRS256.key.pub` inside `/tmp/.ssh`. | ||
|
||
```shell | ||
$ openssl genrsa -out jwtRS256.key 4096 | ||
$ openssl rsa -in jwtRS256.key -outform PEM -pubout -out jwtRS256.key.pub | ||
$ mkdir -p /tmp/.ssh | ||
$ mv jwtRS256.key jwtRS256.key.pub /tmp/.ssh | ||
``` | ||
|
||
Finally, run the application. | ||
|
||
```shell | ||
$ go build . | ||
|
||
# Run the application in a single process. | ||
$ weaver single deploy weaver.toml | ||
|
||
# Run the application in multiple processes. | ||
$ weaver multi deploy weaver.toml | ||
``` | ||
|
||
[accounts-db]: https://github.com/GoogleCloudPlatform/bank-of-anthos/tree/main/src/accounts/accounts-db/initdb | ||
[boa]: https://github.com/GoogleCloudPlatform/bank-of-anthos | ||
[ledger-db]: https://github.com/GoogleCloudPlatform/bank-of-anthos/tree/main/src/ledger/ledger-db/initdb | ||
[postgres]: https://www.postgresql.org/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright 2020, Google LLC. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http:https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
-- users stores information about Bank of Anthos customers, including their | ||
-- username, password, name, etc. | ||
CREATE TABLE IF NOT EXISTS users ( | ||
accountid CHAR(10) PRIMARY KEY, | ||
username VARCHAR(64) UNIQUE NOT NULL, | ||
passhash BYTEA NOT NULL, | ||
firstname VARCHAR(64) NOT NULL, | ||
lastname VARCHAR(64) NOT NULL, | ||
birthday DATE NOT NULL, | ||
timezone VARCHAR(8) NOT NULL, | ||
address VARCHAR(64) NOT NULL, | ||
state CHAR(2) NOT NULL, | ||
zip VARCHAR(5) NOT NULL, | ||
ssn CHAR(11) NOT NULL | ||
); | ||
|
||
CREATE INDEX IF NOT EXISTS ON users (accountid); | ||
CREATE INDEX IF NOT EXISTS ON users (username); | ||
|
||
-- contacts stores the contacts for every user. A contact is a bank account to | ||
-- which a user can send funds. For example, if Alice has Bob as a contact, | ||
-- Alice can send funds to Bob. | ||
CREATE TABLE IF NOT EXISTS contacts ( | ||
username VARCHAR(64) NOT NULL, | ||
label VARCHAR(128) NOT NULL, | ||
account_num CHAR(10) NOT NULL, | ||
routing_num CHAR(9) NOT NULL, | ||
is_external BOOLEAN NOT NULL, | ||
FOREIGN KEY (username) REFERENCES users(username) | ||
); | ||
|
||
CREATE INDEX IF NOT EXISTS ON contacts (username); | ||
|
||
-- Populate the users table. | ||
INSERT INTO users VALUES | ||
('1011226111', 'testuser', '\x243262243132244c48334f54422e70653274596d6834534b756673727563564b3848774630494d2f34717044746868366e42352e744b575978314e61', 'Test', 'User', '2000-01-01', '-5', 'Bowling Green, New York City', 'NY', '10004', '111-22-3333'), | ||
('1033623433', 'alice', '\x243262243132244c48334f54422e70653274596d6834534b756673727563564b3848774630494d2f34717044746868366e42352e744b575978314e61', 'Alice', 'User', '2000-01-01', '-5', 'Bowling Green, New York City', 'NY', '10004', '111-22-3333'), | ||
('1055757655', 'bob', '\x243262243132244c48334f54422e70653274596d6834534b756673727563564b3848774630494d2f34717044746868366e42352e744b575978314e61', 'Bob', 'User', '2000-01-01', '-5', 'Bowling Green, New York City', 'NY', '10004', '111-22-3333'), | ||
('1077441377', 'eve', '\x243262243132244c48334f54422e70653274596d6834534b756673727563564b3848774630494d2f34717044746868366e42352e744b575978314e61', 'Eve', 'User', '2000-01-01', '-5', 'Bowling Green, New York City', 'NY', '10004', '111-22-3333') | ||
ON CONFLICT DO NOTHING; | ||
|
||
-- Populate the contacts table with internal contacts. | ||
INSERT INTO contacts VALUES | ||
('testuser', 'Alice', '1033623433', '883745000', 'false'), | ||
('testuser', 'Bob', '1055757655', '883745000', 'false'), | ||
('testuser', 'Eve', '1077441377', '883745000', 'false'), | ||
('alice', 'Testuser', '1011226111', '883745000', 'false'), | ||
('alice', 'Bob', '1055757655', '883745000', 'false'), | ||
('alice', 'Eve', '1077441377', '883745000', 'false'), | ||
('bob', 'Testuser', '1011226111', '883745000', 'false'), | ||
('bob', 'Alice', '1033623433', '883745000', 'false'), | ||
('bob', 'Eve', '1077441377', '883745000', 'false'), | ||
('eve', 'Testuser', '1011226111', '883745000', 'false'), | ||
('eve', 'Alice', '1033623433', '883745000', 'false'), | ||
('eve', 'Bob', '1055757655', '883745000', 'false') | ||
ON CONFLICT DO NOTHING; | ||
|
||
-- Populate the contacts table with internal contacts. | ||
INSERT INTO contacts VALUES | ||
('testuser', 'External Bank', '9099791699', '808889588', 'true'), | ||
('alice', 'External Bank', '9099791699', '808889588', 'true'), | ||
('bob', 'External Bank', '9099791699', '808889588', 'true'), | ||
('eve', 'External Bank', '9099791699', '808889588', 'true') | ||
ON CONFLICT DO NOTHING; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http:https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package balancereader | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/ServiceWeaver/weaver" | ||
"github.com/ServiceWeaver/weaver/examples/bankofanthos/common" | ||
"github.com/ServiceWeaver/weaver/examples/bankofanthos/model" | ||
) | ||
|
||
// T is a component that reads user balances. | ||
type T interface { | ||
// GetBalance returns the balance of an account id. | ||
GetBalance(ctx context.Context, accountID string) (int64, error) | ||
} | ||
|
||
type config struct { | ||
LocalRoutingNum string `toml:"local_routing_num"` | ||
DataSourceURL string `toml:"data_source_url"` | ||
} | ||
|
||
type impl struct { | ||
weaver.Implements[T] | ||
weaver.WithConfig[config] | ||
txnRepo *transactionRepository | ||
balanceCache *balanceCache | ||
ledgerReader *common.LedgerReader | ||
} | ||
|
||
var _ common.LedgerReaderCallback = (*impl)(nil) | ||
|
||
// ProcessTransaction implements the common.LedgerReaderCallback interface. | ||
func (i *impl) ProcessTransaction(transaction model.Transaction) { | ||
fromID := transaction.FromAccountNum | ||
fromRoutingNum := transaction.FromRoutingNum | ||
toID := transaction.ToAccountNum | ||
toRouting := transaction.ToRoutingNum | ||
amount := transaction.Amount | ||
if fromRoutingNum == i.Config().LocalRoutingNum { | ||
if got, ok := i.balanceCache.c.GetIfPresent(fromID); ok { | ||
prevBalance := got.(int64) | ||
i.balanceCache.c.Put(fromID, prevBalance-int64(amount)) | ||
} | ||
} | ||
if toRouting == i.Config().LocalRoutingNum { | ||
if got, ok := i.balanceCache.c.GetIfPresent(toID); ok { | ||
prevBalance := got.(int64) | ||
i.balanceCache.c.Put(toID, prevBalance+int64(amount)) | ||
} | ||
} | ||
} | ||
|
||
func (i *impl) Init(ctx context.Context) error { | ||
var err error | ||
i.txnRepo, err = newTransactionRepository(i.Config().DataSourceURL) | ||
if err != nil { | ||
return err | ||
} | ||
const cacheSize = 1000000 | ||
i.balanceCache = newBalanceCache(i.txnRepo, cacheSize, i.Config().LocalRoutingNum) | ||
i.ledgerReader = common.NewLedgerReader(i.txnRepo, i.Logger(ctx)) | ||
i.ledgerReader.StartWithCallback(i) | ||
return nil | ||
} | ||
|
||
func (i *impl) GetBalance(ctx context.Context, accountID string) (int64, error) { | ||
// Load from cache. | ||
got, err := i.balanceCache.c.Get(accountID) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return got.(int64), nil | ||
} |
Oops, something went wrong.