summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/custom_filters.rs11
-rw-r--r--src/handlers.rs94
-rw-r--r--src/main.rs6
-rw-r--r--src/routes.rs30
-rw-r--r--src/schema.rs65
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 @@
3use std::convert::Infallible; 3use std::convert::Infallible;
4use warp::{Filter, Rejection}; 4use warp::{Filter, Rejection};
5 5
6use crate::schema::{Db, Transaction}; // `Block` coming later 6use crate::schema::{Block, Db, Transaction};
7 7
8// Database context for routes 8// Database context for routes
9pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { 9pub 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
19pub fn json_body() -> impl Filter<Extract = (Transaction,), Error = Rejection> + Clone { 19pub 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
24pub 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 2use log::debug; // this is more useful than debug! learn how to use this
3use log::debug; 3use parking_lot::RwLockUpgradableReadGuard;
4use std::convert::Infallible; 4use std::convert::Infallible;
5use warp::{http::StatusCode, reply}; 5use warp::{http::StatusCode, reply};
6 6
7use crate::schema::{Db, Transaction}; // `Block` coming later 7use crate::schema::{Block, Db, Transaction};
8
9/// GET /transaction
10/// Returns JSON array of transactions
11/// Cannot fail
12pub 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
30pub 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> //
11pub async fn propose_transaction( 47pub 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? 64pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, warp::Rejection> {
28pub 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;
11async fn main() { 11async 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;
4use crate::handlers; 4use crate::handlers;
5use crate::schema::Db; 5use crate::schema::Db;
6 6
7// Root, all routes combined 7/// Root, all routes combined
8pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 8pub 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
13pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 16pub 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
24pub 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
21pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 32pub 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
41pub 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 1use chrono::NaiveDateTime;
2 2use parking_lot::RwLock;
3use chrono::{NaiveDate, NaiveDateTime};
4use serde::{Deserialize, Serialize}; 3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc; 5use std::sync::Arc;
6use tokio::sync::Mutex;
7 6
8// use crate::validators; 7// use crate::validators;
9 8
10pub 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
22pub fn create_database() -> Db {
23 Db::new()
21} 24}
22 25
26#[derive(Debug, Clone)]
27pub 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 34impl Db {
25// In real life scenario connection with regular database would be established 35 fn new() -> Self {
26 36 Db {
27pub 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)]
30pub struct Transaction { 45pub 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)]
38pub struct Block { 54pub 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::*;