diff --git a/Cargo.toml b/Cargo.toml index 0dab600..9d6381a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ secp256k1 = "0.29.0" sha2 = "0.10.8" hex = "0.4.3" num-bigint = "0.4.4" -ripemd = "0.1.3" \ No newline at end of file +ripemd = "0.1.3" +ripemd160 = "0.10.0" \ No newline at end of file diff --git a/SOLUTION.md b/SOLUTION.md new file mode 100644 index 0000000..b1c66e0 --- /dev/null +++ b/SOLUTION.md @@ -0,0 +1,35 @@ +# Project Documentation + +## Design Approach + +The project is structured to first validate and filter out invalid transactions. After ensuring the transactions are valid, the process computes fees and sizes. We use a greedy algorithm based on the highest unit fee to optimize the selection of transactions to be included in the next block to be mined. + +## Implementation Details + +### File Reader + +- **Location**: `main.rs` +- **Purpose**: Initializes the reading process for incoming transaction data and directs it to the validation logic. + +### Transaction Validation + +- **Location**: Defined within the `Transaction` struct in `transaction.rs` +- **Functionality**: Validates transactions based on predefined rules and filters out those that are invalid. + +### Fee and Size Calculation + +- **Location**: Methods implemented in the `Transaction` struct in `transaction.rs` +- **Details**: Calculates the transaction fees and sizes which are crucial for the transaction selection algorithm. + +### Block Construction + +- **Location**: `block.rs` +- **Description**: Handles the logic for constructing a block from validated and selected transactions. + +## Results and Performance + +The implementation achieves a performance score of about 65-75, without extensive optimizations. With further refinement, the system is theoretically capable of including more transactions per block, thereby increasing score. + +## Conclusion + +This test project successfully implements block building logic for a btc. It includes critical functionalities such as transaction validation, fee and size calculations, and block mining. This foundation allows for further development and optimization in future iterations. diff --git a/src/block.rs b/src/block.rs index ae3cc7a..560deda 100644 --- a/src/block.rs +++ b/src/block.rs @@ -1,7 +1,5 @@ use crate::tx::{Output, Transaction}; -use num_bigint::BigUint; use serde::{Deserialize, Serialize}; -use serde_json; use sha2::{Digest, Sha256}; use std::{fs::File, io::Write, time::SystemTime, vec}; @@ -129,7 +127,7 @@ impl Block { .expect("Failed to write coinbase transaction to file"); } - for i in 0..self.transactions.len() { + for i in 0..self.transactions.len() { // if tx.calculate_wtxid().unwrap() == "35f1e96e0c00a213134b533d93a6b3cf074c24178b640c1fbdecfe0724455e66" { // println!("{:?}", tx); // println!("{:?}", tx.calculate_txid()); diff --git a/src/main.rs b/src/main.rs index 9fb10eb..26b549d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::fs; use std::io; use std::path::Path; @@ -6,6 +5,7 @@ use std::path::PathBuf; mod block; mod coinbase; +mod p2pkh; mod tx; mod validate; use tx::Transaction; @@ -56,14 +56,8 @@ fn get_tx() -> Vec { } Err(e) => panic!("Error reading transactions: {}", e), }; - - let mut invalid_transactions = 0; - let mut fail = 0; let mut valid_txs = vec![]; for tx in txs { - // if let Err(_) = validate_transaction(tx) { - // invalid_transactions += 1; - // } if tx.is_basic_valid() { valid_txs.push(tx); } @@ -82,7 +76,9 @@ fn select_tx_for_block(txs: Vec) -> Vec { txs_sorted.sort_by(|a, b| { let fee_rate_a = a.fee() as f64 / a.weight() as f64; let fee_rate_b = b.fee() as f64 / b.weight() as f64; - fee_rate_b.partial_cmp(&fee_rate_a).unwrap_or(std::cmp::Ordering::Equal) + fee_rate_b + .partial_cmp(&fee_rate_a) + .unwrap_or(std::cmp::Ordering::Equal) }); let mut c = 0; @@ -91,7 +87,7 @@ fn select_tx_for_block(txs: Vec) -> Vec { let tx_weight = tx.weight(); if total_weight + tx_weight <= MAX_BLOCK_WEIGHT { selected_txs.push(tx); - c+=1; + c += 1; total_weight += tx_weight; if c > 2000 { break; @@ -102,7 +98,11 @@ fn select_tx_for_block(txs: Vec) -> Vec { } } - println!("Total transactions selected: {}, Total weight: {}", selected_txs.len(), total_weight); + println!( + "Total transactions selected: {}, Total weight: {}", + selected_txs.len(), + total_weight + ); selected_txs } @@ -115,10 +115,9 @@ fn main() { let br = 6_250_000_000; let cb_tx = create_coinbase_transaction(br, total_fees, "".to_owned()); - println!("cb {}", cb_tx.weight()); let mut valid_tx = vec![cb_tx]; valid_tx.append(&mut valid); - + // println!("mai{:?}", valid_tx[1].calculate_txid()); // println!("mai{:?}", valid_tx[1].calculate_wtxid()); @@ -146,10 +145,4 @@ fn main() { block.mine(difficulty_target); block.generate_output(); - - // println!("Invalid transactions: {}", invalid_transactions); - // println!("Different script types found:"); - // for script_type in script_types { - // println!("- {}", script_type); - // } } diff --git a/src/p2pkh.rs b/src/p2pkh.rs index 406e5a1..cd95bb7 100644 --- a/src/p2pkh.rs +++ b/src/p2pkh.rs @@ -1,3 +1,92 @@ -fn validate() { - -} \ No newline at end of file +extern crate hex; +extern crate ripemd160; +extern crate secp256k1; +extern crate sha2; + +use hex::FromHex; +use ripemd160::Ripemd160; +use secp256k1::{Message, PublicKey, Secp256k1, Signature}; +use sha2::{Digest, Sha256}; +use std::error::Error; + +use crate::tx::Transaction; + +// Assuming the rest of your provided code is here + +impl Transaction { + /// Validates all inputs in the transaction based on P2PKH rules. + pub fn validate_p2pkh_inputs(&self) -> Result> { + let secp = Secp256k1::new(); + + // Process each input + for input in &self.vin { + if !input.is_coinbase { + let prevout_scriptpubkey = &input.prevout.scriptpubkey; + // Extract public key hash from scriptPubKey + // Assumes scriptPubKey is formatted as "OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG" + let script_parts: Vec<&str> = prevout_scriptpubkey.split_whitespace().collect(); + if script_parts.len() < 5 + || script_parts[0] != "OP_DUP" + || script_parts[1] != "OP_HASH160" + { + return Err(Box::new(std::fmt::Error)); // Invalid scriptPubKey format + } + let expected_pubkey_hash = script_parts[2]; + + // Extract signature and public key from scriptSig + // Assumes scriptSig is formatted as " " + let script_sig_parts: Vec<&str> = input.scriptsig.split_whitespace().collect(); + if script_sig_parts.len() != 2 { + return Err(Box::new(std::fmt::Error)); // Invalid scriptSig format + } + let signature_hex = script_sig_parts[0]; + let pubkey_hex = script_sig_parts[1]; + + let pubkey_bytes = hex::decode(pubkey_hex)?; + let signature_bytes = hex::decode(signature_hex)?; + + // Verify public key hash + let pubkey_hash = Self::hash160(&pubkey_bytes); + if hex::encode(pubkey_hash) != expected_pubkey_hash { + return Ok(false); // Public key hash does not match + } + + // Verify signature + let pubkey = PublicKey::from_slice(&pubkey_bytes)?; + let signature = Signature::from_der(&signature_bytes[..signature_bytes.len() - 1])?; // remove sighash type byte + let message = + Message::from_hashed_data::(&double_sha256(&self.encode_to_vec()?)); + + if !secp.verify(&message, &signature, &pubkey).is_ok() { + return Ok(false); // Signature does not verify + } + } + } + Ok(true) + } + + /// Helper function to hash data using SHA256 followed by RIPEMD-160. + fn hash160(input: &[u8]) -> Vec { + let sha256_result = Sha256::digest(input); + let ripemd_result = Ripemd160::digest(&sha256_result); + ripemd_result.to_vec() + } + + /// Helper function to encode transaction to Vec for hashing. + fn encode_to_vec(&self) -> Result, Box> { + // Simple serialization logic to prepare the transaction data for signing + // In practice, this should handle all transaction fields properly + let mut encoded = vec![]; + encoded.extend(&self.version.to_le_bytes()); + encoded.extend(&self.locktime.to_le_bytes()); + // Encoding other parts is necessary for real applications + Ok(encoded) + } +} + +/// Helper function to compute double SHA256. +fn double_sha256(data: &[u8]) -> Vec { + let first_hash = Sha256::digest(data); + let second_hash = Sha256::digest(&first_hash); + second_hash.to_vec() +} diff --git a/src/tx.rs b/src/tx.rs index 17024ab..0935de5 100644 --- a/src/tx.rs +++ b/src/tx.rs @@ -1,4 +1,4 @@ -use std::{clone, io::Write}; +use std::io::Write; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -63,7 +63,7 @@ impl Transaction { true } - + pub fn fee(&self) -> u64 { let mut in_value = 0; for input in &self.vin { @@ -325,8 +325,6 @@ impl Transaction { fn has_witness(&self) -> bool { self.vin.iter().any(|input| input.witness.is_some()) } - - } fn serialize_varint(value: u64) -> Vec {