Merge remote-tracking branch 'upstream/main' into autodeploy

This commit is contained in:
Luke Bennett 2021-06-23 19:04:03 +01:00
commit 58d5d3e28b
18 changed files with 423 additions and 231 deletions

8
.dockerignore Normal file
View file

@ -0,0 +1,8 @@
/build
/config.json
/users.json
/help.md
/services.md
/APIs.md
/README.md
/benchmarking.cpp

31
.github/workflows/deploy.yaml vendored Normal file
View file

@ -0,0 +1,31 @@
name: Publish Staging
on:
push:
branches:
- main
jobs:
release:
name: Push Docker image to GitHub Packages
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Login to GitHub Docker Registry
uses: docker/login-action@v1
with:
registry: docker.pkg.github.com
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Format repository
run: |
echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
- name: Build container image
uses: docker/build-push-action@v2
with:
push: true
tags: |
docker.pkg.github.com/${{ env.IMAGE_REPOSITORY }}/ccash:${{ github.sha }}
docker.pkg.github.com/${{ env.IMAGE_REPOSITORY }}/ccash:latest

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
.vscode
build
config.json
users.json
users.json

16
Dockerfile Normal file
View file

@ -0,0 +1,16 @@
FROM debian:latest
WORKDIR /ccash
RUN apt update && apt -y install build-essential g++ cmake protobuf-compiler libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev
COPY . .
RUN mkdir build
WORKDIR /ccash/build
RUN cmake ..
RUN make -j$(nprov)
CMD ["/ccash/build/bank", "$CCASH_ADMIN_PASSWORD", "$CCASH_SAVE_FREQUENCY", "$CCASH_THREAD_COUNT"]

View file

@ -16,7 +16,11 @@ CCash solves these issues and adds a level of abstraction, the main philosophy o
drogon depedencies (varies by OS/distro)
```
# Debian
sudo apt install libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev
# macOS
brew install jsoncpp ossp-uuid openssl zlib
```
building the project
@ -44,7 +48,12 @@ sudo ./bank <admin password> <saving frequency in minutes> <threads>
## Connected Services
Go to [here](docs/help.md) to see the API's endpoints. Using the Bank's API allows (you/others) to (make/use) connected services that utilize the bank, a couple ideas can be found [here](docs/services.md)
Using the Bank's API allows (you/others) to (make/use) connected services that utilize the bank, a couple ideas can be found [here](docs/services.md)
## Developing for
as a dev check out
* [APIs](https://github.com/EntireTwix/CCash/blob/main/docs/APIs.md)
* [endpoints](https://github.com/EntireTwix/CCash/blob/main/docs/help.md)
## FAQ
**Q:** how is money initially injected into the economy
@ -54,12 +63,13 @@ Go to [here](docs/help.md) to see the API's endpoints. Using the Bank's API allo
## [Contributions](https://github.com/EntireTwix/CCash/graphs/contributors)
Thank you to the contributors
| Name | Work |
| :------------------------------------------ | ----------------------------------------------------------------- |
| [Expand](https://github.com/Expand-sys) | Frontend |
| [React](https://github.com/Reactified) | CC {API, Shops, and ATM, Logo} |
| [Doggo](https://github.com/FearlessDoggo21) | Logs loading/adding Optimized, Python API, convention suggestions |
| Name | Project Work | Connected Service Work |
| :------------------------------------------ | ----------------------------------------------------------------------- | ---------------------- |
| [Expand](https://github.com/Expand-sys) | Slight docker changes | Frontend |
| [React](https://github.com/Reactified) | CC API, Logo | CC Shop, CC ATM. |
| [Doggo](https://github.com/FearlessDoggo21) | Logs loading/adding Optimized, HTTP convention suggestions, Python API | `N/A` |
| [Luke](https://github.com/LukeeeeBennett) | JS API, Docker, Slight Doc edits | `N/A` |
| [Jolly](https://github.com/STBoyden) | Slight Doc edits | `N/A` |
## Features
@ -70,12 +80,18 @@ Thank you to the contributors
- **multi-threaded**
- **parallel hashmaps** a far [superior](https://greg7mdp.github.io/parallel-hashmap/) HashMap implementation to the STD, that also benefits from multi-threaded
- **Drogon** is a very fast [web framework](https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=composite)
- **Lightweight**, anecodotally I experienced 0.0% idle, <1% CPU usage on average, 7% at peak, 1000 requests in 0.85s
- **xxHash** for the hashing of passwords, it is very fast: [graph](https://user-images.githubusercontent.com/750081/61976089-aedeab00-af9f-11e9-9239-e5375d6c080f.png)
- **Lightweight**, anecodotally I experienced (on my laptop's i7 6700K, 8 threads):
- memory usage of 8.5 MB (with 0 users)
- 0.0% CPU usage idle
- <1% CPU on average
- 1000 requests in parallel completed in 0.85s which spiked CPU usage to 7%
### Safety
- **Tamper Proof** relative to an in-game implementation
- **Auto-Saving** and Saves on close
- All passwords are **Hashed**
- **HTTPS** (OpenSSL)
### Accessibility
@ -84,6 +100,9 @@ Thank you to the contributors
- able to be used millions of blocks away, across dimensions, servers, **vanilla or modded**.
- **Logging** of all transactions, configurable in [consts.hpp](include/consts.hpp)
### Other
- **return balance on deletion**, configurable in [consts.hpp](include/consts.hpp)
## Dependencies
- [Parallel HashMap](https://github.com/greg7mdp/parallel-hashmap/tree/master)

87
benchmarking.cpp Normal file
View file

@ -0,0 +1,87 @@
#include <iostream>
#include <chrono>
#include <thread>
#include <sys/types.h>
#include <unistd.h>
#include "bank_f.h"
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
using namespace std::chrono;
using namespace drogon;
static Bank bank;
#include <ctime>
#include <ratio>
#include <chrono>
#define time_func_a(f, a, x) \
{ \
using namespace std::chrono; \
uint32_t timer = 0; \
for (int i = 0; i < x; ++i) \
{ \
auto t1 = high_resolution_clock::now().time_since_epoch(); \
f; \
auto t2 = high_resolution_clock::now().time_since_epoch(); \
a; \
timer += std::chrono::duration_cast<std::chrono::nanoseconds>((t2 - t1)).count(); \
} \
std::cout << timer / x << '\n'; \
}
#define time_func(f, x) \
{ \
using namespace std::chrono; \
uint32_t timer = 0; \
for (int i = 0; i < x; ++i) \
{ \
auto t1 = high_resolution_clock::now().time_since_epoch(); \
f; \
auto t2 = high_resolution_clock::now().time_since_epoch(); \
timer += std::chrono::duration_cast<std::chrono::nanoseconds>((t2 - t1)).count(); \
} \
std::cout << timer / x << '\n'; \
}
#define Op_a(v, name, x, a) \
{ \
std::cout << name; \
time_func_a(v, a, x); \
}
#define Op(v, name, x) \
{ \
std::cout << name; \
time_func(v, x); \
}
int main(int argc, char **argv)
{
bank.AddUser("twix", "root");
bank.AddUser("jolly", "root");
bank.admin_pass = "root";
Op_a(bank.AddUser("", ""), "add user: ", 1000000, bank.DelUser("", ""));
Op_a(bank.AdminAddUser("root", "", 0, ""), "admin add user: ", 1000000, bank.DelUser("", ""));
Op(bank.SetBal("twix", "root", 1000000), "set bal: ", 1000000);
Op(bank.SendFunds("twix", "jolly", 1, "root"), "send funds: ", 1000000);
bank.AddUser("", "");
Op_a(bank.DelUser("", ""), "del user: ", 1000000, bank.AddUser("", ""));
Op_a(bank.AdminDelUser("", "root"), "admin del user: ", 1000000, bank.AddUser("", ""));
bank.DelUser("", "");
Op(bank.Contains("twix"), "contains: ", 1000000);
Op(bank.AdminVerifyPass("root"), "admin verify pass: ", 1000000);
Op(bank.GetBal("twix"), "get bal: ", 1000000);
Op(bank.VerifyPassword("twix", "root"), "verify pass: ", 1000000);
Op(bank.ChangePassword("twix", "root", "root"), "change pass: ", 1000000);
Op(bank.GetLogs("twix", "root"), "get logs: ", 10000);
Op(bank.Save(), "saving: ", 1);
return 0;
}

View file

@ -1,2 +1,11 @@
[CC API](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua)
[Python API](https://github.com/fearlessdoggo21/ccashpythonclient)
# Language Specific APIs
## Complete
* [JS API](https://github.com/LukeeeeBennett/ccash-client-js)
* [ComputerCraft (Lua) API](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua)
* [Python API](https://github.com/fearlessdoggo21/ccashpythonclient)
## In Dev
* [C API]()
* [CS API](https://github.com/Soverclysm/CCash-dotnet-api)
* [Rust API](https://git.stboyden.com/STBoyden/ccash-rs)

View file

@ -1,44 +1,45 @@
# Error Responses
| # | meaning |
| --- | ------------------ |
| -1 | UserNotFound |
| -2 | WrongPassword |
| -3 | InvalidRequest |
| -5 | NameTooLong |
| -6 | UserAlreadyExists |
| -7 | InsufficientFunds |
| # | meaning |
| --- | ----------------- |
| -1 | UserNotFound |
| -2 | WrongPassword |
| -3 | InvalidRequest |
| -4 | NameTooLong |
| -5 | UserAlreadyExists |
| -6 | InsufficientFunds |
# Things of Note
* all endpoints respond with **JSON** file type
* "**A**" denotes requiring Authentication in the form of a header titled "**Password**"
# Usage
| Name | Path | Method | A | Description |
| :------------: | :-------------------------------- | :----: | :---: | --------------------------------------------------------------------------------------------------------------------------- |
| GetBal | /{name}/bal | GET | false | returns the balance of a given user `{name}` |
| GetLog | /{name}/log | GET | true | returns a list of last `n` number of transactions (a configurable amount) of a given user `{name}` |
| SendFunds | /{name}/send/{to}/amount={amount} | POST | true | sends `{amount}` from user `{name}` to user `{to}` |
| VerifyPassword | /{name}/pass/verify | GET | true | returns `true` or `false` depending on if the supplied user `{name}`'s password matches the password supplied in the header |
| Name | Path | Method | A | Description |
| :------------: | :------------------------------------- | :----: | :---: | ------------------------------------------------------------------------------------------------------------------------------- |
| GetBal | BankF/{name}/bal | GET | false | returns the balance of a given user `{name}` |
| GetLog | BankF/{name}/log | GET | true | returns a list of last `n` number of transactions (a configurable amount when the program is compiled) of a given user `{name}` |
| SendFunds | BankF/{name}/send/{to}?amount={amount} | POST | true | sends `{amount}` from user `{name}` to user `{to}` |
| VerifyPassword | BankF/{name}/pass/verify | GET | true | returns `1` if the supplied user `{name}`'s password matches the password supplied in the header |
# Meta Usage
| Name | Path | Method | A | Description |
| :------------: | :-------------------------------- | :----: | :---: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ChangePassword | /{name}/pass/change | PATCH | true | if the password supplied in the header matches the user `{name}`'s password, the user's password is changed to the one given in the body |
| SetBal | /admin/{name}/bal/amount={amount} | PATCH | true | sets the balance of a give user `{name}` if the supplied password matches the admin password |
| Name | Path | Method | A | Description |
| :------------: | :------------------------------------- | :----: | :---: | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ChangePassword | BankF/{name}/pass/change | PATCH | true | if the password supplied in the header matches the user `{name}`'s password, the user's password is changed to the one given in the body |
| SetBal | BankF/admin/{name}/bal?amount={amount} | PATCH | true | sets the balance of a give user `{name}` if the supplied password matches the admin password |
# System Usage
| Name | Path | Method | A | Description |
| :-------------: | :--------------- | :----: | :---: | -------------------------------------------------------------------------------------------------------- |
| Help | /help | GET | false | the page you're looking at right now! |
| Close | /admin/close | POST | true | saves and then closes the program if the supplied password matches the admin password |
| Contains | /contains/{name} | GET | false | returns `true` or `false` depending on if the supplied user `{name}` exists |
| AdminVerifyPass | /admin/verify | GET | true | returns `true` or `false` depending on if the password supplied in the header matches the admin password |
| Name | Path | Method | A | Description |
| :-------------: | :-------------------- | :----: | :---: | ------------------------------------------------------------------------------------- |
| Help | BankF/help | GET | false | the page you're looking at right now! |
| Ping | BankF/ping | GET | false | for pinging the server to see if its online |
| Close | BankF/admin/close | POST | true | saves and then closes the program if the supplied password matches the admin password |
| Contains | BankF/contains/{name} | GET | false | returns `1` if the supplied user `{name}` exists |
| AdminVerifyPass | BankF/admin/verify | GET | true | returns `1` if the password supplied in the header matches the admin password |
# User Management
| Name | Path | Method | A | Description |
| :----------: | :------------------------------------- | :----: | :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AddUser | /user/{name} | POST | true | registers a user with the name `{name}`, balance of 0 and a password of the password supplied in the header |
| AdminAddUser | /admin/user/{name}?init_bal={init_bal} | POST | true | if the password supplied in the header matches the admin password, then it registers a user with the name `{name}`, balance of `init_bal` and a password supplied by the body of the request |
| DelUser | /user/{name} | DELETE | true | if the password supplied in the header matches the user `{name}`'s password, then the user is deleted |
| AdminDelUser | /admin/user/{name} | DELETE | true | if the password supplied in the header matches the admin password, then the user is deleted |
| Name | Path | Method | A | Description |
| :----------: | :------------------------------------------ | :----: | :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AddUser | BankF/user/{name} | POST | true | registers a user with the name `{name}`, balance of 0 and a password of the password supplied in the header |
| AdminAddUser | BankF/admin/user/{name}?init_bal={init_bal} | POST | true | if the password supplied in the header matches the admin password, then it registers a user with the name `{name}`, balance of `init_bal` and a password supplied by the body of the request |
| DelUser | BankF/user/{name} | DELETE | true | if the password supplied in the header matches the user `{name}`'s password, then the user is deleted |
| AdminDelUser | BankF/admin/user/{name} | DELETE | true | if the password supplied in the header matches the admin password, then the user is deleted |

View file

@ -10,7 +10,7 @@
- [CC Shop](https://github.com/Reactified/rpm/tree/main/packages/ccash-shop)
![image](https://user-images.githubusercontent.com/31377881/120050327-de163700-bfd1-11eb-9d5a-f75c003e867c.png)
![image](https://user-images.githubusercontent.com/31377881/120050367-09992180-bfd2-11eb-9a22-449d73c196cf.png)
- [CC ATM](https://github.com/Reactified/misc/tree/main/lua/ccash-bank) an ATM for economies allowing for an initial exchange to start up
- [CC Reverse ATM](https://github.com/Reactified/misc/tree/main/lua/ccash-bank) an ATM for economies allowing for an initial exchange to start up
![image](https://user-images.githubusercontent.com/31377881/121277361-4d6b1100-c885-11eb-87c8-cfebcf58da4f.png)
### In-Dev:
@ -24,3 +24,6 @@
- Shipping
- High-level bank operations such as loans
- Some trust based system for transactions similiar to Paypal
- a better version of one of these existing ideas
- something completely different

View file

@ -33,28 +33,28 @@ private:
public:
std::string admin_pass;
int_fast8_t AddUser(const std::string &name, const std::string &init_pass);
int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass);
int_fast8_t AddUser(const std::string &name, const std::string &init_pass) noexcept;
int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept;
int_fast8_t DelUser(const std::string &name, const std::string &attempt);
int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt);
int_fast8_t DelUser(const std::string &name, const std::string &attempt) noexcept;
int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt) noexcept;
int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt);
int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept;
bool Contains(const std::string &name) const;
bool AdminVerifyPass(const std::string &attempt);
int_fast8_t Contains(const std::string &name) const noexcept;
int_fast8_t AdminVerifyPass(const std::string &attempt) noexcept;
int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount);
int_fast64_t GetBal(const std::string &name) const;
int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount) noexcept;
int_fast64_t GetBal(const std::string &name) const noexcept;
int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const;
int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass);
int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const noexcept;
int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept;
Json::Value GetLogs(const std::string &name, const std::string &attempt);
Json::Value GetLogs(const std::string &name, const std::string &attempt) noexcept;
void Save();
//NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS
//NOT THREAD SAFE
void Load();
};

View file

@ -13,6 +13,7 @@ class BankF : public HttpController<BankF, false>
public:
BankF(Bank *b);
void Help(req_args) const;
void Ping(req_args) const;
void Close(req_args) const;
void AddUser(req_args, const std::string &name) const;
void AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const;
@ -32,15 +33,16 @@ public:
//Usage
METHOD_ADD(BankF::GetBal, "/{name}/bal", Get, Options);
METHOD_ADD(BankF::GetLog, "/{name}/log", Get, Options);
METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}/amount={amount}", Post, Options);
METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}?amount={amount}", Post, Options);
METHOD_ADD(BankF::VerifyPassword, "/{name}/pass/verify", Get, Options);
//Meta Usage
METHOD_ADD(BankF::ChangePassword, "/{name}/pass/change", Patch, Options);
METHOD_ADD(BankF::SetBal, "/admin/{name}/bal/amount={amount}", Patch, Options);
METHOD_ADD(BankF::SetBal, "/admin/{name}/bal?amount={amount}", Patch, Options);
//System Usage
METHOD_ADD(BankF::Help, "/help", Get, Options);
METHOD_ADD(BankF::Ping, "/ping", Get, Options);
METHOD_ADD(BankF::Close, "/admin/close", Post, Options);
METHOD_ADD(BankF::Contains, "/contains/{name}", Get, Options);
METHOD_ADD(BankF::AdminVerifyPass, "/admin/verify", Get, Options);

View file

@ -3,4 +3,12 @@
// Setting both values to 0 does not compile logging
constexpr unsigned max_log_size = 100;
constexpr unsigned pre_log_size = 10;
constexpr unsigned max_name_size = 50;
constexpr unsigned max_name_size = 50;
constexpr const char *users_location = "../users.json";
constexpr const char *config_location = "../config.json";
//returns money to an account on deletion
constexpr bool return_on_del = false;
constexpr const char *return_account = "";

View file

@ -4,8 +4,7 @@ enum ErrorResponse
UserNotFound = -1,
WrongPassword = -2,
InvalidRequest = -3,
WrongAdminPassword = -4,
NameTooLong = -5,
UserAlreadyExists = -6,
InsufficientFunds = -7,
NameTooLong = -4,
UserAlreadyExists = -5,
InsufficientFunds = -6,
};

View file

@ -55,7 +55,7 @@ int main(int argc, char **argv)
bank.admin_pass = argv[1];
//Auto Saving
const unsigned long saving_freq = std::stoul(argv[2]);
const unsigned long saving_freq = std::stoul(std::string(argv[2]));
if (saving_freq) //if saving frequency is 0 then auto saving is turned off
{
std::thread([saving_freq]() {
@ -73,7 +73,7 @@ int main(int argc, char **argv)
[](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) {
resp->addHeader("Access-Control-Allow-Origin", "*");
});
app().loadConfigFile("../config.json").registerController(API).setThreadNum(std::stoul(argv[3])).run();
app().loadConfigFile(config_location).registerController(API).setThreadNum(std::stoul(std::string(argv[3]))).run();
return 0;
}

View file

@ -1,25 +1,28 @@
#include "bank.h"
int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass)
int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass) noexcept
{
if (name.size() > max_name_size)
{
return ErrorResponse::NameTooLong;
}
for (char c : name)
{
std::shared_lock<std::shared_mutex> lock{size_l};
if (users.try_emplace_l(
name, [](User &) {}, init_pass))
if (c == ' ')
{
return true;
}
else
{
return ErrorResponse::UserAlreadyExists;
return ErrorResponse::InvalidRequest;
}
}
{
std::shared_lock<std::shared_mutex> lock{size_l};
return (users.try_emplace_l(
name, [](User &) {}, init_pass))
? true
: ErrorResponse::UserAlreadyExists;
}
}
int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass)
int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept
{
if (name.size() > max_name_size)
{
@ -27,161 +30,158 @@ int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, u
}
if (admin_pass != attempt)
{
return ErrorResponse::WrongAdminPassword;
}
{
std::shared_lock<std::shared_mutex> lock{size_l};
if (users.try_emplace_l(
name, [](User &) {}, std::move(init_pass)))
{
return true;
}
else
{
return ErrorResponse::UserAlreadyExists;
}
return ErrorResponse::WrongPassword;
}
std::shared_lock<std::shared_mutex> lock{size_l};
return (users.try_emplace_l(
name, [](User &) {}, init_bal, std::move(init_pass)))
? true
: ErrorResponse::UserAlreadyExists;
}
int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt)
int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) noexcept
{
std::shared_lock<std::shared_mutex> lock{size_l};
bool state = false;
if (!users.erase_if(name, [&state, &attempt](User &u) { return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); }))
if (users.erase_if(name, [this, &name, &state, &attempt](User &u) {
if constexpr (return_on_del)
{
if (SendFunds(name, return_account, u.balance, attempt) == 1)
{
return true;
}
}
return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password);
}))
{
return ErrorResponse::UserNotFound;
return (state) ? true : ErrorResponse::WrongPassword;
}
else
{
if (state)
{
return true;
}
else
{
return ErrorResponse::WrongAdminPassword;
}
return ErrorResponse::UserNotFound;
}
}
int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt)
int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt) noexcept
{
std::shared_lock<std::shared_mutex> lock{size_l};
bool state = false;
if (!users.erase_if(name, [this, &state, &attempt](const User &) { return state = (admin_pass == attempt); }))
if (users.erase_if(name, [this, &name, &state, &attempt](const User &u) {
if constexpr (return_on_del)
{
if (SendFunds(name, return_account, u.balance, attempt) == 1)
{
return true;
}
}
return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password);
}))
{
return ErrorResponse::UserNotFound;
return (state) ? true : ErrorResponse::WrongPassword;
}
else
{
if (state)
{
return true;
}
else
{
return ErrorResponse::WrongAdminPassword;
}
return ErrorResponse::UserNotFound;
}
}
int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt)
int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept
{
//cant send money to self, from self or amount is 0
if (a_name == b_name || !amount)
{
return ErrorResponse::InvalidRequest;
}
int_fast8_t state = false;
{
std::shared_lock<std::shared_mutex> lock{send_funds_l}; //because SendFunds requires 3 locking operations
if (users.modify_if(a_name, [&state, amount, &attempt](User &a) {
//if A exists, A can afford it, and A's password matches
if (a.balance < amount)
{
state = ErrorResponse::InsufficientFunds;
}
else
{
if (a.password != XXH3_64bits(attempt.data(), attempt.size()))
{
state = ErrorResponse::WrongPassword;
}
else
{
a.balance -= amount;
state = true;
}
}
}))
{
return ErrorResponse::UserNotFound;
}
else
{
if (state)
{
//if B doesnt exist
if (users.modify_if(b_name, [amount](User &b) {
b.balance += amount;
}))
{
if constexpr (max_log_size)
{
Transaction temp(a_name, b_name, amount);
Transaction temp2 = temp;
users.modify_if(a_name, [&temp](User &a) {
a.log.AddTrans(std::move(temp));
});
users.modify_if(b_name, [&temp2](User &b) {
b.log.AddTrans(std::move(temp2));
});
}
return true;
}
else
{
//attempt to refund if A exist
users.modify_if(a_name, [amount](User &a) {
a.balance += amount;
});
return ErrorResponse::UserNotFound; //because had to refund transaction
}
}
else
{
return state;
}
}
}
}
bool Bank::Contains(const std::string &name) const
{
return users.contains(name);
}
bool Bank::AdminVerifyPass(const std::string &attempt)
{
return (admin_pass == attempt);
}
int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount)
{
if (admin_pass != attempt)
{
return ErrorResponse::WrongAdminPassword;
}
if (users.modify_if(name, [amount](User &u) {
u.balance = amount;
}))
{
return true;
}
else
//as first modify_if checks a_name and grabs unique lock
if (!Contains(b_name))
{
return ErrorResponse::UserNotFound;
}
int_fast8_t state = false;
if constexpr (max_log_size > 0)
{
Transaction temp(a_name, b_name, amount);
std::shared_lock<std::shared_mutex> lock{send_funds_l};
users.modify_if(a_name, [&temp, &state, amount, &attempt](User &a) {
//if A can afford it and A's password matches attempt
if (a.balance < amount)
{
state = ErrorResponse::InsufficientFunds;
}
else if (a.password != XXH3_64bits(attempt.data(), attempt.size()))
{
state = ErrorResponse::WrongPassword;
}
else
{
a.balance -= amount;
a.log.AddTrans(Transaction(temp));
state = true;
}
});
if (state > 0)
{
users.modify_if(b_name, [&a_name, &b_name, &temp, amount](User &b) {
b.balance += amount;
b.log.AddTrans(std::move(temp));
});
}
return state;
}
else
{
std::shared_lock<std::shared_mutex> lock{send_funds_l};
users.modify_if(a_name, [&state, amount, &attempt](User &a) {
//if A can afford it and A's password matches attempt
if (a.balance < amount)
{
state = ErrorResponse::InsufficientFunds;
}
else if (a.password != XXH3_64bits(attempt.data(), attempt.size()))
{
state = ErrorResponse::WrongPassword;
}
else
{
a.balance -= amount;
state = true;
}
});
if (state > 0)
{
users.modify_if(b_name, [&a_name, &b_name, amount](User &b) {
b.balance += amount;
});
}
return state;
}
}
int_fast64_t Bank::GetBal(const std::string &name) const
int_fast8_t Bank::Contains(const std::string &name) const noexcept
{
return (users.contains(name)) ? true : ErrorResponse::UserNotFound;
}
int_fast8_t Bank::AdminVerifyPass(const std::string &attempt) noexcept
{
return (admin_pass == attempt) ? true : ErrorResponse::WrongPassword;
}
int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount) noexcept
{
if (admin_pass != attempt)
{
return ErrorResponse::WrongPassword;
}
return (users.modify_if(name, [amount](User &u) {
u.balance = amount;
}))
? true
: ErrorResponse::UserNotFound;
}
int_fast64_t Bank::GetBal(const std::string &name) const noexcept
{
int_fast64_t res = ErrorResponse::UserNotFound;
users.if_contains(name, [&res](const User &u) {
@ -190,15 +190,15 @@ int_fast64_t Bank::GetBal(const std::string &name) const
return res;
}
int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const
int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const noexcept
{
int_fast8_t res = ErrorResponse::UserNotFound;
users.if_contains(name, [&res, &attempt](const User &u) {
res = u.password == XXH3_64bits(attempt.data(), attempt.size());
res = (u.password == XXH3_64bits(attempt.data(), attempt.size())) ? true : ErrorResponse::WrongPassword;
});
return res;
}
int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass)
int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept
{
int_fast8_t res = ErrorResponse::UserNotFound;
users.modify_if(name, [&res, &attempt, &new_pass](User &u) {
@ -208,13 +208,14 @@ int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &att
}
else
{
res = true;
u.password = XXH3_64bits(new_pass.data(), new_pass.size());
}
});
return res;
}
Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt)
Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt) noexcept
{
Json::Value res;
if (!users.if_contains(name, [&res, &attempt](const User &u) {
@ -224,15 +225,13 @@ Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt)
}
else
{
Json::Value temp;
for (uint32_t i = u.log.data.size(); i > 0; --i)
{
temp[i - 1]["to"] = u.log.data[u.log.data.size() - i].to;
temp[i - 1]["from"] = u.log.data[u.log.data.size() - i].from;
temp[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount;
temp[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time;
res[i - 1]["to"] = u.log.data[u.log.data.size() - i].to;
res[i - 1]["from"] = u.log.data[u.log.data.size() - i].from;
res[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount;
res[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time;
}
res = std::move(temp);
}
}))
{
@ -262,7 +261,7 @@ void Bank::Save()
}
else
{
std::ofstream user_save("../users.json");
std::ofstream user_save(users_location);
Json::StreamWriterBuilder builder;
const std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(temp, &user_save);
@ -276,7 +275,7 @@ void Bank::Load()
Json::CharReaderBuilder builder;
Json::Value temp;
std::ifstream user_save("../users.json");
std::ifstream user_save(users_location);
builder["collectComments"] = true;
JSONCPP_STRING errs;
if (!parseFromStream(builder, user_save, &temp, &errs))
@ -290,7 +289,7 @@ void Bank::Load()
user_save.close();
for (const auto &u : temp.getMemberNames())
{
if constexpr (max_log_size)
if constexpr (max_log_size > 0)
{
users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64()), temp[u]["log"]);
}

File diff suppressed because one or more lines are too long

View file

@ -29,11 +29,11 @@ User::User(uint32_t init_bal, uint64_t init_pass, const Json::Value &log_j) : ba
log.data.reserve(std::min(pre_log_size * ((log_j.size() / pre_log_size) + 1), max_log_size));
for (uint32_t i = (log_j.size() - max_log_size) * (log_j.size() > max_log_size); i < log_j.size(); i++)
{
log.data.push_back(std::move(Transaction(
log.data.push_back(Transaction(
log_j[i]["from"].asCString(),
log_j[i]["to"].asCString(),
log_j[i]["amount"].asUInt(),
log_j[i]["time"].asUInt64())));
log_j[i]["time"].asUInt64()));
}
}
}
@ -43,7 +43,7 @@ Json::Value User::Serialize() const
Json::Value res;
res["balance"] = (Json::UInt)balance;
res["password"] = (Json::UInt64)password;
if constexpr (max_log_size)
if constexpr (max_log_size > 0)
{
res["log"] = log.Serialize();
}

View file

@ -1,2 +1,8 @@
{
"" :
{
"balance" : 0,
"log" : null,
"password" : 0
},
}