Exemplo n.º 1
0
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")
Exemplo n.º 2
0
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")