Malte Kraus пре 8 година
родитељ
комит
c02f887982
18 измењених фајлова са 369 додато и 142 уклоњено
  1. 19 5
      doc/index.rst
  2. 0 7
      doc/tests.rst
  3. 5 1
      miner.py
  4. BIN
      src/__init__.pyc
  5. 35 2
      src/block.py
  6. 38 18
      src/blockchain.py
  7. 37 12
      src/chainbuilder.py
  8. 15 16
      src/crypto.py
  9. 14 6
      src/merkle.py
  10. 35 13
      src/mining.py
  11. 13 5
      src/mining_strategy.py
  12. 25 4
      src/proof_of_work.py
  13. 62 22
      src/protocol.py
  14. 55 23
      src/transaction.py
  15. 2 2
      tests/test_proto.py
  16. 3 1
      tests/utils.py
  17. 1 1
      tests/verifications.py
  18. 10 4
      wallet.py

+ 19 - 5
doc/index.rst

@@ -6,18 +6,26 @@
 Welcome to blockchain's documentation!
 Welcome to blockchain's documentation!
 ======================================
 ======================================
 
 
-Contents:
 
 
-.. toctree::
-   :maxdepth: 2
+Executables
+***********
 
 
-   tests
+.. list-table::
+    :stub-columns: 1
+    :widths: 10 90
 
 
+    * - miner
+      - .. automodule:: miner
+    * - wallet
+      - .. automodule:: wallet
+
+
+Source Code Documentation
+*************************
 
 
 .. autosummary::
 .. autosummary::
     :toctree: _autosummary
     :toctree: _autosummary
 
 
-    src
     src.blockchain
     src.blockchain
     src.block
     src.block
     src.chainbuilder
     src.chainbuilder
@@ -29,6 +37,12 @@ Contents:
     src.protocol
     src.protocol
     src.transaction
     src.transaction
 
 
+Tests
+*****
+Run the tests like this:
+
+    >>> python3 -m tests.test_proto
+    >>> python3 -m tests.verifications
 
 
 
 
 Indices and tables
 Indices and tables

+ 0 - 7
doc/tests.rst

@@ -1,7 +0,0 @@
-Tests
-=====
-
-
-Run the tests like this:
-
-    >>> python3 -m tests.test_proto

+ 5 - 1
miner.py

@@ -1,10 +1,14 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 
 
+""" The executable that participates in the P2P network and optionally mines new blocks. """
+
+__all__ = []
+
 import argparse
 import argparse
+import json
 from urllib.parse import urlparse
 from urllib.parse import urlparse
 
 
 import flask
 import flask
-import json
 app = flask.Flask(__name__)
 app = flask.Flask(__name__)
 
 
 from src.crypto import Signing
 from src.crypto import Signing


+ 35 - 2
src/block.py

@@ -1,3 +1,5 @@
+""" Definitions of blocks, and the genesis block. """
+
 from datetime import datetime
 from datetime import datetime
 from binascii import hexlify, unhexlify
 from binascii import hexlify, unhexlify
 import json
 import json
@@ -5,12 +7,32 @@ import logging
 
 
 from .merkle import merkle_tree
 from .merkle import merkle_tree
 from .crypto import get_hasher
 from .crypto import get_hasher
-from .proof_of_work import verify_proof_of_work, GENESIS_DIFFICULTY
 
 
 __all__ = ['Block', 'GENESIS_BLOCK', 'GENESIS_BLOCK_HASH']
 __all__ = ['Block', 'GENESIS_BLOCK', 'GENESIS_BLOCK_HASH']
 
 
 class Block:
 class Block:
-    """ A block. """
+    """
+    A block.
+
+    :ivar hash: The hash value of this block.
+    :vartype hash: bytes
+    :ivar prev_block_hash: The hash of the previous block.
+    :vartype prev_block_hash: bytes
+    :ivar merkle_root_hash: The hash of the merkle tree root of the transactions in this block.
+    :vartype merkle_root_hash: bytes
+    :ivar time: The time when this block was created.
+    :vartype time: datetime
+    :ivar nonce: The nonce in this block that was required to achieve the proof of work.
+    :vartype nonce: int
+    :ivar height: The height (accumulated difficulty) of this block.
+    :vartype height: int
+    :ivar received_time: The time when we received this block.
+    :vartype received_time: datetime
+    :ivar difficulty: The difficulty of this block.
+    :vartype difficulty: int
+    :ivar transactions: The list of transactions in this block.
+    :vartype transactions: List[Transaction]
+    """
 
 
     def __init__(self, hash_val, prev_block_hash, time, nonce, height, received_time, difficulty, transactions, merkle_root_hash=None):
     def __init__(self, hash_val, prev_block_hash, time, nonce, height, received_time, difficulty, transactions, merkle_root_hash=None):
         self.hash = hash_val
         self.hash = hash_val
@@ -24,6 +46,7 @@ class Block:
         self.transactions = transactions
         self.transactions = transactions
 
 
     def to_json_compatible(self):
     def to_json_compatible(self):
+        """ Returns a JSON-serializable representation of this object. """
         val = {}
         val = {}
         val['hash'] = hexlify(self.hash).decode()
         val['hash'] = hexlify(self.hash).decode()
         val['prev_block_hash'] = hexlify(self.prev_block_hash).decode()
         val['prev_block_hash'] = hexlify(self.prev_block_hash).decode()
@@ -37,6 +60,7 @@ class Block:
 
 
     @classmethod
     @classmethod
     def from_json_compatible(cls, val):
     def from_json_compatible(cls, val):
+        """ Create a new block from its JSON-serializable representation. """
         from .transaction import Transaction
         from .transaction import Transaction
         return cls(unhexlify(val['hash']),
         return cls(unhexlify(val['hash']),
                    unhexlify(val['prev_block_hash']),
                    unhexlify(val['prev_block_hash']),
@@ -68,6 +92,11 @@ class Block:
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
 
 
     def get_partial_hash(self):
     def get_partial_hash(self):
+        """
+        Computes a hash over the contents of this block, except for the nonce. The proof of
+        work can use this partial hash to efficiently try different nonces. Other uses should
+        use :any:`get_hash` to get the complete hash.
+        """
         hasher = get_hasher()
         hasher = get_hasher()
         hasher.update(self.prev_block_hash)
         hasher.update(self.prev_block_hash)
         hasher.update(self.merkle_root_hash)
         hasher.update(self.merkle_root_hash)
@@ -129,7 +158,11 @@ class Block:
             return self.hash == GENESIS_BLOCK_HASH
             return self.hash == GENESIS_BLOCK_HASH
         return self.verify_difficulty() and self.verify_merkle() and self.verify_prev_block(chain) and self.verify_transactions(chain)
         return self.verify_difficulty() and self.verify_merkle() and self.verify_prev_block(chain) and self.verify_transactions(chain)
 
 
+from .proof_of_work import verify_proof_of_work, GENESIS_DIFFICULTY
+
 GENESIS_BLOCK = Block(b"", b"None", datetime(2017, 3, 3, 10, 35, 26, 922898),
 GENESIS_BLOCK = Block(b"", b"None", datetime(2017, 3, 3, 10, 35, 26, 922898),
                       0, 0, datetime.now(), GENESIS_DIFFICULTY, [], merkle_tree([]).get_hash())
                       0, 0, datetime.now(), GENESIS_DIFFICULTY, [], merkle_tree([]).get_hash())
 GENESIS_BLOCK_HASH = GENESIS_BLOCK.get_hash()
 GENESIS_BLOCK_HASH = GENESIS_BLOCK.get_hash()
 GENESIS_BLOCK.hash = GENESIS_BLOCK_HASH
 GENESIS_BLOCK.hash = GENESIS_BLOCK_HASH
+
+from .blockchain import Blockchain

+ 38 - 18
src/blockchain.py

@@ -1,16 +1,27 @@
+""" Definition of block chains. """
+
 __all__ = ['Blockchain']
 __all__ = ['Blockchain']
 import logging
 import logging
+from typing import List, Dict, Optional
 
 
 class Blockchain:
 class Blockchain:
-    def __init__(self, blocks: list):
+    """
+    A block chain: a ordrered list of blocks.
+
+    :ivar blocks: The blocks in this chain, oldest first.
+    :vartype blocks: List[Block]
+    :ivar block_indices: A dictionary allowing efficient lookup of the index of a block in this
+                         block chain by its hash value.
+    :vartype block_indices: Dict[bytes, int]
+    """
+
+    def __init__(self, blocks: 'List[Block]'):
         self.blocks = blocks
         self.blocks = blocks
         assert self.blocks[0].height == 0
         assert self.blocks[0].height == 0
         self.block_indices = {block.hash: i for (i, block) in enumerate(blocks)}
         self.block_indices = {block.hash: i for (i, block) in enumerate(blocks)}
 
 
-    def get_transaction_by_hash(self, hash_val: bytes):
-        """
-        Returns a transaction from its hash, or None.
-        """
+    def get_transaction_by_hash(self, hash_val: bytes) -> 'Optional[Transaction]':
+        """ Returns a transaction from its hash, or None. """
         # TODO: build a hash table with this info
         # TODO: build a hash table with this info
         for block in self.blocks[::-1]:
         for block in self.blocks[::-1]:
             for trans in block.transactions:
             for trans in block.transactions:
@@ -18,11 +29,16 @@ class Blockchain:
                     return trans
                     return trans
         return None
         return None
 
 
-    def is_coin_still_valid(self, transaction_input: 'TransactionInput', prev_block: 'Block'=None):
+    def is_coin_still_valid(self, transaction_input: 'TransactionInput',
+                            prev_block: 'Block'=None) -> bool:
         """
         """
         Validates that the coins that were sent in the transaction identified
         Validates that the coins that were sent in the transaction identified
         by `transaction_hash_val` to the nth receiver (n=output_idx) have not been
         by `transaction_hash_val` to the nth receiver (n=output_idx) have not been
         spent before the given block.
         spent before the given block.
+
+        :param transaction_input: The coin to check.
+        :param prev_block: The youngest block in this block chain that should be considered for
+                           the validation.
         """
         """
         if prev_block is None:
         if prev_block is None:
             prev_block = self.head
             prev_block = self.head
@@ -35,40 +51,41 @@ class Blockchain:
                     return False
                     return False
         return True
         return True
 
 
-    def get_block_by_hash(self, hash_val):
-        """
-        Returns a block by its hash value, or None if it cannot be found.
-        """
+    def get_block_by_hash(self, hash_val: bytes) -> 'Optional[Block]':
+        """ Returns a block by its hash value, or None if it cannot be found. """
         idx = self.block_indices.get(hash_val)
         idx = self.block_indices.get(hash_val)
         if idx is None:
         if idx is None:
             return None
             return None
         return self.blocks[idx]
         return self.blocks[idx]
 
 
-    def verify_all_transactions(self):
-        """
-        Verify the transactions in all blocks in this chain.
-        """
+    def verify_all_transactions(self) -> bool:
+        """ Verify the transactions in all blocks in this chain. """
         for block in self.blocks:
         for block in self.blocks:
             if not block.verify_transactions(self):
             if not block.verify_transactions(self):
                 return False
                 return False
         return True
         return True
 
 
-    def verify_all(self):
+    def verify_all(self) -> bool:
         """ Verify all blocks in this block chain. """
         """ Verify all blocks in this block chain. """
         return all(block.verify(self) for block in self.blocks)
         return all(block.verify(self) for block in self.blocks)
 
 
     @property
     @property
     def head(self):
     def head(self):
-        """ The head of this block chain. """
+        """
+        The head of this block chain.
+
+        :rtype: Block
+        """
         return self.blocks[-1]
         return self.blocks[-1]
 
 
-    def compute_difficulty(self):
+    def compute_difficulty(self) -> int:
         """ Compute the desired difficulty for the next block. """
         """ Compute the desired difficulty for the next block. """
         # TODO: dynamic calculation
         # TODO: dynamic calculation
         # TODO: verify difficulty in new blocks
         # TODO: verify difficulty in new blocks
         return self.head.difficulty
         return self.head.difficulty
 
 
-    def compute_blockreward(self, prev_block):
+    def compute_blockreward(self, prev_block: 'Block') -> int:
+        """ Compute the block reward that is expected for the block following `prev_block`. """
         assert prev_block is not None
         assert prev_block is not None
         reward = 1000
         reward = 1000
         l = self.block_indices[prev_block.hash]
         l = self.block_indices[prev_block.hash]
@@ -76,3 +93,6 @@ class Blockchain:
             l = l - 10000
             l = l - 10000
             reward = reward // 2
             reward = reward // 2
         return reward
         return reward
+
+from .block import Block
+from .transaction import TransactionInput, Transaction

+ 37 - 12
src/chainbuilder.py

@@ -1,14 +1,36 @@
-from .block import GENESIS_BLOCK, GENESIS_BLOCK_HASH
-from .blockchain import Blockchain
+"""
+The chain builder maintains the current longest confirmed (primary) block chain as well as one
+candidate for an even longer chain that it attempts to download and verify.
+"""
 
 
 import threading
 import threading
+from typing import List, Dict, Callable, Optional
+
+from .block import GENESIS_BLOCK, GENESIS_BLOCK_HASH
+from .blockchain import Blockchain
 
 
 __all__ = ['ChainBuilder']
 __all__ = ['ChainBuilder']
 
 
 class ChainBuilder:
 class ChainBuilder:
     """
     """
-    Maintains the current longest confirmed (primary) block chain as well as one candidate for an even longer
-    chain that it attempts to download and verify.
+    Maintains the current longest confirmed (primary) block chain as well as one candidate for an
+    even longer chain that it attempts to download and verify.
+
+    :ivar primary_block_chain: The longest fully validated block chain we know of.
+    :vartype primary_block_chain: Blockchain
+    :ivar unconfirmed_block_chain: A list of blocks (ordered young to old) that might lead to a
+                                   new primary block chain after all predecessors are fully
+                                   downloaded and verified.
+    :vartype unconfirmed_transactions: List[Block]
+    :ivar block_cache: A cache of received blocks, not bound to any one specific block chain.
+    :vartype block_cache: Dict[bytes, Block]
+    :ivar unconfirmed_transactions: Known transactions that are not part of the primary block chain.
+    :vartype unconfirmed_transactions: Dict[bytes, Transaction]
+    :ivar chain_change_handlers: Event handlers that get called when we find out about a new primary
+                                 block chain.
+    :vartype chain_change_handlers: List[Callable]
+    :ivar protocol: The protocol instance used by this chain builder.
+    :vartype protocol: Protocol
     """
     """
 
 
     def __init__(self, protocol):
     def __init__(self, protocol):
@@ -34,11 +56,12 @@ class ChainBuilder:
             self._thread_id = threading.get_ident()
             self._thread_id = threading.get_ident()
         assert self._thread_id == threading.get_ident()
         assert self._thread_id == threading.get_ident()
 
 
-    def block_request_received(self, block_hash):
+    def block_request_received(self, block_hash: bytes) -> 'Optional[Block]':
+        """ Our event handler for block requests in the protocol. """
         self._assert_thread_safety()
         self._assert_thread_safety()
         return self.block_cache.get(block_hash)
         return self.block_cache.get(block_hash)
 
 
-    def new_transaction_received(self, transaction):
+    def new_transaction_received(self, transaction: 'Transaction'):
         """ Event handler that is called by the network layer when a transaction is received. """
         """ Event handler that is called by the network layer when a transaction is received. """
         self._assert_thread_safety()
         self._assert_thread_safety()
         hash_val = transaction.get_hash()
         hash_val = transaction.get_hash()
@@ -47,10 +70,8 @@ class ChainBuilder:
             self.unconfirmed_transactions[hash_val] = transaction
             self.unconfirmed_transactions[hash_val] = transaction
             self.protocol.broadcast_transaction(transaction)
             self.protocol.broadcast_transaction(transaction)
 
 
-    def _new_primary_block_chain(self, chain):
-        """
-        Does all the housekeeping that needs to be done when a new longest chain is found.
-        """
+    def _new_primary_block_chain(self, chain: 'Blockchain'):
+        """ Does all the housekeeping that needs to be done when a new longest chain is found. """
         self._assert_thread_safety()
         self._assert_thread_safety()
         self.primary_block_chain = chain
         self.primary_block_chain = chain
         todelete = set()
         todelete = set()
@@ -82,10 +103,11 @@ class ChainBuilder:
         else:
         else:
             self.protocol.send_block_request(unc[-1].prev_block_hash)
             self.protocol.send_block_request(unc[-1].prev_block_hash)
 
 
-    def new_block_received(self, block):
+    def new_block_received(self, block: 'Block'):
         """ Event handler that is called by the network layer when a block is received. """
         """ Event handler that is called by the network layer when a block is received. """
         self._assert_thread_safety()
         self._assert_thread_safety()
-        if not block.verify_difficulty() or not block.verify_merkle() or block.hash in self.block_cache:
+        if not block.verify_difficulty() or not block.verify_merkle() or \
+                block.hash in self.block_cache:
             return
             return
         self.block_cache[block.hash] = block
         self.block_cache[block.hash] = block
 
 
@@ -101,3 +123,6 @@ class ChainBuilder:
             self.unconfirmed_block_chain = [block]
             self.unconfirmed_block_chain = [block]
             self.get_next_unconfirmed_block()
             self.get_next_unconfirmed_block()
 
 
+from .protocol import Protocol
+from .block import Block
+from .transaction import Transaction

+ 15 - 16
src/crypto.py

@@ -1,4 +1,4 @@
-""" Cryptographic primitives. """
+""" Generic functions for the cryptographic primitives used in this project. """
 
 
 from Crypto.Signature import PKCS1_PSS
 from Crypto.Signature import PKCS1_PSS
 from Crypto.Hash import SHA512
 from Crypto.Hash import SHA512
@@ -7,10 +7,7 @@ from Crypto.PublicKey import RSA
 __all__ = ['get_hasher', 'Signing', 'MAX_HASH']
 __all__ = ['get_hasher', 'Signing', 'MAX_HASH']
 
 
 def get_hasher():
 def get_hasher():
-    """
-    Returns a object that you can use for hashing. Currently SHA512, swap it
-    out for something if you feel like it!
-    """
+    """ Returns a object that you can use for hashing, compatible to the `hashlib` interface. """
     return SHA512.new()
     return SHA512.new()
 
 
 
 
@@ -19,38 +16,40 @@ MAX_HASH = (1 << 512) - 1 # the largest possible hash value
 
 
 class Signing:
 class Signing:
     """
     """
-    Functionality for creating and verifying signatures, and their
-    public/private keys.
+    Functionality for creating and verifying signatures, and their public/private keys.
+
+    :param byte_repr: The bytes serialization of a public key.
     """
     """
 
 
-    def __init__(self, byte_repr):
+    def __init__(self, byte_repr: bytes):
         self.rsa = RSA.importKey(byte_repr)
         self.rsa = RSA.importKey(byte_repr)
 
 
-    def verify_sign(self, hashed_value, signature):
-        """ Verify a signature for a already hashed value and a public key. """
+    def verify_sign(self, hashed_value: bytes, signature: bytes) -> bool:
+        """ Verify a signature for an already hashed value and a public key. """
         ver = PKCS1_PSS.new(self.rsa)
         ver = PKCS1_PSS.new(self.rsa)
         h = get_hasher()
         h = get_hasher()
         h.update(hashed_value)
         h.update(hashed_value)
         return ver.verify(h, signature)
         return ver.verify(h, signature)
 
 
-    def sign(self, hashed_value):
-        """ Sign a hashed value with a private key. """
+    def sign(self, hashed_value: bytes) -> bytes:
+        """ Sign a hashed value with this private key. """
         signer = PKCS1_PSS.new(self.rsa)
         signer = PKCS1_PSS.new(self.rsa)
         h = get_hasher()
         h = get_hasher()
         h.update(hashed_value)
         h.update(hashed_value)
         return signer.sign(h)
         return signer.sign(h)
 
 
     @classmethod
     @classmethod
-    def generatePrivateKey(cls):
+    def generate_private_key(cls):
+        """ Generate a new private key. """
         return Signing(RSA.generate(3072).exportKey())
         return Signing(RSA.generate(3072).exportKey())
 
 
-    def as_bytes(self, include_priv=False):
-        """ bytes representation of this key. """
+    def as_bytes(self, include_priv: bool=False) -> bytes:
+        """ Serialize this key to a `bytes` value. """
         if include_priv:
         if include_priv:
             return self.rsa.exportKey()
             return self.rsa.exportKey()
         else:
         else:
             return self.rsa.publickey().exportKey()
             return self.rsa.publickey().exportKey()
 
 
-    def __eq__(self, other):
+    def __eq__(self, other: 'Signing'):
         # TODO: it's possible that the same key has multiple different representations
         # TODO: it's possible that the same key has multiple different representations
         return self.as_bytes() == other.as_bytes()
         return self.as_bytes() == other.as_bytes()

+ 14 - 6
src/merkle.py

@@ -1,14 +1,22 @@
+""" Functionality for creating a Merkle tree. """
+
 import json
 import json
 from binascii import hexlify
 from binascii import hexlify
-from treelib import Node, Tree
 from itertools import zip_longest
 from itertools import zip_longest
 
 
+from treelib import Node, Tree
+
 from .crypto import get_hasher
 from .crypto import get_hasher
 
 
 __all__ = ['merkle_tree', 'MerkleNode']
 __all__ = ['merkle_tree', 'MerkleNode']
 
 
 class MerkleNode:
 class MerkleNode:
-    """ A hash tree node, pointing to a leaf value or another node. """
+    """
+    A hash tree node, pointing to a leaf value or another node.
+
+    :ivar v1: The first child of this node.
+    :ivar v2: The second child of this node.
+    """
 
 
     def __init__(self, v1, v2):
     def __init__(self, v1, v2):
         self.v1 = v1
         self.v1 = v1
@@ -16,7 +24,7 @@ class MerkleNode:
         self.v2 = v2
         self.v2 = v2
         self.v2_hash = b'' if v2 is None else v2.get_hash()
         self.v2_hash = b'' if v2 is None else v2.get_hash()
 
 
-    def get_hash(self):
+    def get_hash(self) -> bytes:
         """ Compute the hash of this node. """
         """ Compute the hash of this node. """
         hasher = get_hasher()
         hasher = get_hasher()
         hasher.update(self.v1_hash)
         hasher.update(self.v1_hash)
@@ -40,11 +48,11 @@ class MerkleNode:
         self._get_tree(tree, None)
         self._get_tree(tree, None)
         return str(tree)
         return str(tree)
 
 
-def merkle_tree(values):
+def merkle_tree(values: list) -> MerkleNode:
     """
     """
-    Constructs a merkle tree from a list of values.
+    Constructs a Merkle tree from a list of values.
 
 
-    All values need to support a method get_hash().
+    All `values` need to support a method `get_hash()`.
     """
     """
 
 
     if not values:
     if not values:

+ 35 - 13
src/mining.py

@@ -1,31 +1,49 @@
-from .proof_of_work import ProofOfWork
-from .chainbuilder import ChainBuilder
-from . import mining_strategy
-from .protocol import Protocol
+""" Functionality for mining new blocks. """
 
 
 from threading import Thread
 from threading import Thread
 from time import sleep
 from time import sleep
+from typing import Optional
+
+from .proof_of_work import ProofOfWork
+from .chainbuilder import ChainBuilder
+from . import mining_strategy
 
 
 __all__ = ['Miner']
 __all__ = ['Miner']
 
 
+def _yield():
+    sleep(0)
+
 class Miner:
 class Miner:
+    """
+    Management of a background thread that mines for new blocks.
+
+    :ivar proto: The protocol where newly mined blocks will be sent to.
+    :vartype proto: Protocol
+    :ivar chainbuilder: The chain builder used by :any:`start_mining` to find the primary chain.
+    :vartype chainbuilder: ChainBuilder
+    :ivar _cur_miner: The proof of work we're currently working on.
+    :vartype _cur_miner: Optional[ProofOfWork]
+    :ivar reward_pubkey: The public key to which mining fees and block rewards should be sent to.
+    :vartype reward_pubkey: Signing
+    """
+
     def __init__(self, proto, reward_pubkey):
     def __init__(self, proto, reward_pubkey):
         self.proto = proto
         self.proto = proto
         self.chainbuilder = ChainBuilder(proto)
         self.chainbuilder = ChainBuilder(proto)
         self.chainbuilder.chain_change_handlers.append(self.start_mining)
         self.chainbuilder.chain_change_handlers.append(self.start_mining)
-        self.is_mining = False
-        self.cur_miner = None
+        self._cur_miner = None
         self.reward_pubkey = reward_pubkey
         self.reward_pubkey = reward_pubkey
         Thread(target=self._miner_thread, daemon=True).start()
         Thread(target=self._miner_thread, daemon=True).start()
 
 
     def _miner_thread(self):
     def _miner_thread(self):
         while True:
         while True:
-            miner = self.cur_miner
+            miner = self._cur_miner
             if miner is None:
             if miner is None:
-                sleep(0)
+                # TODO: condition variable
+                _yield()
             else:
             else:
                 block = miner.run()
                 block = miner.run()
-                self.cur_miner = None
+                self._cur_miner = None
                 if block is not None:
                 if block is not None:
                     self.proto.broadcast_primary_block(block)
                     self.proto.broadcast_primary_block(block)
 
 
@@ -36,10 +54,14 @@ class Miner:
         chain = self.chainbuilder.primary_block_chain
         chain = self.chainbuilder.primary_block_chain
         transactions = self.chainbuilder.unconfirmed_transactions.values()
         transactions = self.chainbuilder.unconfirmed_transactions.values()
         block = mining_strategy.create_block(chain, transactions, self.reward_pubkey)
         block = mining_strategy.create_block(chain, transactions, self.reward_pubkey)
-        self.cur_miner = ProofOfWork(block)
+        self._cur_miner = ProofOfWork(block)
 
 
     def stop_mining(self):
     def stop_mining(self):
         """ Stop all mining. """
         """ Stop all mining. """
-        if self.cur_miner:
-            self.cur_miner.abort()
-            self.cur_miner = None
+        if self._cur_miner:
+            self._cur_miner.abort()
+            self._cur_miner = None
+
+from .protocol import Protocol
+from .chainbuilder import ChainBuilder
+from .crypto import Signing

+ 13 - 5
src/mining_strategy.py

@@ -1,16 +1,21 @@
-from datetime import datetime
+""" Defines the contents of newly mined blocks. """
+
+from typing import List
 
 
 from .block import Block
 from .block import Block
-from .merkle import merkle_tree
 from .transaction import Transaction, TransactionTarget
 from .transaction import Transaction, TransactionTarget
 
 
-from Crypto.PublicKey import RSA
-
 __all__ = ['create_block']
 __all__ = ['create_block']
 
 
-def create_block(blockchain, unconfirmed_transactions, reward_pubkey):
+def create_block(blockchain: 'Blockchain', unconfirmed_transactions: 'List[Transaction]',
+                 reward_pubkey: 'Signing') -> 'Block':
     """
     """
     Creates a new block that can be mined.
     Creates a new block that can be mined.
+
+    :param blockchain: The blockchain on top of which the new block should fit.
+    :param unconfirmed_transactions: The transactions that should be considered for inclusion in
+                                     this block.
+    :param reward_pubkey: The key that should receive block rewards.
     """
     """
     head = blockchain.head
     head = blockchain.head
 
 
@@ -26,3 +31,6 @@ def create_block(blockchain, unconfirmed_transactions, reward_pubkey):
     transactions.add(trans)
     transactions.add(trans)
 
 
     return Block.create(blockchain, list(transactions))
     return Block.create(blockchain, list(transactions))
+
+from .blockchain import Blockchain
+from .crypto import Signing

+ 25 - 4
src/proof_of_work.py

@@ -1,25 +1,44 @@
+""" Implementation and verification of the proof of work. """
+
+from typing import Optional
+
 from .crypto import MAX_HASH
 from .crypto import MAX_HASH
 
 
 __all__ = ['verify_proof_of_work', 'GENESIS_DIFFICULTY', 'ProofOfWork']
 __all__ = ['verify_proof_of_work', 'GENESIS_DIFFICULTY', 'ProofOfWork']
 
 
-def verify_proof_of_work(block):
+def verify_proof_of_work(block: 'Block'):
     """ Verify the proof of work on a block. """
     """ Verify the proof of work on a block. """
     return int.from_bytes(block.hash, byteorder='little', signed=False) > block.difficulty
     return int.from_bytes(block.hash, byteorder='little', signed=False) > block.difficulty
 
 
 GENESIS_DIFFICULTY = MAX_HASH - (MAX_HASH // 10000)
 GENESIS_DIFFICULTY = MAX_HASH - (MAX_HASH // 10000)
+""" The difficulty of the genesis block. """
 
 
 class ProofOfWork:
 class ProofOfWork:
-    def __init__(self, block):
+    """
+    Allows performing (and aborting) a proof of work.
+
+    :ivar stopped: A flag that is set to `True` to abort the `run` operation.
+    :vartype stopped: bool
+    :ivar block: The block on which the proof of work should be performed.
+    :param block: The block on which the proof of work should be performed.
+    :vartype block: Block
+    :ivar success: A flag indication whether the proof of work was successful or not.
+    :vartype success: bool
+    """
+
+    def __init__(self, block: 'Block'):
         self.stopped = False
         self.stopped = False
         self.block = block
         self.block = block
         self.success = False
         self.success = False
 
 
     def abort(self):
     def abort(self):
+        """ Aborts execution of this proof of work. """
         self.stopped = True
         self.stopped = True
 
 
-    def run(self):
+    def run(self) -> 'Optional[Block]':
         """
         """
-        Perform the proof of work on a block, until cond.stopped becomes True or the proof of work was sucessful.
+        Perform the proof of work on a block, until `stopped` becomes True or the proof of
+        work was successful.
         """
         """
         hasher = self.block.get_partial_hash()
         hasher = self.block.get_partial_hash()
         while not self.stopped:
         while not self.stopped:
@@ -31,3 +50,5 @@ class ProofOfWork:
                     return self.block
                     return self.block
                 self.block.nonce += 1
                 self.block.nonce += 1
         return None
         return None
+
+from .block import Block

+ 62 - 22
src/protocol.py

@@ -1,39 +1,44 @@
+""" Implementation of the P2P protocol. """
+
 import json
 import json
-from enum import Enum
 import socket
 import socket
 import socketserver
 import socketserver
-from threading import Thread, Lock
 import logging
 import logging
+from threading import Thread, Lock
 from queue import Queue, PriorityQueue
 from queue import Queue, PriorityQueue
 from binascii import unhexlify, hexlify
 from binascii import unhexlify, hexlify
+from typing import Callable, List
 
 
-from .block import Block
-from .transaction import Transaction
 
 
-__all__ = ['Protocol', 'PeerConnection']
+__all__ = ['Protocol', 'PeerConnection', 'MAX_PEERS', 'HELLO_MSG']
 
 
 MAX_PEERS = 10
 MAX_PEERS = 10
+""" The maximum number of peers that we connect to."""
+
 HELLO_MSG = b"bl0ckch41n"
 HELLO_MSG = b"bl0ckch41n"
+""" The hello message two peers use to make sure they are speaking the same protocol. """
 
 
+# TODO: set this centrally
 logging.basicConfig(level=logging.INFO)
 logging.basicConfig(level=logging.INFO)
-
 socket.setdefaulttimeout(30)
 socket.setdefaulttimeout(30)
 
 
 class PeerConnection:
 class PeerConnection:
     """
     """
     Handles the low-level socket connection to one other peer.
     Handles the low-level socket connection to one other peer.
     :ivar peer_addr: The self-reported address one can use to connect to this peer.
     :ivar peer_addr: The self-reported address one can use to connect to this peer.
+    :ivar param: The self-reported address one can use to connect to this peer.
     :ivar _sock_addr: The address our socket is or will be connected to.
     :ivar _sock_addr: The address our socket is or will be connected to.
     :ivar socket: The socket object we use to communicate with our peer.
     :ivar socket: The socket object we use to communicate with our peer.
+    :param sock: A socket object we should use to communicate with our peer.
     :ivar proto: The Protocol instance this peer connection belongs to.
     :ivar proto: The Protocol instance this peer connection belongs to.
     :ivar is_connected: A boolean indicating the current connection status.
     :ivar is_connected: A boolean indicating the current connection status.
     :ivar outgoing_msgs: A queue of messages we want to send to this peer.
     :ivar outgoing_msgs: A queue of messages we want to send to this peer.
     """
     """
 
 
-    def __init__(self, peer_addr, proto, socket=None):
+    def __init__(self, peer_addr: tuple, proto: 'Protocol', sock: socket.socket=None):
         self.peer_addr = None
         self.peer_addr = None
         self._sock_addr = peer_addr
         self._sock_addr = peer_addr
-        self.socket = socket
+        self.socket = sock
         self.proto = proto
         self.proto = proto
         self.is_connected = False
         self.is_connected = False
         self.outgoing_msgs = Queue()
         self.outgoing_msgs = Queue()
@@ -69,7 +74,7 @@ class PeerConnection:
         Thread(target=self.reader_thread, daemon=True).start()
         Thread(target=self.reader_thread, daemon=True).start()
         self.writer_thread()
         self.writer_thread()
 
 
-    def close_on_error(fn):
+    def close_on_error(fn: Callable):
         """ A decorator that closes both threads if one dies. """
         """ A decorator that closes both threads if one dies. """
 
 
         def wrapper(self, *args, **kwargs):
         def wrapper(self, *args, **kwargs):
@@ -83,6 +88,8 @@ class PeerConnection:
         return wrapper
         return wrapper
 
 
     def close(self):
     def close(self):
+        """ Closes the connection to this peer. """
+
         if not self.is_connected:
         if not self.is_connected:
             return
             return
 
 
@@ -95,7 +102,7 @@ class PeerConnection:
             self.proto.peers.remove(self)
             self.proto.peers.remove(self)
         self.socket.close()
         self.socket.close()
 
 
-    def send_msg(self, msg_type, msg_param):
+    def send_msg(self, msg_type: str, msg_param):
         """
         """
         Sends a message to this peer.
         Sends a message to this peer.
 
 
@@ -122,7 +129,9 @@ class PeerConnection:
 
 
     @close_on_error
     @close_on_error
     def reader_thread(self):
     def reader_thread(self):
-        """ The reader thread reads messages from the socket and passes them to the protocol to handle. """
+        """
+        The reader thread reads messages from the socket and passes them to the protocol to handle.
+        """
         while True:
         while True:
             buf = b""
             buf = b""
             while not buf or buf[-1] != ord('\n'):
             while not buf or buf[-1] != ord('\n'):
@@ -158,8 +167,16 @@ class PeerConnection:
 
 
 
 
 class SocketServer(socketserver.TCPServer):
 class SocketServer(socketserver.TCPServer):
+    """
+    A TCP socketserver that calls does not close the connections on its own.
+    """
+
     allow_reuse_address = True
     allow_reuse_address = True
+    """ Make sure the server can be restarted without delays. """
+
     def serve_forever_bg(self):
     def serve_forever_bg(self):
+        """ Runs the server forever in a background thread. """
+
         logging.info("listening on %s", self.server_address)
         logging.info("listening on %s", self.server_address)
         Thread(target=self.serve_forever, daemon=True).start()
         Thread(target=self.serve_forever, daemon=True).start()
 
 
@@ -173,9 +190,19 @@ class Protocol:
     """
     """
     Manages connections to our peers. Allows sending messages to them and has event handlers
     Manages connections to our peers. Allows sending messages to them and has event handlers
     for handling messages from other peers.
     for handling messages from other peers.
+
+    :ivar block_receive_handlers: Event handlers that get called when a new block is received.
+    :vartype block_receive_handlers: List[Callable]
+    :ivar trans_receive_handlers: Event handlers that get called when a new transaction is received.
+    :vartype trans_receive_handlers: List[Callable]
+    :ivar block_request_handlers: Event handlers that get called when a block request is received.
+    :vartype block_request_handlers: List[Callable]
+    :ivar peers: The peers we are connected to.
+    :vartype peers: List[PeerConnection]
     """
     """
 
 
-    def __init__(self, bootstrap_peers, primary_block, listen_port=0, listen_addr=""):
+    def __init__(self, bootstrap_peers: 'List[tuple]',
+                 primary_block: 'Block', listen_port: int=0, listen_addr: str=""):
         """
         """
         :param bootstrap_peers: network addresses of peers where we bootstrap the P2P network from
         :param bootstrap_peers: network addresses of peers where we bootstrap the P2P network from
         :param primary_block: the head of the primary block chain
         :param primary_block: the head of the primary block chain
@@ -198,7 +225,8 @@ class Protocol:
             def handle(self):
             def handle(self):
                 logging.info("connection from peer %s", repr(self.client_address))
                 logging.info("connection from peer %s", repr(self.client_address))
                 if len(self.proto.peers) > MAX_PEERS:
                 if len(self.proto.peers) > MAX_PEERS:
-                    logging.warn("too many connections: rejecting peer %s", repr(self.client_address))
+                    logging.warning("too many connections: rejecting peer %s",
+                                    repr(self.client_address))
                     self.request.close()
                     self.request.close()
                     # TODO: separate limits for incoming and outgoing connections
                     # TODO: separate limits for incoming and outgoing connections
                     return
                     return
@@ -213,38 +241,47 @@ class Protocol:
 
 
         Thread(target=self._main_thread, daemon=True).start()
         Thread(target=self._main_thread, daemon=True).start()
 
 
-    def broadcast_primary_block(self, block: Block):
+    def broadcast_primary_block(self, block: 'Block'):
         """ Notifies all peers and local listeners of a new primary block. """
         """ Notifies all peers and local listeners of a new primary block. """
         self._primary_block = block.to_json_compatible()
         self._primary_block = block.to_json_compatible()
         for peer in self.peers:
         for peer in self.peers:
             peer.send_msg("block", self._primary_block)
             peer.send_msg("block", self._primary_block)
         self.received('block', self._primary_block, None, 0)
         self.received('block', self._primary_block, None, 0)
 
 
-    def broadcast_transaction(self, trans: Transaction):
+    def broadcast_transaction(self, trans: 'Transaction'):
         """ Notifies all peers and local listeners of a new transaction. """
         """ Notifies all peers and local listeners of a new transaction. """
         for peer in self.peers:
         for peer in self.peers:
             peer.send_msg("transaction", trans.to_json_compatible())
             peer.send_msg("transaction", trans.to_json_compatible())
 
 
-    def received(self, msg_type, msg_param, peer, prio=1):
-        """ Called by a PeerConnection when a new message was received. """
+    def received(self, msg_type: str, msg_param, peer: PeerConnection, prio: int=1):
+        """
+        Called by a PeerConnection when a new message was received.
+
+        :param msg_type: The message type identifier.
+        :param msg_param: The JSON-compatible object that was received.
+        :param peer: The peer who sent us the message.
+        :param prio: The priority of the message. (Should be lower for locally generated events
+                     than for remote events, to make sure self-mined blocks get handled first.)
+        """
         with self._callback_counter_lock:
         with self._callback_counter_lock:
             counter = self._callback_counter + 1
             counter = self._callback_counter + 1
             self._callback_counter = counter
             self._callback_counter = counter
         self._callback_queue.put((prio, counter, msg_type, msg_param, peer))
         self._callback_queue.put((prio, counter, msg_type, msg_param, peer))
 
 
     def _main_thread(self):
     def _main_thread(self):
+        """ The main loop of the one thread where all incoming events are handled. """
         while True:
         while True:
             _, _, msg_type, msg_param, peer = self._callback_queue.get()
             _, _, msg_type, msg_param, peer = self._callback_queue.get()
             try:
             try:
                 getattr(self, 'received_' + msg_type)(msg_param, peer)
                 getattr(self, 'received_' + msg_type)(msg_param, peer)
-            except Exception:
+            except:
                 logging.exception("unhandled exception in event handler")
                 logging.exception("unhandled exception in event handler")
                 try:
                 try:
                     peer.close()
                     peer.close()
                 except OSError:
                 except OSError:
                     pass
                     pass
 
 
-    def received_peer(self, peer_addr, _):
+    def received_peer(self, peer_addr: list, _):
         """ Information about a peer has been received. """
         """ Information about a peer has been received. """
 
 
         peer_addr = tuple(peer_addr)
         peer_addr = tuple(peer_addr)
@@ -258,7 +295,7 @@ class Protocol:
         # TODO: if the other peer also just learned of us, we can end up with two connections (one from each direction)
         # TODO: if the other peer also just learned of us, we can end up with two connections (one from each direction)
         self.peers.append(PeerConnection(peer_addr, self))
         self.peers.append(PeerConnection(peer_addr, self))
 
 
-    def received_getblock(self, block_hash, peer):
+    def received_getblock(self, block_hash: str, peer: PeerConnection):
         """ We received a request for a new block from a certain peer. """
         """ We received a request for a new block from a certain peer. """
         for handler in self.block_request_handlers:
         for handler in self.block_request_handlers:
             block = handler(unhexlify(block_hash))
             block = handler(unhexlify(block_hash))
@@ -266,12 +303,12 @@ class Protocol:
                 peer.send_msg("block", block.to_json_compatible())
                 peer.send_msg("block", block.to_json_compatible())
                 break
                 break
 
 
-    def received_block(self, block, _):
+    def received_block(self, block: dict, _):
         """ Someone sent us a block. """
         """ Someone sent us a block. """
         for handler in self.block_receive_handlers:
         for handler in self.block_receive_handlers:
             handler(Block.from_json_compatible(block))
             handler(Block.from_json_compatible(block))
 
 
-    def received_transaction(self, transaction, _):
+    def received_transaction(self, transaction: dict, _):
         """ Someone sent us a transaction. """
         """ Someone sent us a transaction. """
         for handler in self.trans_receive_handlers:
         for handler in self.trans_receive_handlers:
             handler(Transaction.from_json_compatible(transaction))
             handler(Transaction.from_json_compatible(transaction))
@@ -280,3 +317,6 @@ class Protocol:
         """ Sends a request for a block to all our peers. """
         """ Sends a request for a block to all our peers. """
         for peer in self.peers:
         for peer in self.peers:
             peer.send_msg("getblock", hexlify(block_hash).decode())
             peer.send_msg("getblock", hexlify(block_hash).decode())
+
+from .block import Block
+from .transaction import Transaction

+ 55 - 23
src/transaction.py

@@ -1,38 +1,62 @@
+""" Defines transactions and their inputs and outputs. """
+
+import logging
 from collections import namedtuple
 from collections import namedtuple
 from binascii import hexlify, unhexlify
 from binascii import hexlify, unhexlify
-import logging
-
-from .blockchain import Blockchain
-from .block import Block
+from typing import List, Set
 
 
 from .crypto import get_hasher, Signing
 from .crypto import get_hasher, Signing
 
 
 __all__ = ['TransactionTarget', 'TransactionInput', 'Transaction']
 __all__ = ['TransactionTarget', 'TransactionInput', 'Transaction']
 
 
 TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
 TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
-""" The recipient of a transaction ('coin'). """
+"""
+The recipient of a transaction ('coin').
+
+:ivar recipient_pk: The public key of the recipient.
+:vartype recipient_pk: Signing
+:ivar amount: The amount sent to `recipient_pk`.
+:vartype amount: int
+"""
 
 
 TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_idx"])
 TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_idx"])
-""" One transaction input (pointer to 'coin'). """
+"""
+One transaction input (pointer to 'coin').
 
 
-from typing import List
-SigningListType = List[Signing]
+:ivar transaction_hash: The hash of the transaction that sent money to the sender.
+:vartype transaction_hash: bytes
+:ivar output_idx: The index into `Transaction.targets` of the `transaction_hash`.
+:vartype output_idx: int
+"""
 
 
 
 
 class Transaction:
 class Transaction:
-
-    def __init__(self, inputs: list, targets: list, signatures:list=None, iv:bytes=None):
+    """
+    A transaction.
+
+    :ivar inputs: The inputs of this transaction. Empty in the case of block reward transactions.
+    :vartype inputs: List[TransactionInput]
+    :ivar targets: The targets of this transaction.
+    :vartype targets: List[TransactionTarget]
+    :ivar signatures: Signatures for each input. Must be in the same order as `inputs`. Filled
+                      by :func:`sign`.
+    :vartype signatures: List[bytes]
+    :ivar iv: The IV is used to differentiate block reward transactions.  These have no inputs and
+              therefore would otherwise hash to the same value, when the target is identical.
+              Reuse of IVs leads to inaccessible coins.
+    :vartype iv: bytes
+    """
+
+    def __init__(self, inputs: 'List[TransactionInput]', targets: 'List[TransactionTarget]',
+                 signatures: 'List[bytes]'=None, iv: bytes=None):
         self.inputs = inputs
         self.inputs = inputs
         self.targets = targets
         self.targets = targets
         self.signatures = signatures or []
         self.signatures = signatures or []
-        # The IV is used to differentiate block reward transactions.
-        # These have no inputs and therefore would otherwise hash to the
-        # same value, when the target is identical.
-        # Reuse of IVs leads to inaccessible coins.
         self.iv = iv
         self.iv = iv
         self._hash = None
         self._hash = None
 
 
     def to_json_compatible(self):
     def to_json_compatible(self):
+        """ Returns a JSON-serializable representation of this object. """
         val = {}
         val = {}
         val['inputs'] = []
         val['inputs'] = []
         for inp in self.inputs:
         for inp in self.inputs:
@@ -55,6 +79,7 @@ class Transaction:
 
 
     @classmethod
     @classmethod
     def from_json_compatible(cls, obj: dict):
     def from_json_compatible(cls, obj: dict):
+        """ Creates a new object of this class, from a JSON-serializable representation. """
         inputs = []
         inputs = []
         for inp in obj['inputs']:
         for inp in obj['inputs']:
             inputs.append(TransactionInput(unhexlify(inp['transaction_hash']),
             inputs.append(TransactionInput(unhexlify(inp['transaction_hash']),
@@ -62,7 +87,7 @@ class Transaction:
         targets = []
         targets = []
         for targ in obj['targets']:
         for targ in obj['targets']:
             targets.append(TransactionTarget(Signing(unhexlify(targ['recipient_pk'])),
             targets.append(TransactionTarget(Signing(unhexlify(targ['recipient_pk'])),
-                                           int(targ['amount'])))
+                                             int(targ['amount'])))
         signatures = []
         signatures = []
         for sig in obj['signatures']:
         for sig in obj['signatures']:
             signatures.append(unhexlify(sig))
             signatures.append(unhexlify(sig))
@@ -71,7 +96,7 @@ class Transaction:
         return cls(inputs, targets, signatures, iv)
         return cls(inputs, targets, signatures, iv)
 
 
 
 
-    def get_hash(self):
+    def get_hash(self) -> bytes:
         """ Hash this transaction. Returns raw bytes. """
         """ Hash this transaction. Returns raw bytes. """
         if self._hash is None:
         if self._hash is None:
             h = get_hasher()
             h = get_hasher()
@@ -86,7 +111,7 @@ class Transaction:
             self._hash = h.digest()
             self._hash = h.digest()
         return self._hash
         return self._hash
 
 
-    def sign(self, private_keys: SigningListType):
+    def sign(self, private_keys: 'List[Signing]'):
         """
         """
         Sign this transaction with the given private keys. The private keys need
         Sign this transaction with the given private keys. The private keys need
         to be in the same order as the inputs.
         to be in the same order as the inputs.
@@ -94,7 +119,7 @@ class Transaction:
         for private_key in private_keys:
         for private_key in private_keys:
             self.signatures.append(private_key.sign(self.get_hash()))
             self.signatures.append(private_key.sign(self.get_hash()))
 
 
-    def _verify_signatures(self, chain: Blockchain):
+    def _verify_signatures(self, chain: 'Blockchain'):
         """ Verify that all inputs are signed and the signatures are valid. """
         """ Verify that all inputs are signed and the signatures are valid. """
         if len(self.signatures) != len(self.inputs):
         if len(self.signatures) != len(self.inputs):
             logging.warning("wrong number of signatures")
             logging.warning("wrong number of signatures")
@@ -105,7 +130,7 @@ class Transaction:
                 return False
                 return False
         return True
         return True
 
 
-    def _verify_single_sig(self, sig: str, inp: TransactionInput, chain: Blockchain):
+    def _verify_single_sig(self, sig: str, inp: TransactionInput, chain: 'Blockchain') -> bool:
         """ Verifies the signature on a single input. """
         """ Verifies the signature on a single input. """
         trans = chain.get_transaction_by_hash(inp.transaction_hash)
         trans = chain.get_transaction_by_hash(inp.transaction_hash)
         if trans is None:
         if trans is None:
@@ -118,7 +143,8 @@ class Transaction:
         return True
         return True
 
 
 
 
-    def _verify_single_spend(self, chain: Blockchain, other_trans: set, prev_block: Block):
+    def _verify_single_spend(self, chain: 'Blockchain', other_trans: set,
+                             prev_block: 'Block') -> bool:
         """ Verifies that all inputs have not been spent yet. """
         """ Verifies that all inputs have not been spent yet. """
         inp_set = set(self.inputs)
         inp_set = set(self.inputs)
         if len(self.inputs) != len(inp_set):
         if len(self.inputs) != len(inp_set):
@@ -126,7 +152,8 @@ class Transaction:
             return False
             return False
         other_inputs = {i for t in other_trans for i in t.inputs}
         other_inputs = {i for t in other_trans for i in t.inputs}
         if other_inputs.intersection(inp_set):
         if other_inputs.intersection(inp_set):
-            logging.warning("Transaction may not spend the same coin as another transaction in the same block.")
+            logging.warning("Transaction may not spend the same coin as another transaction in the"
+                            " same block.")
             return False
             return False
 
 
         for i in self.inputs:
         for i in self.inputs:
@@ -135,6 +162,11 @@ class Transaction:
                 return False
                 return False
         return True
         return True
 
 
-    def verify(self, chain: Blockchain, other_trans:set, prev_block:Block=None):
+    def verify(self, chain: 'Blockchain', other_trans: 'Set[Transaction]',
+               prev_block: 'Block'=None) -> bool:
         """ Verifies that this transaction is completely valid. """
         """ Verifies that this transaction is completely valid. """
-        return self._verify_single_spend(chain, other_trans, prev_block) and self._verify_signatures(chain)
+        return self._verify_single_spend(chain, other_trans, prev_block) and \
+               self._verify_signatures(chain)
+
+from .blockchain import Blockchain
+from .block import Block

+ 2 - 2
tests/test_proto.py

@@ -6,7 +6,7 @@ from src.transaction import Transaction, TransactionInput, TransactionTarget
 
 
 from time import sleep
 from time import sleep
 
 
-reward_key = Signing.generatePrivateKey()
+reward_key = Signing.generate_private_key()
 
 
 proto1 = Protocol([], GENESIS_BLOCK, 1337)
 proto1 = Protocol([], GENESIS_BLOCK, 1337)
 proto2 = Protocol([("127.0.0.1", 1337)], GENESIS_BLOCK, 1338)
 proto2 = Protocol([("127.0.0.1", 1337)], GENESIS_BLOCK, 1338)
@@ -18,7 +18,7 @@ miner1.start_mining()
 
 
 
 
 sleep(5)
 sleep(5)
-target_key = Signing.generatePrivateKey()
+target_key = Signing.generate_private_key()
 reward_trans = miner2.chainbuilder.primary_block_chain.blocks[20].transactions[0]
 reward_trans = miner2.chainbuilder.primary_block_chain.blocks[20].transactions[0]
 trans_in = TransactionInput(reward_trans.get_hash(), 0)
 trans_in = TransactionInput(reward_trans.get_hash(), 0)
 trans_targ = TransactionTarget(target_key, reward_trans.targets[0].amount)
 trans_targ = TransactionTarget(target_key, reward_trans.targets[0].amount)

+ 3 - 1
tests/utils.py

@@ -1,5 +1,7 @@
 import src.proof_of_work
 import src.proof_of_work
+import src.block
 src.proof_of_work.verify_proof_of_work = lambda b: True
 src.proof_of_work.verify_proof_of_work = lambda b: True
+src.block.verify_proof_of_work = src.proof_of_work.verify_proof_of_work
 
 
 from src.block import *
 from src.block import *
 from src.blockchain import *
 from src.blockchain import *
@@ -23,7 +25,7 @@ def trans_as_input(trans, out_idx=0):
 
 
 def new_trans(old_trans, out_idx=0):
 def new_trans(old_trans, out_idx=0):
     amount = old_trans.targets[out_idx].amount
     amount = old_trans.targets[out_idx].amount
-    key = Signing.generatePrivateKey()
+    key = Signing.generate_private_key()
     trans = Transaction([trans_as_input(old_trans, out_idx)],
     trans = Transaction([trans_as_input(old_trans, out_idx)],
                         [TransactionTarget(key, amount)])
                         [TransactionTarget(key, amount)])
     trans.sign([old_trans.targets[out_idx].recipient_pk])
     trans.sign([old_trans.targets[out_idx].recipient_pk])

+ 1 - 1
tests/verifications.py

@@ -8,7 +8,7 @@ def trans_test(fn):
 
 
     gen_chain = Blockchain([GENESIS_BLOCK])
     gen_chain = Blockchain([GENESIS_BLOCK])
     assert gen_chain.verify_all()
     assert gen_chain.verify_all()
-    key = Signing.generatePrivateKey()
+    key = Signing.generate_private_key()
     reward_trans = Transaction([], [TransactionTarget(key, gen_chain.compute_blockreward(gen_chain.head))])
     reward_trans = Transaction([], [TransactionTarget(key, gen_chain.compute_blockreward(gen_chain.head))])
     chain = extend_blockchain(gen_chain, [reward_trans])
     chain = extend_blockchain(gen_chain, [reward_trans])
     try:
     try:

+ 10 - 4
wallet.py

@@ -1,10 +1,15 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 
 
+"""
+The wallet allows a user to query account balance, send money, and get status information about a
+miner.
+"""
+
+__all__ = []
+
 import argparse
 import argparse
 import requests
 import requests
 
 
-from binascii import hexlify, unhexlify
-
 from src.blockchain import Blockchain
 from src.blockchain import Blockchain
 from src.block import Block
 from src.block import Block
 from src.transaction import Transaction
 from src.transaction import Transaction
@@ -28,8 +33,9 @@ def main():
     parser = argparse.ArgumentParser(description="Wallet.")
     parser = argparse.ArgumentParser(description="Wallet.")
     parser.add_argument("--miner-port", default=40203, type=int,
     parser.add_argument("--miner-port", default=40203, type=int,
                         help="The RPC port of the miner to connect to.")
                         help="The RPC port of the miner to connect to.")
-    parser.add_argument("--show-transactions", type=argparse.FileType("rb"), default=[], action="append",
-                        help="Shows all transactions involving the public key stored in the specified file.")
+    parser.add_argument("--show-transactions", type=argparse.FileType("rb"), default=[],
+                        action="append", help="Shows all transactions involving the public key "
+                                              "stored in the specified file.")
     parser.add_argument("--show-network", action="store_true", default=False,
     parser.add_argument("--show-network", action="store_true", default=False,
                         help="Prints networking information about the miner.")
                         help="Prints networking information about the miner.")
     args = parser.parse_args()
     args = parser.parse_args()