From 44d21b676f90a2fc8b255eb9c1393e53f40c9daa Mon Sep 17 00:00:00 2001
From: Yigit Sever
Date: Mon, 12 Apr 2021 05:32:53 +0300
Subject: 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
---
 Cargo.lock            | 35 ++++++++++++++++++++++
 Cargo.toml            |  2 ++
 TODO.md               |  4 +--
 examples/mining.rs    | 35 ++++++++++++++++++++++
 src/custom_filters.rs |  8 ++----
 src/handlers.rs       | 54 +++++++++++++++++++++++-----------
 src/lib.rs            |  9 ++++++
 src/main.rs           |  6 ++--
 src/routes.rs         | 80 ++++++++++++++++++++++++++++++++++++++++++++-------
 src/schema.rs         | 12 ++++++--
 10 files changed, 207 insertions(+), 38 deletions(-)
 create mode 100644 examples/mining.rs
 create mode 100644 src/lib.rs

diff --git a/Cargo.lock b/Cargo.lock
index d4d5926..1116c65 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -44,6 +44,17 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
 
+[[package]]
+name = "blake2"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4"
+dependencies = [
+ "crypto-mac",
+ "digest",
+ "opaque-debug",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.9.0"
@@ -122,6 +133,16 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
 
+[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
 [[package]]
 name = "digest"
 version = "0.9.0"
@@ -280,7 +301,9 @@ dependencies = [
 name = "gradecoin"
 version = "0.1.0"
 dependencies = [
+ "blake2",
  "chrono",
+ "hex-literal",
  "lazy_static",
  "log",
  "parking_lot",
@@ -352,6 +375,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hex-literal"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8"
+
 [[package]]
 name = "http"
 version = "0.2.3"
@@ -979,6 +1008,12 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
 [[package]]
 name = "syn"
 version = "1.0.68"
diff --git a/Cargo.toml b/Cargo.toml
index 2ae2b25..7e12c2d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,8 @@ pretty_env_logger = "0.3.1"
 parking_lot = "0.10.0"
 serde_json = "1.0.59"
 lazy_static = "1.4.0"
+blake2 = "0.9.1"
+hex-literal = "0.3.1"
 
 [dev-dependencies]
 serde_test = "1.0.117"
diff --git a/TODO.md b/TODO.md
index 47cf45b..e28373b 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,8 +1,6 @@
 # TODO
 
 ## Proof-of-work
-- [ ] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions)
-- [ ] check the nonce for incoming blocks
 
 ## Authentication
 - [X] pick a user authentication scheme = [JWT](https://tools.ietf.org/html/rfc7519) Seems perfect
@@ -26,3 +24,5 @@
 ## Done & Brag
 - [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code)
 - [x] We need our own representation of students and their grades, "there is no blockchain" (done at 2021-04-12 00:05)
+- [x] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions) (done at 2021-04-12 05:30)
+- [x] check the nonce for incoming blocks (done at 2021-04-12 05:30)
diff --git a/examples/mining.rs b/examples/mining.rs
new file mode 100644
index 0000000..56e33f3
--- /dev/null
+++ b/examples/mining.rs
@@ -0,0 +1,35 @@
+use chrono::NaiveDate;
+use gradecoin::schema::NakedBlock;
+use serde_json;
+use std::time::Instant;
+
+use blake2::{Blake2s, Digest};
+
+pub fn main() {
+    let mut b = NakedBlock {
+        transaction_list: vec![
+            "hash_value".to_owned(),
+        ],
+        nonce: 0,
+        timestamp: NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30),
+    };
+
+    let now = Instant::now();
+
+    for nonce in 0..u32::MAX {
+        b.nonce = nonce;
+
+        let j = serde_json::to_vec(&b).unwrap();
+
+        let result = Blake2s::digest(&j);
+
+        let first_five = result[31] as i32 + result[30] as i32 + (result[29] << 4) as i32;
+
+        if first_five == 0 {
+            println!("{} - {:x}\n{:?}", nonce, result, b);
+            break;
+        }
+    }
+
+    println!("it took {} seconds", now.elapsed().as_secs());
+}
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 @@
-// Common filters ment to be shared between many endpoints
-
+use gradecoin::schema::{AuthRequest, Block, Db, Transaction};
 use std::convert::Infallible;
 use warp::{Filter, Rejection};
 
-use crate::schema::{Block, Db, Transaction, AuthRequest};
-
 // Database context for routes
 pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone {
     warp::any().map(move || db.clone())
@@ -12,7 +9,8 @@ pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clo
 
 // Accept only json encoded User body and reject big payloads
 // TODO: find a good limit for this, (=e2482057; 8 char String + rsa pem) <11-04-21, yigit> //
-pub fn auth_request_json_body() -> impl Filter<Extract = (AuthRequest,), Error = Rejection> + Clone {
+pub fn auth_request_json_body() -> impl Filter<Extract = (AuthRequest,), Error = Rejection> + Clone
+{
     warp::body::content_length_limit(1024 * 32).and(warp::body::json())
 }
 
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 @@
 use log::debug;
 use parking_lot::RwLockUpgradableReadGuard;
 use serde_json;
+use serde_json::json;
 use std::convert::Infallible;
 use warp::{http::Response, http::StatusCode, reply};
 
+use blake2::{Blake2s, Digest};
+
 use std::fs;
 
-use crate::schema::{AuthRequest, Block, Db, MetuId, Transaction, User};
+use gradecoin::schema::{AuthRequest, Block, Db, MetuId, NakedBlock, Transaction, User};
 
 /// POST /register
 /// Enables a student to introduce themselves to the system
@@ -22,7 +25,6 @@ pub async fn authenticate_user(
         let userlist = db.users.upgradable_read();
 
         if userlist.contains_key(&given_id) {
-
             let res = Response::builder()
                 .status(StatusCode::BAD_REQUEST)
                 .body("This user is already authenticated");
@@ -124,24 +126,44 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply,
         }
     }
 
-    // TODO: check 2, block hash (\w nonce) asserts $hash_condition? <07-04-21, yigit> //
-    // assume it is for now
+    let naked_block = NakedBlock {
+        transaction_list: new_block.transaction_list.clone(),
+        nonce: new_block.nonce.clone(),
+        timestamp: new_block.timestamp.clone(),
+    };
 
-    let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain);
+    let naked_block_flat = serde_json::to_vec(&naked_block).unwrap();
 
-    let block_json = serde_json::to_string(&new_block).unwrap();
+    let hashvalue = Blake2s::digest(&naked_block_flat);
+    let hash_string = format!("{:x}", hashvalue);
 
-    // let mut file = File::create(format!("{}.block", new_block.timestamp.timestamp())).unwrap();
-    fs::write(
-        format!("blocks/{}.block", new_block.timestamp.timestamp()),
-        block_json,
-    )
-    .unwrap();
+    // 5 rightmost bits are zero
+    let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + (hashvalue[29] << 4) as i32;
 
-    *blockchain = new_block;
+    if should_zero == 0 {
+        // one last check to see if block is telling the truth
+        if hash_string == new_block.hash {
+            let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain);
 
-    let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
-    pending_transactions.clear();
+            let block_json = serde_json::to_string(&new_block).unwrap();
 
-    Ok(StatusCode::CREATED)
+            fs::write(
+                format!("blocks/{}.block", new_block.timestamp.timestamp()),
+                block_json,
+            )
+            .unwrap();
+
+            *blockchain = new_block;
+
+            let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
+            pending_transactions.clear();
+
+            Ok(StatusCode::CREATED)
+        } else {
+            Ok(StatusCode::BAD_REQUEST)
+        }
+    } else {
+        // reject
+        Ok(StatusCode::BAD_REQUEST)
+    }
 }
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 @@
+pub mod schema;
+
+pub use schema::create_database;
+pub use schema::AuthRequest;
+pub use schema::Block;
+pub use schema::Db;
+pub use schema::MetuId;
+pub use schema::Transaction;
+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 @@
+use gradecoin::schema::create_database;
 use std::env;
 use warp::Filter;
 
-mod handlers;
 mod custom_filters;
+mod handlers;
 mod routes;
-mod schema;
 // mod validators;
 
 #[tokio::main]
@@ -15,7 +15,7 @@ async fn main() {
     }
     pretty_env_logger::init();
 
-    let db = schema::create_database();
+    let db = create_database();
 
     let api = routes::consensus_routes(db);
 
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};
 
 use crate::custom_filters;
 use crate::handlers;
-use crate::schema::Db;
+use gradecoin::schema::Db;
 
 /// Root, all routes combined
 pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
@@ -65,12 +65,11 @@ mod tests {
     // use std::sync::Arc;
     use warp::http::StatusCode;
 
-    use crate::schema;
-    use crate::schema::{AuthRequest, Block, Transaction};
+    use gradecoin::schema::{create_database, AuthRequest, Block, Transaction};
 
     /// Create a mock database to be used in tests
     fn mocked_db() -> Db {
-        let db = schema::create_database();
+        let db = create_database();
 
         db.pending_transactions.write().insert(
             "hash_value".to_owned(),
@@ -88,7 +87,7 @@ mod tests {
                 "old_transaction_hash_2".to_owned(),
                 "old_transaction_hash_3".to_owned(),
             ],
-            nonce: "not_a_thing_yet".to_owned(),
+            nonce: 0,
             timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30),
             hash: "not_a_thing_yet".to_owned(),
         };
@@ -122,6 +121,26 @@ mod tests {
         }
     }
 
+    /// Create a mock block with a correct mined hash to be used in tests
+    fn mocked_block() -> Block {
+        Block {
+            transaction_list: vec!["hash_value".to_owned()],
+            nonce: 560108,
+            timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30),
+            hash: "c7d053f3e5b056ba948db3f5c0d30408fb0c29a328a0c3c1cf435fb68d700000".to_owned(),
+        }
+    }
+
+    /// Create a mock block with a wrong hash and nonce
+    fn mocked_wrong_block() -> Block {
+        Block {
+            transaction_list: vec!["foobarbaz".to_owned(), "dazsaz".to_owned()],
+            nonce: 1000, // can you imagine
+            timestamp: chrono::NaiveDate::from_ymd(2021, 04, 12).and_hms(05, 29, 30),
+            hash: "tnarstnarsuthnarsthlarjstk".to_owned(),
+        }
+    }
+
     /// Test simple GET request to /transaction, resource that exists
     /// https://tools.ietf.org/html/rfc7231#section-6.3.1
     /// We should get the only pending transaction available in the database as json
@@ -160,7 +179,7 @@ mod tests {
 
         assert_eq!(res.status(), StatusCode::OK);
 
-        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"}"#;
+        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"}"#;
         assert_eq!(res.body(), expected_json_body);
     }
 
@@ -201,7 +220,48 @@ mod tests {
         assert_eq!(db.pending_transactions.read().len(), 2);
     }
 
-    /// TEST a POST request to /transaction, an endpoint that exists
+    /// Test a POST request to /block, a resource that exists
+    /// https://tools.ietf.org/html/rfc7231#section-6.3.2
+    /// Should accept the json request, create
+    /// the block
+    #[tokio::test]
+    async fn post_block_201() {
+        let db = mocked_db();
+        let filter = consensus_routes(db.clone());
+
+        let res = warp::test::request()
+            .method("POST")
+            .json(&mocked_block())
+            .path("/block")
+            .reply(&filter)
+            .await;
+
+        assert_eq!(res.status(), StatusCode::CREATED);
+        assert_eq!(
+            *db.blockchain.read().hash,
+            "c7d053f3e5b056ba948db3f5c0d30408fb0c29a328a0c3c1cf435fb68d700000".to_owned()
+        );
+    }
+
+    /// Test a POST request to /block, a resource that exists
+    /// https://tools.ietf.org/html/rfc7231#section-6.3.2
+    /// Should reject the block because of the wrong hash
+    #[tokio::test]
+    async fn post_block_wrong_hash() {
+        let db = mocked_db();
+        let filter = consensus_routes(db.clone());
+
+        let res = warp::test::request()
+            .method("POST")
+            .json(&mocked_wrong_block())
+            .path("/block")
+            .reply(&filter)
+            .await;
+
+        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
+    }
+
+    /// Test a POST request to /register, an endpoint that exists
     /// https://tools.ietf.org/html/rfc7231#section-6.3.2
     /// Should accept the json request, create a new user and
     /// add it to the user hashmap in the db
@@ -221,9 +281,10 @@ mod tests {
         assert_eq!(res.status(), StatusCode::CREATED);
         assert_eq!(db.users.read().len(), 1);
     }
-    /// TEST a POST request to /transaction, an endpoint that exists
+
+    /// Test a POST request to /transaction, an endpoint that exists
     /// https://tools.ietf.org/html/rfc7231#section-6.3.2
-    /// Should NOT accept the json request
+    /// Should NOT accept the json request as the user is unpriviliged
     #[tokio::test]
     async fn post_register_unpriviliged_user() {
         let db = mocked_db();
@@ -261,6 +322,5 @@ mod tests {
     }
 }
 
-// TODO: POST block test <09-04-21, yigit> //
 // TODO: POST block without correct transactions test <09-04-21, yigit> //
 // 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 {
     // somewhere
     // I want to keep this as a String vector because it makes things easier elsewhere
     pub transaction_list: Vec<String>, // hashes of the transactions (or just "source" for now)
-    pub nonce: String,
+    pub nonce: u32,
     pub timestamp: NaiveDateTime,
     pub hash: String, // future proof'd baby
 }
 
+/// For prototyping and letting serde handle everything json
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NakedBlock {
+    pub transaction_list: Vec<String>,
+    pub nonce: u32,
+    pub timestamp: NaiveDateTime,
+}
+
 impl Block {
     /// Genesis block
     pub fn new() -> Block {
         Block {
             transaction_list: vec![],
-            nonce: String::from(""),
+            nonce: 0,
             timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00),
             hash: String::from(""),
         }
-- 
cgit v1.2.3-70-g09d2