├── .gitignore ├── src ├── main.rs ├── block.rs ├── utxoset.rs ├── wallets.rs ├── blockchain.rs ├── transaction.rs ├── cli.rs └── server.rs ├── .github └── workflows │ └── main.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── document ├── part1.md └── part2.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /data -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | mod block; 4 | mod blockchain; 5 | mod cli; 6 | mod server; 7 | mod transaction; 8 | mod utxoset; 9 | mod wallets; 10 | 11 | #[macro_use] 12 | extern crate log; 13 | 14 | pub type Result = std::result::Result; 15 | 16 | use crate::cli::Cli; 17 | use env_logger::Env; 18 | 19 | fn main() { 20 | env_logger::from_env(Env::default().default_filter_or("warning")).init(); 21 | 22 | let mut cli = Cli::new(); 23 | if let Err(e) = cli.run() { 24 | println!("Error: {}", e); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | 22 | test: 23 | 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Run tests 29 | run: cargo test --verbose -- --test-threads=1 30 | - name: Check code format 31 | run: cargo fmt --all -- --check 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blockchain-demo" 3 | version = "0.1.0" 4 | authors = ["yunwei37 <1067852565@qq.com>"] 5 | description = "A simplified blockchain implementation in rust for leaning" 6 | edition = "2018" 7 | readme = "README.md" 8 | homepage = "https://github.com/yunwei37/blockchain-rust" 9 | repository = "https://github.com/yunwei37/blockchain-rust" 10 | license = "MIT" 11 | keywords = ["blockchain", "demo"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | 17 | sha2 = "0.9" 18 | rust-crypto = "^0.2" 19 | bincode = "1.3" 20 | failure = "0.1" 21 | sled = "0.34" 22 | serde = {version ="1.0", features =["derive"]} 23 | log = "0.4" 24 | env_logger = "0.7.1" 25 | clap = "~2.33" 26 | bitcoincash-addr = "0.5.2" 27 | rand = "0.4.6" 28 | merkle-cbt = "0.2.2" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 云微 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blockchain-rust - 用 rust 从零开始构建区块链(Bitcoin)系列 2 | 3 | [![Actions Status](https://github.com/yunwei37/blockchain-demo/workflows/CI/badge.svg)](https://github.com/yunwei37/blockchain-demo/actions) 4 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 5 | 6 | reimplement `blockchain_go` in rust, and not only blockchain_go; 7 | 8 | a simple blockchain demo for learning 9 | 10 | ## the code for each article 11 | 12 | 1. part1: Basic Prototype `基本原型` [commit bd0efe7](https://github.com/yunwei37/blockchain-rust/tree/bd0efe7f4105a3daafd9311d3dd643482b63cb84) 13 | 2. part2: Proof-of-Work `工作量证明` [commit 9d9370a](https://github.com/yunwei37/blockchain-rust/tree/9d9370aa22af34244659034918f2aad4a2cb96d2) 14 | 3. part3: Persistence and CLI` 持久化、命令行、日志` [commit e2094c0](https://github.com/yunwei37/blockchain-rust/tree/e2094c0ef94fadc4e01030312a1ad890ec633d6f) 15 | 4. part4: Transactions 1 `交易(1)` [commit bdbdcec](https://github.com/yunwei37/blockchain-rust/tree/bdbdcec8b79d5ff701d207f67a1b68849a35d865) 16 | 5. part5: Addresses `地址和签名` [commit 440cba2](https://github.com/yunwei37/blockchain-rust/tree/440cba230cbd81957c3285b21c705a5708ed2b5b) 17 | 6. part6: Transactions 2 `交易(2)` [commit 4912743](https://github.com/yunwei37/blockchain-rust/tree/4912743daa2699cb8c0c4ba5bf136534b272cecd) 18 | 7. part7: Network `网络和分布式一致性算法` master 19 | 20 | ## Chinese Documents 21 | 22 | - 基本原型和工作量证明算法: [part1.md](document/part1.md) 23 | 24 | ## usage 25 | 26 | - Create wallet: 27 | ```sh 28 | cargo run createwallet 29 | ``` 30 | - Create blockchain: 31 | ``` 32 | cargo run createblockchain
33 | ``` 34 | - send coins (if `-m` is specified, the block will be mined immediately in the same node): 35 | ``` 36 | cargo run send -m 37 | ``` 38 | - start server: 39 | ``` 40 | cargo run startnode 41 | ``` 42 | or start miner node: 43 | ``` 44 | cargo run startminer
45 | ``` 46 | - get balance: 47 | ``` 48 | cargo run getbalance
49 | ``` 50 | 51 | You can use the `RUST_LOG=info` to print the log. 52 | 53 | ## reference 54 | 55 | - `blockchain_go` code: [https://github.com/Jeiwan/blockchain_go](https://github.com/Jeiwan/blockchain_go) 56 | - Build a cryptocurrency! - Blockchain in Rust: [https://github.com/GeekLaunch/blockchain-rust](https://github.com/GeekLaunch/blockchain-rust) 57 | - 中文版文档:[https://liuchengxu.gitbook.io/blockchain/](https://liuchengxu.gitbook.io/blockchain/) -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | //! Block implement of blockchain 2 | 3 | use super::*; 4 | use crate::transaction::Transaction; 5 | use bincode::serialize; 6 | use crypto::digest::Digest; 7 | use crypto::sha2::Sha256; 8 | use merkle_cbt::merkle_tree::Merge; 9 | use merkle_cbt::merkle_tree::CBMT; 10 | use serde::{Deserialize, Serialize}; 11 | use std::time::SystemTime; 12 | 13 | const TARGET_HEXS: usize = 4; 14 | 15 | /// Block keeps block headers 16 | #[derive(Serialize, Deserialize, Debug, Clone)] 17 | pub struct Block { 18 | timestamp: u128, 19 | transactions: Vec, 20 | prev_block_hash: String, 21 | hash: String, 22 | nonce: i32, 23 | height: i32, 24 | } 25 | 26 | impl Block { 27 | pub fn get_hash(&self) -> String { 28 | self.hash.clone() 29 | } 30 | 31 | pub fn get_prev_hash(&self) -> String { 32 | self.prev_block_hash.clone() 33 | } 34 | 35 | pub fn get_transaction(&self) -> &Vec { 36 | &self.transactions 37 | } 38 | 39 | pub fn get_height(&self) -> i32 { 40 | self.height 41 | } 42 | 43 | /// NewBlock creates and returns Block 44 | pub fn new_block( 45 | transactions: Vec, 46 | prev_block_hash: String, 47 | height: i32, 48 | ) -> Result { 49 | let timestamp = SystemTime::now() 50 | .duration_since(SystemTime::UNIX_EPOCH)? 51 | .as_millis(); 52 | let mut block = Block { 53 | timestamp, 54 | transactions, 55 | prev_block_hash, 56 | hash: String::new(), 57 | nonce: 0, 58 | height, 59 | }; 60 | block.run_proof_of_work()?; 61 | Ok(block) 62 | } 63 | 64 | /// NewGenesisBlock creates and returns genesis Block 65 | pub fn new_genesis_block(coinbase: Transaction) -> Block { 66 | Block::new_block(vec![coinbase], String::new(), 0).unwrap() 67 | } 68 | 69 | /// Run performs a proof-of-work 70 | fn run_proof_of_work(&mut self) -> Result<()> { 71 | info!("Mining the block"); 72 | while !self.validate()? { 73 | self.nonce += 1; 74 | } 75 | let data = self.prepare_hash_data()?; 76 | let mut hasher = Sha256::new(); 77 | hasher.input(&data[..]); 78 | self.hash = hasher.result_str(); 79 | Ok(()) 80 | } 81 | 82 | /// HashTransactions returns a hash of the transactions in the block 83 | fn hash_transactions(&self) -> Result> { 84 | let mut transactions = Vec::new(); 85 | for tx in &self.transactions { 86 | transactions.push(tx.hash()?.as_bytes().to_owned()); 87 | } 88 | let tree = CBMT::, MergeVu8>::build_merkle_tree(transactions); 89 | 90 | Ok(tree.root()) 91 | } 92 | 93 | fn prepare_hash_data(&self) -> Result> { 94 | let content = ( 95 | self.prev_block_hash.clone(), 96 | self.hash_transactions()?, 97 | self.timestamp, 98 | TARGET_HEXS, 99 | self.nonce, 100 | ); 101 | let bytes = serialize(&content)?; 102 | Ok(bytes) 103 | } 104 | 105 | /// Validate validates block's PoW 106 | fn validate(&self) -> Result { 107 | let data = self.prepare_hash_data()?; 108 | let mut hasher = Sha256::new(); 109 | hasher.input(&data[..]); 110 | let mut vec1: Vec = Vec::new(); 111 | vec1.resize(TARGET_HEXS, '0' as u8); 112 | Ok(&hasher.result_str()[0..TARGET_HEXS] == String::from_utf8(vec1)?) 113 | } 114 | } 115 | 116 | struct MergeVu8 {} 117 | 118 | impl Merge for MergeVu8 { 119 | type Item = Vec; 120 | fn merge(left: &Self::Item, right: &Self::Item) -> Self::Item { 121 | let mut hasher = Sha256::new(); 122 | let mut data: Vec = left.clone(); 123 | data.append(&mut right.clone()); 124 | hasher.input(&data); 125 | let mut re: [u8; 32] = [0; 32]; 126 | hasher.result(&mut re); 127 | re.to_vec() 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/utxoset.rs: -------------------------------------------------------------------------------- 1 | //! unspend transaction output set 2 | 3 | use super::*; 4 | use crate::block::*; 5 | use crate::blockchain::*; 6 | use crate::transaction::*; 7 | use bincode::{deserialize, serialize}; 8 | use sled; 9 | use std::collections::HashMap; 10 | 11 | /// UTXOSet represents UTXO set 12 | pub struct UTXOSet { 13 | pub blockchain: Blockchain, 14 | } 15 | 16 | impl UTXOSet { 17 | /// FindUnspentTransactions returns a list of transactions containing unspent outputs 18 | pub fn find_spendable_outputs( 19 | &self, 20 | pub_key_hash: &[u8], 21 | amount: i32, 22 | ) -> Result<(i32, HashMap>)> { 23 | let mut unspent_outputs: HashMap> = HashMap::new(); 24 | let mut accumulated = 0; 25 | 26 | let db = sled::open("data/utxos")?; 27 | for kv in db.iter() { 28 | let (k, v) = kv?; 29 | let txid = String::from_utf8(k.to_vec())?; 30 | let outs: TXOutputs = deserialize(&v.to_vec())?; 31 | 32 | for out_idx in 0..outs.outputs.len() { 33 | if outs.outputs[out_idx].is_locked_with_key(pub_key_hash) && accumulated < amount { 34 | accumulated += outs.outputs[out_idx].value; 35 | match unspent_outputs.get_mut(&txid) { 36 | Some(v) => v.push(out_idx as i32), 37 | None => { 38 | unspent_outputs.insert(txid.clone(), vec![out_idx as i32]); 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | Ok((accumulated, unspent_outputs)) 46 | } 47 | 48 | /// FindUTXO finds UTXO for a public key hash 49 | pub fn find_UTXO(&self, pub_key_hash: &[u8]) -> Result { 50 | let mut utxos = TXOutputs { 51 | outputs: Vec::new(), 52 | }; 53 | let db = sled::open("data/utxos")?; 54 | 55 | for kv in db.iter() { 56 | let (_, v) = kv?; 57 | let outs: TXOutputs = deserialize(&v.to_vec())?; 58 | 59 | for out in outs.outputs { 60 | if out.is_locked_with_key(pub_key_hash) { 61 | utxos.outputs.push(out.clone()) 62 | } 63 | } 64 | } 65 | 66 | Ok(utxos) 67 | } 68 | 69 | /// CountTransactions returns the number of transactions in the UTXO set 70 | pub fn count_transactions(&self) -> Result { 71 | let mut counter = 0; 72 | let db = sled::open("data/utxos")?; 73 | for kv in db.iter() { 74 | kv?; 75 | counter += 1; 76 | } 77 | Ok(counter) 78 | } 79 | 80 | /// Reindex rebuilds the UTXO set 81 | pub fn reindex(&self) -> Result<()> { 82 | std::fs::remove_dir_all("data/utxos").ok(); 83 | let db = sled::open("data/utxos")?; 84 | 85 | let utxos = self.blockchain.find_UTXO(); 86 | 87 | for (txid, outs) in utxos { 88 | db.insert(txid.as_bytes(), serialize(&outs)?)?; 89 | } 90 | 91 | Ok(()) 92 | } 93 | 94 | /// Update updates the UTXO set with transactions from the Block 95 | /// 96 | /// The Block is considered to be the tip of a blockchain 97 | pub fn update(&self, block: &Block) -> Result<()> { 98 | let db = sled::open("data/utxos")?; 99 | 100 | for tx in block.get_transaction() { 101 | if !tx.is_coinbase() { 102 | for vin in &tx.vin { 103 | let mut update_outputs = TXOutputs { 104 | outputs: Vec::new(), 105 | }; 106 | let outs: TXOutputs = deserialize(&db.get(&vin.txid)?.unwrap().to_vec())?; 107 | for out_idx in 0..outs.outputs.len() { 108 | if out_idx != vin.vout as usize { 109 | update_outputs.outputs.push(outs.outputs[out_idx].clone()); 110 | } 111 | } 112 | 113 | if update_outputs.outputs.is_empty() { 114 | db.remove(&vin.txid)?; 115 | } else { 116 | db.insert(vin.txid.as_bytes(), serialize(&update_outputs)?)?; 117 | } 118 | } 119 | } 120 | 121 | let mut new_outputs = TXOutputs { 122 | outputs: Vec::new(), 123 | }; 124 | for out in &tx.vout { 125 | new_outputs.outputs.push(out.clone()); 126 | } 127 | 128 | db.insert(tx.id.as_bytes(), serialize(&new_outputs)?)?; 129 | } 130 | Ok(()) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/wallets.rs: -------------------------------------------------------------------------------- 1 | //! bitcoin wallet 2 | 3 | use super::*; 4 | use bincode::{deserialize, serialize}; 5 | use bitcoincash_addr::*; 6 | use crypto::digest::Digest; 7 | use crypto::ed25519; 8 | use crypto::ripemd160::Ripemd160; 9 | use crypto::sha2::Sha256; 10 | use rand::Rng; 11 | use serde::{Deserialize, Serialize}; 12 | use sled; 13 | use std::collections::HashMap; 14 | 15 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 16 | pub struct Wallet { 17 | pub secret_key: Vec, 18 | pub public_key: Vec, 19 | } 20 | 21 | impl Wallet { 22 | /// NewWallet creates and returns a Wallet 23 | fn new() -> Self { 24 | let mut key: [u8; 32] = [0; 32]; 25 | let mut rand = rand::OsRng::new().unwrap(); 26 | rand.fill_bytes(&mut key); 27 | let (secret_key, public_key) = ed25519::keypair(&key); 28 | let secret_key = secret_key.to_vec(); 29 | let public_key = public_key.to_vec(); 30 | Wallet { 31 | secret_key, 32 | public_key, 33 | } 34 | } 35 | 36 | /// GetAddress returns wallet address 37 | pub fn get_address(&self) -> String { 38 | let mut pub_hash: Vec = self.public_key.clone(); 39 | hash_pub_key(&mut pub_hash); 40 | let address = Address { 41 | body: pub_hash, 42 | scheme: Scheme::Base58, 43 | hash_type: HashType::Script, 44 | ..Default::default() 45 | }; 46 | address.encode().unwrap() 47 | } 48 | } 49 | 50 | /// HashPubKey hashes public key 51 | pub fn hash_pub_key(pubKey: &mut Vec) { 52 | let mut hasher1 = Sha256::new(); 53 | hasher1.input(pubKey); 54 | hasher1.result(pubKey); 55 | let mut hasher2 = Ripemd160::new(); 56 | hasher2.input(pubKey); 57 | pubKey.resize(20, 0); 58 | hasher2.result(pubKey); 59 | } 60 | 61 | pub struct Wallets { 62 | wallets: HashMap, 63 | } 64 | 65 | impl Wallets { 66 | /// NewWallets creates Wallets and fills it from a file if it exists 67 | pub fn new() -> Result { 68 | let mut wlt = Wallets { 69 | wallets: HashMap::::new(), 70 | }; 71 | let db = sled::open("data/wallets")?; 72 | 73 | for item in db.into_iter() { 74 | let i = item?; 75 | let address = String::from_utf8(i.0.to_vec())?; 76 | let wallet = deserialize(&i.1.to_vec())?; 77 | wlt.wallets.insert(address, wallet); 78 | } 79 | drop(db); 80 | Ok(wlt) 81 | } 82 | 83 | /// CreateWallet adds a Wallet to Wallets 84 | pub fn create_wallet(&mut self) -> String { 85 | let wallet = Wallet::new(); 86 | let address = wallet.get_address(); 87 | self.wallets.insert(address.clone(), wallet); 88 | info!("create wallet: {}", address); 89 | address 90 | } 91 | 92 | /// GetAddresses returns an array of addresses stored in the wallet file 93 | pub fn get_all_addresses(&self) -> Vec { 94 | let mut addresses = Vec::::new(); 95 | for (address, _) in &self.wallets { 96 | addresses.push(address.clone()); 97 | } 98 | addresses 99 | } 100 | 101 | /// GetWallet returns a Wallet by its address 102 | pub fn get_wallet(&self, address: &str) -> Option<&Wallet> { 103 | self.wallets.get(address) 104 | } 105 | 106 | /// SaveToFile saves wallets to a file 107 | pub fn save_all(&self) -> Result<()> { 108 | let db = sled::open("data/wallets")?; 109 | 110 | for (address, wallet) in &self.wallets { 111 | let data = serialize(wallet)?; 112 | db.insert(address, data)?; 113 | } 114 | 115 | db.flush()?; 116 | drop(db); 117 | Ok(()) 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod test { 123 | use super::*; 124 | 125 | #[test] 126 | fn test_create_wallet_and_hash() { 127 | let w1 = Wallet::new(); 128 | let w2 = Wallet::new(); 129 | assert_ne!(w1, w2); 130 | assert_ne!(w1.get_address(), w2.get_address()); 131 | 132 | let mut p2 = w2.public_key.clone(); 133 | hash_pub_key(&mut p2); 134 | assert_eq!(p2.len(), 20); 135 | let pub_key_hash = Address::decode(&w2.get_address()).unwrap().body; 136 | assert_eq!(pub_key_hash, p2); 137 | } 138 | 139 | #[test] 140 | fn test_wallets() { 141 | let mut ws = Wallets::new().unwrap(); 142 | let wa1 = ws.create_wallet(); 143 | let w1 = ws.get_wallet(&wa1).unwrap().clone(); 144 | ws.save_all().unwrap(); 145 | 146 | let ws2 = Wallets::new().unwrap(); 147 | let w2 = ws2.get_wallet(&wa1).unwrap(); 148 | assert_eq!(&w1, w2); 149 | } 150 | 151 | #[test] 152 | #[should_panic] 153 | fn test_wallets_not_exist() { 154 | let w3 = Wallet::new(); 155 | let ws2 = Wallets::new().unwrap(); 156 | ws2.get_wallet(&w3.get_address()).unwrap(); 157 | } 158 | 159 | #[test] 160 | fn test_signature() { 161 | let w = Wallet::new(); 162 | let signature = ed25519::signature("test".as_bytes(), &w.secret_key); 163 | assert!(ed25519::verify( 164 | "test".as_bytes(), 165 | &w.public_key, 166 | &signature 167 | )); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /document/part1.md: -------------------------------------------------------------------------------- 1 | #! https://zhuanlan.zhihu.com/p/237854049 2 | # 用 rust 从零开始构建区块链(Bitcoin)系列 - 基本原型和工作量证明算法 3 | 4 | Github链接,包含文档和全部代码:[https://github.com/yunwei37/blockchain-rust](https://github.com/yunwei37/blockchain-rust) 5 | 6 | 区块链是 21 世纪最具革命性的技术之一,尽管区块链的热潮已经褪去,但不可否认的是它确实有其存在的意义和价值:区块链的本质是一个分布式记账和存储系统,一种无法被篡改的数据结构,它也应当会成为未来在金融和政治方面的某种信息基础设施之一。当然,肯定和各种空气币无关;目前我们所提出来的大多数应用也只是一个比较的设想而已,它的应用并没有那么广泛。 7 | 8 | 作为一个强调安全性的语言,Rust 和区块链的应用方向是十分契合的,也可以看到越来越多的区块链项目使用 Rust 进行开发。本文将使用 Rust 构建一个简单的区块链模型,并基于它来构建一个简化版的加密货币(参考比特币的实现),以供学习之用。大部分设计参考自Github上面使用Go语言完成的另外一个项目:blockchain_go,和 go 相比,Rust 的开发效率并不会低太多,但安全性和运行速度还是要好不少的。另外,得益于 Rust 在区块链相关部分的社区看起来还是挺繁荣的(?),我们很多简单的组件也可以采用开源库代替,例如地址生成等等。 9 | 10 | >本文不会涉及太多的区块链基础知识,具体可以参考原文的中文翻译:[liuchengxu.gitbook.io/blockchain/](https://liuchengxu.gitbook.io/blockchain/) 11 | >阅读本文需要您对 rust 事先有一定了解 12 | 13 | 第一篇文章希望讨论区块链最基本的数据结构原型,以及挖矿的本质 ---- 工作量证明算法 14 | 15 | ## 基本原型 16 | 17 | 本阶段的代码实现在这里:[github.com/yunwei37/blockchain-rust/tree/bd0efe7f4105a3daafd9311d3dd643482b63cb84](https://github.com/yunwei37/blockchain-rust/tree/bd0efe7f4105a3daafd9311d3dd643482b63cb84) 18 | 19 | 区块链本质是一个很简单的概念。 20 | 21 | 在区块链中,真正存储有效信息的是区块(block),这里是个简单的定义: 22 | 23 | ```rs 24 | /// Block keeps block headers 25 | #[derive(Debug)] 26 | pub struct Block { 27 | timestamp: u128, 28 | data: String, 29 | prev_block_hash: String, 30 | hash: String, 31 | } 32 | ``` 33 | 34 | 通过在每个区块中间保存上一个区块的哈希值,就可以保证链条中间的某一个区块不会被篡改; 35 | 36 | 参数: 37 | 38 | - Timestamp 当前时间戳,也就是区块创建的时间 39 | - data 区块存储的实际有效信息,也就是交易 40 | - prev_block_hash 前一个块的哈希,即父哈希 41 | - hash 当前块的哈希 42 | 43 | 可以通过调用 `set_hash` 方法来计算区块哈希值,这里采用 SHA-256 算法进行:取 Block 结构的部分字段(Timestamp, Data 和 PrevBlockHash),并将它们相互拼接起来,然后在拼接后的结果上计算一个 SHA-256 值,并赋值给 hash 字段: 44 | 45 | `block.rs`: 46 | 47 | ```rs 48 | impl Block { 49 | /// SetHash calculates and sets block hash 50 | pub fn set_hash(&mut self) -> Result<()> { 51 | self.timestamp = SystemTime::now() 52 | .duration_since(SystemTime::UNIX_EPOCH)? 53 | .as_millis(); 54 | let content = (self.data.clone(),self.prev_block_hash.clone(), self.timestamp); 55 | let bytes = serialize(&content)?; 56 | let mut hasher = Sha256::new(); 57 | hasher.input(&bytes[..]); 58 | self.hash = hasher.result_str(); 59 | Ok(()) 60 | } 61 | } 62 | ``` 63 | 64 | 我们需要一个方法来封装创建一个块: 65 | 66 | `block.rs`: 67 | 68 | ```rs 69 | impl Block { 70 | /// NewBlock creates and returns Block 71 | pub fn new_block(data: String, prev_block_hash: String) -> Result { 72 | let mut block = Block { 73 | timestamp: 0, 74 | data, 75 | prev_block_hash, 76 | hash: String::new(), 77 | }; 78 | block.set_hash()?; 79 | Ok(block) 80 | } 81 | } 82 | ``` 83 | 84 | 有了区块,接下来可以看一下区块链:本质上来说:区块链就是一个有着特定结构的数据库,是一个有序,每一个块都连接到前一个块的链表,只是采用哈希值取代了指针进行链接。 85 | 86 | 这里可以采用 vec 进行存储 87 | 88 | `blockchain.rs`: 89 | 90 | ```rs 91 | /// Blockchain keeps a sequence of Blocks 92 | #[derive(Debug)] 93 | pub struct Blockchain { 94 | blocks: Vec, 95 | } 96 | ``` 97 | 98 | 接下来看看如何添加一个区块: 99 | 100 | `blockchain.rs`: 101 | 102 | ```rs 103 | impl Blockchain { 104 | /// AddBlock saves provided data as a block in the blockchain 105 | pub fn add_block(&mut self, data: String) -> Result<()> { 106 | let prev = self.blocks.last().unwrap(); 107 | let newblock = Block::new_block(data, prev.get_hash())?; 108 | self.blocks.push(newblock); 109 | Ok(()) 110 | } 111 | } 112 | ``` 113 | 114 | 好啦!基本工作就这些!不过还有一点,为了加入一个新的块,我们需要有一个已有的块,在初始情况下就需要一个创世块: 115 | 116 | `block.rs`: 117 | 118 | ```rs 119 | impl Block { 120 | /// NewGenesisBlock creates and returns genesis Block 121 | pub fn new_genesis_block() -> Block { 122 | Block::new_block(String::from("Genesis Block"), String::new()).unwrap() 123 | } 124 | } 125 | ``` 126 | 127 | 创建一个区块链的函数接口: 128 | 129 | `blockchain.rs`: 130 | 131 | ```rs 132 | impl Blockchain { 133 | /// NewBlockchain creates a new Blockchain with genesis Block 134 | pub fn new() -> Blockchain { 135 | Blockchain { 136 | blocks: vec![Block::new_genesis_block()], 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | 结束! 143 | 144 | ## 工作量证明: 145 | 146 | 上面那个是单机版的,从分布式系统的角度上来讲,每个主机都可能自行往区块链中添加区块,如何协调一致性和保证系统不会被攻击就是一个大问题。 147 | 148 | 比特币采用的是 PoW 算法,要让一个人必须经过一系列困难的工作,才能将数据放入到区块链中,完成这个工作的人,也会获得相应奖励(这也就是通过挖矿获得币)。并不只有这个算法有效,具体关于分布式的内容,可以参考后面的网络部分。 149 | 150 | 这里具体的算法是 Hashcash,可以参考维基:[https://en.wikipedia.org/wiki/Hashcash](https://en.wikipedia.org/wiki/Hashcash) 151 | 152 | 步骤: 153 | 154 | 1. 取一些公开的数据(比如,如果是 email 的话,它可以是接收者的邮件地址;在比特币中,它是区块头) 155 | 2. 给这个公开数据添加一个计数器。计数器默认从 0 开始 156 | 3. 将 data(数据) 和 counter(计数器) 组合到一起,获得一个哈希 157 | 4. 检查哈希是否符合一定的条件: 158 | - 如果符合条件,结束 159 | - 如果不符合,增加计数器,重复步骤 3-4 160 | 161 | 我们继续在 `block.rs` 中写代码: 162 | 163 | 首先,在 block 里面增加一个计数器: 164 | 165 | ```rs 166 | /// Block keeps block headers 167 | pub struct Block { 168 | ... 169 | nonce: i32, 170 | } 171 | ``` 172 | 173 | 写个辅助函数,获取需要被哈希的数据序列值: 174 | 175 | ```rs 176 | impl Block { 177 | fn prepare_hash_data(&self) -> Result> { 178 | let content = ( 179 | self.prev_block_hash.clone(), 180 | self.data.clone(), 181 | self.timestamp, 182 | TARGET_HEXS, 183 | self.nonce, 184 | ); 185 | let bytes = serialize(&content)?; 186 | Ok(bytes) 187 | } 188 | } 189 | ``` 190 | 191 | 然后,判断当前的哈希值是否满足要求: 192 | 193 | ```rs 194 | const TARGET_HEXS: usize = 4; 195 | 196 | impl Block { 197 | /// Validate validates block's PoW 198 | fn validate(&self) -> Result { 199 | let data = self.prepare_hash_data()?; 200 | let mut hasher = Sha256::new(); 201 | hasher.input(&data[..]); 202 | let mut vec1: Vec = Vec::new(); 203 | vec1.resize(TARGET_HEXS, '0' as u8); 204 | Ok(&hasher.result_str()[0..TARGET_HEXS] == String::from_utf8(vec1)?) 205 | } 206 | } 207 | ``` 208 | 209 | 然后,就可以跑算法啦: 210 | 211 | ```rs 212 | impl Block { 213 | /// Run performs a proof-of-work 214 | fn run_proof_of_work(&mut self) -> Result<()> { 215 | println!("Mining the block containing \"{}\"\n", self.data); 216 | while !self.validate()? { 217 | self.nonce += 1; 218 | } 219 | let data = self.prepare_hash_data()?; 220 | let mut hasher = Sha256::new(); 221 | hasher.input(&data[..]); 222 | self.hash = hasher.result_str(); 223 | Ok(()) 224 | } 225 | } 226 | ``` 227 | 228 | 这样我们就完成了工作量证明也就是挖矿的程序啦! 229 | 230 | 在 main 里面写个测试程序,我们可以用 Debug 宏打印区块链: 231 | 232 | ```rs 233 | fn main() -> Result<()> { 234 | let mut bc = Blockchain::new(); 235 | sleep(Duration::from_millis(10)); 236 | bc.add_block(String::from("Send 1 BTC to Ivan"))?; 237 | sleep(Duration::from_millis(30)); 238 | bc.add_block(String::from("Send 2 more BTC to Ivan"))?; 239 | 240 | println!("Blockchain: {:#?}", bc); 241 | Ok(()) 242 | } 243 | ``` 244 | 245 | 输出: 246 | 247 | ```json 248 | Mining the block containing "Genesis Block" 249 | 250 | Mining the block containing "Send 1 BTC to Ivan" 251 | 252 | Mining the block containing "Send 2 more BTC to Ivan" 253 | 254 | Blockchain: Blockchain { 255 | blocks: [ 256 | Block { 257 | timestamp: 1599905545625, 258 | data: "Genesis Block", 259 | prev_block_hash: "", 260 | hash: "0000f81cad3bda84526e742a2931bd94ac689c3795ee2da713f8e3bf5d6b461a", 261 | nonce: 47246, 262 | }, 263 | Block { 264 | timestamp: 1599905546544, 265 | data: "Send 1 BTC to Ivan", 266 | prev_block_hash: "0000f81cad3bda84526e742a2931bd94ac689c3795ee2da713f8e3bf5d6b461a", 267 | hash: "00008e9348e0500ff0324bbc0b861f5a01033ac317a12b28987675b5906bf03e", 268 | nonce: 31604, 269 | }, 270 | Block { 271 | timestamp: 1599905547428, 272 | data: "Send 2 more BTC to Ivan", 273 | prev_block_hash: "00008e9348e0500ff0324bbc0b861f5a01033ac317a12b28987675b5906bf03e", 274 | hash: "0000c6d5e4f800116be7551ba0afe01174eebadc6897edc9dc2090b6fb387096", 275 | nonce: 24834, 276 | }, 277 | ], 278 | } 279 | 280 | ``` 281 | 282 | ## 参考资料: 283 | 284 | - 源代码:[github.com/yunwei37/blockchain-rust](https://github.com/yunwei37/blockchain-rust) 285 | - Go 原版代码:[https://github.com/Jeiwan/blockchain_go/tree/part_2](https://github.com/Jeiwan/blockchain_go/tree/part_2) 286 | - 区块链理论学习入门指南:[daimajia.com/2017/08/24/how-to-start-blockchain-learning](https://daimajia.com/2017/08/24/how-to-start-blockchain-learning) 287 | - <<区块链技术指南>>: [yeasy.gitbooks.io/blockchain_guide/content](https://yeasy.gitbooks.io/blockchain_guide/content) -------------------------------------------------------------------------------- /src/blockchain.rs: -------------------------------------------------------------------------------- 1 | //! Blockchain 2 | 3 | use super::*; 4 | use crate::block::*; 5 | use crate::transaction::*; 6 | use bincode::{deserialize, serialize}; 7 | use failure::format_err; 8 | use sled; 9 | use std::collections::HashMap; 10 | 11 | const GENESIS_COINBASE_DATA: &str = 12 | "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"; 13 | 14 | /// Blockchain implements interactions with a DB 15 | #[derive(Debug)] 16 | pub struct Blockchain { 17 | pub tip: String, 18 | pub db: sled::Db, 19 | } 20 | 21 | /// BlockchainIterator is used to iterate over blockchain blocks 22 | pub struct BlockchainIterator<'a> { 23 | current_hash: String, 24 | bc: &'a Blockchain, 25 | } 26 | 27 | impl Blockchain { 28 | /// NewBlockchain creates a new Blockchain db 29 | pub fn new() -> Result { 30 | info!("open blockchain"); 31 | 32 | let db = sled::open("data/blocks")?; 33 | let hash = match db.get("LAST")? { 34 | Some(l) => l.to_vec(), 35 | None => Vec::new(), 36 | }; 37 | info!("Found block database"); 38 | let lasthash = if hash.is_empty() { 39 | String::new() 40 | } else { 41 | String::from_utf8(hash.to_vec())? 42 | }; 43 | Ok(Blockchain { tip: lasthash, db }) 44 | } 45 | 46 | /// CreateBlockchain creates a new blockchain DB 47 | pub fn create_blockchain(address: String) -> Result { 48 | info!("Creating new blockchain"); 49 | 50 | std::fs::remove_dir_all("data/blocks").ok(); 51 | let db = sled::open("data/blocks")?; 52 | debug!("Creating new block database"); 53 | let cbtx = Transaction::new_coinbase(address, String::from(GENESIS_COINBASE_DATA))?; 54 | let genesis: Block = Block::new_genesis_block(cbtx); 55 | db.insert(genesis.get_hash(), serialize(&genesis)?)?; 56 | db.insert("LAST", genesis.get_hash().as_bytes())?; 57 | let bc = Blockchain { 58 | tip: genesis.get_hash(), 59 | db, 60 | }; 61 | bc.db.flush()?; 62 | Ok(bc) 63 | } 64 | 65 | /// MineBlock mines a new block with the provided transactions 66 | pub fn mine_block(&mut self, transactions: Vec) -> Result { 67 | info!("mine a new block"); 68 | 69 | for tx in &transactions { 70 | if !self.verify_transacton(tx)? { 71 | return Err(format_err!("ERROR: Invalid transaction")); 72 | } 73 | } 74 | 75 | let lasthash = self.db.get("LAST")?.unwrap(); 76 | 77 | let newblock = Block::new_block( 78 | transactions, 79 | String::from_utf8(lasthash.to_vec())?, 80 | self.get_best_height()? + 1, 81 | )?; 82 | self.db.insert(newblock.get_hash(), serialize(&newblock)?)?; 83 | self.db.insert("LAST", newblock.get_hash().as_bytes())?; 84 | self.db.flush()?; 85 | 86 | self.tip = newblock.get_hash(); 87 | Ok(newblock) 88 | } 89 | 90 | /// Iterator returns a BlockchainIterat 91 | pub fn iter(&self) -> BlockchainIterator { 92 | BlockchainIterator { 93 | current_hash: self.tip.clone(), 94 | bc: &self, 95 | } 96 | } 97 | 98 | /// FindUTXO finds and returns all unspent transaction outputs 99 | pub fn find_UTXO(&self) -> HashMap { 100 | let mut utxos: HashMap = HashMap::new(); 101 | let mut spend_txos: HashMap> = HashMap::new(); 102 | 103 | for block in self.iter() { 104 | for tx in block.get_transaction() { 105 | for index in 0..tx.vout.len() { 106 | if let Some(ids) = spend_txos.get(&tx.id) { 107 | if ids.contains(&(index as i32)) { 108 | continue; 109 | } 110 | } 111 | 112 | match utxos.get_mut(&tx.id) { 113 | Some(v) => { 114 | v.outputs.push(tx.vout[index].clone()); 115 | } 116 | None => { 117 | utxos.insert( 118 | tx.id.clone(), 119 | TXOutputs { 120 | outputs: vec![tx.vout[index].clone()], 121 | }, 122 | ); 123 | } 124 | } 125 | } 126 | 127 | if !tx.is_coinbase() { 128 | for i in &tx.vin { 129 | match spend_txos.get_mut(&i.txid) { 130 | Some(v) => { 131 | v.push(i.vout); 132 | } 133 | None => { 134 | spend_txos.insert(i.txid.clone(), vec![i.vout]); 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | utxos 143 | } 144 | 145 | /// FindTransaction finds a transaction by its ID 146 | pub fn find_transacton(&self, id: &str) -> Result { 147 | for b in self.iter() { 148 | for tx in b.get_transaction() { 149 | if tx.id == id { 150 | return Ok(tx.clone()); 151 | } 152 | } 153 | } 154 | Err(format_err!("Transaction is not found")) 155 | } 156 | 157 | fn get_prev_TXs(&self, tx: &Transaction) -> Result> { 158 | let mut prev_TXs = HashMap::new(); 159 | for vin in &tx.vin { 160 | let prev_TX = self.find_transacton(&vin.txid)?; 161 | prev_TXs.insert(prev_TX.id.clone(), prev_TX); 162 | } 163 | Ok(prev_TXs) 164 | } 165 | 166 | /// SignTransaction signs inputs of a Transaction 167 | pub fn sign_transacton(&self, tx: &mut Transaction, private_key: &[u8]) -> Result<()> { 168 | let prev_TXs = self.get_prev_TXs(tx)?; 169 | tx.sign(private_key, prev_TXs)?; 170 | Ok(()) 171 | } 172 | 173 | /// VerifyTransaction verifies transaction input signatures 174 | pub fn verify_transacton(&self, tx: &Transaction) -> Result { 175 | if tx.is_coinbase() { 176 | return Ok(true); 177 | } 178 | let prev_TXs = self.get_prev_TXs(tx)?; 179 | tx.verify(prev_TXs) 180 | } 181 | 182 | /// AddBlock saves the block into the blockchain 183 | pub fn add_block(&mut self, block: Block) -> Result<()> { 184 | let data = serialize(&block)?; 185 | if let Some(_) = self.db.get(block.get_hash())? { 186 | return Ok(()); 187 | } 188 | self.db.insert(block.get_hash(), data)?; 189 | 190 | let lastheight = self.get_best_height()?; 191 | if block.get_height() > lastheight { 192 | self.db.insert("LAST", block.get_hash().as_bytes())?; 193 | self.tip = block.get_hash(); 194 | self.db.flush()?; 195 | } 196 | Ok(()) 197 | } 198 | 199 | // GetBlock finds a block by its hash and returns it 200 | pub fn get_block(&self, block_hash: &str) -> Result { 201 | let data = self.db.get(block_hash)?.unwrap(); 202 | let block = deserialize(&data.to_vec())?; 203 | Ok(block) 204 | } 205 | 206 | /// GetBestHeight returns the height of the latest block 207 | pub fn get_best_height(&self) -> Result { 208 | let lasthash = if let Some(h) = self.db.get("LAST")? { 209 | h 210 | } else { 211 | return Ok(-1); 212 | }; 213 | let last_data = self.db.get(lasthash)?.unwrap(); 214 | let last_block: Block = deserialize(&last_data.to_vec())?; 215 | Ok(last_block.get_height()) 216 | } 217 | 218 | /// GetBlockHashes returns a list of hashes of all the blocks in the chain 219 | pub fn get_block_hashs(&self) -> Vec { 220 | let mut list = Vec::new(); 221 | for b in self.iter() { 222 | list.push(b.get_hash()); 223 | } 224 | list 225 | } 226 | } 227 | 228 | impl<'a> Iterator for BlockchainIterator<'a> { 229 | type Item = Block; 230 | 231 | fn next(&mut self) -> Option { 232 | if let Ok(encoded_block) = self.bc.db.get(&self.current_hash) { 233 | return match encoded_block { 234 | Some(b) => { 235 | if let Ok(block) = deserialize::(&b) { 236 | self.current_hash = block.get_prev_hash(); 237 | Some(block) 238 | } else { 239 | None 240 | } 241 | } 242 | None => None, 243 | }; 244 | } 245 | None 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/transaction.rs: -------------------------------------------------------------------------------- 1 | //! transaction implement 2 | 3 | use super::*; 4 | use crate::utxoset::*; 5 | use crate::wallets::*; 6 | use bincode::serialize; 7 | use bitcoincash_addr::Address; 8 | use crypto::digest::Digest; 9 | use crypto::ed25519; 10 | use crypto::sha2::Sha256; 11 | use failure::format_err; 12 | use rand::Rng; 13 | use serde::{Deserialize, Serialize}; 14 | use std::collections::HashMap; 15 | 16 | const SUBSIDY: i32 = 10; 17 | 18 | /// TXInput represents a transaction input 19 | #[derive(Serialize, Deserialize, Debug, Clone)] 20 | pub struct TXInput { 21 | pub txid: String, 22 | pub vout: i32, 23 | pub signature: Vec, 24 | pub pub_key: Vec, 25 | } 26 | 27 | /// TXOutput represents a transaction output 28 | #[derive(Serialize, Deserialize, Debug, Clone)] 29 | pub struct TXOutput { 30 | pub value: i32, 31 | pub pub_key_hash: Vec, 32 | } 33 | 34 | // TXOutputs collects TXOutput 35 | #[derive(Serialize, Deserialize, Debug, Clone)] 36 | pub struct TXOutputs { 37 | pub outputs: Vec, 38 | } 39 | 40 | /// Transaction represents a Bitcoin transaction 41 | #[derive(Serialize, Deserialize, Debug, Clone)] 42 | pub struct Transaction { 43 | pub id: String, 44 | pub vin: Vec, 45 | pub vout: Vec, 46 | } 47 | 48 | impl Transaction { 49 | /// NewUTXOTransaction creates a new transaction 50 | pub fn new_UTXO(wallet: &Wallet, to: &str, amount: i32, utxo: &UTXOSet) -> Result { 51 | info!( 52 | "new UTXO Transaction from: {} to: {}", 53 | wallet.get_address(), 54 | to 55 | ); 56 | let mut vin = Vec::new(); 57 | 58 | let mut pub_key_hash = wallet.public_key.clone(); 59 | hash_pub_key(&mut pub_key_hash); 60 | 61 | let acc_v = utxo.find_spendable_outputs(&pub_key_hash, amount)?; 62 | 63 | if acc_v.0 < amount { 64 | error!("Not Enough balance"); 65 | return Err(format_err!( 66 | "Not Enough balance: current balance {}", 67 | acc_v.0 68 | )); 69 | } 70 | 71 | for tx in acc_v.1 { 72 | for out in tx.1 { 73 | let input = TXInput { 74 | txid: tx.0.clone(), 75 | vout: out, 76 | signature: Vec::new(), 77 | pub_key: wallet.public_key.clone(), 78 | }; 79 | vin.push(input); 80 | } 81 | } 82 | 83 | let mut vout = vec![TXOutput::new(amount, to.to_string())?]; 84 | if acc_v.0 > amount { 85 | vout.push(TXOutput::new(acc_v.0 - amount, wallet.get_address())?) 86 | } 87 | 88 | let mut tx = Transaction { 89 | id: String::new(), 90 | vin, 91 | vout, 92 | }; 93 | tx.id = tx.hash()?; 94 | utxo.blockchain 95 | .sign_transacton(&mut tx, &wallet.secret_key)?; 96 | Ok(tx) 97 | } 98 | 99 | /// NewCoinbaseTX creates a new coinbase transaction 100 | pub fn new_coinbase(to: String, mut data: String) -> Result { 101 | info!("new coinbase Transaction to: {}", to); 102 | let mut key: [u8; 32] = [0; 32]; 103 | if data.is_empty() { 104 | let mut rand = rand::OsRng::new().unwrap(); 105 | rand.fill_bytes(&mut key); 106 | data = format!("Reward to '{}'", to); 107 | } 108 | let mut pub_key = Vec::from(data.as_bytes()); 109 | pub_key.append(&mut Vec::from(key)); 110 | 111 | let mut tx = Transaction { 112 | id: String::new(), 113 | vin: vec![TXInput { 114 | txid: String::new(), 115 | vout: -1, 116 | signature: Vec::new(), 117 | pub_key, 118 | }], 119 | vout: vec![TXOutput::new(SUBSIDY, to)?], 120 | }; 121 | tx.id = tx.hash()?; 122 | Ok(tx) 123 | } 124 | 125 | /// IsCoinbase checks whether the transaction is coinbase 126 | pub fn is_coinbase(&self) -> bool { 127 | self.vin.len() == 1 && self.vin[0].txid.is_empty() && self.vin[0].vout == -1 128 | } 129 | 130 | /// Verify verifies signatures of Transaction inputs 131 | pub fn verify(&self, prev_TXs: HashMap) -> Result { 132 | if self.is_coinbase() { 133 | return Ok(true); 134 | } 135 | 136 | for vin in &self.vin { 137 | if prev_TXs.get(&vin.txid).unwrap().id.is_empty() { 138 | return Err(format_err!("ERROR: Previous transaction is not correct")); 139 | } 140 | } 141 | 142 | let mut tx_copy = self.trim_copy(); 143 | 144 | for in_id in 0..self.vin.len() { 145 | let prev_Tx = prev_TXs.get(&self.vin[in_id].txid).unwrap(); 146 | tx_copy.vin[in_id].signature.clear(); 147 | tx_copy.vin[in_id].pub_key = prev_Tx.vout[self.vin[in_id].vout as usize] 148 | .pub_key_hash 149 | .clone(); 150 | tx_copy.id = tx_copy.hash()?; 151 | tx_copy.vin[in_id].pub_key = Vec::new(); 152 | 153 | if !ed25519::verify( 154 | &tx_copy.id.as_bytes(), 155 | &self.vin[in_id].pub_key, 156 | &self.vin[in_id].signature, 157 | ) { 158 | return Ok(false); 159 | } 160 | } 161 | 162 | Ok(true) 163 | } 164 | 165 | /// Sign signs each input of a Transaction 166 | pub fn sign( 167 | &mut self, 168 | private_key: &[u8], 169 | prev_TXs: HashMap, 170 | ) -> Result<()> { 171 | if self.is_coinbase() { 172 | return Ok(()); 173 | } 174 | 175 | for vin in &self.vin { 176 | if prev_TXs.get(&vin.txid).unwrap().id.is_empty() { 177 | return Err(format_err!("ERROR: Previous transaction is not correct")); 178 | } 179 | } 180 | 181 | let mut tx_copy = self.trim_copy(); 182 | 183 | for in_id in 0..tx_copy.vin.len() { 184 | let prev_Tx = prev_TXs.get(&tx_copy.vin[in_id].txid).unwrap(); 185 | tx_copy.vin[in_id].signature.clear(); 186 | tx_copy.vin[in_id].pub_key = prev_Tx.vout[tx_copy.vin[in_id].vout as usize] 187 | .pub_key_hash 188 | .clone(); 189 | tx_copy.id = tx_copy.hash()?; 190 | tx_copy.vin[in_id].pub_key = Vec::new(); 191 | let signature = ed25519::signature(tx_copy.id.as_bytes(), private_key); 192 | self.vin[in_id].signature = signature.to_vec(); 193 | } 194 | 195 | Ok(()) 196 | } 197 | 198 | /// Hash returns the hash of the Transaction 199 | pub fn hash(&self) -> Result { 200 | let mut copy = self.clone(); 201 | copy.id = String::new(); 202 | let data = serialize(©)?; 203 | let mut hasher = Sha256::new(); 204 | hasher.input(&data[..]); 205 | Ok(hasher.result_str()) 206 | } 207 | 208 | /// TrimmedCopy creates a trimmed copy of Transaction to be used in signing 209 | fn trim_copy(&self) -> Transaction { 210 | let mut vin = Vec::new(); 211 | let mut vout = Vec::new(); 212 | 213 | for v in &self.vin { 214 | vin.push(TXInput { 215 | txid: v.txid.clone(), 216 | vout: v.vout.clone(), 217 | signature: Vec::new(), 218 | pub_key: Vec::new(), 219 | }) 220 | } 221 | 222 | for v in &self.vout { 223 | vout.push(TXOutput { 224 | value: v.value, 225 | pub_key_hash: v.pub_key_hash.clone(), 226 | }) 227 | } 228 | 229 | Transaction { 230 | id: self.id.clone(), 231 | vin, 232 | vout, 233 | } 234 | } 235 | } 236 | 237 | impl TXOutput { 238 | /// IsLockedWithKey checks if the output can be used by the owner of the pubkey 239 | pub fn is_locked_with_key(&self, pub_key_hash: &[u8]) -> bool { 240 | self.pub_key_hash == pub_key_hash 241 | } 242 | /// Lock signs the output 243 | fn lock(&mut self, address: &str) -> Result<()> { 244 | let pub_key_hash = Address::decode(address).unwrap().body; 245 | debug!("lock: {}", address); 246 | self.pub_key_hash = pub_key_hash; 247 | Ok(()) 248 | } 249 | 250 | pub fn new(value: i32, address: String) -> Result { 251 | let mut txo = TXOutput { 252 | value, 253 | pub_key_hash: Vec::new(), 254 | }; 255 | txo.lock(&address)?; 256 | Ok(txo) 257 | } 258 | } 259 | 260 | #[cfg(test)] 261 | mod test { 262 | use super::*; 263 | 264 | #[test] 265 | fn test_signature() { 266 | let mut ws = Wallets::new().unwrap(); 267 | let wa1 = ws.create_wallet(); 268 | let w = ws.get_wallet(&wa1).unwrap().clone(); 269 | ws.save_all().unwrap(); 270 | drop(ws); 271 | 272 | let data = String::from("test"); 273 | let tx = Transaction::new_coinbase(wa1, data).unwrap(); 274 | assert!(tx.is_coinbase()); 275 | 276 | let signature = ed25519::signature(tx.id.as_bytes(), &w.secret_key); 277 | assert!(ed25519::verify(tx.id.as_bytes(), &w.public_key, &signature)); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | //! cli process 2 | 3 | use super::*; 4 | use crate::blockchain::*; 5 | use crate::server::*; 6 | use crate::transaction::*; 7 | use crate::utxoset::*; 8 | use crate::wallets::*; 9 | use bitcoincash_addr::Address; 10 | use clap::{App, Arg}; 11 | use std::process::exit; 12 | 13 | pub struct Cli {} 14 | 15 | impl Cli { 16 | pub fn new() -> Cli { 17 | Cli {} 18 | } 19 | 20 | pub fn run(&mut self) -> Result<()> { 21 | info!("run app"); 22 | let matches = App::new("blockchain-demo") 23 | .version("0.1") 24 | .author("yunwei37. 1067852565@qq.com") 25 | .about("reimplement blockchain_go in rust: a simple blockchain for learning") 26 | .subcommand(App::new("printchain").about("print all the chain blocks")) 27 | .subcommand(App::new("createwallet").about("create a wallet")) 28 | .subcommand(App::new("listaddresses").about("list all addresses")) 29 | .subcommand(App::new("reindex").about("reindex UTXO")) 30 | .subcommand( 31 | App::new("startnode") 32 | .about("start the node server") 33 | .arg(Arg::from_usage(" 'the port server bind to locally'")), 34 | ) 35 | .subcommand( 36 | App::new("startminer") 37 | .about("start the minner server") 38 | .arg(Arg::from_usage(" 'the port server bind to locally'")) 39 | .arg(Arg::from_usage("
'wallet address'")), 40 | ) 41 | .subcommand( 42 | App::new("getbalance") 43 | .about("get balance in the blockchain") 44 | .arg(Arg::from_usage( 45 | "
'The address to get balance for'", 46 | )), 47 | ) 48 | .subcommand(App::new("createblockchain").about("create blockchain").arg( 49 | Arg::from_usage("
'The address to send genesis block reward to'"), 50 | )) 51 | .subcommand( 52 | App::new("send") 53 | .about("send in the blockchain") 54 | .arg(Arg::from_usage(" 'Source wallet address'")) 55 | .arg(Arg::from_usage(" 'Destination wallet address'")) 56 | .arg(Arg::from_usage(" 'Amount to send'")) 57 | .arg(Arg::from_usage( 58 | "-m --mine 'the from address mine immediately'", 59 | )), 60 | ) 61 | .get_matches(); 62 | 63 | if let Some(ref matches) = matches.subcommand_matches("getbalance") { 64 | if let Some(address) = matches.value_of("address") { 65 | let balance = cmd_get_balance(address)?; 66 | println!("Balance: {}\n", balance); 67 | } 68 | } else if let Some(_) = matches.subcommand_matches("createwallet") { 69 | println!("address: {}", cmd_create_wallet()?); 70 | } else if let Some(_) = matches.subcommand_matches("printchain") { 71 | cmd_print_chain()?; 72 | } else if let Some(_) = matches.subcommand_matches("reindex") { 73 | let count = cmd_reindex()?; 74 | println!("Done! There are {} transactions in the UTXO set.", count); 75 | } else if let Some(_) = matches.subcommand_matches("listaddresses") { 76 | cmd_list_address()?; 77 | } else if let Some(ref matches) = matches.subcommand_matches("createblockchain") { 78 | if let Some(address) = matches.value_of("address") { 79 | cmd_create_blockchain(address)?; 80 | } 81 | } else if let Some(ref matches) = matches.subcommand_matches("send") { 82 | let from = if let Some(address) = matches.value_of("from") { 83 | address 84 | } else { 85 | println!("from not supply!: usage\n{}", matches.usage()); 86 | exit(1) 87 | }; 88 | let to = if let Some(address) = matches.value_of("to") { 89 | address 90 | } else { 91 | println!("to not supply!: usage\n{}", matches.usage()); 92 | exit(1) 93 | }; 94 | let amount: i32 = if let Some(amount) = matches.value_of("amount") { 95 | amount.parse()? 96 | } else { 97 | println!("amount in send not supply!: usage\n{}", matches.usage()); 98 | exit(1) 99 | }; 100 | if matches.is_present("mine") { 101 | cmd_send(from, to, amount, true)?; 102 | } else { 103 | cmd_send(from, to, amount, false)?; 104 | } 105 | } else if let Some(ref matches) = matches.subcommand_matches("startnode") { 106 | if let Some(port) = matches.value_of("port") { 107 | println!("Start node..."); 108 | let bc = Blockchain::new()?; 109 | let utxo_set = UTXOSet { blockchain: bc }; 110 | let server = Server::new(port, "", utxo_set)?; 111 | server.start_server()?; 112 | } 113 | } else if let Some(ref matches) = matches.subcommand_matches("startminer") { 114 | let address = if let Some(address) = matches.value_of("address") { 115 | address 116 | } else { 117 | println!("address not supply!: usage\n{}", matches.usage()); 118 | exit(1) 119 | }; 120 | let port = if let Some(port) = matches.value_of("port") { 121 | port 122 | } else { 123 | println!("port not supply!: usage\n{}", matches.usage()); 124 | exit(1) 125 | }; 126 | println!("Start miner node..."); 127 | let bc = Blockchain::new()?; 128 | let utxo_set = UTXOSet { blockchain: bc }; 129 | let server = Server::new(port, address, utxo_set)?; 130 | server.start_server()?; 131 | } 132 | 133 | Ok(()) 134 | } 135 | } 136 | 137 | fn cmd_send(from: &str, to: &str, amount: i32, mine_now: bool) -> Result<()> { 138 | let bc = Blockchain::new()?; 139 | let mut utxo_set = UTXOSet { blockchain: bc }; 140 | let wallets = Wallets::new()?; 141 | let wallet = wallets.get_wallet(from).unwrap(); 142 | let tx = Transaction::new_UTXO(wallet, to, amount, &utxo_set)?; 143 | if mine_now { 144 | let cbtx = Transaction::new_coinbase(from.to_string(), String::from("reward!"))?; 145 | let new_block = utxo_set.blockchain.mine_block(vec![cbtx, tx])?; 146 | 147 | utxo_set.update(&new_block)?; 148 | } else { 149 | Server::send_transaction(&tx, utxo_set)?; 150 | } 151 | 152 | println!("success!"); 153 | Ok(()) 154 | } 155 | 156 | fn cmd_create_wallet() -> Result { 157 | let mut ws = Wallets::new()?; 158 | let address = ws.create_wallet(); 159 | ws.save_all()?; 160 | Ok(address) 161 | } 162 | 163 | fn cmd_reindex() -> Result { 164 | let bc = Blockchain::new()?; 165 | let utxo_set = UTXOSet { blockchain: bc }; 166 | utxo_set.reindex()?; 167 | utxo_set.count_transactions() 168 | } 169 | 170 | fn cmd_create_blockchain(address: &str) -> Result<()> { 171 | let address = String::from(address); 172 | let bc = Blockchain::create_blockchain(address)?; 173 | 174 | let utxo_set = UTXOSet { blockchain: bc }; 175 | utxo_set.reindex()?; 176 | println!("create blockchain"); 177 | Ok(()) 178 | } 179 | 180 | fn cmd_get_balance(address: &str) -> Result { 181 | let pub_key_hash = Address::decode(address).unwrap().body; 182 | let bc = Blockchain::new()?; 183 | let utxo_set = UTXOSet { blockchain: bc }; 184 | let utxos = utxo_set.find_UTXO(&pub_key_hash)?; 185 | 186 | let mut balance = 0; 187 | for out in utxos.outputs { 188 | balance += out.value; 189 | } 190 | Ok(balance) 191 | } 192 | 193 | fn cmd_print_chain() -> Result<()> { 194 | let bc = Blockchain::new()?; 195 | for b in bc.iter() { 196 | println!("{:#?}", b); 197 | } 198 | Ok(()) 199 | } 200 | 201 | fn cmd_list_address() -> Result<()> { 202 | let ws = Wallets::new()?; 203 | let addresses = ws.get_all_addresses(); 204 | println!("addresses: "); 205 | for ad in addresses { 206 | println!("{}", ad); 207 | } 208 | Ok(()) 209 | } 210 | 211 | #[cfg(test)] 212 | mod test { 213 | use super::*; 214 | 215 | #[test] 216 | fn test_locally() { 217 | let addr1 = cmd_create_wallet().unwrap(); 218 | let addr2 = cmd_create_wallet().unwrap(); 219 | cmd_create_blockchain(&addr1).unwrap(); 220 | 221 | let b1 = cmd_get_balance(&addr1).unwrap(); 222 | let b2 = cmd_get_balance(&addr2).unwrap(); 223 | assert_eq!(b1, 10); 224 | assert_eq!(b2, 0); 225 | 226 | cmd_send(&addr1, &addr2, 5, true).unwrap(); 227 | 228 | let b1 = cmd_get_balance(&addr1).unwrap(); 229 | let b2 = cmd_get_balance(&addr2).unwrap(); 230 | assert_eq!(b1, 15); 231 | assert_eq!(b2, 5); 232 | 233 | cmd_send(&addr2, &addr1, 15, true).unwrap_err(); 234 | let b1 = cmd_get_balance(&addr1).unwrap(); 235 | let b2 = cmd_get_balance(&addr2).unwrap(); 236 | assert_eq!(b1, 15); 237 | assert_eq!(b2, 5); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /document/part2.md: -------------------------------------------------------------------------------- 1 | #! https://zhuanlan.zhihu.com/p/256444986 2 | # rust 从零开始构建区块链(Bitcoin)系列 - 交易和一些辅助工具 3 | 4 | Github链接,包含文档和全部代码: 5 | 6 | [https://github.com/yunwei37/blockchain-rust](https://github.com/yunwei37/blockchain-rust) 7 | 8 | 这篇文章对应着 Go 原文的第三部分和第四部分,包括 `持久化和命令行接口` 以及 `交易`。交易是一个由区块链构成的虚拟货币系统的核心,但在讨论交易之前,我们还会先着手做一些辅助的工具部分: 9 | 10 | - 将区块链持久化到一个数据库中(在内存中肯定是不现实的); 11 | - 提供一点命令行交互的接口; 12 | - 除了上述在 Go 原文中的两个方面,我们还会添加一个 Rust 的日志功能,这部分在现在看来用处可能不大,但在网络交互中还是非常有必要的。 13 | 14 | ## 一些辅助工具 15 | 16 | 本阶段的代码实现在这里:[commit e2094c0](https://github.com/yunwei37/blockchain-rust/tree/e2094c0ef94fadc4e01030312a1ad890ec633d6f) 17 | 18 | ### 数据库 19 | 20 | 首先来看数据库部分,这里我选择了一个简单的 Rust 键值对数据库 [sled](https://github.com/spacejam/sled): 21 | 22 | >Sled是基于Bw树构建的嵌入式KV数据库,其API接近于一个线程安全的BTreeMap<[u8], [u8]>。而其Bw树的数据结构加上包括crossbeam-epoch的“GC”等技术,使得Sled成为一个lock-free的数据库而在并发环境中傲视群雄。官方宣称在一台16核的机器上,在一个小数据集上可以达到每分钟10亿次操作(95%读核5%写)。 23 | 24 | 示例代码可以参考上面那个链接里面的 README 文件,它的 API 也比原文 Go 里面的更简单。这里,我们使用的键值对有两种类型: 25 | 26 | - 32 字节的 block-hash -> block 结构体 27 | - 'LAST' -> 链中最后一个块的 hash 28 | 29 | 实际存储的全部都是序列化的数据,这里我们可以使用 [serde](https://github.com/serde-rs/serde) 进行序列化和反序列化的操作: 30 | 31 | >serde, 是rust语言用来序列化和反序列化数据的一个非常高效的解决方案。 32 | >本质上,serde提供了一个序列化层的概念。可以将任何支持的数据格式反序列化成中间格式,然后序列化成任何一种支持的数据格式。 33 | 34 | 来看看代码吧! 35 | 36 | 首先,我们修改一下 Blockchain 数据结构的定义,在其中保存有一个 sled 数据库的实例: 37 | 38 | ```rs 39 | #[derive(Debug)] 40 | pub struct Blockchain { 41 | tip: String, 42 | db: sled::Db, 43 | } 44 | ``` 45 | 46 | 由于我们需要对 Block 结构体进行序列化,我们可以很简单地使用 serde 库的 derive 特性机制,只要在结构体上面添加上 `#[derive(Serialize, Deserialize)` 就好: 47 | 48 | ```rs 49 | #[derive(Serialize, Deserialize, Debug, Clone)] 50 | pub struct Block { 51 | timestamp: u128, 52 | data: String, 53 | prev_block_hash: String, 54 | hash: String, 55 | nonce: i32, 56 | } 57 | ``` 58 | 59 | 然后,我们可以这样创建一个 Blockchain 结构体,如果存在数据库(找到了 `LAST` 键对应的内容)的话就加载对应的区块链,不存在就创建之: 60 | 61 | ```rs 62 | impl Blockchain { 63 | pub fn new() -> Result { 64 | info!("Creating new blockchain"); 65 | 66 | let db = sled::open("data/blocks")?; 67 | match db.get("LAST")? { 68 | Some(hash) => { // 如果存在数据库(找到了 `LAST` 键对应的内容) 69 | info!("Found block database"); 70 | let lasthash = String::from_utf8(hash.to_vec())?; 71 | Ok(Blockchain { 72 | tip: lasthash.clone(), 73 | current_hash: lasthash, 74 | db, 75 | }) 76 | } 77 | None => { // 数据库里面没有 LAST ,创建一个新的区块链 78 | info!("Creating new block database"); 79 | let block = Block::new_genesis_block(); 80 | db.insert(block.get_hash(), serialize(&block)?)?; // 序列化然后插入 81 | db.insert("LAST", block.get_hash().as_bytes())?; 82 | let bc = Blockchain { 83 | tip: block.get_hash(), 84 | db, 85 | }; 86 | bc.db.flush()?; 87 | Ok(bc) 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | 然后是向数据库里面添加区块,代码的逻辑很简单:找到 LAST 对应的区块,把它作为上个区块来创建新的区块,然后插入数据库,更新 LAST: 94 | 95 | ```rs 96 | impl Blockchain { 97 | pub fn add_block(&mut self, data: String) -> Result<()> { 98 | info!("add new block to the chain"); 99 | 100 | let lasthash = self.db.get("LAST")?.unwrap(); 101 | 102 | let newblock = Block::new_block(data, String::from_utf8(lasthash.to_vec())?)?; 103 | self.db.insert(newblock.get_hash(), serialize(&newblock)?)?; 104 | self.db.insert("LAST", newblock.get_hash().as_bytes())?; 105 | self.db.flush()?; 106 | 107 | self.tip = newblock.get_hash(); 108 | 109 | Ok(()) 110 | } 111 | ``` 112 | 113 | 为了让调用比较方便,我们可以考虑为区块链增加一个迭代器,这个迭代器能够顺序打印区块链(在数据库中是按hash存储的)。具体代码这里就忽略过去啦(毕竟也不算特别重要,可以参考源代码 blockchain.rs)。 114 | 115 | ### 命令行接口和日志 116 | 117 | rust 没有 Go 那样原生强大的标准库,但我们还是有很多社区库可以用的,比如这个 [clap](https://github.com/clap-rs/clap): 118 | 119 | > clap是一个简单易用,功能强大的命令行参数解析库,用于解析并验证用户在运行命令行程序时提供的命令行参数字符串。 你所需要做的只是提供有效参数的列表,clap会自动处理其余的繁杂工作。 这样工程师可以把时间和精力放在实现程序功能上,而不是参数的解析和验证上。 120 | 121 | 我们这里创建了一个简单的结构体 `Cli`: 122 | 123 | ```rs 124 | pub struct Cli { 125 | bc: Blockchain, 126 | } 127 | ``` 128 | 129 | 大概 clap 匹配命令的代码是这样的: 130 | 131 | ```rs 132 | impl Cli { 133 | pub fn run(&mut self) -> Result<()> { 134 | let matches = App::new("blockchain-demo") 135 | .version("0.1") 136 | .author("yunwei37. 1067852565@qq.com") 137 | .about("reimplement blockchain_go in rust: a simple blockchain for learning") 138 | .subcommand(App::new("printchain").about("print all the chain blocks")) 139 | .subcommand( 140 | App::new("addblock") 141 | .about("add a block in the blockchain") 142 | .arg(Arg::from_usage(" 'the blockchain data'")), 143 | ) 144 | .get_matches(); 145 | 146 | if let Some(ref matches) = matches.subcommand_matches("addblock") { 147 | if let Some(c) = matches.value_of("data") { 148 | self.bc.add_block(data)?; 149 | } else { 150 | println!("Not data..."); 151 | } 152 | } 153 | 154 | if let Some(_) = matches.subcommand_matches("printchain") { 155 | let bc = Blockchain::new()?; 156 | for b in bc.iter() { 157 | println!("block: {:#?}", b); 158 | } 159 | } 160 | 161 | .... 162 | ``` 163 | 164 | 我们在这里创建了两个子命令: 165 | 166 | - `addblock ` 添加一个新的区块 167 | - `printchain` 使用迭代打印区块链 168 | 169 | 最后,我们可以使用 [env_logger](http://www.eclipse.org/paho/files/rustdoc/env_logger/index.html) 来进行日志记录,具体实例就像上面提到的那样: `info!("add new block to the chain");` 170 | 171 | 在 main 函数中初始化 env_logger,指定默认日志等级为warnning,然后运行命令行处理程序: 172 | 173 | ```rs 174 | fn main() -> Result<()> { 175 | env_logger::from_env(Env::default().default_filter_or("warning")).init(); 176 | 177 | let mut cli = Cli::new()?; 178 | cli.run()?; 179 | 180 | Ok(()) 181 | } 182 | ``` 183 | 184 | 这样我们就有一个看起来还行的 blockchain demo 啦,下一步就是加上交易! 185 | 186 | ## 交易 187 | 188 | 交易(transaction)是比特币的核心所在,而比特币使用区块链唯一的目的,也正是为了能够安全可靠地存储交易。在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它;同时,由于比特币采用的是 UTXO 模型,并非账户模型,并不直接存在“余额”这个概念,余额需要通过遍历整个交易历史得来。 189 | 190 | 详细的信息可以参考:[en.bitcoin.it/wiki/Transaction](https://en.bitcoin.it/wiki/Transaction) 191 | 192 | 也可以对照原版的中文翻译看,他那关于原理的介绍比较详细:[transactions-1](https://liuchengxu.gitbook.io/blockchain/bitcoin/transactions-1#yin-yan) 193 | 194 | 首先,我们看看交易的数据结构,一笔交易由一些输入(input)和输出(output)组合而来: 195 | 196 | ```rs 197 | #[derive(Serialize, Deserialize, Debug, Clone)] 198 | pub struct Transaction { 199 | pub id: String, 200 | pub vin: Vec, 201 | pub vout: Vec, 202 | } 203 | ``` 204 | 205 | 对于每一笔新的交易,它的输入会引用(reference)之前一笔交易的输出,不过: 206 | 207 | - 有一些输出并没有被关联到某个输入上,对应着 coinbase; 208 | - 一笔交易的输入可以引用之前多笔交易的输出; 209 | - 一个输入必须引用一个输出; 210 | 211 | 这是输出,包含一定量的比特币和一个锁定脚本(这里并不会实现全面的脚本语言): 212 | 213 | ```rs 214 | #[derive(Serialize, Deserialize, Debug, Clone)] 215 | pub struct TXOutput { 216 | pub value: i32, 217 | pub script_pub_key: String, 218 | } 219 | ``` 220 | 221 | 我们将只会简单地把输出和用户定义的钱包地址(一个任意的字符串)作比较: 222 | 223 | ```rs 224 | impl TXOutput { 225 | /// CanBeUnlockedWith checks if the output can be unlocked with the provided data 226 | pub fn can_be_unlock_with(&self, unlockingData: &str) -> bool { 227 | self.script_pub_key == unlockingData 228 | } 229 | ``` 230 | 231 | 这是输入,引用(reference)之前一笔交易的输出: 232 | 233 | ```rs 234 | #[derive(Serialize, Deserialize, Debug, Clone)] 235 | pub struct TXInput { 236 | pub txid: String, 237 | pub vout: i32, 238 | pub script_sig: String, 239 | } 240 | ``` 241 | 242 | 事实上,虚拟货币就是存储在输出中里面。由于还没有实现地址(address),所以目前我们会避免涉及逻辑相关的完整脚本。 243 | 244 | 来看看一个基本的使用场景:从一个用户向另外一个用户发送币,即创建一笔交易: 245 | 246 | 之前我们仅仅实现了简单的 `coinbase` 交易方法,也就是挖矿,现在我们需要一种通用的普通交易: 247 | 248 | - 首先,我们需要使用 `find_spendable_outputs` 函数,找到发送方可以花费的货币数量,以及包含这些货币的未使用输出; 249 | - 然后,我们使用这些输出创建一个新的输入给接收方,这里已经被引用的输入就相当于被花掉了;注意,输出是不可再分的; 250 | - 最后,将多余的钱(找零)创建一个新的输出返回给发送方。 251 | 252 | 好啦!一个最基本的交易原型就这样完成了,现在来看看代码; 253 | 254 | ```rs 255 | impl Transaction { 256 | /// NewUTXOTransaction creates a new transaction 257 | pub fn new_UTXO(from: &str, to: &str, amount: i32, bc: &Blockchain) -> Result { 258 | info!("new UTXO Transaction from: {} to: {}", from, to); 259 | let mut vin = Vec::new(); 260 | let acc_v = bc.find_spendable_outputs(from, amount); 261 | 262 | if acc_v.0 < amount { 263 | error!("Not Enough balance"); 264 | return Err(format_err!( 265 | "Not Enough balance: current balance {}", 266 | acc_v.0 267 | )); 268 | } 269 | 270 | for tx in acc_v.1 { 271 | for out in tx.1 { 272 | let input = TXInput { 273 | txid: tx.0.clone(), 274 | vout: out, 275 | script_sig: String::from(from), 276 | }; 277 | vin.push(input); 278 | } 279 | } 280 | 281 | let mut vout = vec![TXOutput { 282 | value: amount, 283 | script_pub_key: String::from(to), 284 | }]; 285 | if acc_v.0 > amount { 286 | vout.push(TXOutput { 287 | value: acc_v.0 - amount, 288 | script_pub_key: String::from(from), 289 | }) 290 | } 291 | 292 | let mut tx = Transaction { 293 | id: String::new(), 294 | vin, 295 | vout, 296 | }; 297 | tx.set_id()?; 298 | Ok(tx) 299 | } 300 | ``` 301 | 302 | 来看看相关的辅助函数,首先是在区块链中寻找未花费的输出 find_spendable_outputs,该方法返回一个包含累积未花费输出和相关输出结构体集合的元组: 303 | 304 | - 首先,使用 `find_unspent_transactions` 找到包含发送方的所有未花掉的输出; 305 | - 然后,在所有未花掉的输出上面迭代,将能够被使用者解锁的输出的 id 插入到用交易 id 作为索引的集合中;增加累计数值; 306 | - 当累计数值超过需要数值的时候返回。 307 | 308 | ```rs 309 | impl Blockchain { 310 | pub fn find_spendable_outputs( 311 | &self, 312 | address: &str, 313 | amount: i32, 314 | ) -> (i32, HashMap>) { 315 | let mut unspent_outputs: HashMap> = HashMap::new(); 316 | let mut accumulated = 0; 317 | let unspend_TXs = self.find_unspent_transactions(address); 318 | 319 | for tx in unspend_TXs { 320 | for index in 0..tx.vout.len() { 321 | if tx.vout[index].can_be_unlock_with(address) && accumulated < amount { 322 | match unspent_outputs.get_mut(&tx.id) { 323 | Some(v) => v.push(index as i32), 324 | None => { 325 | unspent_outputs.insert(tx.id.clone(), vec![index as i32]); 326 | } 327 | } 328 | accumulated += tx.vout[index].value; 329 | 330 | if accumulated >= amount { 331 | return (accumulated, unspent_outputs); 332 | } 333 | } 334 | } 335 | } 336 | (accumulated, unspent_outputs) 337 | } 338 | ``` 339 | 340 | 下一步是找到区块链中对应地址能解锁的包含未花费输出的交易,即 `find_unspent_transactions`: 341 | 342 | - 如果一个输出可以被发送方的地址解锁,并且该输出没有被包含在一个交易的输入中,它就是可以使用的; 343 | - 由于我们对区块链是从尾部往头部迭代,因此如果我们见到的输出没有被包含在我们见到的任何一笔输入中,它就是未使用的; 344 | - 我们将见到的输入加入一个集合,然后在这个集合中查找对应的输出,如果一个输出可以被解锁并且没有在集合中找到的话,它就是可以被花费的。 345 | 346 | ```rs 347 | impl Blockchain { 348 | fn find_unspent_transactions(&self, address: &str) -> Vec { 349 | let mut spent_TXOs: HashMap> = HashMap::new(); 350 | let mut unspend_TXs: Vec = Vec::new(); 351 | 352 | for block in self.iter() { 353 | for tx in block.get_transaction() { 354 | for index in 0..tx.vout.len() { 355 | if let Some(ids) = spent_TXOs.get(&tx.id) { 356 | if ids.contains(&(index as i32)) { 357 | continue; 358 | } 359 | } 360 | 361 | if tx.vout[index].can_be_unlock_with(address) { 362 | unspend_TXs.push(tx.to_owned()) 363 | } 364 | } 365 | 366 | if !tx.is_coinbase() { 367 | for i in &tx.vin { 368 | if i.can_unlock_output_with(address) { 369 | match spent_TXOs.get_mut(&i.txid) { 370 | Some(v) => { 371 | v.push(i.vout); 372 | } 373 | None => { 374 | spent_TXOs.insert(i.txid.clone(), vec![i.vout]); 375 | } 376 | } 377 | } 378 | } 379 | } 380 | } 381 | } 382 | 383 | unspend_TXs 384 | } 385 | 386 | ``` 387 | 388 | 对于一个普通的交易,可以用以上方法完成;但我们还需要一种 `coinbase` 交易,它“凭空”产生了币,这是矿工获得挖出新块的奖励; 389 | 390 | ```rs 391 | impl Transaction { 392 | pub fn new_coinbase(to: String, mut data: String) -> Result { 393 | info!("new coinbase Transaction to: {}", to); 394 | if data == String::from("") { 395 | data += &format!("Reward to '{}'", to); 396 | } 397 | let mut tx = Transaction { 398 | id: String::new(), 399 | vin: vec![TXInput { 400 | txid: String::new(), 401 | vout: -1, 402 | script_sig: data, 403 | }], 404 | vout: vec![TXOutput { 405 | value: SUBSIDY, 406 | script_pub_key: to, 407 | }], 408 | }; 409 | tx.set_id()?; 410 | Ok(tx) 411 | } 412 | ``` 413 | 414 | 我们还可以创建一个简单的辅助函数,让我们可以比较简单地获取余额:这个函数返回了一个交易列表,里面包含了未花费输出; 415 | 416 | ```rs 417 | impl Blockchain { 418 | /// FindUTXO finds and returns all unspent transaction outputs 419 | pub fn find_UTXO(&self, address: &str) -> Vec { 420 | let mut utxos = Vec::::new(); 421 | let unspend_TXs = self.find_unspent_transactions(address); 422 | for tx in unspend_TXs { 423 | for out in &tx.vout { 424 | if out.can_be_unlock_with(&address) { 425 | utxos.push(out.clone()); 426 | } 427 | } 428 | } 429 | utxos 430 | } 431 | ``` 432 | 433 | 交易的部分差不多就这些啦!我们已经完成了准备工作,现在可以更改一下之前留下来的接口: 434 | 435 | 我们首先需要在区块中添加一下包含的交易,是这样的: 436 | 437 | ```rs 438 | #[derive(Serialize, Deserialize, Debug, Clone)] 439 | pub struct Block { 440 | timestamp: u128, 441 | transactions: Vec, 442 | prev_block_hash: String, 443 | hash: String, 444 | nonce: i32, 445 | } 446 | 447 | ``` 448 | 449 | 然后也要更改一下添加区块的接口: 450 | 451 | ```rs 452 | 453 | impl Block { 454 | pub fn new_block(transactions: Vec, prev_block_hash: String) -> Result { 455 | .... 456 | } 457 | 458 | pub fn new_genesis_block(coinbase: Transaction) -> Block { 459 | Block::new_block(vec![coinbase], String::new()).unwrap() 460 | } 461 | ``` 462 | 463 | 在创建区块链的时候,我们也需要创建一笔 `coinbase` 交易: 464 | 465 | ```rs 466 | impl Blockchain { 467 | pub fn create_blockchain(address: String) -> Result { 468 | ... 469 | let cbtx = Transaction::new_coinbase(address, String::from(GENESIS_COINBASE_DATA))?; 470 | let genesis: Block = Block::new_genesis_block(cbtx); 471 | ... 472 | } 473 | ``` 474 | 475 | 基本上大功告成!我们看看具体的命令实现: 476 | 477 | send 命令: 478 | 479 | ```rs 480 | ... 481 | let mut bc = Blockchain::new()?; 482 | let tx = Transaction::new_UTXO(from, to, amount, &bc)?; 483 | bc.mine_block(vec![tx])?; 484 | ... 485 | ``` 486 | 487 | getbalance 命令: 488 | 489 | ```rs 490 | ... 491 | let bc = Blockchain::new()?; 492 | let utxos = bc.find_UTXO(&address); 493 | 494 | let mut balance = 0; 495 | for out in utxos { 496 | balance += out.value; 497 | } 498 | println!("Balance of '{}': {}\n", address, balance); 499 | ... 500 | ``` 501 | 502 | 这样就好啦!如果想要进一步观察交易的相关知识,可以参考:[https://en.bitcoin.it/wiki/Transaction](https://en.bitcoin.it/wiki/Transaction) 503 | 504 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! server of Blockchain 2 | 3 | use super::*; 4 | use crate::block::*; 5 | use crate::transaction::*; 6 | use crate::utxoset::*; 7 | use bincode::{deserialize, serialize}; 8 | use failure::format_err; 9 | use serde::{Deserialize, Serialize}; 10 | use std::collections::{HashMap, HashSet}; 11 | use std::io::prelude::*; 12 | use std::net::{TcpListener, TcpStream}; 13 | use std::sync::*; 14 | use std::thread; 15 | use std::time::Duration; 16 | 17 | #[derive(Serialize, Deserialize, Debug, Clone)] 18 | enum Message { 19 | Addr(Vec), 20 | Version(Versionmsg), 21 | Tx(Txmsg), 22 | GetData(GetDatamsg), 23 | GetBlock(GetBlocksmsg), 24 | Inv(Invmsg), 25 | Block(Blockmsg), 26 | } 27 | 28 | #[derive(Serialize, Deserialize, Debug, Clone)] 29 | struct Blockmsg { 30 | addr_from: String, 31 | block: Block, 32 | } 33 | 34 | #[derive(Serialize, Deserialize, Debug, Clone)] 35 | struct GetBlocksmsg { 36 | addr_from: String, 37 | } 38 | 39 | #[derive(Serialize, Deserialize, Debug, Clone)] 40 | struct GetDatamsg { 41 | addr_from: String, 42 | kind: String, 43 | id: String, 44 | } 45 | 46 | #[derive(Serialize, Deserialize, Debug, Clone)] 47 | struct Invmsg { 48 | addr_from: String, 49 | kind: String, 50 | items: Vec, 51 | } 52 | 53 | #[derive(Serialize, Deserialize, Debug, Clone)] 54 | struct Txmsg { 55 | addr_from: String, 56 | transaction: Transaction, 57 | } 58 | 59 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 60 | struct Versionmsg { 61 | addr_from: String, 62 | version: i32, 63 | best_height: i32, 64 | } 65 | 66 | pub struct Server { 67 | node_address: String, 68 | mining_address: String, 69 | inner: Arc>, 70 | } 71 | 72 | struct ServerInner { 73 | known_nodes: HashSet, 74 | utxo: UTXOSet, 75 | blocks_in_transit: Vec, 76 | mempool: HashMap, 77 | } 78 | 79 | const KNOWN_NODE1: &str = "localhost:3000"; 80 | const CMD_LEN: usize = 12; 81 | const VERSION: i32 = 1; 82 | 83 | impl Server { 84 | pub fn new(port: &str, miner_address: &str, utxo: UTXOSet) -> Result { 85 | let mut node_set = HashSet::new(); 86 | node_set.insert(String::from(KNOWN_NODE1)); 87 | Ok(Server { 88 | node_address: String::from("localhost:") + port, 89 | mining_address: miner_address.to_string(), 90 | inner: Arc::new(Mutex::new(ServerInner { 91 | known_nodes: node_set, 92 | utxo, 93 | blocks_in_transit: Vec::new(), 94 | mempool: HashMap::new(), 95 | })), 96 | }) 97 | } 98 | 99 | pub fn start_server(&self) -> Result<()> { 100 | let server1 = Server { 101 | node_address: self.node_address.clone(), 102 | mining_address: self.mining_address.clone(), 103 | inner: Arc::clone(&self.inner), 104 | }; 105 | info!( 106 | "Start server at {}, minning address: {}", 107 | &self.node_address, &self.mining_address 108 | ); 109 | 110 | thread::spawn(move || { 111 | thread::sleep(Duration::from_millis(1000)); 112 | if server1.get_best_height()? == -1 { 113 | server1.request_blocks() 114 | } else { 115 | server1.send_version(KNOWN_NODE1) 116 | } 117 | }); 118 | 119 | let listener = TcpListener::bind(&self.node_address).unwrap(); 120 | info!("Server listen..."); 121 | 122 | for stream in listener.incoming() { 123 | let stream = stream?; 124 | let server1 = Server { 125 | node_address: self.node_address.clone(), 126 | mining_address: self.mining_address.clone(), 127 | inner: Arc::clone(&self.inner), 128 | }; 129 | thread::spawn(move || server1.handle_connection(stream)); 130 | } 131 | 132 | Ok(()) 133 | } 134 | 135 | pub fn send_transaction(tx: &Transaction, utxoset: UTXOSet) -> Result<()> { 136 | let server = Server::new("7000", "", utxoset)?; 137 | server.send_tx(KNOWN_NODE1, tx)?; 138 | Ok(()) 139 | } 140 | 141 | /* ------------------- inner halp functions ----------------------------------*/ 142 | 143 | fn remove_node(&self, addr: &str) { 144 | self.inner.lock().unwrap().known_nodes.remove(addr); 145 | } 146 | 147 | fn add_nodes(&self, addr: &str) { 148 | self.inner 149 | .lock() 150 | .unwrap() 151 | .known_nodes 152 | .insert(String::from(addr)); 153 | } 154 | 155 | fn get_known_nodes(&self) -> HashSet { 156 | self.inner.lock().unwrap().known_nodes.clone() 157 | } 158 | 159 | fn node_is_known(&self, addr: &str) -> bool { 160 | self.inner.lock().unwrap().known_nodes.get(addr).is_some() 161 | } 162 | 163 | fn replace_in_transit(&self, hashs: Vec) { 164 | let bit = &mut self.inner.lock().unwrap().blocks_in_transit; 165 | bit.clone_from(&hashs); 166 | } 167 | 168 | fn get_in_transit(&self) -> Vec { 169 | self.inner.lock().unwrap().blocks_in_transit.clone() 170 | } 171 | 172 | fn get_mempool_tx(&self, addr: &str) -> Option { 173 | match self.inner.lock().unwrap().mempool.get(addr) { 174 | Some(tx) => Some(tx.clone()), 175 | None => None, 176 | } 177 | } 178 | 179 | fn get_mempool(&self) -> HashMap { 180 | self.inner.lock().unwrap().mempool.clone() 181 | } 182 | 183 | fn insert_mempool(&self, tx: Transaction) { 184 | self.inner.lock().unwrap().mempool.insert(tx.id.clone(), tx); 185 | } 186 | 187 | fn clear_mempool(&self) { 188 | self.inner.lock().unwrap().mempool.clear() 189 | } 190 | 191 | fn get_best_height(&self) -> Result { 192 | self.inner.lock().unwrap().utxo.blockchain.get_best_height() 193 | } 194 | 195 | fn get_block_hashs(&self) -> Vec { 196 | self.inner.lock().unwrap().utxo.blockchain.get_block_hashs() 197 | } 198 | 199 | fn get_block(&self, block_hash: &str) -> Result { 200 | self.inner 201 | .lock() 202 | .unwrap() 203 | .utxo 204 | .blockchain 205 | .get_block(block_hash) 206 | } 207 | 208 | fn verify_tx(&self, tx: &Transaction) -> Result { 209 | self.inner 210 | .lock() 211 | .unwrap() 212 | .utxo 213 | .blockchain 214 | .verify_transacton(tx) 215 | } 216 | 217 | fn add_block(&self, block: Block) -> Result<()> { 218 | self.inner.lock().unwrap().utxo.blockchain.add_block(block) 219 | } 220 | 221 | fn mine_block(&self, txs: Vec) -> Result { 222 | self.inner.lock().unwrap().utxo.blockchain.mine_block(txs) 223 | } 224 | 225 | fn utxo_reindex(&self) -> Result<()> { 226 | self.inner.lock().unwrap().utxo.reindex() 227 | } 228 | 229 | /* -----------------------------------------------------*/ 230 | 231 | fn send_data(&self, addr: &str, data: &[u8]) -> Result<()> { 232 | if addr == &self.node_address { 233 | return Ok(()); 234 | } 235 | let mut stream = match TcpStream::connect(addr) { 236 | Ok(s) => s, 237 | Err(_) => { 238 | self.remove_node(addr); 239 | return Ok(()); 240 | } 241 | }; 242 | 243 | stream.write(data)?; 244 | 245 | info!("data send successfully"); 246 | Ok(()) 247 | } 248 | 249 | fn request_blocks(&self) -> Result<()> { 250 | for node in self.get_known_nodes() { 251 | self.send_get_blocks(&node)? 252 | } 253 | Ok(()) 254 | } 255 | 256 | fn send_block(&self, addr: &str, b: &Block) -> Result<()> { 257 | info!("send block data to: {} block hash: {}", addr, b.get_hash()); 258 | let data = Blockmsg { 259 | addr_from: self.node_address.clone(), 260 | block: b.clone(), 261 | }; 262 | let data = serialize(&(cmd_to_bytes("block"), data))?; 263 | self.send_data(addr, &data) 264 | } 265 | 266 | fn send_addr(&self, addr: &str) -> Result<()> { 267 | info!("send address info to: {}", addr); 268 | let nodes = self.get_known_nodes(); 269 | let data = serialize(&(cmd_to_bytes("addr"), nodes))?; 270 | self.send_data(addr, &data) 271 | } 272 | 273 | fn send_inv(&self, addr: &str, kind: &str, items: Vec) -> Result<()> { 274 | info!( 275 | "send inv message to: {} kind: {} data: {:?}", 276 | addr, kind, items 277 | ); 278 | let data = Invmsg { 279 | addr_from: self.node_address.clone(), 280 | kind: kind.to_string(), 281 | items, 282 | }; 283 | let data = serialize(&(cmd_to_bytes("inv"), data))?; 284 | self.send_data(addr, &data) 285 | } 286 | 287 | fn send_get_blocks(&self, addr: &str) -> Result<()> { 288 | info!("send get blocks message to: {}", addr); 289 | let data = GetBlocksmsg { 290 | addr_from: self.node_address.clone(), 291 | }; 292 | let data = serialize(&(cmd_to_bytes("getblocks"), data))?; 293 | self.send_data(addr, &data) 294 | } 295 | 296 | fn send_get_data(&self, addr: &str, kind: &str, id: &str) -> Result<()> { 297 | info!( 298 | "send get data message to: {} kind: {} id: {}", 299 | addr, kind, id 300 | ); 301 | let data = GetDatamsg { 302 | addr_from: self.node_address.clone(), 303 | kind: kind.to_string(), 304 | id: id.to_string(), 305 | }; 306 | let data = serialize(&(cmd_to_bytes("getdata"), data))?; 307 | self.send_data(addr, &data) 308 | } 309 | 310 | pub fn send_tx(&self, addr: &str, tx: &Transaction) -> Result<()> { 311 | info!("send tx to: {} txid: {}", addr, &tx.id); 312 | let data = Txmsg { 313 | addr_from: self.node_address.clone(), 314 | transaction: tx.clone(), 315 | }; 316 | let data = serialize(&(cmd_to_bytes("tx"), data))?; 317 | self.send_data(addr, &data) 318 | } 319 | 320 | fn send_version(&self, addr: &str) -> Result<()> { 321 | info!("send version info to: {}", addr); 322 | let data = Versionmsg { 323 | addr_from: self.node_address.clone(), 324 | best_height: self.get_best_height()?, 325 | version: VERSION, 326 | }; 327 | let data = serialize(&(cmd_to_bytes("version"), data))?; 328 | self.send_data(addr, &data) 329 | } 330 | 331 | fn handle_version(&self, msg: Versionmsg) -> Result<()> { 332 | info!("receive version msg: {:#?}", msg); 333 | let my_best_height = self.get_best_height()?; 334 | if my_best_height < msg.best_height { 335 | self.send_get_blocks(&msg.addr_from)?; 336 | } else if my_best_height > msg.best_height { 337 | self.send_version(&msg.addr_from)?; 338 | } 339 | 340 | self.send_addr(&msg.addr_from)?; 341 | 342 | if !self.node_is_known(&msg.addr_from) { 343 | self.add_nodes(&msg.addr_from); 344 | } 345 | Ok(()) 346 | } 347 | 348 | fn handle_addr(&self, msg: Vec) -> Result<()> { 349 | info!("receive address msg: {:#?}", msg); 350 | for node in msg { 351 | self.add_nodes(&node); 352 | } 353 | //self.request_blocks()?; 354 | Ok(()) 355 | } 356 | 357 | fn handle_block(&self, msg: Blockmsg) -> Result<()> { 358 | info!( 359 | "receive block msg: {}, {}", 360 | msg.addr_from, 361 | msg.block.get_hash() 362 | ); 363 | self.add_block(msg.block)?; 364 | 365 | let mut in_transit = self.get_in_transit(); 366 | if in_transit.len() > 0 { 367 | let block_hash = &in_transit[0]; 368 | self.send_get_data(&msg.addr_from, "block", block_hash)?; 369 | in_transit.remove(0); 370 | self.replace_in_transit(in_transit); 371 | } else { 372 | self.utxo_reindex()?; 373 | } 374 | 375 | Ok(()) 376 | } 377 | 378 | fn handle_inv(&self, msg: Invmsg) -> Result<()> { 379 | info!("receive inv msg: {:#?}", msg); 380 | if msg.kind == "block" { 381 | let block_hash = &msg.items[0]; 382 | self.send_get_data(&msg.addr_from, "block", block_hash)?; 383 | 384 | let mut new_in_transit = Vec::new(); 385 | for b in &msg.items { 386 | if b != block_hash { 387 | new_in_transit.push(b.clone()); 388 | } 389 | } 390 | self.replace_in_transit(new_in_transit); 391 | } else if msg.kind == "tx" { 392 | let txid = &msg.items[0]; 393 | match self.get_mempool_tx(txid) { 394 | Some(tx) => { 395 | if tx.id.is_empty() { 396 | self.send_get_data(&msg.addr_from, "tx", txid)? 397 | } 398 | } 399 | None => self.send_get_data(&msg.addr_from, "tx", txid)?, 400 | } 401 | } 402 | Ok(()) 403 | } 404 | 405 | fn handle_get_blocks(&self, msg: GetBlocksmsg) -> Result<()> { 406 | info!("receive get blocks msg: {:#?}", msg); 407 | let block_hashs = self.get_block_hashs(); 408 | self.send_inv(&msg.addr_from, "block", block_hashs)?; 409 | Ok(()) 410 | } 411 | 412 | fn handle_get_data(&self, msg: GetDatamsg) -> Result<()> { 413 | info!("receive get data msg: {:#?}", msg); 414 | if msg.kind == "block" { 415 | let block = self.get_block(&msg.id)?; 416 | self.send_block(&msg.addr_from, &block)?; 417 | } else if msg.kind == "tx" { 418 | let tx = self.get_mempool_tx(&msg.id).unwrap(); 419 | self.send_tx(&msg.addr_from, &tx)?; 420 | } 421 | Ok(()) 422 | } 423 | 424 | fn handle_tx(&self, msg: Txmsg) -> Result<()> { 425 | info!("receive tx msg: {} {}", msg.addr_from, &msg.transaction.id); 426 | self.insert_mempool(msg.transaction.clone()); 427 | 428 | let known_nodes = self.get_known_nodes(); 429 | if self.node_address == KNOWN_NODE1 { 430 | for node in known_nodes { 431 | if node != self.node_address && node != msg.addr_from { 432 | self.send_inv(&node, "tx", vec![msg.transaction.id.clone()])?; 433 | } 434 | } 435 | } else { 436 | let mut mempool = self.get_mempool(); 437 | debug!("Current mempool: {:#?}", &mempool); 438 | if mempool.len() >= 1 && !self.mining_address.is_empty() { 439 | loop { 440 | let mut txs = Vec::new(); 441 | 442 | for (_, tx) in &mempool { 443 | if self.verify_tx(tx)? { 444 | txs.push(tx.clone()); 445 | } 446 | } 447 | 448 | if txs.is_empty() { 449 | return Ok(()); 450 | } 451 | 452 | let cbtx = 453 | Transaction::new_coinbase(self.mining_address.clone(), String::new())?; 454 | txs.push(cbtx); 455 | 456 | for tx in &txs { 457 | mempool.remove(&tx.id); 458 | } 459 | 460 | let new_block = self.mine_block(txs)?; 461 | self.utxo_reindex()?; 462 | 463 | for node in self.get_known_nodes() { 464 | if node != self.node_address { 465 | self.send_inv(&node, "block", vec![new_block.get_hash()])?; 466 | } 467 | } 468 | 469 | if mempool.len() == 0 { 470 | break; 471 | } 472 | } 473 | self.clear_mempool(); 474 | } 475 | } 476 | 477 | Ok(()) 478 | } 479 | 480 | fn handle_connection(&self, mut stream: TcpStream) -> Result<()> { 481 | let mut buffer = Vec::new(); 482 | let count = stream.read_to_end(&mut buffer)?; 483 | info!("Accept request: length {}", count); 484 | 485 | let cmd = bytes_to_cmd(&buffer)?; 486 | 487 | match cmd { 488 | Message::Addr(data) => self.handle_addr(data)?, 489 | Message::Block(data) => self.handle_block(data)?, 490 | Message::Inv(data) => self.handle_inv(data)?, 491 | Message::GetBlock(data) => self.handle_get_blocks(data)?, 492 | Message::GetData(data) => self.handle_get_data(data)?, 493 | Message::Tx(data) => self.handle_tx(data)?, 494 | Message::Version(data) => self.handle_version(data)?, 495 | } 496 | 497 | Ok(()) 498 | } 499 | } 500 | 501 | fn cmd_to_bytes(cmd: &str) -> [u8; CMD_LEN] { 502 | let mut data = [0; CMD_LEN]; 503 | for (i, d) in cmd.as_bytes().iter().enumerate() { 504 | data[i] = *d; 505 | } 506 | data 507 | } 508 | 509 | fn bytes_to_cmd(bytes: &[u8]) -> Result { 510 | let mut cmd = Vec::new(); 511 | let cmd_bytes = &bytes[..CMD_LEN]; 512 | let data = &bytes[CMD_LEN..]; 513 | for b in cmd_bytes { 514 | if 0 as u8 != *b { 515 | cmd.push(*b); 516 | } 517 | } 518 | info!("cmd: {}", String::from_utf8(cmd.clone())?); 519 | 520 | if cmd == "addr".as_bytes() { 521 | let data: Vec = deserialize(data)?; 522 | Ok(Message::Addr(data)) 523 | } else if cmd == "block".as_bytes() { 524 | let data: Blockmsg = deserialize(data)?; 525 | Ok(Message::Block(data)) 526 | } else if cmd == "inv".as_bytes() { 527 | let data: Invmsg = deserialize(data)?; 528 | Ok(Message::Inv(data)) 529 | } else if cmd == "getblocks".as_bytes() { 530 | let data: GetBlocksmsg = deserialize(data)?; 531 | Ok(Message::GetBlock(data)) 532 | } else if cmd == "getdata".as_bytes() { 533 | let data: GetDatamsg = deserialize(data)?; 534 | Ok(Message::GetData(data)) 535 | } else if cmd == "tx".as_bytes() { 536 | let data: Txmsg = deserialize(data)?; 537 | Ok(Message::Tx(data)) 538 | } else if cmd == "version".as_bytes() { 539 | let data: Versionmsg = deserialize(data)?; 540 | Ok(Message::Version(data)) 541 | } else { 542 | Err(format_err!("Unknown command in the server")) 543 | } 544 | } 545 | 546 | #[cfg(test)] 547 | mod test { 548 | use super::*; 549 | use crate::blockchain::*; 550 | use crate::wallets::*; 551 | 552 | #[test] 553 | fn test_cmd() { 554 | let mut ws = Wallets::new().unwrap(); 555 | let wa1 = ws.create_wallet(); 556 | let bc = Blockchain::create_blockchain(wa1).unwrap(); 557 | let utxo_set = UTXOSet { blockchain: bc }; 558 | let server = Server::new("7878", "localhost:3001", utxo_set).unwrap(); 559 | 560 | let vmsg = Versionmsg { 561 | addr_from: server.node_address.clone(), 562 | best_height: server.get_best_height().unwrap(), 563 | version: VERSION, 564 | }; 565 | let data = serialize(&(cmd_to_bytes("version"), vmsg.clone())).unwrap(); 566 | if let Message::Version(v) = bytes_to_cmd(&data).unwrap() { 567 | assert_eq!(v, vmsg); 568 | } else { 569 | panic!("wrong!"); 570 | } 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.13.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "0.2.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "0.7.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.11.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 34 | dependencies = [ 35 | "winapi", 36 | ] 37 | 38 | [[package]] 39 | name = "atty" 40 | version = "0.2.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 43 | dependencies = [ 44 | "hermit-abi", 45 | "libc", 46 | "winapi", 47 | ] 48 | 49 | [[package]] 50 | name = "autocfg" 51 | version = "1.0.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 54 | 55 | [[package]] 56 | name = "backtrace" 57 | version = "0.3.50" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" 60 | dependencies = [ 61 | "addr2line", 62 | "cfg-if", 63 | "libc", 64 | "miniz_oxide", 65 | "object", 66 | "rustc-demangle", 67 | ] 68 | 69 | [[package]] 70 | name = "bincode" 71 | version = "1.3.1" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" 74 | dependencies = [ 75 | "byteorder", 76 | "serde", 77 | ] 78 | 79 | [[package]] 80 | name = "bitcoin_hashes" 81 | version = "0.7.6" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "b375d62f341cef9cd9e77793ec8f1db3fc9ce2e4d57e982c8fe697a2c16af3b6" 84 | 85 | [[package]] 86 | name = "bitcoincash-addr" 87 | version = "0.5.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ad79afbfd27efc52fc928b198a365a7ee9da8d881a18c16d88764880b675e543" 90 | dependencies = [ 91 | "bitcoin_hashes", 92 | ] 93 | 94 | [[package]] 95 | name = "bitflags" 96 | version = "1.2.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 99 | 100 | [[package]] 101 | name = "block-buffer" 102 | version = "0.9.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 105 | dependencies = [ 106 | "generic-array", 107 | ] 108 | 109 | [[package]] 110 | name = "blockchain-demo" 111 | version = "0.1.0" 112 | dependencies = [ 113 | "bincode", 114 | "bitcoincash-addr", 115 | "clap", 116 | "env_logger", 117 | "failure", 118 | "log", 119 | "merkle-cbt", 120 | "rand 0.4.6", 121 | "rust-crypto", 122 | "serde", 123 | "sha2", 124 | "sled", 125 | ] 126 | 127 | [[package]] 128 | name = "byteorder" 129 | version = "1.3.4" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 132 | 133 | [[package]] 134 | name = "cfg-if" 135 | version = "0.1.10" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 138 | 139 | [[package]] 140 | name = "clap" 141 | version = "2.33.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 144 | dependencies = [ 145 | "ansi_term", 146 | "atty", 147 | "bitflags", 148 | "strsim", 149 | "textwrap", 150 | "unicode-width", 151 | "vec_map", 152 | ] 153 | 154 | [[package]] 155 | name = "cloudabi" 156 | version = "0.1.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" 159 | dependencies = [ 160 | "bitflags", 161 | ] 162 | 163 | [[package]] 164 | name = "cpuid-bool" 165 | version = "0.1.2" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" 168 | 169 | [[package]] 170 | name = "crc32fast" 171 | version = "1.2.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 174 | dependencies = [ 175 | "cfg-if", 176 | ] 177 | 178 | [[package]] 179 | name = "crossbeam-epoch" 180 | version = "0.8.2" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 183 | dependencies = [ 184 | "autocfg", 185 | "cfg-if", 186 | "crossbeam-utils", 187 | "lazy_static", 188 | "maybe-uninit", 189 | "memoffset", 190 | "scopeguard", 191 | ] 192 | 193 | [[package]] 194 | name = "crossbeam-utils" 195 | version = "0.7.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 198 | dependencies = [ 199 | "autocfg", 200 | "cfg-if", 201 | "lazy_static", 202 | ] 203 | 204 | [[package]] 205 | name = "digest" 206 | version = "0.9.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 209 | dependencies = [ 210 | "generic-array", 211 | ] 212 | 213 | [[package]] 214 | name = "env_logger" 215 | version = "0.7.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 218 | dependencies = [ 219 | "atty", 220 | "humantime", 221 | "log", 222 | "regex", 223 | "termcolor", 224 | ] 225 | 226 | [[package]] 227 | name = "failure" 228 | version = "0.1.8" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 231 | dependencies = [ 232 | "backtrace", 233 | "failure_derive", 234 | ] 235 | 236 | [[package]] 237 | name = "failure_derive" 238 | version = "0.1.8" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 241 | dependencies = [ 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | "synstructure", 246 | ] 247 | 248 | [[package]] 249 | name = "fs2" 250 | version = "0.4.3" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" 253 | dependencies = [ 254 | "libc", 255 | "winapi", 256 | ] 257 | 258 | [[package]] 259 | name = "fuchsia-cprng" 260 | version = "0.1.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 263 | 264 | [[package]] 265 | name = "fxhash" 266 | version = "0.2.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 269 | dependencies = [ 270 | "byteorder", 271 | ] 272 | 273 | [[package]] 274 | name = "gcc" 275 | version = "0.3.55" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 278 | 279 | [[package]] 280 | name = "generic-array" 281 | version = "0.14.4" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 284 | dependencies = [ 285 | "typenum", 286 | "version_check", 287 | ] 288 | 289 | [[package]] 290 | name = "gimli" 291 | version = "0.22.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" 294 | 295 | [[package]] 296 | name = "hermit-abi" 297 | version = "0.1.15" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 300 | dependencies = [ 301 | "libc", 302 | ] 303 | 304 | [[package]] 305 | name = "humantime" 306 | version = "1.3.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 309 | dependencies = [ 310 | "quick-error", 311 | ] 312 | 313 | [[package]] 314 | name = "instant" 315 | version = "0.1.6" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" 318 | 319 | [[package]] 320 | name = "lazy_static" 321 | version = "1.4.0" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 324 | 325 | [[package]] 326 | name = "libc" 327 | version = "0.2.76" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" 330 | 331 | [[package]] 332 | name = "lock_api" 333 | version = "0.4.6" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 336 | dependencies = [ 337 | "scopeguard", 338 | ] 339 | 340 | [[package]] 341 | name = "log" 342 | version = "0.4.11" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 345 | dependencies = [ 346 | "cfg-if", 347 | ] 348 | 349 | [[package]] 350 | name = "maybe-uninit" 351 | version = "2.0.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 354 | 355 | [[package]] 356 | name = "memchr" 357 | version = "2.5.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 360 | 361 | [[package]] 362 | name = "memoffset" 363 | version = "0.5.5" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" 366 | dependencies = [ 367 | "autocfg", 368 | ] 369 | 370 | [[package]] 371 | name = "merkle-cbt" 372 | version = "0.2.2" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "0c95c71a8dc57c7ad9b7623cf05711bb4e3daef44f1931c91e7d49c60de693ca" 375 | dependencies = [ 376 | "cfg-if", 377 | ] 378 | 379 | [[package]] 380 | name = "miniz_oxide" 381 | version = "0.4.1" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" 384 | dependencies = [ 385 | "adler", 386 | ] 387 | 388 | [[package]] 389 | name = "object" 390 | version = "0.20.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 393 | 394 | [[package]] 395 | name = "opaque-debug" 396 | version = "0.3.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 399 | 400 | [[package]] 401 | name = "parking_lot" 402 | version = "0.11.0" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" 405 | dependencies = [ 406 | "instant", 407 | "lock_api", 408 | "parking_lot_core", 409 | ] 410 | 411 | [[package]] 412 | name = "parking_lot_core" 413 | version = "0.8.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" 416 | dependencies = [ 417 | "cfg-if", 418 | "cloudabi", 419 | "instant", 420 | "libc", 421 | "redox_syscall", 422 | "smallvec", 423 | "winapi", 424 | ] 425 | 426 | [[package]] 427 | name = "proc-macro2" 428 | version = "1.0.19" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 431 | dependencies = [ 432 | "unicode-xid", 433 | ] 434 | 435 | [[package]] 436 | name = "quick-error" 437 | version = "1.2.3" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 440 | 441 | [[package]] 442 | name = "quote" 443 | version = "1.0.7" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 446 | dependencies = [ 447 | "proc-macro2", 448 | ] 449 | 450 | [[package]] 451 | name = "rand" 452 | version = "0.3.23" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 455 | dependencies = [ 456 | "libc", 457 | "rand 0.4.6", 458 | ] 459 | 460 | [[package]] 461 | name = "rand" 462 | version = "0.4.6" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 465 | dependencies = [ 466 | "fuchsia-cprng", 467 | "libc", 468 | "rand_core 0.3.1", 469 | "rdrand", 470 | "winapi", 471 | ] 472 | 473 | [[package]] 474 | name = "rand_core" 475 | version = "0.3.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 478 | dependencies = [ 479 | "rand_core 0.4.2", 480 | ] 481 | 482 | [[package]] 483 | name = "rand_core" 484 | version = "0.4.2" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 487 | 488 | [[package]] 489 | name = "rdrand" 490 | version = "0.4.0" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 493 | dependencies = [ 494 | "rand_core 0.3.1", 495 | ] 496 | 497 | [[package]] 498 | name = "redox_syscall" 499 | version = "0.1.57" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 502 | 503 | [[package]] 504 | name = "regex" 505 | version = "1.5.6" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 508 | dependencies = [ 509 | "aho-corasick", 510 | "memchr", 511 | "regex-syntax", 512 | ] 513 | 514 | [[package]] 515 | name = "regex-syntax" 516 | version = "0.6.26" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 519 | 520 | [[package]] 521 | name = "rust-crypto" 522 | version = "0.2.36" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 525 | dependencies = [ 526 | "gcc", 527 | "libc", 528 | "rand 0.3.23", 529 | "rustc-serialize", 530 | "time", 531 | ] 532 | 533 | [[package]] 534 | name = "rustc-demangle" 535 | version = "0.1.16" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 538 | 539 | [[package]] 540 | name = "rustc-serialize" 541 | version = "0.3.24" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 544 | 545 | [[package]] 546 | name = "scopeguard" 547 | version = "1.1.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 550 | 551 | [[package]] 552 | name = "serde" 553 | version = "1.0.115" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" 556 | dependencies = [ 557 | "serde_derive", 558 | ] 559 | 560 | [[package]] 561 | name = "serde_derive" 562 | version = "1.0.115" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" 565 | dependencies = [ 566 | "proc-macro2", 567 | "quote", 568 | "syn", 569 | ] 570 | 571 | [[package]] 572 | name = "sha2" 573 | version = "0.9.1" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" 576 | dependencies = [ 577 | "block-buffer", 578 | "cfg-if", 579 | "cpuid-bool", 580 | "digest", 581 | "opaque-debug", 582 | ] 583 | 584 | [[package]] 585 | name = "sled" 586 | version = "0.34.3" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "fcbf56c35b0c3f9bc208fab35e45c3bc03137d3c1a4a85bdf0b8db69aecffb45" 589 | dependencies = [ 590 | "crc32fast", 591 | "crossbeam-epoch", 592 | "crossbeam-utils", 593 | "fs2", 594 | "fxhash", 595 | "libc", 596 | "log", 597 | "parking_lot", 598 | ] 599 | 600 | [[package]] 601 | name = "smallvec" 602 | version = "1.8.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 605 | 606 | [[package]] 607 | name = "strsim" 608 | version = "0.8.0" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 611 | 612 | [[package]] 613 | name = "syn" 614 | version = "1.0.39" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" 617 | dependencies = [ 618 | "proc-macro2", 619 | "quote", 620 | "unicode-xid", 621 | ] 622 | 623 | [[package]] 624 | name = "synstructure" 625 | version = "0.12.4" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 628 | dependencies = [ 629 | "proc-macro2", 630 | "quote", 631 | "syn", 632 | "unicode-xid", 633 | ] 634 | 635 | [[package]] 636 | name = "termcolor" 637 | version = "1.1.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 640 | dependencies = [ 641 | "winapi-util", 642 | ] 643 | 644 | [[package]] 645 | name = "textwrap" 646 | version = "0.11.0" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 649 | dependencies = [ 650 | "unicode-width", 651 | ] 652 | 653 | [[package]] 654 | name = "time" 655 | version = "0.1.44" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 658 | dependencies = [ 659 | "libc", 660 | "wasi", 661 | "winapi", 662 | ] 663 | 664 | [[package]] 665 | name = "typenum" 666 | version = "1.12.0" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 669 | 670 | [[package]] 671 | name = "unicode-width" 672 | version = "0.1.8" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 675 | 676 | [[package]] 677 | name = "unicode-xid" 678 | version = "0.2.1" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 681 | 682 | [[package]] 683 | name = "vec_map" 684 | version = "0.8.2" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 687 | 688 | [[package]] 689 | name = "version_check" 690 | version = "0.9.2" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 693 | 694 | [[package]] 695 | name = "wasi" 696 | version = "0.10.0+wasi-snapshot-preview1" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 699 | 700 | [[package]] 701 | name = "winapi" 702 | version = "0.3.9" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 705 | dependencies = [ 706 | "winapi-i686-pc-windows-gnu", 707 | "winapi-x86_64-pc-windows-gnu", 708 | ] 709 | 710 | [[package]] 711 | name = "winapi-i686-pc-windows-gnu" 712 | version = "0.4.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 715 | 716 | [[package]] 717 | name = "winapi-util" 718 | version = "0.1.5" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 721 | dependencies = [ 722 | "winapi", 723 | ] 724 | 725 | [[package]] 726 | name = "winapi-x86_64-pc-windows-gnu" 727 | version = "0.4.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 730 | --------------------------------------------------------------------------------