diff options
author | Yigit Sever | 2021-04-12 05:32:53 +0300 |
---|---|---|
committer | Yigit Sever | 2021-04-12 05:32:53 +0300 |
commit | e0fb91039f34204b2a5c588a95cb3f1789ad2fa7 (patch) | |
tree | 9c38670c348f04c57185639c541a1a93b8cfde2a /src | |
parent | 62aa261e1fd531afd1e5fa7244471d051a78db38 (diff) | |
download | gradecoin-e0fb91039f34204b2a5c588a95cb3f1789ad2fa7.tar.gz gradecoin-e0fb91039f34204b2a5c588a95cb3f1789ad2fa7.tar.bz2 gradecoin-e0fb91039f34204b2a5c588a95cb3f1789ad2fa7.zip |
Implement proof-of-work
Using blacke2s: https://docs.rs/blake2/0.9.1/blake2/
Using this guy's hash checker https://gist.github.com/gkbrk/2e4835e3a17b3fb6e1e7
blacke2s with 5 bits 0 can mine a block between 20 seconds to 359 during
my tests, hope it'll be fun
Diffstat (limited to 'src')
-rw-r--r-- | src/custom_filters.rs | 8 | ||||
-rw-r--r-- | src/handlers.rs | 54 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/main.rs | 6 | ||||
-rw-r--r-- | src/routes.rs | 80 | ||||
-rw-r--r-- | src/schema.rs | 12 |
6 files changed, 133 insertions, 36 deletions
diff --git a/src/custom_filters.rs b/src/custom_filters.rs index 0806c6d..315ba4a 100644 --- a/src/custom_filters.rs +++ b/src/custom_filters.rs | |||
@@ -1,10 +1,7 @@ | |||
1 | // Common filters ment to be shared between many endpoints | 1 | use gradecoin::schema::{AuthRequest, Block, Db, Transaction}; |
2 | |||
3 | use std::convert::Infallible; | 2 | use std::convert::Infallible; |
4 | use warp::{Filter, Rejection}; | 3 | use warp::{Filter, Rejection}; |
5 | 4 | ||
6 | use crate::schema::{Block, Db, Transaction, AuthRequest}; | ||
7 | |||
8 | // Database context for routes | 5 | // Database context for routes |
9 | pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { | 6 | pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { |
10 | warp::any().map(move || db.clone()) | 7 | warp::any().map(move || db.clone()) |
@@ -12,7 +9,8 @@ pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clo | |||
12 | 9 | ||
13 | // Accept only json encoded User body and reject big payloads | 10 | // Accept only json encoded User body and reject big payloads |
14 | // TODO: find a good limit for this, (=e2482057; 8 char String + rsa pem) <11-04-21, yigit> // | 11 | // TODO: find a good limit for this, (=e2482057; 8 char String + rsa pem) <11-04-21, yigit> // |
15 | pub fn auth_request_json_body() -> impl Filter<Extract = (AuthRequest,), Error = Rejection> + Clone { | 12 | pub fn auth_request_json_body() -> impl Filter<Extract = (AuthRequest,), Error = Rejection> + Clone |
13 | { | ||
16 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | 14 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) |
17 | } | 15 | } |
18 | 16 | ||
diff --git a/src/handlers.rs b/src/handlers.rs index bfd57bc..6edc96f 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
@@ -2,12 +2,15 @@ | |||
2 | use log::debug; | 2 | use log::debug; |
3 | use parking_lot::RwLockUpgradableReadGuard; | 3 | use parking_lot::RwLockUpgradableReadGuard; |
4 | use serde_json; | 4 | use serde_json; |
5 | use serde_json::json; | ||
5 | use std::convert::Infallible; | 6 | use std::convert::Infallible; |
6 | use warp::{http::Response, http::StatusCode, reply}; | 7 | use warp::{http::Response, http::StatusCode, reply}; |
7 | 8 | ||
9 | use blake2::{Blake2s, Digest}; | ||
10 | |||
8 | use std::fs; | 11 | use std::fs; |
9 | 12 | ||
10 | use crate::schema::{AuthRequest, Block, Db, MetuId, Transaction, User}; | 13 | use gradecoin::schema::{AuthRequest, Block, Db, MetuId, NakedBlock, Transaction, User}; |
11 | 14 | ||
12 | /// POST /register | 15 | /// POST /register |
13 | /// Enables a student to introduce themselves to the system | 16 | /// Enables a student to introduce themselves to the system |
@@ -22,7 +25,6 @@ pub async fn authenticate_user( | |||
22 | let userlist = db.users.upgradable_read(); | 25 | let userlist = db.users.upgradable_read(); |
23 | 26 | ||
24 | if userlist.contains_key(&given_id) { | 27 | if userlist.contains_key(&given_id) { |
25 | |||
26 | let res = Response::builder() | 28 | let res = Response::builder() |
27 | .status(StatusCode::BAD_REQUEST) | 29 | .status(StatusCode::BAD_REQUEST) |
28 | .body("This user is already authenticated"); | 30 | .body("This user is already authenticated"); |
@@ -124,24 +126,44 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, | |||
124 | } | 126 | } |
125 | } | 127 | } |
126 | 128 | ||
127 | // TODO: check 2, block hash (\w nonce) asserts $hash_condition? <07-04-21, yigit> // | 129 | let naked_block = NakedBlock { |
128 | // assume it is for now | 130 | transaction_list: new_block.transaction_list.clone(), |
131 | nonce: new_block.nonce.clone(), | ||
132 | timestamp: new_block.timestamp.clone(), | ||
133 | }; | ||
129 | 134 | ||
130 | let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); | 135 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); |
131 | 136 | ||
132 | let block_json = serde_json::to_string(&new_block).unwrap(); | 137 | let hashvalue = Blake2s::digest(&naked_block_flat); |
138 | let hash_string = format!("{:x}", hashvalue); | ||
133 | 139 | ||
134 | // let mut file = File::create(format!("{}.block", new_block.timestamp.timestamp())).unwrap(); | 140 | // 5 rightmost bits are zero |
135 | fs::write( | 141 | let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + (hashvalue[29] << 4) as i32; |
136 | format!("blocks/{}.block", new_block.timestamp.timestamp()), | ||
137 | block_json, | ||
138 | ) | ||
139 | .unwrap(); | ||
140 | 142 | ||
141 | *blockchain = new_block; | 143 | if should_zero == 0 { |
144 | // one last check to see if block is telling the truth | ||
145 | if hash_string == new_block.hash { | ||
146 | let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); | ||
142 | 147 | ||
143 | let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); | 148 | let block_json = serde_json::to_string(&new_block).unwrap(); |
144 | pending_transactions.clear(); | ||
145 | 149 | ||
146 | Ok(StatusCode::CREATED) | 150 | fs::write( |
151 | format!("blocks/{}.block", new_block.timestamp.timestamp()), | ||
152 | block_json, | ||
153 | ) | ||
154 | .unwrap(); | ||
155 | |||
156 | *blockchain = new_block; | ||
157 | |||
158 | let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); | ||
159 | pending_transactions.clear(); | ||
160 | |||
161 | Ok(StatusCode::CREATED) | ||
162 | } else { | ||
163 | Ok(StatusCode::BAD_REQUEST) | ||
164 | } | ||
165 | } else { | ||
166 | // reject | ||
167 | Ok(StatusCode::BAD_REQUEST) | ||
168 | } | ||
147 | } | 169 | } |
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..aed4591 --- /dev/null +++ b/src/lib.rs | |||
@@ -0,0 +1,9 @@ | |||
1 | pub mod schema; | ||
2 | |||
3 | pub use schema::create_database; | ||
4 | pub use schema::AuthRequest; | ||
5 | pub use schema::Block; | ||
6 | pub use schema::Db; | ||
7 | pub use schema::MetuId; | ||
8 | pub use schema::Transaction; | ||
9 | pub use schema::User; | ||
diff --git a/src/main.rs b/src/main.rs index 373223c..5683aea 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -1,10 +1,10 @@ | |||
1 | use gradecoin::schema::create_database; | ||
1 | use std::env; | 2 | use std::env; |
2 | use warp::Filter; | 3 | use warp::Filter; |
3 | 4 | ||
4 | mod handlers; | ||
5 | mod custom_filters; | 5 | mod custom_filters; |
6 | mod handlers; | ||
6 | mod routes; | 7 | mod routes; |
7 | mod schema; | ||
8 | // mod validators; | 8 | // mod validators; |
9 | 9 | ||
10 | #[tokio::main] | 10 | #[tokio::main] |
@@ -15,7 +15,7 @@ async fn main() { | |||
15 | } | 15 | } |
16 | pretty_env_logger::init(); | 16 | pretty_env_logger::init(); |
17 | 17 | ||
18 | let db = schema::create_database(); | 18 | let db = create_database(); |
19 | 19 | ||
20 | let api = routes::consensus_routes(db); | 20 | let api = routes::consensus_routes(db); |
21 | 21 | ||
diff --git a/src/routes.rs b/src/routes.rs index 9f0adc5..03a2569 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
@@ -2,7 +2,7 @@ use warp::{Filter, Rejection, Reply}; | |||
2 | 2 | ||
3 | use crate::custom_filters; | 3 | use crate::custom_filters; |
4 | use crate::handlers; | 4 | use crate::handlers; |
5 | use crate::schema::Db; | 5 | use gradecoin::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 { |
@@ -65,12 +65,11 @@ mod tests { | |||
65 | // use std::sync::Arc; | 65 | // use std::sync::Arc; |
66 | use warp::http::StatusCode; | 66 | use warp::http::StatusCode; |
67 | 67 | ||
68 | use crate::schema; | 68 | use gradecoin::schema::{create_database, AuthRequest, Block, Transaction}; |
69 | use crate::schema::{AuthRequest, Block, Transaction}; | ||
70 | 69 | ||
71 | /// Create a mock database to be used in tests | 70 | /// Create a mock database to be used in tests |
72 | fn mocked_db() -> Db { | 71 | fn mocked_db() -> Db { |
73 | let db = schema::create_database(); | 72 | let db = create_database(); |
74 | 73 | ||
75 | db.pending_transactions.write().insert( | 74 | db.pending_transactions.write().insert( |
76 | "hash_value".to_owned(), | 75 | "hash_value".to_owned(), |
@@ -88,7 +87,7 @@ mod tests { | |||
88 | "old_transaction_hash_2".to_owned(), | 87 | "old_transaction_hash_2".to_owned(), |
89 | "old_transaction_hash_3".to_owned(), | 88 | "old_transaction_hash_3".to_owned(), |
90 | ], | 89 | ], |
91 | nonce: "not_a_thing_yet".to_owned(), | 90 | nonce: 0, |
92 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), | 91 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), |
93 | hash: "not_a_thing_yet".to_owned(), | 92 | hash: "not_a_thing_yet".to_owned(), |
94 | }; | 93 | }; |
@@ -122,6 +121,26 @@ mod tests { | |||
122 | } | 121 | } |
123 | } | 122 | } |
124 | 123 | ||
124 | /// Create a mock block with a correct mined hash to be used in tests | ||
125 | fn mocked_block() -> Block { | ||
126 | Block { | ||
127 | transaction_list: vec!["hash_value".to_owned()], | ||
128 | nonce: 560108, | ||
129 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), | ||
130 | hash: "c7d053f3e5b056ba948db3f5c0d30408fb0c29a328a0c3c1cf435fb68d700000".to_owned(), | ||
131 | } | ||
132 | } | ||
133 | |||
134 | /// Create a mock block with a wrong hash and nonce | ||
135 | fn mocked_wrong_block() -> Block { | ||
136 | Block { | ||
137 | transaction_list: vec!["foobarbaz".to_owned(), "dazsaz".to_owned()], | ||
138 | nonce: 1000, // can you imagine | ||
139 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 12).and_hms(05, 29, 30), | ||
140 | hash: "tnarstnarsuthnarsthlarjstk".to_owned(), | ||
141 | } | ||
142 | } | ||
143 | |||
125 | /// Test simple GET request to /transaction, resource that exists | 144 | /// Test simple GET request to /transaction, resource that exists |
126 | /// https://tools.ietf.org/html/rfc7231#section-6.3.1 | 145 | /// https://tools.ietf.org/html/rfc7231#section-6.3.1 |
127 | /// We should get the only pending transaction available in the database as json | 146 | /// We should get the only pending transaction available in the database as json |
@@ -160,7 +179,7 @@ mod tests { | |||
160 | 179 | ||
161 | assert_eq!(res.status(), StatusCode::OK); | 180 | assert_eq!(res.status(), StatusCode::OK); |
162 | 181 | ||
163 | let expected_json_body = r#"{"transaction_list":["old_transaction_hash_1","old_transaction_hash_2","old_transaction_hash_3"],"nonce":"not_a_thing_yet","timestamp":"2021-04-08T12:30:30","hash":"not_a_thing_yet"}"#; | 182 | let expected_json_body = r#"{"transaction_list":["old_transaction_hash_1","old_transaction_hash_2","old_transaction_hash_3"],"nonce":0,"timestamp":"2021-04-08T12:30:30","hash":"not_a_thing_yet"}"#; |
164 | assert_eq!(res.body(), expected_json_body); | 183 | assert_eq!(res.body(), expected_json_body); |
165 | } | 184 | } |
166 | 185 | ||
@@ -201,7 +220,48 @@ mod tests { | |||
201 | assert_eq!(db.pending_transactions.read().len(), 2); | 220 | assert_eq!(db.pending_transactions.read().len(), 2); |
202 | } | 221 | } |
203 | 222 | ||
204 | /// TEST a POST request to /transaction, an endpoint that exists | 223 | /// Test a POST request to /block, a resource that exists |
224 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
225 | /// Should accept the json request, create | ||
226 | /// the block | ||
227 | #[tokio::test] | ||
228 | async fn post_block_201() { | ||
229 | let db = mocked_db(); | ||
230 | let filter = consensus_routes(db.clone()); | ||
231 | |||
232 | let res = warp::test::request() | ||
233 | .method("POST") | ||
234 | .json(&mocked_block()) | ||
235 | .path("/block") | ||
236 | .reply(&filter) | ||
237 | .await; | ||
238 | |||
239 | assert_eq!(res.status(), StatusCode::CREATED); | ||
240 | assert_eq!( | ||
241 | *db.blockchain.read().hash, | ||
242 | "c7d053f3e5b056ba948db3f5c0d30408fb0c29a328a0c3c1cf435fb68d700000".to_owned() | ||
243 | ); | ||
244 | } | ||
245 | |||
246 | /// Test a POST request to /block, a resource that exists | ||
247 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
248 | /// Should reject the block because of the wrong hash | ||
249 | #[tokio::test] | ||
250 | async fn post_block_wrong_hash() { | ||
251 | let db = mocked_db(); | ||
252 | let filter = consensus_routes(db.clone()); | ||
253 | |||
254 | let res = warp::test::request() | ||
255 | .method("POST") | ||
256 | .json(&mocked_wrong_block()) | ||
257 | .path("/block") | ||
258 | .reply(&filter) | ||
259 | .await; | ||
260 | |||
261 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | ||
262 | } | ||
263 | |||
264 | /// Test a POST request to /register, an endpoint that exists | ||
205 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | 265 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 |
206 | /// Should accept the json request, create a new user and | 266 | /// Should accept the json request, create a new user and |
207 | /// add it to the user hashmap in the db | 267 | /// add it to the user hashmap in the db |
@@ -221,9 +281,10 @@ mod tests { | |||
221 | assert_eq!(res.status(), StatusCode::CREATED); | 281 | assert_eq!(res.status(), StatusCode::CREATED); |
222 | assert_eq!(db.users.read().len(), 1); | 282 | assert_eq!(db.users.read().len(), 1); |
223 | } | 283 | } |
224 | /// TEST a POST request to /transaction, an endpoint that exists | 284 | |
285 | /// Test a POST request to /transaction, an endpoint that exists | ||
225 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | 286 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 |
226 | /// Should NOT accept the json request | 287 | /// Should NOT accept the json request as the user is unpriviliged |
227 | #[tokio::test] | 288 | #[tokio::test] |
228 | async fn post_register_unpriviliged_user() { | 289 | async fn post_register_unpriviliged_user() { |
229 | let db = mocked_db(); | 290 | let db = mocked_db(); |
@@ -261,6 +322,5 @@ mod tests { | |||
261 | } | 322 | } |
262 | } | 323 | } |
263 | 324 | ||
264 | // TODO: POST block test <09-04-21, yigit> // | ||
265 | // TODO: POST block without correct transactions test <09-04-21, yigit> // | 325 | // TODO: POST block without correct transactions test <09-04-21, yigit> // |
266 | // TODO: POST transaction while that source has pending transaction test <09-04-21, yigit> // | 326 | // TODO: POST transaction while that source has pending transaction test <09-04-21, yigit> // |
diff --git a/src/schema.rs b/src/schema.rs index 909b5cd..98291d7 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
@@ -62,17 +62,25 @@ pub struct Block { | |||
62 | // somewhere | 62 | // somewhere |
63 | // I want to keep this as a String vector because it makes things easier elsewhere | 63 | // I want to keep this as a String vector because it makes things easier elsewhere |
64 | pub transaction_list: Vec<String>, // hashes of the transactions (or just "source" for now) | 64 | pub transaction_list: Vec<String>, // hashes of the transactions (or just "source" for now) |
65 | pub nonce: String, | 65 | pub nonce: u32, |
66 | pub timestamp: NaiveDateTime, | 66 | pub timestamp: NaiveDateTime, |
67 | pub hash: String, // future proof'd baby | 67 | pub hash: String, // future proof'd baby |
68 | } | 68 | } |
69 | 69 | ||
70 | /// For prototyping and letting serde handle everything json | ||
71 | #[derive(Serialize, Deserialize, Debug)] | ||
72 | pub struct NakedBlock { | ||
73 | pub transaction_list: Vec<String>, | ||
74 | pub nonce: u32, | ||
75 | pub timestamp: NaiveDateTime, | ||
76 | } | ||
77 | |||
70 | impl Block { | 78 | impl Block { |
71 | /// Genesis block | 79 | /// Genesis block |
72 | pub fn new() -> Block { | 80 | pub fn new() -> Block { |
73 | Block { | 81 | Block { |
74 | transaction_list: vec![], | 82 | transaction_list: vec![], |
75 | nonce: String::from(""), | 83 | nonce: 0, |
76 | timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00), | 84 | timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00), |
77 | hash: String::from(""), | 85 | hash: String::from(""), |
78 | } | 86 | } |