Просмотр исходного кода

work in progress for P2P protocol

Malte Kraus 8 лет назад
Родитель
Сommit
e5075afb1d
4 измененных файлов с 192 добавлено и 19 удалено
  1. 29 10
      src/block.py
  2. 2 2
      src/blockchain.py
  3. 113 1
      src/protocol.py
  4. 48 6
      src/transaction.py

+ 29 - 10
src/block.py

@@ -1,5 +1,6 @@
 from datetime import datetime
 from datetime import datetime
-from binascii import hexlify
+from binascii import hexlify, unhexlify
+import json
 
 
 from .merkle import merkle_tree
 from .merkle import merkle_tree
 from .crypto import get_hasher
 from .crypto import get_hasher
@@ -18,17 +19,35 @@ class Block:
         self.received_time = received_time
         self.received_time = received_time
         self.difficulty = difficulty
         self.difficulty = difficulty
         self.transactions = transactions
         self.transactions = transactions
+        assert transactions is not None
+        # TODO: fix param order, make transactions non-optional
 
 
-    def __str__(self):
-        return """Block {}
-    Previous Block: {}
-    Merkle Hash: {}
-    Time: {}
-    Nonce: {:x}
-    Height: {}
-    Received Time: {}
-    Difficulty: {:x}""".format(hexlify(self.hash), hexlify(self.prev_block_hash), hexlify(self.merkle_root_hash), self.time, self.nonce, self.height, self.received_time, self.difficulty)
+    def to_json_compatible(self):
+        val = {}
+        val['hash'] = hexlify(self.hash).encode()
+        val['prev_block_hash'] = hexlify(self.prev_block_hash).encode()
+        val['merkle_root_hash'] = hexlify(self.merkle_root_hash).encode()
+        val['time'] = self.time.timestamp()
+        val['nonce'] = self.nonce
+        val['height'] = self.height
+        val['difficulty'] = self.difficulty
+        val['transactions'] = [t.to_json_compatible() for t in self.transactions]
+        return val
+
+    @classmethod
+    def from_json_compatible(cls):
+        return cls(unhexlify(val['hash']),
+                   unhexlify(val['prev_block_hash']),
+                   datetime.fromtimestamp(int(val['time'])),
+                   int(val['nonce']),
+                   int(val['height']),
+                   datetime.now()
+                   int(val['difficulty']),
+                   unhexlify(val['merkle_root_hash']),
+                   [Transaction.from_json_compatible(t) for t in list(val['transactions'])])
 
 
+    def __str__(self):
+        return json.dumps(self.to_json_compatible(), indent=4)
 
 
     def verify_merkle(self):
     def verify_merkle(self):
         """ Verify that the merkle root hash is correct for the transactions in this block. """
         """ Verify that the merkle root hash is correct for the transactions in this block. """

+ 2 - 2
src/blockchain.py

@@ -1,11 +1,11 @@
 
 
 class Blockchain:
 class Blockchain:
-    def __init__(self, blocks):
+    def __init__(self, blocks: list):
         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):
+    def get_transaction_by_hash(self, hash_val: bytes):
         """
         """
         Returns a transaction from its hash, or None.
         Returns a transaction from its hash, or None.
         """
         """

+ 113 - 1
src/protocol.py

@@ -1,2 +1,114 @@
+from src.mining import Miner
+import json
+from enum import Enum
+import socket
+from threading import Thread
+import logging
+from queue import Queue
+
+MAX_PEERS = 10
+HELLO_MSG = b"bl0ckch41n"
+
+socket.setdefaulttimeout(30)
+
+Messages = Enum("Messages", "peers getblock block transaction")
+
+class PeerConnection:
+    def __init__(self, peer, proto, socket=None):
+        self.peer = peer
+        self.socket = None
+        self.proto = proto
+        self.is_connected = False
+        self.outgoing_msgs = Queue()
+        Thread.start(target=self.run, daemon=True)
+
+    def run(self):
+        if self.socket is not None:
+            self.socket = socket.create_connection(self.peer)
+        self.socket.sendall(HELLO_MSG)
+        if self.socket.recv(len(HELLO_MSG)) != HELLO_MSG:
+            return
+        self.is_connected = True
+        Thread.start(target=self.close_on_error, args=(self.reader_thread,))
+        self.close_on_error(self.writer_thread)
+
+    def close_on_error(self, cmd):
+        try:
+            cmd()
+        except Exception:
+            logging.exception()
+        while not self.outgoing_msgs.empty():
+            self.outgoing_msgs.get_nowait()
+        self.outgoing_msgs.put(None)
+        self.is_connected = False
+        self.socket.close()
+
+    def writer_thread(self):
+        while True:
+            item = self.outgoing_msgs.get()
+            if item is None:
+                break
+            self.socket.sendall(str(len(item)).encode() + b"\n")
+            self.socket.sendall(item)
+            self.outgoing_msgs.task_done()
+
+    def reader_thread(self):
+        while True:
+            buf = b""
+            while not buf or buf[-1] != '\n':
+                tmp = self.socket.recv(1)
+                if not tmp:
+                    return
+                buf += tmp
+            length = int(buf)
+            buf = bytearray(length)
+            read = 0
+            while length > read:
+                tmp = self.socket.recv_into(buf[read:])
+                if not tmp:
+                    return
+                read += tmp
+
+            success = self.proto.received(buf, self)
+            if not success:
+                return
+
+class IncomingHandler(socketserver.BaseRequestHandler):
+    def handle(self):
+        pass
+
 class Protocol:
 class Protocol:
-    pass
+    def __init__(self, bootstrap_peer):
+        self.block_receive_handlers = []
+        self.trans_receive_handlers = []
+        socketserver.TCPServer((HOST, PORT), MyTCPHandler)
+        self.peers = [PeerConnection(bootstrap_peer)]
+
+    def block_received(self, block):
+        for handler in self.block_receive_handlers:
+            handler(block)
+
+    def broadcast_mined_block(self, block):
+        self.fake_block_received(block)
+
+    def received(self, msg, peer):
+        try:
+            obj = json.loads(msg.decode())
+            msg_type = obj['msg_type']
+            msg_param = obj['msg_params']
+        except KeyError:
+            return False
+        except UnicodeDecodeError:
+            return False
+        except json.JSONDecodeError:
+            return False
+        getattr(self, 'received_' + msg_type)(msg_param)
+        return True
+    def received_peers(self, peer_list):
+        pass
+    def received_getblock(self, block_hash):
+        pass
+    def received_block(self, block):
+        pass
+    def received_transaction(self, transaction):
+        pass

+ 48 - 6
src/transaction.py

@@ -1,4 +1,5 @@
 from collections import namedtuple
 from collections import namedtuple
+from binascii import hexlify, unhexlify
 
 
 from .crypto import get_hasher, Signing
 from .crypto import get_hasher, Signing
 
 
@@ -7,11 +8,13 @@ TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
 """ One transaction input (pointer to 'coin'). """
 """ One transaction input (pointer to 'coin'). """
 TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_idx"])
 TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_idx"])
 
 
+from typing import List
+SigningListType = List[Signing]
 
 
 
 
 class Transaction:
 class Transaction:
 
 
-    def __init__(self, inputs, targets, signatures=None, iv=None):
+    def __init__(self, inputs: list, targets: list, signatures=None: list, iv=None: bytes):
         self.inputs = inputs
         self.inputs = inputs
         self.targets = targets
         self.targets = targets
         self.signatures = signatures or []
         self.signatures = signatures or []
@@ -21,6 +24,45 @@ class Transaction:
         # Reuse of IVs leads to inaccessible coins.
         # Reuse of IVs leads to inaccessible coins.
         self.iv = iv
         self.iv = iv
 
 
+    def to_json_compatible(self):
+        val = {}
+        val['inputs'] = []
+        for inp in self.inputs:
+            val['inputs'].append({
+                'recipient_pk': hexlify(inp.recipient_pk.to_bytes()).decode(),
+                'amount': inp.amount,
+            })
+        val['targets'] = []
+        for targ in self.targets:
+            val['targets'].append({
+                'transaction_hash': hexlify(targ.transaction_hash).decode(),
+                'output_idx': targ.output_idx,
+            })
+        val['signatures'] = []
+        for sig in self.signatures:
+            val['signatures'].append(sig)
+        val['iv'] = hexlify(self.iv)
+        return val
+
+    @classmethod
+    def from_json_compatible(cls, obj: dict):
+        inputs = []
+        for inp in obj['inputs']:
+            inputs.append(TransactionInput(Signing(unhexlify(inp['recipient_pk'])),
+                                           int(inp['amount'])))
+        targets = []
+        for targ in obj['targets']:
+            targets.append(TransactionTarget(unhexlify(inp['transaction_hash']),
+                                           int(inp['output_idx'])))
+        signatures = obj['signatures']
+        for sig in signatures:
+            if not isinstance(sig, str):
+                raise ValueError()
+
+        iv = unhexlify(obj['iv'])
+        return cls(inputs, targets, signatures, iv)
+
+
     def get_hash(self):
     def get_hash(self):
         """ Hash this transaction. Returns raw bytes. """
         """ Hash this transaction. Returns raw bytes. """
         h = get_hasher()
         h = get_hasher()
@@ -34,7 +76,7 @@ class Transaction:
             h.update(str(inp.output_idx).encode())
             h.update(str(inp.output_idx).encode())
         return h.digest()
         return h.digest()
 
 
-    def sign(self, private_keys):
+    def sign(self, private_keys: SigningListType):
         """
         """
         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.
@@ -42,7 +84,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):
+    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):
             return False
             return False
@@ -52,20 +94,20 @@ class Transaction:
                 return False
                 return False
         return True
         return True
 
 
-    def _verify_single_sig(self, sig, inp, chain):
+    def _verify_single_sig(self, sig: str, inp: TransactionInput, chain: Blockchain):
         """ 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)
         sender_pk = trans.targets[inp.output_idx].recipient_pk
         sender_pk = trans.targets[inp.output_idx].recipient_pk
         return sender_pk.verify_sign(self.get_hash(), sig)
         return sender_pk.verify_sign(self.get_hash(), sig)
 
 
 
 
-    def _verify_single_spend(self, chain, prev_block):
+    def _verify_single_spend(self, chain: Blockchain, prev_block: Block):
         """ Verifies that all inputs have not been spent yet. """
         """ Verifies that all inputs have not been spent yet. """
         for i in self.inputs:
         for i in self.inputs:
             if not chain.is_coin_still_valid(i, prev_block):
             if not chain.is_coin_still_valid(i, prev_block):
                 return False
                 return False
         return True
         return True
 
 
-    def verify(self, chain, prev_block=None):
+    def verify(self, chain: Blockchain, prev_block=None: Block):
         """ Verifies that this transaction is completely valid. """
         """ Verifies that this transaction is completely valid. """
         return self._verify_single_spend(chain, prev_block) and self._verify_signatures(chain)
         return self._verify_single_spend(chain, prev_block) and self._verify_signatures(chain)