Jelajahi Sumber

transactions, mining, verifications done until testing

Malte Kraus 8 tahun lalu
induk
melakukan
cc3435ce56
9 mengubah file dengan 178 tambahan dan 25 penghapusan
  1. 14 5
      block.py
  2. 36 6
      blockchain.py
  3. 39 9
      chainbuilder.py
  4. 2 0
      crypto.py
  5. 42 0
      mining.py
  6. 5 1
      mining_strategy.py
  7. 31 0
      proof_of_work.py
  8. 2 0
      protocol.py
  9. 7 4
      transaction.py

+ 14 - 5
block.py

@@ -1,7 +1,7 @@
 from merkle import merkle_tree
 from crypto import get_hasher
-
-GENESIS_BLOCK_HASH = b"" # TODO
+from proof_of_work import verify_proof_of_work, GENESIS_DIFFICULTY
+from datetime import datetime
 
 class Block:
     """ A block. """
@@ -21,13 +21,17 @@ class Block:
         """ Verify that the merkle root hash is correct for the transactions in this block. """
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
 
-    def get_hash(self):
-        """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
+    def get_partial_hash(self):
         hasher = get_hasher()
         hasher.update(self.prev_block_hash)
         hasher.update(self.merkle_root_hash)
         hasher.update(self.time)
         hasher.update(self.difficulty)
+        return hasher
+
+    def get_hash(self):
+        """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
+        hasher = self.get_partial_hash()
         hasher.update(self.nonce) # for mining we want to get a copy of hasher here
         return hasher.digest()
 
@@ -36,7 +40,7 @@ class Block:
         # TODO: move this some better place
         if self.hash != self.get_hash():
             return False
-        return int.from_bytes(self.hash, byteorder='little', signed=False) > self.difficulty
+        return verify_proof_of_work(self)
 
     def verify_prev_block(self, chain):
         """ Verify the previous block pointer points to a valid block in the given block chain. """
@@ -54,3 +58,8 @@ class Block:
         if self.height == 0:
             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)
+
+GENESIS_BLOCK = Block(b"", b"None", datetime(2017, 3, 3, 10, 35, 26, 922898),
+                      0, 0, datetime.now(), GENESIS_DIFFICULTY, merkle_tree([]).get_hash(), [])
+GENESIS_BLOCK_HASH = GENESIS_BLOCK.get_hash()
+GENESIS_BLOCK.hash = GENESIS_BLOCK_HASH

+ 36 - 6
blockchain.py

@@ -1,27 +1,57 @@
 
-
 class Blockchain:
+    def __init__(self, blocks):
+        self.blocks = blocks
+        assert self.blocks[0].height == 0
+        self.blocks_by_hash = {block.hash: block for block in blocks}
+
     def get_transaction_by_hash(self, hash_val):
         """
         Returns a transaction from its hash, or None.
         """
-        pass
+        for block in self.blocks[prev_block.height::-1]:
+            for trans in block.transactions:
+                if trans.get_hash() == hash_val:
+                    return trans
+        return None
 
-    def is_coin_still_valid(self, transaction_hash_val, output_idx):
+    def is_coin_still_valid(self, transaction_input, prev_block=None):
         """
         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
-        spent yet.
+        spent before the given block.
         """
-        pass
+        if prev_block is None:
+            prev_block = self.head
+
+        assert self.blocks[prev_block.height] is prev_block
+        for block in self.blocks[prev_block.height::-1]:
+            for trans in block.transactions:
+                if transaction_input in trans.inputs:
+                    return False
+        return True
 
     def get_block_by_hash(self, hash_val):
         """
         Returns a block by its hash value, or None if it cannot be found.
         """
-        pass
+        return self.blocks_by_hash.get(hash_val)
+
+    def verify_all_transactions(self):
+        """
+        Verify the transactions in all blocks in this chain.
+        """
+        for block in self.blocks:
+            block.verify_transactions(self)
 
 
     @getter
     def head(self):
+        """ The head of this block chain. """
         return self.blocks[-1]
+
+    def compute_difficulty(self):
+        """ Compute the desired difficulty for the next block. """
+        # TODO: dynamic calculation
+        # TODO: verify difficulty in new blocks
+        return self.head.difficulty

+ 39 - 9
chainbuilder.py

@@ -1,30 +1,60 @@
+from block import GENESIS_BLOCK, GENESIS_BLOCK_HASH
+from blockchain import Blockchain
+
 class ChainBuilder:
-    confirmed_block_chain = None
-    unconfirmed_block_chain = []
+    """
+    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.
+    """
+
+    def __init__(self):
+        self.primary_block_chain = Blockchain([GENESIS_BLOCK])
+        self.unconfirmed_block_chain = []
 
-    block_cache = {}
-    unconfirmed_transactions = {}
+        self.block_cache = { GENESIS_BLOCK_HASH: GENESIS_BLOCK }
+        self.unconfirmed_transactions = {}
 
+        self.chain_change_handlers = []
 
     def new_transaction_received(self, transaction):
+        """ Event handler that is called by the network layer when a transaction is received. """
         hash_val = transaction.get_hash()
         if block_chain.get_transaction_by_hash(hash_val) is None:
            unconfirmed_transactions[hash_val] = transaction
 
+    def _new_primary_block_chain(self, chain):
+        """
+        Does all the housekeeping that needs to be done when a new longest chain is found.
+        """
+        self.primary_block_chain = chain
+        for (hash_val, trans) in self.unconfirmed_transactions:
+            if not trans.verify(chain):
+                del self.unconfirmed_transactions[hash_val]
+
+        for handler in self.chain_change_handlers:
+            handler()
+
     def get_next_unconfirmed_block(self):
-        """  """
+        """
+        Helper function that tries to complete the unconfirmed chain,
+        possibly asking the network layer for more blocks.
+        """
         unc = self.unconfirmed_block_chain
         while unc[-1].prev_block_hash in self.block_cache:
             unc.append(self.block_cache[unc[-1].prev_block_hash])
 
         if unc[-1].height == 0:
-            # TODO: create and verify this block chain
+            chain = Blockchain(unc[::-1])
+            if chain.verify_all_transactions():
+                self._new_primary_block_chain(chain)
             self.unconfirmed_block_chain = []
         else:
             # TODO: download next block
+            pass
 
     def new_block_received(self, block):
-        if not block.verify_difficulty() or block.hash in self.block_cache:
+        """ Event handler that is called by the network layer when a block is received. """
+        if not block.verify_difficulty() or not block.verify_merkle() or block.hash in self.block_cache:
             return
         self.block_cache[block.hash] = block
 
@@ -35,10 +65,10 @@ class ChainBuilder:
             else if self.unconfirmed_block_chain[0].hash == block.prev_block_hash:
                 self.unconfirmed_block_chain.insert(0, block)
 
-            if block.height > self.unconfirmed_block_chain[0].height and block.height > self.confirmed_block_chain.get_height():
+            if block.height > self.unconfirmed_block_chain[0].height and block.height > self.primary_block_chain.get_height():
                 self.unconfirmed_block_chain = [block]
                 self.get_next_unconfirmed_block()
-        elif block.height > self.confirmed_block_chain.get_height():
+        elif block.height > self.primary_block_chain.get_height():
             self.unconfirmed_block_chain = [block]
             self.get_next_unconfirmed_block()
 

+ 2 - 0
crypto.py

@@ -7,6 +7,8 @@ def get_hasher():
     """ Returns a object that you can use for hashing. Currently SHA512, swap it out for something if you feel like it! """
     return SHA512.new()
 
+MAX_HASH = (1 << 512) - 1
+
 def verify_sign(hashed_value, signature, pub_key):
     """ Verify a signature for a already hashed value and a public key. """
     ver = PKCS1_v1_5.new(pub_key)

+ 42 - 0
mining.py

@@ -0,0 +1,42 @@
+from proof_of_work import ProofOfWork
+from chainbuilder import ChainBuilder
+from threading import Thread
+import mining_strategy
+from time import sleep
+
+class Miner:
+    def __init__(self):
+        self.chainbuilder = ChainBuilder()
+        self.chainbuilder.chain_change_handlers.append(self.chain_changed)
+        self.is_mining = False
+        self.cur_miner = None
+        Thread(target=self._miner_thread, daemon=True).start()
+
+    def _miner_thread(self):
+        while True:
+            miner = self.cur_miner
+            if miner is None:
+                sleep(1)
+            else:
+                success = miner.run()
+
+    def start_mining(self, block):
+        """ Start mining on a new block. """
+        if self.cur_miner:
+            self.cur_miner.abort()
+        self.cur_miner = ProofOfWork(block)
+
+    def stop_mining(self):
+        """ Stop all mining. """
+        if self.cur_miner:
+            self.cur_miner.abort()
+            self.cur_miner = None
+
+    def chain_changed(self):
+        """
+        Used as a event handler on the chainbuilder. It is called when the
+        primary chain changes.
+        """
+        chain = self.chainbuilder.primary_block_chain
+        block = mining_strategy.create_block(chain, self.chainbuilder.unconfirmed_transactions)
+        self.start_mining(block)

+ 5 - 1
mining_strategy.py

@@ -2,8 +2,12 @@ from block import Block
 from datetime import datetime
 from merkle import merkle_tree
 
-def create_block(blockchain, unconfirmed_transactions, difficulty):
+def create_block(blockchain, unconfirmed_transactions):
+    """
+    Creates a new block that can be mined.
+    """
     tree = merkle_tree(unconfirmed_transactions)
     head = blockchain.head
+    difficulty = blockchain.compute_difficulty()
     return Block(None, head.hash, datetime.now(), 0, head.height + difficulty,
                  None, difficulty, tree.get_hash(), unconfirmed_transactions)

+ 31 - 0
proof_of_work.py

@@ -0,0 +1,31 @@
+from crypto import MAX_HASH
+
+def verify_proof_of_work(block):
+    """ Verify the proof of work on a block. """
+    return int.from_bytes(block.hash, byteorder='little', signed=False) > block.difficulty
+
+GENESIS_DIFFICULTY = MAX_HASH - (MAX_HASH // 1000)
+
+class ProofOfWork:
+    def __init__(self, block):
+        self.stopped = False
+        self.block = block
+        self.success = False
+
+    def abort(self)
+        self.stopped = True
+
+    def run(self):
+        """
+        Perform the proof of work on a block, until cond.stopped becomes True or the proof of work was sucessful.
+        """
+        hasher = self.block.get_partial_hash()
+        while not self.stopped:
+            for _ in range(1000):
+                h = hasher.copy()
+                h.update(self.block.nonce)
+                self.block.hash = h.digest()
+                if verify_proof_of_work(self.block):
+                    return True
+                self.block.nonce += 1
+        return False

+ 2 - 0
protocol.py

@@ -0,0 +1,2 @@
+class Protocol:
+    pass

+ 7 - 4
transaction.py

@@ -1,12 +1,15 @@
 from collections import namedtuple
 from crypto import get_hasher, sign, verify_sign
 
+""" The recipient of a transaction ('coin'). """
 TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
+""" One transaction input (pointer to 'coin'). """
 TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_idx"])
 
 
 
 class Transaction:
+
     def __init__(self, inputs, targets, signatures=None):
         self.inputs = inputs
         self.targets = targets
@@ -43,13 +46,13 @@ class Transaction:
         return verify_sign(self.get_hash(), sig)
 
 
-    def _verify_single_spend(self, chain):
+    def _verify_single_spend(self, chain, prev_block):
         """ Verifies that all inputs have not been spent yet. """
         for i in self.inputs:
-            if not chain.is_coin_still_valid(i.transaction_hash, i.output_idx):
+            if not chain.is_coin_still_valid(i, prev_block):
                 return False
         return True
 
-    def verify(self, chain):
+    def verify(self, chain, prev_block=None):
         """ Verifies that this transaction is completely valid. """
-        return self._verify_single_spend(chain) and self._verify_signatures(chain)
+        return self._verify_single_spend(chain, prev_block) and self._verify_signatures(chain)