aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYigit Sever2021-04-12 05:32:53 +0300
committerYigit Sever2021-04-12 05:32:53 +0300
commit44d21b676f90a2fc8b255eb9c1393e53f40c9daa (patch)
treeedfadfd279dc9fcfaa6c27f819c7f0e69d14599c
parent6c0345ecda5e46da88bc6ca513a28c648c29833c (diff)
downloadgradecoin-44d21b676f90a2fc8b255eb9c1393e53f40c9daa.tar.gz
gradecoin-44d21b676f90a2fc8b255eb9c1393e53f40c9daa.tar.bz2
gradecoin-44d21b676f90a2fc8b255eb9c1393e53f40c9daa.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
-rw-r--r--Cargo.lock35
-rw-r--r--Cargo.toml2
-rw-r--r--TODO.md4
-rw-r--r--examples/mining.rs35
-rw-r--r--src/custom_filters.rs8
-rw-r--r--src/handlers.rs54
-rw-r--r--src/lib.rs9
-rw-r--r--src/main.rs6
-rw-r--r--src/routes.rs80
-rw-r--r--src/schema.rs12
10 files changed, 207 insertions, 38 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d4d5926..1116c65 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -45,6 +45,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
45checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 45checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
46 46
47[[package]] 47[[package]]
48name = "blake2"
49version = "0.9.1"
50source = "registry+https://github.com/rust-lang/crates.io-index"
51checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4"
52dependencies = [
53 "crypto-mac",
54 "digest",
55 "opaque-debug",
56]
57
58[[package]]
48name = "block-buffer" 59name = "block-buffer"
49version = "0.9.0" 60version = "0.9.0"
50source = "registry+https://github.com/rust-lang/crates.io-index" 61source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -123,6 +134,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
123checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 134checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
124 135
125[[package]] 136[[package]]
137name = "crypto-mac"
138version = "0.8.0"
139source = "registry+https://github.com/rust-lang/crates.io-index"
140checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
141dependencies = [
142 "generic-array",
143 "subtle",
144]
145
146[[package]]
126name = "digest" 147name = "digest"
127version = "0.9.0" 148version = "0.9.0"
128source = "registry+https://github.com/rust-lang/crates.io-index" 149source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -280,7 +301,9 @@ dependencies = [
280name = "gradecoin" 301name = "gradecoin"
281version = "0.1.0" 302version = "0.1.0"
282dependencies = [ 303dependencies = [
304 "blake2",
283 "chrono", 305 "chrono",
306 "hex-literal",
284 "lazy_static", 307 "lazy_static",
285 "log", 308 "log",
286 "parking_lot", 309 "parking_lot",
@@ -353,6 +376,12 @@ dependencies = [
353] 376]
354 377
355[[package]] 378[[package]]
379name = "hex-literal"
380version = "0.3.1"
381source = "registry+https://github.com/rust-lang/crates.io-index"
382checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8"
383
384[[package]]
356name = "http" 385name = "http"
357version = "0.2.3" 386version = "0.2.3"
358source = "registry+https://github.com/rust-lang/crates.io-index" 387source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -980,6 +1009,12 @@ dependencies = [
980] 1009]
981 1010
982[[package]] 1011[[package]]
1012name = "subtle"
1013version = "2.4.0"
1014source = "registry+https://github.com/rust-lang/crates.io-index"
1015checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
1016
1017[[package]]
983name = "syn" 1018name = "syn"
984version = "1.0.68" 1019version = "1.0.68"
985source = "registry+https://github.com/rust-lang/crates.io-index" 1020source = "registry+https://github.com/rust-lang/crates.io-index"
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"
16parking_lot = "0.10.0" 16parking_lot = "0.10.0"
17serde_json = "1.0.59" 17serde_json = "1.0.59"
18lazy_static = "1.4.0" 18lazy_static = "1.4.0"
19blake2 = "0.9.1"
20hex-literal = "0.3.1"
19 21
20[dev-dependencies] 22[dev-dependencies]
21serde_test = "1.0.117" 23serde_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 @@
1# TODO 1# TODO
2 2
3## Proof-of-work 3## Proof-of-work
4- [ ] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions)
5- [ ] check the nonce for incoming blocks
6 4
7## Authentication 5## Authentication
8- [X] pick a user authentication scheme = [JWT](https://tools.ietf.org/html/rfc7519) Seems perfect 6- [X] pick a user authentication scheme = [JWT](https://tools.ietf.org/html/rfc7519) Seems perfect
@@ -26,3 +24,5 @@
26## Done & Brag 24## Done & Brag
27- [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code) 25- [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code)
28- [x] We need our own representation of students and their grades, "there is no blockchain" (done at 2021-04-12 00:05) 26- [x] We need our own representation of students and their grades, "there is no blockchain" (done at 2021-04-12 00:05)
27- [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)
28- [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 @@
1use chrono::NaiveDate;
2use gradecoin::schema::NakedBlock;
3use serde_json;
4use std::time::Instant;
5
6use blake2::{Blake2s, Digest};
7
8pub fn main() {
9 let mut b = NakedBlock {
10 transaction_list: vec![
11 "hash_value".to_owned(),
12 ],
13 nonce: 0,
14 timestamp: NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30),
15 };
16
17 let now = Instant::now();
18
19 for nonce in 0..u32::MAX {
20 b.nonce = nonce;
21
22 let j = serde_json::to_vec(&b).unwrap();
23
24 let result = Blake2s::digest(&j);
25
26 let first_five = result[31] as i32 + result[30] as i32 + (result[29] << 4) as i32;
27
28 if first_five == 0 {
29 println!("{} - {:x}\n{:?}", nonce, result, b);
30 break;
31 }
32 }
33
34 println!("it took {} seconds", now.elapsed().as_secs());
35}
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 1use gradecoin::schema::{AuthRequest, Block, Db, Transaction};
2
3use std::convert::Infallible; 2use std::convert::Infallible;
4use warp::{Filter, Rejection}; 3use warp::{Filter, Rejection};
5 4
6use crate::schema::{Block, Db, Transaction, AuthRequest};
7
8// Database context for routes 5// Database context for routes
9pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { 6pub 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> //
15pub fn auth_request_json_body() -> impl Filter<Extract = (AuthRequest,), Error = Rejection> + Clone { 12pub 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 @@
2use log::debug; 2use log::debug;
3use parking_lot::RwLockUpgradableReadGuard; 3use parking_lot::RwLockUpgradableReadGuard;
4use serde_json; 4use serde_json;
5use serde_json::json;
5use std::convert::Infallible; 6use std::convert::Infallible;
6use warp::{http::Response, http::StatusCode, reply}; 7use warp::{http::Response, http::StatusCode, reply};
7 8
9use blake2::{Blake2s, Digest};
10
8use std::fs; 11use std::fs;
9 12
10use crate::schema::{AuthRequest, Block, Db, MetuId, Transaction, User}; 13use 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 @@
1pub mod schema;
2
3pub use schema::create_database;
4pub use schema::AuthRequest;
5pub use schema::Block;
6pub use schema::Db;
7pub use schema::MetuId;
8pub use schema::Transaction;
9pub 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 @@
1use gradecoin::schema::create_database;
1use std::env; 2use std::env;
2use warp::Filter; 3use warp::Filter;
3 4
4mod handlers;
5mod custom_filters; 5mod custom_filters;
6mod handlers;
6mod routes; 7mod routes;
7mod 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
3use crate::custom_filters; 3use crate::custom_filters;
4use crate::handlers; 4use crate::handlers;
5use crate::schema::Db; 5use gradecoin::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 {
@@ -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)]
72pub struct NakedBlock {
73 pub transaction_list: Vec<String>,
74 pub nonce: u32,
75 pub timestamp: NaiveDateTime,
76}
77
70impl Block { 78impl 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 }