├── .gitignore ├── README.md ├── Cargo.toml ├── src ├── lib.rs ├── config.rs ├── node.rs ├── wallets.rs ├── proof_of_work.rs ├── wallet.rs ├── block.rs ├── memory_pool.rs ├── utils.rs ├── utxo_set.rs ├── main.rs ├── transaction.rs ├── blockchain.rs └── server.rs ├── tests └── cli.rs ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Blockchain in Rust 2 | 3 | Rust implementation of the [Jeiwan/blockchain_go](https://github.com/Jeiwan/blockchain_go). 4 | 5 | Blog: https://www.cnblogs.com/marszuo/p/15763988.html. 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blockchain_rust" 3 | authors = ["Mars Zuo "] 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ring = "0.16.20" 11 | data-encoding = "2.3.2" 12 | num-bigint = "0.4.3" 13 | sled = "0.34.7" 14 | serde = { version = "1.0.132", features = ["derive"] } 15 | bincode = "1.3.3" 16 | structopt = "0.3.25" 17 | clap = "2.34.0" 18 | rust-crypto = "0.2.36" 19 | bs58 = "0.4.0" 20 | rustc-serialize = "0.3.24" 21 | uuid = { version = "0.8.2", features = ["v4"]} 22 | log = "0.4.14" 23 | env_logger = "0.9.0" 24 | serde_json = "1.0.73" 25 | once_cell = "1.9.0" 26 | 27 | [dev-dependencies] 28 | assert_cmd = "0.11.0" -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod block; 2 | use block::Block; 3 | 4 | mod blockchain; 5 | pub use blockchain::Blockchain; 6 | 7 | mod utxo_set; 8 | pub use utxo_set::UTXOSet; 9 | 10 | mod proof_of_work; 11 | use proof_of_work::ProofOfWork; 12 | 13 | mod transaction; 14 | pub use transaction::Transaction; 15 | 16 | mod wallet; 17 | pub use wallet::convert_address; 18 | pub use wallet::hash_pub_key; 19 | pub use wallet::validate_address; 20 | pub use wallet::Wallet; 21 | pub use wallet::ADDRESS_CHECK_SUM_LEN; 22 | 23 | mod wallets; 24 | pub use wallets::Wallets; 25 | 26 | mod server; 27 | pub use server::send_tx; 28 | pub use server::Package; 29 | pub use server::Server; 30 | pub use server::CENTERAL_NODE; 31 | 32 | mod node; 33 | pub use node::Nodes; 34 | 35 | mod memory_pool; 36 | pub use memory_pool::BlockInTransit; 37 | pub use memory_pool::MemoryPool; 38 | 39 | mod config; 40 | pub use config::Config; 41 | pub use config::GLOBAL_CONFIG; 42 | 43 | pub mod utils; 44 | use utils::base58_decode; 45 | use utils::base58_encode; 46 | use utils::current_timestamp; 47 | use utils::ecdsa_p256_sha256_sign_digest; 48 | use utils::ecdsa_p256_sha256_sign_verify; 49 | use utils::new_key_pair; 50 | use utils::ripemd160_digest; 51 | use utils::sha256_digest; 52 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use std::collections::HashMap; 3 | use std::env; 4 | use std::sync::RwLock; 5 | 6 | pub static GLOBAL_CONFIG: Lazy = Lazy::new(|| Config::new()); 7 | 8 | /// 默认的节点地址 9 | static DEFAULT_NODE_ADDR: &str = "127.0.0.1:2001"; 10 | 11 | const NODE_ADDRESS_KEY: &str = "NODE_ADDRESS"; 12 | const MINING_ADDRESS_KEY: &str = "MINING_ADDRESS"; 13 | 14 | /// Node 配置 15 | pub struct Config { 16 | inner: RwLock>, 17 | } 18 | 19 | impl Config { 20 | pub fn new() -> Config { 21 | // 从环境变量获取节点地址 22 | let mut node_addr = String::from(DEFAULT_NODE_ADDR); 23 | if let Ok(addr) = env::var("NODE_ADDRESS") { 24 | node_addr = addr; 25 | } 26 | let mut map = HashMap::new(); 27 | map.insert(String::from(NODE_ADDRESS_KEY), node_addr); 28 | 29 | Config { 30 | inner: RwLock::new(map), 31 | } 32 | } 33 | 34 | /// 获取节点地址 35 | pub fn get_node_addr(&self) -> String { 36 | let inner = self.inner.read().unwrap(); 37 | inner.get(NODE_ADDRESS_KEY).unwrap().clone() 38 | } 39 | 40 | /// 设置矿工钱包地址 41 | pub fn set_mining_addr(&self, addr: String) { 42 | let mut inner = self.inner.write().unwrap(); 43 | let _ = inner.insert(String::from(MINING_ADDRESS_KEY), addr); 44 | } 45 | 46 | /// 获取矿工钱包地址 47 | pub fn get_mining_addr(&self) -> Option { 48 | let inner = self.inner.read().unwrap(); 49 | if let Some(addr) = inner.get(MINING_ADDRESS_KEY) { 50 | return Some(addr.clone()); 51 | } 52 | None 53 | } 54 | 55 | /// 检查矿工节点 56 | pub fn is_miner(&self) -> bool { 57 | let inner = self.inner.read().unwrap(); 58 | inner.contains_key(MINING_ADDRESS_KEY) 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::NODE_ADDRESS_KEY; 65 | use crate::Config; 66 | use std::env; 67 | 68 | #[test] 69 | fn new_config() { 70 | env::set_var(NODE_ADDRESS_KEY, "127.0.0.1:2002"); 71 | 72 | let config = Config::new(); 73 | let node_addr = config.get_node_addr(); 74 | println!("{}", node_addr) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/cli.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::{CommandCargoExt, OutputAssertExt}; 2 | use std::process::Command; 3 | 4 | #[test] 5 | fn client_createblockchain() { 6 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 7 | .unwrap() 8 | .arg("createblockchain") 9 | .arg("1NA3ZoWS1xHkvhTPXU4PEX9ABR5gJ1CHMR") 10 | .assert() 11 | .success(); 12 | } 13 | 14 | #[test] 15 | fn client_createwallet() { 16 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 17 | .unwrap() 18 | .arg("createwallet") 19 | .assert() 20 | .success(); 21 | } 22 | 23 | #[test] 24 | fn client_get_addresses() { 25 | let assert = Command::cargo_bin(env!("CARGO_PKG_NAME")) 26 | .unwrap() 27 | .arg("listaddresses") 28 | .assert() 29 | .success(); 30 | 31 | let output_bytes = assert.get_output().stdout.as_slice(); 32 | println!("{}", String::from_utf8(output_bytes.to_vec()).unwrap()) 33 | } 34 | 35 | #[test] 36 | fn client_get_balance() { 37 | let command = Command::cargo_bin(env!("CARGO_PKG_NAME")) 38 | .unwrap() 39 | .arg("getbalance") 40 | .arg("1NA3ZoWS1xHkvhTPXU4PEX9ABR5gJ1CHMR") 41 | .assert() 42 | .success(); 43 | 44 | let output_bytes = command.get_output().stdout.as_slice(); 45 | println!("{}", String::from_utf8(output_bytes.to_vec()).unwrap()) 46 | } 47 | 48 | #[test] 49 | fn client_send() { 50 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 51 | .unwrap() 52 | .arg("send") 53 | .arg("1NA3ZoWS1xHkvhTPXU4PEX9ABR5gJ1CHMR") 54 | .arg("1PipUzybS5DcMvNQe3XMiLML9z7fZdpz35") 55 | .arg("5") 56 | .arg("1") 57 | .assert() 58 | .success(); 59 | } 60 | 61 | #[test] 62 | fn client_printchain() { 63 | let command = Command::cargo_bin(env!("CARGO_PKG_NAME")) 64 | .unwrap() 65 | .arg("printchain") 66 | .assert() 67 | .success(); 68 | 69 | let output_bytes = command.get_output().stdout.as_slice(); 70 | println!("{}", String::from_utf8(output_bytes.to_vec()).unwrap()) 71 | } 72 | 73 | #[test] 74 | fn client_reindexutxo() { 75 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 76 | .unwrap() 77 | .arg("reindexutxo") 78 | .assert() 79 | .success(); 80 | } 81 | 82 | #[test] 83 | fn client_startnode() { 84 | Command::cargo_bin(env!("CARGO_PKG_NAME")) 85 | .unwrap() 86 | .arg("startnode") 87 | .arg("1NA3ZoWS1xHkvhTPXU4PEX9ABR5gJ1CHMR") 88 | .assert() 89 | .success(); 90 | } 91 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::sync::RwLock; 3 | 4 | #[derive(Clone)] 5 | pub struct Node { 6 | addr: String, 7 | } 8 | 9 | impl Node { 10 | fn new(addr: String) -> Node { 11 | Node { addr } 12 | } 13 | 14 | pub fn get_addr(&self) -> String { 15 | self.addr.clone() 16 | } 17 | 18 | pub fn parse_socket_addr(&self) -> SocketAddr { 19 | self.addr.parse().unwrap() 20 | } 21 | } 22 | 23 | pub struct Nodes { 24 | inner: RwLock>, 25 | } 26 | 27 | impl Nodes { 28 | pub fn new() -> Nodes { 29 | Nodes { 30 | inner: RwLock::new(vec![]), 31 | } 32 | } 33 | 34 | pub fn add_node(&self, addr: String) { 35 | let mut inner = self.inner.write().unwrap(); 36 | if let None = inner.iter().position(|x| x.get_addr().eq(addr.as_str())) { 37 | inner.push(Node::new(addr)); 38 | } 39 | } 40 | 41 | pub fn evict_node(&self, addr: &str) { 42 | let mut inner = self.inner.write().unwrap(); 43 | if let Some(idx) = inner.iter().position(|x| x.get_addr().eq(addr)) { 44 | inner.remove(idx); 45 | } 46 | } 47 | 48 | pub fn first(&self) -> Option { 49 | let inner = self.inner.read().unwrap(); 50 | if let Some(node) = inner.first() { 51 | return Some(node.clone()); 52 | } 53 | None 54 | } 55 | 56 | pub fn get_nodes(&self) -> Vec { 57 | self.inner.read().unwrap().to_vec() 58 | } 59 | 60 | pub fn len(&self) -> usize { 61 | self.inner.read().unwrap().len() 62 | } 63 | 64 | pub fn node_is_known(&self, addr: &str) -> bool { 65 | let inner = self.inner.read().unwrap(); 66 | if let Some(_) = inner.iter().position(|x| x.get_addr().eq(addr)) { 67 | return true; 68 | } 69 | return false; 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | 76 | #[test] 77 | fn test_remove() { 78 | let nodes = super::Nodes::new(); 79 | nodes.add_node(String::from("127.0.0.1:2001")); 80 | nodes.add_node(String::from("127.0.0.1:3001")); 81 | nodes.add_node(String::from("127.0.0.1:4001")); 82 | 83 | let node = nodes.first().unwrap(); 84 | assert_eq!(node.get_addr(), "127.0.0.1:2001"); 85 | 86 | nodes.evict_node("127.0.0.1:2001"); 87 | let node = nodes.first().unwrap(); 88 | assert_eq!(node.get_addr(), "127.0.0.1:3001"); 89 | } 90 | 91 | #[test] 92 | fn test_node_is_known() { 93 | let nodes = super::Nodes::new(); 94 | nodes.add_node(String::from("127.0.0.1:2001")); 95 | nodes.add_node(String::from("127.0.0.1:3001")); 96 | 97 | assert_eq!(nodes.node_is_known("127.0.0.1:2001"), true); 98 | assert_eq!(nodes.node_is_known("127.0.0.1:4001"), false); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/wallets.rs: -------------------------------------------------------------------------------- 1 | use crate::Wallet; 2 | use std::collections::HashMap; 3 | use std::env::current_dir; 4 | use std::fs::{File, OpenOptions}; 5 | use std::io::{BufWriter, Read, Write}; 6 | 7 | pub const WALLET_FILE: &str = "wallet.dat"; 8 | 9 | pub struct Wallets { 10 | wallets: HashMap, 11 | } 12 | 13 | impl Wallets { 14 | pub fn new() -> Wallets { 15 | let mut wallets = Wallets { 16 | wallets: HashMap::new(), 17 | }; 18 | wallets.load_from_file(); 19 | return wallets; 20 | } 21 | 22 | /// 创建一个钱包 23 | pub fn create_wallet(&mut self) -> String { 24 | let wallet = Wallet::new(); 25 | let address = wallet.get_address(); 26 | self.wallets.insert(address.clone(), wallet); 27 | self.save_to_file(); 28 | return address; 29 | } 30 | 31 | pub fn get_addresses(&self) -> Vec { 32 | let mut addresses = vec![]; 33 | for (address, _) in &self.wallets { 34 | addresses.push(address.clone()) 35 | } 36 | return addresses; 37 | } 38 | 39 | /// 通过钱包地址查询钱包 40 | pub fn get_wallet(&self, address: &str) -> Option<&Wallet> { 41 | if let Some(wallet) = self.wallets.get(address) { 42 | return Some(wallet); 43 | } 44 | None 45 | } 46 | 47 | /// 从本地文件加载钱包 48 | pub fn load_from_file(&mut self) { 49 | let path = current_dir().unwrap().join(WALLET_FILE); 50 | if !path.exists() { 51 | return; 52 | } 53 | let mut file = File::open(path).unwrap(); 54 | let metadata = file.metadata().expect("unable to read metadata"); 55 | let mut buf = vec![0; metadata.len() as usize]; 56 | let _ = file.read(&mut buf).expect("buffer overflow"); 57 | let wallets = bincode::deserialize(&buf[..]).expect("unable to deserialize file data"); 58 | self.wallets = wallets; 59 | } 60 | 61 | /// 钱包持久化到本地文件 62 | fn save_to_file(&self) { 63 | let path = current_dir().unwrap().join(WALLET_FILE); 64 | let file = OpenOptions::new() 65 | .create(true) 66 | .write(true) 67 | .open(&path) 68 | .expect("unable to open wallet.dat"); 69 | let mut writer = BufWriter::new(file); 70 | let wallets_bytes = bincode::serialize(&self.wallets).expect("unable to serialize wallets"); 71 | writer.write(wallets_bytes.as_slice()).unwrap(); 72 | let _ = writer.flush(); 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use crate::Wallets; 79 | 80 | #[test] 81 | fn test_new_wallets() { 82 | let mut wallets = Wallets::new(); 83 | let address = wallets.create_wallet(); 84 | println!("The new wallet address is {}", address); 85 | } 86 | 87 | #[test] 88 | fn test_get_addresses() { 89 | let addresses = Wallets::new().get_addresses(); 90 | // 13SDifQUyLGCwFjh64vihoWQcGsTozHuQb 91 | // 1LecNaLYsDoxRtxBBWKMNbLvccftmFZWcv 92 | println!("{:?}", addresses); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/proof_of_work.rs: -------------------------------------------------------------------------------- 1 | use crate::Block; 2 | use data_encoding::HEXLOWER; 3 | use num_bigint::{BigInt, Sign}; 4 | use std::borrow::Borrow; 5 | use std::ops::ShlAssign; 6 | 7 | pub struct ProofOfWork { 8 | block: Block, 9 | target: BigInt, 10 | } 11 | 12 | /// 难度值,这里表示哈希的前20位必须是0 13 | const TARGET_BITS: i32 = 8; 14 | /// 限制 nonce 避免整型溢出 15 | const MAX_NONCE: i64 = i64::MAX; 16 | 17 | impl ProofOfWork { 18 | pub fn new_proof_of_work(block: Block) -> ProofOfWork { 19 | let mut target = BigInt::from(1); 20 | // target 等于 1 左移 256 - TARGET_BITS 位 21 | target.shl_assign(256 - TARGET_BITS); 22 | ProofOfWork { block, target } 23 | } 24 | 25 | /// 工作量证明用到的数据 26 | fn prepare_data(&self, nonce: i64) -> Vec { 27 | let pre_block_hash = self.block.get_pre_block_hash(); 28 | let transactions_hash = self.block.hash_transactions(); 29 | let timestamp = self.block.get_timestamp(); 30 | let mut data_bytes = vec![]; 31 | data_bytes.extend(pre_block_hash.as_bytes()); 32 | data_bytes.extend(transactions_hash); 33 | data_bytes.extend(timestamp.to_be_bytes()); 34 | data_bytes.extend(TARGET_BITS.to_be_bytes()); 35 | data_bytes.extend(nonce.to_be_bytes()); 36 | return data_bytes; 37 | } 38 | 39 | /// 工作量证明的核心就是寻找有效的哈希 40 | pub fn run(&self) -> (i64, String) { 41 | let mut nonce = 0; 42 | let mut hash = Vec::new(); 43 | println!("Mining the block"); 44 | while nonce < MAX_NONCE { 45 | let data = self.prepare_data(nonce); 46 | hash = crate::sha256_digest(data.as_slice()); 47 | let hash_int = BigInt::from_bytes_be(Sign::Plus, hash.as_slice()); 48 | 49 | // 1.在比特币中,当一个块被挖出来以后,“target bits” 代表了区块头里存储的难度,也就是开头有多少个 0。 50 | // 2.这里的 20 指的是算出来的哈希前 20 位必须是 0,如果用 16 进制表示,就是前 5 位必须是 0,这一点从 51 | // 最后的输出可以看出来。 52 | // 例如:target 16进制输出是 0000100000000000000000000000000000000000000000000000000000000000 53 | // 目前我们并不会实现一个动态调整目标的算法,所以将难度定义为一个全局的常量即可。 54 | // 3.将哈希与目标数 target 进行比较:先把哈希转换成一个大整数,然后检测它是否小于目标,小就是有效的,反之无效。 55 | if hash_int.lt(self.target.borrow()) { 56 | println!("{}", HEXLOWER.encode(hash.as_slice())); 57 | break; 58 | } else { 59 | nonce += 1; 60 | } 61 | } 62 | println!(); 63 | return (nonce, HEXLOWER.encode(hash.as_slice())); 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::TARGET_BITS; 70 | use data_encoding::HEXLOWER; 71 | use num_bigint::BigInt; 72 | use std::ops::ShlAssign; 73 | 74 | #[test] 75 | fn test_bigint_from_bytes() { 76 | let a = BigInt::from(256); // 0 ~ 255 77 | let (s, vec) = a.to_bytes_be(); 78 | println!("{:?}, {:?}", s, vec); 79 | 80 | // big-endian 81 | let b = BigInt::from_signed_bytes_be(vec.as_slice()); 82 | println!("{}", b) 83 | } 84 | 85 | #[test] 86 | fn test_target_bits() { 87 | let mut target = BigInt::from(1); 88 | target.shl_assign(256 - TARGET_BITS); 89 | println!("{}", target); // output: 6901746346790563787434755862277025452451108972170386555162524223799296 90 | 91 | // 16进制输出, 大端序 92 | let (_, vec) = target.to_bytes_be(); 93 | let target_hex = HEXLOWER.encode(vec.as_slice()); 94 | println!("{}", target_hex) // output: 100000000000000000000000000000000000000000000000000000000000 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/wallet.rs: -------------------------------------------------------------------------------- 1 | use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | const VERSION: u8 = 0x00; 5 | pub const ADDRESS_CHECK_SUM_LEN: usize = 4; 6 | 7 | #[derive(Clone, Serialize, Deserialize)] 8 | pub struct Wallet { 9 | pkcs8: Vec, 10 | public_key: Vec, // 原生的公钥 11 | } 12 | 13 | impl Wallet { 14 | /// 创建一个钱包 15 | pub fn new() -> Wallet { 16 | let pkcs8 = crate::new_key_pair(); 17 | let key_pair = 18 | EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref()).unwrap(); 19 | let public_key = key_pair.public_key().as_ref().to_vec(); 20 | Wallet { pkcs8, public_key } 21 | } 22 | 23 | /// 获取钱包地址 24 | /// 这里得到了一个真实的BTC地址,可以在 (Tokenview)[https://tokenview.com/cn/search/173EuX6KuB1EiWYEKyaQud6x91VNjkM3Vu] 查询它的余额. 25 | /// 不过我可以负责任地说,无论生成一个新的地址多少次,检查它的余额都是 0。这就是为什么选择一个合适的公钥加密算法是如此重要:考虑到私钥是随机数,生成 26 | /// 同一个数字的概率必须是尽可能地低。理想情况下,必须是低到“永远”不会重复。 27 | /// 另外,注意:你并不需要连接到一个比特币节点来获得一个地址。地址生成算法使用的多种开源算法可以通过很多编程语言和库实现。 28 | pub fn get_address(&self) -> String { 29 | let pub_key_hash = hash_pub_key(self.public_key.as_slice()); 30 | let mut payload: Vec = vec![]; 31 | payload.push(VERSION); 32 | payload.extend(pub_key_hash.as_slice()); 33 | let checksum = checksum(payload.as_slice()); 34 | payload.extend(checksum.as_slice()); 35 | // version + pub_key_hash + checksum 36 | crate::base58_encode(payload.as_slice()) 37 | } 38 | 39 | pub fn get_public_key(&self) -> &[u8] { 40 | self.public_key.as_slice() 41 | } 42 | 43 | pub fn get_pkcs8(&self) -> &[u8] { 44 | self.pkcs8.as_slice() 45 | } 46 | } 47 | 48 | /// 计算公钥哈希 49 | pub fn hash_pub_key(pub_key: &[u8]) -> Vec { 50 | let pub_key_sha256 = crate::sha256_digest(pub_key); 51 | crate::ripemd160_digest(pub_key_sha256.as_slice()) 52 | } 53 | 54 | /// 计算校验和 55 | fn checksum(payload: &[u8]) -> Vec { 56 | let first_sha = crate::sha256_digest(payload); 57 | let second_sha = crate::sha256_digest(first_sha.as_slice()); 58 | second_sha[0..ADDRESS_CHECK_SUM_LEN].to_vec() 59 | } 60 | 61 | /// 验证地址有效 62 | pub fn validate_address(address: &str) -> bool { 63 | let payload = crate::base58_decode(address); 64 | let actual_checksum = payload[payload.len() - ADDRESS_CHECK_SUM_LEN..].to_vec(); 65 | let version = payload[0]; 66 | let pub_key_hash = payload[1..payload.len() - ADDRESS_CHECK_SUM_LEN].to_vec(); 67 | 68 | let mut target_vec = vec![]; 69 | target_vec.push(version); 70 | target_vec.extend(pub_key_hash); 71 | let target_checksum = checksum(target_vec.as_slice()); 72 | actual_checksum.eq(target_checksum.as_slice()) 73 | } 74 | 75 | /// 通过公钥哈希计算地址 76 | pub fn convert_address(pub_hash_key: &[u8]) -> String { 77 | let mut payload: Vec = vec![]; 78 | payload.push(VERSION); 79 | payload.extend(pub_hash_key); 80 | let checksum = checksum(payload.as_slice()); 81 | payload.extend(checksum.as_slice()); 82 | crate::base58_encode(payload.as_slice()) 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use crate::wallet::validate_address; 88 | 89 | #[test] 90 | pub fn test_new_wallet() { 91 | let address = crate::Wallet::new().get_address(); 92 | println!("The address is {}", address) 93 | } 94 | 95 | #[test] 96 | pub fn test_validate_address() { 97 | // BTC 创世块:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 98 | let valid = validate_address("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); 99 | assert!(valid); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | use crate::{ProofOfWork, Transaction}; 2 | use serde::{Deserialize, Serialize}; 3 | use sled::IVec; 4 | 5 | #[derive(Clone, Serialize, Deserialize)] 6 | pub struct Block { 7 | timestamp: i64, // 区块时间戳 8 | pre_block_hash: String, // 上一区块的哈希值 9 | hash: String, // 当前区块的哈希值 10 | transactions: Vec, // 交易数据 11 | nonce: i64, // 计数器 12 | height: usize, // 区块链中节点的高度 13 | } 14 | 15 | impl Block { 16 | /// 新建一个区块 17 | pub fn new_block(pre_block_hash: String, transactions: &[Transaction], height: usize) -> Block { 18 | let mut block = Block { 19 | timestamp: crate::current_timestamp(), 20 | pre_block_hash, 21 | hash: String::new(), 22 | transactions: transactions.to_vec(), 23 | nonce: 0, 24 | height, 25 | }; 26 | // 挖矿计算哈希 27 | let pow = ProofOfWork::new_proof_of_work(block.clone()); 28 | let (nonce, hash) = pow.run(); 29 | block.nonce = nonce; 30 | block.hash = hash; 31 | return block; 32 | } 33 | 34 | /// 从字节数组反序列化 35 | pub fn deserialize(bytes: &[u8]) -> Block { 36 | bincode::deserialize(bytes).unwrap() 37 | } 38 | 39 | /// 区块序列化 40 | pub fn serialize(&self) -> Vec { 41 | bincode::serialize(self).unwrap().to_vec() 42 | } 43 | 44 | /// 生成创世块 45 | pub fn generate_genesis_block(transaction: &Transaction) -> Block { 46 | let transactions = vec![transaction.clone()]; 47 | return Block::new_block(String::from("None"), &transactions, 0); 48 | } 49 | 50 | /// 计算区块里所有交易的哈希 51 | pub fn hash_transactions(&self) -> Vec { 52 | let mut txhashs = vec![]; 53 | for transaction in &self.transactions { 54 | txhashs.extend(transaction.get_id()); 55 | } 56 | crate::sha256_digest(txhashs.as_slice()) 57 | } 58 | 59 | pub fn get_transactions(&self) -> &[Transaction] { 60 | self.transactions.as_slice() 61 | } 62 | 63 | pub fn get_pre_block_hash(&self) -> String { 64 | self.pre_block_hash.clone() 65 | } 66 | 67 | pub fn get_hash(&self) -> &str { 68 | self.hash.as_str() 69 | } 70 | 71 | pub fn get_hash_bytes(&self) -> Vec { 72 | self.hash.as_bytes().to_vec() 73 | } 74 | 75 | pub fn get_timestamp(&self) -> i64 { 76 | self.timestamp 77 | } 78 | 79 | pub fn get_height(&self) -> usize { 80 | self.height 81 | } 82 | } 83 | 84 | impl From for IVec { 85 | fn from(b: Block) -> Self { 86 | let bytes = bincode::serialize(&b).unwrap(); 87 | Self::from(bytes) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::Block; 94 | use crate::Transaction; 95 | 96 | #[test] 97 | fn test_new_block() { 98 | let block = Block::new_block( 99 | String::from("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"), 100 | &vec![], 101 | 0, 102 | ); 103 | println!("new block hash is {}", block.hash) 104 | } 105 | 106 | #[test] 107 | fn test_block_serialize() { 108 | let tx = Transaction::new_coinbase_tx("Genesis"); 109 | let block = Block::new_block( 110 | String::from("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"), 111 | &vec![tx], 112 | 0, 113 | ); 114 | let block_bytes = block.serialize(); 115 | let desc_block = Block::deserialize(&block_bytes[..]); 116 | assert_eq!(block.hash, desc_block.hash) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/memory_pool.rs: -------------------------------------------------------------------------------- 1 | use crate::Transaction; 2 | use data_encoding::HEXLOWER; 3 | use std::collections::HashMap; 4 | use std::sync::RwLock; 5 | 6 | /// 交易内存池 ( K -> txid_hex, V => Transaction ) 7 | pub struct MemoryPool { 8 | inner: RwLock>, 9 | } 10 | 11 | impl MemoryPool { 12 | pub fn new() -> MemoryPool { 13 | MemoryPool { 14 | inner: RwLock::new(HashMap::new()), 15 | } 16 | } 17 | 18 | pub fn containes(&self, txid_hex: &str) -> bool { 19 | self.inner.read().unwrap().contains_key(txid_hex) 20 | } 21 | 22 | pub fn add(&self, tx: Transaction) { 23 | let txid_hex = HEXLOWER.encode(tx.get_id()); 24 | self.inner.write().unwrap().insert(txid_hex, tx); 25 | } 26 | 27 | pub fn get(&self, txid_hex: &str) -> Option { 28 | if let Some(tx) = self.inner.read().unwrap().get(txid_hex) { 29 | return Some(tx.clone()); 30 | } 31 | None 32 | } 33 | 34 | pub fn remove(&self, txid_hex: &str) { 35 | let mut inner = self.inner.write().unwrap(); 36 | inner.remove(txid_hex); 37 | } 38 | 39 | pub fn get_all(&self) -> Vec { 40 | let inner = self.inner.read().unwrap(); 41 | let mut txs = vec![]; 42 | for (_, v) in inner.iter() { 43 | txs.push(v.clone()); 44 | } 45 | return txs; 46 | } 47 | 48 | pub fn len(&self) -> usize { 49 | self.inner.read().unwrap().len() 50 | } 51 | } 52 | 53 | /// 传输中的块, 用于来跟踪已下载的块, 这能够实现从不同的节点下载块. 54 | pub struct BlockInTransit { 55 | inner: RwLock>>, 56 | } 57 | 58 | impl BlockInTransit { 59 | pub fn new() -> BlockInTransit { 60 | BlockInTransit { 61 | inner: RwLock::new(vec![]), 62 | } 63 | } 64 | 65 | pub fn add_blocks(&self, blocks: &[Vec]) { 66 | let mut inner = self.inner.write().unwrap(); 67 | for hash in blocks { 68 | inner.push(hash.to_vec()); 69 | } 70 | } 71 | 72 | pub fn first(&self) -> Option> { 73 | let inner = self.inner.read().unwrap(); 74 | if let Some(block_hash) = inner.first() { 75 | return Some(block_hash.to_vec()); 76 | } 77 | None 78 | } 79 | 80 | pub fn remove(&self, block_hash: &[u8]) { 81 | let mut inner = self.inner.write().unwrap(); 82 | if let Some(idx) = inner.iter().position(|x| x.eq(block_hash)) { 83 | inner.remove(idx); 84 | } 85 | } 86 | 87 | pub fn clear(&self) { 88 | let mut inner = self.inner.write().unwrap(); 89 | inner.clear(); 90 | } 91 | 92 | pub fn len(&self) -> usize { 93 | self.inner.read().unwrap().len() 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | mod tests { 99 | use super::{BlockInTransit, MemoryPool}; 100 | use crate::Transaction; 101 | use data_encoding::HEXLOWER; 102 | 103 | #[test] 104 | fn test_memory_pool() { 105 | let pool = MemoryPool::new(); 106 | let tx = Transaction::new_coinbase_tx("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); 107 | let txid_hex = HEXLOWER.encode(tx.get_id()); 108 | pool.add(tx); 109 | let option = pool.get(txid_hex.as_str()); 110 | assert!(option.is_some()); 111 | 112 | pool.remove(txid_hex.as_str()); 113 | let option = pool.get(txid_hex.as_str()); 114 | assert!(option.is_none()); 115 | } 116 | 117 | #[test] 118 | fn test_blocks_in_transit() { 119 | let mut block_hashs = vec![]; 120 | block_hashs.push("a123".as_bytes().to_vec()); 121 | block_hashs.push("b123".as_bytes().to_vec()); 122 | block_hashs.push("c123".as_bytes().to_vec()); 123 | 124 | let transit = BlockInTransit::new(); 125 | transit.add_blocks(&block_hashs); 126 | assert_eq!(transit.first().unwrap(), "a123".as_bytes().to_vec()); 127 | 128 | transit.remove("a123".as_bytes()); 129 | assert_eq!(transit.first().unwrap(), "b123".as_bytes().to_vec()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crypto::digest::Digest; 2 | use ring::digest::{Context, SHA256}; 3 | use ring::rand::SystemRandom; 4 | use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING}; 5 | use std::iter::repeat; 6 | use std::time::{SystemTime, UNIX_EPOCH}; 7 | 8 | /// 获取当前时间戳,单位:ms 9 | pub fn current_timestamp() -> i64 { 10 | SystemTime::now() 11 | .duration_since(UNIX_EPOCH) 12 | .expect("Time went backwards") 13 | .as_millis() as i64 14 | } 15 | 16 | /// 计算 sha256 哈希值 17 | pub fn sha256_digest(data: &[u8]) -> Vec { 18 | let mut context = Context::new(&SHA256); 19 | context.update(data); 20 | let digest = context.finish(); 21 | digest.as_ref().to_vec() 22 | } 23 | 24 | /// 计算 ripemd160 哈希值 25 | pub fn ripemd160_digest(data: &[u8]) -> Vec { 26 | let mut ripemd160 = crypto::ripemd160::Ripemd160::new(); 27 | ripemd160.input(data); 28 | let mut buf: Vec = repeat(0).take(ripemd160.output_bytes()).collect(); 29 | ripemd160.result(&mut buf); 30 | return buf; 31 | } 32 | 33 | /// base58 编码 34 | pub fn base58_encode(data: &[u8]) -> String { 35 | bs58::encode(data).into_string() 36 | } 37 | 38 | /// base58 解码 39 | pub fn base58_decode(data: &str) -> Vec { 40 | bs58::decode(data).into_vec().unwrap() 41 | } 42 | 43 | /// 创建密钥对(椭圆曲线加密) 44 | pub fn new_key_pair() -> Vec { 45 | let rng = SystemRandom::new(); 46 | let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng).unwrap(); 47 | pkcs8.as_ref().to_vec() 48 | } 49 | 50 | /// ECDSA P256 SHA256 签名 51 | pub fn ecdsa_p256_sha256_sign_digest(pkcs8: &[u8], message: &[u8]) -> Vec { 52 | let key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8).unwrap(); 53 | let rng = ring::rand::SystemRandom::new(); 54 | key_pair.sign(&rng, message).unwrap().as_ref().to_vec() 55 | } 56 | 57 | /// ECDSA P256 SHA256 签名验证 58 | pub fn ecdsa_p256_sha256_sign_verify(public_key: &[u8], signature: &[u8], message: &[u8]) -> bool { 59 | let peer_public_key = 60 | ring::signature::UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, public_key); 61 | let result = peer_public_key.verify(message, signature.as_ref()); 62 | result.is_ok() 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use crate::new_key_pair; 68 | use data_encoding::HEXLOWER; 69 | use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; 70 | 71 | #[test] 72 | fn test_sha256_digest() { 73 | // sha256 会产生256位的哈希值,作为消息的摘要。这个摘要相当于一个32个字节的数组,通常有一个长度为64的16进制 74 | // 字符串表示,其中一个字节等于8位,一个16进制的字符长度为4位。 75 | let digest = crate::sha256_digest("hello".as_bytes()); 76 | // 16进制编码输出 77 | let hex_digest = HEXLOWER.encode(digest.as_slice()); 78 | println!("SHA-256 digest is {}", hex_digest) 79 | } 80 | 81 | #[test] 82 | fn test_ripemd160() { 83 | let bytes = crate::ripemd160_digest("mars".as_bytes()); 84 | let hex_str = HEXLOWER.encode(bytes.as_slice()); 85 | // dd2324928f0552d4f4c6e57d9e5f6009ab085d85 86 | println!("ripemd160 digest is {}", hex_str) 87 | } 88 | 89 | #[test] 90 | fn test_base58() { 91 | let sign = "dd2324928f0552d4f4c6e57d9e5f6009ab085d85"; 92 | let base58_sign = crate::base58_encode(sign.as_bytes()); 93 | 94 | let decode_bytes = crate::base58_decode(base58_sign.as_str()); 95 | let decode_str = String::from_utf8(decode_bytes).unwrap(); 96 | assert_eq!(sign, decode_str.as_str()); 97 | } 98 | 99 | #[test] 100 | fn test_ecdsa_sign_and_verify() { 101 | const MESSAGE: &[u8] = b"hello, world"; 102 | let pkcs8 = new_key_pair(); 103 | // 签名 104 | let signature = crate::ecdsa_p256_sha256_sign_digest(pkcs8.as_slice(), MESSAGE); 105 | 106 | // 签名验证 107 | let key_pair = 108 | EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_slice()).unwrap(); 109 | let public_key = key_pair.public_key().as_ref(); 110 | let verify = 111 | crate::ecdsa_p256_sha256_sign_verify(public_key, signature.as_slice(), MESSAGE); 112 | assert!(verify) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/utxo_set.rs: -------------------------------------------------------------------------------- 1 | use crate::transaction::TXOutput; 2 | use crate::{Block, Blockchain}; 3 | use data_encoding::HEXLOWER; 4 | use std::collections::HashMap; 5 | 6 | const UTXO_TREE: &str = "chainstate"; 7 | 8 | /// UTXO 集 9 | pub struct UTXOSet { 10 | blockchain: Blockchain, 11 | } 12 | 13 | impl UTXOSet { 14 | /// 创建 UTXO 集 15 | pub fn new(blockchain: Blockchain) -> UTXOSet { 16 | UTXOSet { blockchain } 17 | } 18 | 19 | pub fn get_blockchain(&self) -> &Blockchain { 20 | &self.blockchain 21 | } 22 | 23 | /// 找到未花费的输出 24 | pub fn find_spendable_outputs( 25 | &self, 26 | pub_key_hash: &[u8], 27 | amount: i32, 28 | ) -> (i32, HashMap>) { 29 | let mut unspent_outputs: HashMap> = HashMap::new(); 30 | let mut accmulated = 0; 31 | let db = self.blockchain.get_db(); 32 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 33 | for item in utxo_tree.iter() { 34 | let (k, v) = item.unwrap(); 35 | let txid_hex = HEXLOWER.encode(k.to_vec().as_slice()); 36 | let outs: Vec = bincode::deserialize(v.to_vec().as_slice()) 37 | .expect("unable to deserialize TXOutput"); 38 | for (idx, out) in outs.iter().enumerate() { 39 | if out.is_locked_with_key(pub_key_hash) && accmulated < amount { 40 | accmulated += out.get_value(); 41 | if unspent_outputs.contains_key(txid_hex.as_str()) { 42 | unspent_outputs 43 | .get_mut(txid_hex.as_str()) 44 | .unwrap() 45 | .push(idx); 46 | } else { 47 | unspent_outputs.insert(txid_hex.clone(), vec![idx]); 48 | } 49 | } 50 | } 51 | } 52 | (accmulated, unspent_outputs) 53 | } 54 | 55 | /// 通过公钥哈希查找 UTXO 集 56 | pub fn find_utxo(&self, pub_key_hash: &[u8]) -> Vec { 57 | let db = self.blockchain.get_db(); 58 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 59 | let mut utxos = vec![]; 60 | for item in utxo_tree.iter() { 61 | let (_, v) = item.unwrap(); 62 | let outs: Vec = bincode::deserialize(v.to_vec().as_slice()) 63 | .expect("unable to deserialize TXOutput"); 64 | for out in outs.iter() { 65 | if out.is_locked_with_key(pub_key_hash) { 66 | utxos.push(out.clone()) 67 | } 68 | } 69 | } 70 | utxos 71 | } 72 | 73 | /// 统计 UTXO 集合中的交易数量 74 | pub fn count_transactions(&self) -> i32 { 75 | let db = self.blockchain.get_db(); 76 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 77 | let mut counter = 0; 78 | for _ in utxo_tree.iter() { 79 | counter += 1; 80 | } 81 | counter 82 | } 83 | 84 | /// 重建 UTXO 集 85 | pub fn reindex(&self) { 86 | let db = self.blockchain.get_db(); 87 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 88 | let _ = utxo_tree.clear().unwrap(); 89 | 90 | let utxo_map = self.blockchain.find_utxo(); 91 | for (txid_hex, outs) in &utxo_map { 92 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 93 | let value = bincode::serialize(outs).unwrap(); 94 | let _ = utxo_tree.insert(txid.as_slice(), value).unwrap(); 95 | } 96 | } 97 | 98 | /// 使用来自区块的交易更新 UTXO 集 99 | pub fn update(&self, block: &Block) { 100 | let db = self.blockchain.get_db(); 101 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 102 | for tx in block.get_transactions() { 103 | if tx.is_coinbase() == false { 104 | for vin in tx.get_vin() { 105 | let mut updated_outs = vec![]; 106 | let outs_bytes = utxo_tree.get(vin.get_txid()).unwrap().unwrap(); 107 | let outs: Vec = bincode::deserialize(outs_bytes.as_ref()) 108 | .expect("unable to deserialize TXOutput"); 109 | for (idx, out) in outs.iter().enumerate() { 110 | if idx != vin.get_vout() { 111 | updated_outs.push(out.clone()) 112 | } 113 | } 114 | if updated_outs.len() == 0 { 115 | let _ = utxo_tree.remove(vin.get_txid()).unwrap(); 116 | } else { 117 | let outs_bytes = bincode::serialize(&updated_outs) 118 | .expect("unable to serialize TXOutput"); 119 | utxo_tree.insert(vin.get_txid(), outs_bytes).unwrap(); 120 | } 121 | } 122 | } 123 | let mut new_outputs = vec![]; 124 | for out in tx.get_vout() { 125 | new_outputs.push(out.clone()) 126 | } 127 | let outs_bytes = 128 | bincode::serialize(&new_outputs).expect("unable to serialize TXOutput"); 129 | let _ = utxo_tree.insert(tx.get_id(), outs_bytes).unwrap(); 130 | } 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use crate::{validate_address, Blockchain, UTXOSet}; 137 | 138 | #[test] 139 | fn test_get_balance() { 140 | let address = "13SDifQUyLGCwFjh64vihoWQcGsTozHuQb"; 141 | if validate_address(address) == false { 142 | panic!("The address is not valid") 143 | } 144 | let payload = crate::base58_decode(address); 145 | let pub_key_hash = &payload[1..payload.len() - crate::ADDRESS_CHECK_SUM_LEN]; 146 | 147 | let blockchain = Blockchain::new_blockchain(); 148 | let utxo_set = UTXOSet::new(blockchain); 149 | 150 | let utxos = utxo_set.find_utxo(pub_key_hash); 151 | let mut balance = 0; 152 | for utxo in utxos { 153 | balance += utxo.get_value(); 154 | } 155 | println!("The address {} balance is {}", address, balance) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use blockchain_rust::{ 2 | convert_address, hash_pub_key, send_tx, utils, validate_address, Blockchain, Server, 3 | Transaction, UTXOSet, Wallets, ADDRESS_CHECK_SUM_LEN, CENTERAL_NODE, GLOBAL_CONFIG, 4 | }; 5 | use data_encoding::HEXLOWER; 6 | use log::LevelFilter; 7 | use structopt::StructOpt; 8 | 9 | /// mine 标志指的是块会立刻被同一节点挖出来。必须要有这个标志,因为初始状态时,网络中没有矿工节点。 10 | const MINE_TRUE: usize = 1; 11 | 12 | #[derive(Debug, StructOpt)] 13 | #[structopt(name = "blockchain_rust")] 14 | struct Opt { 15 | #[structopt(subcommand)] 16 | command: Command, 17 | } 18 | 19 | #[derive(StructOpt, Debug)] 20 | enum Command { 21 | #[structopt(name = "createblockchain", about = "Create a new blockchain")] 22 | Createblockchain { 23 | #[structopt(name = "address", help = "The address to send genesis block reward to")] 24 | address: String, 25 | }, 26 | #[structopt(name = "createwallet", about = "Create a new wallet")] 27 | Createwallet, 28 | #[structopt( 29 | name = "getbalance", 30 | about = "Get the wallet balance of the target address" 31 | )] 32 | GetBalance { 33 | #[structopt(name = "address", help = "The wallet address")] 34 | address: String, 35 | }, 36 | #[structopt(name = "listaddresses", about = "Print local wallet addres")] 37 | ListAddresses, 38 | #[structopt(name = "send", about = "Add new block to chain")] 39 | Send { 40 | #[structopt(name = "from", help = "Source wallet address")] 41 | from: String, 42 | #[structopt(name = "to", help = "Destination wallet address")] 43 | to: String, 44 | #[structopt(name = "amount", help = "Amount to send")] 45 | amount: i32, 46 | #[structopt(name = "mine", help = "Mine immediately on the same node")] 47 | mine: usize, 48 | }, 49 | #[structopt(name = "printchain", about = "Print blockchain all block")] 50 | Printchain, 51 | #[structopt(name = "reindexutxo", about = "rebuild UTXO index set")] 52 | Reindexutxo, 53 | #[structopt(name = "startnode", about = "Start a node")] 54 | StartNode { 55 | #[structopt(name = "miner", help = "Enable mining mode and send reward to ADDRESS")] 56 | miner: Option, 57 | }, 58 | } 59 | 60 | fn main() { 61 | env_logger::builder().filter_level(LevelFilter::Info).init(); 62 | let opt = Opt::from_args(); 63 | match opt.command { 64 | Command::Createblockchain { address } => { 65 | let blockchain = Blockchain::create_blockchain(address.as_str()); 66 | let utxo_set = UTXOSet::new(blockchain); 67 | utxo_set.reindex(); 68 | println!("Done!"); 69 | } 70 | Command::Createwallet => { 71 | let mut wallet = Wallets::new(); 72 | let address = wallet.create_wallet(); 73 | println!("Your new address: {}", address) 74 | } 75 | Command::GetBalance { address } => { 76 | let address_valid = validate_address(address.as_str()); 77 | if address_valid == false { 78 | panic!("ERROR: Address is not valid") 79 | } 80 | let payload = utils::base58_decode(address.as_str()); 81 | let pub_key_hash = &payload[1..payload.len() - ADDRESS_CHECK_SUM_LEN]; 82 | 83 | let blockchain = Blockchain::new_blockchain(); 84 | let utxo_set = UTXOSet::new(blockchain); 85 | let utxos = utxo_set.find_utxo(pub_key_hash); 86 | let mut balance = 0; 87 | for utxo in utxos { 88 | balance += utxo.get_value(); 89 | } 90 | println!("Balance of {}: {}", address, balance); 91 | } 92 | Command::ListAddresses => { 93 | let wallets = Wallets::new(); 94 | for address in wallets.get_addresses() { 95 | println!("{}", address) 96 | } 97 | } 98 | Command::Send { 99 | from, 100 | to, 101 | amount, 102 | mine, 103 | } => { 104 | if !validate_address(from.as_str()) { 105 | panic!("ERROR: Sender address is not valid") 106 | } 107 | if !validate_address(to.as_str()) { 108 | panic!("ERROR: Recipient address is not valid") 109 | } 110 | let blockchain = Blockchain::new_blockchain(); 111 | let utxo_set = UTXOSet::new(blockchain.clone()); 112 | // 创建 UTXO 交易 113 | let transaction = 114 | Transaction::new_utxo_transaction(from.as_str(), to.as_str(), amount, &utxo_set); 115 | 116 | if mine == MINE_TRUE { 117 | // 挖矿奖励 118 | let coinbase_tx = Transaction::new_coinbase_tx(from.as_str()); 119 | // 挖新区块 120 | let block = blockchain.mine_block(&vec![transaction, coinbase_tx]); 121 | // 更新 UTXO 集 122 | utxo_set.update(&block); 123 | } else { 124 | send_tx(CENTERAL_NODE, &transaction); 125 | } 126 | println!("Success!") 127 | } 128 | Command::Printchain => { 129 | let mut block_iterator = Blockchain::new_blockchain().iterator(); 130 | loop { 131 | let option = block_iterator.next(); 132 | if option.is_none() { 133 | break; 134 | } 135 | let block = option.unwrap(); 136 | println!("Pre block hash: {}", block.get_pre_block_hash()); 137 | println!("Cur block hash: {}", block.get_hash()); 138 | println!("Cur block Timestamp: {}", block.get_timestamp()); 139 | for tx in block.get_transactions() { 140 | let cur_txid_hex = HEXLOWER.encode(tx.get_id()); 141 | println!("- Transaction txid_hex: {}", cur_txid_hex); 142 | 143 | if tx.is_coinbase() == false { 144 | for input in tx.get_vin() { 145 | let txid_hex = HEXLOWER.encode(input.get_txid()); 146 | let pub_key_hash = hash_pub_key(input.get_pub_key()); 147 | let address = convert_address(pub_key_hash.as_slice()); 148 | println!( 149 | "-- Input txid = {}, vout = {}, from = {}", 150 | txid_hex, 151 | input.get_vout(), 152 | address, 153 | ) 154 | } 155 | } 156 | for output in tx.get_vout() { 157 | let pub_key_hash = output.get_pub_key_hash(); 158 | let address = convert_address(pub_key_hash); 159 | println!("-- Output value = {}, to = {}", output.get_value(), address,) 160 | } 161 | } 162 | println!() 163 | } 164 | } 165 | Command::Reindexutxo => { 166 | let blockchain = Blockchain::new_blockchain(); 167 | let utxo_set = UTXOSet::new(blockchain); 168 | utxo_set.reindex(); 169 | let count = utxo_set.count_transactions(); 170 | println!("Done! There are {} transactions in the UTXO set.", count); 171 | } 172 | Command::StartNode { miner } => { 173 | if let Some(addr) = miner { 174 | if validate_address(addr.as_str()) == false { 175 | panic!("Wrong miner address!") 176 | } 177 | println!("Mining is on. Address to receive rewards: {}", addr); 178 | GLOBAL_CONFIG.set_mining_addr(addr); 179 | } 180 | let blockchain = Blockchain::new_blockchain(); 181 | let sockert_addr = GLOBAL_CONFIG.get_node_addr(); 182 | Server::new(blockchain).run(sockert_addr.as_str()); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::wallet::hash_pub_key; 2 | use crate::{base58_decode, wallet, Blockchain, UTXOSet, Wallets}; 3 | use data_encoding::HEXLOWER; 4 | use serde::{Deserialize, Serialize}; 5 | use uuid::Uuid; 6 | 7 | /// 挖矿奖励金 8 | const SUBSIDY: i32 = 10; 9 | 10 | /// 交易输入 11 | #[derive(Clone, Default, Serialize, Deserialize)] 12 | pub struct TXInput { 13 | txid: Vec, // 一个交易输入引用了前一笔交易的一个输出,ID表明是之前的哪一笔交易 14 | vout: usize, // 输出的索引 15 | signature: Vec, // 签名 16 | pub_key: Vec, // 原生的公钥 17 | } 18 | 19 | impl TXInput { 20 | /// 创建一个输入 21 | pub fn new(txid: &[u8], vout: usize) -> TXInput { 22 | TXInput { 23 | txid: txid.to_vec(), 24 | vout, 25 | signature: vec![], 26 | pub_key: vec![], 27 | } 28 | } 29 | 30 | pub fn get_txid(&self) -> &[u8] { 31 | self.txid.as_slice() 32 | } 33 | 34 | pub fn get_vout(&self) -> usize { 35 | self.vout 36 | } 37 | 38 | pub fn get_pub_key(&self) -> &[u8] { 39 | self.pub_key.as_slice() 40 | } 41 | 42 | /// 检查输入使用了指定密钥来解锁一个输出 43 | pub fn uses_key(&self, pub_key_hash: &[u8]) -> bool { 44 | let locking_hash = wallet::hash_pub_key(self.pub_key.as_slice()); 45 | return locking_hash.eq(pub_key_hash); 46 | } 47 | } 48 | 49 | /// 交易输出 50 | #[derive(Clone, Serialize, Deserialize)] 51 | pub struct TXOutput { 52 | value: i32, // 币的数量 53 | pub_key_hash: Vec, // 公钥哈希 54 | } 55 | 56 | impl TXOutput { 57 | /// 创建一个输出 58 | pub fn new(value: i32, address: &str) -> TXOutput { 59 | let mut output = TXOutput { 60 | value, 61 | pub_key_hash: vec![], 62 | }; 63 | output.lock(address); 64 | return output; 65 | } 66 | 67 | pub fn get_value(&self) -> i32 { 68 | self.value 69 | } 70 | 71 | pub fn get_pub_key_hash(&self) -> &[u8] { 72 | self.pub_key_hash.as_slice() 73 | } 74 | 75 | fn lock(&mut self, address: &str) { 76 | let payload = base58_decode(address); 77 | let pub_key_hash = payload[1..payload.len() - wallet::ADDRESS_CHECK_SUM_LEN].to_vec(); 78 | self.pub_key_hash = pub_key_hash; 79 | } 80 | 81 | pub fn is_locked_with_key(&self, pub_key_hash: &[u8]) -> bool { 82 | self.pub_key_hash.eq(pub_key_hash) 83 | } 84 | } 85 | 86 | /// 交易 87 | #[derive(Clone, Default, Serialize, Deserialize)] 88 | pub struct Transaction { 89 | id: Vec, // 交易ID 90 | vin: Vec, // 输入 91 | vout: Vec, // 输出 92 | } 93 | 94 | impl Transaction { 95 | /// 创建一个 coinbase 交易,该没有输入,只有一个输出 96 | pub fn new_coinbase_tx(to: &str) -> Transaction { 97 | let txout = TXOutput::new(SUBSIDY, to); 98 | let mut tx_input = TXInput::default(); 99 | tx_input.signature = Uuid::new_v4().as_bytes().to_vec(); 100 | 101 | let mut tx = Transaction { 102 | id: vec![], 103 | vin: vec![tx_input], 104 | vout: vec![txout], 105 | }; 106 | 107 | tx.id = tx.hash(); 108 | return tx; 109 | } 110 | 111 | /// 创建一笔 UTXO 的交易 112 | pub fn new_utxo_transaction( 113 | from: &str, 114 | to: &str, 115 | amount: i32, 116 | utxo_set: &UTXOSet, 117 | ) -> Transaction { 118 | // 1.查找钱包 119 | let wallets = Wallets::new(); 120 | let wallet = wallets.get_wallet(from).expect("unable to found wallet"); 121 | let public_key_hash = hash_pub_key(wallet.get_public_key()); 122 | // 2.找到足够的未花费输出 123 | let (accumulated, valid_outputs) = 124 | utxo_set.find_spendable_outputs(public_key_hash.as_slice(), amount); 125 | if accumulated < amount { 126 | panic!("Error: Not enough funds") 127 | } 128 | // 3.交易数据 129 | // 3.1.交易的输入 130 | let mut inputs = vec![]; 131 | for (txid_hex, outs) in valid_outputs { 132 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 133 | for out in outs { 134 | let input = TXInput { 135 | txid: txid.clone(), // 上一笔交易的ID 136 | vout: out, // 输出的索引 137 | signature: vec![], 138 | pub_key: wallet.get_public_key().to_vec(), 139 | }; 140 | inputs.push(input); 141 | } 142 | } 143 | // 3.2.交易的输出 144 | let mut outputs = vec![TXOutput::new(amount, to)]; 145 | // 如果 UTXO 总数超过所需,则产生找零 146 | if accumulated > amount { 147 | outputs.push(TXOutput::new(accumulated - amount, from)) // to: 币收入 148 | } 149 | // 4.生成交易 150 | let mut tx = Transaction { 151 | id: vec![], 152 | vin: inputs, 153 | vout: outputs, 154 | }; 155 | // 生成交易ID 156 | tx.id = tx.hash(); 157 | // 5.交易中的 TXInput 签名 158 | tx.sign(utxo_set.get_blockchain(), wallet.get_pkcs8()); 159 | return tx; 160 | } 161 | 162 | /// 创建一个修剪后的交易副本 163 | fn trimmed_copy(&self) -> Transaction { 164 | let mut inputs = vec![]; 165 | let mut outputs = vec![]; 166 | for input in &self.vin { 167 | let txinput = TXInput::new(input.get_txid(), input.get_vout()); 168 | inputs.push(txinput); 169 | } 170 | for output in &self.vout { 171 | outputs.push(output.clone()); 172 | } 173 | Transaction { 174 | id: self.id.clone(), 175 | vin: inputs, 176 | vout: outputs, 177 | } 178 | } 179 | 180 | /// 对交易的每个输入进行签名 181 | fn sign(&mut self, blockchain: &Blockchain, pkcs8: &[u8]) { 182 | let mut tx_copy = self.trimmed_copy(); 183 | 184 | for (idx, vin) in self.vin.iter_mut().enumerate() { 185 | // 查找输入引用的交易 186 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 187 | if prev_tx_option.is_none() { 188 | panic!("ERROR: Previous transaction is not correct") 189 | } 190 | let prev_tx = prev_tx_option.unwrap(); 191 | tx_copy.vin[idx].signature = vec![]; 192 | tx_copy.vin[idx].pub_key = prev_tx.vout[vin.vout].pub_key_hash.clone(); 193 | tx_copy.id = tx_copy.hash(); 194 | tx_copy.vin[idx].pub_key = vec![]; 195 | 196 | // 使用私钥对数据签名 197 | let signature = crate::ecdsa_p256_sha256_sign_digest(pkcs8, tx_copy.get_id()); 198 | vin.signature = signature; 199 | } 200 | } 201 | 202 | /// 对交易的每个输入进行签名验证 203 | pub fn verify(&self, blockchain: &Blockchain) -> bool { 204 | if self.is_coinbase() { 205 | return true; 206 | } 207 | let mut tx_copy = self.trimmed_copy(); 208 | for (idx, vin) in self.vin.iter().enumerate() { 209 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 210 | if prev_tx_option.is_none() { 211 | panic!("ERROR: Previous transaction is not correct") 212 | } 213 | let prev_tx = prev_tx_option.unwrap(); 214 | tx_copy.vin[idx].signature = vec![]; 215 | tx_copy.vin[idx].pub_key = prev_tx.vout[vin.vout].pub_key_hash.clone(); 216 | tx_copy.id = tx_copy.hash(); 217 | tx_copy.vin[idx].pub_key = vec![]; 218 | 219 | // 使用公钥验证签名 220 | let verify = crate::ecdsa_p256_sha256_sign_verify( 221 | vin.pub_key.as_slice(), 222 | vin.signature.as_slice(), 223 | tx_copy.get_id(), 224 | ); 225 | if !verify { 226 | return false; 227 | } 228 | } 229 | true 230 | } 231 | 232 | /// 判断是否是 coinbase 交易 233 | pub fn is_coinbase(&self) -> bool { 234 | return self.vin.len() == 1 && self.vin[0].pub_key.len() == 0; 235 | } 236 | 237 | /// 生成交易的哈希 238 | fn hash(&mut self) -> Vec { 239 | let tx_copy = Transaction { 240 | id: vec![], 241 | vin: self.vin.clone(), 242 | vout: self.vout.clone(), 243 | }; 244 | crate::sha256_digest(tx_copy.serialize().as_slice()) 245 | } 246 | 247 | pub fn get_id(&self) -> &[u8] { 248 | self.id.as_slice() 249 | } 250 | 251 | pub fn get_id_bytes(&self) -> Vec { 252 | self.id.clone() 253 | } 254 | 255 | pub fn get_vin(&self) -> &[TXInput] { 256 | self.vin.as_slice() 257 | } 258 | 259 | pub fn get_vout(&self) -> &[TXOutput] { 260 | self.vout.as_slice() 261 | } 262 | 263 | pub fn serialize(&self) -> Vec { 264 | bincode::serialize(self).unwrap().to_vec() 265 | } 266 | 267 | pub fn deserialize(bytes: &[u8]) -> Transaction { 268 | bincode::deserialize(bytes).unwrap() 269 | } 270 | } 271 | 272 | #[cfg(test)] 273 | mod tests { 274 | use crate::{Blockchain, Transaction, UTXOSet}; 275 | use data_encoding::HEXLOWER; 276 | 277 | #[test] 278 | fn new_coinbase_tx() { 279 | // BTC 创世块: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 280 | let tx = Transaction::new_coinbase_tx("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); 281 | let txid_hex = HEXLOWER.encode(tx.get_id()); 282 | println!("txid = {}", txid_hex); 283 | } 284 | 285 | #[test] 286 | fn test_blockchain_serialize() { 287 | let tx = Transaction::new_coinbase_tx("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); 288 | let tx_bytes = tx.serialize(); 289 | let new_tx = Transaction::deserialize(tx_bytes.as_ref()); 290 | assert_eq!(tx.get_id(), new_tx.get_id()) 291 | } 292 | 293 | #[test] 294 | fn new_utxo_transaction() { 295 | let blockchain = Blockchain::new_blockchain(); 296 | let utxo_set = UTXOSet::new(blockchain); 297 | let tx = Transaction::new_utxo_transaction( 298 | "13SDifQUyLGCwFjh64vihoWQcGsTozHuQb", 299 | "1LecNaLYsDoxRtxBBWKMNbLvccftmFZWcv", 300 | 5, 301 | &utxo_set, 302 | ); 303 | let txid_hex = HEXLOWER.encode(tx.get_id()); 304 | // b4a0498750e48c431b84e03dd3ec0dc4d9df1b823c45a7e3c59171b8c2f099cd 305 | println!("txid_hex = {}", txid_hex); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/blockchain.rs: -------------------------------------------------------------------------------- 1 | use crate::transaction::TXOutput; 2 | use crate::{Block, Transaction}; 3 | use data_encoding::HEXLOWER; 4 | use sled::transaction::TransactionResult; 5 | use sled::{Db, Tree}; 6 | use std::collections::HashMap; 7 | use std::env::current_dir; 8 | use std::sync::{Arc, RwLock}; 9 | 10 | const TIP_BLOCK_HASH_KEY: &str = "tip_block_hash"; 11 | const BLOCKS_TREE: &str = "blocks"; 12 | 13 | #[derive(Clone)] 14 | pub struct Blockchain { 15 | tip_hash: Arc>, // hash of last block 16 | db: Db, 17 | } 18 | 19 | impl Blockchain { 20 | /// 创建新的区块链 21 | pub fn create_blockchain(genesis_address: &str) -> Blockchain { 22 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 23 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 24 | 25 | let data = blocks_tree.get(TIP_BLOCK_HASH_KEY).unwrap(); 26 | let tip_hash; 27 | if data.is_none() { 28 | let coinbase_tx = Transaction::new_coinbase_tx(genesis_address); 29 | let block = Block::generate_genesis_block(&coinbase_tx); 30 | Self::update_blocks_tree(&blocks_tree, &block); 31 | tip_hash = String::from(block.get_hash()); 32 | } else { 33 | tip_hash = String::from_utf8(data.unwrap().to_vec()).unwrap(); 34 | } 35 | Blockchain { 36 | tip_hash: Arc::new(RwLock::new(tip_hash)), 37 | db, 38 | } 39 | } 40 | 41 | fn update_blocks_tree(blocks_tree: &Tree, block: &Block) { 42 | let block_hash = block.get_hash(); 43 | let _: TransactionResult<(), ()> = blocks_tree.transaction(|tx_db| { 44 | let _ = tx_db.insert(block_hash, block.clone()); 45 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block_hash); 46 | Ok(()) 47 | }); 48 | } 49 | 50 | /// 创建区块链实例 51 | pub fn new_blockchain() -> Blockchain { 52 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 53 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 54 | let tip_bytes = blocks_tree 55 | .get(TIP_BLOCK_HASH_KEY) 56 | .unwrap() 57 | .expect("No existing blockchain found. Create one first."); 58 | let tip_hash = String::from_utf8(tip_bytes.to_vec()).unwrap(); 59 | Blockchain { 60 | tip_hash: Arc::new(RwLock::new(tip_hash)), 61 | db, 62 | } 63 | } 64 | 65 | pub fn get_db(&self) -> &Db { 66 | &self.db 67 | } 68 | 69 | pub fn get_tip_hash(&self) -> String { 70 | self.tip_hash.read().unwrap().clone() 71 | } 72 | 73 | pub fn set_tip_hash(&self, new_tip_hash: &str) { 74 | let mut tip_hash = self.tip_hash.write().unwrap(); 75 | *tip_hash = String::from(new_tip_hash) 76 | } 77 | 78 | /// 挖矿新区块 79 | pub fn mine_block(&self, transactions: &[Transaction]) -> Block { 80 | for trasaction in transactions { 81 | if trasaction.verify(self) == false { 82 | panic!("ERROR: Invalid transaction") 83 | } 84 | } 85 | let best_height = self.get_best_height(); 86 | 87 | let block = Block::new_block(self.get_tip_hash(), transactions, best_height + 1); 88 | let block_hash = block.get_hash(); 89 | 90 | let blocks_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 91 | Self::update_blocks_tree(&blocks_tree, &block); 92 | self.set_tip_hash(block_hash); 93 | block 94 | } 95 | 96 | pub fn iterator(&self) -> BlockchainIterator { 97 | BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 98 | } 99 | 100 | /// 查找所有未花费的交易输出 ( K -> txid_hex, V -> Vec HashMap> { 102 | let mut utxo: HashMap> = HashMap::new(); 103 | let mut spent_txos: HashMap> = HashMap::new(); 104 | 105 | let mut iterator = self.iterator(); 106 | loop { 107 | let option = iterator.next(); 108 | if option.is_none() { 109 | break; 110 | } 111 | let block = option.unwrap(); 112 | 'outer: for tx in block.get_transactions() { 113 | let txid_hex = HEXLOWER.encode(tx.get_id()); 114 | for (idx, out) in tx.get_vout().iter().enumerate() { 115 | // 过滤已花费的输出 116 | if let Some(outs) = spent_txos.get(txid_hex.as_str()) { 117 | for spend_out_idx in outs { 118 | if idx.eq(spend_out_idx) { 119 | continue 'outer; 120 | } 121 | } 122 | } 123 | if utxo.contains_key(txid_hex.as_str()) { 124 | utxo.get_mut(txid_hex.as_str()).unwrap().push(out.clone()); 125 | } else { 126 | utxo.insert(txid_hex.clone(), vec![out.clone()]); 127 | } 128 | } 129 | if tx.is_coinbase() { 130 | continue; 131 | } 132 | // 在输入中查找已花费输出 133 | for txin in tx.get_vin() { 134 | let txid_hex = HEXLOWER.encode(txin.get_txid()); 135 | if spent_txos.contains_key(txid_hex.as_str()) { 136 | spent_txos 137 | .get_mut(txid_hex.as_str()) 138 | .unwrap() 139 | .push(txin.get_vout()); 140 | } else { 141 | spent_txos.insert(txid_hex, vec![txin.get_vout()]); 142 | } 143 | } 144 | } 145 | } 146 | utxo 147 | } 148 | 149 | /// 从区块链中查找交易 150 | pub fn find_transaction(&self, txid: &[u8]) -> Option { 151 | let mut iterator = self.iterator(); 152 | loop { 153 | let option = iterator.next(); 154 | if option.is_none() { 155 | break; 156 | } 157 | let block = option.unwrap(); 158 | for transaction in block.get_transactions() { 159 | if txid.eq(transaction.get_id()) { 160 | return Some(transaction.clone()); 161 | } 162 | } 163 | } 164 | None 165 | } 166 | 167 | /// 添加一个区块到区块链 168 | pub fn add_block(&self, block: &Block) { 169 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 170 | if let Some(_) = block_tree.get(block.get_hash()).unwrap() { 171 | return; 172 | } 173 | let _: TransactionResult<(), ()> = block_tree.transaction(|tx_db| { 174 | let _ = tx_db.insert(block.get_hash(), block.serialize()).unwrap(); 175 | 176 | let tip_block_bytes = tx_db 177 | .get(self.get_tip_hash()) 178 | .unwrap() 179 | .expect("The tip hash is not valid"); 180 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 181 | if block.get_height() > tip_block.get_height() { 182 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block.get_hash()).unwrap(); 183 | self.set_tip_hash(block.get_hash()); 184 | } 185 | Ok(()) 186 | }); 187 | } 188 | 189 | /// 获取最新区块在链中的高度 190 | pub fn get_best_height(&self) -> usize { 191 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 192 | let tip_block_bytes = block_tree 193 | .get(self.get_tip_hash()) 194 | .unwrap() 195 | .expect("The tip hash is valid"); 196 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 197 | tip_block.get_height() 198 | } 199 | 200 | /// 通过区块哈希查询区块 201 | pub fn get_block(&self, block_hash: &[u8]) -> Option { 202 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 203 | if let Some(block_bytes) = block_tree.get(block_hash).unwrap() { 204 | let block = Block::deserialize(block_bytes.as_ref()); 205 | return Some(block); 206 | } 207 | return None; 208 | } 209 | 210 | /// 返回链中所有区块的哈希列表 211 | pub fn get_block_hashes(&self) -> Vec> { 212 | let mut iterator = self.iterator(); 213 | let mut blocks = vec![]; 214 | loop { 215 | let option = iterator.next(); 216 | if option.is_none() { 217 | break; 218 | } 219 | let block = option.unwrap(); 220 | blocks.push(block.get_hash_bytes()); 221 | } 222 | return blocks; 223 | } 224 | } 225 | 226 | pub struct BlockchainIterator { 227 | db: Db, 228 | current_hash: String, 229 | } 230 | 231 | impl BlockchainIterator { 232 | fn new(tip_hash: String, db: Db) -> BlockchainIterator { 233 | BlockchainIterator { 234 | current_hash: tip_hash, 235 | db, 236 | } 237 | } 238 | 239 | pub fn next(&mut self) -> Option { 240 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 241 | let data = block_tree.get(self.current_hash.clone()).unwrap(); 242 | if data.is_none() { 243 | return None; 244 | } 245 | let block = Block::deserialize(data.unwrap().to_vec().as_slice()); 246 | self.current_hash = block.get_pre_block_hash().clone(); 247 | return Some(block); 248 | } 249 | } 250 | 251 | #[cfg(test)] 252 | mod tests { 253 | use crate::Block; 254 | 255 | #[test] 256 | fn test_create_blockchain() { 257 | let _ = super::Blockchain::create_blockchain("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); 258 | } 259 | 260 | #[test] 261 | fn test_mine_block() { 262 | let blockchain = super::Blockchain::new_blockchain(); 263 | let _ = blockchain.mine_block(&vec![]); 264 | } 265 | 266 | #[test] 267 | fn test_get_best_height() { 268 | let blockchain = super::Blockchain::new_blockchain(); 269 | println!( 270 | "tip_hash = {}, best_height: {}", 271 | blockchain.get_tip_hash(), 272 | blockchain.get_best_height() 273 | ); 274 | } 275 | 276 | #[test] 277 | fn test_add_block() { 278 | let blockchain = super::Blockchain::new_blockchain(); 279 | let best_height = blockchain.get_best_height(); 280 | let block = Block::new_block(blockchain.get_tip_hash(), &vec![], best_height + 1); 281 | blockchain.add_block(&block); 282 | println!( 283 | "tip_hash = {}, best_height = {}", 284 | blockchain.get_tip_hash(), 285 | blockchain.get_best_height() 286 | ); 287 | } 288 | 289 | #[test] 290 | fn test_get_block_hashes() { 291 | let blockchain = super::Blockchain::new_blockchain(); 292 | let block_hashs = blockchain.get_block_hashes(); 293 | for hash_bytes in block_hashs { 294 | println!("{}", String::from_utf8(hash_bytes).unwrap()) 295 | } 296 | } 297 | 298 | #[test] 299 | fn test_get_block() { 300 | let blockchain = super::Blockchain::new_blockchain(); 301 | if let Some(block) = blockchain.get_block( 302 | "0060a9e030158c9fa012f06eeb18f8d1f26523aa1483face260730c14a140fce".as_bytes(), 303 | ) { 304 | println!("{}", block.get_hash()) 305 | } 306 | } 307 | 308 | #[test] 309 | fn test_find_transaction() { 310 | let blockchain = super::Blockchain::new_blockchain(); 311 | let trasaction = blockchain.find_transaction( 312 | "00aee463227e52bf2c6986033d86a2572942f9d79a1da7c4cebe790a8b8ead92".as_bytes(), 313 | ); 314 | assert!(trasaction.is_none()) 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | Block, BlockInTransit, Blockchain, MemoryPool, Nodes, Transaction, UTXOSet, GLOBAL_CONFIG, 3 | }; 4 | use data_encoding::HEXLOWER; 5 | use log::{error, info}; 6 | use once_cell::sync::Lazy; 7 | use serde::{Deserialize, Serialize}; 8 | use serde_json::Deserializer; 9 | use std::error::Error; 10 | use std::io::{BufReader, Write}; 11 | use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream}; 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | /// 版本硬编码 16 | const NODE_VERSION: usize = 1; 17 | 18 | /// 中心节点硬编码 19 | pub const CENTERAL_NODE: &str = "127.0.0.1:2001"; 20 | 21 | /// 内存池中的交易到达阈值, 触发矿工挖新区块 22 | pub const TRANSACTION_THRESHOLD: usize = 2; 23 | 24 | /// 全网的节点地址 25 | static GLOBAL_NODES: Lazy = Lazy::new(|| { 26 | let nodes = Nodes::new(); 27 | // 记录中心地址 28 | nodes.add_node(String::from(CENTERAL_NODE)); 29 | return nodes; 30 | }); 31 | 32 | /// 交易内存池 33 | static GLOBAL_MEMORY_POOL: Lazy = Lazy::new(|| MemoryPool::new()); 34 | 35 | /// 传输中的Block, 用于来跟踪已下载的块, 这能够实现从不同的节点下载块 36 | static GLOBAL_BLOCKS_IN_TRANSIT: Lazy = Lazy::new(|| BlockInTransit::new()); 37 | 38 | /// 网络写超时 39 | const TCP_WRITE_TIMEOUT: u64 = 1000; 40 | 41 | pub struct Server { 42 | blockchain: Blockchain, 43 | } 44 | 45 | impl Server { 46 | pub fn new(blockchain: Blockchain) -> Server { 47 | Server { blockchain } 48 | } 49 | 50 | pub fn run(&self, addr: &str) { 51 | let listener = TcpListener::bind(addr).unwrap(); 52 | 53 | // 发送 version 握手 54 | if addr.eq(CENTERAL_NODE) == false { 55 | let best_height = self.blockchain.get_best_height(); 56 | info!("send sersion best_height: {}", best_height); 57 | send_version(CENTERAL_NODE, best_height); 58 | } 59 | info!("Start node server on {}", addr); 60 | for stream in listener.incoming() { 61 | let blockchain = self.blockchain.clone(); 62 | thread::spawn(|| match stream { 63 | Ok(stream) => { 64 | if let Err(e) = serve(blockchain, stream) { 65 | error!("Error on serving client: {}", e); 66 | } 67 | } 68 | Err(e) => { 69 | error!("Connection failed: {}", e); 70 | } 71 | }); 72 | } 73 | } 74 | } 75 | 76 | #[derive(Debug, Serialize, Deserialize)] 77 | pub enum OpType { 78 | Tx, 79 | Block, 80 | } 81 | 82 | #[derive(Debug, Serialize, Deserialize)] 83 | pub enum Package { 84 | Block { 85 | addr_from: String, 86 | block: Vec, 87 | }, 88 | GetBlocks { 89 | addr_from: String, 90 | }, 91 | GetData { 92 | addr_from: String, 93 | op_type: OpType, 94 | id: Vec, 95 | }, 96 | Inv { 97 | addr_from: String, 98 | op_type: OpType, 99 | items: Vec>, 100 | }, 101 | Tx { 102 | addr_from: String, 103 | transaction: Vec, 104 | }, 105 | Version { 106 | addr_from: String, 107 | version: usize, 108 | best_height: usize, 109 | }, 110 | } 111 | 112 | fn send_get_data(addr: &str, op_type: OpType, id: &[u8]) { 113 | let socket_addr = addr.parse().unwrap(); 114 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 115 | send_data( 116 | socket_addr, 117 | Package::GetData { 118 | addr_from: node_addr, 119 | op_type, 120 | id: id.to_vec(), 121 | }, 122 | ); 123 | } 124 | 125 | fn send_inv(addr: &str, op_type: OpType, blocks: &[Vec]) { 126 | let socket_addr = addr.parse().unwrap(); 127 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 128 | send_data( 129 | socket_addr, 130 | Package::Inv { 131 | addr_from: node_addr, 132 | op_type, 133 | items: blocks.to_vec(), 134 | }, 135 | ); 136 | } 137 | 138 | fn send_block(addr: &str, block: &Block) { 139 | let socket_addr = addr.parse().unwrap(); 140 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 141 | send_data( 142 | socket_addr, 143 | Package::Block { 144 | addr_from: node_addr, 145 | block: block.serialize(), 146 | }, 147 | ); 148 | } 149 | 150 | pub fn send_tx(addr: &str, tx: &Transaction) { 151 | let socket_addr = addr.parse().unwrap(); 152 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 153 | send_data( 154 | socket_addr, 155 | Package::Tx { 156 | addr_from: node_addr, 157 | transaction: tx.serialize(), 158 | }, 159 | ); 160 | } 161 | 162 | fn send_version(addr: &str, height: usize) { 163 | let socket_addr = addr.parse().unwrap(); 164 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 165 | send_data( 166 | socket_addr, 167 | Package::Version { 168 | addr_from: node_addr, 169 | version: NODE_VERSION, 170 | best_height: height, 171 | }, 172 | ); 173 | } 174 | 175 | fn send_get_blocks(addr: &str) { 176 | let socket_addr = addr.parse().unwrap(); 177 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 178 | send_data( 179 | socket_addr, 180 | Package::GetBlocks { 181 | addr_from: node_addr, 182 | }, 183 | ); 184 | } 185 | 186 | fn serve(blockchain: Blockchain, stream: TcpStream) -> Result<(), Box> { 187 | let peer_addr = stream.peer_addr()?; 188 | let reader = BufReader::new(&stream); 189 | let pkg_reader = Deserializer::from_reader(reader).into_iter::(); 190 | for pkg in pkg_reader { 191 | let pkg = pkg?; 192 | info!("Receive request from {}: {:?}", peer_addr, pkg); 193 | match pkg { 194 | Package::Block { addr_from, block } => { 195 | let block = Block::deserialize(block.as_slice()); 196 | blockchain.add_block(&block); 197 | info!("Added block {}", block.get_hash()); 198 | 199 | if GLOBAL_BLOCKS_IN_TRANSIT.len() > 0 { 200 | // 继续下载区块 201 | let block_hash = GLOBAL_BLOCKS_IN_TRANSIT.first().unwrap(); 202 | send_get_data(addr_from.as_str(), OpType::Block, &block_hash); 203 | // 从下载列表中移除 204 | GLOBAL_BLOCKS_IN_TRANSIT.remove(block_hash.as_slice()); 205 | } else { 206 | // 区块全部下载后,再重建索引 207 | let utxo_set = UTXOSet::new(blockchain.clone()); 208 | utxo_set.reindex(); 209 | } 210 | } 211 | Package::GetBlocks { addr_from } => { 212 | let blocks = blockchain.get_block_hashes(); 213 | send_inv(addr_from.as_str(), OpType::Block, &blocks); 214 | } 215 | Package::GetData { 216 | addr_from, 217 | op_type, 218 | id, 219 | } => match op_type { 220 | OpType::Block => { 221 | if let Some(block) = blockchain.get_block(id.as_slice()) { 222 | send_block(addr_from.as_str(), &block); 223 | } 224 | } 225 | OpType::Tx => { 226 | let txid_hex = HEXLOWER.encode(id.as_slice()); 227 | if let Some(tx) = GLOBAL_MEMORY_POOL.get(txid_hex.as_str()) { 228 | send_tx(addr_from.as_str(), &tx); 229 | } 230 | } 231 | }, 232 | Package::Inv { 233 | addr_from, 234 | op_type, 235 | items, 236 | } => match op_type { 237 | // 两种触发情况: 238 | // 1. 当 version 消息检查到区块高度落后,会收到全量的 block hash 列表。 239 | // 2. 矿工挖出新的区块后,会将新区块的 hash 广播给所有节点。 240 | OpType::Block => { 241 | // 初始启动才会触发,不可能有存量数据 242 | GLOBAL_BLOCKS_IN_TRANSIT.add_blocks(items.as_slice()); 243 | 244 | // 下载一个区块 245 | let block_hash = items.get(0).unwrap(); 246 | send_get_data(addr_from.as_str(), OpType::Block, block_hash); 247 | // 从下载列表中移除 248 | GLOBAL_BLOCKS_IN_TRANSIT.remove(block_hash); 249 | } 250 | OpType::Tx => { 251 | let txid = items.get(0).unwrap(); 252 | let txid_hex = HEXLOWER.encode(txid); 253 | 254 | // 检查交易池,不包含交易则下载 255 | if GLOBAL_MEMORY_POOL.containes(txid_hex.as_str()) == false { 256 | send_get_data(addr_from.as_str(), OpType::Tx, txid); 257 | } 258 | } 259 | }, 260 | Package::Tx { 261 | addr_from, 262 | transaction, 263 | } => { 264 | // 记录交易到内存池 265 | let tx = Transaction::deserialize(transaction.as_slice()); 266 | let txid = tx.get_id_bytes(); 267 | GLOBAL_MEMORY_POOL.add(tx); 268 | 269 | let node_addr = GLOBAL_CONFIG.get_node_addr(); 270 | // 中心节点(广播交易) 271 | if node_addr.eq(CENTERAL_NODE) { 272 | let nodes = GLOBAL_NODES.get_nodes(); 273 | for node in &nodes { 274 | if node_addr.eq(node.get_addr().as_str()) { 275 | continue; 276 | } 277 | if addr_from.eq(node.get_addr().as_str()) { 278 | continue; 279 | } 280 | send_inv(node.get_addr().as_str(), OpType::Tx, &vec![txid.clone()]) 281 | } 282 | } 283 | // 矿工节点(内存池中的交易到达一定数量,挖出新区块) 284 | if GLOBAL_MEMORY_POOL.len() >= TRANSACTION_THRESHOLD && GLOBAL_CONFIG.is_miner() { 285 | // 挖矿奖励 286 | let mining_address = GLOBAL_CONFIG.get_mining_addr().unwrap(); 287 | let coinbase_tx = Transaction::new_coinbase_tx(mining_address.as_str()); 288 | let mut txs = GLOBAL_MEMORY_POOL.get_all(); 289 | txs.push(coinbase_tx); 290 | 291 | // 挖区块 292 | let new_block = blockchain.mine_block(&txs); 293 | let utxo_set = UTXOSet::new(blockchain.clone()); 294 | utxo_set.reindex(); 295 | info!("New block {} is mined!", new_block.get_hash()); 296 | 297 | // 从内存池中移除交易 298 | for tx in &txs { 299 | let txid_hex = HEXLOWER.encode(tx.get_id()); 300 | GLOBAL_MEMORY_POOL.remove(txid_hex.as_str()); 301 | } 302 | // 广播新区块 303 | let nodes = GLOBAL_NODES.get_nodes(); 304 | for node in &nodes { 305 | if node_addr.eq(node.get_addr().as_str()) { 306 | continue; 307 | } 308 | send_inv( 309 | node.get_addr().as_str(), 310 | OpType::Block, 311 | &vec![new_block.get_hash_bytes()], 312 | ); 313 | } 314 | } 315 | } 316 | Package::Version { 317 | addr_from, 318 | version, 319 | best_height, 320 | } => { 321 | info!("version = {}, best_height = {}", version, best_height); 322 | let local_best_height = blockchain.get_best_height(); 323 | if local_best_height < best_height { 324 | send_get_blocks(addr_from.as_str()); 325 | } 326 | if local_best_height > best_height { 327 | send_version(addr_from.as_str(), blockchain.get_best_height()); 328 | } 329 | // 记录节点地址 330 | if GLOBAL_NODES.node_is_known(peer_addr.to_string().as_str()) == false { 331 | GLOBAL_NODES.add_node(addr_from); 332 | } 333 | } 334 | } 335 | } 336 | let _ = stream.shutdown(Shutdown::Both); 337 | Ok(()) 338 | } 339 | 340 | /// 统一发送请求 341 | fn send_data(addr: SocketAddr, pkg: Package) { 342 | info!("send package: {:?}", &pkg); 343 | let stream = TcpStream::connect(addr); 344 | if stream.is_err() { 345 | error!("The {} is not valid", addr); 346 | // 驱逐不健康的 Node 347 | GLOBAL_NODES.evict_node(addr.to_string().as_str()); 348 | return; 349 | } 350 | let mut stream = stream.unwrap(); 351 | let _ = stream.set_write_timeout(Option::from(Duration::from_millis(TCP_WRITE_TIMEOUT))); 352 | let _ = serde_json::to_writer(&stream, &pkg); 353 | let _ = stream.flush(); 354 | } 355 | 356 | #[cfg(test)] 357 | mod tests { 358 | use crate::server::{send_get_data, OpType}; 359 | use data_encoding::HEXLOWER; 360 | 361 | #[test] 362 | fn test_send_get_block() { 363 | send_get_data( 364 | "127.0.0.1:2001", 365 | OpType::Block, 366 | "00f95a9ca28526e95e94f2eda7d3c6559f41a30b184991d5ccc036de7b134408".as_bytes(), 367 | ); 368 | } 369 | 370 | #[test] 371 | fn test_send_get_transaction() { 372 | let txid = HEXLOWER 373 | .decode("164651291115cbf132f6c3e2a9729a84b0eb29da4481b7dfcd1e1b9e708cb6fa".as_bytes()) 374 | .unwrap(); 375 | send_get_data("127.0.0.1:2001", OpType::Tx, &txid); 376 | } 377 | 378 | #[test] 379 | fn test_vec() { 380 | let a = vec![6, 1, 2, 3, 5, 4]; 381 | println!("{:?}", a); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "assert_cmd" 25 | version = "0.11.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "2dc477793bd82ec39799b6f6b3df64938532fdf2ab0d49ef817eac65856a5a1e" 28 | dependencies = [ 29 | "escargot", 30 | "predicates", 31 | "predicates-core", 32 | "predicates-tree", 33 | ] 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.14" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 40 | dependencies = [ 41 | "hermit-abi", 42 | "libc", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.0.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 51 | 52 | [[package]] 53 | name = "bincode" 54 | version = "1.3.3" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 57 | dependencies = [ 58 | "serde", 59 | ] 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.3.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 66 | 67 | [[package]] 68 | name = "blockchain_rust" 69 | version = "0.1.0" 70 | dependencies = [ 71 | "assert_cmd", 72 | "bincode", 73 | "bs58", 74 | "clap", 75 | "data-encoding", 76 | "env_logger", 77 | "log", 78 | "num-bigint", 79 | "once_cell", 80 | "ring", 81 | "rust-crypto", 82 | "rustc-serialize", 83 | "serde", 84 | "serde_json", 85 | "sled", 86 | "structopt", 87 | "uuid", 88 | ] 89 | 90 | [[package]] 91 | name = "bs58" 92 | version = "0.4.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" 95 | 96 | [[package]] 97 | name = "bumpalo" 98 | version = "3.8.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 101 | 102 | [[package]] 103 | name = "byteorder" 104 | version = "1.4.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 107 | 108 | [[package]] 109 | name = "cc" 110 | version = "1.0.72" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 113 | 114 | [[package]] 115 | name = "cfg-if" 116 | version = "1.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 119 | 120 | [[package]] 121 | name = "clap" 122 | version = "2.34.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 125 | dependencies = [ 126 | "ansi_term", 127 | "atty", 128 | "bitflags", 129 | "strsim", 130 | "textwrap", 131 | "unicode-width", 132 | "vec_map", 133 | ] 134 | 135 | [[package]] 136 | name = "crc32fast" 137 | version = "1.3.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" 140 | dependencies = [ 141 | "cfg-if", 142 | ] 143 | 144 | [[package]] 145 | name = "crossbeam-epoch" 146 | version = "0.9.5" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" 149 | dependencies = [ 150 | "cfg-if", 151 | "crossbeam-utils", 152 | "lazy_static", 153 | "memoffset", 154 | "scopeguard", 155 | ] 156 | 157 | [[package]] 158 | name = "crossbeam-utils" 159 | version = "0.8.5" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 162 | dependencies = [ 163 | "cfg-if", 164 | "lazy_static", 165 | ] 166 | 167 | [[package]] 168 | name = "data-encoding" 169 | version = "2.3.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" 172 | 173 | [[package]] 174 | name = "difference" 175 | version = "2.0.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 178 | 179 | [[package]] 180 | name = "env_logger" 181 | version = "0.9.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 184 | dependencies = [ 185 | "atty", 186 | "humantime", 187 | "log", 188 | "regex", 189 | "termcolor", 190 | ] 191 | 192 | [[package]] 193 | name = "escargot" 194 | version = "0.4.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "ceb9adbf9874d5d028b5e4c5739d22b71988252b25c9c98fe7cf9738bee84597" 197 | dependencies = [ 198 | "lazy_static", 199 | "log", 200 | "serde", 201 | "serde_json", 202 | ] 203 | 204 | [[package]] 205 | name = "fs2" 206 | version = "0.4.3" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" 209 | dependencies = [ 210 | "libc", 211 | "winapi", 212 | ] 213 | 214 | [[package]] 215 | name = "fuchsia-cprng" 216 | version = "0.1.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 219 | 220 | [[package]] 221 | name = "fxhash" 222 | version = "0.2.1" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 225 | dependencies = [ 226 | "byteorder", 227 | ] 228 | 229 | [[package]] 230 | name = "gcc" 231 | version = "0.3.55" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 234 | 235 | [[package]] 236 | name = "getrandom" 237 | version = "0.2.3" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 240 | dependencies = [ 241 | "cfg-if", 242 | "libc", 243 | "wasi", 244 | ] 245 | 246 | [[package]] 247 | name = "heck" 248 | version = "0.3.3" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 251 | dependencies = [ 252 | "unicode-segmentation", 253 | ] 254 | 255 | [[package]] 256 | name = "hermit-abi" 257 | version = "0.1.19" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 260 | dependencies = [ 261 | "libc", 262 | ] 263 | 264 | [[package]] 265 | name = "humantime" 266 | version = "2.1.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 269 | 270 | [[package]] 271 | name = "instant" 272 | version = "0.1.12" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 275 | dependencies = [ 276 | "cfg-if", 277 | ] 278 | 279 | [[package]] 280 | name = "itoa" 281 | version = "1.0.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 284 | 285 | [[package]] 286 | name = "js-sys" 287 | version = "0.3.55" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 290 | dependencies = [ 291 | "wasm-bindgen", 292 | ] 293 | 294 | [[package]] 295 | name = "lazy_static" 296 | version = "1.4.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 299 | 300 | [[package]] 301 | name = "libc" 302 | version = "0.2.111" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "8e167738f1866a7ec625567bae89ca0d44477232a4f7c52b1c7f2adc2c98804f" 305 | 306 | [[package]] 307 | name = "lock_api" 308 | version = "0.4.5" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" 311 | dependencies = [ 312 | "scopeguard", 313 | ] 314 | 315 | [[package]] 316 | name = "log" 317 | version = "0.4.14" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 320 | dependencies = [ 321 | "cfg-if", 322 | ] 323 | 324 | [[package]] 325 | name = "memchr" 326 | version = "2.4.1" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 329 | 330 | [[package]] 331 | name = "memoffset" 332 | version = "0.6.5" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 335 | dependencies = [ 336 | "autocfg", 337 | ] 338 | 339 | [[package]] 340 | name = "num-bigint" 341 | version = "0.4.3" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 344 | dependencies = [ 345 | "autocfg", 346 | "num-integer", 347 | "num-traits", 348 | ] 349 | 350 | [[package]] 351 | name = "num-integer" 352 | version = "0.1.44" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 355 | dependencies = [ 356 | "autocfg", 357 | "num-traits", 358 | ] 359 | 360 | [[package]] 361 | name = "num-traits" 362 | version = "0.2.14" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 365 | dependencies = [ 366 | "autocfg", 367 | ] 368 | 369 | [[package]] 370 | name = "once_cell" 371 | version = "1.9.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 374 | 375 | [[package]] 376 | name = "parking_lot" 377 | version = "0.11.2" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 380 | dependencies = [ 381 | "instant", 382 | "lock_api", 383 | "parking_lot_core", 384 | ] 385 | 386 | [[package]] 387 | name = "parking_lot_core" 388 | version = "0.8.5" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 391 | dependencies = [ 392 | "cfg-if", 393 | "instant", 394 | "libc", 395 | "redox_syscall", 396 | "smallvec", 397 | "winapi", 398 | ] 399 | 400 | [[package]] 401 | name = "predicates" 402 | version = "1.0.8" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" 405 | dependencies = [ 406 | "difference", 407 | "predicates-core", 408 | ] 409 | 410 | [[package]] 411 | name = "predicates-core" 412 | version = "1.0.2" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" 415 | 416 | [[package]] 417 | name = "predicates-tree" 418 | version = "1.0.4" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7" 421 | dependencies = [ 422 | "predicates-core", 423 | "termtree", 424 | ] 425 | 426 | [[package]] 427 | name = "proc-macro-error" 428 | version = "1.0.4" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 431 | dependencies = [ 432 | "proc-macro-error-attr", 433 | "proc-macro2", 434 | "quote", 435 | "syn", 436 | "version_check", 437 | ] 438 | 439 | [[package]] 440 | name = "proc-macro-error-attr" 441 | version = "1.0.4" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 444 | dependencies = [ 445 | "proc-macro2", 446 | "quote", 447 | "version_check", 448 | ] 449 | 450 | [[package]] 451 | name = "proc-macro2" 452 | version = "1.0.33" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" 455 | dependencies = [ 456 | "unicode-xid", 457 | ] 458 | 459 | [[package]] 460 | name = "quote" 461 | version = "1.0.10" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 464 | dependencies = [ 465 | "proc-macro2", 466 | ] 467 | 468 | [[package]] 469 | name = "rand" 470 | version = "0.3.23" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 473 | dependencies = [ 474 | "libc", 475 | "rand 0.4.6", 476 | ] 477 | 478 | [[package]] 479 | name = "rand" 480 | version = "0.4.6" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 483 | dependencies = [ 484 | "fuchsia-cprng", 485 | "libc", 486 | "rand_core 0.3.1", 487 | "rdrand", 488 | "winapi", 489 | ] 490 | 491 | [[package]] 492 | name = "rand_core" 493 | version = "0.3.1" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 496 | dependencies = [ 497 | "rand_core 0.4.2", 498 | ] 499 | 500 | [[package]] 501 | name = "rand_core" 502 | version = "0.4.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 505 | 506 | [[package]] 507 | name = "rdrand" 508 | version = "0.4.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 511 | dependencies = [ 512 | "rand_core 0.3.1", 513 | ] 514 | 515 | [[package]] 516 | name = "redox_syscall" 517 | version = "0.2.10" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 520 | dependencies = [ 521 | "bitflags", 522 | ] 523 | 524 | [[package]] 525 | name = "regex" 526 | version = "1.5.4" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 529 | dependencies = [ 530 | "aho-corasick", 531 | "memchr", 532 | "regex-syntax", 533 | ] 534 | 535 | [[package]] 536 | name = "regex-syntax" 537 | version = "0.6.25" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 540 | 541 | [[package]] 542 | name = "ring" 543 | version = "0.16.20" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 546 | dependencies = [ 547 | "cc", 548 | "libc", 549 | "once_cell", 550 | "spin", 551 | "untrusted", 552 | "web-sys", 553 | "winapi", 554 | ] 555 | 556 | [[package]] 557 | name = "rust-crypto" 558 | version = "0.2.36" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 561 | dependencies = [ 562 | "gcc", 563 | "libc", 564 | "rand 0.3.23", 565 | "rustc-serialize", 566 | "time", 567 | ] 568 | 569 | [[package]] 570 | name = "rustc-serialize" 571 | version = "0.3.24" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 574 | 575 | [[package]] 576 | name = "ryu" 577 | version = "1.0.9" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 580 | 581 | [[package]] 582 | name = "scopeguard" 583 | version = "1.1.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 586 | 587 | [[package]] 588 | name = "serde" 589 | version = "1.0.132" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" 592 | dependencies = [ 593 | "serde_derive", 594 | ] 595 | 596 | [[package]] 597 | name = "serde_derive" 598 | version = "1.0.132" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" 601 | dependencies = [ 602 | "proc-macro2", 603 | "quote", 604 | "syn", 605 | ] 606 | 607 | [[package]] 608 | name = "serde_json" 609 | version = "1.0.73" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" 612 | dependencies = [ 613 | "itoa", 614 | "ryu", 615 | "serde", 616 | ] 617 | 618 | [[package]] 619 | name = "sled" 620 | version = "0.34.7" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" 623 | dependencies = [ 624 | "crc32fast", 625 | "crossbeam-epoch", 626 | "crossbeam-utils", 627 | "fs2", 628 | "fxhash", 629 | "libc", 630 | "log", 631 | "parking_lot", 632 | ] 633 | 634 | [[package]] 635 | name = "smallvec" 636 | version = "1.7.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 639 | 640 | [[package]] 641 | name = "spin" 642 | version = "0.5.2" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 645 | 646 | [[package]] 647 | name = "strsim" 648 | version = "0.8.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 651 | 652 | [[package]] 653 | name = "structopt" 654 | version = "0.3.25" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" 657 | dependencies = [ 658 | "clap", 659 | "lazy_static", 660 | "structopt-derive", 661 | ] 662 | 663 | [[package]] 664 | name = "structopt-derive" 665 | version = "0.4.18" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 668 | dependencies = [ 669 | "heck", 670 | "proc-macro-error", 671 | "proc-macro2", 672 | "quote", 673 | "syn", 674 | ] 675 | 676 | [[package]] 677 | name = "syn" 678 | version = "1.0.82" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 681 | dependencies = [ 682 | "proc-macro2", 683 | "quote", 684 | "unicode-xid", 685 | ] 686 | 687 | [[package]] 688 | name = "termcolor" 689 | version = "1.1.2" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 692 | dependencies = [ 693 | "winapi-util", 694 | ] 695 | 696 | [[package]] 697 | name = "termtree" 698 | version = "0.2.3" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" 701 | 702 | [[package]] 703 | name = "textwrap" 704 | version = "0.11.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 707 | dependencies = [ 708 | "unicode-width", 709 | ] 710 | 711 | [[package]] 712 | name = "time" 713 | version = "0.1.44" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 716 | dependencies = [ 717 | "libc", 718 | "wasi", 719 | "winapi", 720 | ] 721 | 722 | [[package]] 723 | name = "unicode-segmentation" 724 | version = "1.8.0" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 727 | 728 | [[package]] 729 | name = "unicode-width" 730 | version = "0.1.9" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 733 | 734 | [[package]] 735 | name = "unicode-xid" 736 | version = "0.2.2" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 739 | 740 | [[package]] 741 | name = "untrusted" 742 | version = "0.7.1" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 745 | 746 | [[package]] 747 | name = "uuid" 748 | version = "0.8.2" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 751 | dependencies = [ 752 | "getrandom", 753 | ] 754 | 755 | [[package]] 756 | name = "vec_map" 757 | version = "0.8.2" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 760 | 761 | [[package]] 762 | name = "version_check" 763 | version = "0.9.3" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 766 | 767 | [[package]] 768 | name = "wasi" 769 | version = "0.10.0+wasi-snapshot-preview1" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 772 | 773 | [[package]] 774 | name = "wasm-bindgen" 775 | version = "0.2.78" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 778 | dependencies = [ 779 | "cfg-if", 780 | "wasm-bindgen-macro", 781 | ] 782 | 783 | [[package]] 784 | name = "wasm-bindgen-backend" 785 | version = "0.2.78" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 788 | dependencies = [ 789 | "bumpalo", 790 | "lazy_static", 791 | "log", 792 | "proc-macro2", 793 | "quote", 794 | "syn", 795 | "wasm-bindgen-shared", 796 | ] 797 | 798 | [[package]] 799 | name = "wasm-bindgen-macro" 800 | version = "0.2.78" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 803 | dependencies = [ 804 | "quote", 805 | "wasm-bindgen-macro-support", 806 | ] 807 | 808 | [[package]] 809 | name = "wasm-bindgen-macro-support" 810 | version = "0.2.78" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 813 | dependencies = [ 814 | "proc-macro2", 815 | "quote", 816 | "syn", 817 | "wasm-bindgen-backend", 818 | "wasm-bindgen-shared", 819 | ] 820 | 821 | [[package]] 822 | name = "wasm-bindgen-shared" 823 | version = "0.2.78" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 826 | 827 | [[package]] 828 | name = "web-sys" 829 | version = "0.3.55" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 832 | dependencies = [ 833 | "js-sys", 834 | "wasm-bindgen", 835 | ] 836 | 837 | [[package]] 838 | name = "winapi" 839 | version = "0.3.9" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 842 | dependencies = [ 843 | "winapi-i686-pc-windows-gnu", 844 | "winapi-x86_64-pc-windows-gnu", 845 | ] 846 | 847 | [[package]] 848 | name = "winapi-i686-pc-windows-gnu" 849 | version = "0.4.0" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 852 | 853 | [[package]] 854 | name = "winapi-util" 855 | version = "0.1.5" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 858 | dependencies = [ 859 | "winapi", 860 | ] 861 | 862 | [[package]] 863 | name = "winapi-x86_64-pc-windows-gnu" 864 | version = "0.4.0" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 867 | --------------------------------------------------------------------------------