-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implementation of the dict format of accessing transaction data
- Loading branch information
Showing
8 changed files
with
184 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,68 @@ | ||
import hashlib | ||
import time | ||
|
||
def mine_block(valid_transactions) -> str: | ||
""" | ||
Mine all blocks from all the valid transactions | ||
Mine a block by including the valid transactions and finding a valid block hash. | ||
:param valid_transactions: all the validated transactions | ||
:return: str | ||
:param valid_transactions: List of valid transactions to include in the block. | ||
:return: str representing the block data in the required format. | ||
""" | ||
# TODO: implement block mining here | ||
# Initialize the block header | ||
version = 0x20000000 # Version number (0x20000000 for Bitcoin) | ||
prev_block_hash = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000") # Placeholder for the previous block hash | ||
merkle_root = calculate_merkle_root(valid_transactions) | ||
time_stamp = int(time.time()) # Current Unix timestamp | ||
bits = 0x1d00ffff # Difficulty target (0x1d00ffff for the given target) | ||
nonce = 0 # Initial nonce value | ||
|
||
# Construct the block header | ||
block_header = ( | ||
version.to_bytes(4, byteorder="little") + | ||
prev_block_hash + | ||
merkle_root.digest() + | ||
time_stamp.to_bytes(4, byteorder="little") + | ||
bits.to_bytes(4, byteorder="little") + | ||
nonce.to_bytes(4, byteorder="little") | ||
) | ||
|
||
# Mine the block by finding a valid hash | ||
target = 0x00000000ffff0000000000000000000000000000000000000000000000000000 | ||
while True: | ||
block_hash = hashlib.sha256(hashlib.sha256(block_header).digest()).digest() | ||
if int.from_bytes(block_hash, byteorder="little") < target: | ||
break | ||
nonce += 1 | ||
block_header = ( | ||
block_header[:68] + | ||
nonce.to_bytes(4, byteorder="little") | ||
) | ||
|
||
# Construct the block data | ||
block_data = f"{block_header.hex()}\n" # Block header | ||
block_data += f"{calculate_coinbase_transaction(valid_transactions).hex()}\n" # Coinbase transaction | ||
block_data += "\n".join(tx.txid for tx in valid_transactions) # Transaction IDs | ||
|
||
block_data = "Block header\nCoinbase transaction\n" | ||
for tx in valid_transactions: | ||
block_data += f"{tx.txid}\n" | ||
return block_data | ||
|
||
def calculate_merkle_root(transactions): | ||
""" | ||
Calculate the Merkle root for the given transactions. | ||
:param transactions: List of transactions to include in the Merkle tree. | ||
:return: Hashlib object representing the Merkle root. | ||
""" | ||
# TODO: Implement the Merkle tree calculation logic | ||
# For now, we'll use a placeholder value | ||
return hashlib.sha256(b"placeholder_merkle_root") | ||
|
||
def calculate_coinbase_transaction(transactions): | ||
""" | ||
Calculate the coinbase transaction for the given block. | ||
:param transactions: List of transactions to include in the block. | ||
:return: Bytes representing the coinbase transaction. | ||
""" | ||
# TODO: Implement the coinbase transaction calculation logic | ||
# For now, we'll use a placeholder value | ||
return b"placeholder_coinbase_transaction" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,86 +1,147 @@ | ||
import asyncio | ||
|
||
from bitcoin.core import MAX_BLOCK_SIZE | ||
from cryptography.hazmat.primitives.asymmetric import ec | ||
from cryptography.exceptions import InvalidSignature | ||
from cryptography.hazmat.primitives import hashes | ||
from cryptography.hazmat.backends import default_backend | ||
from typing import Tuple | ||
from bitcoinlib.transactions import Transaction as BitcoinTransaction | ||
|
||
|
||
def validate_transaction(transaction): | ||
async def validate_transaction(transaction) -> Tuple[bool, str]: | ||
""" | ||
Validate a single transaction. | ||
:param transaction: The transaction to validate. | ||
:return: A tuple containing a boolean indicating whether the transaction is valid, and a string message. | ||
""" | ||
# Check if the transaction has inputs and outputs | ||
if not transaction.vin: | ||
return False, "Transaction has no inputs" | ||
if not transaction.vout: | ||
return False, "Transaction has no outputs" | ||
|
||
# Initialize the total input and output values | ||
total_input_value = 0 | ||
total_output_value = 0 | ||
# Iterate over the inputs | ||
for tx_input in transaction.vin: | ||
# Extract the public key from the input witness | ||
if not tx_input.witness: | ||
return False, f"Input {tx_input.txid}:{tx_input.vout} has no witness" | ||
|
||
public_key_bytes = bytes.fromhex(tx_input.witness[-1]) | ||
|
||
# Construct the public key object | ||
public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key_bytes) | ||
# Handle coinbase transactions separately | ||
if tx_input.is_coinbase: | ||
# Coinbase transactions have specific rules that need to be validated | ||
# Check if the coinbase transaction follows the correct format | ||
# and has a valid block height and coinbase value | ||
if not validate_coinbase_transaction(tx_input): | ||
return False, f"Invalid coinbase transaction input: {tx_input}" | ||
else: | ||
# Extract the public key from the input witness | ||
if not tx_input.witness: | ||
return False, f"Input {tx_input.txid}:{tx_input.vout} has no witness" | ||
|
||
public_key_bytes = bytes.fromhex(tx_input.witness[-1]) | ||
|
||
# Construct the public key object | ||
public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), public_key_bytes) | ||
|
||
# Extract the signature from the input witness | ||
signature_bytes = b"".join(bytes.fromhex(witness_item) for witness_item in tx_input.witness[:-1]) | ||
|
||
# Get the transaction data that was signed for this input | ||
tx_input_data = get_tx_input_data(transaction, tx_input) | ||
|
||
# Define the signature algorithm | ||
signature_algorithm = ec.EllipticCurveSignatureAlgorithm(hashes.SHA256()) | ||
|
||
try: | ||
# Verify the signature using the public key, signature, and transaction data | ||
public_key.verify( | ||
signature_bytes, | ||
tx_input_data, | ||
signature_algorithm | ||
) | ||
except InvalidSignature: | ||
return False, f"Invalid signature for input {tx_input.txid}:{tx_input.vout}" | ||
|
||
# Add the input value to the total input value | ||
total_input_value += tx_input.prevout.value | ||
|
||
# Validate the input script | ||
try: | ||
bitcoin_tx = BitcoinTransaction.from_dict(transaction) | ||
bitcoin_tx.verify_input_signature(tx_input.vout) | ||
except Exception as e: | ||
return False, f"Invalid input script for {tx_input.txid}:{tx_input.vout}: {str(e)}" | ||
|
||
# Iterate over the outputs | ||
for tx_output in transaction.vout: | ||
# Add the output value to the total output value | ||
total_output_value += tx_output.value | ||
|
||
# Validate the output script | ||
try: | ||
bitcoin_tx = BitcoinTransaction.from_dict(transaction) | ||
bitcoin_tx.verify_output_script(tx_output.scriptpubkey_asm) | ||
except Exception as e: | ||
return False, f"Invalid output script: {str(e)}" | ||
|
||
# Extract the signature from the input witness | ||
signature_bytes = b"".join(bytes.fromhex(witness_item) for witness_item in tx_input.witness[:-1]) | ||
# Check if the total input value is greater than or equal to the total output value | ||
if total_input_value < total_output_value: | ||
return False, "Total input value is less than total output value" | ||
|
||
# Get the transaction data that was signed for this input | ||
tx_input_data = get_tx_input_data(transaction, tx_input) | ||
# Calculate the transaction fee | ||
transaction_fee = total_input_value - total_output_value | ||
|
||
# Define the signature algorithm | ||
signature_algorithm = ec.EllipticCurveSignatureAlgorithm(hashes.SHA256()) | ||
# Validate transaction fee according to the fee rules | ||
if transaction_fee < 0: | ||
return False, "Transaction fee cannot be negative" | ||
|
||
try: | ||
# Verify the signature using the public key, signature, and transaction data | ||
public_key.verify( | ||
signature_bytes, | ||
tx_input_data, | ||
signature_algorithm | ||
) | ||
except InvalidSignature: | ||
return False, f"Invalid signature for input {tx_input.txid}:{tx_input.vout}" | ||
# Check if the transaction size exceeds the maximum block size | ||
transaction_size = sum(len(tx_input.scriptsig) for tx_input in transaction.vin) + \ | ||
sum(len(tx_output.scriptpubkey) for tx_output in transaction.vout) | ||
if transaction_size > MAX_BLOCK_SIZE: | ||
return False, "Transaction size exceeds the maximum block size" | ||
|
||
return True, "Transaction is valid" | ||
|
||
|
||
def get_tx_input_data(transaction, tx_input): | ||
def validate_coinbase_transaction(tx_input) -> bool: | ||
""" | ||
Helper function to construct the transaction data that was signed for a given input. | ||
This implementation assumes the transaction version is 1 or higher. | ||
Validate a coinbase transaction input. | ||
:param tx_input: The coinbase transaction input to validate. | ||
:return: True if the coinbase transaction input is valid, False otherwise. | ||
""" | ||
tx_data = b"" | ||
tx_data += transaction.version.to_bytes(4, byteorder="little") | ||
tx_data += tx_input.prevout.scriptpubkey.encode() | ||
tx_data += tx_input.prevout.value.to_bytes(8, byteorder="little") | ||
tx_data += tx_input.sequence.to_bytes(4, byteorder="little") | ||
tx_data += transaction.locktime.to_bytes(4, byteorder="little") | ||
|
||
return tx_data | ||
|
||
# Implement your coinbase transaction validation logic here | ||
# For example, you could check if the block height and coinbase value are valid | ||
# based on the current network rules and block subsidies. | ||
# This is just a placeholder function, you need to implement the actual validation logic. | ||
return True | ||
|
||
async def validate_transactions(transactions) -> list: | ||
""" | ||
gather and return all valid transactions | ||
:param transactions: all transactions, both valid and invalid | ||
:return: list | ||
Validate a list of transactions asynchronously. | ||
:param transactions: A list of transactions to validate. | ||
:return: A list of valid transactions. | ||
""" | ||
async_tasks = [] | ||
for tx in transactions: | ||
async_task = validate_transaction(tx) | ||
async_tasks.append(async_task) | ||
async_tasks = [validate_transaction(tx) for tx in transactions] | ||
|
||
try: | ||
validation_results = await asyncio.gather(*async_tasks) | ||
except Exception as e: | ||
print(f"An error occurred during transaction validation, Error: {e}") | ||
return [] | ||
|
||
valid_transactions = [] | ||
# Check and store valid transactions | ||
for tx, is_valid in zip(transactions, validation_results): | ||
if is_valid: | ||
valid_transactions.append(tx) | ||
|
||
valid_transactions = [tx for tx, (is_valid, _) in zip(transactions, validation_results) if is_valid] | ||
|
||
return valid_transactions | ||
|
||
def get_tx_input_data(transaction, tx_input): | ||
""" | ||
Helper function to construct the transaction data that was signed for a given input. | ||
This implementation assumes the transaction version is 1 or higher. | ||
""" | ||
tx_data = b"" | ||
tx_data += transaction.version.to_bytes(4, byteorder="little") | ||
tx_data += tx_input.prevout.scriptpubkey.encode() | ||
tx_data += tx_input.prevout.value.to_bytes(8, byteorder="little") | ||
tx_data += tx_input.sequence.to_bytes(4, byteorder="little") | ||
tx_data += transaction.locktime.to_bytes(4, byteorder="little") | ||
|
||
return tx_data |