├── .gitignore
├── .gitmodules
├── .idea
└── vcs.xml
├── .travis.yml
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── README.md
├── build.sh
├── doc
├── bft.jpg
├── dpos.png
├── flow.gliffy
└── rfc.md
├── examples
├── .bash_history
├── bft.rs
├── c1.toml
├── c2.toml
├── c3.toml
├── c4.toml
├── c5.toml
├── c6.toml
├── config.toml
├── docker_build.sh
├── docker_start.sh
└── p2p.rs
├── rustfmt.toml
└── src
├── api
└── mod.rs
├── cmd
└── mod.rs
├── common
└── mod.rs
├── config
└── mod.rs
├── consensus
├── backend.rs
├── config.rs
├── consensus.rs
├── dpos
│ ├── delegates.rs
│ ├── mod.rs
│ ├── slot.rs
│ └── util.rs
├── engine.rs
├── error.rs
├── events.rs
├── mod.rs
├── pbft
│ ├── core
│ │ ├── back_log.rs
│ │ ├── commit.rs
│ │ ├── core.rs
│ │ ├── mod.rs
│ │ ├── new_header.rs
│ │ ├── prepare.rs
│ │ ├── preprepare.rs
│ │ ├── request.rs
│ │ ├── round_change.rs
│ │ ├── round_change_set.rs
│ │ ├── round_state.rs
│ │ ├── timer.rs
│ │ └── types.rs
│ └── mod.rs
├── types.rs
└── validator.rs
├── core
├── actor.rs
├── chain.rs
├── genesis.rs
├── ledger.rs
├── mod.rs
├── transaction_pool.rs
└── tx_pool.rs
├── error
└── mod.rs
├── lib.rs
├── logger
└── mod.rs
├── minner
└── mod.rs
├── mocks
├── mock_config.toml
├── mod.rs
└── utils.rs
├── p2p
├── codec.rs
├── config.rs
├── discover_service.rs
├── mod.rs
├── node.rs
├── p2p
│ ├── Cargo.toml
│ ├── doc
│ │ ├── Sequence Diagram0.asta
│ │ ├── Sequence Diagram0.asta.bak
│ │ └── roadmap
│ └── src
│ │ ├── client.rs
│ │ ├── codec.rs
│ │ ├── codec2.rs
│ │ ├── kad
│ │ ├── base.rs
│ │ ├── knodetable.rs
│ │ ├── mod.rs
│ │ ├── protocol.rs
│ │ ├── service.rs
│ │ └── utils.rs
│ │ ├── lib.rs
│ │ ├── peer.rs
│ │ ├── server.rs
│ │ └── session.rs
├── protocol.rs
├── server.rs
└── session.rs
├── pprof
└── mod.rs
├── protocol
└── mod.rs
├── store
├── base_index.rs
├── entry.rs
├── iter.rs
├── list_index.rs
├── map_index.rs
├── mod.rs
├── schema.rs
└── types.rs
├── subscriber
├── events.rs
└── mod.rs
├── types
├── block.rs
├── mod.rs
├── transaction.rs
└── votes.rs
└── util
└── mod.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 | .idea/*
12 | bft/target/*
13 | parity-common/*
14 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "parity-common"]
2 | path = parity-common
3 | url = https://github.com/laohanlinux/parity-common.git
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 |
3 | rust:
4 | - nightly-2019-04-07
5 |
6 | script:
7 | - git submodule add --force https://github.com/laohanlinux/parity-common.git
8 | - cargo build --verbose --all
9 | - cargo test --verbose --all
10 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "consensus"
3 | version = "0.1.0"
4 | authors = ["Rg "]
5 | description = "bft consensus"
6 | license = "GPL-3.0"
7 | edition = "2018"
8 |
9 | [dependencies]
10 | chrono = { version = "0.4", features = ["serde"] }
11 | chrono-humanize = "0.0.11"
12 | serde = "1.0"
13 | serde_derive = "1.0"
14 | serde_json = "1.0"
15 | runtime-fmt = "*"
16 | bigint = "4.4.1"
17 | byteorder = "1"
18 | rand = "0.5"
19 | hex = "*"
20 | sha3 = "0.7.3"
21 | rlp = "0.2.4"
22 | lazy_static = "1.1.0"
23 | crossbeam = {git = "https://github.com/crossbeam-rs/crossbeam.git"}
24 | ethereum-types = "0.5.2"
25 | lru_time_cache = "0.8.0"
26 | eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1" }
27 | cryptocurrency-kit = {git = "https://github.com/laohanlinux/cryptocurrency-kit-rs.git", tag = "v0.1.1"}
28 | kvdb-rocksdb = {path = "./parity-common/kvdb-rocksdb"}
29 | kvdb = {path = "./parity-common/kvdb"}
30 | transaction-pool = {path = "./parity-common/transaction-pool"}
31 | log = "0.4"
32 | env_logger = "0.6.0"
33 | priority-queue = "0.5.2"
34 | evmap = "4.0.0"
35 | actix = "0.7"
36 | actix-broker = "0.1.6"
37 | actix-web-async-await = "0.1.0"
38 | failure = "0.1.3"
39 | #futures = "0.1.26"
40 | tokio = "0.1"
41 | tokio-threadpool = "0.1.9"
42 | bytes = "0.4"
43 | clap = "2.32.0"
44 | toml = "0.4"
45 | serde_millis = "0.1.1"
46 | parking_lot = {version = "0.6", features = ["nightly"]}
47 | uuid = { version = "0.7", features = ["v5"] }
48 | flame = "0.2.2"
49 | tokio-signal = "0.2"
50 | tide = "0.0.5"
51 | http = "0.1"
52 | futures-preview = "0.3.0-alpha.13"
53 |
54 | [dependencies.libp2p]
55 | git = "https://github.com/laohanlinux/rust-libp2p.git"
56 |
57 | [profile.release]
58 | debug = true
59 |
60 | [dependencies.tap]
61 | git = "https://git.myrrlyn.net/myrrlyn/tap"
62 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM zhrong/bft
2 |
3 | MAINTAINER 0x80
4 |
5 | COPY examples/c1.toml /data/
6 | COPY examples/c2.toml /data/
7 | COPY examples/c3.toml /data/
8 | COPY examples/c4.toml /data/
9 | COPY examples/c5.toml /data/
10 | COPY examples/docker_build.sh /data/
11 | COPY examples/docker_start.sh /data/
12 |
13 | WORKDIR /root
14 |
15 | RUN /data/docker_start.sh && tail -f /tmp/c1.log
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # consensus-rs [](https://travis-ci.org/laohanlinux/consensus-rs)
2 | Implement multiple blockchain consensus, including raft, pbft, paxos, dpos, power
3 |
4 | - [x] pbft
5 | - [ ] raft
6 | - [ ] paxos
7 | - [ ] dpos
8 | - [ ] power
9 |
10 | ## start example
11 |
12 | ``` sh
13 | # git submodule add --force https://github.com/laohanlinux/parity-common.git
14 |
15 | # ./build.sh
16 | ```
17 |
18 | ## RUN Docker
19 |
20 | ``` sh
21 | docker build -t tt .
22 | ```
23 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | version="nightly-2019-04-07"
4 | rustup override set $version
5 |
6 | pkill bft
7 |
8 | cargo build --example bft
9 |
10 | function cluster() {
11 | RUST_BACKTRACE=full RUST_LOG=info ./target/debug/examples/bft start --config examples/c1.toml 1> /tmp/c1.log 2>&1 &
12 | RUST_BACKTRACE=full RUST_LOG=info ./target/debug/examples/bft start --config examples/c2.toml 1> /tmp/c2.log 2>&1 &
13 | RUST_BACKTRACE=full RUST_LOG=info ./target/debug/examples/bft start --config examples/c3.toml 1> /tmp/c3.log 2>&1 &
14 | RUST_BACKTRACE=full RUST_LOG=info ./target/debug/examples/bft start --config examples/c4.toml 1> /tmp/c4.log 2>&1 &
15 | RUST_BACKTRACE=full RUST_LOG=info ./target/debug/examples/bft start --config examples/c5.toml 1> /tmp/c5.log 2>&1 &
16 | }
17 |
18 | echo "run in 5 nodes"
19 | pkill bft
20 | cluster
21 |
--------------------------------------------------------------------------------
/doc/bft.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laohanlinux/consensus-rs/ebf0879ea3cf653c62ac79ff658c804a3bddcc6f/doc/bft.jpg
--------------------------------------------------------------------------------
/doc/dpos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laohanlinux/consensus-rs/ebf0879ea3cf653c62ac79ff658c804a3bddcc6f/doc/dpos.png
--------------------------------------------------------------------------------
/doc/rfc.md:
--------------------------------------------------------------------------------
1 | # 共识
2 |
3 | - 为什么开发这个项目?
4 |
5 | 主要是个人比较喜欢这方面的东西,且以前在做分布式云存储的时候,也经常遇到这些算法的问题。不过存储用到的算法和`Paxos`这类还是有些区别的,业界用到
6 | 较多的还是亚马逊的`Dynamo`算法(`Quorum`,`Consistent Hash`),`w + r > n` 这种模型; 又或者`MetaServer` + `Master + Slave`这种模型(`Fastdfs`,`Tfs`,元数据可以使用`Paxos`去实现集群来管理),但不管那种都涉及到`CAP`的终极问题。
7 |
8 | 而`Paxos`这类了解程度还是停留在看小说的认识上,虽然在工作中也做了不少的东西,但也仅仅是应用以及白皮书阶段而已,`BFT`就更少了,要不是区块链的兴起,很难想象`拜占庭容错`算法适合的场景,长期躺尸的节奏。
9 |
10 | - 为什么采用Rust语言
11 |
12 | 一直想使用一门底层的语言,`c/c++`长年不用,其他语言嘛,还不如`Go`来得酸爽。毫无疑问,对本来说,`Rust`是现阶段最好的选择-`安全/高性能`,当然难度也是很高的,至少`Rust`目前是本人感到最难入门的一门语言了。可以说,`Rust`是一门值得研究的语言,够研究几十年了。
13 |
14 | ## BFT
15 |
16 | 目前采用的是`PBFT`, 最基本的版本。麻雀虽小,五肺俱全,上个总图先:
17 |
18 | 
19 |
20 | 每个节点使用`Validator`来标示。
21 |
22 | ### validator 状态
23 | validator每一轮次(Height+Round)共有5种状态:
24 | `AccpetRequest`, `PrePrepared`, `Prepared`, `Committed`,`round change`。
25 |
26 | - AccpetRequest
27 |
28 | 启动新轮次,进入等待提案阶段
29 |
30 | - PrePrepared
31 |
32 | 收到预准备的消息(包含提案),即预准备阶段
33 |
34 | - Prepared
35 |
36 | 收到`+2/3`准备的消息,进入准备阶段
37 |
38 | - Committed
39 |
40 | 收到`+2/3`提交的消息,进入准备阶段
41 |
42 | - round change
43 |
44 | 这是一种特殊的状态,即在规定的时间内,本轮次无法达成,节点超时后,进入`Round change`状态,进行投票,切换视图。
45 |
46 | ### 锁机制
47 |
48 | 要做到拜占庭容错,除了防止`坏人`外,还得防止`好人`出错。比如下面的场景:
49 | `A, B, C, D, E` 都到了最后一个阶段,都收到了`3`票`commit`,然后`E`投出一票`commit`,由于网络的原因,`A,B,C,D` 没有收到来之`E`的`commit`消息,`E` 自己`commit`了,而其他节点在本轮次中未达成共识,进而切换视图,继续共识。那么这时候,其他节点该提哪个`提案`?最好是继续上一轮的`提案`,因为`E`在上一轮已经`commit`了,这是时候,如果不提相同的提案,就会出现同一高度(同一序列号)出现了两个合法的提案(区块),在区块链里,就出现了分叉,因为这两个提案都是合法的。而`锁机制`就是为了解决这种情况,那么具体是怎么解决这种问题?
50 |
51 | `Validator` 在接收到`+2/3`的`prepare + commit`时,会锁定在对应的提案,往后的投票validator只会投锁定的提案,不会对其他提案进行投票;如果没有锁定,还是按照正常的流程去投票,如:preprepare时,将军会使用`自己`的准备提案。
52 |
53 | 这样,即使`E`出现上面的情况,其他节点`A, B, C, D`接下来也是使用`E`的`commit` 继续共识,因为他们都锁定在该提案上。这样想想好像挺不错的.
54 |
55 | `But` 哦,`No`,这里又引生出新的问题。
56 |
57 | #### 锁机制带来的问题
58 |
59 | 假设:
60 |
61 | Round: 0, proposal hash `0x22702c...ed16ca` from A
62 |
63 | | A | receive 4 prepare votes |
64 | | :--: | :---------------------: |
65 | | B | receive 3 prepare votes |
66 | | C | receive 1 prepare votes |
67 | | D | receive 3 prepare votes |
68 | | E | No receive |
69 |
70 | 此时除了`A`,其他都是节点都没有收到`+2/3`的票,即`A`锁定在`0x22702c...ed16ca`的提案上。当前`Round`未达成共识,切换视图,继续。
71 |
72 | Round:1,proposal hash `0xc6d378...a95c0e` from B
73 |
74 | | A | No |
75 | | :--: | :---------------------: |
76 | | B | receive 4 prepare votes |
77 | | C | receive 1 prepare votes |
78 | | D | receive 3 prepare votes |
79 | | E | No receive |
80 |
81 | A因为锁定在不同的提案上,所以当前轮,不再投票(只投 `round change`),`B`收到了`4`票,锁定在`0xc6d378...a95c0e`。试想一下,现在网络就好像出现了网络分区的情况,各个区`state`都不同,很难再达成共识。Why?
82 |
83 | - `round 2`: 将军是`C`,无锁,所以提自己的提案:`0x74b9ba...dfb229`
84 |
85 | - `round 3`: 将军是`D`,无锁,所以提自己的提案:`0xd62cf2...0b176e`
86 |
87 | - `round 4`: 将军是`E`,无锁,所以提自己的提案:`0xc0fde9...2b3acc`
88 |
89 | 可以看出一个循环过去了,还是未达成共识。
90 |
91 | `Round `5: 将军是`E`,有锁,所以提锁定的提案:`0x22702c...ed16ca`
92 |
93 | 可能会收到来之`C,D,E`加上自己的那一票,刚好`+2/3`票,即达成共识。
94 |
95 | 这算最好的结果了,试想一下,如果`C, D, E`中的某个节点`Down`机,那么共识将无法达。`A`和`B`只投自己锁定的提案,最多只会收到`2/3`的票,直到`Down`掉的节点再次恢复回来(即使恢复回来,也那么快成共识,因为其他节点的round已经比`Down`机节点的`round`高出好多)。
96 |
97 | 如何解决这些问题?
98 |
99 | 增加`lock hash pprof`,`A`,`B`在发起`PrePrepare`消息时,携带锁证明,证明前`round`大家是有投票给`lock proposal`的,不能够耍赖。其他节点收到证明后就验证(验证签名即可),如果验证通过则也锁定在该提案上。对未锁定的节点来说,可以这样做,但对于已经锁定的节点`B`,它该如果处理?
100 |
101 | 横向证明再加一个指标,除了签名证明外,还要采用优先级策略,即`A`锁定的`round`为`0`,`B`锁定的`round`为`1`,B是不会解锁`A`锁定的提案,`B`在本轮(`round 5`)不会投票给`A`。如果这一轮还未达到共识,`round 6`的时候,`B`广播的提案为`0xc6d378...a95c0e`,其他节点收到`PrePrepare`时,验证`B`的提案,发现`B`锁定提案的轮次(`round 1`)比他们锁定提案的轮次(来之于`A`的锁定高度`round 0`)高,所以他们就把锁重新锁定到`B`锁定的提案`0xc6d378...a95c0e`上,这样就达到了一致性,很快就可以达成共识。
102 |
103 | ### Round change 处理
104 |
105 | `Round change`也可叫做`View Change`,简单来说就是视图切换,切换步骤如下(不同的项目采用的策略不一定相同):
106 |
107 | - 收到比当前小的轮次
108 |
109 | 投赞同票,让其能快速追上最新的步伐
110 |
111 | ```rust
112 | if current_view.round > subject.view.round && subject.view.round > 0 {
113 | // may be peer is less than network node
114 | self.send_round_change(subject.view.round);
115 | return Ok(());
116 | }
117 | ```
118 |
119 | - 收到更高的轮次
120 |
121 | `+2/3`票,且处于`WaitForChange`状态,变更轮次
122 |
123 | ```rust
124 | if n >= (current_val_set.two_thirds_majority() + 1)
125 | && (self.wait_round_change && current_view.round < subject.view.round) {
126 | self.send_round_change(subject.view.round);
127 | self.start_new_round(subject.view.round, &vec![]);
128 | return Ok(());
129 | }
130 | ```
131 |
132 | > PS: WaitForChange在收到+2/3的票时可以忽略其约束,即+2/3票即可catch up round
133 |
134 | - 超时
135 |
136 | 获取票最多且最大的轮次,投票给它,而不是单循的`Round+1`投票。因为如果收到了某个较高轮次大量的投票,证明大多数人都已经到了相同的高度,各个节点都该往最终一致性的方向投票,加快共识的达成。
137 |
138 | 为了防止恶意节点的攻击,`大多数票`这是一个重要的指标(目前没有限制大多数至少是多少票,如果用于生产环境中,建议不要低于`1/3`的票数)。
139 |
140 | ```rust
141 | // Find the max
142 | let round = self.round_change_set.max_round();
143 | if round <= current_view.round {
144 | self.send_round_change(current_view.round + 1);
145 | } else {
146 | self.send_round_change(round);
147 | }
148 | ```
149 |
150 | ### Leader选举
151 |
152 | `Leader = validator[hash(pre_hash + round) % validators.size]`
153 |
154 | ```rust
155 | pub fn fn_selector(blh: &Hash, height: Height, round: u64, vals: &Validators) -> Validator {
156 | assert!(!vals.is_empty());
157 | let seed = (randon_seed(blh, height, vals) + round) % vals.len() as u64;
158 | vals[seed as usize].clone()
159 | }
160 |
161 | fn randon_seed(blh: &Hash, _height: Height, vals: &Validators) -> u64 {
162 | let blh = blh.as_ref();
163 | let mut seed_buf = [0; 16];
164 | for (idx, item) in seed_buf[..8].iter_mut().enumerate() {
165 | *item = blh[idx];
166 | }
167 |
168 | let block_seed: U128 = U128::from(seed_buf);
169 | (block_seed % U128::from(vals.len())).as_u64()
170 | }
171 | ```
172 |
173 |
174 |
175 | ### 其他特殊以及改进的情况
176 |
177 | - 提案带宽优化
178 |
179 | 采用`[Tendermint]`的方案,将区块切割成`小份+纠错码`的方式传播
180 |
181 | - 增加`Lock Hash`
182 | - 增加`Validator`管理合约,如剔除/新增某个合约等
183 | - 增加当前区块包括上一个区块的`commit`信息,即当前区块应包含上一区块最后阶段投票的签名
184 | - `Leader`选举更加随机化
185 | - 增加更多的角色,让产生块以及共识的每个阶段又不同的角色去执行,提高公平性以及安全系数
186 |
187 | 资料来源:
188 |
189 | - [Tendermint](https://github.com/tendermint/tendermint)
190 | - [Ont](https://github.com/ontio/ontology)
191 | - [Istanbul Byzantine Fault Tolerance](https://github.com/ethereum/EIPs/issues/650)
192 |
193 |
--------------------------------------------------------------------------------
/examples/.bash_history:
--------------------------------------------------------------------------------
1 | ls
2 | cd
3 | ls
4 | exit
5 | ls
6 | cd
7 | ls
8 | exit
9 | cd
10 | ls
11 | ./docker_start.sh
12 | ls
13 | exit
14 |
--------------------------------------------------------------------------------
/examples/bft.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate consensus;
3 | extern crate clap;
4 | #[macro_use]
5 | extern crate log;
6 | extern crate actix;
7 |
8 | use ::actix::prelude::*;
9 | use clap::{Arg, App, SubCommand, ArgMatches};
10 |
11 | use std::sync::mpsc::channel;
12 |
13 | fn main() {
14 | let _config = consensus::config::Config::default();
15 | let matches = App::new("bft-consensus")
16 | .version("v0.1")
17 | .author("Rg. ")
18 | .about("bft consensus block chain implements")
19 | .subcommand(SubCommand::with_name("start")
20 | .about("star bft-rs")
21 | .arg(
22 | Arg::with_name("config")
23 | .long("config")
24 | .default_value("config.toml")
25 | .short("c")
26 | .value_name("CONFIG")))
27 | .get_matches();
28 | let result = run(matches);
29 | if let Err(err) = result {
30 | println!("--->{}", err);
31 | }
32 | }
33 |
34 | fn run(matches: ArgMatches) -> Result<(), String> {
35 | match matches.subcommand() {
36 | ("start", Some(m)) => {
37 | run_start(&m)
38 | }
39 | _ => Err("not matches any command".to_string())
40 | }
41 | }
42 |
43 | fn run_start(matches: &ArgMatches) -> Result<(), String> {
44 | let config = matches.value_of("config").expect("config is None");
45 | let (tx, rx) = channel();
46 | consensus::cmd::start_node(config, tx)?;
47 | rx.recv().unwrap();
48 | Ok(())
49 | }
--------------------------------------------------------------------------------
/examples/c1.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7691
4 | api_ip = "0.0.0.0"
5 | api_port = 8691
6 | block_period = 1000 # ms
7 | request_time = 5000 # ms
8 | peer_id = "QmbBr2fHwLFKvHkAq1BpbEr4dvR8P6orQxHkVaxeJsJiW8"
9 | ttl = 3000
10 | store = "/tmp/block/c1"
11 | secret = "7f3b0a324e13e5358c3fd686737acd7adf2e5556084ec6d9e48b497082b7ef98"
12 |
13 | [genesis]
14 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
15 | epoch_time = 2018-09-09T09:09:09.09-09:09
16 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
17 | gas_used = 10000
18 | extra = "Hello Word!"
19 | [genesis.accounts]
20 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
21 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
22 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
23 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
24 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
25 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
26 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
27 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
28 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
29 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
30 |
--------------------------------------------------------------------------------
/examples/c2.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7692
4 | api_ip = "0.0.0.0"
5 | api_port = 8692
6 | block_period = 1000 # ms
7 | request_time = 5000 # ms
8 | peer_id = "QmZgaUTxwWRWHbH6sukyFWAaNwPKvZGX9m7epEHGBJHPvt"
9 | ttl = 3000
10 | store = "/tmp/block/c2"
11 | secret = "ec84caf3d58e6bbcdcd6b243203fbaafee19e91048c61fe34e12fa7a93af27f9"
12 |
13 | [genesis]
14 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
15 | epoch_time = 2018-09-09T09:09:09.09-09:09
16 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
17 | gas_used = 10000
18 | extra = "Hello Word!"
19 | [genesis.accounts]
20 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
21 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
22 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
23 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
24 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
25 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
26 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
27 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
28 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
29 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
30 |
--------------------------------------------------------------------------------
/examples/c3.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7693
4 | api_ip = "0.0.0.0"
5 | api_port = 8693
6 | block_period = 1000 # ms
7 | request_time = 5000 # ms
8 | peer_id = "QmZREuJee6NMFpYGroctffcwUVDAYmt3mB1eisSVqVVfwA"
9 | ttl = 3000
10 | store = "/tmp/block/c3"
11 | secret = "64115814914b9d1aaa7d485770f50274b673df4634fcdd0ea3347e73e4b800ad"
12 |
13 | [genesis]
14 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
15 | epoch_time = 2018-09-09T09:09:09.09-09:09
16 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
17 | gas_used = 10000
18 | extra = "Hello Word!"
19 | [genesis.accounts]
20 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
21 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
22 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
23 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
24 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
25 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
26 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
27 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
28 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
29 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
30 |
--------------------------------------------------------------------------------
/examples/c4.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7694
4 | api_ip = "0.0.0.0"
5 | api_port = 8694
6 | block_period = 1000 # ms
7 | request_time = 5000 # ms
8 | peer_id = "QmVQVbmHE2BwFjeKx4eAKwGBtZ5348ufVUUR8jG2pa8uhG"
9 | ttl = 3000
10 | store = "/tmp/block/c4"
11 | secret = "f9093897ce74d867cdbc5c5a1b6e840ffb4343cbb0ea5b3ad5525edc6bad8c95"
12 |
13 | [genesis]
14 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
15 | epoch_time = 2018-09-09T09:09:09.09-09:09
16 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
17 | gas_used = 10000
18 | extra = "Hello Word!"
19 | [genesis.accounts]
20 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
21 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
22 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
23 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
24 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
25 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
26 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
27 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
28 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
29 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
30 |
--------------------------------------------------------------------------------
/examples/c5.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7695
4 | api_ip = "0.0.0.0"
5 | api_port = 8695
6 | block_period = 1000 # ms
7 | request_time = 5000 # ms
8 | peer_id = "QmX5e9hkQf7B45e2MZf38vhsC2wfA5aKQrrBuLujwaUBGw"
9 | ttl = 3000
10 | store = "/tmp/block/c5"
11 | secret = "6a30cfa9d15d64e4d7b0f15a18d6ea78d242e820e012b9980af5dbdc6403f61a"
12 |
13 | [genesis]
14 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
15 | epoch_time = 2018-09-09T09:09:09.09-09:09
16 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
17 | gas_used = 10000
18 | extra = "Hello Word!"
19 | [genesis.accounts]
20 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
21 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
22 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
23 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
24 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
25 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
26 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
27 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
28 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
29 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
30 |
--------------------------------------------------------------------------------
/examples/c6.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7696
4 | api_ip = "0.0.0.0"
5 | api_port = 8696
6 | block_period = 1000 # ms
7 | request_time = 5000 # ms
8 | peer_id = "QmZgaZTxwWRWHbH6sukyFWAaNwPKvZGX9m7epEHGBJHPvt"
9 | ttl = 3000
10 | store = "/tmp/block/c6"
11 | secret = "ec84caf3d58e6bbcdcd6b243203fbaafee19e91048c61fe34e12fa7a93af27f9"
12 |
13 | [genesis]
14 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
15 | epoch_time = 2018-09-09T09:09:09.09-09:09
16 | proposer = "0x0701fbd05e77cac003a6894e4b2a3c12287ed313"
17 | gas_used = 10000
18 | extra = "Hello Word!"
19 | [genesis.accounts]
20 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
21 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
22 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
23 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
24 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
25 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
26 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
27 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
28 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
29 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
30 |
--------------------------------------------------------------------------------
/examples/config.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7690
4 | block_period = 10000 # ms
5 | request_time = 5000 # ms
6 | peer_id = "QmbBr2fHwLFKvHkAq1BpbEr4dvR8P6orQxHkVaxeJsJiW8"
7 | ttl = 3000
8 | store = "/tmp/block/c0"
9 | secret = "6a30cfa9d15d64e4d7b0f15a18d6ea78d242e820e012b9980af5dbdc6403f61a"
10 |
11 | [genesis]
12 | validator = ["0x5701fbd05e77cac003a6894e4b2a3c12287ed313", "0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d", "0x3140bda54df92f9453b487afdb3bcce02d154c74", "0x7035dafbeac1792ab5b7ed5c903ac63522eb534a","0x6730933a2cb6f26af786d7f5979efbdf29049c3a"]
13 | epoch_time = 2018-09-09T09:09:09.09-09:09
14 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
15 | gas_used = 10000
16 | extra = "Hello Word!"
17 | [genesis.accounts]
18 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
19 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
20 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
21 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
22 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
23 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
24 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
25 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
26 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
27 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
--------------------------------------------------------------------------------
/examples/docker_build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | dir=`pwd` && docker run -ti --rm -v $dir/examples/:/data/ -v $dir/docker_start.sh:/data/docker_start.sh zhrong/bft /data/docker_start.sh && tail -f /tmp/c2.log
4 |
--------------------------------------------------------------------------------
/examples/docker_start.sh:
--------------------------------------------------------------------------------
1 | RUST_BACKTRACE=full RUST_LOG=info nohup /root/release/examples/bft start -c /data/c1.toml 1> /tmp/c1.log 2>&1 &
2 | RUST_BACKTRACE=full RUST_LOG=info nohup /root/release/examples/bft start -c /data/c2.toml 1> /tmp/c2.log 2>&1 &
3 | RUST_BACKTRACE=full RUST_LOG=info nohup /root/release/examples/bft start -c /data/c3.toml 1> /tmp/c3.log 2>&1 &
4 | RUST_BACKTRACE=full RUST_LOG=info nohup /root/release/examples/bft start -c /data/c4.toml 1> /tmp/c4.log 2>&1 &
5 | RUST_BACKTRACE=full RUST_LOG=info nohup /root/release/examples/bft start -c /data/c5.toml 1> /tmp/c5.log 2>&1 &
6 |
--------------------------------------------------------------------------------
/examples/p2p.rs:
--------------------------------------------------------------------------------
1 | extern crate bft;
2 | extern crate futures;
3 | extern crate libp2p;
4 | extern crate rand;
5 | extern crate tokio;
6 |
7 | use futures::prelude::*;
8 | use libp2p::mdns::{MdnsPacket, MdnsService};
9 | use libp2p::PeerId;
10 | use libp2p::core::PublicKey;
11 | use libp2p::multiaddr::{Multiaddr, ToMultiaddr};
12 | use std::io;
13 | use rand::Rng;
14 | use std::time::Duration;
15 |
16 | fn main() {
17 | let mut service = MdnsService::new().expect("Error while creating mDNS service");
18 | let my_peer_id = PeerId::random();
19 | let mut my_listened_addrs = Vec::new();
20 | println!("my pid {:?}", my_peer_id);
21 | let port = rand::random::();
22 | let address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port).parse().unwrap();
23 |
24 |
25 | my_listened_addrs.push(address);
26 | let future = futures::future::poll_fn(move || -> Poll<(), io::Error> {
27 | loop {
28 | // Grab the next available packet from the service.
29 | let packet = match service.poll() {
30 | Async::Ready(packet) => packet,
31 | Async::NotReady => return Ok(Async::NotReady),
32 | };
33 |
34 | match packet {
35 | MdnsPacket::Query(query) => {
36 | // We detected a libp2p mDNS query on the network. In a real application, you
37 | // probably want to answer this query by doing `query.respond(...)`.
38 | println!("Detected query from {:?}", query.remote_addr());
39 | query.respond(my_peer_id.clone(), my_listened_addrs.clone(), Duration::from_secs(3)).unwrap();
40 | }
41 | MdnsPacket::Response(response) => {
42 | // We detected a libp2p mDNS response on the network. Responses are for
43 | // everyone and not just for the requester, which makes it possible to
44 | // passively listen.
45 | for peer in response.discovered_peers() {
46 | println!("Discovered peer {:?}", peer.id());
47 | // These are the self-reported addresses of the peer we just discovered.
48 | for addr in peer.addresses() {
49 | println!(" Address = {:?}", addr);
50 | }
51 | }
52 | }
53 | MdnsPacket::ServiceDiscovery(query) => {
54 | // The last possibility is a service detection query from DNS-SD.
55 | // Just like `Query`, in a real application you probably want to call
56 | // `query.respond`.
57 | println!("Detected service query from {:?}", query.remote_addr());
58 | query.respond(std::time::Duration::from_secs(120));
59 | }
60 | }
61 | }
62 | });
63 |
64 | // Blocks the thread until the future runs to completion (which will never happen).
65 | tokio::run(future.map_err(|err| panic!("{:?}", err)));
66 | }
67 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | write_mode = "overwrite"
--------------------------------------------------------------------------------
/src/api/mod.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 |
3 | use crate::core::chain::Chain;
4 | use crate::types::block::Blocks;
5 |
6 | use http::StatusCode;
7 | use tide::{body, head, configuration::{Configuration, Environment}, App, AppData};
8 |
9 | async fn blocks(mut chain: AppData>) -> String {
10 | let state: &Arc = &chain.0;
11 | let last_height = state.get_last_height();
12 | let mut blocks: Blocks = Blocks(vec![]);
13 | (0..last_height + 1).for_each(|height| {
14 | let block_hash = state.get_block_hash_by_height(height).unwrap();
15 | let block = state.get_block_by_hash(&block_hash).unwrap();
16 | blocks.0.push(block);
17 | });
18 | serde_json::to_string(&blocks).unwrap()
19 | }
20 |
21 | async fn transactions(mut chain: AppData>) -> String {
22 | let state: &Arc = &chain.0;
23 | let mut transactions = state.get_transactions();
24 | serde_json::to_string(&transactions).unwrap()
25 | }
26 |
27 | pub fn start_api(chain: Arc, ip: String, port: u16) {
28 | let mut app = App::new(chain);
29 | app.at("/blocks").get(blocks);
30 | app.at("/transactions").get(transactions);
31 | app.config(Configuration {
32 | env: Environment::Production,
33 | address: ip,
34 | port: port,
35 | });
36 | app.serve();
37 | }
--------------------------------------------------------------------------------
/src/cmd/mod.rs:
--------------------------------------------------------------------------------
1 | use std::fs::File;
2 | use std::io::{self, prelude::*};
3 | use std::str::FromStr;
4 | use std::sync::mpsc::Sender;
5 | use std::sync::Arc;
6 | use std::thread::{spawn, JoinHandle};
7 | use std::time::Duration;
8 |
9 | use ::actix::prelude::*;
10 | use cryptocurrency_kit::crypto::Hash;
11 | use cryptocurrency_kit::ethkey::{Generator, KeyPair, Secret, Random};
12 | use futures::Future;
13 | use kvdb_rocksdb::Database;
14 | use libp2p::{Multiaddr, PeerId};
15 | use lru_time_cache::LruCache;
16 | use parking_lot::RwLock;
17 |
18 | use crate::{
19 | common,
20 | config::Config,
21 | consensus::pbft::core::core::{Core, handle_msg_middle},
22 | consensus::consensus::{create_bft_engine, SafeEngine},
23 | core::chain::Chain,
24 | core::ledger::{LastMeta, Ledger},
25 | core::tx_pool::{BaseTxPool, TxPool, SafeTxPool},
26 | error::ChainResult,
27 | logger::init_log,
28 | minner::Minner,
29 | p2p::{
30 | protocol::Payload,
31 | discover_service::DiscoverService,
32 | server::{author_handshake, TcpServer},
33 | spawn_sync_subscriber,
34 | },
35 | pprof::spawn_signal_handler,
36 | store::schema::Schema,
37 | subscriber::events::{BroadcastEventSubscriber, ChainEventSubscriber, SubscriberType},
38 | subscriber::*,
39 | types::Validator,
40 | api::start_api,
41 | };
42 |
43 | pub fn start_node(config: &str, sender: Sender<()>) -> Result<(), String> {
44 | print_art();
45 | init_log();
46 | let result = init_config(config);
47 | if result.is_err() {
48 | return Err(result.err().unwrap());
49 | }
50 | let config = result.unwrap();
51 | let secret = Secret::from_str(&config.secret).expect("Secret is uncorrect");
52 | let key_pair = KeyPair::from_secret(secret).unwrap();
53 | let ledger = init_store(&config)?;
54 | let ledger: Arc> = Arc::new(RwLock::new(ledger));
55 |
56 | let mut chain = Chain::new(config.clone(), ledger);
57 |
58 | // init genesis
59 | init_genesis(&mut chain).map_err(|err| format!("{}", err))?;
60 | let genesis = chain.get_genesis().clone();
61 | info!("Genesis hash: {:?}", chain.get_genesis().hash());
62 |
63 | // init transaction pool
64 | let _tx_pool = Arc::new(RwLock::new(init_transaction_pool(&config)));
65 |
66 | let chain = Arc::new(chain);
67 |
68 | init_api(&config, chain.clone());
69 |
70 | let broadcast_subscriber = BroadcastEventSubscriber::new(SubscriberType::Async).start();
71 |
72 | let (core_pid, engine) = start_consensus_engine(
73 | &config,
74 | key_pair.clone(),
75 | chain.clone(),
76 | broadcast_subscriber.clone(),
77 | );
78 |
79 | let config_clone = config.clone();
80 | {
81 | let p2p_event_notify = init_p2p_event_notify();
82 | let _discover_pid = init_p2p_service(p2p_event_notify.clone(), &config_clone);
83 | init_tcp_server(chain.clone(), p2p_event_notify.clone(), genesis.hash(), core_pid.clone(), &config_clone);
84 | }
85 |
86 | // spawn new thread to handle mine
87 | ::std::thread::spawn(move || {
88 | let code = System::run(move || {
89 | start_mint(&config, key_pair.clone(), chain.clone(), _tx_pool.clone(), engine);
90 | });
91 | ::std::process::exit(code);
92 | });
93 |
94 | init_signal_handle();
95 | Ok(())
96 | }
97 |
98 | fn init_p2p_event_notify() -> Addr {
99 | info!("Init p2p event nofity");
100 | spawn_sync_subscriber()
101 | }
102 |
103 | fn init_p2p_service(
104 | p2p_subscriber: Addr,
105 | config: &Config,
106 | ) -> Addr {
107 | let peer_id = PeerId::from_str(&config.peer_id).unwrap();
108 | let mul_addr = Multiaddr::from_str(&format!("/ip4/{}/tcp/{}", config.ip, config.port)).unwrap();
109 | let discover_service =
110 | DiscoverService::spawn_discover_service(p2p_subscriber, peer_id, mul_addr, config.ttl);
111 | info!("Init p2p service successfully");
112 | discover_service
113 | }
114 |
115 | fn init_tcp_server(chain: Arc, p2p_subscriber: Addr, genesis: Hash, core_pid: Addr, config: &Config) {
116 | let peer_id = PeerId::from_str(&config.peer_id).unwrap();
117 | let mul_addr = Multiaddr::from_str(&format!("/ip4/{}/tcp/{}", config.ip, config.port)).unwrap();
118 | let author = author_handshake(genesis.clone());
119 | let h1 = Box::new(handle_msg_middle(core_pid, chain.clone()));
120 | let server = TcpServer::new(peer_id, mul_addr, None, genesis.clone(), Box::new(author), h1);
121 |
122 | // subscriber p2p event, sync operation
123 | {
124 | let recipient = server.clone().recipient();
125 | // register
126 | let message = SubscribeMessage::SubScribe(recipient);
127 | let request_fut = p2p_subscriber.send(message);
128 | Arbiter::spawn(
129 | request_fut
130 | .and_then(|_result| {
131 | info!("Subsribe p2p discover event successfully");
132 | futures::future::ok(())
133 | })
134 | .map_err(|err| unimplemented!("{}", err)),
135 | );
136 | }
137 |
138 | // subscriber chain event, async operation
139 | {
140 | chain.subscriber_event(server.clone().recipient());
141 | }
142 | info!("Init tcp server successfully");
143 | }
144 |
145 | fn init_config(config: &str) -> Result {
146 | info!("Init config: {}", config);
147 | let mut input = String::new();
148 | File::open(config)
149 | .and_then(|mut f| f.read_to_string(&mut input))
150 | .map(|_| toml::from_str::(&input).unwrap())
151 | .map_err(|err| err.to_string())
152 | }
153 |
154 | fn init_transaction_pool(_: &Config) -> SafeTxPool {
155 | info!("Init transaction pool successfully");
156 | Box::new(BaseTxPool::new()) as SafeTxPool
157 | }
158 |
159 | fn init_store(config: &Config) -> Result {
160 | info!("Init store: {}", config.store);
161 | let genesis_config = config.genesis.as_ref().unwrap();
162 |
163 | let mut validators: Vec = vec![];
164 | for validator in &genesis_config.validator {
165 | validators.push(Validator::new(common::string_to_address(validator)?));
166 | }
167 |
168 | let database = Database::open_default(&config.store).map_err(|err| err.to_string())?;
169 | let schema = Schema::new(Arc::new(database));
170 | Ok(Ledger::new(
171 | LastMeta::new_zero(),
172 | LruCache::with_capacity(1 << 10),
173 | LruCache::with_capacity(1 << 10),
174 | validators,
175 | schema,
176 | ))
177 | }
178 |
179 | fn init_genesis(chain: &mut Chain) -> ChainResult {
180 | info!("Init genesis block");
181 | chain.store_genesis_block()
182 | }
183 |
184 | fn start_consensus_engine(
185 | _config: &Config,
186 | key_pair: KeyPair,
187 | chain: Arc,
188 | subscriber: Addr,
189 | ) -> (Addr, SafeEngine) {
190 | info!("Init consensus engine");
191 | let mut result = create_bft_engine(key_pair, chain, subscriber);
192 | result.1.start().unwrap();
193 | result
194 | }
195 |
196 | fn start_mint(
197 | config: &Config,
198 | key_pair: KeyPair,
199 | chain: Arc,
200 | txpool: Arc>,
201 | engine: SafeEngine,
202 | ) -> Addr {
203 | let minter = key_pair.address();
204 | Minner::create(move |ctx| {
205 | let recipient = ctx.address().recipient();
206 | chain.subscriber_event(recipient);
207 | let (tx, rx) = crossbeam::channel::bounded(1);
208 | Minner::new(minter, key_pair, chain, txpool, engine, tx, rx)
209 | })
210 | }
211 |
212 | fn init_api(config: &Config, chain: Arc) {
213 | let config = config.clone();
214 | let chain = chain.clone();
215 | spawn(move || {
216 | info!("Start service api");
217 | start_api(chain, config.api_ip, config.api_port);
218 | });
219 | }
220 |
221 | fn init_signal_handle() {
222 | spawn_signal_handler(*common::random_dir());
223 | }
224 |
225 | fn print_art() {
226 | let art = r#"
227 | A large collection of ASCII art drawings of bears and other related animal ASCII art pictures.
228 |
229 | lazy bears by Joan G. Stark
230 |
231 | _,-""`""-~`)
232 | (`~_,=========\
233 | |---,___.-.__,\
234 | | o \ ___ _,,,,_ _.--.
235 | \ `^` /`_.-"~ `~-;` \
236 | \_ _ .' `, |
237 | |`- \'__/
238 | / ,_ \ `'-.
239 | / .-""~~--. `"-, ;_ /
240 | | \ \ | `""`
241 | \__.--'`"-. /_ |'
242 | `"` `~~~---.., |
243 | \ _.-'`-.
244 | "#;
245 | println!("{}", art);
246 | }
247 |
--------------------------------------------------------------------------------
/src/common/mod.rs:
--------------------------------------------------------------------------------
1 | use bigint::U256;
2 | use rand::random;
3 | use sha3::{Digest, Sha3_256};
4 |
5 | use core::str::FromStr;
6 | use std::env;
7 | use std::fmt::{self, Display};
8 | use std::net::{SocketAddr, AddrParseError};
9 |
10 | use cryptocurrency_kit::crypto::{hash, CryptoHash, Hash};
11 | use cryptocurrency_kit::merkle_tree::MerkleTree;
12 | use cryptocurrency_kit::storage::values::StorageValue;
13 | use libp2p::{
14 | multiaddr::Protocol,
15 | Multiaddr,
16 | };
17 |
18 | pub fn merkle_tree_root(input: Vec) -> Hash {
19 | let mut v: Vec> = vec![];
20 | for item in input {
21 | let bytes = item.into_bytes();
22 | v.push(bytes);
23 | }
24 | let root = MerkleTree::new_merkle_tree(v).root.unwrap();
25 | // info!("{:?}, len: {}", &root.data);
26 | Hash::from_slice(&root.data).unwrap()
27 | }
28 |
29 | #[derive(Serialize, Deserialize, Debug, Clone)]
30 | pub struct HexBytes {
31 | inner: [u8; 32],
32 | }
33 |
34 | impl HexBytes {
35 | pub fn bytes(&self) -> &[u8; 32] {
36 | &self.inner
37 | }
38 |
39 | pub fn string(&self) -> String {
40 | String::from_utf8_lossy(&self.inner).to_string()
41 | }
42 | }
43 |
44 | impl Display for HexBytes {
45 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
46 | use std::fmt;
47 | writeln!(f, "{}", self.string()).unwrap();
48 | Ok(())
49 | }
50 | }
51 |
52 | pub fn as_256(data: &[u8]) -> U256 {
53 | U256::from_big_endian(data)
54 | }
55 |
56 | pub fn u256_hash(input: &[u8]) -> Vec {
57 | let mut hasher = Sha3_256::default();
58 | hasher.input(input);
59 | hasher.result().to_vec()
60 | }
61 |
62 | pub fn random_dir() -> Box {
63 | Box::new(format!(
64 | "{}{}",
65 | env::temp_dir().to_str().unwrap(),
66 | random::()
67 | ))
68 | }
69 |
70 |
71 | pub fn multiaddr_to_ipv4(mul_addr: &Multiaddr) -> Result {
72 | let mut ipv4: String = "".to_string();
73 | let v = mul_addr.iter().collect::>();
74 | for protocol in v {
75 | match protocol {
76 | Protocol::Ip4(ref ip4) => {
77 | ipv4.push_str(&format!("{}:", ip4));
78 | }
79 | Protocol::Tcp(ref port) => {
80 | ipv4.push_str(&format!("{}", port));
81 | }
82 | _ => {}
83 | }
84 | }
85 | ipv4.parse()
86 | }
87 |
88 | pub fn random_uuid() -> uuid::Uuid {
89 | use uuid::Uuid;
90 | Uuid::new_v5(&Uuid::NAMESPACE_DNS, chrono::Local::now().to_string().as_bytes())
91 | }
92 |
93 |
94 | use ethereum_types::{Address, H160};
95 |
96 | pub fn string_to_address(s: &String) -> Result {
97 | if s.len() < 40 {
98 | return Err("less than 40 chars".to_string());
99 | }
100 | if s.len() > 42 {
101 | return Err("more than 42 chars".to_string());
102 | }
103 |
104 | if s.len() == 42 {
105 | return Ok(Address::from_str(&s[2..]).unwrap());
106 | }
107 | Ok(Address::from_str(&s).unwrap())
108 | }
109 |
110 | pub fn strings_to_addresses(strs: &Vec) -> Result, String> {
111 | let mut addresses = Vec::new();
112 | for str in strs {
113 | let address = string_to_address(str)?;
114 | addresses.push(address);
115 | }
116 | Ok(addresses)
117 | }
118 |
119 | #[cfg(test)]
120 | mod test {
121 | use super::*;
122 |
123 | #[test]
124 | fn t_string_to_address() {
125 | let address = string_to_address(&"0x93908f59c6eff007d228398349214acb6b4ac9a4".to_owned()).unwrap();
126 | assert_eq!("0x93908f59c6eff007d228398349214acb6b4ac9a4", format!("{:?}", address));
127 | println!("address: {:?}", address);
128 | }
129 | }
--------------------------------------------------------------------------------
/src/config/mod.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 | use std::collections::HashMap;
3 |
4 | use toml::Value as Toml;
5 | use toml::value::Table;
6 | use toml::value::Datetime;
7 |
8 | use crate::common::random_dir;
9 |
10 | #[derive(Debug, Clone, Deserialize)]
11 | pub struct Config {
12 | pub chain_id: u64,
13 | pub ip: String,
14 | pub port: u16,
15 | pub api_ip: String,
16 | pub api_port: u16,
17 | #[serde(with = "serde_millis")]
18 | pub block_period: Duration,
19 | #[serde(with = "serde_millis")]
20 | pub request_time: Duration,
21 | pub peer_id: String,
22 | #[serde(with = "serde_millis")]
23 | pub ttl: Duration,
24 | pub store: String,
25 | pub secret: String,
26 | pub genesis: Option,
27 | }
28 |
29 | #[derive(Debug, Deserialize, Clone)]
30 | pub struct GenesisConfig {
31 | pub validator: Vec,
32 | pub accounts: Table,
33 | pub epoch_time: Datetime,
34 | pub proposer: String,
35 | pub gas_used: u64,
36 | pub extra: String,
37 | }
38 |
39 | impl Default for Config {
40 | fn default() -> Self {
41 | Config {
42 | chain_id: 98,
43 | ip: "127.0.0.1".to_string(),
44 | port: 7960,
45 | api_ip: "0.0.0.0".to_owned(),
46 | api_port: 8960,
47 | block_period: Duration::from_millis(3 * 1000),
48 | request_time: Duration::from_millis(3 * 1000),
49 | peer_id: "QmbBr2fHwLFKvHkAq1BpbEr4dvR8P6orQxHkVaxeJsJiW8".to_string(),
50 | ttl: Duration::from_millis(5 * 1000),
51 | store: *random_dir(),
52 | secret: "".into(),
53 | genesis: None,
54 | }
55 | }
56 | }
57 |
58 | #[cfg(test)]
59 | mod tests {
60 | use super::*;
61 | use libp2p::PeerId;
62 | use std::str::FromStr;
63 |
64 | #[test]
65 | fn t_config() {
66 | println!("{:?}", PeerId::random());
67 | println!("{:?}", PeerId::from_str("QmbBr2fHwLFKvHkAq1BpbEr4dvR8P6orQxHkVaxeJsJiW8").unwrap());
68 | }
69 |
70 | #[test]
71 | fn t_load_secret(){
72 | use cryptocurrency_kit::ethkey::{Secret, KeyPair};
73 |
74 | let secret = Secret::from_str("7f3b0a324e13e5358c3fd686737acd7adf2e5556084ec6d9e48b497082b7ef98").unwrap();
75 | let key_pair = KeyPair::from_secret(secret).unwrap();
76 | println!("{:?}, {:?}", key_pair, key_pair.address());
77 | }
78 | }
--------------------------------------------------------------------------------
/src/consensus/config.rs:
--------------------------------------------------------------------------------
1 | #[derive(Debug, Clone)]
2 | pub struct Config {
3 | pub request_time: u64,
4 | pub block_period: u64,
5 | pub chain_id: u64,
6 | }
7 |
8 | impl Config {
9 | pub fn new(request_time:u64, block_period: u64, chain_id: u64) -> Self {
10 | Config{
11 | request_time,
12 | block_period,
13 | chain_id,
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/consensus/consensus.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 |
3 | use actix::Addr;
4 | use cryptocurrency_kit::ethkey::Address;
5 | use cryptocurrency_kit::ethkey::KeyPair;
6 | use crossbeam::Receiver;
7 |
8 | use super::{
9 | error::{EngineError, EngineResult},
10 | types::Proposal,
11 | pbft::core::core::Core,
12 | backend::{Backend, ImplBackend, new_impl_backend},
13 | validator::ImplValidatorSet,
14 | };
15 |
16 | use crate::{
17 | subscriber::events::{BroadcastEvent, BroadcastEventSubscriber},
18 | types::block::{Block, Header},
19 | core::chain::Chain,
20 | consensus::events::OpCMD,
21 | };
22 |
23 | struct BftConfig {
24 | request_time: u64,
25 | block_period: u64,
26 | }
27 |
28 | pub trait Engine {
29 | fn start(&mut self) -> Result<(), String>;
30 | fn stop(&mut self) -> Result<(), String>;
31 | fn author(&self, header: &Header) -> Result;
32 | fn verify_header(&self, header: &Header, seal: bool) -> EngineResult;
33 | fn verify_seal(&self, header: &Header) -> EngineResult;
34 | fn new_chain_header(&mut self, proposal: &Proposal) -> EngineResult;
35 | fn prepare(&mut self, header: &mut Header) -> Result<(), String>;
36 | fn finalize(&mut self, header: &Header) -> Result<(), String>;
37 | fn seal(&mut self, new_block: &mut Block, abort: Receiver<()>) -> EngineResult;
38 | }
39 |
40 | pub type SafeEngine = Box;
41 |
42 | pub fn create_bft_engine(key_pair: KeyPair, chain: Arc, subscriber: Addr) -> (Addr, SafeEngine) {
43 | info!("Create bft consensus engine");
44 | let mut backend = new_impl_backend(key_pair.clone(), chain.clone(), subscriber);
45 |
46 | // use new thread to handle core
47 | let (tx, rx) = ::std::sync::mpsc::channel();
48 | let core_backend = backend.clone();
49 | ::std::thread::spawn(move || {
50 | let core = actix::System::run(move || {
51 | let core_pid = Core::new(chain, core_backend, key_pair);
52 | tx.send(core_pid).unwrap();
53 | });
54 | ::std::process::exit(core);
55 | });
56 | let core_pid = rx.recv().unwrap();
57 | backend.set_core_pid(core_pid.clone());
58 | let engine_backend: SafeEngine = Box::new(backend.clone()) as SafeEngine;
59 | (core_pid, engine_backend)
60 | }
61 |
--------------------------------------------------------------------------------
/src/consensus/dpos/delegates.rs:
--------------------------------------------------------------------------------
1 | use crate::types::{Height, transaction::Transaction, block::Block};
2 |
3 | use super::{util, slot};
4 |
5 | // TODO: add_delegate
6 | fn add_delegate() {}
7 |
8 | /// Generates delegates list and checks if block generator publicKey maches delegate id.
9 | pub fn validate_block_slot(block: Block) -> bool {
10 | // get the height delegates list
11 | let block_height = block.height();
12 | let delegates = slot::get_active_delegates(block_height);
13 | let slot_time = block.header().time as i64;
14 |
15 | let current_slot = slot::get_slot_number(slot_time);
16 | let idx = current_slot as usize % delegates.len();
17 | let delegate_id = delegates[idx];
18 |
19 | util::equal_delegate(delegate_id, &block.header().proposer.as_bytes())
20 | }
21 |
22 |
23 | //// TODO: Opz
24 | //pub fn generate_delegate_list<'a>(height: Height) -> (Timespec, Vec<&'a str>){
25 | // let generator_id = slot::get_active_delegates(height);
26 | //}
27 |
28 | pub fn get_block_slot_data<'a>(slot: i64, height: Height) -> Option<(&'a str, i64)> {
29 | let current_slot = slot;
30 | let delegates = slot::get_active_delegates(height);
31 | let delegate_pos = current_slot % slot::DELEGATES;
32 | let delegate_id = delegates.get(delegate_pos as usize);
33 | Some((delegate_id.unwrap(), slot::get_slot_time(slot)))
34 | }
35 |
36 | // TODO:
37 | // height --> slot_list
38 | // 根据当前的slot,找到高度为height的相应见证人id以及相对应的slot
39 | //
40 | //fn get_block_slot_data<'a>(slot: i64, height: Height) -> Option<(&'a str, i64)> {
41 | // let current_slot = slot;
42 | // let last_slot = slot::get_last_slot(current_slot);
43 | // let delegates = slot::get_active_delegates(height);
44 | //
45 | // for _slot in current_slot..last_slot {
46 | // let delegate_pos = _slot % slot::DELEGATES;
47 | // let deletegate_id = delegates.get(delegate_pos as usize);
48 | // if deletegate_id.is_none() {
49 | // continue;
50 | // }
51 | // return Some((deletegate_id.unwrap(), slot::get_slot_time(_slot)));
52 | // }
53 | // None
54 | //}
55 |
56 | //
57 | //fn get_keys_sort_by_vote() {
58 | //
59 | //}
60 | //
61 | //// TODO
62 | //fn get_accounts(_: String) {
63 | //
64 | //}
65 |
66 | #[cfg(test)]
67 | mod tests {
68 | use std::io::{self, Write};
69 | use super::*;
70 |
71 | #[test]
72 | fn test_get_block_slot_data() {
73 | for height in 1..12 {
74 | let slot = height - 1;
75 | let (delegate_id, slot_time) = get_block_slot_data(slot, Height(height as u64)).unwrap();
76 | let slot_number = super::slot::get_slot_number(slot_time);
77 | writeln!(io::stdout(), "deletegate_id: {}, slot_number: {}, slot_time: {}", delegate_id, slot_number, slot_time);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/consensus/dpos/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod delegates;
2 | pub mod slot;
3 | pub mod util;
--------------------------------------------------------------------------------
/src/consensus/dpos/slot.rs:
--------------------------------------------------------------------------------
1 | use time::{self, Timespec, Duration};
2 | use chrono::*;
3 | use cryptocurrency_kit::ethkey::Address;
4 |
5 | use crate::types::Height;
6 |
7 | ///
8 | /// [1, 2, 3, 4], [5, 6, 7, 8], [9, 10]
9 | /// slot0 slot1 slot2(current slot)
10 | /// next_slot = [13, 14, 15, 16]
11 | pub const INTERVAL: i64 = 3;
12 | pub const DELEGATES: i64 = 11;
13 | pub const ACTIVE_DELEGATES:[&str; DELEGATES as usize] = [
14 | "a",
15 | "b",
16 | "c",
17 | "d",
18 | "e",
19 | "f",
20 | "g",
21 | "h",
22 | "i",
23 | "j",
24 | "k"];
25 |
26 | lazy_static! {
27 | static ref ACTIVE_DELEGATES_LIST: Vec<&'static str> = {
28 | let mut active_delegates = vec![];
29 | {
30 | active_delegates.push("a");
31 | active_delegates.push("b");
32 | active_delegates.push("c");
33 | active_delegates.push("d");
34 | active_delegates.push("e");
35 | active_delegates.push("f");
36 | active_delegates.push("g");
37 | active_delegates.push("h");
38 | active_delegates.push("i");
39 | active_delegates.push("j");
40 | active_delegates.push("k");
41 | }
42 | active_delegates
43 | };
44 | }
45 |
46 | pub fn get_active_delegates<'a>(height: Height) -> Vec<&'a str> {
47 | // ACTIVE_DELEGATES.to_vec()
48 | ACTIVE_DELEGATES_LIST.clone()
49 | }
50 |
51 | /// this is a epoch time
52 | pub fn get_time(time_spec: Timespec) -> i64{
53 | return epoch_time(time_spec)
54 | }
55 |
56 | /// real time, accurate to milliseconds
57 | pub fn get_real_time(epoch_spec: i64) -> i64 {
58 | (epoch_spec + begin_epoch_time()) * 1000
59 | }
60 |
61 | /// epoch_time time's slot
62 | pub fn get_slot_number(mut epoch_time: i64) -> i64 {
63 | if epoch_time == 0 {
64 | epoch_time = get_time(time::get_time());
65 | }
66 | return epoch_time / INTERVAL
67 | }
68 |
69 | /// this is epoch time
70 | pub fn get_slot_time(slot: i64) -> i64{
71 | return slot * INTERVAL
72 | }
73 |
74 | // current slot + 1
75 | pub fn get_next_slot() -> i64 {
76 | let time_now = time::get_time();
77 | let epoch_time = get_time(time_now);
78 | let slot = get_slot_number(epoch_time);
79 | slot + 1
80 | }
81 |
82 | pub fn get_last_slot(next_slot: i64) -> i64 {
83 | next_slot + DELEGATES
84 | }
85 |
86 | // [time_spec - begin_time]
87 | fn epoch_time(time_spec: Timespec) -> i64 {
88 | time_spec.sec - begin_epoch_time()
89 | }
90 |
91 | // return begin epoch time
92 | fn begin_epoch_time() -> i64 {
93 | let epoch_time = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
94 | epoch_time.timestamp()
95 | }
96 |
97 | fn round_time(data: Timespec) -> i64 {
98 | data.sec
99 | }
100 |
101 | // calc height round
102 | fn calc_round(height: i64) -> i64{
103 | let round = (height as f64) / (DELEGATES as f64);
104 | round.ceil() as i64
105 | }
106 |
107 | #[cfg(test)]
108 | mod tests {
109 | use std::io::{self, Write};
110 |
111 | #[test]
112 | fn test_epoch_time(){
113 | println!("Hello Word ....");
114 | let epoch = super::DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();
115 | writeln!(io::stdout(), "{}", epoch.timestamp()).unwrap();
116 |
117 | let time_now = super::time::get_time();
118 | let epoch_time = super::epoch_time(time_now);
119 | writeln!(io::stdout(), "epoch time {}", epoch_time).unwrap();
120 | }
121 |
122 | #[test]
123 | fn test_get_real_time(){
124 | let time_now = super::time::get_time();
125 | let epoch_time = super::get_time(time_now);
126 | assert_eq!(super::get_real_time(epoch_time), time_now.sec *1000);
127 | writeln!(io::stdout(), "real time {}", super::get_real_time(epoch_time)).unwrap();
128 | }
129 |
130 | #[test]
131 | fn test_get_slot_number(){
132 | let time_now = super::time::get_time();
133 | let epoch_time = super::get_time(time_now);
134 | writeln!(io::stdout(), "epoch time {}, slot number {}",epoch_time, super::get_slot_number(epoch_time)).unwrap();
135 | }
136 |
137 | #[test]
138 | fn test_get_next_slot_number(){
139 | let time_now = super::time::get_time();
140 | let epoch_time = super::get_time(time_now);
141 | let slot_number = super::get_slot_number(epoch_time);
142 |
143 | writeln!(io::stdout(), "prev slot number {}, next slot number {}", slot_number, super::get_next_slot()).unwrap();
144 | }
145 |
146 | #[test]
147 | fn test_round_time(){
148 | assert_eq!(super::calc_round(1), 1);
149 | assert_eq!(super::calc_round(10), 1);
150 | assert_eq!(super::calc_round(11), 1);
151 | assert_eq!(super::calc_round(12), 2);
152 | }
153 | }
--------------------------------------------------------------------------------
/src/consensus/dpos/util.rs:
--------------------------------------------------------------------------------
1 | use std::string::String;
2 |
3 | // TODO: opz
4 | pub fn equal_delegate(a: &str, b: &Vec) -> bool {
5 | String::from_utf8_lossy(b).as_ref() == a
6 | }
7 |
--------------------------------------------------------------------------------
/src/consensus/engine.rs:
--------------------------------------------------------------------------------
1 | use cryptocurrency_kit::ethkey::Address;
2 | use crossbeam::crossbeam_channel::Sender;
3 |
4 | use crate::types::block::{Header, Block};
5 |
6 | //pub trait Engine {
7 | // fn author(&self, header: &Header)-> Result;
8 | // fn verify_header(&self, header: &Header, seal: bool) -> Result<(), String>;
9 | // fn verify_seal(&self, header: &Header) -> Result<(), String>;
10 | //
11 | // // preare initializes the consensus fields of a block header according to the rules of a
12 | // // particular engine. The changes are executed inline.
13 | // fn prepare(&mut self, header: &Header) -> Result<(), String>;
14 | //
15 | // fn finalize(&mut self, header: &Header) -> Result;
16 | //
17 | // // seal generate a new block for the given input block with the local miner's
18 | // // seal place on top
19 | // fn seal(&self, block: &Block, stop: Sender) -> Result;
20 | //}
21 |
22 |
23 | // Handler should be implemented is the consensus needs to handle and send peer's message
24 | pub trait Handler{
25 | // NewChainHeader handles a new head block comes
26 | fn new_chain_head(&self) -> Result<(), String>;
27 |
28 | fn handle_message(&self, address: Address, data: &[u8]) -> Result;
29 | }
--------------------------------------------------------------------------------
/src/consensus/error.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 | use failure::Error;
3 |
4 | use cryptocurrency_kit::crypto::Hash;
5 |
6 | use crate::types::Height;
7 |
8 | pub type ConsensusResult = Result<(), ConsensusError>;
9 | pub type EngineResult = Result<(), EngineError>;
10 |
11 | #[derive(Debug, Fail)]
12 | pub enum ConsensusError {
13 | #[fail(display = "Ignore message")]
14 | Ignored,
15 | #[fail(display = "Future message")]
16 | FutureMessage,
17 | #[fail(display = "Future round message")]
18 | FutureRoundMessage,
19 | #[fail(display = "Future unit message")]
20 | FutureBlockMessage(Height),
21 | #[fail(display = "inconsistent subjects")]
22 | InconsistentSubject,
23 | #[fail(display = "Old message")]
24 | OldMessage,
25 | #[fail(display = "Invalid message")]
26 | InvalidMessage,
27 | #[fail(display = "Unauthorized address")]
28 | UnauthorizedAddress,
29 | #[fail(display = "Waiting for new round")]
30 | WaitNewRound,
31 | #[fail(display = "Not from proposer")]
32 | NotFromProposer,
33 | #[fail(display = "Timeout message")]
34 | TimeoutMessage,
35 | #[fail(display = "An unknown error has occurred, ({})", _0)]
36 | Unknown(String),
37 | #[fail(display = "engine error hash occurred, ({})", _0)]
38 | Engine(EngineError),
39 | }
40 |
41 | #[derive(Debug, Fail)]
42 | pub enum EngineError {
43 | #[fail(display = "engine is not started")]
44 | EngineNotStarted,
45 | #[fail(display = "Invalid proposal")]
46 | InvalidProposal,
47 | #[fail(display = "Invalid signature")]
48 | InvalidSignature,
49 | #[fail(display = "Invalid height")]
50 | InvalidHeight,
51 | #[fail(display = "Invalid timestamp")]
52 | InvalidTimestamp,
53 | #[fail(display = "Invalid transaction hash, expect: {:?}, got: {:?}", _0, _1)]
54 | InvalidTransactionHash(Hash, Hash),
55 | #[fail(display = "Unauthorized")]
56 | Unauthorized,
57 | #[fail(display = "Lack votes, expect: {}, got: {}", _0, _1)]
58 | LackVotes(usize, usize),
59 | #[fail(display = "Block in the future")]
60 | FutureBlock,
61 | #[fail(display = "Invalid block number")]
62 | InvalidBlock,
63 | #[fail(display = "Unknown ancestor, child:{:?}, parent: {:?}", _0, _1)]
64 | UnknownAncestor(Height, Height),
65 | #[fail(display = "Consensus interrupt")]
66 | Interrupt,
67 | #[fail(display = "An unknown error has occurred, ({})", _0)]
68 | Unknown(String),
69 | }
70 |
--------------------------------------------------------------------------------
/src/consensus/events.rs:
--------------------------------------------------------------------------------
1 | use std::any::{Any, TypeId};
2 |
3 | use ::actix::prelude::*;
4 |
5 | use crate::{
6 | protocol::GossipMessage,
7 | types::Height,
8 | };
9 | use super::{
10 | types::{Proposal, View},
11 | error::ConsensusResult,
12 | };
13 |
14 | #[derive(Debug, Message)]
15 | pub enum OpCMD {
16 | stop,
17 | Ping,
18 | }
19 |
20 | #[derive(Debug)]
21 | pub enum RequestEventType {
22 | Block,
23 | Msg,
24 | }
25 |
26 | pub struct RequestEvent {
27 | proposal: Proposal,
28 | }
29 |
30 | fn is_view(_s: &T) -> bool {
31 | TypeId::of::() == TypeId::of::()
32 | }
33 |
34 | #[derive(Debug)]
35 | pub struct NewHeaderEvent {
36 | pub proposal: Proposal,
37 | }
38 |
39 | impl Message for NewHeaderEvent {
40 | type Result = ConsensusResult;
41 | }
42 |
43 | #[derive(Debug)]
44 | pub struct MessageEvent {
45 | pub payload: Vec,
46 | }
47 |
48 | impl Message for MessageEvent {
49 | type Result = ConsensusResult;
50 | }
51 |
52 | #[derive(Debug, Message)]
53 | pub struct FinalCommittedEvent {}
54 |
55 | #[derive(Debug, Message)]
56 | pub struct TimerEvent {}
57 |
58 | #[derive(Debug)]
59 | pub struct BackLogEvent {
60 | pub msg: GossipMessage,
61 | }
62 |
63 | impl Message for BackLogEvent {
64 | type Result = ConsensusResult;
65 | }
66 |
67 | #[derive(Debug, Message)]
68 | pub enum ConsensusEvent {
69 | NetWork(MessageEvent),
70 | FinalCommitted(FinalCommittedEvent),
71 | Timer(TimerEvent),
72 | BackLog(BackLogEvent),
73 | }
74 |
75 | #[cfg(test)]
76 | mod test {
77 | use super::*;
78 |
79 | #[derive(Debug)]
80 | struct testView {}
81 |
82 | impl ::std::fmt::Display for testView {
83 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
84 | write!(f, "")
85 | }
86 | }
87 |
88 | #[test]
89 | fn test_type_of() {
90 | let view = View { height: 10, round: 20 };
91 | assert!(is_view(&view));
92 | assert!(!is_view(&testView {}));
93 | }
94 | }
--------------------------------------------------------------------------------
/src/consensus/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod consensus;
2 | pub mod config;
3 | pub mod validator;
4 | pub mod types;
5 | pub mod events;
6 | pub mod backend;
7 | pub mod engine;
8 | pub mod error;
9 | pub mod pbft;
10 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/back_log.rs:
--------------------------------------------------------------------------------
1 | use actix::{Actor, Addr, Arbiter, Context, Handler, msgs, System};
2 | use actix::AsyncContext;
3 | use priority_queue::PriorityQueue;
4 | use cryptocurrency_kit::ethkey::Address;
5 | use cryptocurrency_kit::crypto::{hash, CryptoHash, Hash};
6 | use cryptocurrency_kit::storage::values::StorageValue;
7 | use serde::{Deserialize, Serialize};
8 |
9 | use std::borrow::Cow;
10 | use std::io::Cursor;
11 | use std::collections::HashMap;
12 | use std::time::Duration;
13 |
14 | use crate::protocol::{GossipMessage, MessageType, to_priority};
15 | use crate::consensus::types::{View, Subject, PrePrepare};
16 | use crate::consensus::validator::ImplValidatorSet;
17 | use super::core::Core;
18 |
19 | pub struct BackLogActor {
20 | qp: HashMap>,
21 | core: Addr,
22 | }
23 |
24 |
25 | impl Actor for BackLogActor {
26 | type Context = Context;
27 |
28 | fn started(&mut self, ctx: &mut Self::Context) {
29 | info!("Back Log actor has started");
30 | self.process_back_log(ctx);
31 | }
32 |
33 | fn stopped(&mut self, _ctx: &mut Self::Context) {
34 | info!("Back Log actor has stoppped");
35 | }
36 | }
37 |
38 | impl Handler for BackLogActor {
39 | type Result = ();
40 | fn handle(&mut self, msg: GossipMessage, _ctx: &mut Context) -> Self::Result {
41 | match &msg.code {
42 | MessageType::Preprepare => {
43 | let msg_payload = msg.msg();
44 | let preprepare: PrePrepare = PrePrepare::from_bytes(Cow::from(msg_payload));
45 | let view = preprepare.view;
46 | let weight = to_priority(MessageType::Preprepare, view);
47 | self.qp.entry(msg.address).or_insert_with(|| {
48 | let mut qp = PriorityQueue::new();
49 | qp.push(msg, weight);
50 | qp
51 | });
52 | }
53 | other_code => {
54 | let msg_payload = msg.msg();
55 | let subject: Subject = Subject::from_bytes(Cow::from(msg_payload));
56 | let weight = to_priority(other_code.clone(), subject.view);
57 | self.qp.entry(msg.address).or_insert_with(|| {
58 | let mut qp = PriorityQueue::new();
59 | qp.push(msg, weight);
60 | qp
61 | });
62 | }
63 | }
64 | ()
65 | }
66 | }
67 |
68 | impl BackLogActor {
69 | pub fn new(core_pid: Addr) -> Self {
70 | BackLogActor { core: core_pid, qp: HashMap::new() }
71 | }
72 |
73 | fn process_back_log(&self, ctx: &mut actix::Context) {
74 | ctx.run_interval(Duration::from_millis(100), |act, _ctx| {
75 | for (_key, value) in act.qp.iter_mut() {
76 | for (message, _) in value.iter_mut() {
77 | let view;
78 | match &message.code {
79 | MessageType::Preprepare => {
80 | let preprepare: PrePrepare = PrePrepare::from_bytes(Cow::from(message.msg()));
81 | view = preprepare.view;
82 | }
83 | _other_type => {
84 | let subject: Subject = Subject::from_bytes(Cow::from(message.msg()));
85 | view = subject.view;
86 | }
87 | }
88 | }
89 | }
90 | });
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/commit.rs:
--------------------------------------------------------------------------------
1 | use cryptocurrency_kit::crypto::Hash;
2 | use cryptocurrency_kit::ethkey::{
3 | public_to_address, recover, verify_address, Address, Message as SignMessage, Signature,
4 | };
5 | use cryptocurrency_kit::storage::values::StorageValue;
6 |
7 | use super::core::Core;
8 | use crate::{
9 | consensus::error::{ConsensusError, ConsensusResult},
10 | consensus::types::{Subject, View},
11 | consensus::validator::ValidatorSet,
12 | protocol::{GossipMessage, MessageType, State},
13 | types::{
14 | votes::{decrypt_commit_bytes, encrypt_commit_bytes, Votes},
15 | Validator,
16 | },
17 | };
18 |
19 | use std::borrow::Cow;
20 | use ethereum_types::H256;
21 | use cryptocurrency_kit::common::to_fixed_array_32;
22 |
23 | pub trait HandleCommit {
24 | fn send_commit(&mut self);
25 | fn send_commit_for_old_block(&mut self, view: &View, digest: Hash);
26 | fn broadcast_commit(&mut self, sub: &Subject, seal: Hash);
27 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> Result<(), ConsensusError>;
28 | fn verify_commit(
29 | &self,
30 | commit_seal: Option<&Signature>,
31 | subject: &Subject,
32 | sender: Address,
33 | src: Validator,
34 | ) -> Result<(), ConsensusError>;
35 | fn accept(&mut self, msg: &GossipMessage, src: &Validator) -> Result<(), ConsensusError>;
36 | }
37 |
38 | impl HandleCommit for Core {
39 | fn send_commit(&mut self) {
40 | let current_state = &self.current_state;
41 | let proposal = current_state.proposal().unwrap();
42 | let block = proposal.block();
43 | let subject = current_state.subject();
44 | self.broadcast_commit(subject.as_ref().unwrap(), block.hash())
45 | }
46 |
47 | fn send_commit_for_old_block(&mut self, view: &View, digest: Hash) {
48 | let subject = Subject {
49 | view: view.clone(),
50 | digest: digest,
51 | };
52 | self.broadcast_commit(&subject, digest)
53 | }
54 |
55 | // TOOD
56 | fn broadcast_commit(&mut self, subject: &Subject, _digest: Hash) {
57 | trace!("broadcast commit");
58 | let commit_seal = encrypt_commit_bytes(&subject.digest, self.keypair.secret());
59 | let encoded_subject = subject.clone().into_bytes();
60 | let msg = GossipMessage::new(MessageType::Commit, encoded_subject, Some(commit_seal));
61 | self.broadcast(&msg);
62 | }
63 |
64 | // handle commit type message
65 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> Result<(), ConsensusError> {
66 | debug!("Handle commit message from {:?}", src.address());
67 | let subject = Subject::from(msg.msg());
68 | // let _current_subject = self.current_state.subject().unwrap();
69 | self.check_message(MessageType::Commit, &subject.view)?;
70 | let sender = msg.address;
71 | let subject = Subject::from_bytes(Cow::from(msg.msg()));
72 | self.verify_commit(msg.commit_seal.as_ref(), &subject, sender, src.clone())?;
73 | debug!(
74 | "Pass very commit, commit size:{}, state:{:?}, {}",
75 | self.current_state.commits.len(),
76 | self.state,
77 | msg.trace()
78 | );
79 | ::accept(self, msg, src)?;
80 | let val_set = self.val_set();
81 | // receive more +2/3 votes
82 | if self.current_state.commits.len() > val_set.two_thirds_majority()
83 | && self.state < State::Committed
84 | {
85 | self.current_state.lock_hash();
86 | self.commit();
87 | }
88 | Ok(())
89 | }
90 |
91 | fn verify_commit(
92 | &self,
93 | commit_seal: Option<&Signature>,
94 | commit_subject: &Subject,
95 | sender: Address,
96 | _src: Validator,
97 | ) -> Result<(), ConsensusError> {
98 | if commit_seal.is_none() {
99 | return Err(ConsensusError::Unknown("commit seal is nil".to_string()));
100 | }
101 | let commit_seal = commit_seal.unwrap();
102 | let digest = H256::from(to_fixed_array_32(commit_subject.digest.as_ref()));
103 | let sign_message = SignMessage::from(digest);
104 | verify_address(&sender, commit_seal, &sign_message)
105 | .map(|_| ())
106 | .map_err(|_| {
107 | ConsensusError::Unknown("message's sender should be commit seal".to_string())
108 | })?;
109 | let current_state = &self.current_state;
110 | let current_subject = current_state.subject().unwrap();
111 | if current_subject.digest != commit_subject.digest
112 | || current_subject.view != commit_subject.view
113 | {
114 | warn!(
115 | "Inconsistent subjects between commit and proposal, d1={}, d2={}",
116 | current_subject.digest.short(),
117 | commit_subject.digest.short()
118 | );
119 | //return Err(ConsensusError::Unknown(
120 | // "Inconsistent subjects between commit and proposal".to_string(),
121 | //));
122 | }
123 | Ok(())
124 | }
125 |
126 | fn accept(&mut self, msg: &GossipMessage, _: &Validator) -> ConsensusResult {
127 | self.current_state
128 | .commits
129 | .add(msg.clone())
130 | .map_err(|err| ConsensusError::Unknown(err))
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod core;
2 | pub mod round_state;
3 | pub mod back_log;
4 | mod timer;
5 | pub mod types;
6 | mod round_change_set;
7 | pub mod new_header;
8 | pub mod request;
9 | pub mod preprepare;
10 | pub mod prepare;
11 | pub mod commit;
12 | pub mod round_change;
--------------------------------------------------------------------------------
/src/consensus/pbft/core/new_header.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | consensus::error::{ConsensusError, ConsensusResult},
3 | consensus::types::{Subject, View, Request},
4 | consensus::validator::ValidatorSet,
5 | protocol::{GossipMessage, MessageType, State},
6 | consensus::events::{RequestEvent, NewHeaderEvent},
7 | types::Validator,
8 | };
9 |
10 | use super::{
11 | core::Core,
12 | commit::HandleCommit,
13 | request::HandlerRequest,
14 | };
15 |
16 | pub trait HandleNewHeader {
17 | fn handle(&mut self, msg: &NewHeaderEvent, src: &Validator) -> ConsensusResult;
18 | }
19 |
20 | impl HandleNewHeader for Core {
21 | fn handle(&mut self, msg: &NewHeaderEvent, src: &Validator) -> ConsensusResult {
22 | // start new round, height = last_height + 1
23 | self.start_new_zero_round();
24 | ::handle(self, &Request::new(msg.proposal.clone()))
25 | }
26 | }
--------------------------------------------------------------------------------
/src/consensus/pbft/core/prepare.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::time::Duration;
3 |
4 | use cryptocurrency_kit::storage::values::StorageValue;
5 |
6 |
7 | use crate::{
8 | consensus::error::{ConsensusError, ConsensusResult},
9 | consensus::types::{Subject, View},
10 | consensus::validator::ValidatorSet,
11 | protocol::{GossipMessage, MessageType, State},
12 | types::Validator,
13 | };
14 |
15 | use super::{
16 | core::Core,
17 | commit::HandleCommit,
18 | };
19 |
20 | pub trait HandlePrepare {
21 | fn send_prepare(&mut self);
22 | fn verify_prepare(&mut self, prepare: &Subject, src: &Validator) -> ConsensusResult;
23 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> ConsensusResult;
24 | fn accept(&mut self, msg: &GossipMessage, src: &Validator) -> ConsensusResult;
25 | }
26 |
27 | impl HandlePrepare for Core {
28 | fn send_prepare(&mut self) {
29 | let current_view = self.current_view();
30 | let subject = self.current_state.subject().as_ref().cloned().unwrap();
31 | let payload = subject.into_bytes();
32 |
33 | self.broadcast(&GossipMessage::new(
34 | MessageType::Prepare,
35 | payload,
36 | None,
37 | ));
38 | }
39 |
40 | fn verify_prepare(&mut self, subject: &Subject, _src: &Validator) -> ConsensusResult {
41 | let current_view = self.current_view();
42 | if current_view != subject.view {
43 | return Err(ConsensusError::InconsistentSubject);
44 | }
45 | Ok(())
46 | }
47 |
48 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> ConsensusResult {
49 | let subject: Subject = Subject::from_bytes(Cow::from(msg.msg()));
50 | self.check_message(MessageType::Prepare, &subject.view)?;
51 | self.verify_prepare(&subject, src)?;
52 | ::accept(self, msg, src)?;
53 | // Add lock hash prove
54 | if self.current_state.is_locked() && subject.digest == *self.current_state.get_lock_hash().as_ref().unwrap() {
55 | self.current_state.lock_hash();
56 | self.set_state(State::Prepared);
57 | self.send_commit();
58 | }
59 | if self.current_state.get_prepare_or_commit_size() > self.val_set().two_thirds_majority() {
60 | self.current_state.lock_hash();
61 | self.set_state(State::Prepared);
62 | self.send_commit();
63 | }
64 |
65 | Ok(())
66 | }
67 |
68 | fn accept(&mut self, msg: &GossipMessage, _src: &Validator) -> ConsensusResult {
69 | self.current_state
70 | .prepares
71 | .add(msg.clone())
72 | .map_err(|err| ConsensusError::Unknown(err))
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/preprepare.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::time::Duration;
3 |
4 | use cryptocurrency_kit::storage::values::StorageValue;
5 | use cryptocurrency_kit::crypto::{CryptoHash, Hash, hash};
6 |
7 | use crate::{
8 | consensus::error::{ConsensusError, ConsensusResult, EngineError},
9 | consensus::types::{PrePrepare, Proposal, Request, Subject},
10 | consensus::validator::Validators,
11 | consensus::validator::ValidatorSet,
12 | protocol::{GossipMessage, MessageType, State},
13 | types::Validator,
14 | };
15 |
16 | use super::{
17 | round_change::HandleRoundChange,
18 | core::Core,
19 | commit::HandleCommit,
20 | prepare::HandlePrepare,
21 | };
22 |
23 | pub trait HandlePreprepare {
24 | fn send_preprepare(&mut self, requst: &Request);
25 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> Result<(), ConsensusError>;
26 | fn accetp(&mut self, preprepare: &PrePrepare);
27 | }
28 |
29 | impl HandlePreprepare for Core {
30 | fn send_preprepare(&mut self, request: &Request) {
31 | //TODO add lock hash prove
32 | if self.current_state.height() == request.proposal().block().height() && self.is_proposer()
33 | {
34 | let preprepre = PrePrepare::new(self.current_view(), request.proposal.clone());
35 | self.broadcast(&GossipMessage::new(
36 | MessageType::Preprepare,
37 | preprepre.into_bytes(),
38 | None,
39 | ));
40 | } else {
41 | debug!("Im's not proposer");
42 | }
43 | }
44 |
45 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> ConsensusResult {
46 | let preprepare: PrePrepare = PrePrepare::from_bytes(Cow::from(msg.msg()));
47 | let result = self.check_message(MessageType::Preprepare, &preprepare.view);
48 | // Ensure we have the same view with the PRE-PREPARE message
49 | // If it is old message, see if we need to broadcast COMMIT
50 | if let Err(ref err) = result {
51 | match err {
52 | ConsensusError::OldMessage => {
53 | let block = preprepare.proposal.block();
54 | let pre_header = match self.backend.get_header_by_height(block.height()) {
55 | Some(header) => {
56 | header
57 | }
58 | None => {
59 | return Err(ConsensusError::Engine(EngineError::InvalidProposal));
60 | }
61 | };
62 | if pre_header.block_hash() != block.hash() {
63 | return Err(ConsensusError::Engine(EngineError::InvalidProposal));
64 | }
65 | let pre_height = block.height() - 1;
66 | let mut val_set = self.backend.validators(pre_height).clone();
67 | let _previous_proposer = self.backend.get_proposer(pre_height);
68 | val_set.calc_proposer(&block.header().prev_hash, pre_height, preprepare.view.round);
69 | if val_set.is_proposer(src.address().clone())
70 | && self.backend.has_proposal(&block.hash(), block.height())
71 | {
72 | self.send_commit_for_old_block(&preprepare.view, block.hash());
73 | }
74 | }
75 | ConsensusError::FutureBlockMessage(_) => {
76 | // forward EngineError::FutureBlock to handle
77 | // self.new_round_future_preprepare_timer()
78 | }
79 | _ => return result
80 | }
81 | }
82 |
83 | let val_set = self.val_set();
84 | if val_set.is_proposer(src.address().clone()) == false {
85 | return Err(ConsensusError::NotFromProposer);
86 | }
87 |
88 | // TODO
89 | let (d, result) = self
90 | .backend
91 | .verify(&preprepare.proposal);
92 |
93 | if let Err(ref err) = result {
94 | match err {
95 | EngineError::FutureBlock => {
96 | self.new_round_future_preprepare_timer(d, msg.clone());
97 | return Err(ConsensusError::FutureBlockMessage(preprepare.proposal.block().height()));
98 | }
99 | // other error
100 | _ => {
101 | // send next round change, because proposal is invalid, so proposer is bad node
102 | self.send_next_round_change();
103 | return Err(ConsensusError::Unknown(format!("{}", err)));
104 | }
105 | }
106 | }
107 |
108 | if self.state == State::AcceptRequest {
109 | if self.current_state.is_locked() {
110 | if preprepare.proposal.block().hash() == self.current_state.get_lock_hash().unwrap() {
111 | ::accetp(self, &preprepare);
112 | self.set_state(State::Prepared);
113 | self.send_commit();
114 | } else {
115 | self.send_next_round_change();
116 | }
117 | } else {
118 | ::accetp(self, &preprepare);
119 | self.set_state(State::PrePrepared);
120 | self.send_prepare();
121 | }
122 | }
123 |
124 | // TODO
125 | Ok(())
126 | }
127 |
128 | fn accetp(&mut self, preprepare: &PrePrepare) {
129 | let header = preprepare.proposal.block().header();
130 | self.consensus_timestamp = Duration::from_nanos(header.time);
131 | self.current_state.set_preprepare(preprepare.clone())
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/request.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 |
3 | use crate::{
4 | protocol::State,
5 | consensus::error::{ConsensusError, ConsensusResult},
6 | consensus::types::{Request, PrePrepare, Proposal},
7 | };
8 |
9 | use super::core::Core;
10 | use super::preprepare::HandlePreprepare;
11 |
12 | pub trait HandlerRequest {
13 | fn handle(&mut self, request: &Request) -> ConsensusResult;
14 | fn check_request_message(&self, request: &Request) ->ConsensusResult;
15 | fn accept(&mut self, request: &Request);
16 | }
17 |
18 | impl HandlerRequest for Core {
19 | fn handle(&mut self, request: &Request) -> ConsensusResult {
20 | self.check_request_message(request)?;
21 | assert_eq!(self.state, State::AcceptRequest);
22 | ::accept(self, request);
23 | self.send_preprepare(request);
24 | Ok(())
25 | }
26 |
27 | fn check_request_message(&self, request: &Request) -> ConsensusResult {
28 | if self.current_state.height() == 0 {
29 | return Err(ConsensusError::WaitNewRound);
30 | }
31 | if self.current_state.height() > request.proposal.block().height() {
32 | return Err(ConsensusError::OldMessage);
33 | }
34 | if self.current_state.height() < request.proposal.block().height() {
35 | return Err(ConsensusError::FutureMessage);
36 | }
37 | Ok(())
38 | }
39 |
40 | fn accept(&mut self, request: &Request) {
41 | self.current_state.pending_request = Some(Request{proposal: request.proposal.clone()});
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/round_change.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::time::Instant;
3 | use std::time::Duration;
4 |
5 | use cryptocurrency_kit::crypto::EMPTY_HASH;
6 | use cryptocurrency_kit::storage::values::StorageValue;
7 |
8 | use crate::{
9 | consensus::error::{ConsensusError, ConsensusResult},
10 | consensus::validator::ValidatorSet,
11 | consensus::types::{Round, Subject, View},
12 | protocol::{GossipMessage, MessageType},
13 | types::Validator,
14 | };
15 |
16 | use super::core::Core;
17 |
18 | pub trait HandleRoundChange {
19 | fn send_next_round_change(&mut self);
20 | fn send_round_change(&mut self, round: Round);
21 | // receive a round change message and handle it
22 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> ConsensusResult;
23 | }
24 |
25 | impl HandleRoundChange for Core {
26 | fn send_next_round_change(&mut self) {
27 | let current_view = self.current_view();
28 | self.round_change_set.print_info();
29 | // Find the max
30 | let round = self.round_change_set.max_round();
31 | if round <= current_view.round {
32 | self.send_round_change(current_view.round + 1);
33 | } else {
34 | self.send_round_change(round);
35 | }
36 | }
37 |
38 | fn send_round_change(&mut self, round: Round) {
39 | if Instant::now().duration_since(self.round_change_limiter) <= Duration::from_millis(50) {
40 | debug!("Skip round change sent");
41 | self.new_round_change_timer();
42 | return;
43 | }
44 | self.round_change_limiter = Instant::now();
45 |
46 | if self.current_view().round < round {
47 | self.catchup_round(round);
48 | }
49 | let current_view = self.current_view();
50 |
51 | // let ok = current_view.round < round;
52 | // assert!(ok);
53 |
54 | // TODO add pre max round change prove
55 | let subject = Subject {
56 | view: View::new(current_view.height, round),
57 | digest: EMPTY_HASH,
58 | };
59 | debug!("Vote for round change, current:{}, vote: {}", current_view.round, round);
60 | let mut msg = GossipMessage::new(MessageType::RoundChange, subject.into_bytes(), None);
61 | msg.create_time = chrono::Local::now().timestamp_millis() as u64;
62 | self.broadcast(&msg);
63 | }
64 |
65 | fn handle(&mut self, msg: &GossipMessage, src: &Validator) -> ConsensusResult {
66 | let subject: Subject = Subject::from_bytes(Cow::from(msg.msg()));
67 | debug!("Handle round change message from {:?}, from me: {}, subject: {:?}", src.address(), self.address() == *src.address(), subject);
68 | self.check_message(MessageType::RoundChange, &subject.view)?;
69 | let current_view = self.current_view();
70 | let current_val_set = self.val_set().clone();
71 | if current_view.round > subject.view.round && subject.view.round > 0 {
72 | debug!("round change, current_round:{}, round:{}", current_view.round, subject.view.round, );
73 | // may be peer is less than network node
74 | self.send_round_change(subject.view.round);
75 | return Ok(());
76 | }
77 |
78 | let n = self
79 | .round_change_set
80 | .add(subject.view.round, msg.clone())
81 | .map_err(|err| ConsensusError::Unknown(err))?;
82 | debug!("round change, current_round:{}, round:{}, votes size {}", current_view.round, subject.view.round, n);
83 |
84 | // check round change more detail
85 | // if n >= (current_val_set.two_thirds_majority() + 1)
86 | // && (self.wait_round_change && current_view.round < subject.view.round) {
87 | if n >= (current_val_set.two_thirds_majority() + 1)
88 | && (current_view.round < subject.view.round) {
89 | // 注意:假设节点刚起动,这时候,其wait_round_change 可能未false,这样即使收到了超过+2/3的票,如果采用
90 | // n == (current_val_set.two_thirds_majority() + 1, 是有问题的
91 | // receive more than local round and +2/3 has vote it
92 | self.send_round_change(subject.view.round);
93 | self.start_new_round(subject.view.round, &vec![]);
94 | return Ok(());
95 | } else if self.wait_round_change && current_view.round < subject.view.round {
96 | // receive more than local round
97 | return Err(ConsensusError::FutureRoundMessage);
98 | }
99 | Ok(())
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/round_change_set.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use crate::{
4 | protocol::{MessageManage, GossipMessage},
5 | consensus::types::{Round, View},
6 | consensus::validator::{ValidatorSet, Validators, ImplValidatorSet},
7 | };
8 |
9 | type PreRCSignBytes = Vec>;
10 |
11 | pub struct RoundChangeSet {
12 | validator_set: V,
13 | // 当前所有轮次的validators
14 | round_changes: HashMap,
15 | // 每个轮次的消息管理器
16 | pre_rc_sign_bytes: Option, // 暂时未用
17 | }
18 |
19 | impl RoundChangeSet {
20 | pub fn new(validators: ImplValidatorSet, pre_rc_sign_bytes: Option) -> RoundChangeSet {
21 | RoundChangeSet {
22 | validator_set: validators,
23 | round_changes: HashMap::new(),
24 | pre_rc_sign_bytes: pre_rc_sign_bytes,
25 | }
26 | }
27 |
28 | pub fn add(&mut self, round: Round, msg: GossipMessage) -> Result {
29 | let val_set = self.validator_set.clone();
30 | let msg_manager = self.round_changes.entry(round).or_insert_with(|| {
31 | MessageManage::new(View::default(), val_set)
32 | });
33 | msg_manager.add(msg).map(|_| { 0 })?;
34 | Ok(msg_manager.len())
35 | }
36 |
37 | pub fn round_change_set(&self, round: &Round) -> Option<&MessageManage> {
38 | self.round_changes.get(round)
39 | }
40 |
41 | // TODO
42 | // pub fn pre_round_change_bytes(&self)
43 |
44 | // pub fn max_round_change_changes_bytes(&self, n: usize) -> (Round, &Vec>) {
45 | //
46 | // }
47 | pub fn clear(&mut self, round: Round) {
48 | // dereference
49 | self.round_changes.retain(|&round_, mm| {
50 | mm.len() > 0 && round > round_
51 | });
52 | }
53 |
54 | // return the max round which the number of messages is equal or larger than num
55 | pub fn max_round_more_than_n(&self, num: usize) -> Option {
56 | if let Some((round, _)) = self.round_changes.iter().max_by(|x, y| x.0.cmp(y.0)) {
57 | if self.round_changes.get(round).unwrap().len() >= num {
58 | return Some(*round);
59 | }
60 | }
61 | None
62 | }
63 |
64 | pub fn max_round(&self) -> Round {
65 | let mut max = 0;
66 | let mut total = 0;
67 | self.round_changes.iter().for_each(|x| {
68 | if x.1.len() >= total && *x.0 > max {
69 | max = *x.0;
70 | total = x.1.len();
71 | };
72 | });
73 | max
74 | }
75 |
76 | pub fn print_info(&self) {
77 | for round_change in &self.round_changes {
78 | debug!("round:{:?}, size:{:?}", round_change.0, round_change.1.len());
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/src/consensus/pbft/core/round_state.rs:
--------------------------------------------------------------------------------
1 | use cryptocurrency_kit::crypto::{Hash, EMPTY_HASH};
2 | use cryptocurrency_kit::storage::values::StorageValue;
3 |
4 | use crate::{
5 | consensus::validator::{ValidatorSet, ImplValidatorSet},
6 | consensus::types::{PrePrepare, Proposal, Request, Round, Subject, View},
7 | protocol::{GossipMessage, MessageManage, MessageType},
8 | types::Height,
9 | };
10 |
11 | // it is not safe
12 | pub struct RoundState {
13 | round: Round,
14 | height: Height,
15 | // 提案
16 | pub preprepare: Option,
17 | pub prepares: MessageManage,
18 | pub commits: MessageManage,
19 | pub pending_request: Option>,
20 | // 自己的提案
21 | lock_hash: Option, // 锁hash
22 | }
23 |
24 |
25 | impl RoundState
26 | {
27 | pub(crate) fn new_round_state(view: View, vals: ImplValidatorSet,
28 | lock_hash: Option,
29 | preprepare: Option,
30 | pending_request: Option>)
31 | -> Self {
32 | RoundState {
33 | round: view.round,
34 | height: view.height,
35 | preprepare: preprepare,
36 | prepares: MessageManage::new(view.clone(), vals.clone()),
37 | commits: MessageManage::new(view.clone(), vals.clone()),
38 | pending_request: pending_request,
39 | lock_hash: lock_hash,
40 | }
41 | }
42 |
43 | pub(crate) fn get_prepare_or_commit_size(&self) -> usize {
44 | let mut result = self.prepares.len() + self.commits.len();
45 | self.prepares.values().iter().for_each(|message| {
46 | if self.commits.get_message(message.address).is_some() {
47 | result -= 1;
48 | }
49 | });
50 |
51 | result
52 | }
53 |
54 | pub(crate) fn subject(&self) -> Option {
55 | if self.preprepare.is_none() {
56 | return None;
57 | }
58 | Some(Subject {
59 | view: View {
60 | round: self.round,
61 | height: self.height,
62 | },
63 | digest: self.preprepare.as_ref().unwrap().proposal.block().hash(),
64 | })
65 | }
66 |
67 | pub(crate) fn set_preprepare(&mut self, preprepare: PrePrepare) {
68 | self.preprepare = Some(preprepare);
69 | }
70 |
71 | pub(crate) fn proposal(&self) -> Option<&Proposal> {
72 | if self.preprepare.is_none() {
73 | None
74 | } else {
75 | Some(&self.preprepare.as_ref().unwrap().proposal)
76 | }
77 | }
78 |
79 | pub(crate) fn set_round(&mut self, round: Round) {
80 | self.round = round;
81 | }
82 |
83 | pub(crate) fn round(&self) -> Round {
84 | self.round
85 | }
86 |
87 | pub(crate) fn set_height(&mut self, height: Height) {
88 | self.height = height;
89 | }
90 |
91 | pub(crate) fn height(&self) -> Height {
92 | self.height
93 | }
94 |
95 | pub(crate) fn is_locked(&self) -> bool {
96 | self.lock_hash.is_some()
97 | }
98 |
99 | // 锁定提案
100 | pub(crate) fn lock_hash(&mut self) {
101 | if self.preprepare.is_none() {
102 | return;
103 | }
104 |
105 | self.lock_hash = Some(self.preprepare.as_ref().unwrap().proposal.block().hash());
106 | trace!(
107 | "Lock proposal, hash:{}",
108 | self.lock_hash.as_ref().unwrap().short()
109 | );
110 | }
111 |
112 | // 解锁提案
113 | pub(crate) fn unlock_hash(&mut self) {
114 | trace!(
115 | "Unlock proposal, hash:{}",
116 | self.lock_hash.as_ref().or_else(|| Some(&EMPTY_HASH)).unwrap().short()
117 | );
118 | self.lock_hash = None;
119 | }
120 |
121 | pub(crate) fn get_lock_hash(&self) -> Option {
122 | self.lock_hash.as_ref().cloned()
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/consensus/pbft/core/timer.rs:
--------------------------------------------------------------------------------
1 | use ::actix::prelude::*;
2 | use uuid::Uuid;
3 |
4 | use std::time::Duration;
5 |
6 | use crate::{
7 | consensus::validator::{ValidatorSet, ImplValidatorSet},
8 | consensus::events::{TimerEvent, BackLogEvent},
9 | protocol::GossipMessage,
10 | common::random_uuid,
11 | };
12 |
13 | use super::core::Core;
14 |
15 | #[derive(Debug, Message)]
16 | pub enum Op {
17 | Stop,
18 | Interval,
19 | }
20 |
21 | pub struct Timer {
22 | uuid: Uuid,
23 | name: String,
24 | pub interval: Duration,
25 | pub pid: Option>,
26 | msg: Option,
27 | }
28 |
29 | impl Actor for Timer {
30 | type Context = Context;
31 |
32 | fn started(&mut self, ctx: &mut Self::Context) {
33 | trace!("[{:?}]{}'s timer actor has started, du:{:?}", self.uuid.to_string(), self.name, self.interval.as_secs());
34 | ctx.notify_later(Op::Interval, self.interval);
35 | }
36 |
37 | fn stopped(&mut self, _: &mut Self::Context) {
38 | trace!("[{:?}]{}'s timer actor has stopped", self.uuid.to_string(), self.name);
39 | }
40 | }
41 |
42 | impl Handler for Timer {
43 | type Result = ();
44 | fn handle(&mut self, msg: Op, ctx: &mut Self::Context) -> Self::Result {
45 | // info!("[{:?}]{}'s timer actor triggers, op:{:?}", self.uuid.to_string(), self.name, msg);
46 | match msg {
47 | Op::Stop => ctx.stop(),
48 | Op::Interval => {
49 | if self.pid.is_some() {
50 | if let Some(ref msg) = self.msg {
51 | self.pid.as_ref().unwrap().do_send(BackLogEvent { msg: msg.clone() })
52 | } else {
53 | //FIXME
54 | if self.name == "future" {
55 | return;
56 | }
57 | self.pid.as_ref().unwrap().do_send(TimerEvent {})
58 | };
59 | }
60 | }
61 | }
62 | ()
63 | }
64 | }
65 |
66 | impl Timer {
67 | pub fn new(name: String, interval: Duration, pid: Addr, msg: Option) -> Self {
68 | Timer { uuid: random_uuid(), name, interval, pid: Some(pid), msg: msg }
69 | }
70 |
71 | pub fn new_tmp(name: String, interval: Duration) -> Self {
72 | Timer { uuid: random_uuid(), name, interval, pid: None, msg: None }
73 | }
74 | }
--------------------------------------------------------------------------------
/src/consensus/pbft/core/types.rs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laohanlinux/consensus-rs/ebf0879ea3cf653c62ac79ff658c804a3bddcc6f/src/consensus/pbft/core/types.rs
--------------------------------------------------------------------------------
/src/consensus/pbft/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod core;
--------------------------------------------------------------------------------
/src/core/actor.rs:
--------------------------------------------------------------------------------
1 | use ::actix::prelude::*;
2 | use actix_broker::BrokerSubscribe;
--------------------------------------------------------------------------------
/src/core/genesis.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 | use std::str::FromStr;
3 | use std::str::Utf8Error;
4 |
5 | use serde::{Serialize, Serializer, Deserialize, Deserializer};
6 | use ethereum_types::H160;
7 | use parking_lot::RwLock;
8 |
9 | use cryptocurrency_kit::ethkey::Address;
10 | use cryptocurrency_kit::crypto::EMPTY_HASH;
11 |
12 | use crate::{
13 | types::{Timestamp, Gas, Difficulty, Height, EMPTY_ADDRESS},
14 | types::block::{Block, Header},
15 | types::votes::{decrypt_commit_bytes, encrypt_commit_bytes, Votes},
16 | types::{Validator, Validators},
17 | config::GenesisConfig,
18 | common,
19 | };
20 | use super::{
21 | ledger::Ledger,
22 | };
23 |
24 | pub(crate) fn store_genesis_block(genesis_config: &GenesisConfig, ledger: Arc>) -> Result<(), String> {
25 | use chrono::{Local, DateTime, ParseError};
26 | let mut ledger = ledger.write();
27 | if let Some(genesis) = ledger.get_genesis_block() {
28 | info!("Genesis hash:{:?}", genesis.hash());
29 | ledger.reload_meta();
30 | return Ok(());
31 | }
32 | // add validators
33 | {
34 | let validators: Validators = genesis_config.validator.iter().map(|validator| {
35 | common::string_to_address(validator).unwrap()
36 | }).map(|address| {
37 | Validator::new(address)
38 | }).collect();
39 | ledger.add_validators(validators);
40 | }
41 |
42 | // TODO Add more xin
43 | {
44 | let proposer = common::string_to_address(&genesis_config.proposer)?;
45 | let epoch_time: DateTime = {
46 | let epoch_time_str = genesis_config.epoch_time.to_string();
47 | DateTime::from_str(&epoch_time_str)
48 | }.map_err(|err: ParseError| err.to_string())?;
49 |
50 | let extra = genesis_config.extra.as_bytes().to_vec();
51 | let mut header = Header::new(EMPTY_HASH, proposer, EMPTY_HASH, EMPTY_HASH, EMPTY_HASH,
52 | 0, 0, 0, genesis_config.gas_used + 10, genesis_config.gas_used,
53 | epoch_time.timestamp() as Timestamp, None, Some(extra));
54 | let block = Block::new(header, vec![]);
55 | ledger.add_genesis_block(&block);
56 | }
57 |
58 | Ok(())
59 | }
60 |
61 | #[cfg(test)]
62 | mod test {
63 | use super::*;
64 | use crate::common::random_dir;
65 | use cryptocurrency_kit::ethkey::{Generator, Random};
66 | use kvdb_rocksdb::Database;
67 | use crate::store::schema::Schema;
68 | use crate::core::ledger::{Ledger, LastMeta};
69 | use lru_time_cache::LruCache;
70 |
71 | #[test]
72 | fn t_genesis_block() {
73 | let secret = Random.generate().unwrap();
74 |
75 | let database = Database::open_default(&random_dir()).map_err(|err| err.to_string()).unwrap();
76 | let schema = Schema::new(Arc::new(database));
77 | let mut ledger = Ledger::new(
78 | LastMeta::new_zero(),
79 | LruCache::with_capacity(1 << 10),
80 | LruCache::with_capacity(1 << 10),
81 | vec![],
82 | schema,
83 | );
84 |
85 | let mut header = Header::new(EMPTY_HASH, Address::from(10), EMPTY_HASH, EMPTY_HASH, EMPTY_HASH,
86 | 0, 0, 0, 10, 10,
87 | 192, None, Some(vec![12, 1]));
88 | let block = Block::new(header, vec![]);
89 |
90 | ledger.add_genesis_block(&block);
91 |
92 | assert_eq!(false, ledger.get_block_hash_by_height(0).is_none());
93 | assert_eq!(true, ledger.get_block_hash_by_height(1).is_none());
94 | }
95 |
96 | #[test]
97 | fn t_back_block() {
98 | let secret = Random.generate().unwrap();
99 |
100 | let database = Database::open_default(&random_dir()).map_err(|err| err.to_string()).unwrap();
101 | let schema = Schema::new(Arc::new(database));
102 | let mut ledger = Ledger::new(
103 | LastMeta::new_zero(),
104 | LruCache::with_capacity(1 << 10),
105 | LruCache::with_capacity(1 << 10),
106 | vec![],
107 | schema,
108 | );
109 |
110 | let mut header = Header::new(EMPTY_HASH, Address::from(10), EMPTY_HASH, EMPTY_HASH, EMPTY_HASH,
111 | 0, 0, 0, 10, 10,
112 | 192, None, Some(vec![12, 1]));
113 | let block = Block::new(header, vec![]);
114 |
115 | ledger.add_genesis_block(&block);
116 | ledger.reload_meta();
117 |
118 | (1_u64..10).for_each(|height|{
119 | let mut header = Header::new(EMPTY_HASH, Address::from(10), EMPTY_HASH, EMPTY_HASH, EMPTY_HASH,
120 | 0, 0, height, 10, 10,
121 | 192, None, Some(vec![12, 1]));
122 | let block = Block::new(header, vec![]);
123 |
124 | ledger.add_block(&block);
125 | });
126 |
127 | (1_u64..10).for_each(|height|{
128 | let block = ledger.get_block_by_height(height).unwrap();
129 | let block1 = ledger.get_block(&block.hash()).unwrap();
130 | println!("{:?}", block);
131 | println!("|{:?}", block1);
132 |
133 | });
134 |
135 | // let schema = ledger.get_schema();
136 | // for block in schema.blocks().iter() {
137 | // println!("{:?}", block);
138 | // }
139 | //
140 | // println!("last_block {:?}", ledger.get_last_block());
141 | }
142 |
143 | #[test]
144 | fn t_exists_db() {
145 | // let database = Database::open_default("/tmp/block/c1").map_err(|err| err.to_string()).unwrap();
146 | // let schema = Schema::new(Arc::new(database));
147 | // for key in schema.blocks().keys() {
148 | // println!("{:?}", key);
149 | // }
150 |
151 | // for block in schema.blocks().iter() {
152 | // println!("{:?}", block);
153 | // }
154 |
155 | // for value in schema.blocks().values() {
156 | // println!("{:?}", value);
157 | // }
158 | }
159 | }
--------------------------------------------------------------------------------
/src/core/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod ledger;
2 | pub mod genesis;
3 | pub mod transaction_pool;
4 | pub mod tx_pool;
5 | pub mod chain;
6 | pub mod actor;
7 |
--------------------------------------------------------------------------------
/src/core/transaction_pool.rs:
--------------------------------------------------------------------------------
1 | use std::fmt::{self, LowerHex, Formatter};
2 | use std::sync::Arc;
3 |
4 | use ethereum_types::U256;
5 | use cryptocurrency_kit::{
6 | ethkey::Address,
7 | crypto::{CryptoHash, Hash, hash},
8 | };
9 | use transaction_pool::VerifiedTransaction;
10 |
11 |
12 | use crate::{
13 | types::{Gas, Height, Timestamp},
14 | types::transaction::Transaction,
15 | };
16 |
17 | #[derive(Debug, Default, Clone)]
18 | pub struct TransactionBuilder {
19 | nonce: U256,
20 | gas_price: Gas,
21 | gas: Gas,
22 | sender: Address,
23 | mem_usage: usize,
24 | }
25 |
26 | impl TransactionBuilder {
27 | pub fn tx(&self) -> Self {
28 | self.clone()
29 | }
30 |
31 | pub fn nonce>(mut self, nonce: T) -> Self {
32 | self.nonce = nonce.into();
33 | self
34 | }
35 |
36 | pub fn gas_price>(mut self, gas_price: T) -> Self {
37 | self.gas_price = gas_price.into();
38 | self
39 | }
40 |
41 | pub fn sender>(mut self, sender: T) -> Self {
42 | self.sender = sender.into();
43 | self
44 | }
45 |
46 | pub fn mem_usage(mut self, mem_usage: usize) -> Self {
47 | self.mem_usage = mem_usage;
48 | self
49 | }
50 |
51 | // pub fn new(self) -> Transaction {
52 | // let hash = self.nonce ^ (U256::from(100) * self.gas_price) ^ (U256::from(100_000) * U256::from(self.sender.low_u64()));
53 | //// Transaction::new()
54 | // }
55 | }
56 |
57 | impl VerifiedTransaction for Transaction {
58 | type Hash = Hash;
59 | type Sender = Address;
60 | fn hash(&self) -> &Hash { self.get_hash().as_ref().unwrap() }
61 |
62 | /// TODO
63 | fn mem_usage(&self) -> usize { 0 }
64 |
65 | fn sender(&self) -> &Address { self.to().unwrap() }
66 | }
67 |
68 | pub type SharedTransaction = Arc;
69 |
70 | #[cfg(test)]
71 | mod tests {
72 | use super::*;
73 | use transaction_pool::Pool;
74 |
75 |
76 | #[test]
77 | fn t_transaction_pool(){
78 |
79 | }
80 | }
--------------------------------------------------------------------------------
/src/core/tx_pool.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 | use std::collections::BTreeMap;
3 |
4 | use ::actix::prelude::*;
5 | use priority_queue::PriorityQueue;
6 | use cryptocurrency_kit::crypto::{Hash, hash, EMPTY_HASH};
7 | use evmap::{self, WriteHandle, ReadHandle};
8 |
9 | use crate::{
10 | types::transaction::Transaction,
11 | error::TxPoolError,
12 | };
13 |
14 | pub const MAX_TXPOOL_SIZE: u64 = 10_000_000;
15 | pub const MAX_SLOT_SIZE: u32 = 1_000;
16 |
17 | pub trait TxPool {
18 | fn len(&self) -> usize;
19 | fn get_tx(&self, tx_hash: &Hash) -> Option<&Transaction>;
20 | fn get_n_tx(&self, n: u64) -> Vec<&Transaction>;
21 | fn add_tx(&mut self, transaction: Transaction) -> Result;
22 | fn add_txs(&mut self, transactions: &Vec) -> Result;
23 | fn remove_txs(&mut self, tx_hashes: Vec<&Hash>);
24 | }
25 |
26 | pub type SafeTxPool = Box;
27 |
28 | pub struct BaseTxPool {
29 | pq: PriorityQueue,
30 | txs: Vec>,
31 | }
32 |
33 | impl Actor for BaseTxPool {
34 | type Context = Context;
35 | }
36 |
37 | impl TxPool for BaseTxPool {
38 | fn len(&self) -> usize {
39 | self.txs.len()
40 | }
41 |
42 | fn get_tx(&self, tx_hash: &Hash) -> Option<&Transaction> {
43 | self.txs[self.get_idx(tx_hash)].get(tx_hash)
44 | }
45 |
46 | fn get_n_tx(&self, n: u64) -> Vec<&Transaction> {
47 | let mut txs = vec![];
48 | let i: u64 = 0;
49 | for (tx_hash, _) in self.pq.iter() {
50 | if i >= n {
51 | break;
52 | }
53 | let idx = self.get_idx(tx_hash);
54 |
55 | let m = self.txs.get(idx).unwrap();
56 | txs.push(m.get(&tx_hash).unwrap());
57 | }
58 | txs
59 | }
60 |
61 | fn add_tx(&mut self, tx: Transaction) -> Result {
62 | let idx = self.get_idx(tx.get_hash().unwrap());
63 | let v: &mut BTreeMap<_, _> = self.txs.get_mut(idx).unwrap();
64 | if v.get(&tx.get_hash().unwrap()).is_some() {
65 | return Ok(self.pq.len() as u64);
66 | }
67 | v.insert(tx.get_hash().unwrap().clone(), tx.clone());
68 | self.pq.push(tx.get_hash().unwrap().clone(), tx.amount());
69 | Ok(self.pq.len() as u64)
70 | }
71 |
72 | fn add_txs(&mut self, txs: &Vec) -> Result {
73 | let mut start: u64 = 0;
74 | for tx in txs {
75 | self.add_tx(tx.clone())?;
76 | start += 1;
77 | }
78 | Ok(start)
79 | }
80 |
81 | fn remove_txs(&mut self, tx_hashes: Vec<&Hash>) {
82 | tx_hashes.iter().for_each(|tx_hash| {
83 | let idx = self.get_idx(tx_hash);
84 | let m: &mut BTreeMap<_, _> = self.txs.get_mut(idx).unwrap();
85 | m.remove(tx_hash);
86 | });
87 | }
88 | }
89 |
90 | impl BaseTxPool {
91 | pub fn new() -> Self {
92 | let n = (MAX_TXPOOL_SIZE / u64::from(MAX_SLOT_SIZE)) as usize;
93 | let mut tx_pool = BaseTxPool {
94 | pq: PriorityQueue::new(),
95 | txs: Vec::with_capacity(n),
96 | };
97 | (0..n).for_each(|_| {
98 | tx_pool.txs.push(BTreeMap::new());
99 | });
100 | tx_pool
101 | }
102 | fn get_idx(&self, tx_hash: &Hash) -> usize {
103 | use ethereum_types::U256;
104 | let u = U256::from(tx_hash.as_ref());
105 | (u % U256::from(self.txs.len())).as_u64() as usize
106 | }
107 | }
108 |
109 |
110 | #[cfg(test)]
111 | mod tests {
112 | use super::*;
113 | use std::sync::RwLock;
114 |
115 | struct TxPoolActor {
116 | tx_pool: Arc>>,
117 | }
118 |
119 | impl Actor for TxPoolActor {
120 | type Context = Context;
121 | }
122 |
123 | #[test]
124 | fn t_txpool() {
125 | // let mut v = vec![];
126 | (0..10_0000).for_each(|_idx| {})
127 | }
128 | }
--------------------------------------------------------------------------------
/src/error/mod.rs:
--------------------------------------------------------------------------------
1 | use failure::Error;
2 |
3 | use cryptocurrency_kit::crypto::Hash;
4 |
5 | #[derive(Debug, Fail)]
6 | pub enum TxPoolError {
7 | #[fail(display = "More than max txpool limit, max:{}", _0)]
8 | MoreThanMaxSIZE(u64),
9 | }
10 |
11 | #[derive(Debug, Fail)]
12 | pub enum P2PError {
13 | #[fail(display = "Handshake fail")]
14 | HandShakeFailed,
15 | #[fail(display = "different genesis")]
16 | DifferentGenesis,
17 | #[fail(display = "Dump connected")]
18 | DumpConnected,
19 | #[fail(display = "Invalid Message type")]
20 | InvalidMessage,
21 | #[fail(display = "Timeout")]
22 | Timeout,
23 | }
24 |
25 | pub type ChainResult = Result<(), ChainError>;
26 |
27 | #[derive(Debug, Fail)]
28 | pub enum ChainError {
29 | #[fail(display = "the block has exist, ({:?})", _0)]
30 | Exists(Hash),
31 | #[fail(display = "An unknown error has occurred, ({})", _0)]
32 | Unknown(String),
33 | }
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![feature(custom_attribute)]
2 | #![feature(nll)]
3 | #![feature(vec_remove_item)]
4 | #![feature(get_type_id)]
5 | #![feature(duration_as_u128)]
6 | #![feature(await_macro, futures_api, async_await)]
7 |
8 | extern crate serde;
9 | #[macro_use]
10 | extern crate serde_derive;
11 | #[macro_use]
12 | extern crate serde_json;
13 | extern crate serde_millis;
14 | #[macro_use]
15 | extern crate runtime_fmt;
16 | #[macro_use]
17 | extern crate lazy_static;
18 | #[macro_use]
19 | extern crate ethereum_types;
20 | #[macro_use]
21 | extern crate cryptocurrency_kit;
22 | #[macro_use]
23 | extern crate actix;
24 | extern crate actix_broker;
25 | #[macro_use]
26 | extern crate crossbeam;
27 | #[macro_use]
28 | extern crate log;
29 | extern crate env_logger;
30 | #[macro_use]
31 | extern crate failure;
32 |
33 | pub mod common;
34 | pub mod util;
35 | pub mod consensus;
36 | pub mod types;
37 | pub mod store;
38 | pub mod core;
39 | pub mod protocol;
40 | pub mod p2p;
41 | pub mod error;
42 | pub mod pprof;
43 | #[macro_use]
44 | pub mod subscriber;
45 | pub mod minner;
46 | pub mod cmd;
47 | pub mod config;
48 | pub mod logger;
49 | pub mod mocks;
50 | pub mod api;
--------------------------------------------------------------------------------
/src/logger/mod.rs:
--------------------------------------------------------------------------------
1 | use log::Level;
2 |
3 | pub fn init_log() {
4 | env_logger::init();
5 | info!("👊 logger init successfully");
6 | }
7 |
8 | pub (crate) fn init_test_env_log() {
9 | use std::env;
10 | use env_logger::{Builder, Target};
11 |
12 | env::set_var("RUST_LOG", "trace");
13 | let mut builder = Builder::from_default_env();
14 | builder.target(Target::Stdout);
15 | builder.init();
16 | }
--------------------------------------------------------------------------------
/src/minner/mod.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 |
3 | use crossbeam::scope;
4 | use ::actix::prelude::*;
5 | use actix_broker::{BrokerSubscribe, BrokerIssue};
6 | use parking_lot::RwLock;
7 | use crossbeam::{Sender, Receiver, channel::bounded};
8 | use rand::random;
9 | use cryptocurrency_kit::ethkey::{Address, KeyPair};
10 | use cryptocurrency_kit::storage::values::StorageValue;
11 | use cryptocurrency_kit::crypto::Hash;
12 | use cryptocurrency_kit::crypto::CryptoHash;
13 | use cryptocurrency_kit::crypto::hash;
14 | use tokio_threadpool;
15 | use futures::*;
16 | use futures::sync::oneshot;
17 |
18 | use crate::{
19 | subscriber::events::ChainEvent,
20 | core::chain::Chain,
21 | core::tx_pool::{TxPool, SafeTxPool},
22 | consensus::consensus::{Engine, SafeEngine},
23 | types::{Height, Timestamp},
24 | types::block::{Block, Header},
25 | types::transaction::{Transaction, merkle_root_transactions},
26 | };
27 |
28 | pub struct Minner {
29 | minter: Address,
30 | key_pair: KeyPair,
31 | chain: Arc,
32 | txpool: Arc>,
33 | engine: Box,
34 | seal_tx: Sender<()>,
35 | seal_rx: Receiver<()>,
36 | mint_height: Height,
37 | worker: tokio_threadpool::ThreadPool,
38 | }
39 |
40 | impl Actor for Minner {
41 | type Context = Context;
42 |
43 | fn started(&mut self, ctx: &mut Self::Context) {
44 | self.subscribe_async::(ctx);
45 | info!("Start minner actor");
46 | self.chain.post_event(ChainEvent::SyncBlock(self.chain.get_last_height() + 1));
47 | self.mine(self.seal_rx.clone());
48 | }
49 |
50 | fn stopped(&mut self, _ctx: &mut Self::Context) {
51 | info!("Minner actor has stoppped");
52 | }
53 | }
54 |
55 |
56 | impl Handler for Minner {
57 | type Result = ();
58 | fn handle(&mut self, msg: ChainEvent, _ctx: &mut Self::Context) -> Self::Result {
59 | match msg {
60 | ChainEvent::NewHeader(last_header) => {
61 | debug!("Receive a new header event notify, hash:{:?}, height: {:?}", last_header.block_hash(), last_header.height);
62 | if last_header.height >= self.mint_height {
63 | // stop current consensus
64 | self.seal_tx.send(()).unwrap();
65 | let seal = self.seal_rx.clone();
66 | self.mine(seal);
67 | }
68 | }
69 | _ => {}
70 | }
71 | }
72 | }
73 |
74 | impl Minner {
75 | pub fn new(minter: Address,
76 | key_pair: KeyPair,
77 | chain: Arc,
78 | txpool: Arc>,
79 | engine: SafeEngine,
80 | tx: Sender<()>,
81 | rx: Receiver<()>) -> Self {
82 | Minner {
83 | minter,
84 | key_pair,
85 | chain,
86 | txpool,
87 | engine,
88 | seal_tx: tx,
89 | seal_rx: rx,
90 | mint_height: 0,
91 | worker: tokio_threadpool::ThreadPool::new(),
92 | }
93 | }
94 |
95 | fn mine(&mut self, abort: Receiver<()>) {
96 | debug!("Ready to mine next block");
97 | let mut block = self.packet_next_block();
98 | self.mint_height = block.height();
99 | match self.engine.seal(&mut block, abort) {
100 | Ok(_) => {}
101 | Err(err) => {
102 | error!("Failed to seal consensus, err: {:?}", err);
103 | }
104 | }
105 | }
106 |
107 | fn packet_next_block(&self) -> Block {
108 | let (next_time, pre_header) = self.next_block();
109 | let coinbase = self.coinbase_transaction();
110 | // let mut mock_transactions = generate_batch_transactions(self.key_pair.secret(), self.minter, self.chain.config.chain_id, 200);
111 | // mock_transactions.push(coinbase);
112 |
113 | let pre_hash: Hash = pre_header.block_hash();
114 | let tx_hash = merkle_root_transactions(vec![coinbase.clone()]);
115 | let extra = Vec::from("Coinse base");
116 |
117 | let mut header = Header::new_mock(pre_hash, self.minter, tx_hash, pre_header.height + 1, next_time, Some(extra));
118 | header.cache_hash(None);
119 | Block::new(header, vec![coinbase])
120 | }
121 |
122 | fn coinbase_transaction(&self) -> Transaction {
123 | let nonce: u64 = random();
124 | let to = self.minter;
125 | let amount = random::();
126 | let gas_limit = random::();
127 | let gas_price = 1_u64;
128 | let payload = Vec::from(chrono::Local::now().to_string());
129 |
130 | let mut transaction = Transaction::new(nonce, to, amount, gas_limit, gas_price, payload);
131 | transaction.sign(self.chain.config.chain_id, &self.key_pair.secret());
132 | transaction
133 | }
134 |
135 | fn next_block(&self) -> (u64, Header) {
136 | let pre_block = self.chain.get_last_block();
137 | let pre_header = pre_block.header();
138 | let pre_timestamp = pre_header.time;
139 | let next_timestamp = pre_timestamp + self.chain.config.block_period.as_secs();
140 | let now_timestamp = chrono::Local::now().timestamp() as u64;
141 | trace!("now timestamp: {}, pre_timestamp: {}, next_timestamp: {}", now_timestamp, pre_timestamp, next_timestamp);
142 | if now_timestamp > next_timestamp {
143 | return (now_timestamp, pre_header.clone());
144 | }
145 | (next_timestamp, pre_header.clone())
146 | }
147 | }
148 |
149 | #[cfg(test)]
150 | mod test {
151 | use super::*;
152 | use cryptocurrency_kit::ethkey::{Random, Generator};
153 |
154 | #[test]
155 | fn t_basecoin() {
156 | let nonce: u64 = random();
157 | let to = Address::from(199);
158 | let amount = random::();
159 | let gas_limit = random::();
160 | let gas_price = 1_u64;
161 | let payload = Vec::from(chrono::Local::now().to_string());
162 |
163 | let mut transaction = Transaction::new(nonce, to, amount, gas_limit, gas_price, payload);
164 | transaction.sign(100, Random.generate().unwrap().secret());
165 |
166 | let coinbase = transaction;
167 | let tx_hash = merkle_root_transactions(vec![coinbase.clone()]);
168 | println!("coin base hash: {:?}", tx_hash);
169 | }
170 | }
--------------------------------------------------------------------------------
/src/mocks/mock_config.toml:
--------------------------------------------------------------------------------
1 | chain_id = 10
2 | ip = "127.0.0.1"
3 | port = 7691
4 | block_period = 3000 # ms
5 | request_time = 3000 # ms
6 | peer_id = "QmbBr2fHwLFKvHkAq1BpbEr4dvR8P6orQxHkVaxeJsJiW8"
7 | ttl = 3000
8 | store = "/tmp/block/c1"
9 | secret = "7f3b0a324e13e5358c3fd686737acd7adf2e5556084ec6d9e48b497082b7ef98"
10 |
11 | [genesis]
12 | validator = ["0x7193d8f91724b39f10cc81e94934c187fa257277", "0x93908f59c6eff007d228398349214acb6b4ac9a4", "0x72d5c75fd6703414aa87f79b3e4797dd09cd9251", "0x58096d35c7a8ff67eba159f33cea7740fc9a737c","0xc759616c865d349ec2afced268fc6f33ff7414a4"]
13 | epoch_time = 2018-09-09T09:09:09.09-09:09
14 | proposer = "0x5701fbd05e77cac003a6894e4b2a3c12287ed313"
15 | gas_used = 10000
16 | extra = "Hello Word!"
17 | [genesis.accounts]
18 | 0x5701fbd05e77cac003a6894e4b2a3c12287ed313 = 500000
19 | 0x6510f8d84c0b8b3091fc3abe2fdff6036c90865d = 500000
20 | 0x3140bda54df92f9453b487afdb3bcce02d154c74 = 500000
21 | 0x7035dafbeac1792ab5b7ed5c903ac63522eb534a = 500000
22 | 0x6730933a2cb6f26af786d7f5979efbdf29049c3a = 500000
23 | 0xfb1bbe89190c9793aec79713e35bdd82a7e5b08b = 500000
24 | 0x1f6f0d11339b5a0db7cef22ae278c15d55178faf = 500000
25 | 0x17f3309f405f53ae3e3c7e98533c58aa0c8c9417 = 500000
26 | 0x6e6e4a7aa7cedac4c4f3e8a7cd363e5f3208e8a6 = 500000
27 | 0x91b73cc738754c4fd7d6a2f0b6b354e293177c80 = 500000
--------------------------------------------------------------------------------
/src/mocks/mod.rs:
--------------------------------------------------------------------------------
1 | use toml::value::Value as Toml;
2 |
3 | use std::fs::{self, File};
4 | use std::env;
5 |
6 | use crate::config::Config;
7 |
8 | pub(crate) mod utils;
9 |
10 |
11 | pub(crate) fn t_config() -> Config {
12 | let s = env::current_dir().unwrap().to_string_lossy().to_string() + &"/src/mocks/mock_config.toml".to_owned();
13 | println!("--> {:?}", s);
14 | let config: Config = toml::from_str(&fs::read_to_string(s).unwrap()).unwrap();
15 | config
16 | }
17 |
18 | #[cfg(test)]
19 | mod test {
20 | use super::*;
21 |
22 | #[test]
23 | fn t_() {
24 | let config = t_config();
25 | println!("{:?}", config);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/mocks/utils.rs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/laohanlinux/consensus-rs/ebf0879ea3cf653c62ac79ff658c804a3bddcc6f/src/mocks/utils.rs
--------------------------------------------------------------------------------
/src/p2p/codec.rs:
--------------------------------------------------------------------------------
1 | use std::borrow::Cow;
2 | use std::io;
3 |
4 | use byteorder::{BigEndian, ByteOrder};
5 | use bytes::{BufMut, BytesMut};
6 | use cryptocurrency_kit::storage::values::StorageValue;
7 | use tokio::codec::{Decoder, Encoder};
8 |
9 | use super::protocol::*;
10 |
11 | pub const MAX_MSG_SIZE: u32 = 1 << 10;
12 | pub const MSG_SIZE: u32 = 4; // byte
13 |
14 | // |msg_size: 4bytes| msg encode |
15 | pub struct MsgPacketCodec;
16 |
17 | impl Decoder for MsgPacketCodec {
18 | type Item = RawMessage;
19 | type Error = io::Error;
20 |
21 | fn decode(&mut self, src: &mut BytesMut) -> Result