Skip to content

Commit

Permalink
cleanup and add solution
Browse files Browse the repository at this point in the history
  • Loading branch information
justcode740 committed May 3, 2024
1 parent 9c53ca4 commit 1a47953
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 29 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
ripemd = "0.1.3"
ripemd160 = "0.10.0"
35 changes: 35 additions & 0 deletions SOLUTION.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 1 addition & 3 deletions src/block.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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());
Expand Down
29 changes: 11 additions & 18 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::collections::HashSet;
use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;

mod block;
mod coinbase;
mod p2pkh;
mod tx;
mod validate;
use tx::Transaction;
Expand Down Expand Up @@ -56,14 +56,8 @@ fn get_tx() -> Vec<Transaction> {
}
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);
}
Expand All @@ -82,7 +76,9 @@ fn select_tx_for_block(txs: Vec<Transaction>) -> Vec<Transaction> {
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;

Expand All @@ -91,7 +87,7 @@ fn select_tx_for_block(txs: Vec<Transaction>) -> Vec<Transaction> {
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;
Expand All @@ -102,7 +98,11 @@ fn select_tx_for_block(txs: Vec<Transaction>) -> Vec<Transaction> {
}
}

println!("Total transactions selected: {}, Total weight: {}", selected_txs.len(), total_weight);
println!(
"Total transactions selected: {}, Total weight: {}",
selected_txs.len(),
total_weight
);
selected_txs
}

Expand All @@ -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());

Expand Down Expand Up @@ -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);
// }
}
95 changes: 92 additions & 3 deletions src/p2pkh.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,92 @@
fn validate() {

}
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<bool, Box<dyn Error>> {
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 <pubkeyhash> 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 "<signature> <pubkey>"
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::<Sha256>(&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<u8> {
let sha256_result = Sha256::digest(input);
let ripemd_result = Ripemd160::digest(&sha256_result);
ripemd_result.to_vec()
}

/// Helper function to encode transaction to Vec<u8> for hashing.
fn encode_to_vec(&self) -> Result<Vec<u8>, Box<dyn Error>> {
// 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<u8> {
let first_hash = Sha256::digest(data);
let second_hash = Sha256::digest(&first_hash);
second_hash.to_vec()
}
6 changes: 2 additions & 4 deletions src/tx.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{clone, io::Write};
use std::io::Write;

use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -63,7 +63,7 @@ impl Transaction {

true
}

pub fn fee(&self) -> u64 {
let mut in_value = 0;
for input in &self.vin {
Expand Down Expand Up @@ -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<u8> {
Expand Down

0 comments on commit 1a47953

Please sign in to comment.