diff options
author | Yigit Sever | 2021-04-07 04:33:45 +0300 |
---|---|---|
committer | Yigit Sever | 2021-04-07 04:35:44 +0300 |
commit | 84d9c14a17e864058527981e3388cef148827c11 (patch) | |
tree | 19c7b4b4d67d2ba2d3d87ba7eca009ff997e0cd0 | |
parent | 95ff6371303ac28d05b25fd9f6e436c5d0a58d4c (diff) | |
download | gradecoin-84d9c14a17e864058527981e3388cef148827c11.tar.gz gradecoin-84d9c14a17e864058527981e3388cef148827c11.tar.bz2 gradecoin-84d9c14a17e864058527981e3388cef148827c11.zip |
Implement Block GET/PUT with new schema
- `Arc`+`Mutex` is replaced by `parking_lot::RwLock,` decoupled
Read+Write and ability to upgrade read locks into write locks if
needed
- Schema has changed, `Db` is now a struct that implements `new()` to
return a new instance of itself, pros/cons listed in code but tl;dr
blockchain and pending transactions are separate now
- `custom_filters` now supports extracting Block json and Transaction
json in separate functions too
- /block GET and PUT implemented, `Blocks` currently have one check
(transactions appear in pending transaction)
- debug is working after something, dunno how I fixed it
-rw-r--r-- | Cargo.lock | 63 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | TODO.md | 6 | ||||
-rw-r--r-- | src/custom_filters.rs | 11 | ||||
-rw-r--r-- | src/handlers.rs | 94 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/routes.rs | 30 | ||||
-rw-r--r-- | src/schema.rs | 65 | ||||
-rw-r--r-- | tester.sh | 38 |
9 files changed, 258 insertions, 56 deletions
@@ -108,6 +108,15 @@ dependencies = [ | |||
108 | ] | 108 | ] |
109 | 109 | ||
110 | [[package]] | 110 | [[package]] |
111 | name = "cloudabi" | ||
112 | version = "0.0.3" | ||
113 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
114 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" | ||
115 | dependencies = [ | ||
116 | "bitflags", | ||
117 | ] | ||
118 | |||
119 | [[package]] | ||
111 | name = "cpuid-bool" | 120 | name = "cpuid-bool" |
112 | version = "0.1.2" | 121 | version = "0.1.2" |
113 | source = "registry+https://github.com/rust-lang/crates.io-index" | 122 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -273,6 +282,7 @@ version = "0.1.0" | |||
273 | dependencies = [ | 282 | dependencies = [ |
274 | "chrono", | 283 | "chrono", |
275 | "log", | 284 | "log", |
285 | "parking_lot", | ||
276 | "pretty_env_logger", | 286 | "pretty_env_logger", |
277 | "serde", | 287 | "serde", |
278 | "serde_json", | 288 | "serde_json", |
@@ -475,6 +485,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
475 | checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" | 485 | checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" |
476 | 486 | ||
477 | [[package]] | 487 | [[package]] |
488 | name = "lock_api" | ||
489 | version = "0.3.4" | ||
490 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
491 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" | ||
492 | dependencies = [ | ||
493 | "scopeguard", | ||
494 | ] | ||
495 | |||
496 | [[package]] | ||
478 | name = "log" | 497 | name = "log" |
479 | version = "0.4.14" | 498 | version = "0.4.14" |
480 | source = "registry+https://github.com/rust-lang/crates.io-index" | 499 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -597,6 +616,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
597 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" | 616 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" |
598 | 617 | ||
599 | [[package]] | 618 | [[package]] |
619 | name = "parking_lot" | ||
620 | version = "0.10.2" | ||
621 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
622 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" | ||
623 | dependencies = [ | ||
624 | "lock_api", | ||
625 | "parking_lot_core", | ||
626 | ] | ||
627 | |||
628 | [[package]] | ||
629 | name = "parking_lot_core" | ||
630 | version = "0.7.2" | ||
631 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
632 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" | ||
633 | dependencies = [ | ||
634 | "cfg-if 0.1.10", | ||
635 | "cloudabi", | ||
636 | "libc", | ||
637 | "redox_syscall 0.1.57", | ||
638 | "smallvec", | ||
639 | "winapi 0.3.9", | ||
640 | ] | ||
641 | |||
642 | [[package]] | ||
600 | name = "percent-encoding" | 643 | name = "percent-encoding" |
601 | version = "2.1.0" | 644 | version = "2.1.0" |
602 | source = "registry+https://github.com/rust-lang/crates.io-index" | 645 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -784,6 +827,12 @@ dependencies = [ | |||
784 | 827 | ||
785 | [[package]] | 828 | [[package]] |
786 | name = "redox_syscall" | 829 | name = "redox_syscall" |
830 | version = "0.1.57" | ||
831 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
832 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" | ||
833 | |||
834 | [[package]] | ||
835 | name = "redox_syscall" | ||
787 | version = "0.2.5" | 836 | version = "0.2.5" |
788 | source = "registry+https://github.com/rust-lang/crates.io-index" | 837 | source = "registry+https://github.com/rust-lang/crates.io-index" |
789 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" | 838 | checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" |
@@ -836,6 +885,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
836 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" | 885 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" |
837 | 886 | ||
838 | [[package]] | 887 | [[package]] |
888 | name = "scopeguard" | ||
889 | version = "1.1.0" | ||
890 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
891 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" | ||
892 | |||
893 | [[package]] | ||
839 | name = "serde" | 894 | name = "serde" |
840 | version = "1.0.125" | 895 | version = "1.0.125" |
841 | source = "registry+https://github.com/rust-lang/crates.io-index" | 896 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -907,6 +962,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
907 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" | 962 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" |
908 | 963 | ||
909 | [[package]] | 964 | [[package]] |
965 | name = "smallvec" | ||
966 | version = "1.6.1" | ||
967 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
968 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" | ||
969 | |||
970 | [[package]] | ||
910 | name = "socket2" | 971 | name = "socket2" |
911 | version = "0.3.19" | 972 | version = "0.3.19" |
912 | source = "registry+https://github.com/rust-lang/crates.io-index" | 973 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -937,7 +998,7 @@ dependencies = [ | |||
937 | "cfg-if 1.0.0", | 998 | "cfg-if 1.0.0", |
938 | "libc", | 999 | "libc", |
939 | "rand 0.8.3", | 1000 | "rand 0.8.3", |
940 | "redox_syscall", | 1001 | "redox_syscall 0.2.5", |
941 | "remove_dir_all", | 1002 | "remove_dir_all", |
942 | "winapi 0.3.9", | 1003 | "winapi 0.3.9", |
943 | ] | 1004 | ] |
@@ -13,6 +13,7 @@ serde = { version = "1.0.104", features = ["derive"] } | |||
13 | chrono = { version = "0.4.10", features = ["serde"] } | 13 | chrono = { version = "0.4.10", features = ["serde"] } |
14 | log = "0.4.8" | 14 | log = "0.4.8" |
15 | pretty_env_logger = "0.3.1" | 15 | pretty_env_logger = "0.3.1" |
16 | parking_lot = "0.10.0" | ||
16 | 17 | ||
17 | [dev-dependencies] | 18 | [dev-dependencies] |
18 | serde_json = "1.0.44" | 19 | serde_json = "1.0.44" |
@@ -1,4 +1,7 @@ | |||
1 | # TODO | 1 | # TODO |
2 | ## Process | ||
3 | - [ ] we need our own representation of students and their grades, "there is no blockchain" | ||
4 | |||
2 | ## Proof-of-work | 5 | ## Proof-of-work |
3 | - [ ] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions) | 6 | - [ ] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions) |
4 | - [ ] check the nonce for incoming blocks | 7 | - [ ] check the nonce for incoming blocks |
@@ -7,4 +10,5 @@ | |||
7 | - [ ] pick a user authentication scheme | 10 | - [ ] pick a user authentication scheme |
8 | - [ ] implement it | 11 | - [ ] implement it |
9 | 12 | ||
10 | - [ ] Switch to RwLock (parking_lot) | 13 | ## Done & Brag |
14 | - [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code) | ||
diff --git a/src/custom_filters.rs b/src/custom_filters.rs index 86a78d4..7caf71a 100644 --- a/src/custom_filters.rs +++ b/src/custom_filters.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use std::convert::Infallible; | 3 | use std::convert::Infallible; |
4 | use warp::{Filter, Rejection}; | 4 | use warp::{Filter, Rejection}; |
5 | 5 | ||
6 | use crate::schema::{Db, Transaction}; // `Block` coming later | 6 | use crate::schema::{Block, Db, Transaction}; |
7 | 7 | ||
8 | // Database context for routes | 8 | // Database context for routes |
9 | pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { | 9 | pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { |
@@ -15,7 +15,12 @@ pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clo | |||
15 | // warp::query::<ListOptions>() | 15 | // warp::query::<ListOptions>() |
16 | // } | 16 | // } |
17 | 17 | ||
18 | // Accept only JSON body and reject big payloads | 18 | // Accept only json encoded Transaction body and reject big payloads |
19 | pub fn json_body() -> impl Filter<Extract = (Transaction,), Error = Rejection> + Clone { | 19 | pub fn transaction_json_body() -> impl Filter<Extract = (Transaction,), Error = Rejection> + Clone { |
20 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | ||
21 | } | ||
22 | |||
23 | // Accept only json encoded Transaction body and reject big payloads | ||
24 | pub fn block_json_body() -> impl Filter<Extract = (Block,), Error = Rejection> + Clone { | ||
20 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | 25 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) |
21 | } | 26 | } |
diff --git a/src/handlers.rs b/src/handlers.rs index 51c7b63..ecf5a92 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
@@ -1,45 +1,93 @@ | |||
1 | // API handlers, the ends of each filter chain | 1 | /// API handlers, the ends of each filter chain |
2 | 2 | use log::debug; // this is more useful than debug! learn how to use this | |
3 | use log::debug; | 3 | use parking_lot::RwLockUpgradableReadGuard; |
4 | use std::convert::Infallible; | 4 | use std::convert::Infallible; |
5 | use warp::{http::StatusCode, reply}; | 5 | use warp::{http::StatusCode, reply}; |
6 | 6 | ||
7 | use crate::schema::{Db, Transaction}; // `Block` coming later | 7 | use crate::schema::{Block, Db, Transaction}; |
8 | |||
9 | /// GET /transaction | ||
10 | /// Returns JSON array of transactions | ||
11 | /// Cannot fail | ||
12 | pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { | ||
13 | debug!("list all transactions"); | ||
14 | let mut result = Vec::new(); | ||
15 | |||
16 | let transactions = db.pending_transactions.read(); | ||
17 | // let transactions = transactions.clone().into_iter().collect(); | ||
18 | |||
19 | for (_, value) in transactions.iter() { | ||
20 | result.push(value) | ||
21 | } | ||
22 | |||
23 | Ok(reply::with_status(reply::json(&result), StatusCode::OK)) | ||
24 | } | ||
25 | |||
26 | /// GET /block | ||
27 | /// Returns JSON array of blocks | ||
28 | /// Cannot fail | ||
29 | /// Mostly around for debug purposes | ||
30 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | ||
31 | debug!("list all blocks"); | ||
32 | |||
33 | let mut result = Vec::new(); | ||
34 | let blocks = db.blockchain.read(); | ||
35 | |||
36 | for block in blocks.iter() { | ||
37 | result.push(block); | ||
38 | } | ||
8 | 39 | ||
9 | // PROPOSE Transaction | 40 | Ok(reply::with_status(reply::json(&result), StatusCode::OK)) |
10 | // POST /transaction | 41 | } |
42 | |||
43 | /// POST /transaction | ||
44 | /// Pushes a new transaction for pending transaction pool | ||
45 | /// Can reject the transaction proposal | ||
46 | /// TODO: when is a new transaction rejected <07-04-21, yigit> // | ||
11 | pub async fn propose_transaction( | 47 | pub async fn propose_transaction( |
12 | new_transaction: Transaction, | 48 | new_transaction: Transaction, |
13 | db: Db, | 49 | db: Db, |
14 | ) -> Result<impl warp::Reply, warp::Rejection> { | 50 | ) -> Result<impl warp::Reply, warp::Rejection> { |
15 | debug!("new transaction request {:?}", new_transaction); | 51 | debug!("new transaction request {:?}", new_transaction); |
16 | 52 | ||
17 | let mut transactions = db.lock().await; | 53 | // let mut transactions = db.lock().await; |
54 | let mut transactions = db.pending_transactions.write(); | ||
18 | 55 | ||
19 | transactions.push(new_transaction); | 56 | transactions.insert(new_transaction.source.to_owned(), new_transaction); |
20 | 57 | ||
21 | Ok(StatusCode::CREATED) | 58 | Ok(StatusCode::CREATED) |
22 | } | 59 | } |
23 | 60 | ||
24 | // GET Transaction List | 61 | /// POST /block |
25 | // GET /transaction | 62 | /// Proposes a new block for the next round |
26 | // Returns JSON array of transactions | 63 | /// Can reject the block |
27 | // Cannot fail? | 64 | pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, warp::Rejection> { |
28 | pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { | 65 | debug!("new block request {:?}", new_block); |
29 | debug!("list all transactions"); | ||
30 | 66 | ||
31 | let transactions = db.lock().await; | 67 | // https://blog.logrocket.com/create-an-async-crud-web-service-in-rust-with-warp/ (this has |
68 | // error.rs, error struct, looks very clean) | ||
32 | 69 | ||
33 | let transactions: Vec<Transaction> = transactions.clone().into_iter().collect(); | 70 | let pending_transactions = db.pending_transactions.upgradable_read(); |
71 | let blockchain = db.blockchain.upgradable_read(); | ||
34 | 72 | ||
35 | Ok(reply::with_status( | 73 | // TODO: check 1, new_block.transaction_list from pending_transactions pool? <07-04-21, yigit> // |
36 | reply::json(&transactions), | 74 | for transaction_hash in new_block.transaction_list.iter() { |
37 | StatusCode::OK, | 75 | if !pending_transactions.contains_key(transaction_hash) { |
38 | )) | 76 | return Ok(StatusCode::BAD_REQUEST); |
39 | } | 77 | } |
78 | } | ||
40 | 79 | ||
41 | // PROPOSE Block | 80 | // TODO: check 2, block hash (\w nonce) asserts $hash_condition? <07-04-21, yigit> // |
42 | // POST /block | 81 | // assume it is for now |
82 | |||
83 | let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); | ||
84 | blockchain.push(new_block); | ||
85 | |||
86 | let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); | ||
87 | pending_transactions.clear(); | ||
88 | |||
89 | Ok(StatusCode::CREATED) | ||
90 | } | ||
43 | 91 | ||
44 | // `GET /games` | 92 | // `GET /games` |
45 | // Returns JSON array of todos | 93 | // Returns JSON array of todos |
diff --git a/src/main.rs b/src/main.rs index bcd4173..7ef2597 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -11,15 +11,15 @@ mod schema; | |||
11 | async fn main() { | 11 | async fn main() { |
12 | // Show debug logs by default by setting `RUST_LOG=restful_rust=debug` | 12 | // Show debug logs by default by setting `RUST_LOG=restful_rust=debug` |
13 | if env::var_os("RUST_LOG").is_none() { | 13 | if env::var_os("RUST_LOG").is_none() { |
14 | env::set_var("RUST_LOG", "restful_rust=debug"); | 14 | env::set_var("RUST_LOG", "gradecoin=debug"); |
15 | } | 15 | } |
16 | pretty_env_logger::init(); | 16 | pretty_env_logger::init(); |
17 | 17 | ||
18 | let db = schema::ledger(); // 1. we need this to return a _simple_ db | 18 | let db = schema::create_database(); |
19 | 19 | ||
20 | let api = routes::consensus_routes(db); | 20 | let api = routes::consensus_routes(db); |
21 | 21 | ||
22 | let routes = api.with(warp::log("restful_rust")); | 22 | let routes = api.with(warp::log("gradecoin")); |
23 | 23 | ||
24 | // Start the server | 24 | // Start the server |
25 | warp::serve(routes).run(([127, 0, 0, 1], 8080)).await; | 25 | warp::serve(routes).run(([127, 0, 0, 1], 8080)).await; |
diff --git a/src/routes.rs b/src/routes.rs index fc4426a..9054fb6 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
@@ -4,12 +4,15 @@ use crate::custom_filters; | |||
4 | use crate::handlers; | 4 | use crate::handlers; |
5 | use crate::schema::Db; | 5 | use crate::schema::Db; |
6 | 6 | ||
7 | // Root, all routes combined | 7 | /// Root, all routes combined |
8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
9 | transaction_list(db.clone()).or(transaction_propose(db.clone())) | 9 | transaction_list(db.clone()) |
10 | .or(transaction_propose(db.clone())) | ||
11 | .or(block_propose(db.clone())) | ||
12 | .or(block_list(db.clone())) | ||
10 | } | 13 | } |
11 | 14 | ||
12 | // GET /transaction | 15 | /// GET /transaction |
13 | pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 16 | pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
14 | warp::path!("transaction") | 17 | warp::path!("transaction") |
15 | .and(warp::get()) | 18 | .and(warp::get()) |
@@ -17,15 +20,32 @@ pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rej | |||
17 | .and_then(handlers::list_transactions) | 20 | .and_then(handlers::list_transactions) |
18 | } | 21 | } |
19 | 22 | ||
20 | // POST /transaction | 23 | /// GET /block |
24 | pub fn block_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | ||
25 | warp::path!("block") | ||
26 | .and(warp::get()) | ||
27 | .and(custom_filters::with_db(db)) | ||
28 | .and_then(handlers::list_blocks) | ||
29 | } | ||
30 | |||
31 | /// POST /transaction | ||
21 | pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 32 | pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
22 | warp::path!("transaction") | 33 | warp::path!("transaction") |
23 | .and(warp::post()) | 34 | .and(warp::post()) |
24 | .and(custom_filters::json_body()) | 35 | .and(custom_filters::transaction_json_body()) |
25 | .and(custom_filters::with_db(db)) | 36 | .and(custom_filters::with_db(db)) |
26 | .and_then(handlers::propose_transaction) | 37 | .and_then(handlers::propose_transaction) |
27 | } | 38 | } |
28 | 39 | ||
40 | /// POST /block | ||
41 | pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | ||
42 | warp::path!("block") | ||
43 | .and(warp::post()) | ||
44 | .and(custom_filters::block_json_body()) | ||
45 | .and(custom_filters::with_db(db)) | ||
46 | .and_then(handlers::propose_block) | ||
47 | } | ||
48 | |||
29 | /////////////////////////// | 49 | /////////////////////////// |
30 | // below are not mine. // | 50 | // below are not mine. // |
31 | /////////////////////////// | 51 | /////////////////////////// |
diff --git a/src/schema.rs b/src/schema.rs index ea36a70..57210a3 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
@@ -1,31 +1,46 @@ | |||
1 | // Common types used across API | 1 | use chrono::NaiveDateTime; |
2 | 2 | use parking_lot::RwLock; | |
3 | use chrono::{NaiveDate, NaiveDateTime}; | ||
4 | use serde::{Deserialize, Serialize}; | 3 | use serde::{Deserialize, Serialize}; |
4 | use std::collections::HashMap; | ||
5 | use std::sync::Arc; | 5 | use std::sync::Arc; |
6 | use tokio::sync::Mutex; | ||
7 | 6 | ||
8 | // use crate::validators; | 7 | // use crate::validators; |
9 | 8 | ||
10 | pub fn ledger() -> Db { | 9 | // In memory data structure |
11 | // TODO: there was something simpler in one of the other tutorials? <07-04-21, yigit> // | 10 | |
12 | 11 | // Two approaches here | |
13 | Arc::new(Mutex::new(vec![ | 12 | // 1. Db is a type |
14 | Transaction { | 13 | // pub type Db = Arc<RwLock<Vec<Ledger>>>; |
15 | source: String::from("Myself"), | 14 | // Ledger is a struct, we wrap the ledger with arc + mutex in ledger() |
16 | target: String::from("Nobody"), | 15 | // to access transactions we need to unwrap blocks as well, vice versa |
17 | amount: 4, | 16 | // |
18 | timestamp: NaiveDate::from_ymd(2021, 4, 7).and_hms(00, 17, 00), | 17 | // 2. Db is a struct, attributes are wrapped |
19 | }, | 18 | // we can offload ::new() to it's member method |
20 | ])) | 19 | // blocks and transactions are accessible separately, which is the biggest pro |
20 | |||
21 | /// Creates a new database | ||
22 | pub fn create_database() -> Db { | ||
23 | Db::new() | ||
21 | } | 24 | } |
22 | 25 | ||
26 | #[derive(Debug, Clone)] | ||
27 | pub struct Db { | ||
28 | // heh. also https://doc.rust-lang.org/std/collections/struct.LinkedList.html says Vec is generally faster | ||
29 | pub blockchain: Arc<RwLock<Vec<Block>>>, | ||
30 | // every proposer can have _one_ pending transaction, a way to enforce this, String is proposer identifier | ||
31 | pub pending_transactions: Arc<RwLock<HashMap<String, Transaction>>>, | ||
32 | } | ||
23 | 33 | ||
24 | // For presentation purposes keep mocked data in in-memory structure | 34 | impl Db { |
25 | // In real life scenario connection with regular database would be established | 35 | fn new() -> Self { |
26 | 36 | Db { | |
27 | pub type Db = Arc<Mutex<Vec<Transaction>>>; | 37 | blockchain: Arc::new(RwLock::new(Vec::new())), |
38 | pending_transactions: Arc::new(RwLock::new(HashMap::new())), | ||
39 | } | ||
40 | } | ||
41 | } | ||
28 | 42 | ||
43 | /// A transaction between `source` and `target` that moves `amount` | ||
29 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | 44 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] |
30 | pub struct Transaction { | 45 | pub struct Transaction { |
31 | pub source: String, | 46 | pub source: String, |
@@ -34,14 +49,22 @@ pub struct Transaction { | |||
34 | pub timestamp: NaiveDateTime, | 49 | pub timestamp: NaiveDateTime, |
35 | } | 50 | } |
36 | 51 | ||
52 | /// A block that was proposed with `transaction_list` and `nonce` that made `hash` valid | ||
37 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | 53 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] |
38 | pub struct Block { | 54 | pub struct Block { |
39 | pub transaction_list: Vec<Transaction>, // [Transaction; N] | 55 | pub transaction_list: Vec<String>, // hashes of the transactions (or just "source" for now) |
40 | pub nonce: i32, | 56 | pub nonce: i32, |
41 | pub timestamp: NaiveDateTime, | 57 | pub timestamp: NaiveDateTime, |
42 | pub hash: String, // future proof'd baby | 58 | pub hash: String, // future proof'd baby |
43 | } | 59 | } |
44 | 60 | ||
61 | // pub struct Ledger { | ||
62 | // // heh. also https://doc.rust-lang.org/std/collections/struct.LinkedList.html says Vec is generally faster | ||
63 | // blockchain: Vec<Block>, | ||
64 | // // every proposer can have _one_ pending transaction, a way to enforce this, String is proposer identifier | ||
65 | // pending_transactions: HashMap<String, Transaction>, | ||
66 | // } | ||
67 | |||
45 | // #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | 68 | // #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] |
46 | // #[serde(rename_all = "camelCase")] | 69 | // #[serde(rename_all = "camelCase")] |
47 | // pub struct Game { | 70 | // pub struct Game { |
@@ -99,6 +122,8 @@ pub struct Block { | |||
99 | // )) | 122 | // )) |
100 | // } | 123 | // } |
101 | 124 | ||
125 | // TODO: these tests are amazing, we should write some when schema is decided upon <07-04-21, yigit> // | ||
126 | |||
102 | // #[cfg(test)] | 127 | // #[cfg(test)] |
103 | // mod tests { | 128 | // mod tests { |
104 | // use super::*; | 129 | // use super::*; |
diff --git a/tester.sh b/tester.sh new file mode 100644 index 0000000..a0396d6 --- /dev/null +++ b/tester.sh | |||
@@ -0,0 +1,38 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | |||
3 | ## When in doubt just write a shell script | ||
4 | |||
5 | curl --request POST \ | ||
6 | --header 'Content-Type: application/json' \ | ||
7 | --data '{ | ||
8 | "source": "foo", | ||
9 | "target": "bar", | ||
10 | "amount": 5, | ||
11 | "timestamp": "2021-04-07T00:17:00" | ||
12 | }' \ | ||
13 | http://localhost:8080/transaction | ||
14 | |||
15 | curl --request POST \ | ||
16 | --header 'Content-Type: application/json' \ | ||
17 | --data '{ | ||
18 | "source": "saz", | ||
19 | "target": "quux", | ||
20 | "amount": 12, | ||
21 | "timestamp": "2021-04-07T00:17:00" | ||
22 | }' \ | ||
23 | http://localhost:8080/transaction | ||
24 | |||
25 | curl localhost:8080/transaction | ||
26 | |||
27 | curl --header "Content-Type: application/json" \ | ||
28 | --request POST \ | ||
29 | --data '{ | ||
30 | "transaction_list": [ | ||
31 | "foo", | ||
32 | "saz" | ||
33 | ], | ||
34 | "nonce": 4, | ||
35 | "timestamp": "2021-04-07T04:17:00", | ||
36 | "hash": "aaaaaa" | ||
37 | }' \ | ||
38 | http://localhost:8080/block | ||