├── LICENSE ├── README.md ├── chapter 2 └── code.rs ├── chapter 3 ├── block.rs ├── req_functions.rs └── structs.rs ├── chapter 4 ├── blockchain.rs ├── node.rs └── server.rs ├── chapter 5 ├── config.rs ├── lib.rs ├── main.rs ├── memory_pool.rs ├── transactions.rs ├── utilits.rs ├── utxoSet.rs └── wallets.rs ├── chapter 6 ├── A basic NFT.sol ├── code.sol └── foundry_complete_app │ ├── .gitignore │ ├── .gitmodules │ ├── README.md │ ├── foundry.toml │ ├── script │ └── Counter.s.sol │ ├── src │ ├── Greeter.sol │ ├── MyContract.sol │ ├── MyContractVer2.sol │ └── NFT.sol │ └── test │ ├── Deployment.t.sol │ ├── ForkAndFuzz.t.sol │ ├── Greeter.t.sol │ ├── InvariantAndDifferential.t.sol │ ├── MyContract.t.sol │ └── NFTTest.t.sol ├── chapter 7 ├── complete_code_chapter_7 │ ├── .gitignore │ ├── .prettierignore │ ├── Anchor.toml │ ├── Cargo.toml │ ├── migrations │ │ └── deploy.ts │ ├── package-lock.json │ ├── package.json │ ├── programs │ │ └── solana-custom │ │ │ ├── Cargo.toml │ │ │ ├── Xargo.toml │ │ │ └── src │ │ │ └── lib.rs │ ├── tests │ │ └── solana-custom.ts │ └── tsconfig.json ├── message accounts lib.rs └── solana test file.sol ├── chapter 8 ├── Cross contract calls.rs ├── NEAR SDK.rs ├── NEAR blockchain.rs ├── NEAR project Crossword.rs ├── NEAR_complete_app │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.sh │ ├── deploy.sh │ ├── rust-toolchain.toml │ ├── sandbox-rs │ │ ├── Cargo.toml │ │ └── src │ │ │ └── tests.rs │ ├── src │ │ └── lib.rs │ └── test.sh ├── Near js to interact with crossword.js ├── State and data structures.rs ├── Transfers and actions └── foundational elements of NEAR.rs ├── chapter 9 ├── Frame.rs ├── core primitives.rs └── parachain transaction type.rs └── complete_blockchain ├── Cargo.lock ├── Cargo.toml └── src ├── block.rs ├── blockchain.rs ├── config.rs ├── lib.rs ├── main.rs ├── memory_pool.rs ├── node.rs ├── req_functions.rs ├── server.rs ├── structs.rs ├── transactions.rs ├── utils.rs ├── utxoSet.rs └── wallets.rs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust for Blockchain Application Development 2 | 3 | no-image 4 | 5 | This is the code repository for [Rust for Blockchain Application Development](https://www.packtpub.com/product/rust-for-blockchain-application-development/9781837634644), published by Packt. 6 | 7 | **Learn to build decentralized applications on popular blockchain technologies using Rust** 8 | 9 | ## What is this book about? 10 | This book helps you build your own blockchains and production-grade decentralized apps on blockchains like Ethereum, Solana, NEAR, and Polkadot. You’ll explore best practices, code, and assets that can be used for scaffolding multiple projects. 11 | 12 | This book covers the following exciting features: 13 | * Understand essential Rust concepts required to build blockchain 14 | * Apply blockchain features such as nodes and p2 communication using Rust 15 | * Understand and implement consensus in blockchain 16 | * Build and deploy a dApp on Ethereum with the Foundry framework in Rust 17 | * Develop and deploy a dApp on Solana and the NEAR protocol 18 | * Build a custom blockchain using the Substrate framework by Polkadot 19 | 20 | If you feel this book is for you, get your [copy](https://www.amazon.com/Rust-Blockchain-Application-Development-decentralized/dp/1837634645/ref=sr_1_1?crid=2C0TV7WOZK24V&dib=eyJ2IjoiMSJ9.VLeHyaoxnARCyNXrIwAZNwjCinJ6S_W4IBTPGuSYu8vny5FWRzKryHAN05m9vasHdvTixbNwvnjl6u3Z_GO4Q62EwWfe2K6VEP1hQQ7x5-5ZFRF85qNn-vhrjMJLMPYPtcUcjoVpn4WkSBdQXBkt2PFg2i1OPbqtsJguhKnj0xfWD9JsV9EYUYYoL27K5WdtmHwnpdVD1-frNBsMsB94ORqMoT7PBhzJzKihiRspj_s.kgXK_G44g3O5KxkRwwqaK11vGctCCQqqMwbVSHSeXR8&dib_tag=se&keywords=Rust+for+Blockchain+Application+Development&qid=1713187625&sprefix=rust+for+blockchain+application+development%2Caps%2C422&sr=8-1) today! 21 | https://www.packtpub.com/ 23 | ## Instructions and Navigations 24 | All of the code is organized into folders. For example, Chapter02. 25 | 26 | The code will look like the following: 27 | ``` 28 | pub struct Block { 29 | timestamp: i64, 30 | pre_block_hash: String, 31 | hash: String, 32 | transactions: Vec, 33 | nonce: i64, 34 | height: usize, 35 | } 36 | ``` 37 | 38 | **Following is what you need for this book:** 39 | This Rust programming book is for blockchain developers interested in building dApps on popular blockchains using Rust. Blockchain architects wanting to save time required to go through documentation and understand each technology can also use this book as a quick-start guide. Experience in building applications on blockchain is required, and familiarity with Rust will be helpful but not necessary. 40 | 41 | With the following software and hardware list you can run all code files present in the book (Chapter 1-11). 42 | ## Software and Hardware List 43 | | Chapter | Software required | OS required | 44 | | -------- | ------------------------------------ | ----------------------------------- | 45 | | 1-11 | Rust 1.74.0 or higher | Windows, macOS, or Linux | 46 | | 1-11 | Cargo | Windows, macOS, or Linux | 47 | 48 | ## Related products 49 | * Blockchain Development for Finance Projects [[Packt]](https://www.packtpub.com/product/blockchain-development-for-finance-projects/9781838829094) [[Amazon]](https://www.amazon.com/Blockchain-Development-Finance-Projects-next-generation/dp/1838829091/ref=sr_1_1?dib=eyJ2IjoiMSJ9.eNf1c3_cfBN-Duwo7lGnHVOsZBPbYWQPejWA4cZlQOVakpAeRuCWiglOHEVqwnFFy29ex3QauzX5aS0UzHtwEpl2uzuEkwbxCqa03gTNf3aglKGHp2H-9TLMUUm7TuWuXmrhAuS_JwRQ47L-g0mWH3GWgh3HzAf8l-e-Y9nQKnPUFNUEHAZotIswjke5X4jwaqlJGPM6TPDm5tXcgBt8si1nAIBLQKySNOcm5MX8cPo.6dXUzgjUJbhmEKZUqMnXRFKRzmRn7zxkgoz5ue_f1bw&dib_tag=se&keywords=Blockchain+Development+for+Finance+Projects&qid=1713417756&sr=8-1) 50 | 51 | * Securing Blockchain Networks like Ethereum and Hyperledger Fabric [[Packt]](https://www.packtpub.com/product/securing-blockchain-networks-like-ethereum-and-hyperledger-fabric/9781838646486) [[Amazon]](https://www.amazon.com/Securing-Blockchain-advanced-configurations-Hyperledger/dp/1838646485/ref=sr_1_1?dib=eyJ2IjoiMSJ9.KXbWqUeboIcuUC50Keis4-Fh8EHDdnTKIlRH7zVexmwWKnIxNQakYOF1ZDL1UG4RXGBRVVXn2SDwEJj3M7tyR223bXb1-f1sDw2_V33-TxgM1XiEde5Q6VtM1tf_LdTErVXvGP-iybgR5sFsvQMZipjVmvRHDq96jAkfvUAzghtQCbqZ-_MXTvnAmnAzQBDqQW0muj3ND3vEnyNJnkNtsvZvuT6U-HmHCUc2V1vHVuA.sUksLXECxbJ2z-NjRfTtHuDwukno1Pf3gEclDlRsnOk&dib_tag=se&keywords=Securing+Blockchain+Networks+like+Ethereum+and+Hyperledger+Fabric&qid=1713417830&sr=8-1) 52 | 53 | ## Get to Know the Author 54 | **Akhil Sharma** 55 | is the Founder at Armur AI, a cybersecurity company that is backed by Techstars, Outlier Ventures, Aptos and is part of the Google AI startups cloud program. 56 | Akhil teaches advanced engineering topics (Rust, GO, Blockchain, AI) on his Youtube channel and has mentored more than 200K engineers across platforms like Linkedin Learning, Udemy and Packt. 57 | Being deeply involved with multiple Rust-based blockchain communities like Aptos, Solana and Polkadot inspired Akhil to write this book. 58 | In his free time, Akhil likes to train in Jiu Jitsu, play the guitar or surf. 59 | -------------------------------------------------------------------------------- /chapter 2/code.rs: -------------------------------------------------------------------------------- 1 | // represent a block from a blockchain, using Rust structs 2 | 3 | pub struct Block { 4 | pub id: u64, 5 | pub hash: String, 6 | pub previous_hash: String, 7 | pub timestamp: i64, 8 | pub txn_data: String, 9 | pub nonce: u64, 10 | } 11 | 12 | 13 | // blockchain can be represented 14 | pub struct Blockchain { 15 | pub blocks: Vec, 16 | } 17 | 18 | // variables in rust 19 | fn main() { 20 | let x = 5; 21 | println!(“The value of is:{x}”); 22 | } 23 | 24 | // all variables in Rust are immutable 25 | fn main() { 26 | let x = 5; 27 | println!(“The value of is:{x}”); 28 | x = 6; 29 | println!(“The value of is:{x}”); 30 | } 31 | 32 | // “mut” keyword 33 | fn main() { 34 | let mut x = 5; 35 | println!(“The value of is:{x}”); 36 | x = 6; 37 | println!(“The value of is:{x}”); 38 | } 39 | 40 | // a constant expression 41 | Const HOURS_IN_A_DAY = 24; 42 | 43 | 44 | // “let” keyword to assign a new value to the variable 45 | fn main() { 46 | let mut x = 5; 47 | println!(“The value of is:{x}”); 48 | let x = 6; 49 | println!(“The value of is:{x}”); 50 | } 51 | 52 | 53 | // floating-point numbers 54 | fn main() { 55 | let x = 2.0; 56 | let y: f32 = 3.0 57 | } 58 | 59 | // Boolean 60 | fn main() { 61 | let t = true; 62 | let f: bool = false; 63 | } 64 | 65 | // char 66 | fn main() { 67 | let t: char = 'z'; 68 | let f: char = Smiley face emoji ; 69 | } 70 | 71 | // tuples 72 | fn main() { 73 | let tup = (500, 6.4, 1); 74 | let (x, y, z) = tup; 75 | println!(“the value of y is: {y}”); 76 | let x:(i32, f64, u8) = (500, 6.4, 1); 77 | let five_hundred = x.0; 78 | } 79 | 80 | // arrays 81 | fn main() { 82 | let a = [1, 2, 3, 4, 5]; 83 | let months = [“jan”, “feb”, “mar”]; 84 | let b: [i32; 5] = [1, 2, 3, 4, 5]; 85 | let z = [3; 5]; 86 | let first = a[0]; 87 | } 88 | 89 | // Numeric Operations 90 | 91 | fn main() { 92 | let sum = 15 + 2; 93 | let difference = 15.3 - 2.2; 94 | let multiplication = 2 * 20; 95 | let division = 20 / 2; 96 | let remainder = 21 %2 ; 97 | } 98 | 99 | // Slices 100 | fn main() { 101 | let n1 = “example”.to_string(); 102 | let c1 = &n1[4..7]; 103 | } 104 | 105 | 106 | fn main() { 107 | let arr = [5, 7, 9, 11, 13]; 108 | let slice = &arr[1..3]; 109 | assert_eq!(slice, &[7, 9]); 110 | } 111 | 112 | 113 | // Strings 114 | fn main() { 115 | let s = “hello”; 116 | } 117 | 118 | // String type 119 | fn main() { 120 | let mut hello = String::from(“hello”); 121 | hello.push('w'); 122 | hello.push_str(“world!”); 123 | } 124 | 125 | // ‘to.string()’ 126 | fn main() { 127 | let i = 5; 128 | let five = String::from(“5”); 129 | assert_eq!(five, i.to_string()); 130 | } 131 | 132 | // Enumns 133 | 134 | enum CacheType{ 135 | LRU, 136 | MRU, 137 | } 138 | let lru = CacheType::LRU; 139 | let mru = CacheType::MRU; 140 | 141 | struct Cache{ 142 | level: String, 143 | type: CacheType 144 | } 145 | 146 | // Control Flow 147 | fn main() { 148 | let i = 5; 149 | if i > 3 { 150 | println!(“condition met, i is greater than 3”); 151 | 152 | } else { 153 | println!(“condition was not met”); 154 | } 155 | } 156 | 157 | // Control Flow 2 158 | fn main() { 159 | let a = 10; 160 | if a % 4 == 0 { 161 | println!(“a is divisible by 4”); 162 | } else if a % 3 == 0 { 163 | println!(“a is divisible by 3”); 164 | } else if a % 2 == 0 { 165 | println!(“a is divisible by 2”); 166 | } else { 167 | println!(“a is not divisible by 4,3, or 2”); 168 | } 169 | } 170 | 171 | 172 | // Functions 173 | fn main() { 174 | let a = plus_ten(); 175 | println!(“the value of a is: {a}”); 176 | } 177 | 178 | fn plus_ten(a: i32) -> i32 { 179 | a+10 180 | } 181 | 182 | 183 | // Match Control Flow 184 | 185 | enum Web3{ 186 | Defi, 187 | NFT, 188 | Game, 189 | Metaverse 190 | } 191 | fn number_assign(web3: Web3) -> u8 { 192 | match web3 { 193 | Web3::Defi => 1, 194 | Web3::NFT => 2, 195 | Web3::Game => 3, 196 | Web3::Metaverse => 4, 197 | } 198 | } 199 | 200 | // Structs 201 | 202 | struct Employee { 203 | name: String, 204 | assigned_id: u64, 205 | email: String, 206 | active: bool, 207 | } 208 | 209 | fn main () { 210 | let emp1 = Employee { 211 | name: String::from(“emplyee1”) 212 | assigned_id: 1, 213 | email: String::from(“employee@acme.com”) 214 | active: true, 215 | } 216 | } 217 | 218 | // Vectors 219 | 220 | Let vector = vec![1, 2, 3]; 221 | 222 | Let vector: Vec = Vec::new( ); 223 | 224 | struct rectangle { 225 | w: i8 226 | h: i8 227 | } 228 | 229 | let mut v = vec![]; 230 | v.push(rectangle{w: 3, h: 4}); 231 | v.push(rectangle{w: 99, h: 42}); 232 | 233 | 234 | // Hashmaps 235 | 236 | fn main() { 237 | use std::collections::HashMap; 238 | let mut rgb = HashMap::new(); 239 | rgb.insert(String::from("Blue"), 10); 240 | rgb.insert(String::from("Green"), 50); 241 | rgb.insert(String::from(“Red”), 100); 242 | for (key, value) in &rgb{ 243 | println!(“{key}:{value}”); 244 | } 245 | } 246 | 247 | 248 | // Ownership and Borrowing 249 | fn main() { 250 | let example = String::from("hello"); 251 | } 252 | 253 | fn main( ){ 254 | let example = String::from(“hello”); 255 | another_function(example); 256 | println!(“{}”, example) 257 | } 258 | fn another_function(example String){ 259 | println!(“{}”, example) 260 | } 261 | 262 | fn main() { 263 | let s1 = String::from("hello"); 264 | let len = calculate_length(&s1); 265 | println!("The length of '{}' is {}.", s1, len); 266 | } 267 | 268 | fn calculate_length(s: &String) -> usize { 269 | s.len() 270 | } 271 | 272 | // Crates, Modules, and Cargo 273 | 274 | mod english { 275 | mod greetings { 276 | } 277 | mod farewells { 278 | } 279 | } 280 | use phrases::english::greetings; 281 | use phrases::english::farewells; -------------------------------------------------------------------------------- /chapter 3/block.rs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------- 2 | // Getting started with building the Blockchain ( block.rs / proof of work.rs) 3 | // -------------------------------------------------------------------------------------------------- 4 | 5 | // Block.rs file 6 | use crate::{ProofOfWork, Transaction}; 7 | use serde::{Deserialize, Serialize}; 8 | use sled::IVec; 9 | 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct Block { 12 | timestamp: i64, 13 | pre_block_hash: String, 14 | hash: String, 15 | transactions: Vec, 16 | nonce: i64, 17 | height: usize, 18 | } 19 | 20 | impl Block { 21 | 22 | pub fn new_block(pre_block_hash: String, transactions: &[Transaction], height: usize) -> Block { 23 | let mut block = Block { 24 | timestamp: crate::current_timestamp(), 25 | pre_block_hash, 26 | hash: String::new(), 27 | transactions: transactions.to_vec(), 28 | nonce: 0, 29 | height, 30 | }; 31 | 32 | let pow = ProofOfWork::new_proof_of_work(block.clone()); 33 | let (nonce, hash) = pow.run(); 34 | block.nonce = nonce; 35 | block.hash = hash; 36 | return block; 37 | } 38 | 39 | 40 | pub fn deserialize(bytes: &[u8]) -> Block { 41 | bincode::deserialize(bytes).unwrap() 42 | } 43 | 44 | 45 | pub fn serialize(&self) -> Vec { 46 | bincode::serialize(self).unwrap().to_vec() 47 | } 48 | 49 | 50 | pub fn generate_genesis_block(transaction: &Transaction) -> Block { 51 | let transactions = vec![transaction.clone()]; 52 | return Block::new_block(String::from("None"), &transactions, 0); 53 | } 54 | 55 | pub fn hash_transactions(&self) -> Vec { 56 | let mut txhashs = vec![]; 57 | for transaction in &self.transactions { 58 | txhashs.extend(transaction.get_id()); 59 | } 60 | crate::sha256_digest(txhashs.as_slice()) 61 | } 62 | 63 | pub fn get_transactions(&self) -> &[Transaction] { 64 | self.transactions.as_slice() 65 | } 66 | 67 | pub fn get_pre_block_hash(&self) -> String { 68 | self.pre_block_hash.clone() 69 | } 70 | 71 | pub fn get_hash(&self) -> &str { 72 | self.hash.as_str() 73 | } 74 | 75 | pub fn get_hash_bytes(&self) -> Vec { 76 | self.hash.as_bytes().to_vec() 77 | } 78 | 79 | pub fn get_timestamp(&self) -> i64 { 80 | self.timestamp 81 | } 82 | 83 | pub fn get_height(&self) -> usize { 84 | self.height 85 | } 86 | } 87 | 88 | impl From for IVec { 89 | fn from(b: Block) -> Self { 90 | let bytes = bincode::serialize(&b).unwrap(); 91 | Self::from(bytes) 92 | } 93 | } 94 | 95 | 96 | 97 | // Proof of work.rs 98 | 99 | use crate::Block; 100 | use data_encoding::HEXLOWER; 101 | use num_bigint::{BigInt, Sign}; 102 | use std::borrow::Borrow; 103 | use std::ops::ShlAssign; 104 | 105 | pub struct ProofOfWork { 106 | block: Block, 107 | target: BigInt, 108 | } 109 | 110 | 111 | const TARGET_BITS: i32 = 8; 112 | 113 | const MAX_NONCE: i64 = i64::MAX; 114 | 115 | impl ProofOfWork { 116 | pub fn new_proof_of_work(block: Block) -> ProofOfWork { 117 | let mut target = BigInt::from(1); 118 | 119 | target.shl_assign(256 - TARGET_BITS); 120 | ProofOfWork { block, target } 121 | } 122 | 123 | fn prepare_data(&self, nonce: i64) -> Vec { 124 | let pre_block_hash = self.block.get_pre_block_hash(); 125 | let transactions_hash = self.block.hash_transactions(); 126 | let timestamp = self.block.get_timestamp(); 127 | let mut data_bytes = vec![]; 128 | data_bytes.extend(pre_block_hash.as_bytes()); 129 | data_bytes.extend(transactions_hash); 130 | data_bytes.extend(timestamp.to_be_bytes()); 131 | data_bytes.extend(TARGET_BITS.to_be_bytes()); 132 | data_bytes.extend(nonce.to_be_bytes()); 133 | return data_bytes; 134 | } 135 | 136 | 137 | pub fn run(&self) -> (i64, String) { 138 | let mut nonce = 0; 139 | let mut hash = Vec::new(); 140 | println!("Mining the block"); 141 | while nonce < MAX_NONCE { 142 | let data = self.prepare_data(nonce); 143 | hash = crate::sha256_digest(data.as_slice()); 144 | let hash_int = BigInt::from_bytes_be(Sign::Plus, hash.as_slice()); 145 | 146 | 147 | if hash_int.lt(self.target.borrow()) { 148 | println!("{}", HEXLOWER.encode(hash.as_slice())); 149 | break; 150 | } else { 151 | nonce += 1; 152 | } 153 | } 154 | println!(); 155 | return (nonce, HEXLOWER.encode(hash.as_slice())); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /chapter 3/req_functions.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | // ------------------------------------------------------------------------------------------ 4 | // Required functions 5 | // ------------------------------------------------------------------------------------------ 6 | 7 | impl Block { 8 | 9 | pub fn new_block(pre_block_hash: String, transactions: &[Transaction], height: usize) -> Block { 10 | let mut block = Block { 11 | timestamp: crate::current_timestamp(), 12 | pre_block_hash, 13 | hash: String::new(), 14 | transactions: transactions.to_vec(), 15 | nonce: 0, 16 | height, 17 | }; 18 | 19 | let pow = ProofOfWork::new_proof_of_work(block.clone()); 20 | let (nonce, hash) = pow.run(); 21 | block.nonce = nonce; 22 | block.hash = hash; 23 | return block; 24 | } 25 | 26 | 27 | pub fn deserialize(bytes: &[u8]) -> Block { 28 | bincode::deserialize(bytes).unwrap() 29 | } 30 | 31 | 32 | pub fn serialize(&self) -> Vec { 33 | bincode::serialize(self).unwrap().to_vec() 34 | } 35 | 36 | 37 | pub fn generate_genesis_block(transaction: &Transaction) -> Block { 38 | let transactions = vec![transaction.clone()]; 39 | return Block::new_block(String::from("None"), &transactions, 0); 40 | } 41 | 42 | pub fn hash_transactions(&self) -> Vec { 43 | let mut txhashs = vec![]; 44 | for transaction in &self.transactions { 45 | txhashs.extend(transaction.get_id()); 46 | } 47 | crate::sha256_digest(txhashs.as_slice()) 48 | } 49 | 50 | pub fn get_transactions(&self) -> &[Transaction] { 51 | self.transactions.as_slice() 52 | } 53 | 54 | pub fn get_pre_block_hash(&self) -> String { 55 | self.pre_block_hash.clone() 56 | } 57 | 58 | pub fn get_hash(&self) -> &str { 59 | self.hash.as_str() 60 | } 61 | 62 | pub fn get_hash_bytes(&self) -> Vec { 63 | self.hash.as_bytes().to_vec() 64 | } 65 | 66 | pub fn get_timestamp(&self) -> i64 { 67 | self.timestamp 68 | } 69 | 70 | pub fn get_height(&self) -> usize { 71 | self.height 72 | } 73 | } 74 | 75 | 76 | impl Blockchain { 77 | 78 | pub fn create_blockchain(genesis_address: &str) -> Blockchain { 79 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 80 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 81 | 82 | let data = blocks_tree.get(TIP_BLOCK_HASH_KEY).unwrap(); 83 | let tip_hash; 84 | if data.is_none() { 85 | let coinbase_tx = Transaction::new_coinbase_tx(genesis_address); 86 | let block = Block::generate_genesis_block(&coinbase_tx); 87 | Self::update_blocks_tree(&blocks_tree, &block); 88 | tip_hash = String::from(block.get_hash()); 89 | } else { 90 | tip_hash = String::from_utf8(data.unwrap().to_vec()).unwrap(); 91 | } 92 | Blockchain { 93 | tip_hash: Arc::new(RwLock::new(tip_hash)), 94 | db, 95 | } 96 | } 97 | 98 | fn update_blocks_tree(blocks_tree: &Tree, block: &Block) { 99 | let block_hash = block.get_hash(); 100 | let _: TransactionResult<(), ()> = blocks_tree.transaction(|tx_db| { 101 | let _ = tx_db.insert(block_hash, block.clone()); 102 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block_hash); 103 | Ok(()) 104 | }); 105 | } 106 | 107 | 108 | pub fn new_blockchain() -> Blockchain { 109 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 110 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 111 | let tip_bytes = blocks_tree 112 | .get(TIP_BLOCK_HASH_KEY) 113 | .unwrap() 114 | .expect("No existing blockchain found. Create one first."); 115 | let tip_hash = String::from_utf8(tip_bytes.to_vec()).unwrap(); 116 | Blockchain { 117 | tip_hash: Arc::new(RwLock::new(tip_hash)), 118 | db, 119 | } 120 | } 121 | 122 | pub fn get_db(&self) -> &Db { 123 | &self.db 124 | } 125 | 126 | pub fn get_tip_hash(&self) -> String { 127 | self.tip_hash.read().unwrap().clone() 128 | } 129 | 130 | pub fn set_tip_hash(&self, new_tip_hash: &str) { 131 | let mut tip_hash = self.tip_hash.write().unwrap(); 132 | *tip_hash = String::from(new_tip_hash) 133 | } 134 | 135 | // let us move the iterator code up for readability of the users ? 136 | // pub fn iterator(&self) -> BlockchainIterator { 137 | // BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 138 | // } 139 | 140 | pub fn mine_block(&self, transactions: &[Transaction]) -> Block { 141 | for trasaction in transactions { 142 | if trasaction.verify(self) == false { 143 | panic!("ERROR: Invalid transaction") 144 | } 145 | } 146 | let best_height = self.get_best_height(); 147 | 148 | let block = Block::new_block(self.get_tip_hash(), transactions, best_height + 1); 149 | let block_hash = block.get_hash(); 150 | 151 | let blocks_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 152 | Self::update_blocks_tree(&blocks_tree, &block); 153 | self.set_tip_hash(block_hash); 154 | block 155 | } 156 | 157 | pub fn iterator(&self) -> BlockchainIterator { 158 | BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 159 | } 160 | 161 | // can we add the BlockchainIterator here so that the readers can follow easily 162 | 163 | 164 | 165 | // ( K -> txid_hex, V -> Vec HashMap> { 167 | let mut utxo: HashMap> = HashMap::new(); 168 | let mut spent_txos: HashMap> = HashMap::new(); 169 | 170 | let mut iterator = self.iterator(); 171 | loop { 172 | let option = iterator.next(); 173 | if option.is_none() { 174 | break; 175 | } 176 | let block = option.unwrap(); 177 | 'outer: for tx in block.get_transactions() { 178 | let txid_hex = HEXLOWER.encode(tx.get_id()); 179 | for (idx, out) in tx.get_vout().iter().enumerate() { 180 | 181 | if let Some(outs) = spent_txos.get(txid_hex.as_str()) { 182 | for spend_out_idx in outs { 183 | if idx.eq(spend_out_idx) { 184 | continue 'outer; 185 | } 186 | } 187 | } 188 | if utxo.contains_key(txid_hex.as_str()) { 189 | utxo.get_mut(txid_hex.as_str()).unwrap().push(out.clone()); 190 | } else { 191 | utxo.insert(txid_hex.clone(), vec![out.clone()]); 192 | } 193 | } 194 | if tx.is_coinbase() { 195 | continue; 196 | } 197 | 198 | for txin in tx.get_vin() { 199 | let txid_hex = HEXLOWER.encode(txin.get_txid()); 200 | if spent_txos.contains_key(txid_hex.as_str()) { 201 | spent_txos 202 | .get_mut(txid_hex.as_str()) 203 | .unwrap() 204 | .push(txin.get_vout()); 205 | } else { 206 | spent_txos.insert(txid_hex, vec![txin.get_vout()]); 207 | } 208 | } 209 | } 210 | } 211 | utxo 212 | } 213 | 214 | 215 | pub fn find_transaction(&self, txid: &[u8]) -> Option { 216 | let mut iterator = self.iterator(); 217 | loop { 218 | let option = iterator.next(); 219 | if option.is_none() { 220 | break; 221 | } 222 | let block = option.unwrap(); 223 | for transaction in block.get_transactions() { 224 | if txid.eq(transaction.get_id()) { 225 | return Some(transaction.clone()); 226 | } 227 | } 228 | } 229 | None 230 | } 231 | 232 | 233 | pub fn add_block(&self, block: &Block) { 234 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 235 | if let Some(_) = block_tree.get(block.get_hash()).unwrap() { 236 | return; 237 | } 238 | let _: TransactionResult<(), ()> = block_tree.transaction(|tx_db| { 239 | let _ = tx_db.insert(block.get_hash(), block.serialize()).unwrap(); 240 | 241 | let tip_block_bytes = tx_db 242 | .get(self.get_tip_hash()) 243 | .unwrap() 244 | .expect("The tip hash is not valid"); 245 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 246 | if block.get_height() > tip_block.get_height() { 247 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block.get_hash()).unwrap(); 248 | self.set_tip_hash(block.get_hash()); 249 | } 250 | Ok(()) 251 | }); 252 | } 253 | 254 | 255 | pub fn get_best_height(&self) -> usize { 256 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 257 | let tip_block_bytes = block_tree 258 | .get(self.get_tip_hash()) 259 | .unwrap() 260 | .expect("The tip hash is valid"); 261 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 262 | tip_block.get_height() 263 | } 264 | 265 | 266 | pub fn get_block(&self, block_hash: &[u8]) -> Option { 267 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 268 | if let Some(block_bytes) = block_tree.get(block_hash).unwrap() { 269 | let block = Block::deserialize(block_bytes.as_ref()); 270 | return Some(block); 271 | } 272 | return None; 273 | } 274 | 275 | 276 | pub fn get_block_hashes(&self) -> Vec> { 277 | let mut iterator = self.iterator(); 278 | let mut blocks = vec![]; 279 | loop { 280 | let option = iterator.next(); 281 | if option.is_none() { 282 | break; 283 | } 284 | let block = option.unwrap(); 285 | blocks.push(block.get_hash_bytes()); 286 | } 287 | return blocks; 288 | } 289 | } 290 | 291 | impl Node { 292 | fn new(addr: String) -> Node { 293 | Node { addr } 294 | } 295 | 296 | pub fn get_addr(&self) -> String { 297 | self.addr.clone() 298 | } 299 | 300 | pub fn parse_socket_addr(&self) -> SocketAddr { 301 | self.addr.parse().unwrap() 302 | } 303 | } 304 | 305 | 306 | impl Server { 307 | pub fn new(blockchain: Blockchain) -> Server { 308 | Server { blockchain } 309 | } 310 | 311 | pub fn run(&self, addr: &str) { 312 | let listener = TcpListener::bind(addr).unwrap(); 313 | 314 | if addr.eq(CENTERAL_NODE) == false { 315 | let best_height = self.blockchain.get_best_height(); 316 | send_version(CENTERAL_NODE, best_height); 317 | } 318 | for stream in listener.incoming() { 319 | let blockchain = self.blockchain.clone(); 320 | thread::spawn(|| match stream { 321 | Ok(stream) => { 322 | ... 323 | } 324 | Err(e) => { 325 | ... 326 | } 327 | }); 328 | } 329 | } 330 | } 331 | 332 | 333 | impl Transaction { 334 | 335 | pub fn new_coinbase_tx(to: &str) -> Transaction { 336 | let txout = TXOutput::new(SUBSIDY, to); 337 | let mut tx_input = TXInput::default(); 338 | tx_input.signature = Uuid::new_v4().as_bytes().to_vec(); 339 | .... 340 | return tx; 341 | } 342 | 343 | 344 | pub fn new_utxo_transaction( 345 | from: &str, 346 | to: &str, 347 | amount: i32, 348 | utxo_set: &UTXOSet, 349 | ) -> Transaction { 350 | ... 351 | let mut inputs = vec![]; 352 | for (txid_hex, outs) in valid_outputs { 353 | ... 354 | } 355 | let mut outputs = vec![TXOutput::new(amount, to)]; 356 | ... 357 | return tx; 358 | } 359 | 360 | 361 | fn trimmed_copy(&self) -> Transaction { 362 | ... 363 | } 364 | 365 | fn sign(&mut self, blockchain: &Blockchain, pkcs8: &[u8]) { 366 | let mut tx_copy = self.trimmed_copy(); 367 | for (idx, vin) in self.vin.iter_mut().enumerate() { 368 | .... 369 | } 370 | } 371 | 372 | pub fn verify(&self, blockchain: &Blockchain) -> bool { 373 | ... 374 | let mut tx_copy = self.trimmed_copy(); 375 | for (idx, vin) in self.vin.iter().enumerate() { 376 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 377 | let prev_tx = prev_tx_option.unwrap(); 378 | ... 379 | let verify = crate::ecdsa_p256_sha256_sign_verify( 380 | ... 381 | ); 382 | if !verify { 383 | return false; 384 | } 385 | } 386 | true 387 | } 388 | 389 | 390 | pub fn is_coinbase(&self) -> bool { 391 | return self.vin.len() == 1 && self.vin[0].pub_key.len() == 0; 392 | } 393 | 394 | 395 | fn hash(&mut self) -> Vec { 396 | ... 397 | crate::sha256_digest(tx_copy.serialize().as_slice()) 398 | } 399 | 400 | pub fn get_id(&self) -> &[u8] { 401 | self.id.as_slice() 402 | } 403 | 404 | pub fn get_id_bytes(&self) -> Vec { 405 | self.id.clone() 406 | } 407 | 408 | pub fn get_vin(&self) -> &[TXInput] { 409 | self.vin.as_slice() 410 | } 411 | 412 | pub fn get_vout(&self) -> &[TXOutput] { 413 | self.vout.as_slice() 414 | } 415 | 416 | pub fn serialize(&self) -> Vec { 417 | bincode::serialize(self).unwrap().to_vec() 418 | } 419 | 420 | pub fn deserialize(bytes: &[u8]) -> Transaction { 421 | bincode::deserialize(bytes).unwrap() 422 | } 423 | } 424 | 425 | 426 | impl Wallet { 427 | 428 | pub fn new() -> Wallet { 429 | let pkcs8 = crate::new_key_pair(); 430 | let key_pair = 431 | EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref()).unwrap(); 432 | let public_key = key_pair.public_key().as_ref().to_vec(); 433 | Wallet { pkcs8, public_key } 434 | } 435 | 436 | 437 | pub fn get_address(&self) -> String { 438 | let pub_key_hash = hash_pub_key(self.public_key.as_slice()); 439 | let mut payload: Vec = vec![]; 440 | payload.push(VERSION); 441 | payload.extend(pub_key_hash.as_slice()); 442 | let checksum = checksum(payload.as_slice()); 443 | payload.extend(checksum.as_slice()); 444 | // version + pub_key_hash + checksum 445 | crate::base58_encode(payload.as_slice()) 446 | } 447 | 448 | pub fn get_public_key(&self) -> &[u8] { 449 | self.public_key.as_slice() 450 | } 451 | 452 | pub fn get_pkcs8(&self) -> &[u8] { 453 | self.pkcs8.as_slice() 454 | } 455 | } 456 | 457 | 458 | impl UTXOSet { 459 | 460 | pub fn new(blockchain: Blockchain) -> UTXOSet { 461 | UTXOSet { blockchain } 462 | } 463 | 464 | pub fn get_blockchain(&self) -> &Blockchain { 465 | &self.blockchain 466 | } 467 | 468 | 469 | pub fn find_spendable_outputs( 470 | &self, 471 | pub_key_hash: &[u8], 472 | amount: i32, 473 | ) -> (i32, HashMap>) { 474 | let mut unspent_outputs: HashMap> = HashMap::new(); 475 | let mut accmulated = 0; 476 | let db = self.blockchain.get_db(); 477 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 478 | for item in utxo_tree.iter() { 479 | let (k, v) = item.unwrap(); 480 | let txid_hex = HEXLOWER.encode(k.to_vec().as_slice()); 481 | let outs: Vec = bincode::deserialize(v.to_vec().as_slice()) 482 | .expect("unable to deserialize TXOutput"); 483 | for (idx, out) in outs.iter().enumerate() { 484 | if out.is_locked_with_key(pub_key_hash) && accmulated < amount { 485 | accmulated += out.get_value(); 486 | if unspent_outputs.contains_key(txid_hex.as_str()) { 487 | unspent_outputs 488 | .get_mut(txid_hex.as_str()) 489 | .unwrap() 490 | .push(idx); 491 | } else { 492 | unspent_outputs.insert(txid_hex.clone(), vec![idx]); 493 | } 494 | } 495 | } 496 | } 497 | (accmulated, unspent_outputs) 498 | } 499 | 500 | 501 | pub fn find_utxo(&self, pub_key_hash: &[u8]) -> Vec { 502 | let db = self.blockchain.get_db(); 503 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 504 | let mut utxos = vec![]; 505 | for item in utxo_tree.iter() { 506 | let (_, v) = item.unwrap(); 507 | let outs: Vec = bincode::deserialize(v.to_vec().as_slice()) 508 | .expect("unable to deserialize TXOutput"); 509 | for out in outs.iter() { 510 | if out.is_locked_with_key(pub_key_hash) { 511 | utxos.push(out.clone()) 512 | } 513 | } 514 | } 515 | utxos 516 | } 517 | 518 | 519 | pub fn count_transactions(&self) -> i32 { 520 | let db = self.blockchain.get_db(); 521 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 522 | let mut counter = 0; 523 | for _ in utxo_tree.iter() { 524 | counter += 1; 525 | } 526 | counter 527 | } 528 | 529 | pub fn reindex(&self) { 530 | let db = self.blockchain.get_db(); 531 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 532 | let _ = utxo_tree.clear().unwrap(); 533 | 534 | let utxo_map = self.blockchain.find_utxo(); 535 | for (txid_hex, outs) in &utxo_map { 536 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 537 | let value = bincode::serialize(outs).unwrap(); 538 | let _ = utxo_tree.insert(txid.as_slice(), value).unwrap(); 539 | } 540 | } 541 | 542 | 543 | pub fn update(&self, block: &Block) { 544 | let db = self.blockchain.get_db(); 545 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 546 | for tx in block.get_transactions() { 547 | if tx.is_coinbase() == false { 548 | for vin in tx.get_vin() { 549 | let mut updated_outs = vec![]; 550 | let outs_bytes = utxo_tree.get(vin.get_txid()).unwrap().unwrap(); 551 | let outs: Vec = bincode::deserialize(outs_bytes.as_ref()) 552 | .expect("unable to deserialize TXOutput"); 553 | for (idx, out) in outs.iter().enumerate() { 554 | if idx != vin.get_vout() { 555 | updated_outs.push(out.clone()) 556 | } 557 | } 558 | if updated_outs.len() == 0 { 559 | let _ = utxo_tree.remove(vin.get_txid()).unwrap(); 560 | } else { 561 | let outs_bytes = bincode::serialize(&updated_outs) 562 | .expect("unable to serialize TXOutput"); 563 | utxo_tree.insert(vin.get_txid(), outs_bytes).unwrap(); 564 | } 565 | } 566 | } 567 | let mut new_outputs = vec![]; 568 | for out in tx.get_vout() { 569 | new_outputs.push(out.clone()) 570 | } 571 | let outs_bytes = 572 | bincode::serialize(&new_outputs).expect("unable to serialize TXOutput"); 573 | let _ = utxo_tree.insert(tx.get_id(), outs_bytes).unwrap(); 574 | } 575 | } 576 | } 577 | 578 | 579 | impl MemoryPool { 580 | pub fn new() -> MemoryPool { 581 | MemoryPool { 582 | inner: RwLock::new(HashMap::new()), 583 | } 584 | } 585 | 586 | pub fn contains(&self, txid_hex: &str) -> bool { 587 | self.inner.read().unwrap().contains_key(txid_hex) 588 | } 589 | 590 | pub fn add(&self, tx: Transaction) { 591 | let txid_hex = HEXLOWER.encode(tx.get_id()); 592 | self.inner.write().unwrap().insert(txid_hex, tx); 593 | } 594 | 595 | pub fn get(&self, txid_hex: &str) -> Option { 596 | if let Some(tx) = self.inner.read().unwrap().get(txid_hex) { 597 | return Some(tx.clone()); 598 | } 599 | None 600 | } 601 | 602 | pub fn remove(&self, txid_hex: &str) { 603 | let mut inner = self.inner.write().unwrap(); 604 | inner.remove(txid_hex); 605 | } 606 | 607 | pub fn get_all(&self) -> Vec { 608 | let inner = self.inner.read().unwrap(); 609 | let mut txs = vec![]; 610 | for (_, v) in inner.iter() { 611 | txs.push(v.clone()); 612 | } 613 | return txs; 614 | } 615 | 616 | pub fn len(&self) -> usize { 617 | self.inner.read().unwrap().len() 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /chapter 3/structs.rs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------- 2 | // Structs 3 | // -------------------------------------------------------------------------------------------------- 4 | 5 | pub struct Block { 6 | timestamp: i64, 7 | pre_block_hash: String, 8 | hash: String, 9 | transactions: Vec, 10 | nonce: i64, 11 | height: usize, 12 | } 13 | 14 | pub struct Transaction { 15 | id: Vec, 16 | vin: Vec, 17 | vout: Vec, 18 | } 19 | 20 | pub struct TXInput { 21 | txid: Vec, 22 | vout: usize, 23 | signature: Vec, 24 | pub_key: Vec, 25 | } 26 | 27 | pub struct TXOutput { 28 | value: i32, 29 | pub_key_hash: Vec, 30 | } 31 | 32 | pub struct Blockchain { 33 | tip_hash: Arc>, // hash of last block 34 | db: Db, 35 | } 36 | 37 | pub struct Wallet { 38 | pkcs8: Vec, 39 | public_key: Vec, 40 | } 41 | 42 | pub struct Node { 43 | addr: String, 44 | } 45 | pub struct Nodes { 46 | inner: RwLock>, 47 | } 48 | 49 | pub struct Server { 50 | blockchain: Blockchain, 51 | } 52 | 53 | pub struct ProofOfWork { 54 | block: Block, 55 | target: BigInt, 56 | } 57 | -------------------------------------------------------------------------------- /chapter 4/blockchain.rs: -------------------------------------------------------------------------------- 1 | // blockchain.rs 2 | 3 | use crate::transaction::TXOutput; 4 | use crate::{Block, Transaction}; 5 | use data_encoding::HEXLOWER; 6 | use sled::transaction::TransactionResult; 7 | use sled::{Db, Tree}; 8 | use std::collections::HashMap; 9 | use std::env::current_dir; 10 | use std::sync::{Arc, RwLock}; 11 | 12 | const TIP_BLOCK_HASH_KEY: &str = "tip_block_hash"; 13 | const BLOCKS_TREE: &str = "blocks"; 14 | 15 | #[derive(Clone)] 16 | pub struct Blockchain { 17 | tip_hash: Arc>, // hash of last block 18 | db: Db, 19 | } 20 | 21 | impl Blockchain { 22 | 23 | pub fn create_blockchain(genesis_address: &str) -> Blockchain { 24 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 25 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 26 | 27 | let data = blocks_tree.get(TIP_BLOCK_HASH_KEY).unwrap(); 28 | let tip_hash; 29 | if data.is_none() { 30 | let coinbase_tx = Transaction::new_coinbase_tx(genesis_address); 31 | let block = Block::generate_genesis_block(&coinbase_tx); 32 | Self::update_blocks_tree(&blocks_tree, &block); 33 | tip_hash = String::from(block.get_hash()); 34 | } else { 35 | tip_hash = String::from_utf8(data.unwrap().to_vec()).unwrap(); 36 | } 37 | Blockchain { 38 | tip_hash: Arc::new(RwLock::new(tip_hash)), 39 | db, 40 | } 41 | } 42 | 43 | fn update_blocks_tree(blocks_tree: &Tree, block: &Block) { 44 | let block_hash = block.get_hash(); 45 | let _: TransactionResult<(), ()> = blocks_tree.transaction(|tx_db| { 46 | let _ = tx_db.insert(block_hash, block.clone()); 47 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block_hash); 48 | Ok(()) 49 | }); 50 | } 51 | 52 | 53 | pub fn new_blockchain() -> Blockchain { 54 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 55 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 56 | let tip_bytes = blocks_tree 57 | .get(TIP_BLOCK_HASH_KEY) 58 | .unwrap() 59 | .expect("No existing blockchain found. Create one first."); 60 | let tip_hash = String::from_utf8(tip_bytes.to_vec()).unwrap(); 61 | Blockchain { 62 | tip_hash: Arc::new(RwLock::new(tip_hash)), 63 | db, 64 | } 65 | } 66 | 67 | pub fn get_db(&self) -> &Db { 68 | &self.db 69 | } 70 | 71 | pub fn get_tip_hash(&self) -> String { 72 | self.tip_hash.read().unwrap().clone() 73 | } 74 | 75 | pub fn set_tip_hash(&self, new_tip_hash: &str) { 76 | let mut tip_hash = self.tip_hash.write().unwrap(); 77 | *tip_hash = String::from(new_tip_hash) 78 | } 79 | 80 | // let us move the iterator code up for readability of the users ? 81 | // pub fn iterator(&self) -> BlockchainIterator { 82 | // BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 83 | // } 84 | 85 | pub fn mine_block(&self, transactions: &[Transaction]) -> Block { 86 | for trasaction in transactions { 87 | if trasaction.verify(self) == false { 88 | panic!("ERROR: Invalid transaction") 89 | } 90 | } 91 | let best_height = self.get_best_height(); 92 | 93 | let block = Block::new_block(self.get_tip_hash(), transactions, best_height + 1); 94 | let block_hash = block.get_hash(); 95 | 96 | let blocks_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 97 | Self::update_blocks_tree(&blocks_tree, &block); 98 | self.set_tip_hash(block_hash); 99 | block 100 | } 101 | 102 | pub fn iterator(&self) -> BlockchainIterator { 103 | BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 104 | } 105 | 106 | // can we add the BlockchainIterator here so that the readers can follow easily 107 | 108 | 109 | 110 | // ( K -> txid_hex, V -> Vec HashMap> { 112 | let mut utxo: HashMap> = HashMap::new(); 113 | let mut spent_txos: HashMap> = HashMap::new(); 114 | 115 | let mut iterator = self.iterator(); 116 | loop { 117 | let option = iterator.next(); 118 | if option.is_none() { 119 | break; 120 | } 121 | let block = option.unwrap(); 122 | 'outer: for tx in block.get_transactions() { 123 | let txid_hex = HEXLOWER.encode(tx.get_id()); 124 | for (idx, out) in tx.get_vout().iter().enumerate() { 125 | 126 | if let Some(outs) = spent_txos.get(txid_hex.as_str()) { 127 | for spend_out_idx in outs { 128 | if idx.eq(spend_out_idx) { 129 | continue 'outer; 130 | } 131 | } 132 | } 133 | if utxo.contains_key(txid_hex.as_str()) { 134 | utxo.get_mut(txid_hex.as_str()).unwrap().push(out.clone()); 135 | } else { 136 | utxo.insert(txid_hex.clone(), vec![out.clone()]); 137 | } 138 | } 139 | if tx.is_coinbase() { 140 | continue; 141 | } 142 | 143 | for txin in tx.get_vin() { 144 | let txid_hex = HEXLOWER.encode(txin.get_txid()); 145 | if spent_txos.contains_key(txid_hex.as_str()) { 146 | spent_txos 147 | .get_mut(txid_hex.as_str()) 148 | .unwrap() 149 | .push(txin.get_vout()); 150 | } else { 151 | spent_txos.insert(txid_hex, vec![txin.get_vout()]); 152 | } 153 | } 154 | } 155 | } 156 | utxo 157 | } 158 | 159 | 160 | pub fn find_transaction(&self, txid: &[u8]) -> Option { 161 | let mut iterator = self.iterator(); 162 | loop { 163 | let option = iterator.next(); 164 | if option.is_none() { 165 | break; 166 | } 167 | let block = option.unwrap(); 168 | for transaction in block.get_transactions() { 169 | if txid.eq(transaction.get_id()) { 170 | return Some(transaction.clone()); 171 | } 172 | } 173 | } 174 | None 175 | } 176 | 177 | 178 | pub fn add_block(&self, block: &Block) { 179 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 180 | if let Some(_) = block_tree.get(block.get_hash()).unwrap() { 181 | return; 182 | } 183 | let _: TransactionResult<(), ()> = block_tree.transaction(|tx_db| { 184 | let _ = tx_db.insert(block.get_hash(), block.serialize()).unwrap(); 185 | 186 | let tip_block_bytes = tx_db 187 | .get(self.get_tip_hash()) 188 | .unwrap() 189 | .expect("The tip hash is not valid"); 190 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 191 | if block.get_height() > tip_block.get_height() { 192 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block.get_hash()).unwrap(); 193 | self.set_tip_hash(block.get_hash()); 194 | } 195 | Ok(()) 196 | }); 197 | } 198 | 199 | 200 | pub fn get_best_height(&self) -> usize { 201 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 202 | let tip_block_bytes = block_tree 203 | .get(self.get_tip_hash()) 204 | .unwrap() 205 | .expect("The tip hash is valid"); 206 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 207 | tip_block.get_height() 208 | } 209 | 210 | 211 | pub fn get_block(&self, block_hash: &[u8]) -> Option { 212 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 213 | if let Some(block_bytes) = block_tree.get(block_hash).unwrap() { 214 | let block = Block::deserialize(block_bytes.as_ref()); 215 | return Some(block); 216 | } 217 | return None; 218 | } 219 | 220 | 221 | pub fn get_block_hashes(&self) -> Vec> { 222 | let mut iterator = self.iterator(); 223 | let mut blocks = vec![]; 224 | loop { 225 | let option = iterator.next(); 226 | if option.is_none() { 227 | break; 228 | } 229 | let block = option.unwrap(); 230 | blocks.push(block.get_hash_bytes()); 231 | } 232 | return blocks; 233 | } 234 | } 235 | 236 | pub struct BlockchainIterator { 237 | db: Db, 238 | current_hash: String, 239 | } 240 | 241 | impl BlockchainIterator { 242 | fn new(tip_hash: String, db: Db) -> BlockchainIterator { 243 | BlockchainIterator { 244 | current_hash: tip_hash, 245 | db, 246 | } 247 | } 248 | 249 | pub fn next(&mut self) -> Option { 250 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 251 | let data = block_tree.get(self.current_hash.clone()).unwrap(); 252 | if data.is_none() { 253 | return None; 254 | } 255 | let block = Block::deserialize(data.unwrap().to_vec().as_slice()); 256 | self.current_hash = block.get_pre_block_hash().clone(); 257 | return Some(block); 258 | } 259 | } 260 | 261 | 262 | -------------------------------------------------------------------------------- /chapter 4/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 | -------------------------------------------------------------------------------- /chapter 4/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 | pub const CENTERAL_NODE: &str = "127.0.0.1:2001"; 18 | 19 | 20 | pub const TRANSACTION_THRESHOLD: usize = 2; 21 | 22 | 23 | static GLOBAL_NODES: Lazy = Lazy::new(|| { 24 | let nodes = Nodes::new(); 25 | 26 | nodes.add_node(String::from(CENTERAL_NODE)); 27 | return nodes; 28 | }); 29 | 30 | 31 | static GLOBAL_MEMORY_POOL: Lazy = Lazy::new(|| MemoryPool::new()); 32 | 33 | 34 | static GLOBAL_BLOCKS_IN_TRANSIT: Lazy = Lazy::new(|| BlockInTransit::new()); 35 | 36 | 37 | const TCP_WRITE_TIMEOUT: u64 = 1000; 38 | 39 | pub struct Server { 40 | blockchain: Blockchain, 41 | } 42 | 43 | impl Server { 44 | pub fn new(blockchain: Blockchain) -> Server { 45 | Server { blockchain } 46 | } 47 | 48 | pub fn run(&self, addr: &str) { 49 | let listener = TcpListener::bind(addr).unwrap(); 50 | 51 | if addr.eq(CENTERAL_NODE) == false { 52 | let best_height = self.blockchain.get_best_height(); 53 | send_version(CENTERAL_NODE, best_height); 54 | } 55 | for stream in listener.incoming() { 56 | let blockchain = self.blockchain.clone(); 57 | thread::spawn(|| match stream { 58 | Ok(stream) => { 59 | ... 60 | } 61 | Err(e) => { 62 | ... 63 | } 64 | }); 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug, Serialize, Deserialize)] 70 | pub enum OpType { 71 | Tx, 72 | Block, 73 | } 74 | 75 | #[derive(Debug, Serialize, Deserialize)] 76 | pub enum Package { 77 | Block { 78 | addr_from: String, 79 | block: Vec, 80 | }, 81 | GetBlocks { 82 | addr_from: String, 83 | }, 84 | GetData { 85 | addr_from: String, 86 | op_type: OpType, 87 | id: Vec, 88 | }, 89 | Inv { 90 | addr_from: String, 91 | op_type: OpType, 92 | items: Vec>, 93 | }, 94 | Tx { 95 | addr_from: String, 96 | transaction: Vec, 97 | }, 98 | Version { 99 | addr_from: String, 100 | version: usize, 101 | best_height: usize, 102 | }, 103 | } 104 | 105 | fn send_get_data(addr: &str, op_type: OpType, id: &[u8]) { 106 | let socket_addr = addr.parse().unwrap(); 107 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 108 | send_data( 109 | socket_addr, 110 | Package::GetData { 111 | addr_from: node_addr, 112 | op_type, 113 | id: id.to_vec(), 114 | }, 115 | ); 116 | } 117 | 118 | fn send_inv(addr: &str, op_type: OpType, blocks: &[Vec]) { 119 | let socket_addr = addr.parse().unwrap(); 120 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 121 | send_data( 122 | socket_addr, 123 | Package::Inv { 124 | addr_from: node_addr, 125 | op_type, 126 | items: blocks.to_vec(), 127 | }, 128 | ); 129 | } 130 | 131 | fn send_block(addr: &str, block: &Block) { 132 | let socket_addr = addr.parse().unwrap(); 133 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 134 | send_data( 135 | socket_addr, 136 | Package::Block { 137 | addr_from: node_addr, 138 | block: block.serialize(), 139 | }, 140 | ); 141 | } 142 | 143 | pub fn send_tx(addr: &str, tx: &Transaction) { 144 | let socket_addr = addr.parse().unwrap(); 145 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 146 | send_data( 147 | socket_addr, 148 | Package::Tx { 149 | addr_from: node_addr, 150 | transaction: tx.serialize(), 151 | }, 152 | ); 153 | } 154 | 155 | fn send_version(addr: &str, height: usize) { 156 | let socket_addr = addr.parse().unwrap(); 157 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 158 | send_data( 159 | socket_addr, 160 | Package::Version { 161 | addr_from: node_addr, 162 | version: NODE_VERSION, 163 | best_height: height, 164 | }, 165 | ); 166 | } 167 | 168 | fn send_get_blocks(addr: &str) { 169 | let socket_addr = addr.parse().unwrap(); 170 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 171 | send_data( 172 | socket_addr, 173 | Package::GetBlocks { 174 | addr_from: node_addr, 175 | }, 176 | ); 177 | } 178 | 179 | fn serve(blockchain: Blockchain, stream: TcpStream) -> Result<(), Box> { 180 | let peer_addr = stream.peer_addr()?; 181 | let reader = BufReader::new(&stream); 182 | let pkg_reader = Deserializer::from_reader(reader).into_iter::(); 183 | for pkg in pkg_reader { 184 | let pkg = pkg?; 185 | info!("Receive request from {}: {:?}", peer_addr, pkg); 186 | match pkg { 187 | Package::Block { addr_from, block } => { 188 | let block = Block::deserialize(block.as_slice()); 189 | blockchain.add_block(&block); 190 | info!("Added block {}", block.get_hash()); 191 | 192 | if GLOBAL_BLOCKS_IN_TRANSIT.len() > 0 { 193 | 194 | let block_hash = GLOBAL_BLOCKS_IN_TRANSIT.first().unwrap(); 195 | send_get_data(addr_from.as_str(), OpType::Block, &block_hash); 196 | 197 | GLOBAL_BLOCKS_IN_TRANSIT.remove(block_hash.as_slice()); 198 | } else { 199 | 200 | let utxo_set = UTXOSet::new(blockchain.clone()); 201 | utxo_set.reindex(); 202 | } 203 | } 204 | Package::GetBlocks { addr_from } => { 205 | let blocks = blockchain.get_block_hashes(); 206 | send_inv(addr_from.as_str(), OpType::Block, &blocks); 207 | } 208 | Package::GetData { 209 | addr_from, 210 | op_type, 211 | id, 212 | } => match op_type { 213 | OpType::Block => { 214 | if let Some(block) = blockchain.get_block(id.as_slice()) { 215 | send_block(addr_from.as_str(), &block); 216 | } 217 | } 218 | OpType::Tx => { 219 | let txid_hex = HEXLOWER.encode(id.as_slice()); 220 | if let Some(tx) = GLOBAL_MEMORY_POOL.get(txid_hex.as_str()) { 221 | send_tx(addr_from.as_str(), &tx); 222 | } 223 | } 224 | }, 225 | Package::Inv { 226 | addr_from, 227 | op_type, 228 | items, 229 | } => match op_type { 230 | 231 | OpType::Block => { 232 | 233 | GLOBAL_BLOCKS_IN_TRANSIT.add_blocks(items.as_slice()); 234 | 235 | 236 | let block_hash = items.get(0).unwrap(); 237 | send_get_data(addr_from.as_str(), OpType::Block, block_hash); 238 | 239 | GLOBAL_BLOCKS_IN_TRANSIT.remove(block_hash); 240 | } 241 | OpType::Tx => { 242 | let txid = items.get(0).unwrap(); 243 | let txid_hex = HEXLOWER.encode(txid); 244 | 245 | if GLOBAL_MEMORY_POOL.contains(txid_hex.as_str()) == false { 246 | send_get_data(addr_from.as_str(), OpType::Tx, txid); 247 | } 248 | } 249 | }, 250 | Package::Tx { 251 | addr_from, 252 | transaction, 253 | } => { 254 | 255 | let tx = Transaction::deserialize(transaction.as_slice()); 256 | let txid = tx.get_id_bytes(); 257 | GLOBAL_MEMORY_POOL.add(tx); 258 | 259 | let node_addr = GLOBAL_CONFIG.get_node_addr(); 260 | 261 | if node_addr.eq(CENTERAL_NODE) { 262 | let nodes = GLOBAL_NODES.get_nodes(); 263 | for node in &nodes { 264 | if node_addr.eq(node.get_addr().as_str()) { 265 | continue; 266 | } 267 | if addr_from.eq(node.get_addr().as_str()) { 268 | continue; 269 | } 270 | send_inv(node.get_addr().as_str(), OpType::Tx, &vec![txid.clone()]) 271 | } 272 | } 273 | 274 | if GLOBAL_MEMORY_POOL.len() >= TRANSACTION_THRESHOLD && GLOBAL_CONFIG.is_miner() { 275 | 276 | let mining_address = GLOBAL_CONFIG.get_mining_addr().unwrap(); 277 | let coinbase_tx = Transaction::new_coinbase_tx(mining_address.as_str()); 278 | let mut txs = GLOBAL_MEMORY_POOL.get_all(); 279 | txs.push(coinbase_tx); 280 | 281 | 282 | let new_block = blockchain.mine_block(&txs); 283 | let utxo_set = UTXOSet::new(blockchain.clone()); 284 | utxo_set.reindex(); 285 | info!("New block {} is mined!", new_block.get_hash()); 286 | 287 | 288 | for tx in &txs { 289 | let txid_hex = HEXLOWER.encode(tx.get_id()); 290 | GLOBAL_MEMORY_POOL.remove(txid_hex.as_str()); 291 | } 292 | 293 | let nodes = GLOBAL_NODES.get_nodes(); 294 | for node in &nodes { 295 | if node_addr.eq(node.get_addr().as_str()) { 296 | continue; 297 | } 298 | send_inv( 299 | node.get_addr().as_str(), 300 | OpType::Block, 301 | &vec![new_block.get_hash_bytes()], 302 | ); 303 | } 304 | } 305 | } 306 | Package::Version { 307 | addr_from, 308 | version, 309 | best_height, 310 | } => { 311 | info!("version = {}, best_height = {}", version, best_height); 312 | let local_best_height = blockchain.get_best_height(); 313 | if local_best_height < best_height { 314 | send_get_blocks(addr_from.as_str()); 315 | } 316 | if local_best_height > best_height { 317 | send_version(addr_from.as_str(), blockchain.get_best_height()); 318 | } 319 | 320 | if GLOBAL_NODES.node_is_known(peer_addr.to_string().as_str()) == false { 321 | GLOBAL_NODES.add_node(addr_from); 322 | } 323 | } 324 | } 325 | } 326 | let _ = stream.shutdown(Shutdown::Both); 327 | Ok(()) 328 | } 329 | 330 | 331 | fn send_data(addr: SocketAddr, pkg: Package) { 332 | info!("send package: {:?}", &pkg); 333 | let stream = TcpStream::connect(addr); 334 | if stream.is_err() { 335 | error!("The {} is not valid", addr); 336 | 337 | GLOBAL_NODES.evict_node(addr.to_string().as_str()); 338 | return; 339 | } 340 | let mut stream = stream.unwrap(); 341 | let _ = stream.set_write_timeout(Option::from(Duration::from_millis(TCP_WRITE_TIMEOUT))); 342 | let _ = serde_json::to_writer(&stream, &pkg); 343 | let _ = stream.flush(); 344 | } -------------------------------------------------------------------------------- /chapter 5/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 | 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 | -------------------------------------------------------------------------------- /chapter 5/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 | -------------------------------------------------------------------------------- /chapter 5/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 | 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 | 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 | 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 | -------------------------------------------------------------------------------- /chapter 5/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 contains(&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 | } -------------------------------------------------------------------------------- /chapter 5/transactions.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, 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, 90 | vin: Vec, 91 | vout: Vec, 92 | } 93 | 94 | impl Transaction { 95 | 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 | 112 | pub fn new_utxo_transaction( 113 | from: &str, 114 | to: &str, 115 | amount: i32, 116 | utxo_set: &UTXOSet, 117 | ) -> Transaction { 118 | 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 | 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 | 129 | let mut inputs = vec![]; 130 | for (txid_hex, outs) in valid_outputs { 131 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 132 | for out in outs { 133 | let input = TXInput { 134 | txid: txid.clone(), 135 | vout: out, 136 | signature: vec![], 137 | pub_key: wallet.get_public_key().to_vec(), 138 | }; 139 | inputs.push(input); 140 | } 141 | } 142 | 143 | let mut outputs = vec![TXOutput::new(amount, to)]; 144 | 145 | if accumulated > amount { 146 | outputs.push(TXOutput::new(accumulated - amount, from)) // to: 币收入 147 | } 148 | 149 | let mut tx = Transaction { 150 | id: vec![], 151 | vin: inputs, 152 | vout: outputs, 153 | }; 154 | 155 | tx.id = tx.hash(); 156 | 157 | tx.sign(utxo_set.get_blockchain(), wallet.get_pkcs8()); 158 | return tx; 159 | } 160 | 161 | 162 | fn trimmed_copy(&self) -> Transaction { 163 | let mut inputs = vec![]; 164 | let mut outputs = vec![]; 165 | for input in &self.vin { 166 | let txinput = TXInput::new(input.get_txid(), input.get_vout()); 167 | inputs.push(txinput); 168 | } 169 | for output in &self.vout { 170 | outputs.push(output.clone()); 171 | } 172 | Transaction { 173 | id: self.id.clone(), 174 | vin: inputs, 175 | vout: outputs, 176 | } 177 | } 178 | 179 | fn sign(&mut self, blockchain: &Blockchain, pkcs8: &[u8]) { 180 | let mut tx_copy = self.trimmed_copy(); 181 | 182 | for (idx, vin) in self.vin.iter_mut().enumerate() { 183 | 184 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 185 | if prev_tx_option.is_none() { 186 | panic!("ERROR: Previous transaction is not correct") 187 | } 188 | let prev_tx = prev_tx_option.unwrap(); 189 | tx_copy.vin[idx].signature = vec![]; 190 | tx_copy.vin[idx].pub_key = prev_tx.vout[vin.vout].pub_key_hash.clone(); 191 | tx_copy.id = tx_copy.hash(); 192 | tx_copy.vin[idx].pub_key = vec![]; 193 | 194 | 195 | let signature = crate::ecdsa_p256_sha256_sign_digest(pkcs8, tx_copy.get_id()); 196 | vin.signature = signature; 197 | } 198 | } 199 | 200 | pub fn verify(&self, blockchain: &Blockchain) -> bool { 201 | if self.is_coinbase() { 202 | return true; 203 | } 204 | let mut tx_copy = self.trimmed_copy(); 205 | for (idx, vin) in self.vin.iter().enumerate() { 206 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 207 | if prev_tx_option.is_none() { 208 | panic!("ERROR: Previous transaction is not correct") 209 | } 210 | let prev_tx = prev_tx_option.unwrap(); 211 | tx_copy.vin[idx].signature = vec![]; 212 | tx_copy.vin[idx].pub_key = prev_tx.vout[vin.vout].pub_key_hash.clone(); 213 | tx_copy.id = tx_copy.hash(); 214 | tx_copy.vin[idx].pub_key = vec![]; 215 | 216 | 217 | let verify = crate::ecdsa_p256_sha256_sign_verify( 218 | vin.pub_key.as_slice(), 219 | vin.signature.as_slice(), 220 | tx_copy.get_id(), 221 | ); 222 | if !verify { 223 | return false; 224 | } 225 | } 226 | true 227 | } 228 | 229 | 230 | pub fn is_coinbase(&self) -> bool { 231 | return self.vin.len() == 1 && self.vin[0].pub_key.len() == 0; 232 | } 233 | 234 | 235 | fn hash(&mut self) -> Vec { 236 | let tx_copy = Transaction { 237 | id: vec![], 238 | vin: self.vin.clone(), 239 | vout: self.vout.clone(), 240 | }; 241 | crate::sha256_digest(tx_copy.serialize().as_slice()) 242 | } 243 | 244 | pub fn get_id(&self) -> &[u8] { 245 | self.id.as_slice() 246 | } 247 | 248 | pub fn get_id_bytes(&self) -> Vec { 249 | self.id.clone() 250 | } 251 | 252 | pub fn get_vin(&self) -> &[TXInput] { 253 | self.vin.as_slice() 254 | } 255 | 256 | pub fn get_vout(&self) -> &[TXOutput] { 257 | self.vout.as_slice() 258 | } 259 | 260 | pub fn serialize(&self) -> Vec { 261 | bincode::serialize(self).unwrap().to_vec() 262 | } 263 | 264 | pub fn deserialize(bytes: &[u8]) -> Transaction { 265 | bincode::deserialize(bytes).unwrap() 266 | } 267 | } -------------------------------------------------------------------------------- /chapter 5/utilits.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 | 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 | 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 | 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 | 34 | pub fn base58_encode(data: &[u8]) -> String { 35 | bs58::encode(data).into_string() 36 | } 37 | 38 | 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 | 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 | 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 | 66 | -------------------------------------------------------------------------------- /chapter 5/utxoSet.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 | 9 | pub struct UTXOSet { 10 | blockchain: Blockchain, 11 | } 12 | 13 | impl UTXOSet { 14 | 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 | 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 | 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 | pub fn reindex(&self) { 85 | let db = self.blockchain.get_db(); 86 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 87 | let _ = utxo_tree.clear().unwrap(); 88 | 89 | let utxo_map = self.blockchain.find_utxo(); 90 | for (txid_hex, outs) in &utxo_map { 91 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 92 | let value = bincode::serialize(outs).unwrap(); 93 | let _ = utxo_tree.insert(txid.as_slice(), value).unwrap(); 94 | } 95 | } 96 | 97 | 98 | pub fn update(&self, block: &Block) { 99 | let db = self.blockchain.get_db(); 100 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 101 | for tx in block.get_transactions() { 102 | if tx.is_coinbase() == false { 103 | for vin in tx.get_vin() { 104 | let mut updated_outs = vec![]; 105 | let outs_bytes = utxo_tree.get(vin.get_txid()).unwrap().unwrap(); 106 | let outs: Vec = bincode::deserialize(outs_bytes.as_ref()) 107 | .expect("unable to deserialize TXOutput"); 108 | for (idx, out) in outs.iter().enumerate() { 109 | if idx != vin.get_vout() { 110 | updated_outs.push(out.clone()) 111 | } 112 | } 113 | if updated_outs.len() == 0 { 114 | let _ = utxo_tree.remove(vin.get_txid()).unwrap(); 115 | } else { 116 | let outs_bytes = bincode::serialize(&updated_outs) 117 | .expect("unable to serialize TXOutput"); 118 | utxo_tree.insert(vin.get_txid(), outs_bytes).unwrap(); 119 | } 120 | } 121 | } 122 | let mut new_outputs = vec![]; 123 | for out in tx.get_vout() { 124 | new_outputs.push(out.clone()) 125 | } 126 | let outs_bytes = 127 | bincode::serialize(&new_outputs).expect("unable to serialize TXOutput"); 128 | let _ = utxo_tree.insert(tx.get_id(), outs_bytes).unwrap(); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /chapter 5/wallets.rs: -------------------------------------------------------------------------------- 1 | // wallet 2 | use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | const VERSION: u8 = 0x00; 6 | pub const ADDRESS_CHECK_SUM_LEN: usize = 4; 7 | 8 | #[derive(Clone, Serialize, Deserialize)] 9 | pub struct Wallet { 10 | pkcs8: Vec, 11 | public_key: Vec, 12 | } 13 | 14 | impl Wallet { 15 | 16 | pub fn new() -> Wallet { 17 | let pkcs8 = crate::new_key_pair(); 18 | let key_pair = 19 | EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref()).unwrap(); 20 | let public_key = key_pair.public_key().as_ref().to_vec(); 21 | Wallet { pkcs8, public_key } 22 | } 23 | 24 | 25 | pub fn get_address(&self) -> String { 26 | let pub_key_hash = hash_pub_key(self.public_key.as_slice()); 27 | let mut payload: Vec = vec![]; 28 | payload.push(VERSION); 29 | payload.extend(pub_key_hash.as_slice()); 30 | let checksum = checksum(payload.as_slice()); 31 | payload.extend(checksum.as_slice()); 32 | // version + pub_key_hash + checksum 33 | crate::base58_encode(payload.as_slice()) 34 | } 35 | 36 | pub fn get_public_key(&self) -> &[u8] { 37 | self.public_key.as_slice() 38 | } 39 | 40 | pub fn get_pkcs8(&self) -> &[u8] { 41 | self.pkcs8.as_slice() 42 | } 43 | } 44 | 45 | 46 | pub fn hash_pub_key(pub_key: &[u8]) -> Vec { 47 | let pub_key_sha256 = crate::sha256_digest(pub_key); 48 | crate::ripemd160_digest(pub_key_sha256.as_slice()) 49 | } 50 | 51 | 52 | fn checksum(payload: &[u8]) -> Vec { 53 | let first_sha = crate::sha256_digest(payload); 54 | let second_sha = crate::sha256_digest(first_sha.as_slice()); 55 | second_sha[0..ADDRESS_CHECK_SUM_LEN].to_vec() 56 | } 57 | 58 | 59 | pub fn validate_address(address: &str) -> bool { 60 | let payload = crate::base58_decode(address); 61 | let actual_checksum = payload[payload.len() - ADDRESS_CHECK_SUM_LEN..].to_vec(); 62 | let version = payload[0]; 63 | let pub_key_hash = payload[1..payload.len() - ADDRESS_CHECK_SUM_LEN].to_vec(); 64 | 65 | let mut target_vec = vec![]; 66 | target_vec.push(version); 67 | target_vec.extend(pub_key_hash); 68 | let target_checksum = checksum(target_vec.as_slice()); 69 | actual_checksum.eq(target_checksum.as_slice()) 70 | } 71 | 72 | 73 | pub fn convert_address(pub_hash_key: &[u8]) -> String { 74 | let mut payload: Vec = vec![]; 75 | payload.push(VERSION); 76 | payload.extend(pub_hash_key); 77 | let checksum = checksum(payload.as_slice()); 78 | payload.extend(checksum.as_slice()); 79 | crate::base58_encode(payload.as_slice()) 80 | } 81 | 82 | // wallets 83 | 84 | use crate::Wallet; 85 | use std::collections::HashMap; 86 | use std::env::current_dir; 87 | use std::fs::{File, OpenOptions}; 88 | use std::io::{BufWriter, Read, Write}; 89 | 90 | pub const WALLET_FILE: &str = "wallet.dat"; 91 | 92 | pub struct Wallets { 93 | wallets: HashMap, 94 | } 95 | 96 | impl Wallets { 97 | pub fn new() -> Wallets { 98 | let mut wallets = Wallets { 99 | wallets: HashMap::new(), 100 | }; 101 | wallets.load_from_file(); 102 | return wallets; 103 | } 104 | 105 | 106 | pub fn create_wallet(&mut self) -> String { 107 | let wallet = Wallet::new(); 108 | let address = wallet.get_address(); 109 | self.wallets.insert(address.clone(), wallet); 110 | self.save_to_file(); 111 | return address; 112 | } 113 | 114 | pub fn get_addresses(&self) -> Vec { 115 | let mut addresses = vec![]; 116 | for (address, _) in &self.wallets { 117 | addresses.push(address.clone()) 118 | } 119 | return addresses; 120 | } 121 | 122 | 123 | pub fn get_wallet(&self, address: &str) -> Option<&Wallet> { 124 | if let Some(wallet) = self.wallets.get(address) { 125 | return Some(wallet); 126 | } 127 | None 128 | } 129 | 130 | 131 | pub fn load_from_file(&mut self) { 132 | let path = current_dir().unwrap().join(WALLET_FILE); 133 | if !path.exists() { 134 | return; 135 | } 136 | let mut file = File::open(path).unwrap(); 137 | let metadata = file.metadata().expect("unable to read metadata"); 138 | let mut buf = vec![0; metadata.len() as usize]; 139 | let _ = file.read(&mut buf).expect("buffer overflow"); 140 | let wallets = bincode::deserialize(&buf[..]).expect("unable to deserialize file data"); 141 | self.wallets = wallets; 142 | } 143 | 144 | 145 | fn save_to_file(&self) { 146 | let path = current_dir().unwrap().join(WALLET_FILE); 147 | let file = OpenOptions::new() 148 | .create(true) 149 | .write(true) 150 | .open(&path) 151 | .expect("unable to open wallet.dat"); 152 | let mut writer = BufWriter::new(file); 153 | let wallets_bytes = bincode::serialize(&self.wallets).expect("unable to serialize wallets"); 154 | writer.write(wallets_bytes.as_slice()).unwrap(); 155 | let _ = writer.flush(); 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /chapter 6/A basic NFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity 0.8.10; 4 | 5 | import "solmate/tokens/ERC721.sol"; 6 | import "openzeppelin-contracts/contracts/utils/Strings.sol"; 7 | 8 | contract NFT is ERC721 { 9 | uint256 public currentTokenId; 10 | constructor( 11 | string memory _name, 12 | string memory _symbol 13 | ) ERC721(_name, _symbol) {} 14 | function mintTo(address recipient) public payable returns (uint256) { 15 | uint256 newItemId = ++currentTokenId; 16 | _safeMint(recipient, newItemId); 17 | return newItemId; 18 | } 19 | function tokenURI(uint256 id) public view virtual override returns (string memory) { 20 | return Strings.toString(id); 21 | } 22 | } -------------------------------------------------------------------------------- /chapter 6/code.sol: -------------------------------------------------------------------------------- 1 | // basic test: 2 | 3 | pragma solidity 0.8.10; 4 | // The following command imports the Test.sol file: 5 | import “forge-std/Test.sol”; 6 | contract ContractBTest is Test { 7 | uint256 testNumber; 8 | function setUp() public { 9 | testNumber = 42; 10 | } 11 | function test_NumberIs42() public { 12 | assertEq(testNumber, 42); 13 | } 14 | function testFail_Subtract43() public { 15 | testNumber -= 43; 16 | } 17 | } 18 | 19 | // Fork and fuzz testing 20 | 21 | function test_ForkSimulation() public { 22 | // Create a local fork of the Ethereum network 23 | Fork fork = new Fork(); 24 | // Perform test operations on the forked network 25 | // ... 26 | // Assert the expected outcomes 27 | // ... 28 | } 29 | 30 | function test_FuzzingContract() public { 31 | // Define properties and invariants for fuzz testing 32 | Property[] properties = [Property1, Property2, Property3]; 33 | // Run fuzz tests with Echidna 34 | EchidnaTestRunner.run(properties); 35 | } 36 | 37 | //Invariant and differential testing 38 | 39 | function test_TotalSupplyEqualsSumOfBalances() public { 40 | // Deploy and initialize the token contract 41 | MyToken token = new MyToken(); 42 | token.transfer(address(1), 100); 43 | token.transfer(address(2), 200); 44 | // Validate the invariant 45 | assertEq(token.totalSupply(), token.balanceOf(address(1)) + token.balanceOf(address(2))); 46 | } 47 | 48 | function test_DifferentialVoting() public { 49 | // Deploy the old and new versions of the voting contract 50 | VotingContract oldVersion = new OldVotingContract(); 51 | VotingContract newVersion = new NewVotingContract(); 52 | // Execute identical inputs and compare results 53 | oldVersion.vote(1); 54 | newVersion.vote(1); 55 | assertEq(oldVersion.getResult(), newVersion.getResult()); 56 | } 57 | 58 | 59 | // Deployment 60 | 61 | function test_ContractDeployment() public { 62 | // Deploy the contract 63 | MyContract contract = new MyContract(); 64 | // Perform initialization steps 65 | contract.initialize(); 66 | // Assert the contract was deployed successfully 67 | assert(contract.isDeployed()); 68 | } 69 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solmate"] 5 | path = lib/solmate 6 | url = https://github.com/transmissions11/solmate 7 | [submodule "lib/openzeppelin-contracts"] 8 | path = lib/openzeppelin-contracts 9 | url = https://github.com/Openzeppelin/openzeppelin-contracts 10 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/src/Greeter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { ERC20 } from "solmate/tokens/ERC20.sol"; 5 | 6 | /// @title Greeter 7 | contract Greeter { 8 | string public greeting; 9 | address public owner; 10 | 11 | // CUSTOMS 12 | error BadGm(); 13 | event GMEverybodyGM(); 14 | 15 | constructor(string memory newGreeting) { 16 | greeting = newGreeting; 17 | owner = msg.sender; 18 | } 19 | 20 | function gm(string memory myGm) external returns(string memory greet) { 21 | if (keccak256(abi.encodePacked((myGm))) != keccak256(abi.encodePacked((greet = greeting)))) revert BadGm(); 22 | emit GMEverybodyGM(); 23 | } 24 | 25 | function setGreeting(string memory newGreeting) external { 26 | greeting = newGreeting; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/src/MyContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract ContractB { 5 | uint256 public testNumber; 6 | 7 | constructor() { 8 | testNumber = 42; 9 | } 10 | 11 | function subtract(uint256 num) public { 12 | require(testNumber >= num, "Subtraction would underflow"); 13 | testNumber -= num; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/src/MyContractVer2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract ContractBVersion2 { 5 | uint256 public testNumber; 6 | event Subtracted(uint256 value, uint256 result); 7 | 8 | constructor() { 9 | testNumber = 42; 10 | } 11 | 12 | function subtract(uint256 num) public { 13 | require(testNumber >= num, "Subtraction would underflow"); 14 | testNumber -= num; 15 | 16 | emit Subtracted(num, testNumber); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/src/NFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "solmate/tokens/ERC721.sol"; 5 | import "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | contract NFT is ERC721 { 8 | using Strings for uint256; 9 | 10 | uint256 public currentTokenId; 11 | string public baseURI; 12 | 13 | constructor( 14 | string memory _name, 15 | string memory _symbol, 16 | string memory _baseURI 17 | ) ERC721(_name, _symbol) { 18 | baseURI = _baseURI; 19 | } 20 | 21 | function mintTo(address recipient) public payable returns (uint256) { 22 | uint256 newItemId = ++currentTokenId; 23 | _safeMint(recipient, newItemId); 24 | return newItemId; 25 | } 26 | 27 | function tokenURI(uint256 id) public view virtual override returns (string memory) { 28 | require(ownerOf(id) != address(0), "ERC721Metadata: URI query for nonexistent token"); 29 | return string(abi.encodePacked(baseURI, id.toString())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/test/Deployment.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/MyContract.sol"; 6 | 7 | contract ContractBTest is Test { 8 | ContractB contractB; 9 | 10 | function setUp() public { 11 | contractB = new ContractB(); 12 | } 13 | 14 | function test_ContractBDeployment() public { 15 | // Deploy the contract inside the test function 16 | ContractB localContractB = new ContractB(); 17 | 18 | // Assert the contract was deployed successfully by checking an initial state 19 | // For example, if ContractB's constructor sets testNumber to 42, we can check that 20 | uint256 expectedInitialValue = 42; 21 | assertEq(localContractB.testNumber(), expectedInitialValue, "ContractB should initialize with the correct value."); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/test/ForkAndFuzz.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/MyContract.sol"; 6 | 7 | contract ContractBForkAndFuzzTest is Test { 8 | ContractB contractB; 9 | 10 | function setUp() public { 11 | contractB = new ContractB(); 12 | } 13 | 14 | // Fuzz test for the subtract function 15 | function testFuzz_Subtract(uint256 num) public { 16 | uint256 initialNumber = contractB.testNumber(); 17 | if(num > initialNumber) { 18 | assertTrue(true); // Pass if subtraction would underflow, avoiding the actual call 19 | } else { 20 | contractB.subtract(num); 21 | uint256 newNumber = contractB.testNumber(); 22 | assertEq(initialNumber - num, newNumber); 23 | } 24 | } 25 | 26 | // Simulated fork test 27 | function testFork_Subtract() public { 28 | // In a local fork simulation, we just redeploy the contract 29 | // This is not an actual fork test in the traditional sense but demonstrates how it would work 30 | contractB = new ContractB(); 31 | uint256 initialNumber = contractB.testNumber(); 32 | uint256 subtractValue = 10; // Example subtract value 33 | 34 | if(initialNumber >= subtractValue) { 35 | contractB.subtract(subtractValue); 36 | uint256 newNumber = contractB.testNumber(); 37 | assertEq(initialNumber - subtractValue, newNumber); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/test/Greeter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {Greeter} from "src/Greeter.sol"; 7 | 8 | contract GreeterTest is Test { 9 | using stdStorage for StdStorage; 10 | 11 | Greeter greeter; 12 | 13 | event GMEverybodyGM(); 14 | 15 | function setUp() external { 16 | greeter = new Greeter("gm"); 17 | } 18 | 19 | // VM Cheatcodes can be found in ./lib/forge-std/src/Vm.sol 20 | // Or at https://github.com/foundry-rs/forge-std 21 | function testSetGm() external { 22 | // slither-disable-next-line reentrancy-events,reentrancy-benign 23 | greeter.setGreeting("gm gm"); 24 | 25 | // Expect the GMEverybodyGM event to be fired 26 | vm.expectEmit(true, true, true, true); 27 | emit GMEverybodyGM(); 28 | // slither-disable-next-line unused-return 29 | greeter.gm("gm gm"); 30 | 31 | // Expect the gm() call to revert 32 | vm.expectRevert(abi.encodeWithSignature("BadGm()")); 33 | // slither-disable-next-line unused-return 34 | greeter.gm("gm"); 35 | 36 | // We can read slots directly 37 | uint256 slot = stdstore.target(address(greeter)).sig(greeter.owner.selector).find(); 38 | assertEq(slot, 1); 39 | bytes32 owner = vm.load(address(greeter), bytes32(slot)); 40 | assertEq(address(this), address(uint160(uint256(owner)))); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/test/InvariantAndDifferential.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/MyContract.sol"; 6 | import "../src/MyContractVer2.sol"; 7 | 8 | 9 | contract ContractBCombinedTest is Test { 10 | ContractB contractB; 11 | ContractBVersion2 contractBVersion2; 12 | 13 | function setUp() public { 14 | contractB = new ContractB(); 15 | contractBVersion2 = new ContractBVersion2(); 16 | } 17 | 18 | // Invariant test to ensure testNumber never becomes negative for both versions 19 | function invariant_testNumberNonNegative() public { 20 | assertTrue(contractB.testNumber() >= 0, "Invariant failed: testNumber is negative in ContractB"); 21 | assertTrue(contractBVersion2.testNumber() >= 0, "Invariant failed: testNumber is negative in ContractBVersion2"); 22 | } 23 | 24 | // Differential test to compare behaviors of two contract versions with fuzzing 25 | function testDifferential_Subtract(uint256 num) public { 26 | uint256 initialNumberB = contractB.testNumber(); 27 | uint256 initialNumberB2 = contractBVersion2.testNumber(); 28 | 29 | if(initialNumberB >= num) { 30 | contractB.subtract(num); 31 | } 32 | if(initialNumberB2 >= num) { 33 | contractBVersion2.subtract(num); 34 | } 35 | 36 | uint256 newNumberB = contractB.testNumber(); 37 | uint256 newNumberB2 = contractBVersion2.testNumber(); 38 | 39 | // Invariant checks after operations 40 | invariant_testNumberNonNegative(); 41 | 42 | // Differential check to ensure both contracts have processed the subtraction similarly 43 | if(initialNumberB >= num && initialNumberB2 >= num) { 44 | assertEq(newNumberB, newNumberB2, "Differential failed: Contract states differ after subtraction"); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/test/MyContract.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/MyContract.sol"; 6 | 7 | contract ContractBTest is Test { 8 | ContractB contractB; 9 | 10 | function setUp() public { 11 | contractB = new ContractB(); 12 | } 13 | 14 | function test_NumberIs42() public { 15 | assertEq(contractB.testNumber(), 42); 16 | } 17 | 18 | function testFail_Subtract43() public { 19 | contractB.subtract(43); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter 6/foundry_complete_app/test/NFTTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/NFT.sol"; // Update the path to your NFT contract 6 | import "@openzeppelin/contracts/utils/Strings.sol"; 7 | 8 | contract NFTTest is Test { 9 | using Strings for uint256; 10 | 11 | NFT nft; 12 | address recipient = address(0x1); 13 | 14 | function setUp() public { 15 | nft = new NFT("TestNFT", "TNFT", "https://example.com/"); 16 | } 17 | 18 | function testMintTo() public { 19 | uint256 tokenId = nft.mintTo(recipient); 20 | assertEq(nft.ownerOf(tokenId), recipient, "Owner should be the recipient after minting"); 21 | } 22 | 23 | function testTokenURI() public { 24 | uint256 tokenId = nft.mintTo(recipient); 25 | string memory expectedURI = string(abi.encodePacked("https://example.com/", tokenId.toString())); 26 | assertEq(nft.tokenURI(tokenId), expectedURI, "Token URI should match the expected value"); 27 | } 28 | 29 | function testFailMintToNonExistentToken() public view { 30 | nft.tokenURI(1); // Should fail if token ID 1 does not exist 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .yarn 9 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | seeds = false 5 | skip-lint = false 6 | 7 | [programs.localnet] 8 | solana_custom = "2cazkdWKXoKgUUcPygLZ9Ty1iwL2qCdGXY9m7mZV1iDp" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "Localnet" 15 | wallet = 'C:\Users\dhana\.config/solana/id.json' 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | 6 | [profile.release] 7 | overflow-checks = true 8 | lto = "fat" 9 | codegen-units = 1 10 | [profile.release.build-override] 11 | opt-level = 3 12 | incremental = false 13 | codegen-units = 1 14 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "^0.29.0" 8 | }, 9 | "devDependencies": { 10 | "chai": "^4.3.4", 11 | "mocha": "^9.0.3", 12 | "ts-mocha": "^10.0.0", 13 | "@types/bn.js": "^5.1.0", 14 | "@types/chai": "^4.3.0", 15 | "@types/mocha": "^9.0.0", 16 | "typescript": "^4.3.5", 17 | "prettier": "^2.6.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/programs/solana-custom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana-custom" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "solana_custom" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | 18 | [dependencies] 19 | anchor-lang = "0.29.0" 20 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/programs/solana-custom/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/programs/solana-custom/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("2cazkdWKXoKgUUcPygLZ9Ty1iwL2qCdGXY9m7mZV1iDp"); 4 | 5 | #[program] 6 | pub mod solana_custom { 7 | use super::*; 8 | 9 | pub fn initialize(ctx: Context) -> Result<()> { 10 | Ok(()) 11 | } 12 | } 13 | 14 | #[derive(Accounts)] 15 | pub struct Initialize {} 16 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/tests/solana-custom.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program } from "@coral-xyz/anchor"; 3 | import { SolanaCustom } from "../target/types/solana_custom"; 4 | 5 | describe("solana-custom", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.SolanaCustom as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /chapter 7/complete_code_chapter_7/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter 7/message accounts lib.rs: -------------------------------------------------------------------------------- 1 | #[account] 2 | pub struct Message { 3 | pub author: Pubkey, 4 | pub timestamp: i64, 5 | pub topic: String, 6 | pub content: String, 7 | } 8 | 9 | // Constants defining sizes of account properties 10 | 11 | const DISCRIMINATOR_LENGTH: usize = 8; 12 | const PUBLIC_KEY_LENGTH: usize = 32; 13 | const TIMESTAMP_LENGTH: usize = 8; 14 | const STRING_LENGTH_PREFIX: usize = 4; // Stores the size of the string. 15 | const MAX_TOPIC_LENGTH: usize = 50 * 4; // 50 chars max. 16 | const MAX_CONTENT_LENGTH: usize = 280 * 4; // 280 chars max. 17 | 18 | impl Message { 19 | // Constant representing the total size of the Message account 20 | const LEN: usize = DISCRIMINATOR_LENGTH 21 | + PUBLIC_KEY_LENGTH // Author. 22 | + TIMESTAMP_LENGTH // Timestamp. 23 | + STRING_LENGTH_PREFIX + MAX_TOPIC_LENGTH // Topic. 24 | + STRING_LENGTH_PREFIX + MAX_CONTENT_LENGTH; // Content. 25 | } 26 | 27 | 28 | #[derive(Accounts)] 29 | pub struct SendMessage<'info> { 30 | #[account(init, payer = author, space = Message::LEN)] 31 | pub message: Account<'info, Message>, 32 | #[account(mut)] 33 | pub author: Signer<'info>, 34 | #[account(address = system_program::ID)] 35 | pub system_program: AccountInfo<'info>, 36 | pub system_program: Program<'info, System>, 37 | } 38 | 39 | 40 | #[program] 41 | pub mod solana_custom { 42 | use super::*; 43 | pub fn send_message(ctx: Context, topic: String, content: String) -> ProgramResult { 44 | let message: &mut Account = &mut ctx.accounts.message; 45 | let sender: &Signer = &ctx.accounts.sender; 46 | let clock: Clock = Clock::get().unwrap(); 47 | if topic.chars().count() > 50 { 48 | return Err(ErrorCode::TopicTooLong.into()); 49 | } 50 | if content.chars().count() > 280 { 51 | return Err(ErrorCode::ContentTooLong.into()); 52 | } 53 | message.sender = *sender.key; 54 | message.timestamp = clock.unix_timestamp; 55 | message.topic = topic; 56 | message.content = content; 57 | Ok(()); 58 | } 59 | } -------------------------------------------------------------------------------- /chapter 7/solana test file.sol: -------------------------------------------------------------------------------- 1 | import * as anchor from '@project-serum/anchor'; 2 | import { Program } from '@project-serum/anchor'; 3 | import { SolanaCustom } from '../target/types/solana_custom'; 4 | import * as assert from "assert"; 5 | import * as bs58 from "bs58"; 6 | 7 | describe('solana-custom', () => { 8 | // Configure the client to use the local cluster. 9 | anchor.setProvider(anchor.Provider.env()); 10 | const program = anchor.workspace.SolanaCustom as Program; 11 | 12 | it('can send a new message', async () => { 13 | // Execute the "SendMessage" instruction. 14 | const message = anchor.web3.Keypair.generate(); 15 | await program.rpc.sendMessage('space exploration', 'Discovering new worlds!', { 16 | accounts: { 17 | message: message.publicKey, 18 | author: program.provider.wallet.publicKey, 19 | systemProgram: anchor.web3.SystemProgram.programId, 20 | }, 21 | signers: [message], 22 | }); 23 | 24 | // Fetch the account details of the created message. 25 | const messageAccount = await program.account.message.fetch(message.publicKey); 26 | 27 | // Ensure it has the right data. 28 | assert.equal(messageAccount.author.toBase58(), program.provider.wallet.publicKey.toBase58()); 29 | assert.equal(messageAccount.topic, 'space exploration'); 30 | assert.equal(messageAccount.content, 'Discovering new worlds!'); 31 | assert.ok(messageAccount.timestamp); 32 | }); 33 | 34 | 35 | it('can fetch all messages', async () => { 36 | const messageAccounts = await program.account.message.all(); 37 | assert.equal(messageAccounts.length, 3); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /chapter 8/Cross contract calls.rs: -------------------------------------------------------------------------------- 1 | use near_sdk::{env, near_bindgen, Promise}; 2 | #[near_bindgen] 3 | pub struct ContractA {} 4 | 5 | #[near_bindgen] 6 | impl ContractA { 7 | pub fn call_contract_b(&self, account_id: String, amount: u128) { 8 | let promise = Promise::new(account_id) 9 | .function_call(b"do_something".to_vec(), vec![], amount, env::prepaid_gas() - 10); 10 | promise.then(env::promise_result); 11 | } 12 | } 13 | 14 | use near_sdk::{env, near_bindgen}; 15 | #[near_bindgen] 16 | pub struct ContractB {} 17 | #[near_bindgen] 18 | impl ContractB { 19 | pub fn do_something(&self) { 20 | // Perform some action 21 | env::log(b"Doing something..."); 22 | } 23 | } -------------------------------------------------------------------------------- /chapter 8/NEAR SDK.rs: -------------------------------------------------------------------------------- 1 | // Account creation and contract deployment 2 | 3 | const NEAR_RPC_URL: &str = "https://rpc.mainnet.near.org"; 4 | // Connect to the NEAR network 5 | let near = near_sdk::connect::connect(near_sdk::Config { 6 | network_id: "mainnet".to_string(), 7 | node_url: NEAR_RPC_URL.to_string(), 8 | }); 9 | // Create a new account 10 | let new_account = near.create_account("new_account"); 11 | // Deploy a contract to the new account 12 | let contract_code = include_bytes!("path/to/contract.wasm"); 13 | new_account.deploy_contract(contract_code); 14 | 15 | // Interacting with smart contracts: 16 | // Instantiate a contract object 17 | let contract = Contract::new(account_id, contract_id, signer); 18 | // Call a method on the contract 19 | contract.call_method("method_name", json!({ "param": "value" })); 20 | // Get contract state 21 | let state: ContractState = contract.view_method("get_state", json!({})); 22 | 23 | // Handling tokens 24 | // Transfer tokens from one account to another 25 | let sender = near.get_account("sender_account"); 26 | let recipient = near.get_account("recipient_account"); 27 | sender.transfer(&recipient, 100); 28 | 29 | // Check token balance 30 | let balance = recipient.get_balance(); -------------------------------------------------------------------------------- /chapter 8/NEAR blockchain.rs: -------------------------------------------------------------------------------- 1 | // Contract class in Rust. 2 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; 3 | use near_sdk::collections::Vector; 4 | use near_sdk::{env, near_bindgen, AccountId, Balance, PanicOnDefault, Promise, StorageUsage}; 5 | 6 | // We start by importing necessary dependencies from the NEAR SDK. 7 | #[near_bindgen] 8 | #[derive(Default, BorshDeserialize, BorshSerialize, PanicOnDefault)] 9 | pub struct MyContract { 10 | pub items: Vector, 11 | } 12 | // We define the main contract struct MyContract with its associated methods. 13 | impl MyContract { 14 | pub fn new() -> Self { 15 | Self { 16 | items: Vector::new(b"i".to_vec()), 17 | } 18 | } 19 | 20 | // The constructor function new() initializes the contract, including the vector of items. 21 | pub fn add_item(&mut self, item: String) { 22 | self.items.push(&item); 23 | } 24 | pub fn get_items(&self) -> Vec { 25 | self.items.to_vec() 26 | } 27 | } 28 | #[near_bindgen] 29 | impl MyContract { 30 | pub fn contract_metadata(&self) -> ContractMetadata { 31 | ContractMetadata { 32 | name: "MyContract".to_string(), 33 | version: "1.0.0".to_string(), 34 | // Additional metadata fields... 35 | } 36 | } 37 | } 38 | 39 | // We define additional contract functions, including contract_metadata(), to provide metadata. 40 | 41 | #[derive(Default, BorshDeserialize, BorshSerialize)] 42 | pub struct ContractMetadata { 43 | pub name: String, 44 | pub version: String, 45 | // Additional metadata fields... 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /chapter 8/NEAR project Crossword.rs: -------------------------------------------------------------------------------- 1 | use near_sdk::{env, near_bindgen, BorshDeserialize, BorshSerialize, PanicOnDefault}; 2 | use near_sdk::collections::UnorderedMap; 3 | use near_sdk::serde::{Deserialize, Serialize}; 4 | 5 | #[near_bindgen] 6 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] 7 | pub struct CrosswordGameContract { 8 | games: UnorderedMap, 9 | next_game_id: u64, 10 | } 11 | 12 | #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] 13 | #[serde(crate = "near_sdk::serde")] 14 | pub struct CrosswordGame { 15 | pub title: String, 16 | pub grid: Vec>>, 17 | } 18 | 19 | #[near_bindgen] 20 | impl CrosswordGameContract { 21 | #[init] 22 | pub fn new() -> Self { 23 | Self { 24 | games: UnorderedMap::new(b"g"), 25 | next_game_id: 0, 26 | } 27 | } 28 | 29 | pub fn create_game(&mut self, title: String, grid: Vec>>) -> u64 { 30 | let game_id = self.next_game_id; 31 | let game = CrosswordGame { title, grid }; 32 | self.games.insert(&game_id, &game); 33 | self.next_game_id += 1; 34 | game_id 35 | } 36 | 37 | pub fn get_game(&self, game_id: u64) -> Option { 38 | self.games.get(&game_id) 39 | } 40 | 41 | pub fn submit_word(&mut self, game_id: u64, word: String, row: usize, column: usize, direction: String) -> bool { 42 | if let Some(mut game) = self.games.get(&game_id) { 43 | let chars: Vec = word.chars().collect(); 44 | let mut current_row = row; 45 | let mut current_column = column; 46 | 47 | // Check if the word fits 48 | for &c in &chars { 49 | if direction == "horizontal" { 50 | if current_column >= game.grid[current_row].len() || 51 | (game.grid[current_row][current_column].is_some() && 52 | game.grid[current_row][current_column] != Some(c)) { 53 | env::log("Word does not fit in the grid or conflicts with existing letters.".as_bytes()); 54 | return false; 55 | } 56 | current_column += 1; 57 | } else if direction == "vertical" { 58 | if current_row >= game.grid.len() || 59 | (game.grid[current_row][current_column].is_some() && 60 | game.grid[current_row][current_column] != Some(c)) { 61 | env::log("Word does not fit in the grid or conflicts with existing letters.".as_bytes()); 62 | return false; 63 | } 64 | current_row += 1; 65 | } else { 66 | env::log("Invalid direction.".as_bytes()); 67 | return false; 68 | } 69 | } 70 | 71 | // Reset to start position for actual update 72 | current_row = row; 73 | current_column = column; 74 | 75 | // Update the grid with the word 76 | for &c in &chars { 77 | if direction == "horizontal" { 78 | game.grid[current_row][current_column] = Some(c); 79 | current_column += 1; 80 | } else { // direction == "vertical" 81 | game.grid[current_row][current_column] = Some(c); 82 | current_row += 1; 83 | } 84 | } 85 | 86 | // Save the updated game back to storage 87 | self.games.insert(&game_id, &game); 88 | 89 | env::log(format!("Word '{}' successfully submitted and grid updated for game {}", word, game_id).as_bytes()); 90 | true 91 | } else { 92 | env::log(format!("Game {} not found", game_id).as_bytes()); 93 | false 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | /target/ 3 | **/*.rs.bk 4 | 5 | # Generated files 6 | /neardev/ 7 | /out/ 8 | 9 | # Dependency directories 10 | **/node_modules/ 11 | 12 | # Environment variables 13 | .env 14 | .env.local 15 | .env.*.local 16 | 17 | # IDEs and editors 18 | /.idea/ 19 | .vscode/ 20 | *.swp 21 | *.swo 22 | *.sln 23 | *.suo 24 | *.cache 25 | *.csproj 26 | *.user 27 | *.userprefs 28 | *.unityproj 29 | *.pidb 30 | *.booproj 31 | *.svd 32 | *.pdb 33 | *.opendb 34 | *.VC.db 35 | 36 | # OS generated files 37 | .DS_Store 38 | .DS_Store? 39 | ._* 40 | .Spotlight-V100 41 | .Trashes 42 | ehthumbs.db 43 | Thumbs.db 44 | 45 | # Near CLI history 46 | .near_history 47 | -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_near" 3 | version = "1.0.0" 4 | authors = ["Near Inc "] 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [dependencies] 11 | near-sdk = "4.1.1" 12 | borsh = "0.9" 13 | uint = { version = "0.9.3", default-features = false } 14 | 15 | [patch.crates-io] 16 | parity-secp256k1 = { git = 'https://github.com/paritytech/rust-secp256k1.git' } 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | opt-level = "z" 21 | lto = true 22 | debug = false 23 | panic = "abort" 24 | overflow-checks = true 25 | 26 | [workspace] 27 | members = ["sandbox-rs"] -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/README.md: -------------------------------------------------------------------------------- 1 | # Hello NEAR Contract 2 | 3 | The smart contract exposes two methods to enable storing and retrieving a greeting in the NEAR network. 4 | 5 | ```rust 6 | const DEFAULT_GREETING: &str = "Hello"; 7 | 8 | #[near_bindgen] 9 | #[derive(BorshDeserialize, BorshSerialize)] 10 | pub struct Contract { 11 | greeting: String, 12 | } 13 | 14 | impl Default for Contract { 15 | fn default() -> Self { 16 | Self { greeting: DEFAULT_GREETING.to_string() } 17 | } 18 | } 19 | 20 | #[near_bindgen] 21 | impl Contract { 22 | // Public: Returns the stored greeting, defaulting to 'Hello' 23 | pub fn get_greeting(&self) -> String { 24 | return self.greeting.clone(); 25 | } 26 | 27 | // Public: Takes a greeting, such as 'howdy', and records it 28 | pub fn set_greeting(&mut self, greeting: String) { 29 | // Record a log permanently to the blockchain! 30 | log!("Saving greeting {}", greeting); 31 | self.greeting = greeting; 32 | } 33 | } 34 | ``` 35 | 36 |
37 | 38 | # Quickstart 39 | 40 | 1. Make sure you have installed [rust](https://rust.org/). 41 | 2. Install the [`NEAR CLI`](https://github.com/near/near-cli#setup) 42 | 43 |
44 | 45 | ## 1. Build, Test and Deploy 46 | To build the contract you can execute the `./build.sh` script, which will in turn run: 47 | 48 | ```bash 49 | rustup target add wasm32-unknown-unknown 50 | cargo build --target wasm32-unknown-unknown --release 51 | ``` 52 | 53 | Then, run the `./deploy.sh` script, which will in turn run: 54 | 55 | ```bash 56 | near dev-deploy --wasmFile ./target/wasm32-unknown-unknown/release/hello_near.wasm 57 | ``` 58 | 59 | the command [`near dev-deploy`](https://docs.near.org/tools/near-cli#near-dev-deploy) automatically creates an account in the NEAR testnet, and deploys the compiled contract on it. 60 | 61 | Once finished, check the `./neardev/dev-account` file to find the address in which the contract was deployed: 62 | 63 | ```bash 64 | cat ./neardev/dev-account 65 | # e.g. dev-1659899566943-21539992274727 66 | ``` 67 | 68 |
69 | 70 | ## 2. Retrieve the Greeting 71 | 72 | `get_greeting` is a read-only method (aka `view` method). 73 | 74 | `View` methods can be called for **free** by anyone, even people **without a NEAR account**! 75 | 76 | ```bash 77 | # Use near-cli to get the greeting 78 | near view get_greeting 79 | ``` 80 | 81 |
82 | 83 | ## 3. Store a New Greeting 84 | `set_greeting` changes the contract's state, for which it is a `change` method. 85 | 86 | `Change` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. In this case, we are asking the account we created in step 1 to sign the transaction. 87 | 88 | ```bash 89 | # Use near-cli to set a new greeting 90 | near call set_greeting '{"greeting":"howdy"}' --accountId 91 | ``` 92 | 93 | **Tip:** If you would like to call `set_greeting` using your own account, first login into NEAR using: 94 | 95 | ```bash 96 | # Use near-cli to login your NEAR account 97 | near login 98 | ``` 99 | 100 | and then use the logged account to sign the transaction: `--accountId `. -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rustup target add wasm32-unknown-unknown 3 | cargo build --target wasm32-unknown-unknown --release -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | near dev-deploy --wasmFile ./target/wasm32-unknown-unknown/release/hello_near.wasm -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.74" 3 | components = ["rustfmt"] 4 | targets = ["wasm32-unknown-unknown"] -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/sandbox-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sandbox" 3 | version = "1.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [dev-dependencies] 8 | tokio = { version = "1.18.1", features = ["full"] } 9 | near-workspaces = "0.9.0" 10 | serde_json = { version = "1.0", features = ["arbitrary_precision"] } 11 | 12 | [[example]] 13 | name = "sandbox" 14 | path = "src/tests.rs" -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/sandbox-rs/src/tests.rs: -------------------------------------------------------------------------------- 1 | use near_workspaces::{types::NearToken, Account, Contract}; 2 | use serde_json::json; 3 | use std::{env, fs}; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Box> { 7 | let wasm_arg: &str = &(env::args().nth(1).unwrap()); 8 | let wasm_filepath = fs::canonicalize(env::current_dir()?.join(wasm_arg))?; 9 | 10 | let worker = near_workspaces::sandbox().await?; 11 | let wasm = std::fs::read(wasm_filepath)?; 12 | let contract = worker.dev_deploy(&wasm).await?; 13 | 14 | // create accounts 15 | let account = worker.dev_create_account().await?; 16 | let alice = account 17 | .create_subaccount("alice") 18 | .initial_balance(NearToken::from_near(30)) 19 | .transact() 20 | .await? 21 | .into_result()?; 22 | 23 | // begin tests 24 | test_default_message(&alice, &contract).await?; 25 | test_changes_message(&alice, &contract).await?; 26 | Ok(()) 27 | } 28 | 29 | async fn test_default_message( 30 | user: &Account, 31 | contract: &Contract, 32 | ) -> Result<(), Box> { 33 | let greeting: String = user 34 | .call(contract.id(), "get_greeting") 35 | .args_json(json!({})) 36 | .transact() 37 | .await? 38 | .json()?; 39 | 40 | assert_eq!(greeting, "Hello".to_string()); 41 | println!(" Passed ✅ gets default greeting"); 42 | Ok(()) 43 | } 44 | 45 | async fn test_changes_message( 46 | user: &Account, 47 | contract: &Contract, 48 | ) -> Result<(), Box> { 49 | user.call(contract.id(), "set_greeting") 50 | .args_json(json!({"greeting": "Howdy"})) 51 | .transact() 52 | .await? 53 | .into_result()?; 54 | 55 | let greeting: String = user 56 | .call(contract.id(), "get_greeting") 57 | .args_json(json!({})) 58 | .transact() 59 | .await? 60 | .json()?; 61 | 62 | assert_eq!(greeting, "Howdy".to_string()); 63 | println!(" Passed ✅ changes greeting"); 64 | Ok(()) 65 | } -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/src/lib.rs: -------------------------------------------------------------------------------- 1 | use near_sdk::{env, near_bindgen, PanicOnDefault}; 2 | use near_sdk::collections::UnorderedMap; 3 | use near_sdk::serde::{Deserialize, Serialize}; 4 | use borsh::{BorshDeserialize, BorshSerialize}; 5 | 6 | #[near_bindgen] 7 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] 8 | pub struct CrosswordGameContract { 9 | games: UnorderedMap, 10 | next_game_id: u64, 11 | } 12 | 13 | #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] 14 | #[serde(crate = "near_sdk::serde")] 15 | pub struct CrosswordGame { 16 | pub title: String, 17 | pub grid: Vec>>, // Storing each cell as a String 18 | } 19 | 20 | #[near_bindgen] 21 | impl CrosswordGameContract { 22 | #[init] 23 | pub fn new() -> Self { 24 | Self { 25 | games: UnorderedMap::new(b"g"), 26 | next_game_id: 0, 27 | } 28 | } 29 | 30 | pub fn create_game(&mut self, title: String, grid: Vec>>) -> u64 { 31 | let grid_string = grid.into_iter() 32 | .map(|row| row.into_iter() 33 | .map(|opt_char| opt_char.map(|c| c.to_string())) 34 | .collect()) 35 | .collect(); 36 | 37 | let game_id = self.next_game_id; 38 | let game = CrosswordGame { title, grid: grid_string }; 39 | self.games.insert(&game_id, &game); 40 | self.next_game_id += 1; 41 | game_id 42 | } 43 | 44 | pub fn get_game(&self, game_id: u64) -> Option { 45 | self.games.get(&game_id) 46 | } 47 | 48 | pub fn submit_word(&mut self, game_id: u64, word: String, row: usize, column: usize, direction: String) -> bool { 49 | if let Some(mut game) = self.games.get(&game_id) { 50 | let chars: Vec = word.chars().collect(); 51 | let mut current_row = row; 52 | let mut current_column = column; 53 | 54 | for &c in &chars { 55 | let c_string = c.to_string(); 56 | if direction == "horizontal" { 57 | if current_column >= game.grid[current_row].len() || 58 | (game.grid[current_row][current_column].is_some() && 59 | game.grid[current_row][current_column] != Some(c_string.clone())) { 60 | env::log_str("Word does not fit in the grid or conflicts with existing letters."); 61 | return false; 62 | } 63 | current_column += 1; 64 | } else if direction == "vertical" { 65 | if current_row >= game.grid.len() || 66 | (game.grid[current_row][current_column].is_some() && 67 | game.grid[current_row][current_column] != Some(c_string.clone())) { 68 | env::log_str("Word does not fit in the grid or conflicts with existing letters."); 69 | return false; 70 | } 71 | current_row += 1; 72 | } else { 73 | env::log_str("Invalid direction."); 74 | return false; 75 | } 76 | } 77 | 78 | current_row = row; 79 | current_column = column; 80 | 81 | for &c in &chars { 82 | let c_string = c.to_string(); 83 | if direction == "horizontal" { 84 | game.grid[current_row][current_column] = Some(c_string); 85 | current_column += 1; 86 | } else { // direction == "vertical" 87 | game.grid[current_row][current_column] = Some(c_string); 88 | current_row += 1; 89 | } 90 | } 91 | 92 | self.games.insert(&game_id, &game); 93 | 94 | env::log_str(&format!("Word '{}' successfully submitted and grid updated for game {}", word, game_id)); 95 | return true; 96 | } else { 97 | env::log_str(&format!("Game {} not found", game_id)); 98 | return false; 99 | } 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | 107 | #[test] 108 | fn test_create_game() { 109 | let mut contract = CrosswordGameContract::new(); 110 | let game_id = contract.create_game("Test Game".to_string(), vec![vec![Some('A'), None], vec![None, Some('B')]]); 111 | 112 | assert_eq!(game_id, 0); 113 | } 114 | 115 | #[test] 116 | fn test_get_game() { 117 | let mut contract = CrosswordGameContract::new(); 118 | contract.create_game("Test Game".to_string(), vec![vec![Some('A'), None], vec![None, Some('B')]]); 119 | 120 | let game = contract.get_game(0).unwrap(); 121 | assert_eq!(game.title, "Test Game"); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /chapter 8/NEAR_complete_app/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # unit testing 4 | cargo test 5 | 6 | # sandbox testing 7 | ./build.sh 8 | cd sandbox-rs 9 | cargo run --example sandbox "../target/wasm32-unknown-unknown/release/hello_near.wasm" -------------------------------------------------------------------------------- /chapter 8/Near js to interact with crossword.js: -------------------------------------------------------------------------------- 1 | // NEAR JavaScript SDK to interact with the deployed contract. 2 | const near = require("near-api-js"); 3 | async function interactWithCrosswordGameContract() { 4 | const keyStore = new near.keyStores.InMemoryKeyStore(); 5 | const nearConfig = { 6 | keyStore, 7 | nodeUrl: "https://rpc.testnet.near.org", 8 | networkId: "testnet", 9 | contractName: "your_account_id", 10 | }; 11 | const near = await near.connect(nearConfig); 12 | const account = await near.account(nearConfig.contractName); 13 | const crosswordGameContract = new near.Contract( 14 | account, 15 | nearConfig.contractName, 16 | { 17 | viewMethods: ["get_game"], 18 | changeMethods: ["create_game", "submit_word"], 19 | sender: nearConfig.contractName, 20 | } 21 | ); 22 | // Interact with the contract methods 23 | const gameDetails = await crosswordGameContract.get_game({ game_id: 1 }); 24 | console.log("Game Details:", gameDetails); 25 | // Add more contract interactions here 26 | } 27 | interactWithCrosswordGameContract(); 28 | -------------------------------------------------------------------------------- /chapter 8/State and data structures.rs: -------------------------------------------------------------------------------- 1 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; 2 | use near_sdk::collections::Map; 3 | use near_sdk::{env, near_bindgen, AccountId}; 4 | 5 | // Import necessary dependencies and modules from the NEAR SDK. 6 | #[near_bindgen] 7 | #[derive(Default, BorshDeserialize, BorshSerialize)] 8 | pub struct UserRegistry { 9 | users: Map, 10 | } 11 | // Define the UserRegistry smart contract struct, which includes a users field of type Map to store user information. 12 | 13 | #[derive(BorshDeserialize, BorshSerialize)] 14 | pub struct UserInfo { 15 | name: String, 16 | age: u32, 17 | address: String, 18 | } 19 | 20 | // Define the UserInfo struct, which represents the data structure for user information, including name, age, and address. 21 | 22 | impl UserRegistry { 23 | pub fn new_user(&mut self, name: String, age: u32, address: String) { 24 | let caller = env::signer_account_id(); 25 | let user_info = UserInfo { 26 | name, 27 | age, 28 | address, 29 | }; 30 | self.users.insert(&caller, &user_info); 31 | } 32 | pub fn get_user(&self, user_id: AccountId) -> Option { 33 | self.users.get(&user_id) 34 | } 35 | } -------------------------------------------------------------------------------- /chapter 8/Transfers and actions: -------------------------------------------------------------------------------- 1 | use near_sdk::env; 2 | use near_sdk::ext_contract; 3 | use near_sdk::near_bindgen; 4 | 5 | #[near_bindgen] 6 | pub struct MyContract {} 7 | #[ext_contract] 8 | pub trait NEARToken { 9 | fn transfer(&mut self, receiver_id: String, amount: u128); 10 | } 11 | 12 | impl MyContract { 13 | pub fn transfer_tokens(&mut self, receiver_id: String, amount: u128) { 14 | NEARToken::transfer(&mut self, receiver_id, amount); 15 | } 16 | } 17 | 18 | fn main() {} 19 | 20 | // Actions 21 | 22 | use near_sdk::env; 23 | use near_sdk::near_bindgen; 24 | #[near_bindgen] 25 | pub struct MyContract { 26 | counter: u32, 27 | } 28 | 29 | impl MyContract { 30 | pub fn increment_counter(&mut self) { 31 | self.counter += 1; 32 | } 33 | pub fn get_counter(&self) -> u32 { 34 | self.counter 35 | } 36 | } 37 | 38 | fn main() {} -------------------------------------------------------------------------------- /chapter 8/foundational elements of NEAR.rs: -------------------------------------------------------------------------------- 1 | // Create an implicit account in a transaction 2 | 3 | #[near_bindgen] 4 | pub fn create_implicit_account(&mut self, account_id: String) { 5 | let account_id: ValidAccountId = account_id.try_into().unwrap(); 6 | env::log(format!("Creating implicit account: {}", account_id).as_bytes()); 7 | // Perform actions with the implicit account 8 | // ... 9 | } 10 | 11 | // Create a full access key 12 | 13 | #[near_bindgen] 14 | pub fn create_full_access_key(&mut self, public_key: PublicKey) { 15 | self.env().key_create( 16 | public_key, 17 | &access_key::AccessKey { 18 | nonce: 0, 19 | permission: access_key::Permission::FullAccess, 20 | }, 21 | ); 22 | } 23 | 24 | // Create a function call key 25 | #[near_bindgen] 26 | pub fn create_function_call_key(&mut self, public_key: PublicKey) { 27 | self.env().key_create( 28 | public_key, 29 | &access_key::AccessKey { 30 | nonce: 0, 31 | permission: access_key::Permission::FunctionCall { 32 | allowance: access_key::FunctionCallPermission { 33 | allowance: 10.into(), // Maximum number of function call allowances 34 | receiver_id: "receiver_account".to_string(), 35 | method_names: vec!["allowed_method".to_string()], 36 | }, 37 | }, 38 | }, 39 | ); 40 | } 41 | 42 | // simple smart contract written in Rust 43 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; 44 | use near_sdk::collections::Vector; 45 | use near_sdk::{env, near_bindgen}; 46 | 47 | #[near_bindgen] 48 | #[derive(Default, BorshDeserialize, BorshSerialize)] 49 | pub struct MyContract { 50 | items: Vector, 51 | } 52 | #[near_bindgen] 53 | impl MyContract { 54 | pub fn add_item(&mut self, item: String) { 55 | self.items.push(&item); 56 | env::log(format!("Added item: {}", item).as_bytes()); 57 | } 58 | pub fn get_items(&self) -> Vec { 59 | self.items.to_vec() 60 | } 61 | } 62 | 63 | //storing account metadata 64 | // Import necessary libraries 65 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; 66 | use near_sdk::env; 67 | use near_sdk::near_bindgen; 68 | 69 | // Define the contract structure 70 | #[near_bindgen] 71 | #[derive(Default, BorshDeserialize, BorshSerialize)] 72 | pub struct YourContract { 73 | // Declare a field to store account metadata 74 | pub account_metadata: Option, 75 | } 76 | 77 | // Implement methods for storing and retrieving account metadata 78 | #[near_bindgen] 79 | impl YourContract { 80 | // Method to set or update account metadata 81 | pub fn set_account_metadata(&mut self, metadata: String) { 82 | self.account_metadata = Some(metadata); 83 | } 84 | // Method to retrieve account metadata 85 | pub fn get_account_metadata(&self) -> Option { 86 | self.account_metadata.clone() 87 | } 88 | } 89 | 90 | //smart contract in Rust on the NEAR blockchain, showcasing the management of a contract state. 91 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; 92 | use near_sdk::near_bindgen; 93 | #[near_bindgen] 94 | #[derive(Default, BorshDeserialize, BorshSerialize)] 95 | pub struct MyContract { 96 | counter: i32, 97 | } 98 | #[near_bindgen] 99 | impl MyContract { 100 | pub fn new() -> Self { 101 | Self { counter: 0 } 102 | } 103 | pub fn increment(&mut self) { 104 | self.counter += 1; 105 | } 106 | pub fn get_counter(&self) -> i32 { 107 | self.counter 108 | } 109 | } 110 | 111 | // simple transaction using the NEAR SDK 112 | 113 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; 114 | use near_sdk::near_bindgen; 115 | use near_sdk::env; 116 | use near_sdk::serde_json::json; 117 | 118 | #[near_bindgen] 119 | #[derive(Default, BorshDeserialize, BorshSerialize)] 120 | 121 | pub struct MyContract { 122 | // Contract state and functionality 123 | } 124 | 125 | #[near_bindgen] 126 | impl MyContract { 127 | pub fn transfer_tokens(&mut self, receiver_id: String, amount: u64) { 128 | let current_account_id = env::current_account_id(); 129 | let transfer_action = json!({ 130 | "receiver_id": receiver_id, 131 | "amount": amount 132 | }); 133 | let transfer_call = near_sdk::json!({ 134 | "contract": current_account_id, 135 | "method": "transfer", 136 | "args": transfer_action, 137 | "gas": env::prepaid_gas() - 100_000_000, // Subtracting 100 TeraGas for additional actions 138 | "attached_gas": 5_000_000_000, // 5 GigaGas attached to cover the cost 139 | "attached_tokens": amount 140 | }); 141 | 142 | env::promise_create( 143 | env::current_account_id(), 144 | "do_transfer", 145 | transfer_call.to_string().as_bytes(), 146 | &receiver_id.into_bytes(), 147 | 0, 148 | 0 149 | ); 150 | } 151 | } 152 | 153 | 154 | // Implementing token transfer safeguards 155 | pub fn transfer_tokens(recipient: AccountId, amount: Balance) { 156 | assert!(env::is_valid_account_id(&recipient), "Invalid recipient account"); 157 | let sender_balance = self.get_account_balance(env::current_account_id()); 158 | assert!(sender_balance >= amount, "Insufficient balance"); 159 | // Perform the token transfer 160 | token::transfer(&env::current_account_id(), &recipient, amount); 161 | } 162 | 163 | // Implementing token vesting 164 | 165 | pub struct TokenVesting { 166 | beneficiary: AccountId, 167 | start_timestamp: u64, 168 | duration: u64, 169 | total_tokens: Balance, 170 | } 171 | 172 | impl TokenVesting { 173 | pub fn release_tokens(&mut self) { 174 | let current_timestamp = env::block_timestamp(); 175 | let elapsed_time = current_timestamp - self.start_timestamp; 176 | if elapsed_time >= self.duration { 177 | token::transfer(&env::current_account_id(), &self.beneficiary, self.total_tokens); 178 | } else { 179 | let tokens_to_release = (self.total_tokens * elapsed_time) / self.duration; 180 | token::transfer(&env::current_account_id(), &self.beneficiary, tokens_to_release); 181 | } 182 | } 183 | } 184 | 185 | // Storage options 186 | 187 | // Declare a vector of u64 elements 188 | let mut my_vector: Vec = Vec::new(); 189 | // Add elements to the vector 190 | my_vector.push(10); 191 | my_vector.push(20); 192 | my_vector.push(30); 193 | 194 | // Access elements in the vector 195 | let second_element = my_vector[1]; 196 | 197 | // Declare a LookupSet of string elements 198 | let mut my_lookupset: LookupSet = LookupSet::new(); 199 | // Add elements to the LookupSet 200 | my_lookupset.insert("apple".to_string()); 201 | my_lookupset.insert("banana".to_string()); 202 | // Check membership 203 | let contains_apple = my_lookupset.contains("apple".to_string()); 204 | 205 | // Declare an UnorderedSet of u32 elements 206 | let mut my_unorderedset: UnorderedSet = UnorderedSet::new(); 207 | // Add elements to the UnorderedSet 208 | my_unorderedset.insert(1); 209 | my_unorderedset.insert(2); 210 | // Iterate over the elements 211 | for element in my_unorderedset.iter() { 212 | // Process each element 213 | } 214 | 215 | // Declare a LookupMap with string keys and u64 values 216 | 217 | let mut my_lookupmap: LookupMap = LookupMap::new(); 218 | // Add key-value pairs to the LookupMap 219 | my_lookupmap.insert("key1".to_string(), 10); 220 | my_lookupmap.insert("key2".to_string(), 20); 221 | 222 | // Access values based on keys 223 | let value = my_lookupmap.get("key1".to_string()); 224 | 225 | // Declare an UnorderedMap with u32 keys and string values 226 | let mut my_unorderedmap: UnorderedMap = UnorderedMap::new(); 227 | // Add key-value pairs to the UnorderedMap 228 | my_unorderedmap.insert(1, "value1".to_string()); 229 | my_unorderedmap.insert(2, "value2".to_string()); 230 | // Iterate over the key-value pairs 231 | for (key, value) in my_unorderedmap.iter() { 232 | // Process each key-value pair 233 | } 234 | 235 | // Declare a TreeMap with u64 keys and string values 236 | let mut my_treemap: TreeMap = TreeMap::new(); 237 | // Add key-value pairs to the TreeMap 238 | my_treemap.insert(3, "value3".to_string()); 239 | my_treemap.insert(1, "value1".to_string()); 240 | my_treemap.insert(2, "value2".to_string()); 241 | // Iterate over the key-value pairs in sorted order 242 | for (key, value) in my_treemap.iter() { 243 | // Process each key-value pair 244 | } 245 | 246 | -------------------------------------------------------------------------------- /chapter 9/Frame.rs: -------------------------------------------------------------------------------- 1 | pub use pallet_staking; 2 | use frame_system::Module as System; 3 | use pallet_staking::Module as Staking; 4 | 5 | decl_module! { 6 | pub struct Module for enum Call where origin: T::Origin { 7 | fn deposit_event() = default; 8 | // Define custom blockchain logic using FRAME modules. 9 | } 10 | } -------------------------------------------------------------------------------- /chapter 9/core primitives.rs: -------------------------------------------------------------------------------- 1 | // Example to maintain a balance ledger: 2 | decl_storage! { 3 | trait Store for Module as Balances { 4 | Balances: map T::AccountId => Balance; 5 | } 6 | } 7 | 8 | // Example: To define and use a custom event in Substrate: 9 | decl_event! { 10 | pub enum Event where AccountId = ::AccountId { 11 | Transfer(AccountId, AccountId, Balance), 12 | } 13 | } -------------------------------------------------------------------------------- /chapter 9/parachain transaction type.rs: -------------------------------------------------------------------------------- 1 | // Import necessary dependencies from Substrate 2 | use frame_system::Module as System; 3 | use frame_system::RawOrigin; 4 | use frame_system::ensure_signed; 5 | use frame_system::ensure_root; 6 | 7 | // Define your custom transaction module 8 | pub mod my_custom_module { 9 | use super::*; 10 | // Define the custom transaction struct 11 | #[derive(codec::Encode, codec::Decode, Default, Clone, PartialEq)] 12 | pub struct MyCustomTransaction { 13 | // Define transaction fields here 14 | pub sender: AccountId, 15 | pub amount: Balance, 16 | // Add any other fields you need 17 | } 18 | // Implement the dispatchable function for your custom transaction 19 | decl_module! { 20 | pub struct Module for enum Call where origin: T::Origin { 21 | fn my_custom_transaction(origin, transaction: MyCustomTransaction) { 22 | let sender = ensure_signed(origin)?; 23 | // Your custom logic for processing the transaction here 24 | // You can access the fields of transaction, like transaction.sender and transaction.amount 25 | // Emit an event or perform other actions as needed 26 | // Self::deposit_event(RawEvent::CustomTransactionProcessed(sender, transaction.amount)); 27 | } 28 | } 29 | } 30 | } 31 | 32 | // Ensure your custom module is included in the runtime configuration 33 | impl frame_system::Module { 34 | fn dispatch_bypass_filter( 35 | origin: T::Origin, 36 | _call: T::Call, 37 | ) -> dispatch::DispatchResult { 38 | ensure_root(origin)?; 39 | Ok(()) 40 | } 41 | } -------------------------------------------------------------------------------- /complete_blockchain/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" -------------------------------------------------------------------------------- /complete_blockchain/src/block.rs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------- 2 | // Getting started with building the Blockchain ( block.rs / proof of work.rs) 3 | // -------------------------------------------------------------------------------------------------- 4 | 5 | // Block.rs file 6 | use crate::{ProofOfWork, Transaction}; 7 | use serde::{Deserialize, Serialize}; 8 | use sled::IVec; 9 | 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct Block { 12 | timestamp: i64, 13 | pre_block_hash: String, 14 | hash: String, 15 | transactions: Vec, 16 | nonce: i64, 17 | height: usize, 18 | } 19 | 20 | impl Block { 21 | 22 | pub fn new_block(pre_block_hash: String, transactions: &[Transaction], height: usize) -> Block { 23 | let mut block = Block { 24 | timestamp: crate::current_timestamp(), 25 | pre_block_hash, 26 | hash: String::new(), 27 | transactions: transactions.to_vec(), 28 | nonce: 0, 29 | height, 30 | }; 31 | 32 | let pow = ProofOfWork::new_proof_of_work(block.clone()); 33 | let (nonce, hash) = pow.run(); 34 | block.nonce = nonce; 35 | block.hash = hash; 36 | return block; 37 | } 38 | 39 | 40 | pub fn deserialize(bytes: &[u8]) -> Block { 41 | bincode::deserialize(bytes).unwrap() 42 | } 43 | 44 | 45 | pub fn serialize(&self) -> Vec { 46 | bincode::serialize(self).unwrap().to_vec() 47 | } 48 | 49 | 50 | pub fn generate_genesis_block(transaction: &Transaction) -> Block { 51 | let transactions = vec![transaction.clone()]; 52 | return Block::new_block(String::from("None"), &transactions, 0); 53 | } 54 | 55 | pub fn hash_transactions(&self) -> Vec { 56 | let mut txhashs = vec![]; 57 | for transaction in &self.transactions { 58 | txhashs.extend(transaction.get_id()); 59 | } 60 | crate::sha256_digest(txhashs.as_slice()) 61 | } 62 | 63 | pub fn get_transactions(&self) -> &[Transaction] { 64 | self.transactions.as_slice() 65 | } 66 | 67 | pub fn get_pre_block_hash(&self) -> String { 68 | self.pre_block_hash.clone() 69 | } 70 | 71 | pub fn get_hash(&self) -> &str { 72 | self.hash.as_str() 73 | } 74 | 75 | pub fn get_hash_bytes(&self) -> Vec { 76 | self.hash.as_bytes().to_vec() 77 | } 78 | 79 | pub fn get_timestamp(&self) -> i64 { 80 | self.timestamp 81 | } 82 | 83 | pub fn get_height(&self) -> usize { 84 | self.height 85 | } 86 | } 87 | 88 | impl From for IVec { 89 | fn from(b: Block) -> Self { 90 | let bytes = bincode::serialize(&b).unwrap(); 91 | Self::from(bytes) 92 | } 93 | } 94 | 95 | 96 | 97 | // Proof of work.rs 98 | 99 | use crate::Block; 100 | use data_encoding::HEXLOWER; 101 | use num_bigint::{BigInt, Sign}; 102 | use std::borrow::Borrow; 103 | use std::ops::ShlAssign; 104 | 105 | pub struct ProofOfWork { 106 | block: Block, 107 | target: BigInt, 108 | } 109 | 110 | 111 | const TARGET_BITS: i32 = 8; 112 | 113 | const MAX_NONCE: i64 = i64::MAX; 114 | 115 | impl ProofOfWork { 116 | pub fn new_proof_of_work(block: Block) -> ProofOfWork { 117 | let mut target = BigInt::from(1); 118 | 119 | target.shl_assign(256 - TARGET_BITS); 120 | ProofOfWork { block, target } 121 | } 122 | 123 | fn prepare_data(&self, nonce: i64) -> Vec { 124 | let pre_block_hash = self.block.get_pre_block_hash(); 125 | let transactions_hash = self.block.hash_transactions(); 126 | let timestamp = self.block.get_timestamp(); 127 | let mut data_bytes = vec![]; 128 | data_bytes.extend(pre_block_hash.as_bytes()); 129 | data_bytes.extend(transactions_hash); 130 | data_bytes.extend(timestamp.to_be_bytes()); 131 | data_bytes.extend(TARGET_BITS.to_be_bytes()); 132 | data_bytes.extend(nonce.to_be_bytes()); 133 | return data_bytes; 134 | } 135 | 136 | 137 | pub fn run(&self) -> (i64, String) { 138 | let mut nonce = 0; 139 | let mut hash = Vec::new(); 140 | println!("Mining the block"); 141 | while nonce < MAX_NONCE { 142 | let data = self.prepare_data(nonce); 143 | hash = crate::sha256_digest(data.as_slice()); 144 | let hash_int = BigInt::from_bytes_be(Sign::Plus, hash.as_slice()); 145 | 146 | 147 | if hash_int.lt(self.target.borrow()) { 148 | println!("{}", HEXLOWER.encode(hash.as_slice())); 149 | break; 150 | } else { 151 | nonce += 1; 152 | } 153 | } 154 | println!(); 155 | return (nonce, HEXLOWER.encode(hash.as_slice())); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /complete_blockchain/src/blockchain.rs: -------------------------------------------------------------------------------- 1 | // blockchain.rs 2 | 3 | use crate::transaction::TXOutput; 4 | use crate::{Block, Transaction}; 5 | use data_encoding::HEXLOWER; 6 | use sled::transaction::TransactionResult; 7 | use sled::{Db, Tree}; 8 | use std::collections::HashMap; 9 | use std::env::current_dir; 10 | use std::sync::{Arc, RwLock}; 11 | 12 | const TIP_BLOCK_HASH_KEY: &str = "tip_block_hash"; 13 | const BLOCKS_TREE: &str = "blocks"; 14 | 15 | #[derive(Clone)] 16 | pub struct Blockchain { 17 | tip_hash: Arc>, // hash of last block 18 | db: Db, 19 | } 20 | 21 | impl Blockchain { 22 | 23 | pub fn create_blockchain(genesis_address: &str) -> Blockchain { 24 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 25 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 26 | 27 | let data = blocks_tree.get(TIP_BLOCK_HASH_KEY).unwrap(); 28 | let tip_hash; 29 | if data.is_none() { 30 | let coinbase_tx = Transaction::new_coinbase_tx(genesis_address); 31 | let block = Block::generate_genesis_block(&coinbase_tx); 32 | Self::update_blocks_tree(&blocks_tree, &block); 33 | tip_hash = String::from(block.get_hash()); 34 | } else { 35 | tip_hash = String::from_utf8(data.unwrap().to_vec()).unwrap(); 36 | } 37 | Blockchain { 38 | tip_hash: Arc::new(RwLock::new(tip_hash)), 39 | db, 40 | } 41 | } 42 | 43 | fn update_blocks_tree(blocks_tree: &Tree, block: &Block) { 44 | let block_hash = block.get_hash(); 45 | let _: TransactionResult<(), ()> = blocks_tree.transaction(|tx_db| { 46 | let _ = tx_db.insert(block_hash, block.clone()); 47 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block_hash); 48 | Ok(()) 49 | }); 50 | } 51 | 52 | 53 | pub fn new_blockchain() -> Blockchain { 54 | let db = sled::open(current_dir().unwrap().join("data")).unwrap(); 55 | let blocks_tree = db.open_tree(BLOCKS_TREE).unwrap(); 56 | let tip_bytes = blocks_tree 57 | .get(TIP_BLOCK_HASH_KEY) 58 | .unwrap() 59 | .expect("No existing blockchain found. Create one first."); 60 | let tip_hash = String::from_utf8(tip_bytes.to_vec()).unwrap(); 61 | Blockchain { 62 | tip_hash: Arc::new(RwLock::new(tip_hash)), 63 | db, 64 | } 65 | } 66 | 67 | pub fn get_db(&self) -> &Db { 68 | &self.db 69 | } 70 | 71 | pub fn get_tip_hash(&self) -> String { 72 | self.tip_hash.read().unwrap().clone() 73 | } 74 | 75 | pub fn set_tip_hash(&self, new_tip_hash: &str) { 76 | let mut tip_hash = self.tip_hash.write().unwrap(); 77 | *tip_hash = String::from(new_tip_hash) 78 | } 79 | 80 | // let us move the iterator code up for readability of the users ? 81 | // pub fn iterator(&self) -> BlockchainIterator { 82 | // BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 83 | // } 84 | 85 | pub fn mine_block(&self, transactions: &[Transaction]) -> Block { 86 | for trasaction in transactions { 87 | if trasaction.verify(self) == false { 88 | panic!("ERROR: Invalid transaction") 89 | } 90 | } 91 | let best_height = self.get_best_height(); 92 | 93 | let block = Block::new_block(self.get_tip_hash(), transactions, best_height + 1); 94 | let block_hash = block.get_hash(); 95 | 96 | let blocks_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 97 | Self::update_blocks_tree(&blocks_tree, &block); 98 | self.set_tip_hash(block_hash); 99 | block 100 | } 101 | 102 | pub fn iterator(&self) -> BlockchainIterator { 103 | BlockchainIterator::new(self.get_tip_hash(), self.db.clone()) 104 | } 105 | 106 | // can we add the BlockchainIterator here so that the readers can follow easily 107 | 108 | 109 | 110 | // ( K -> txid_hex, V -> Vec HashMap> { 112 | let mut utxo: HashMap> = HashMap::new(); 113 | let mut spent_txos: HashMap> = HashMap::new(); 114 | 115 | let mut iterator = self.iterator(); 116 | loop { 117 | let option = iterator.next(); 118 | if option.is_none() { 119 | break; 120 | } 121 | let block = option.unwrap(); 122 | 'outer: for tx in block.get_transactions() { 123 | let txid_hex = HEXLOWER.encode(tx.get_id()); 124 | for (idx, out) in tx.get_vout().iter().enumerate() { 125 | 126 | if let Some(outs) = spent_txos.get(txid_hex.as_str()) { 127 | for spend_out_idx in outs { 128 | if idx.eq(spend_out_idx) { 129 | continue 'outer; 130 | } 131 | } 132 | } 133 | if utxo.contains_key(txid_hex.as_str()) { 134 | utxo.get_mut(txid_hex.as_str()).unwrap().push(out.clone()); 135 | } else { 136 | utxo.insert(txid_hex.clone(), vec![out.clone()]); 137 | } 138 | } 139 | if tx.is_coinbase() { 140 | continue; 141 | } 142 | 143 | for txin in tx.get_vin() { 144 | let txid_hex = HEXLOWER.encode(txin.get_txid()); 145 | if spent_txos.contains_key(txid_hex.as_str()) { 146 | spent_txos 147 | .get_mut(txid_hex.as_str()) 148 | .unwrap() 149 | .push(txin.get_vout()); 150 | } else { 151 | spent_txos.insert(txid_hex, vec![txin.get_vout()]); 152 | } 153 | } 154 | } 155 | } 156 | utxo 157 | } 158 | 159 | 160 | pub fn find_transaction(&self, txid: &[u8]) -> Option { 161 | let mut iterator = self.iterator(); 162 | loop { 163 | let option = iterator.next(); 164 | if option.is_none() { 165 | break; 166 | } 167 | let block = option.unwrap(); 168 | for transaction in block.get_transactions() { 169 | if txid.eq(transaction.get_id()) { 170 | return Some(transaction.clone()); 171 | } 172 | } 173 | } 174 | None 175 | } 176 | 177 | 178 | pub fn add_block(&self, block: &Block) { 179 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 180 | if let Some(_) = block_tree.get(block.get_hash()).unwrap() { 181 | return; 182 | } 183 | let _: TransactionResult<(), ()> = block_tree.transaction(|tx_db| { 184 | let _ = tx_db.insert(block.get_hash(), block.serialize()).unwrap(); 185 | 186 | let tip_block_bytes = tx_db 187 | .get(self.get_tip_hash()) 188 | .unwrap() 189 | .expect("The tip hash is not valid"); 190 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 191 | if block.get_height() > tip_block.get_height() { 192 | let _ = tx_db.insert(TIP_BLOCK_HASH_KEY, block.get_hash()).unwrap(); 193 | self.set_tip_hash(block.get_hash()); 194 | } 195 | Ok(()) 196 | }); 197 | } 198 | 199 | 200 | pub fn get_best_height(&self) -> usize { 201 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 202 | let tip_block_bytes = block_tree 203 | .get(self.get_tip_hash()) 204 | .unwrap() 205 | .expect("The tip hash is valid"); 206 | let tip_block = Block::deserialize(tip_block_bytes.as_ref()); 207 | tip_block.get_height() 208 | } 209 | 210 | 211 | pub fn get_block(&self, block_hash: &[u8]) -> Option { 212 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 213 | if let Some(block_bytes) = block_tree.get(block_hash).unwrap() { 214 | let block = Block::deserialize(block_bytes.as_ref()); 215 | return Some(block); 216 | } 217 | return None; 218 | } 219 | 220 | 221 | pub fn get_block_hashes(&self) -> Vec> { 222 | let mut iterator = self.iterator(); 223 | let mut blocks = vec![]; 224 | loop { 225 | let option = iterator.next(); 226 | if option.is_none() { 227 | break; 228 | } 229 | let block = option.unwrap(); 230 | blocks.push(block.get_hash_bytes()); 231 | } 232 | return blocks; 233 | } 234 | } 235 | 236 | pub struct BlockchainIterator { 237 | db: Db, 238 | current_hash: String, 239 | } 240 | 241 | impl BlockchainIterator { 242 | fn new(tip_hash: String, db: Db) -> BlockchainIterator { 243 | BlockchainIterator { 244 | current_hash: tip_hash, 245 | db, 246 | } 247 | } 248 | 249 | pub fn next(&mut self) -> Option { 250 | let block_tree = self.db.open_tree(BLOCKS_TREE).unwrap(); 251 | let data = block_tree.get(self.current_hash.clone()).unwrap(); 252 | if data.is_none() { 253 | return None; 254 | } 255 | let block = Block::deserialize(data.unwrap().to_vec().as_slice()); 256 | self.current_hash = block.get_pre_block_hash().clone(); 257 | return Some(block); 258 | } 259 | } 260 | 261 | 262 | -------------------------------------------------------------------------------- /complete_blockchain/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 | 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 | -------------------------------------------------------------------------------- /complete_blockchain/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 | -------------------------------------------------------------------------------- /complete_blockchain/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 | 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 | 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 | 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 | -------------------------------------------------------------------------------- /complete_blockchain/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 contains(&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 | } -------------------------------------------------------------------------------- /complete_blockchain/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 | -------------------------------------------------------------------------------- /complete_blockchain/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 | pub const CENTERAL_NODE: &str = "127.0.0.1:2001"; 18 | 19 | 20 | pub const TRANSACTION_THRESHOLD: usize = 2; 21 | 22 | 23 | static GLOBAL_NODES: Lazy = Lazy::new(|| { 24 | let nodes = Nodes::new(); 25 | 26 | nodes.add_node(String::from(CENTERAL_NODE)); 27 | return nodes; 28 | }); 29 | 30 | 31 | static GLOBAL_MEMORY_POOL: Lazy = Lazy::new(|| MemoryPool::new()); 32 | 33 | 34 | static GLOBAL_BLOCKS_IN_TRANSIT: Lazy = Lazy::new(|| BlockInTransit::new()); 35 | 36 | 37 | const TCP_WRITE_TIMEOUT: u64 = 1000; 38 | 39 | pub struct Server { 40 | blockchain: Blockchain, 41 | } 42 | 43 | impl Server { 44 | pub fn new(blockchain: Blockchain) -> Server { 45 | Server { blockchain } 46 | } 47 | 48 | pub fn run(&self, addr: &str) { 49 | let listener = TcpListener::bind(addr).unwrap(); 50 | 51 | if addr.eq(CENTERAL_NODE) == false { 52 | let best_height = self.blockchain.get_best_height(); 53 | send_version(CENTERAL_NODE, best_height); 54 | } 55 | for stream in listener.incoming() { 56 | let blockchain = self.blockchain.clone(); 57 | thread::spawn(|| match stream { 58 | Ok(stream) => { 59 | ... 60 | } 61 | Err(e) => { 62 | ... 63 | } 64 | }); 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug, Serialize, Deserialize)] 70 | pub enum OpType { 71 | Tx, 72 | Block, 73 | } 74 | 75 | #[derive(Debug, Serialize, Deserialize)] 76 | pub enum Package { 77 | Block { 78 | addr_from: String, 79 | block: Vec, 80 | }, 81 | GetBlocks { 82 | addr_from: String, 83 | }, 84 | GetData { 85 | addr_from: String, 86 | op_type: OpType, 87 | id: Vec, 88 | }, 89 | Inv { 90 | addr_from: String, 91 | op_type: OpType, 92 | items: Vec>, 93 | }, 94 | Tx { 95 | addr_from: String, 96 | transaction: Vec, 97 | }, 98 | Version { 99 | addr_from: String, 100 | version: usize, 101 | best_height: usize, 102 | }, 103 | } 104 | 105 | fn send_get_data(addr: &str, op_type: OpType, id: &[u8]) { 106 | let socket_addr = addr.parse().unwrap(); 107 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 108 | send_data( 109 | socket_addr, 110 | Package::GetData { 111 | addr_from: node_addr, 112 | op_type, 113 | id: id.to_vec(), 114 | }, 115 | ); 116 | } 117 | 118 | fn send_inv(addr: &str, op_type: OpType, blocks: &[Vec]) { 119 | let socket_addr = addr.parse().unwrap(); 120 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 121 | send_data( 122 | socket_addr, 123 | Package::Inv { 124 | addr_from: node_addr, 125 | op_type, 126 | items: blocks.to_vec(), 127 | }, 128 | ); 129 | } 130 | 131 | fn send_block(addr: &str, block: &Block) { 132 | let socket_addr = addr.parse().unwrap(); 133 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 134 | send_data( 135 | socket_addr, 136 | Package::Block { 137 | addr_from: node_addr, 138 | block: block.serialize(), 139 | }, 140 | ); 141 | } 142 | 143 | pub fn send_tx(addr: &str, tx: &Transaction) { 144 | let socket_addr = addr.parse().unwrap(); 145 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 146 | send_data( 147 | socket_addr, 148 | Package::Tx { 149 | addr_from: node_addr, 150 | transaction: tx.serialize(), 151 | }, 152 | ); 153 | } 154 | 155 | fn send_version(addr: &str, height: usize) { 156 | let socket_addr = addr.parse().unwrap(); 157 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 158 | send_data( 159 | socket_addr, 160 | Package::Version { 161 | addr_from: node_addr, 162 | version: NODE_VERSION, 163 | best_height: height, 164 | }, 165 | ); 166 | } 167 | 168 | fn send_get_blocks(addr: &str) { 169 | let socket_addr = addr.parse().unwrap(); 170 | let node_addr = GLOBAL_CONFIG.get_node_addr().parse().unwrap(); 171 | send_data( 172 | socket_addr, 173 | Package::GetBlocks { 174 | addr_from: node_addr, 175 | }, 176 | ); 177 | } 178 | 179 | fn serve(blockchain: Blockchain, stream: TcpStream) -> Result<(), Box> { 180 | let peer_addr = stream.peer_addr()?; 181 | let reader = BufReader::new(&stream); 182 | let pkg_reader = Deserializer::from_reader(reader).into_iter::(); 183 | for pkg in pkg_reader { 184 | let pkg = pkg?; 185 | info!("Receive request from {}: {:?}", peer_addr, pkg); 186 | match pkg { 187 | Package::Block { addr_from, block } => { 188 | let block = Block::deserialize(block.as_slice()); 189 | blockchain.add_block(&block); 190 | info!("Added block {}", block.get_hash()); 191 | 192 | if GLOBAL_BLOCKS_IN_TRANSIT.len() > 0 { 193 | 194 | let block_hash = GLOBAL_BLOCKS_IN_TRANSIT.first().unwrap(); 195 | send_get_data(addr_from.as_str(), OpType::Block, &block_hash); 196 | 197 | GLOBAL_BLOCKS_IN_TRANSIT.remove(block_hash.as_slice()); 198 | } else { 199 | 200 | let utxo_set = UTXOSet::new(blockchain.clone()); 201 | utxo_set.reindex(); 202 | } 203 | } 204 | Package::GetBlocks { addr_from } => { 205 | let blocks = blockchain.get_block_hashes(); 206 | send_inv(addr_from.as_str(), OpType::Block, &blocks); 207 | } 208 | Package::GetData { 209 | addr_from, 210 | op_type, 211 | id, 212 | } => match op_type { 213 | OpType::Block => { 214 | if let Some(block) = blockchain.get_block(id.as_slice()) { 215 | send_block(addr_from.as_str(), &block); 216 | } 217 | } 218 | OpType::Tx => { 219 | let txid_hex = HEXLOWER.encode(id.as_slice()); 220 | if let Some(tx) = GLOBAL_MEMORY_POOL.get(txid_hex.as_str()) { 221 | send_tx(addr_from.as_str(), &tx); 222 | } 223 | } 224 | }, 225 | Package::Inv { 226 | addr_from, 227 | op_type, 228 | items, 229 | } => match op_type { 230 | 231 | OpType::Block => { 232 | 233 | GLOBAL_BLOCKS_IN_TRANSIT.add_blocks(items.as_slice()); 234 | 235 | 236 | let block_hash = items.get(0).unwrap(); 237 | send_get_data(addr_from.as_str(), OpType::Block, block_hash); 238 | 239 | GLOBAL_BLOCKS_IN_TRANSIT.remove(block_hash); 240 | } 241 | OpType::Tx => { 242 | let txid = items.get(0).unwrap(); 243 | let txid_hex = HEXLOWER.encode(txid); 244 | 245 | if GLOBAL_MEMORY_POOL.contains(txid_hex.as_str()) == false { 246 | send_get_data(addr_from.as_str(), OpType::Tx, txid); 247 | } 248 | } 249 | }, 250 | Package::Tx { 251 | addr_from, 252 | transaction, 253 | } => { 254 | 255 | let tx = Transaction::deserialize(transaction.as_slice()); 256 | let txid = tx.get_id_bytes(); 257 | GLOBAL_MEMORY_POOL.add(tx); 258 | 259 | let node_addr = GLOBAL_CONFIG.get_node_addr(); 260 | 261 | if node_addr.eq(CENTERAL_NODE) { 262 | let nodes = GLOBAL_NODES.get_nodes(); 263 | for node in &nodes { 264 | if node_addr.eq(node.get_addr().as_str()) { 265 | continue; 266 | } 267 | if addr_from.eq(node.get_addr().as_str()) { 268 | continue; 269 | } 270 | send_inv(node.get_addr().as_str(), OpType::Tx, &vec![txid.clone()]) 271 | } 272 | } 273 | 274 | if GLOBAL_MEMORY_POOL.len() >= TRANSACTION_THRESHOLD && GLOBAL_CONFIG.is_miner() { 275 | 276 | let mining_address = GLOBAL_CONFIG.get_mining_addr().unwrap(); 277 | let coinbase_tx = Transaction::new_coinbase_tx(mining_address.as_str()); 278 | let mut txs = GLOBAL_MEMORY_POOL.get_all(); 279 | txs.push(coinbase_tx); 280 | 281 | 282 | let new_block = blockchain.mine_block(&txs); 283 | let utxo_set = UTXOSet::new(blockchain.clone()); 284 | utxo_set.reindex(); 285 | info!("New block {} is mined!", new_block.get_hash()); 286 | 287 | 288 | for tx in &txs { 289 | let txid_hex = HEXLOWER.encode(tx.get_id()); 290 | GLOBAL_MEMORY_POOL.remove(txid_hex.as_str()); 291 | } 292 | 293 | let nodes = GLOBAL_NODES.get_nodes(); 294 | for node in &nodes { 295 | if node_addr.eq(node.get_addr().as_str()) { 296 | continue; 297 | } 298 | send_inv( 299 | node.get_addr().as_str(), 300 | OpType::Block, 301 | &vec![new_block.get_hash_bytes()], 302 | ); 303 | } 304 | } 305 | } 306 | Package::Version { 307 | addr_from, 308 | version, 309 | best_height, 310 | } => { 311 | info!("version = {}, best_height = {}", version, best_height); 312 | let local_best_height = blockchain.get_best_height(); 313 | if local_best_height < best_height { 314 | send_get_blocks(addr_from.as_str()); 315 | } 316 | if local_best_height > best_height { 317 | send_version(addr_from.as_str(), blockchain.get_best_height()); 318 | } 319 | 320 | if GLOBAL_NODES.node_is_known(peer_addr.to_string().as_str()) == false { 321 | GLOBAL_NODES.add_node(addr_from); 322 | } 323 | } 324 | } 325 | } 326 | let _ = stream.shutdown(Shutdown::Both); 327 | Ok(()) 328 | } 329 | 330 | 331 | fn send_data(addr: SocketAddr, pkg: Package) { 332 | info!("send package: {:?}", &pkg); 333 | let stream = TcpStream::connect(addr); 334 | if stream.is_err() { 335 | error!("The {} is not valid", addr); 336 | 337 | GLOBAL_NODES.evict_node(addr.to_string().as_str()); 338 | return; 339 | } 340 | let mut stream = stream.unwrap(); 341 | let _ = stream.set_write_timeout(Option::from(Duration::from_millis(TCP_WRITE_TIMEOUT))); 342 | let _ = serde_json::to_writer(&stream, &pkg); 343 | let _ = stream.flush(); 344 | } -------------------------------------------------------------------------------- /complete_blockchain/src/structs.rs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------- 2 | // Structs 3 | // -------------------------------------------------------------------------------------------------- 4 | 5 | pub struct Block { 6 | timestamp: i64, 7 | pre_block_hash: String, 8 | hash: String, 9 | transactions: Vec, 10 | nonce: i64, 11 | height: usize, 12 | } 13 | 14 | pub struct Transaction { 15 | id: Vec, 16 | vin: Vec, 17 | vout: Vec, 18 | } 19 | 20 | pub struct TXInput { 21 | txid: Vec, 22 | vout: usize, 23 | signature: Vec, 24 | pub_key: Vec, 25 | } 26 | 27 | pub struct TXOutput { 28 | value: i32, 29 | pub_key_hash: Vec, 30 | } 31 | 32 | pub struct Blockchain { 33 | tip_hash: Arc>, // hash of last block 34 | db: Db, 35 | } 36 | 37 | pub struct Wallet { 38 | pkcs8: Vec, 39 | public_key: Vec, 40 | } 41 | 42 | pub struct Node { 43 | addr: String, 44 | } 45 | pub struct Nodes { 46 | inner: RwLock>, 47 | } 48 | 49 | pub struct Server { 50 | blockchain: Blockchain, 51 | } 52 | 53 | pub struct ProofOfWork { 54 | block: Block, 55 | target: BigInt, 56 | } 57 | -------------------------------------------------------------------------------- /complete_blockchain/src/transactions.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, 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, 90 | vin: Vec, 91 | vout: Vec, 92 | } 93 | 94 | impl Transaction { 95 | 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 | 112 | pub fn new_utxo_transaction( 113 | from: &str, 114 | to: &str, 115 | amount: i32, 116 | utxo_set: &UTXOSet, 117 | ) -> Transaction { 118 | 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 | 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 | 129 | let mut inputs = vec![]; 130 | for (txid_hex, outs) in valid_outputs { 131 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 132 | for out in outs { 133 | let input = TXInput { 134 | txid: txid.clone(), 135 | vout: out, 136 | signature: vec![], 137 | pub_key: wallet.get_public_key().to_vec(), 138 | }; 139 | inputs.push(input); 140 | } 141 | } 142 | 143 | let mut outputs = vec![TXOutput::new(amount, to)]; 144 | 145 | if accumulated > amount { 146 | outputs.push(TXOutput::new(accumulated - amount, from)) // to: 币收入 147 | } 148 | 149 | let mut tx = Transaction { 150 | id: vec![], 151 | vin: inputs, 152 | vout: outputs, 153 | }; 154 | 155 | tx.id = tx.hash(); 156 | 157 | tx.sign(utxo_set.get_blockchain(), wallet.get_pkcs8()); 158 | return tx; 159 | } 160 | 161 | 162 | fn trimmed_copy(&self) -> Transaction { 163 | let mut inputs = vec![]; 164 | let mut outputs = vec![]; 165 | for input in &self.vin { 166 | let txinput = TXInput::new(input.get_txid(), input.get_vout()); 167 | inputs.push(txinput); 168 | } 169 | for output in &self.vout { 170 | outputs.push(output.clone()); 171 | } 172 | Transaction { 173 | id: self.id.clone(), 174 | vin: inputs, 175 | vout: outputs, 176 | } 177 | } 178 | 179 | fn sign(&mut self, blockchain: &Blockchain, pkcs8: &[u8]) { 180 | let mut tx_copy = self.trimmed_copy(); 181 | 182 | for (idx, vin) in self.vin.iter_mut().enumerate() { 183 | 184 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 185 | if prev_tx_option.is_none() { 186 | panic!("ERROR: Previous transaction is not correct") 187 | } 188 | let prev_tx = prev_tx_option.unwrap(); 189 | tx_copy.vin[idx].signature = vec![]; 190 | tx_copy.vin[idx].pub_key = prev_tx.vout[vin.vout].pub_key_hash.clone(); 191 | tx_copy.id = tx_copy.hash(); 192 | tx_copy.vin[idx].pub_key = vec![]; 193 | 194 | 195 | let signature = crate::ecdsa_p256_sha256_sign_digest(pkcs8, tx_copy.get_id()); 196 | vin.signature = signature; 197 | } 198 | } 199 | 200 | pub fn verify(&self, blockchain: &Blockchain) -> bool { 201 | if self.is_coinbase() { 202 | return true; 203 | } 204 | let mut tx_copy = self.trimmed_copy(); 205 | for (idx, vin) in self.vin.iter().enumerate() { 206 | let prev_tx_option = blockchain.find_transaction(vin.get_txid()); 207 | if prev_tx_option.is_none() { 208 | panic!("ERROR: Previous transaction is not correct") 209 | } 210 | let prev_tx = prev_tx_option.unwrap(); 211 | tx_copy.vin[idx].signature = vec![]; 212 | tx_copy.vin[idx].pub_key = prev_tx.vout[vin.vout].pub_key_hash.clone(); 213 | tx_copy.id = tx_copy.hash(); 214 | tx_copy.vin[idx].pub_key = vec![]; 215 | 216 | 217 | let verify = crate::ecdsa_p256_sha256_sign_verify( 218 | vin.pub_key.as_slice(), 219 | vin.signature.as_slice(), 220 | tx_copy.get_id(), 221 | ); 222 | if !verify { 223 | return false; 224 | } 225 | } 226 | true 227 | } 228 | 229 | 230 | pub fn is_coinbase(&self) -> bool { 231 | return self.vin.len() == 1 && self.vin[0].pub_key.len() == 0; 232 | } 233 | 234 | 235 | fn hash(&mut self) -> Vec { 236 | let tx_copy = Transaction { 237 | id: vec![], 238 | vin: self.vin.clone(), 239 | vout: self.vout.clone(), 240 | }; 241 | crate::sha256_digest(tx_copy.serialize().as_slice()) 242 | } 243 | 244 | pub fn get_id(&self) -> &[u8] { 245 | self.id.as_slice() 246 | } 247 | 248 | pub fn get_id_bytes(&self) -> Vec { 249 | self.id.clone() 250 | } 251 | 252 | pub fn get_vin(&self) -> &[TXInput] { 253 | self.vin.as_slice() 254 | } 255 | 256 | pub fn get_vout(&self) -> &[TXOutput] { 257 | self.vout.as_slice() 258 | } 259 | 260 | pub fn serialize(&self) -> Vec { 261 | bincode::serialize(self).unwrap().to_vec() 262 | } 263 | 264 | pub fn deserialize(bytes: &[u8]) -> Transaction { 265 | bincode::deserialize(bytes).unwrap() 266 | } 267 | } -------------------------------------------------------------------------------- /complete_blockchain/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 | 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 | 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 | 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 | 34 | pub fn base58_encode(data: &[u8]) -> String { 35 | bs58::encode(data).into_string() 36 | } 37 | 38 | 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 | 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 | 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 | 66 | -------------------------------------------------------------------------------- /complete_blockchain/src/utxoSet.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 | 9 | pub struct UTXOSet { 10 | blockchain: Blockchain, 11 | } 12 | 13 | impl UTXOSet { 14 | 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 | 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 | 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 | pub fn reindex(&self) { 85 | let db = self.blockchain.get_db(); 86 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 87 | let _ = utxo_tree.clear().unwrap(); 88 | 89 | let utxo_map = self.blockchain.find_utxo(); 90 | for (txid_hex, outs) in &utxo_map { 91 | let txid = HEXLOWER.decode(txid_hex.as_bytes()).unwrap(); 92 | let value = bincode::serialize(outs).unwrap(); 93 | let _ = utxo_tree.insert(txid.as_slice(), value).unwrap(); 94 | } 95 | } 96 | 97 | 98 | pub fn update(&self, block: &Block) { 99 | let db = self.blockchain.get_db(); 100 | let utxo_tree = db.open_tree(UTXO_TREE).unwrap(); 101 | for tx in block.get_transactions() { 102 | if tx.is_coinbase() == false { 103 | for vin in tx.get_vin() { 104 | let mut updated_outs = vec![]; 105 | let outs_bytes = utxo_tree.get(vin.get_txid()).unwrap().unwrap(); 106 | let outs: Vec = bincode::deserialize(outs_bytes.as_ref()) 107 | .expect("unable to deserialize TXOutput"); 108 | for (idx, out) in outs.iter().enumerate() { 109 | if idx != vin.get_vout() { 110 | updated_outs.push(out.clone()) 111 | } 112 | } 113 | if updated_outs.len() == 0 { 114 | let _ = utxo_tree.remove(vin.get_txid()).unwrap(); 115 | } else { 116 | let outs_bytes = bincode::serialize(&updated_outs) 117 | .expect("unable to serialize TXOutput"); 118 | utxo_tree.insert(vin.get_txid(), outs_bytes).unwrap(); 119 | } 120 | } 121 | } 122 | let mut new_outputs = vec![]; 123 | for out in tx.get_vout() { 124 | new_outputs.push(out.clone()) 125 | } 126 | let outs_bytes = 127 | bincode::serialize(&new_outputs).expect("unable to serialize TXOutput"); 128 | let _ = utxo_tree.insert(tx.get_id(), outs_bytes).unwrap(); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /complete_blockchain/src/wallets.rs: -------------------------------------------------------------------------------- 1 | // wallet 2 | use ring::signature::{EcdsaKeyPair, KeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | const VERSION: u8 = 0x00; 6 | pub const ADDRESS_CHECK_SUM_LEN: usize = 4; 7 | 8 | #[derive(Clone, Serialize, Deserialize)] 9 | pub struct Wallet { 10 | pkcs8: Vec, 11 | public_key: Vec, 12 | } 13 | 14 | impl Wallet { 15 | 16 | pub fn new() -> Wallet { 17 | let pkcs8 = crate::new_key_pair(); 18 | let key_pair = 19 | EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref()).unwrap(); 20 | let public_key = key_pair.public_key().as_ref().to_vec(); 21 | Wallet { pkcs8, public_key } 22 | } 23 | 24 | 25 | pub fn get_address(&self) -> String { 26 | let pub_key_hash = hash_pub_key(self.public_key.as_slice()); 27 | let mut payload: Vec = vec![]; 28 | payload.push(VERSION); 29 | payload.extend(pub_key_hash.as_slice()); 30 | let checksum = checksum(payload.as_slice()); 31 | payload.extend(checksum.as_slice()); 32 | // version + pub_key_hash + checksum 33 | crate::base58_encode(payload.as_slice()) 34 | } 35 | 36 | pub fn get_public_key(&self) -> &[u8] { 37 | self.public_key.as_slice() 38 | } 39 | 40 | pub fn get_pkcs8(&self) -> &[u8] { 41 | self.pkcs8.as_slice() 42 | } 43 | } 44 | 45 | 46 | pub fn hash_pub_key(pub_key: &[u8]) -> Vec { 47 | let pub_key_sha256 = crate::sha256_digest(pub_key); 48 | crate::ripemd160_digest(pub_key_sha256.as_slice()) 49 | } 50 | 51 | 52 | fn checksum(payload: &[u8]) -> Vec { 53 | let first_sha = crate::sha256_digest(payload); 54 | let second_sha = crate::sha256_digest(first_sha.as_slice()); 55 | second_sha[0..ADDRESS_CHECK_SUM_LEN].to_vec() 56 | } 57 | 58 | 59 | pub fn validate_address(address: &str) -> bool { 60 | let payload = crate::base58_decode(address); 61 | let actual_checksum = payload[payload.len() - ADDRESS_CHECK_SUM_LEN..].to_vec(); 62 | let version = payload[0]; 63 | let pub_key_hash = payload[1..payload.len() - ADDRESS_CHECK_SUM_LEN].to_vec(); 64 | 65 | let mut target_vec = vec![]; 66 | target_vec.push(version); 67 | target_vec.extend(pub_key_hash); 68 | let target_checksum = checksum(target_vec.as_slice()); 69 | actual_checksum.eq(target_checksum.as_slice()) 70 | } 71 | 72 | 73 | pub fn convert_address(pub_hash_key: &[u8]) -> String { 74 | let mut payload: Vec = vec![]; 75 | payload.push(VERSION); 76 | payload.extend(pub_hash_key); 77 | let checksum = checksum(payload.as_slice()); 78 | payload.extend(checksum.as_slice()); 79 | crate::base58_encode(payload.as_slice()) 80 | } 81 | 82 | // wallets 83 | 84 | use crate::Wallet; 85 | use std::collections::HashMap; 86 | use std::env::current_dir; 87 | use std::fs::{File, OpenOptions}; 88 | use std::io::{BufWriter, Read, Write}; 89 | 90 | pub const WALLET_FILE: &str = "wallet.dat"; 91 | 92 | pub struct Wallets { 93 | wallets: HashMap, 94 | } 95 | 96 | impl Wallets { 97 | pub fn new() -> Wallets { 98 | let mut wallets = Wallets { 99 | wallets: HashMap::new(), 100 | }; 101 | wallets.load_from_file(); 102 | return wallets; 103 | } 104 | 105 | 106 | pub fn create_wallet(&mut self) -> String { 107 | let wallet = Wallet::new(); 108 | let address = wallet.get_address(); 109 | self.wallets.insert(address.clone(), wallet); 110 | self.save_to_file(); 111 | return address; 112 | } 113 | 114 | pub fn get_addresses(&self) -> Vec { 115 | let mut addresses = vec![]; 116 | for (address, _) in &self.wallets { 117 | addresses.push(address.clone()) 118 | } 119 | return addresses; 120 | } 121 | 122 | 123 | pub fn get_wallet(&self, address: &str) -> Option<&Wallet> { 124 | if let Some(wallet) = self.wallets.get(address) { 125 | return Some(wallet); 126 | } 127 | None 128 | } 129 | 130 | 131 | pub fn load_from_file(&mut self) { 132 | let path = current_dir().unwrap().join(WALLET_FILE); 133 | if !path.exists() { 134 | return; 135 | } 136 | let mut file = File::open(path).unwrap(); 137 | let metadata = file.metadata().expect("unable to read metadata"); 138 | let mut buf = vec![0; metadata.len() as usize]; 139 | let _ = file.read(&mut buf).expect("buffer overflow"); 140 | let wallets = bincode::deserialize(&buf[..]).expect("unable to deserialize file data"); 141 | self.wallets = wallets; 142 | } 143 | 144 | 145 | fn save_to_file(&self) { 146 | let path = current_dir().unwrap().join(WALLET_FILE); 147 | let file = OpenOptions::new() 148 | .create(true) 149 | .write(true) 150 | .open(&path) 151 | .expect("unable to open wallet.dat"); 152 | let mut writer = BufWriter::new(file); 153 | let wallets_bytes = bincode::serialize(&self.wallets).expect("unable to serialize wallets"); 154 | writer.write(wallets_bytes.as_slice()).unwrap(); 155 | let _ = writer.flush(); 156 | } 157 | } 158 | 159 | --------------------------------------------------------------------------------