Skip to content

Commit 5b46dcf

Browse files
author
sigma67
committed
Initial commit
0 parents  commit 5b46dcf

File tree

10 files changed

+405
-0
lines changed

10 files changed

+405
-0
lines changed

Diff for: .gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### Rust template
3+
# Generated by Cargo
4+
# will have compiled files and executables
5+
/target/
6+
7+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
8+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
9+
Cargo.lock
10+
11+
# These are backup files generated by rustfmt
12+
**/*.rs.bk
13+
14+
.idea

Diff for: Cargo.toml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "exonum-bench"
3+
version = "0.1.0"
4+
authors = ["sigma67 <[email protected]>"]
5+
edition = "2018"
6+
license = "Apache 2.0"
7+
8+
[dependencies]
9+
exonum = { version = "0.11.0", path = "../exonum/exonum" }
10+
exonum-crypto = { version = "0.11.0", path = "../exonum/components/crypto" }
11+
exonum-derive = { version = "0.11.0", path = "../exonum/components/derive" }
12+
exonum-merkledb = { version = "0.11.0", path = "../exonum/components/merkledb" }
13+
exonum-configuration = { version = "0.11.0", path = "../exonum/services/configuration" }
14+
serde = "1.0.10"
15+
serde_derive = "1.0.10"
16+
serde_json = "1.0.2"
17+
failure = "0.1.5"
18+
protobuf = "2.6.0"
19+
reqwest = "0.9.14"
20+
tokio = "0.1.18"
21+
futures = "0.1.26"
22+
rand = "0.6"
23+
24+
[build-dependencies]
25+
exonum-build = { version = "0.11.0", path = "../exonum/components/build" }
26+
27+
[features]
28+
default = ["with-serde"]
29+
with-serde = []

Diff for: README.md

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# exonum-timestamping benchmark
2+
Simple client-based benchmarking tool for the [exonum-timestamping service](https://github.com/exonum/exonum/tree/master/examples/timestamping).
3+
4+
This tool was built from the need to have a **high-throughput and efficient benchmarking tool** for the Exonum blockchain. Using the nodeJS client for Exonum resulted in inconsistent results and would often be bottlenecked by the sending client.
5+
6+
By reusing the [transactions schema](https://github.com/sigma67/exonum-bench/tree/master/src/transactions.rs) from the Rust service implementation it becomes possible to properly benchmark the blockchain.
7+
8+
An adjusted frontend for the timestamping service with deployment and benchmarking scripts can be found at https://bitbucket.org/sigma67/exonum-log-timestamping/
9+
10+
## Research Paper
11+
You can find the associated research paper here:
12+
13+
[A secure and auditable logging infrastructure based on a permissioned blockchain](https://www.sciencedirect.com/science/article/pii/S0167404818313907)
14+
15+
## Prerequisites
16+
Initially you need to set up at least 4 instances of the Exonum blockchain with the timestamping service by following the instructions in examples/timestamping/README.md
17+
```sh
18+
# clone the main Exonum repository v0.11
19+
git clone https://github.com/exonum/exonum/ --branch "0-11-1-release" --depth 1
20+
21+
cd exonum/examples/timestamping/backend
22+
cargo install
23+
```
24+
25+
To install the frontend, run
26+
```sh
27+
git clone https://bitbucket.org/sigma67/exonum-log-timestamping/ --depth 1
28+
npm install
29+
npm run build
30+
npm start -- --port=2268 --api-root=http://127.0.0.1:8200
31+
```
32+
33+
## Build the benchmarking tool
34+
```sh
35+
# Clone this repository
36+
git clone https://github.com/sigma67/exonum-bench
37+
cargo install
38+
```
39+
For the build to succeed, the main exonum repository needs to be present in the same parent directory.
40+
## Run
41+
The benchmarking tool sends a specific number of transactions to the configured blockchain node per second.
42+
```
43+
# Send 100 transactions per second for 60 seconds
44+
exonum-bench 100 60 1
45+
```
46+
47+
There is also an automated script for remote benchmarking spread across all blockchain nodes at https://bitbucket.org/sigma67/exonum-log-timestamping/src/master/deploy/run_benchmark.sh.
48+
49+
This results in a more realistic benchmark since all blockchain nodes receive an equal amount of transactions.

Diff for: build.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extern crate exonum_build;
2+
3+
use exonum_build::{get_exonum_protobuf_files_path, protobuf_generate};
4+
5+
fn main() {
6+
let exonum_protos = get_exonum_protobuf_files_path();
7+
protobuf_generate(
8+
"src/proto",
9+
&["src/proto", &exonum_protos],
10+
"protobuf_mod.rs",
11+
);
12+
}

Diff for: src/api.rs

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use serde_json;
2+
use serde_json::json;
3+
4+
use reqwest::{r#async::Client, Response, StatusCode};
5+
//for async
6+
use tokio;
7+
use futures::{stream, Future, Stream};
8+
9+
const URL: &str = "http://localhost:8200/api/explorer/v1/transactions";
10+
const PARALLEL_REQUESTS: usize = 100;
11+
12+
pub fn post(data: &String) -> String{
13+
let client = reqwest::Client::new();
14+
15+
let query = &json!({ "tx_body": data });
16+
17+
//println!("POST {}", url);
18+
19+
let builder = client.post(&URL.to_string());
20+
//println!("Body: {}", serde_json::to_string_pretty(&query).unwrap());
21+
let builder = builder.json(&query);
22+
let response = builder.send().expect("Unable to send request");
23+
24+
self::response_to_api_result(response)
25+
}
26+
27+
pub fn post_async(items: Vec<String>) {
28+
let client = Client::new();
29+
let bodies = stream::iter_ok(items)
30+
.map(move |item| {
31+
let json = &json!({ "tx_body": item });
32+
client
33+
.post(&URL.to_string())
34+
.json(json)
35+
.send()
36+
.and_then(|res| res.into_body().concat2().from_err())
37+
})
38+
.buffer_unordered(PARALLEL_REQUESTS);
39+
40+
let work = bodies
41+
.for_each(|b| {
42+
Ok(())
43+
})
44+
.map_err(|e| panic!("Error while processing: {}", e));
45+
46+
tokio::run(work);
47+
}
48+
49+
pub fn post_batch(data: Vec<String>){
50+
}
51+
52+
/// Converts reqwest Response to api::Result.
53+
fn response_to_api_result(response: Response) -> String
54+
{
55+
fn extract_description(body: &str) -> Option<String> {
56+
match serde_json::from_str::<serde_json::Value>(body).ok()? {
57+
serde_json::Value::Object(ref object) if object.contains_key("description") => {
58+
Some(object["description"].as_str()?.to_owned())
59+
}
60+
serde_json::Value::String(string) => Some(string),
61+
_ => None,
62+
}
63+
}
64+
65+
fn error(mut response: Response) -> String {
66+
let body = response.text().expect("Unable to get response text");
67+
extract_description(&body).unwrap_or(body)
68+
}
69+
70+
match response.status() {
71+
StatusCode::OK => String::from("OK"),
72+
_ => error(response),
73+
}.parse().unwrap()
74+
}

Diff for: src/main.rs

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2019 Benedikt Putz
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#[macro_use]
16+
extern crate serde_derive;
17+
#[macro_use]
18+
extern crate exonum_derive;
19+
20+
use exonum::{
21+
crypto::{gen_keypair, hash, PublicKey, SecretKey},
22+
messages::{to_hex_string},
23+
};
24+
use std::env;
25+
use rand::Rng;
26+
use std::time::{SystemTime};
27+
use std::thread;
28+
use std::time;
29+
use std::slice::Chunks;
30+
31+
mod proto;
32+
mod schema;
33+
mod transactions;
34+
mod api;
35+
36+
use transactions::{ TxTimestamp };
37+
use schema::Timestamp;
38+
39+
fn main() {
40+
let args: Vec<String> = env::args().collect();
41+
if args.len() < 3 {
42+
println!("Insufficient arguments. Please specify (1) number of tx per time unit, (2) total duration of test, (3) time unit (i.e. 1 sec).");
43+
return;
44+
}
45+
//benchmark parameters
46+
let count = args[1].parse::<usize>().unwrap();
47+
let seconds = args[2].parse::<usize>().unwrap();
48+
let wait = args[3].parse::<u64>().unwrap();
49+
50+
//create transactions
51+
let mut txs : Vec<String> = vec![String::new(); count*seconds];
52+
let mut rng = rand::thread_rng();
53+
let keypair = gen_keypair();
54+
55+
for i in 1..(count*seconds) {
56+
let x: u64 = rng.gen();
57+
if i == 1 {println!("{}", &x.to_string())}
58+
let tx = self::create_transaction(&keypair.0, &keypair.1, x.to_string());
59+
//api::post(&tx);
60+
txs[i] = tx;
61+
}
62+
63+
//create chunks
64+
let posts = txs.chunks(count);
65+
let mut items : Vec<Vec<String>> = Vec::with_capacity(seconds);
66+
for p in posts {
67+
items.push(p.to_vec());
68+
}
69+
70+
//post transactions
71+
let start = SystemTime::now();
72+
for p in items {
73+
let pre = SystemTime::now();
74+
api::post_async(p);
75+
let passed = SystemTime::now().duration_since(pre).unwrap();
76+
println!("{} for posting", passed.as_millis());
77+
if passed.as_millis() < u128::from(wait) {
78+
thread::sleep(time::Duration::from_millis(wait) - passed);
79+
}
80+
}
81+
82+
let end= SystemTime::now();
83+
println!("Sent {} transactions in {}ms",
84+
(count*seconds).to_string(),
85+
end.duration_since(start).expect("").as_millis().to_string());
86+
}
87+
88+
fn create_transaction(public: &PublicKey, secret: &SecretKey, payload: String) -> String {
89+
let content = Timestamp::new(&hash(payload.as_bytes()), "metadata");
90+
let tx = TxTimestamp::sign(&public, content, &secret);
91+
let data = to_hex_string(&tx);
92+
data
93+
}

Diff for: src/proto/mod.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2019 The Exonum Team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Module of the rust-protobuf generated files.
16+
17+
#![allow(bare_trait_objects)]
18+
#![allow(renamed_and_removed_lints)]
19+
20+
pub use self::timestamping::{Timestamp, TxTimestamp};
21+
22+
include!(concat!(env!("OUT_DIR"), "/protobuf_mod.rs"));
23+
24+
use exonum::proto::schema::*;

Diff for: src/proto/timestamping.proto

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2019 The Exonum Team
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
import "helpers.proto";
18+
import "google/protobuf/timestamp.proto";
19+
20+
// Stores content's hash and some metadata about it.
21+
message Timestamp {
22+
exonum.Hash content_hash = 1;
23+
string metadata = 2;
24+
}
25+
26+
/// Timestamping transaction.
27+
message TxTimestamp { Timestamp content = 1; }

Diff for: src/schema.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use super::proto;
2+
use exonum::crypto::Hash;
3+
4+
/// Stores content's hash and some metadata about it.
5+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ProtobufConvert)]
6+
#[exonum(pb = "proto::Timestamp")]
7+
pub struct Timestamp {
8+
/// Hash of the content.
9+
pub content_hash: Hash,
10+
11+
/// Additional metadata.
12+
pub metadata: String,
13+
}
14+
15+
impl Timestamp {
16+
/// Create new Timestamp.
17+
pub fn new(&content_hash: &Hash, metadata: &str) -> Self {
18+
Self {
19+
content_hash,
20+
metadata: metadata.to_owned(),
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)