class Transaction(base.Base): ID = base.UInt128() inputs = base.SequenceField(TransactionInput) outputs = base.SequenceField(TransactionOutput) signature = base.UInt512() transaction_type = base.UInt8() def __init__(self, **kwargs): kwargs.setdefault("ID", int(uuid.uuid4())) kwargs.setdefault( "transaction_type", 0) # 0 indicates normal transaction. 1 is for reward transaction self.blockchain = BlockChain() super().__init__(**kwargs) def get_fee(self): self.verify() if self.transaction_type == 1: # Reward transaction - balance comes out of thin air return 0 bl = BlockChain() input_amount = sum( bl.get_output_from_input(inp).amount for inp in self.inputs) output_amount = sum(out.amount for out in self.outputs) if output_amount > input_amount: raise AmountError( f"Total fee is negative '{output_amount - input_amount}'") return input_amount - output_amount def verify(self): for input in self.inputs: input.verify() self.verify_transaction_signature() def verify_transaction_signature(self): if self.transaction_type == 1: return wallet = BlockChain().get_output_from_input(self.inputs[0]).wallet pubkey = ECC.import_key(bytes.fromhex(wallet)) verifier = DSS.new(pubkey, "fips-186-3") signature = self.signature self.signature = 0 hash_ = SHA256.new(self.serialize()) self.signature = signature try: verifier.verify(hash_, signature.to_bytes(64, "little")) except ValueError as check_fail: raise SecretError("Invalid transaction signature") def sign_transaction(self, wallets): """ Sign transaction with private key # based on https://github.com/adilmoujahid/blockchain-python-tutorial/blob/master/blockchain_client/blockchain_client.py """ from .wallet import Wallet if isinstance(wallets, (Sequence, Iterable)): wallets = {wallet.public_key: wallet for wallet in wallets} elif isinstance(wallets, Wallet): wallets = {wallets.public_key: wallets} for input in self.inputs: output = BlockChain().get_output_from_input(input) try: input.sign(wallets[output.wallet]) except KeyError as error: raise WalletError from error # Use the wallet used for the first input to sign the whole transaction if self.inputs: signer_wallet = BlockChain().get_output_from_input( self.inputs[0]).wallet wallet = wallets[signer_wallet] elif self.transaction_type == 1: # we have input if we are a reward transaction wallet = next(iter(wallets.values())) else: raise TransactionError("Trying to sign transaction with no input") private_key = ECC.import_key(bytes.fromhex(wallet.private_key)) signer = DSS.new(private_key, 'fips-186-3') self.signature = 0 hash_ = SHA256.new(self.serialize()) self.signature = int.from_bytes(signer.sign(hash_), "little")
class Block(base.Base): number = base.UInt32() previous_block = base.UInt256() transactions = base.SequenceField(Transaction) nonce = base.UInt128() difficulty = base.UInt256() def __init__(self, **kwargs): kwargs.setdefault("difficulty", registry["network_difficulty"]) super().__init__(**kwargs) self.state = "new" def prepare(self): """Prepares block acording to current chain so that it gets ready to be mined. If no transactions have been manually added prior to calling this, will pick all transactions in the blockchain pool. """ if not self.transactions: self.fetch_transactions() previous_block = BlockChain()[-1] self.previous_block = int.from_bytes(previous_block.hash(), "big") self.number = previous_block.number + 1 self.make_reward() def fetch_transactions(self): bl = BlockChain() self.transactions.fill(bl.transaction_pool.values()) def set_previous(self, block): self.previous_block = sha256(block.serialize()).digest() def make_reward(self): from .wallet import Wallet reward = registry["coin_reward"] target_wallet = registry["miner_public_key"] server_wallet = Wallet.from_data(registry["server_wallet"]) fees = sum(transaction.get_fee() for transaction in self.transactions) out1 = TransactionOutput(wallet=target_wallet, amount=reward * TOKENMULTIPLIER, extra_data="Mining Reward") out2 = TransactionOutput(wallet=target_wallet, amount=fees, extra_data="Mining Fees") tr = Transaction() tr.transaction_type = 1 # Reward transaction tr.outputs.append(out1) tr.outputs.append(out2) tr.sign_transaction(server_wallet) self.transactions.append(tr) def check_difficulty(self): difficulty = (2**256 - 1) // self.difficulty return int.from_bytes(self.hash(), "big") <= difficulty def hash(self): # This is what makes one "mining attempt" hash_ = SHA256.new(self.serialize()) return hash_.digest() def __hash__(self): # Python coerces the return value of this to Int64 return self.hash() def verify(self): first = True for transaction in reversed(self.transactions): if transaction.transaction_type == 1 and not first: raise BlockError( "Reward transaction not as last of transactions list") transaction.verify() first = False if not self.check_difficulty(): raise BlockError("Block hash do not match its set difficulty")