aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock63
-rw-r--r--Cargo.toml1
-rw-r--r--TODO.md6
-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
-rw-r--r--tester.sh38
9 files changed, 258 insertions, 56 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c531d87..45ae0a0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -108,6 +108,15 @@ dependencies = [
108] 108]
109 109
110[[package]] 110[[package]]
111name = "cloudabi"
112version = "0.0.3"
113source = "registry+https://github.com/rust-lang/crates.io-index"
114checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
115dependencies = [
116 "bitflags",
117]
118
119[[package]]
111name = "cpuid-bool" 120name = "cpuid-bool"
112version = "0.1.2" 121version = "0.1.2"
113source = "registry+https://github.com/rust-lang/crates.io-index" 122source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -273,6 +282,7 @@ version = "0.1.0"
273dependencies = [ 282dependencies = [
274 "chrono", 283 "chrono",
275 "log", 284 "log",
285 "parking_lot",
276 "pretty_env_logger", 286 "pretty_env_logger",
277 "serde", 287 "serde",
278 "serde_json", 288 "serde_json",
@@ -475,6 +485,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
475checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" 485checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
476 486
477[[package]] 487[[package]]
488name = "lock_api"
489version = "0.3.4"
490source = "registry+https://github.com/rust-lang/crates.io-index"
491checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
492dependencies = [
493 "scopeguard",
494]
495
496[[package]]
478name = "log" 497name = "log"
479version = "0.4.14" 498version = "0.4.14"
480source = "registry+https://github.com/rust-lang/crates.io-index" 499source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -597,6 +616,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
597checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 616checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
598 617
599[[package]] 618[[package]]
619name = "parking_lot"
620version = "0.10.2"
621source = "registry+https://github.com/rust-lang/crates.io-index"
622checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
623dependencies = [
624 "lock_api",
625 "parking_lot_core",
626]
627
628[[package]]
629name = "parking_lot_core"
630version = "0.7.2"
631source = "registry+https://github.com/rust-lang/crates.io-index"
632checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
633dependencies = [
634 "cfg-if 0.1.10",
635 "cloudabi",
636 "libc",
637 "redox_syscall 0.1.57",
638 "smallvec",
639 "winapi 0.3.9",
640]
641
642[[package]]
600name = "percent-encoding" 643name = "percent-encoding"
601version = "2.1.0" 644version = "2.1.0"
602source = "registry+https://github.com/rust-lang/crates.io-index" 645source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -784,6 +827,12 @@ dependencies = [
784 827
785[[package]] 828[[package]]
786name = "redox_syscall" 829name = "redox_syscall"
830version = "0.1.57"
831source = "registry+https://github.com/rust-lang/crates.io-index"
832checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
833
834[[package]]
835name = "redox_syscall"
787version = "0.2.5" 836version = "0.2.5"
788source = "registry+https://github.com/rust-lang/crates.io-index" 837source = "registry+https://github.com/rust-lang/crates.io-index"
789checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" 838checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
@@ -836,6 +885,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
836checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 885checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
837 886
838[[package]] 887[[package]]
888name = "scopeguard"
889version = "1.1.0"
890source = "registry+https://github.com/rust-lang/crates.io-index"
891checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
892
893[[package]]
839name = "serde" 894name = "serde"
840version = "1.0.125" 895version = "1.0.125"
841source = "registry+https://github.com/rust-lang/crates.io-index" 896source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -907,6 +962,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
907checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 962checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
908 963
909[[package]] 964[[package]]
965name = "smallvec"
966version = "1.6.1"
967source = "registry+https://github.com/rust-lang/crates.io-index"
968checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
969
970[[package]]
910name = "socket2" 971name = "socket2"
911version = "0.3.19" 972version = "0.3.19"
912source = "registry+https://github.com/rust-lang/crates.io-index" 973source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -937,7 +998,7 @@ dependencies = [
937 "cfg-if 1.0.0", 998 "cfg-if 1.0.0",
938 "libc", 999 "libc",
939 "rand 0.8.3", 1000 "rand 0.8.3",
940 "redox_syscall", 1001 "redox_syscall 0.2.5",
941 "remove_dir_all", 1002 "remove_dir_all",
942 "winapi 0.3.9", 1003 "winapi 0.3.9",
943] 1004]
diff --git a/Cargo.toml b/Cargo.toml
index 3f9c00d..a203a6f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ serde = { version = "1.0.104", features = ["derive"] }
13chrono = { version = "0.4.10", features = ["serde"] } 13chrono = { version = "0.4.10", features = ["serde"] }
14log = "0.4.8" 14log = "0.4.8"
15pretty_env_logger = "0.3.1" 15pretty_env_logger = "0.3.1"
16parking_lot = "0.10.0"
16 17
17[dev-dependencies] 18[dev-dependencies]
18serde_json = "1.0.44" 19serde_json = "1.0.44"
diff --git a/TODO.md b/TODO.md
index 76292d2..4e65094 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,4 +1,7 @@
1# TODO 1# TODO
2## Process
3- [ ] we need our own representation of students and their grades, "there is no blockchain"
4
2## Proof-of-work 5## Proof-of-work
3- [ ] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions) 6- [ ] pick a block proposal scheme (= pick hash function) [list of hash functions](https://en.bitcoinwiki.org/wiki/List_of_hash_functions)
4- [ ] check the nonce for incoming blocks 7- [ ] check the nonce for incoming blocks
@@ -7,4 +10,5 @@
7- [ ] pick a user authentication scheme 10- [ ] pick a user authentication scheme
8 - [ ] implement it 11 - [ ] implement it
9 12
10- [ ] Switch to RwLock (parking_lot) 13## Done & Brag
14- [x] Switch to RwLock (parking_lot) (done at 2021-04-07 03:43, two possible schemes to represent inner Db (ledger) in code)
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::*;
diff --git a/tester.sh b/tester.sh
new file mode 100644
index 0000000..a0396d6
--- /dev/null
+++ b/tester.sh
@@ -0,0 +1,38 @@
1#!/usr/bin/env bash
2
3## When in doubt just write a shell script
4
5curl --request POST \
6 --header 'Content-Type: application/json' \
7 --data '{
8 "source": "foo",
9 "target": "bar",
10 "amount": 5,
11 "timestamp": "2021-04-07T00:17:00"
12}' \
13 http://localhost:8080/transaction
14
15curl --request POST \
16 --header 'Content-Type: application/json' \
17 --data '{
18 "source": "saz",
19 "target": "quux",
20 "amount": 12,
21 "timestamp": "2021-04-07T00:17:00"
22}' \
23 http://localhost:8080/transaction
24
25curl localhost:8080/transaction
26
27curl --header "Content-Type: application/json" \
28 --request POST \
29 --data '{
30 "transaction_list": [
31 "foo",
32 "saz"
33 ],
34 "nonce": 4,
35 "timestamp": "2021-04-07T04:17:00",
36 "hash": "aaaaaa"
37}' \
38 http://localhost:8080/block