├── 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 |
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 |
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 |
--------------------------------------------------------------------------------