diff options
author | Yigit Sever | 2021-04-07 04:33:45 +0300 |
---|---|---|
committer | Yigit Sever | 2021-04-07 04:35:44 +0300 |
commit | 20dad7d4290f2c98583168cd4b9afcdec4802944 (patch) | |
tree | b761b56e8b629d8a295b4dd7ca55a5d7745fd4c6 /src | |
parent | 95ff6371303ac28d05b25fd9f6e436c5d0a58d4c (diff) | |
download | gradecoin-20dad7d4290f2c98583168cd4b9afcdec4802944.tar.gz gradecoin-20dad7d4290f2c98583168cd4b9afcdec4802944.tar.bz2 gradecoin-20dad7d4290f2c98583168cd4b9afcdec4802944.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
Diffstat (limited to 'src')
-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 |
5 files changed, 152 insertions, 54 deletions
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::*; |