Răsfoiți Sursa

bug fixes and major update

Bernardo Magri 7 ani în urmă
părinte
comite
396271c92b

+ 24 - 4
README.md

@@ -12,18 +12,21 @@ from urllib.parse import urlparse
 from typing import Tuple
 from typing import Tuple
 
 
 import logging
 import logging
+
 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
 
 
-from src.crypto import Signing
+from src.config import *
+from src.crypto import Key
 from src.protocol import Protocol
 from src.protocol import Protocol
-from src.block import GENESIS_BLOCK
+from src.blockchain import GENESIS_BLOCK
 from src.chainbuilder import ChainBuilder
 from src.chainbuilder import ChainBuilder
 from src.mining import Miner
 from src.mining import Miner
 from src.persistence import Persistence
 from src.persistence import Persistence
 from src.rpc_server import rpc_server
 from src.rpc_server import rpc_server
 
 
+
 def parse_addr_port(val: str) -> Tuple[str, int]:
 def parse_addr_port(val: str) -> Tuple[str, int]:
-    """ Parse a user-specified "host:port" value to a tuple. """
+    """ Parse a user-specified `host:port` value to a tuple. """
     url = urlparse("//" + val)
     url = urlparse("//" + val)
     assert url.scheme == ''
     assert url.scheme == ''
     assert url.path == ''
     assert url.path == ''
@@ -36,6 +39,15 @@ def parse_addr_port(val: str) -> Tuple[str, int]:
 
 
 
 
 def main():
 def main():
+    """
+    Takes arguments:
+    `listen-address`: The IP address where the P2P server should bind to. Default is: `\"\"`
+    `listen-port`: The port where the P2P server should listen. Defaults a dynamically assigned port. Default is: `0`
+    `mining-pubkey`: The public key where mining rewards should be sent to. No mining is performed if this is left unspecified.
+    `bootstrap-peer`: Addresses of other P2P peers in the network. Default is: `[]`
+    `rpc-port`: The port number where the wallet can find an RPC server. Default is: `40203`
+    `persist-path`: The file where data is persisted.
+    """
     parser = argparse.ArgumentParser(description="Blockchain Miner.")
     parser = argparse.ArgumentParser(description="Blockchain Miner.")
     parser.add_argument("--listen-address", default="",
     parser.add_argument("--listen-address", default="",
                         help="The IP address where the P2P server should bind to.")
                         help="The IP address where the P2P server should bind to.")
@@ -54,7 +66,7 @@ def main():
 
 
     proto = Protocol(args.bootstrap_peer, GENESIS_BLOCK, args.listen_port, args.listen_address)
     proto = Protocol(args.bootstrap_peer, GENESIS_BLOCK, args.listen_port, args.listen_address)
     if args.mining_pubkey is not None:
     if args.mining_pubkey is not None:
-        pubkey = Signing(args.mining_pubkey.read())
+        pubkey = Key(args.mining_pubkey.read())
         args.mining_pubkey.close()
         args.mining_pubkey.close()
         miner = Miner(proto, pubkey)
         miner = Miner(proto, pubkey)
         miner.start_mining()
         miner.start_mining()
@@ -73,5 +85,13 @@ def main():
 
 
     rpc_server(args.rpc_port, chainbuilder, persist)
     rpc_server(args.rpc_port, chainbuilder, persist)
 
 
+
+def start_listener(rpc_port: int, bootstrap_peer: str, listen_port: int, listen_address: str):
+    """ Starts the RPC Server and initializes the protocol. """
+    proto = Protocol([parse_addr_port(bootstrap_peer)], GENESIS_BLOCK, listen_port, listen_address)
+    chainbuilder = ChainBuilder(proto)
+    rpc_server(rpc_port, chainbuilder, None)
+
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     main()
     main()

+ 3 - 2
requirements.txt

@@ -3,6 +3,7 @@ Babel==2.3.4
 click==6.7
 click==6.7
 docutils==0.13.1
 docutils==0.13.1
 Flask==0.12
 Flask==0.12
+Flask_API==1.0
 imagesize==0.7.1
 imagesize==0.7.1
 itsdangerous==0.24
 itsdangerous==0.24
 Jinja2==2.9.5
 Jinja2==2.9.5
@@ -16,8 +17,8 @@ pytz==2016.10
 requests==2.13.0
 requests==2.13.0
 six==1.10.0
 six==1.10.0
 snowballstemmer==1.2.1
 snowballstemmer==1.2.1
-Sphinx==1.5.3
-sphinx-autodoc-typehints==1.1.0
+Sphinx==1.7.1
+sphinx-autodoc-typehints==1.2.5
 treelib==1.3.5
 treelib==1.3.5
 urwid==1.3.1
 urwid==1.3.1
 Werkzeug==0.12
 Werkzeug==0.12

+ 0 - 0
src/__init__.py


+ 62 - 57
src/block.py

@@ -1,16 +1,19 @@
 """ Definitions of blocks, and the genesis block. """
 """ Definitions of blocks, and the genesis block. """
 
 
-from datetime import datetime, timedelta
+from datetime import datetime
 from binascii import hexlify, unhexlify
 from binascii import hexlify, unhexlify
-from struct import pack
+
 import json
 import json
 import logging
 import logging
-import math
 
 
+import src.utils as utils
+
+from .config import *
 from .merkle import merkle_tree
 from .merkle import merkle_tree
 from .crypto import get_hasher
 from .crypto import get_hasher
 
 
-__all__ = ['Block', 'GENESIS_BLOCK', 'GENESIS_BLOCK_HASH']
+__all__ = ['Block']
+
 
 
 class Block:
 class Block:
     """
     """
@@ -21,6 +24,8 @@ class Block:
 
 
     :ivar hash: The hash value of this block.
     :ivar hash: The hash value of this block.
     :vartype hash: bytes
     :vartype hash: bytes
+    :ivar id: The ID of this block. Genesis-Block has the ID '0'.
+    :vartype id: int
     :ivar prev_block_hash: The hash of the previous block.
     :ivar prev_block_hash: The hash of the previous block.
     :vartype prev_block_hash: bytes
     :vartype prev_block_hash: bytes
     :ivar merkle_root_hash: The hash of the merkle tree root of the transactions in this block.
     :ivar merkle_root_hash: The hash of the merkle tree root of the transactions in this block.
@@ -39,7 +44,9 @@ class Block:
     :vartype transactions: List[Transaction]
     :vartype transactions: List[Transaction]
     """
     """
 
 
-    def __init__(self, prev_block_hash, time, nonce, height, received_time, difficulty, transactions, merkle_root_hash=None):
+    def __init__(self, prev_block_hash, time, nonce, height, received_time, difficulty, transactions,
+                 merkle_root_hash=None, id=None):
+        self.id = id
         self.prev_block_hash = prev_block_hash
         self.prev_block_hash = prev_block_hash
         self.merkle_root_hash = merkle_root_hash
         self.merkle_root_hash = merkle_root_hash
         self.time = time
         self.time = time
@@ -53,6 +60,8 @@ class Block:
     def to_json_compatible(self):
     def to_json_compatible(self):
         """ Returns a JSON-serializable representation of this object. """
         """ Returns a JSON-serializable representation of this object. """
         val = {}
         val = {}
+        val['id'] = self.id
+        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()
         val['merkle_root_hash'] = hexlify(self.merkle_root_hash).decode()
         val['merkle_root_hash'] = hexlify(self.merkle_root_hash).decode()
         val['time'] = self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC")
         val['time'] = self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC")
@@ -73,33 +82,27 @@ class Block:
                    datetime.utcnow(),
                    datetime.utcnow(),
                    int(val['difficulty']),
                    int(val['difficulty']),
                    [Transaction.from_json_compatible(t) for t in list(val['transactions'])],
                    [Transaction.from_json_compatible(t) for t in list(val['transactions'])],
-                   unhexlify(val['merkle_root_hash']))
+                   unhexlify(val['merkle_root_hash']),
+                   int(val['id']))
 
 
     @classmethod
     @classmethod
-    def create(cls, blockchain: 'Blockchain', transactions: list, ts=None):
+    def create(cls, chain_difficulty: int, prev_block: 'Block', transactions: list, ts=None):
         """
         """
         Create a new block for a certain blockchain, containing certain transactions.
         Create a new block for a certain blockchain, containing certain transactions.
         """
         """
         tree = merkle_tree(transactions)
         tree = merkle_tree(transactions)
-        difficulty = blockchain.compute_difficulty_next_block()
+        difficulty = chain_difficulty
+        id = prev_block.height + 1
         if ts is None:
         if ts is None:
             ts = datetime.utcnow()
             ts = datetime.utcnow()
-        if ts <= blockchain.head.time:
-            ts = blockchain.head.time + timedelta(microseconds=1)
-        return Block(blockchain.head.hash, ts, 0, blockchain.head.height + difficulty,
-                     None, difficulty, transactions, tree.get_hash())
+        if ts <= prev_block.time:
+            ts = prev_block.time + timedelta(microseconds=1)
+        return Block(prev_block.hash, ts, 0, prev_block.height + 1,
+                     None, difficulty, transactions, tree.get_hash(), id)
 
 
     def __str__(self):
     def __str__(self):
         return json.dumps(self.to_json_compatible(), indent=4)
         return json.dumps(self.to_json_compatible(), indent=4)
 
 
-    @staticmethod
-    def _int_to_bytes(val: int) -> bytes:
-        """ Turns an (arbitrarily long) integer into a bytes sequence. """
-        l = val.bit_length() + 1
-        # we need to include the length in the hash in some way, otherwise e.g.
-        # the numbers (0xffff, 0x00) would be encoded identically to (0xff, 0xff00)
-        return pack("<Q", l) + val.to_bytes(l, 'little', signed=True)
-
     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
         Computes a hash over the contents of this block, except for the nonce. The proof of
@@ -110,7 +113,7 @@ class Block:
         hasher.update(self.prev_block_hash)
         hasher.update(self.prev_block_hash)
         hasher.update(self.merkle_root_hash)
         hasher.update(self.merkle_root_hash)
         hasher.update(self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC").encode())
         hasher.update(self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC").encode())
-        hasher.update(self._int_to_bytes(self.difficulty))
+        hasher.update(utils.int_to_bytes(self.difficulty))
         return hasher
         return hasher
 
 
     def finish_hash(self, hasher):
     def finish_hash(self, hasher):
@@ -119,7 +122,7 @@ class Block:
         work can use this function to efficiently try different nonces. Other uses should
         work can use this function to efficiently try different nonces. Other uses should
         use `hash` to get the complete hash in one step.
         use `hash` to get the complete hash in one step.
         """
         """
-        hasher.update(self._int_to_bytes(self.nonce))
+        hasher.update(utils.int_to_bytes(self.nonce))
         return hasher.digest()
         return hasher.digest()
 
 
     def _get_hash(self):
     def _get_hash(self):
@@ -131,52 +134,62 @@ class Block:
         """ 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. """
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
 
 
+    def verify_proof_of_work(self):
+        """ Verify the proof of work on a block. """
+        return int.from_bytes(self.hash, 'big') < self.difficulty
+
     def verify_difficulty(self):
     def verify_difficulty(self):
         """ Verifies that the hash value is correct and fulfills its difficulty promise. """
         """ Verifies that the hash value is correct and fulfills its difficulty promise. """
-        if self.hash == GENESIS_BLOCK_HASH:
+        if self.height == 0:  # can be removed?!
             return True
             return True
-        if not verify_proof_of_work(self):
+        if not self.verify_proof_of_work():
             logging.warning("block does not satisfy proof of work")
             logging.warning("block does not satisfy proof of work")
             return False
             return False
         return True
         return True
 
 
-    def verify_prev_block(self, chain: 'Blockchain'):
+    def verify_prev_block(self, prev_block: 'Block', chain_difficulty: int):
         """ Verifies that the previous block pointer points to the head of the given block chain and difficulty and height are correct. """
         """ Verifies that the previous block pointer points to the head of the given block chain and difficulty and height are correct. """
-        if chain.head.hash != self.prev_block_hash:
+        if prev_block.hash != self.prev_block_hash:
             logging.warning("Previous block is not head of the block chain.")
             logging.warning("Previous block is not head of the block chain.")
             return False
             return False
-        if self.difficulty != chain.compute_difficulty_next_block():
+        if self.difficulty != chain_difficulty:
             logging.warning("Block has wrong difficulty.")
             logging.warning("Block has wrong difficulty.")
             return False
             return False
-        if chain.head.height + self.difficulty != self.height:
+        if prev_block.height + 1 != self.height:
             logging.warning("Block has wrong height.")
             logging.warning("Block has wrong height.")
             return False
             return False
         return True
         return True
 
 
-    def verify_transactions(self, chain: 'Blockchain'):
+    def verify_block_transactions(self, unspent_coins: dict, reward: int):
         """ Verifies that all transaction in this block are valid in the given block chain. """
         """ Verifies that all transaction in this block are valid in the given block chain. """
-        mining_reward = None
+        mining_rewards = []
+        all_inputs = []
 
 
-        trans_set = set(self.transactions)
         for t in self.transactions:
         for t in self.transactions:
+
+            all_inputs += t.inputs
             if not t.inputs:
             if not t.inputs:
-                if mining_reward is not None:
-                    logging.warning("block has more than one reward transaction")
+                if len(mining_rewards) > 1:
+                    logging.warning("block has more than one coinbase transaction")
                     return False
                     return False
-                mining_reward = t
+                mining_rewards.append(t)
+
+            if not t.validate_tx(unspent_coins):
+                return False
 
 
-            if not t.verify(chain, trans_set - {t}):
+            if reward != sum(t.amount for t in mining_rewards[0].targets):
+                logging.warning("reward is different than specified")
                 return False
                 return False
-        if mining_reward is not None:
-            fees = sum(t.get_transaction_fee(chain) for t in self.transactions)
-            reward = chain.compute_blockreward_next_block()
-            used = sum(t.amount for t in mining_reward.targets)
-            if used > fees + reward:
-                logging.warning("mining reward is too large")
+
+            if not self._verify_input_consistency(all_inputs):
                 return False
                 return False
+
         return True
         return True
 
 
-    def verify_time(self, chain: 'Blockchain'):
+    def _verify_input_consistency(self, tx_inputs: 'List[TransactionInputs]'):
+        return len(tx_inputs) == len(set(tx_inputs))
+
+    def verify_time(self, head_time: datetime):
         """
         """
         Verifies that blocks are not from far in the future, but a bit younger
         Verifies that blocks are not from far in the future, but a bit younger
         than the head of `chain`.
         than the head of `chain`.
@@ -184,30 +197,22 @@ class Block:
         if self.time - timedelta(hours=2) > datetime.utcnow():
         if self.time - timedelta(hours=2) > datetime.utcnow():
             logging.warning("discarding block because it is from the far future")
             logging.warning("discarding block because it is from the far future")
             return False
             return False
-        if self.time <= chain.head.time:
+        if self.time <= head_time:
             logging.warning("discarding block because it is younger than its predecessor")
             logging.warning("discarding block because it is younger than its predecessor")
             return False
             return False
         return True
         return True
 
 
-    def verify(self, chain: 'Blockchain'):
+    def verify(self, prev_block: 'Block', chain_difficulty: int, unspent_coins: dict, chain_indices: dict, reward: int):
         """
         """
         Verifies that this block contains only valid data and can be applied on top of the block
         Verifies that this block contains only valid data and can be applied on top of the block
         chain `chain`.
         chain `chain`.
         """
         """
-        assert self.hash not in chain.block_indices
+        assert self.hash not in chain_indices
         if self.height == 0:
         if self.height == 0:
             logging.warning("only the genesis block may have height=0")
             logging.warning("only the genesis block may have height=0")
             return False
             return False
-        return self.verify_difficulty() and self.verify_merkle() and self.verify_prev_block(chain) \
-                and self.verify_transactions(chain) and self.verify_time(chain)
-
-from .proof_of_work import verify_proof_of_work, GENESIS_DIFFICULTY, DIFFICULTY_BLOCK_INTERVAL, \
-        DIFFICULTY_TARGET_TIMEDELTA
-
-
-GENESIS_BLOCK = Block("None; {} {}".format(DIFFICULTY_BLOCK_INTERVAL,
-        DIFFICULTY_TARGET_TIMEDELTA).encode(), datetime(2017, 3, 3, 10, 35, 26, 922898), 0, 0,
-        datetime.utcnow(), GENESIS_DIFFICULTY, [], merkle_tree([]).get_hash())
-GENESIS_BLOCK_HASH = GENESIS_BLOCK.hash
 
 
-from .blockchain import Blockchain
+        return self.verify_difficulty() and self.verify_merkle() and self.verify_prev_block(prev_block,
+                                                                                            chain_difficulty) \
+               and self.verify_time(prev_block
+                                    .time) and self.verify_block_transactions(unspent_coins, reward)

+ 50 - 35
src/blockchain.py

@@ -1,22 +1,32 @@
 """ Definition of block chains. """
 """ Definition of block chains. """
 
 
-__all__ = ['Blockchain']
+__all__ = ['Blockchain', 'GENESIS_BLOCK']
 import logging
 import logging
-from fractions import Fraction
-from typing import List, Dict, Optional
 
 
-from .proof_of_work import DIFFICULTY_BLOCK_INTERVAL, DIFFICULTY_TARGET_TIMEDELTA
+from typing import Optional
 
 
-GENESIS_REWARD = 1000
-""" The reward that is available for the first `REWARD_HALF_LIFE` blocks, starting with the genesis block. """
+from datetime import datetime
+
+from .merkle import merkle_tree
+from .block import Block
+
+from .config import *
+from .utils import compute_blockreward_next_block
+
+# If you want to add transactions in the genesis block you can create a Transaction object and include it in the list below (after GENESIS_DIFFICULTY)
+GENESIS_BLOCK = Block("None; {} {}".format(DIFFICULTY_BLOCK_INTERVAL,
+                                           DIFFICULTY_TARGET_TIMEDELTA).encode(),
+                      datetime(2017, 3, 3, 10, 35, 26, 922898), 0, 0,
+                      datetime.utcnow(), GENESIS_DIFFICULTY, [], merkle_tree([]).get_hash(),
+                      0)
+
+GENESIS_BLOCK_HASH = GENESIS_BLOCK.hash
 
 
-REWARD_HALF_LIFE = 10000
-""" The number of blocks until the block reward is halved. """
 
 
 class Blockchain:
 class Blockchain:
     """
     """
-    A block chain: a ordered, immutable list of valid blocks. The only ways to create a blockchain
-    instance are the constructor, which will create a block chain containing only the genesis block,
+    A block chain: a ordered, immutable list of valid blocks. The only way to create a blockchain
+    instance is the constructor, which will create a block chain containing only the genesis block,
     and the `try_append` method which creates a new block chain only if the given block is valid on
     and the `try_append` method which creates a new block chain only if the given block is valid on
     top of `self`.
     top of `self`.
 
 
@@ -34,32 +44,38 @@ class Blockchain:
         self.blocks = [GENESIS_BLOCK]
         self.blocks = [GENESIS_BLOCK]
         assert self.blocks[0].height == 0
         assert self.blocks[0].height == 0
         self.block_indices = {GENESIS_BLOCK_HASH: 0}
         self.block_indices = {GENESIS_BLOCK_HASH: 0}
-        assert not GENESIS_BLOCK.transactions
         self.unspent_coins = {}
         self.unspent_coins = {}
+        self.total_difficulty = GENESIS_BLOCK.difficulty
 
 
     def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
     def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
         """
         """
-        If `block` is valid on top of this chain, returns a new block chain including that block.
+        If `block` is valid on top of this chain, returns a new blockchain including that block.
         Otherwise, it returns `None`.
         Otherwise, it returns `None`.
         """
         """
 
 
-        if not block.verify(self):
+        if not block.verify(self.head, self.compute_difficulty_next_block(), self.unspent_coins, self.block_indices,
+                            compute_blockreward_next_block(self.head.height)):
             return None
             return None
 
 
         unspent_coins = self.unspent_coins.copy()
         unspent_coins = self.unspent_coins.copy()
 
 
         for t in block.transactions:
         for t in block.transactions:
             for inp in t.inputs:
             for inp in t.inputs:
-                assert inp in unspent_coins, "Aborting computation of unspent transactions because a transaction spent an unavailable coin."
-                del unspent_coins[inp]
+                try:
+                    del unspent_coins[inp.transaction_hash, inp.output_idx]
+                except KeyError:
+                    logging.info("Input was already spent in this block!")
+                    return None
             for i, target in enumerate(t.targets):
             for i, target in enumerate(t.targets):
-                unspent_coins[TransactionInput(t.get_hash(), i)] = target
+                if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
+                    unspent_coins[(t.get_hash(), i)] = target
 
 
         chain = Blockchain()
         chain = Blockchain()
         chain.unspent_coins = unspent_coins
         chain.unspent_coins = unspent_coins
         chain.blocks = self.blocks + [block]
         chain.blocks = self.blocks + [block]
         chain.block_indices = self.block_indices.copy()
         chain.block_indices = self.block_indices.copy()
         chain.block_indices[block.hash] = len(self.blocks)
         chain.block_indices[block.hash] = len(self.blocks)
+        chain.total_difficulty = self.total_difficulty + block.difficulty
 
 
         return chain
         return chain
 
 
@@ -81,33 +97,32 @@ class Blockchain:
 
 
     def compute_difficulty_next_block(self) -> int:
     def compute_difficulty_next_block(self) -> int:
         """ Compute the desired difficulty for the block following this chain's `head`. """
         """ Compute the desired difficulty for the block following this chain's `head`. """
-        target_timedelta = Fraction(int(DIFFICULTY_TARGET_TIMEDELTA.total_seconds() * 1000 * 1000))
+        should_duration = DIFFICULTY_TARGET_TIMEDELTA.total_seconds()
 
 
-        block_idx = len(self.blocks)
-        if block_idx % DIFFICULTY_BLOCK_INTERVAL != 0:
+        if (self.head.height % DIFFICULTY_BLOCK_INTERVAL != 0) or (self.head.height == 0):
             return self.head.difficulty
             return self.head.difficulty
 
 
-        duration = self.head.time - self.blocks[block_idx - DIFFICULTY_BLOCK_INTERVAL].time
-        duration = Fraction(int(duration.total_seconds() * 1000 * 1000))
+        last_duration = (
+                self.head.time - self.blocks[self.head.height - DIFFICULTY_BLOCK_INTERVAL].time).total_seconds()
+        diff_adjustment_factor = last_duration / should_duration
+        prev_difficulty = self.head.difficulty
 
 
-        prev_difficulty = Fraction(self.head.difficulty)
-        hash_rate = prev_difficulty * DIFFICULTY_BLOCK_INTERVAL / duration
-
-        new_difficulty = hash_rate * target_timedelta / DIFFICULTY_BLOCK_INTERVAL
+        new_difficulty = prev_difficulty * diff_adjustment_factor
 
 
         # the genesis difficulty was very easy, dropping below it means there was a pause
         # the genesis difficulty was very easy, dropping below it means there was a pause
         # in mining, so let's start with a new difficulty!
         # in mining, so let's start with a new difficulty!
-        if new_difficulty < self.blocks[0].difficulty:
+        if new_difficulty > self.blocks[0].difficulty:
             new_difficulty = self.blocks[0].difficulty
             new_difficulty = self.blocks[0].difficulty
 
 
         return int(new_difficulty)
         return int(new_difficulty)
 
 
-    def compute_blockreward_next_block(self) -> int:
-        """ Compute the block reward that is expected for the block following this chain's `head`. """
-        half_lives = len(self.blocks) // REWARD_HALF_LIFE
-        reward = GENESIS_REWARD // (2 ** half_lives)
-
-        return reward
-
-from .block import Block, GENESIS_BLOCK, GENESIS_BLOCK_HASH
-from .transaction import TransactionInput, Transaction
+    # def get_key_for_tx(self, tx:Transaction) -> Key:
+    #     inp = tx.inputs[0]
+    #     for block in self.blocks:
+    #         for transaction in block.transactions:
+    #             if transaction.get_hash() == inp.transaction_hash:
+    #                 target = transaction.targets[inp.output_idx]
+    #                 logging.info("REWARD TO")
+    #                 logging.info(target.get_pubkey)
+    #                 return target.get_pubkey
+    #     return None

+ 39 - 33
src/chainbuilder.py

@@ -27,17 +27,21 @@ most checkpoints being relatively recent. There also is always one checkpoint wi
 block.
 block.
 """
 """
 
 
+import binascii
 import threading
 import threading
 import logging
 import logging
 import math
 import math
 from typing import List, Dict, Callable, Optional
 from typing import List, Dict, Callable, Optional
-from datetime import datetime, timedelta
+from datetime import datetime
 
 
-from .block import GENESIS_BLOCK, GENESIS_BLOCK_HASH, Block
-from .blockchain import Blockchain
+from .config import *
+from .block import Block
+from .blockchain import Blockchain, GENESIS_BLOCK, GENESIS_BLOCK_HASH
+from .protocol import Protocol
 
 
 __all__ = ['ChainBuilder']
 __all__ = ['ChainBuilder']
 
 
+
 class BlockRequest:
 class BlockRequest:
     """
     """
     Stores information about a pending block request and the partial chains that depend on it.
     Stores information about a pending block request and the partial chains that depend on it.
@@ -51,12 +55,6 @@ class BlockRequest:
     :vartype _request_count: int
     :vartype _request_count: int
     """
     """
 
 
-    BLOCK_REQUEST_RETRY_INTERVAL = timedelta(minutes=1)
-    """ The approximate interval after which a block request will be retried. """
-    BLOCK_REQUEST_RETRY_COUNT = 3
-    """ The number of failed requests of a block until we give up and delete the depending partial chains. """
-
-
     def __init__(self):
     def __init__(self):
         self.partial_chains = [[]]
         self.partial_chains = [[]]
         self.clear()
         self.clear()
@@ -71,11 +69,12 @@ class BlockRequest:
         self._request_count += 1
         self._request_count += 1
         self._last_update = datetime.utcnow()
         self._last_update = datetime.utcnow()
         protocol.send_block_request(self.partial_chains[0][-1].prev_block_hash)
         protocol.send_block_request(self.partial_chains[0][-1].prev_block_hash)
-        logging.debug("asking for another block %d (attempt %d)", max(len(r) for r in self.partial_chains), self._request_count)
+        logging.debug("asking for another block %d (attempt %d)", max(len(r) for r in self.partial_chains),
+                      self._request_count)
 
 
     def timeout_reached(self) -> bool:
     def timeout_reached(self) -> bool:
         """ Returns a bool indicating whether all attempts to download this block have failed. """
         """ Returns a bool indicating whether all attempts to download this block have failed. """
-        return self._request_count > self.BLOCK_REQUEST_RETRY_COUNT
+        return self._request_count > BLOCK_REQUEST_RETRY_COUNT
 
 
     def checked_retry(self, protocol: 'Protocol'):
     def checked_retry(self, protocol: 'Protocol'):
         """
         """
@@ -83,12 +82,13 @@ class BlockRequest:
         request was sent yet.
         request was sent yet.
         """
         """
 
 
-        if self._last_update + self.BLOCK_REQUEST_RETRY_INTERVAL < datetime.utcnow():
-            if self._request_count >= self.BLOCK_REQUEST_RETRY_COUNT:
+        if self._last_update + BLOCK_REQUEST_RETRY_INTERVAL < datetime.utcnow():
+            if self._request_count >= BLOCK_REQUEST_RETRY_COUNT:
                 self._request_count += 1
                 self._request_count += 1
             else:
             else:
                 self.send_request(protocol)
                 self.send_request(protocol)
 
 
+
 class ChainBuilder:
 class ChainBuilder:
     """
     """
     The chain builder maintains the current longest confirmed (primary) block chain as well as
     The chain builder maintains the current longest confirmed (primary) block chain as well as
@@ -106,7 +106,7 @@ class ChainBuilder:
     :ivar unconfirmed_transactions: Known transactions that are not part of the primary block chain.
     :ivar unconfirmed_transactions: Known transactions that are not part of the primary block chain.
     :vartype unconfirmed_transactions: Dict[bytes, Transaction]
     :vartype unconfirmed_transactions: Dict[bytes, Transaction]
     :ivar chain_change_handlers: Event handlers that get called when we find out about a new primary
     :ivar chain_change_handlers: Event handlers that get called when we find out about a new primary
-                                 block chain.
+                                 block chain.unconfirmed_transactions
     :vartype chain_change_handlers: List[Callable]
     :vartype chain_change_handlers: List[Callable]
     :ivar transaction_change_handlers: Event handlers that get called when we find out about a new
     :ivar transaction_change_handlers: Event handlers that get called when we find out about a new
                                        transaction.
                                        transaction.
@@ -118,10 +118,17 @@ class ChainBuilder:
     def __init__(self, protocol: 'Protocol'):
     def __init__(self, protocol: 'Protocol'):
         self.primary_block_chain = Blockchain()
         self.primary_block_chain = Blockchain()
         self._block_requests = {}
         self._block_requests = {}
-        self._blockchain_checkpoints = { GENESIS_BLOCK_HASH: self.primary_block_chain }
+        self._blockchain_checkpoints = {GENESIS_BLOCK_HASH: self.primary_block_chain}
 
 
-        self.block_cache = { GENESIS_BLOCK_HASH: GENESIS_BLOCK }
+        self.block_cache = {GENESIS_BLOCK_HASH: GENESIS_BLOCK}
         self.unconfirmed_transactions = {}
         self.unconfirmed_transactions = {}
+
+        # Adding the tx from Genesis block to unspent coins
+        for tx in GENESIS_BLOCK.transactions:
+            for i, target in enumerate(tx.targets):
+                if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
+                    self.primary_block_chain.unspent_coins[(tx.get_hash(), i)] = target
+
         # TODO: we want this to be sorted by some function of rewards and age
         # TODO: we want this to be sorted by some function of rewards and age
         # TODO: we want two lists, one with known valid, unapplied transactions, the other with all known transactions (with some limit)
         # TODO: we want two lists, one with known valid, unapplied transactions, the other with all known transactions (with some limit)
 
 
@@ -151,8 +158,8 @@ class ChainBuilder:
         hash_val = transaction.get_hash()
         hash_val = transaction.get_hash()
 
 
         def input_ok(inp):
         def input_ok(inp):
-            return inp in self.primary_block_chain.unspent_coins or \
-                    inp.transaction_hash in self.unconfirmed_transactions
+            return (inp.transaction_hash, inp.output_idx) in self.primary_block_chain.unspent_coins or \
+                   inp.transaction_hash in self.unconfirmed_transactions
 
 
         if hash_val not in self.unconfirmed_transactions and \
         if hash_val not in self.unconfirmed_transactions and \
                 all(input_ok(inp) for inp in transaction.inputs):
                 all(input_ok(inp) for inp in transaction.inputs):
@@ -163,12 +170,13 @@ class ChainBuilder:
 
 
     def _new_primary_block_chain(self, chain: 'Blockchain'):
     def _new_primary_block_chain(self, chain: 'Blockchain'):
         """ Does all the housekeeping that needs to be done when a new longest chain is found. """
         """ Does all the housekeeping that needs to be done when a new longest chain is found. """
-        logging.info("new primary block chain with height %d with current difficulty %d", len(chain.blocks), chain.head.difficulty)
+        logging.info("new primary block chain with height %d with current difficulty %d", len(chain.blocks),
+                     chain.head.difficulty)
         self._assert_thread_safety()
         self._assert_thread_safety()
         self.primary_block_chain = chain
         self.primary_block_chain = chain
         todelete = set()
         todelete = set()
         for (hash_val, trans) in self.unconfirmed_transactions.items():
         for (hash_val, trans) in self.unconfirmed_transactions.items():
-            if not trans.verify(chain, set()):
+            if not trans.validate_tx(chain.unspent_coins):
                 todelete.add(hash_val)
                 todelete.add(hash_val)
         for hash_val in todelete:
         for hash_val in todelete:
             del self.unconfirmed_transactions[hash_val]
             del self.unconfirmed_transactions[hash_val]
@@ -201,6 +209,7 @@ class ChainBuilder:
             if next_chain is None:
             if next_chain is None:
                 logging.warning("invalid block")
                 logging.warning("invalid block")
                 break
                 break
+                # TODO we need to figure out why the miner stops after an invalid block!
             chain = next_chain
             chain = next_chain
             checkpoints[chain.head.hash] = chain
             checkpoints[chain.head.hash] = chain
 
 
@@ -213,7 +222,6 @@ class ChainBuilder:
         self._blockchain_checkpoints = checkpoints
         self._blockchain_checkpoints = checkpoints
         self._new_primary_block_chain(chain)
         self._new_primary_block_chain(chain)
 
 
-
     def _retry_expired_requests(self):
     def _retry_expired_requests(self):
         """ Sends new block requests to our peers for unanswered pending requests. """
         """ Sends new block requests to our peers for unanswered pending requests. """
         for request in self._block_requests.values():
         for request in self._block_requests.values():
@@ -243,28 +251,30 @@ class ChainBuilder:
     def new_block_received(self, block: '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 block.hash in self.block_cache or not block.verify_difficulty() or \
-                not block.verify_merkle():
+
+        if (block.hash in self.block_cache) or (not block.verify_difficulty()) or (not block.verify_merkle()):
             return
             return
+
         self.block_cache[block.hash] = block
         self.block_cache[block.hash] = block
 
 
         self._retry_expired_requests()
         self._retry_expired_requests()
 
 
         if block.hash not in self._block_requests:
         if block.hash not in self._block_requests:
-            if block.height > self.primary_block_chain.head.height:
-                if block.hash not in self._block_requests:
-                    self._block_requests[block.hash] = BlockRequest()
-            else:
-                return
+            self._block_requests[block.hash] = BlockRequest()
+        else:
+            return
 
 
         request = self._block_requests[block.hash]
         request = self._block_requests[block.hash]
         del self._block_requests[block.hash]
         del self._block_requests[block.hash]
+
         while True:
         while True:
             for partial_chain in request.partial_chains:
             for partial_chain in request.partial_chains:
                 partial_chain.append(block)
                 partial_chain.append(block)
-            if block.prev_block_hash not in self.block_cache or block.prev_block_hash in self._blockchain_checkpoints:
+            if (block.prev_block_hash not in self.block_cache) or (
+                    block.prev_block_hash in self._blockchain_checkpoints):
                 break
                 break
             block = self.block_cache[block.prev_block_hash]
             block = self.block_cache[block.prev_block_hash]
+
         if block.prev_block_hash in self._block_requests:
         if block.prev_block_hash in self._block_requests:
             chains = request.partial_chains
             chains = request.partial_chains
             request = self._block_requests[block.prev_block_hash]
             request = self._block_requests[block.prev_block_hash]
@@ -279,7 +289,3 @@ class ChainBuilder:
             for partial_chain in request.partial_chains:
             for partial_chain in request.partial_chains:
                 self._build_blockchain(checkpoint, partial_chain[::-1])
                 self._build_blockchain(checkpoint, partial_chain[::-1])
         request.checked_retry(self.protocol)
         request.checked_retry(self.protocol)
-
-from .protocol import Protocol
-from .block import Block
-from .transaction import Transaction

+ 26 - 0
src/config.py

@@ -0,0 +1,26 @@
+from datetime import timedelta
+
+GENESIS_REWARD = 1000
+""" The reward that is available for the first `REWARD_HALF_LIFE` blocks, starting with the genesis block. """
+
+REWARD_HALF_LIFE = 10000
+""" The number of blocks until the block reward is halved. """
+
+BLOCK_REQUEST_RETRY_INTERVAL = timedelta(minutes=1)
+""" The approximate interval after which a block request will be retried. """
+BLOCK_REQUEST_RETRY_COUNT = 3
+""" The number of failed requests of a block until we give up and delete the depending partial chains. """
+
+GENESIS_DIFFICULTY = (1 << 256) - 1
+"""
+The difficulty of the genesis block.
+
+It is directly used for comparison with the hash and thus we start with the smallest possible difficulty which is the maximal possible hash value.
+Thus, everything bellow is a valid proof of work
+"""
+
+DIFFICULTY_BLOCK_INTERVAL = 5
+""" The number of blocks between difficulty changes. """
+DIFFICULTY_TARGET_TIMEDELTA = timedelta(seconds=60)
+""" The time span that it should approximately take to mine `DIFFICULTY_BLOCK_INTERVAL` blocks.  """
+

+ 17 - 11
src/crypto.py

@@ -3,25 +3,31 @@
 import os
 import os
 import os.path
 import os.path
 import tempfile
 import tempfile
+import random
+import string
 from binascii import hexlify, unhexlify
 from binascii import hexlify, unhexlify
 from typing import Iterator, Iterable
 from typing import Iterator, Iterable
 
 
 from Crypto.Signature import PKCS1_PSS
 from Crypto.Signature import PKCS1_PSS
-from Crypto.Hash import SHA512
+from Crypto.Hash import SHA256
 from Crypto.PublicKey import RSA
 from Crypto.PublicKey import RSA
 
 
-__all__ = ['get_hasher', 'Signing', 'MAX_HASH']
+# TODO upgrade to ecdsa at https://pypi.org/project/fastecdsa/
+
+__all__ = ['get_hasher', 'Key']
+
 
 
 def get_hasher():
 def get_hasher():
     """ Returns a object that you can use for hashing, compatible to the `hashlib` interface. """
     """ Returns a object that you can use for hashing, compatible to the `hashlib` interface. """
-    return SHA512.new()
+    return SHA256.new()
 
 
 
 
-MAX_HASH = (1 << 512) - 1
-""" The largest possible hash value, when interpreted as an unsigned int. """
+def get_random_int(length: int) -> int:
+    rnd = random.SystemRandom()
+    return rnd.randint(0, (2 ** length) - 1)
 
 
 
 
-class Signing:
+class Key:
     """
     """
     Functionality for creating and verifying signatures, and their public/private keys.
     Functionality for creating and verifying signatures, and their public/private keys.
 
 
@@ -48,7 +54,7 @@ class Signing:
     @classmethod
     @classmethod
     def generate_private_key(cls):
     def generate_private_key(cls):
         """ Generate a new private key. """
         """ Generate a new private key. """
-        return Signing(RSA.generate(3072).exportKey())
+        return Key(RSA.generate(1024).exportKey())
 
 
     @classmethod
     @classmethod
     def from_file(cls, path):
     def from_file(cls, path):
@@ -56,7 +62,7 @@ class Signing:
         with open(path, 'rb') as f:
         with open(path, 'rb') as f:
             return cls(f.read())
             return cls(f.read())
 
 
-    def as_bytes(self, include_priv: bool=False) -> bytes:
+    def as_bytes(self, include_priv: bool = False) -> bytes:
         """ Serialize this key to a `bytes` value. """
         """ Serialize this key to a `bytes` value. """
         if include_priv:
         if include_priv:
             return self.rsa.exportKey()
             return self.rsa.exportKey()
@@ -72,7 +78,7 @@ class Signing:
         """ Creates a new object of this class, from a JSON-serializable representation. """
         """ Creates a new object of this class, from a JSON-serializable representation. """
         return cls(unhexlify(obj))
         return cls(unhexlify(obj))
 
 
-    def __eq__(self, other: 'Signing'):
+    def __eq__(self, other: 'Key'):
         return self.rsa.e == other.rsa.e and self.rsa.n == other.rsa.n
         return self.rsa.e == other.rsa.e and self.rsa.n == other.rsa.n
 
 
     def __hash__(self):
     def __hash__(self):
@@ -87,7 +93,7 @@ class Signing:
         return self.rsa.has_private()
         return self.rsa.has_private()
 
 
     @classmethod
     @classmethod
-    def read_many_private(cls, file_contents: bytes) -> 'Iterator[Signing]':
+    def read_many_private(cls, file_contents: bytes) -> 'Iterator[Key]':
         """ Reads many private keys from the (binary) contents of a file written with `write_many_private`. """
         """ Reads many private keys from the (binary) contents of a file written with `write_many_private`. """
         end = b"-----END RSA PRIVATE KEY-----"
         end = b"-----END RSA PRIVATE KEY-----"
         for key in file_contents.strip().split(end):
         for key in file_contents.strip().split(end):
@@ -98,7 +104,7 @@ class Signing:
             yield cls(key)
             yield cls(key)
 
 
     @staticmethod
     @staticmethod
-    def write_many_private(path: str, keys: 'Iterable[Signing]'):
+    def write_many_private(path: str, keys: 'Iterable[Key]'):
         """ Writes the private keys in `keys` to the file at `path`. """
         """ Writes the private keys in `keys` to the file at `path`. """
         dirname = os.path.dirname(path) or "."
         dirname = os.path.dirname(path) or "."
         with tempfile.NamedTemporaryFile("wb", delete=False, dir=dirname) as fp:
         with tempfile.NamedTemporaryFile("wb", delete=False, dir=dirname) as fp:

+ 1662 - 0
src/labscript.py

@@ -0,0 +1,1662 @@
+#! /usr/bin/env/python3
+import hashlib
+import logging
+from .crypto import *
+from binascii import hexlify, unhexlify
+from datetime import datetime
+from .transaction import *
+
+global transaction
+global prog_stack
+global validity
+global control_flow_stack
+global alt_stack
+
+#The actual interpreter for our scriptlanguage
+def interpreter(inputscript: str, outputscript: str, tx):
+
+    global transaction
+    #get nLockTime from transaction for OP_CHECKLOCKTIME / OP_CHECKLOCKTIMEVERIFY / OP_NOP2
+    transaction = tx
+
+    #parse script-strings to LabScript
+    scriptSig = LabScript(inputscript + " " + outputscript)
+    #scriptPubKey = LabScript(outputscript)
+    
+    #create Scriptstack
+    stack = LabStack()
+
+    #execute scripts and merge the results
+    return scriptSig.execute_script(stack)
+    #return scriptPubKey.execute_script(stack)
+
+def invalidate():
+    """
+    Makes the script invalid. 
+    """
+    global validity
+    validity = False
+
+class LabStack:
+    """
+    Labscript uses a stack for imperative control flow. LabStack is a simple 
+    implementation of a stack. The LabStack is used by the LabScript 
+    interpreter to store intermediate values. A newly initialized LabStack is
+    always empty. 
+    """
+
+    def __init__(self):
+        self.items = []    
+        self.sp = 0
+        return  
+
+    def is_empty(self):
+        """
+        Tests whether the stack is empty and returns true if yes, false 
+        otherwise.
+        """
+        return self.sp == 0
+
+    def size(self):
+        """
+        Returns the number of items currently in the stack.
+        """
+        return self.sp
+
+    def peek(self):
+        """
+        Returns the item at the top of the stack without removing it from the
+        stack. If the stack is empty, None is returned.
+        """
+        if self.is_empty():
+            return None
+        
+        return self.items[self.sp-1]
+
+    def push(self, item):
+        """
+        Appends a new item to the top of the stack and increases the stack 
+        pointer by 1.
+        """
+        self.items.append(item)
+        self.sp += 1
+        return
+
+    def pop(self):
+        """
+        Removes the item at the top of the stack and returns it and the stack
+        pointer is decreased by 1. If the stack is empty, None is returned.
+        """
+        if self.is_empty():
+            return None
+        
+        self.sp -= 1
+        return self.items.pop()
+
+    def set_sp(pos):
+        """
+        Sets the stack pointer to pos and alters the stack accordingly:
+        If pos is greater than the current sp, the stack is made bigger and
+        the new items are initialized with None. If pos is less than the 
+        current sp, the stack is made smaller and all items above the current
+        sp are simply removed from the stack.
+        """
+        while(sp != pos):
+            if pos > self.sp:
+                self.push(None)
+            else:
+                self.pop()
+        return   
+
+    def print_stack(self):
+        """
+        Prints all items on the stack, from top to bottom.
+        """
+        for i in self.items[::-1]:
+            print(str(i))
+        return
+
+    
+class LabScript:
+    """
+    LabScript is a simple imperative stack-based script language. This class
+    implements a script as a List of commands to be executed from left to
+    right. The items in the list of commands can be any data or an operation.
+
+    USAGE:
+    The constructor is called using a string that represents the script. 
+    This string is a long chain consisting of commands, which are arbitrary
+    substrings, each separated by a whitespace. If a command substring matches
+    an opcode string, as specified in the OPLIST below, the interpreter will
+    parse opcode into an operation and execute its behavior. Any other command
+    will be simply pushed onto the stack as data.
+    """
+
+    """
+    Following is an overview  of all implemented operations sorted after area
+    of application.
+    For more information go to https://en.bitcoin.it/wiki/Script
+    or read the explanation within the op-implementation below:
+
+        Constants:
+
+            OP_0
+            OP_FALSE
+            OP_1NEGATE
+            OP_1
+            OP_TRUE
+
+        Flow Control:
+
+            OP_NOP
+            OP_IF
+            OP_NOTIF
+            OP_ELSE
+            OP_ENDIF
+            OP_VERIFY
+            OP_RETURN
+
+        Stack:
+
+            OP_TOALTSTACK
+            OP_FROMALTSTACK
+            OP_IFDUP
+            OP_DROP
+            OP_DUP
+            OP_NIP
+            OP_OVER
+            OP_PICK
+            OP_ROLL
+            OP_ROT
+            OP_SWAP
+            OP_TUCK
+            OP_2DROP
+            OP_2DUP
+            OP_3DUP
+            OP_2OVER
+            OP_2ROT
+            OP_2SWAP
+
+        Splice:
+
+            -
+
+        Bitwise logic:
+
+            OP_INVERT
+            OP_AND
+            OP_OR
+            OP_XOR
+            OP_EQUAL
+            OP_EQUALVERIFY
+
+        Arithmetic:
+
+            OP_1ADD
+            OP_1SUB
+            OP_2MUL
+            OP_2DIV
+            OP_NOT
+            OP_0NOTEQUAL
+            OP_ADD
+            OP_SUB
+            OP_MUL
+            OP_DIV
+            OP_MOD
+            OP_BOOLAND
+            OP_BOOLOR
+            OP_NUMEQUAL
+            OP_NUMEQUALVERIFY
+            OP_NUMNOTEQUAL
+            OP_LESSTHAN
+            OP_GREATERTHAN
+            OP_LESSTHANOREQUAL
+            OP_GREATERTHANOREQUAL
+            OP_MIN
+            OP_MAX
+            OP_WITHIN
+
+        Crypto:
+
+            OP_RIPDEM160
+            OP_SHA1
+            OP_SHA256
+            OP_HASH160
+            OP_HASH256
+            OP_CHECKSIG
+            OP_CHECKSIGVERIFY
+            #OP_CHECKMULTISIG ???
+            #OP_CHECKMULTISIGVERIFY ???
+
+        Locktime:
+
+            OP_CHECKLOCKTIME
+            OP_CHECKLOCKTIMEVERIFY
+            OP_NOP2
+
+        Reserved words:
+
+            OP_NOP1
+            OP_NOP4
+            OP_NOP5
+            OP_NOP6
+            OP_NOP7
+            OP_NOP8
+            OP_NOP9
+            OP_NOP10
+    """
+
+    def __init__(self, list_of_commands: str):
+        self.prog_queue = list_of_commands.split()
+        self.pc = 1 # program counter
+        self.pend = len(self.prog_queue) # end of program
+
+        self.valid = True # is the script valid? if not, better not use it
+                            # for anything important.
+
+        self.if_endif_syntax_check()
+        return
+
+
+    def to_string(self):
+        schtring_aeeh_string = ""
+        for i in self.prog_queue:
+            schtring_aeeh_string+=str(i)+" "
+
+        schtring_aeeh_string = schtring_aeeh_string[:-1]
+        return schtring_aeeh_string 
+
+
+
+    # operation implementations
+
+    def op_ripemd160():
+        #The input is hashed using RIPEMD160.
+        #DONE
+
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        ripemd160 = hashlib.new('ripemd160')
+        ripemd160.update(str(prog_stack.pop()).encode('utf-8'))
+        ripemd160 = hexlify(ripemd160.digest())
+        prog_stack.push(ripemd160.decode('utf-8'))
+        return
+
+    def op_sha1():
+        #The input is hashed using SHA-1.
+        #DONE
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        sha1 = hashlib.sha1()
+        sha1.update(str(prog_stack.pop()).encode('utf-8'))
+        sha1 = hexlify(sha1.digest())
+        prog_stack.push(sha1.decode('utf-8'))
+        return
+    def op_sha256():
+        #The input is hashed using SHA-256.
+        #DONE
+
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        sha256 = hashlib.sha256()
+        sha256.update(str(prog_stack.pop()).encode('utf-8'))
+        sha256 = hexlify(sha256.digest())
+        prog_stack.push(sha256.decode('utf-8'))
+        return
+
+    def op_hash256():
+        #The input is hashed two times with SHA-256.
+        #DONE
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        sha256 = hashlib.sha256()
+        sha256.update(str(prog_stack.pop()).encode('utf-8'))
+        sha256 = hexlify(sha256.digest())
+        prog_stack.push(sha256.decode('utf-8'))
+
+        sha256 = hashlib.sha256()
+        sha256.update(str(prog_stack.pop()).encode('utf-8'))
+        sha256 = hexlify(sha256.digest())
+        prog_stack.push(sha256.decode('utf-8'))
+        return
+
+    def op_hash160():
+        #The input is hashed twice: first with SHA-256 and then with
+        # RIPEMD-160.
+        #DONE
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        sha256 = hashlib.sha256()
+        sha256.update(str(prog_stack.pop()).encode('utf-8'))
+        sha256 = hexlify(sha256.digest())
+        prog_stack.push(sha256.decode('utf-8'))
+
+        ripemd160 = hashlib.new('ripemd160')
+        ripemd160.update(str(prog_stack.pop()).encode('utf-8'))
+        ripemd160 = hexlify(ripemd160.digest())
+        prog_stack.push(ripemd160.decode('utf-8'))
+        return
+
+    def op_checksig():
+        # The signature used by OP_CHECKSIG must be a valid signature for
+        # this hash and public key.
+        #If it is, 1 is returned, 0 otherwise.
+
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            prog_stack.push(str(0))
+            return
+
+        pubKey = Key.from_json_compatible(prog_stack.pop())
+
+        tx_hash = transaction.get_hash()
+
+        sig = unhexlify(prog_stack.pop())
+
+        if pubKey.verify_sign(tx_hash, sig):
+            prog_stack.push(str(1))
+            return
+
+        logging.warning("Signature not verified")
+        prog_stack.push(str(0))
+        return
+
+    def op_checksigverify():
+        #runs checksig and verify afterwards
+        
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            prog_stack.push(str(0))
+            return
+
+        mystring = "-----BEGIN PUBLIC KEY-----" + prog_stack.pop() + "-----END PUBLIC KEY-----"
+        pubKey = bytes(mystring, 'utf-8')
+        sig = unhexlify(prog_stack.pop())
+        #sig = bytes(prog_stack.pop(), 'utf-8')
+
+        signing_service = Key(pubKey)
+
+        hash = transaction.get_hash()
+
+        #verify signature
+        if signing_service.verify_sign(hash, sig):
+            prog_stack.push(str(1))
+            return
+
+        logging.warning("Signature not verified")
+        prog_stack.push(str(0))
+
+        #verify
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        if(not(int(prog_stack.pop()))):
+            logging.warning('Transaction not valid!')
+            invalidate()
+        return
+        
+
+    def op_nop():
+        #Does nothing
+        #DONE
+        pass
+        return
+
+    def op_equal():
+        #Returns 1 if the inputs are exactly equal, 0 otherwise.
+        #DONE
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop() 
+        x2 = prog_stack.pop()
+        
+        if(x1 == x2):
+            prog_stack.push(str(1))
+        else:
+            prog_stack.push(str(0))
+
+        return
+
+    def op_verify():
+        #Marks transaction as invalid if top stack value is not true. The
+        # top stack value is removed.
+        #DONE
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        if(not(int(prog_stack.pop()))):
+            logging.warning('Transaction not valid!')
+            invalidate()
+        return
+
+    def op_if():
+        #DONE
+        # If the top stack value is not False, the statements are executed.
+        # The top stack value is removed. 
+
+            # this operation pushes a new conditional flag onto the
+            # control flow stack according to this scheme:
+            #
+            # control_flow_stack, prog_stack:   push flag
+            # -----------------------------------------------
+            # ALLOW_ALL, TRUE:      push ALLOW_ALL
+            # ALLOW_ALL, FALSE:     push ALLOW_IF_ELSE
+            #
+            # ALLOW_IF_ELSE, TRUE:  push ALLOW_IF
+            # ALLOW_IF_ELSE, FALSE: push ALLOW_IF
+            #
+            # ALLOW_IF, TRUE:       push ALLOW_IF
+            # ALLOW_IF, FALSE:      push ALLOW_IF 
+
+        ALLOW_ALL=0 # Any operation can be executed.
+        ALLOW_IF_ELSE=1 # Nothing but ifs, elses and endifs can be executed.
+        ALLOW_IF=2  # Nothing but ifs and endifs can be executed. 
+
+        flag = control_flow_stack.peek()
+
+        if flag == ALLOW_ALL:
+
+            if(prog_stack.is_empty()):
+                logging.warning("Stack is empty")
+                invalidate()
+                return
+
+            if int(prog_stack.pop()):
+                control_flow_stack.push(ALLOW_ALL)
+            else: 
+                control_flow_stack.push(ALLOW_IF_ELSE)
+        else:
+            control_flow_stack.push(ALLOW_IF)
+
+        return
+
+    def op_notif():
+        #DONE
+        # If the top stack value is False, the statements are executed.
+        # The top stack value is removed. 
+
+            # this operation pushes a new conditional flag onto the
+            # control flow stack. Any operations that follow are only 
+            # executed if the current flag is True. 
+
+        ALLOW_ALL=0 # Any operation can be executed.
+        ALLOW_IF_ELSE=1 # Nothing but ifs, elses and endifs can be executed.
+        ALLOW_IF=2  # Nothing but ifs and endifs can be executed.
+
+        flag = control_flow_stack.peek()
+
+        if flag == ALLOW_ALL:
+
+            if(prog_stack.is_empty()):
+                logging.warning("Not enough arguments")
+                invalidate()
+                return
+
+            if int(prog_stack.pop()):
+                control_flow_stack.push(ALLOW_IF_ELSE)
+            else:
+                control_flow_stack.push(ALLOW_ALL)
+        else:
+            control_flow_stack.push(ALLOW_IF)
+        
+        return
+
+    def op_else():
+        #DONE
+        # If the preceding OP_IF or OP_NOTIF or OP_ELSE was not executed
+        # then these statements are and if the preceding OP_IF or OP_NOTIF
+        # or OP_ELSE was executed then these statements are not. 
+
+            # this operation changes the current conditional flag onto the
+            # control flow stack according to this scheme:
+            #
+            # control_flow_stack, prog_stack:   push flag
+            # -----------------------------------------------
+            # ALLOW_ALL     -> ALLOW_IF_ELSE    
+            # ALLOW_IF_ELSE -> ALLOW_ALL
+            # if the flag is ALLOW_IF, this is never executed.
+
+        ALLOW_ALL=0 # Any operation can be executed.
+        ALLOW_IF_ELSE=1 # Nothing but ifs, elses and endifs can be executed.
+        ALLOW_IF=2  # Nothing but ifs and endifs can be executed.
+
+        flag = control_flow_stack.pop()
+        if flag == ALLOW_ALL:
+            control_flow_stack.push(ALLOW_IF_ELSE)
+        if flag == ALLOW_IF_ELSE:
+            control_flow_stack.push(ALLOW_ALL)
+
+        return
+
+    def op_endif():
+        #DONE
+        # Ends an if/else block.
+        control_flow_stack.pop()
+        return
+
+    def op_return():
+
+        """Marks transaction as invalid.
+        A standard way of attaching extra data to transactions is to add a zero-value output with a
+        scriptPubKey consisting of OP_RETURN followed by exactly one pushdata op. Such outputs are
+        provably unspendable, reducing their cost to the network.
+        Currently it is usually considered non-standard (though valid) for a transaction to have more
+        than one OP_RETURN output or an OP_RETURN output with more than one pushdata op. """
+
+        #DONE
+        prog_stack.push(str(0))
+        logging.warning('Transaction not valid!')
+        invalidate()
+        return           
+
+    def op_dup():
+        #Duplicates the top stack item.
+        #DONE
+
+        if(prog_stack.peek()!=None):
+            prog_stack.push(prog_stack.peek())
+            return
+
+        logging.warning("Stack is empty")
+        invalidate()
+        return
+
+    def op_drop():
+        #Removes the top stack item.
+        #DONE
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is already empty")
+            invalidate()
+            return
+
+        prog_stack.pop()
+        return
+
+    def op_checklocktime():
+
+        global transaction
+
+        #Error Indicator
+        errno = 0
+
+        #Error if Stack is empty
+        if(prog_stack.is_empty() or prog_stack.size() < 2):
+            errno = 1
+
+
+        #if top stack item is greater than the transactions nLockTime field ERROR
+        temp = float(prog_stack.pop())
+        try:
+            timestamp = datetime.fromtimestamp(temp)
+        except TypeError:
+            logging.error("A timestamp needs to be supplied after the OP_CHECKLOCKTIME operation!")
+            invalidate()
+            return
+
+        #TODO we need to make sure that the timezones are being taken care of
+        if(timestamp > datetime.utcnow()):
+            print("You need to wait at least " + str(timestamp - datetime.utcnow()) + " to spend this Tx")
+            errno = 3
+
+        if(errno):
+            #errno = 1 Stack is empty
+            if(errno == 1):
+                logging.warning('Stack is empty!')
+                invalidate()
+                return
+
+            #errno = 2 Top-Stack-Value < 0
+            if(errno == 2):
+                logging.warning('Top-Stack-Value < 0')
+                invalidate()
+                return
+
+            #errno = 3 top stack item is greater than the transactions
+            if(errno == 3):
+                #logging.warning('you need to wait more to unlock the funds!')
+                invalidate()
+                return
+        return
+
+    def op_equalverify():
+        #Same as OP_EQUAL, but runs OP_VERIFY afterward.
+        #DONE
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop() 
+        x2 = prog_stack.pop()
+        
+        if(x1 == x2):
+            prog_stack.push(str(1))
+        else:
+            prog_stack.push(str(0))
+
+        #verify
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        if(not(int(prog_stack.pop()))):
+            logging.warning('Transaction not valid!')
+            invalidate()
+        return
+
+    def op_invert():
+        #Flips all of the bits in the input. disabled at BITCOIN.
+        #DONE
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        prog_stack.push(str(~ int(prog_stack.pop())))
+        return
+
+    def op_and():
+        #Boolean and between each bit in the inputs. disabled at BITCOIN.
+        #DONE
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x2 = int(prog_stack.pop())
+
+        prog_stack.push(str(x1 & x2))
+
+        return
+
+    def op_or():
+        #Boolean or between each bit in the inputs. disabled at BITCOIN.
+        #DONE
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x2 = int(prog_stack.pop())
+
+        prog_stack.push(str(x1 | x2))
+
+        return
+
+    def op_xor():
+        #Boolean exclusive or between each bit in the inputs. disabled at BITCOIN.
+        #DONE
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x2 = int(prog_stack.pop())
+
+        prog_stack.push(str(x1 ^ x2))
+
+        return
+
+    def op_true():
+        #op_1()
+        #The number 1 is pushed onto the stack. 
+        #DONE
+        prog_stack.push(str(1))
+        return
+
+    def op_false():
+        #op_0()
+        #The number 0 is pushed onto the stack.
+        #DONE
+        prog_stack.push(str(0))
+        return
+
+    def op_1negate():
+        #The number -1 is pushed onto the stack.
+        #DONE
+        prog_stack.push(str(-1))
+        return
+
+    def op_toaltstack():
+        #Puts the input onto the top of the alt stack.
+        #Removes it from the main stack.
+        #DONE
+
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+
+        alt_stack.push(prog_stack.pop())
+        return
+
+    def op_fromaltstack():
+        #Puts the input onto the top of the main stack.
+        #Removes it from the alt stack.
+        #DONE
+
+        if(alt_stack.is_empty()):
+            logging.warning("Alt_stack is empty")
+            invalidate()
+            return
+
+        prog_stack.push(alt_stack.pop())
+        return
+
+    def op_ifdup():
+        #If the top stack value is not 0, duplicate it.
+        #DONE
+
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        if(int(prog_stack.peek()) != 0):
+            op_dup()
+
+        return
+
+    def op_nip():
+        #Removes the second-to-top stack item.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        prog_stack.pop()
+        prog_stack.push(x1)
+
+        return
+
+    def op_over():
+        #Copies the second-to-top stack item to the top.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.peek()
+        prog_stack.push(x1)
+        prog_stack.push(x2)
+        return
+
+    def op_pick():
+        #The item n back in the stack is copied to the top.
+
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        n = int(prog_stack.pop())
+
+        if(prog_stack.size() < n):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        stack_list = list()
+
+        for i in range(n):
+            stack_list.append(prog_stack.pop())
+
+        for i in range(n):
+            prog_stack.push(stack_list[n - 1 - i])
+
+        prog_stack.push(stack_list[n-1])
+
+        return
+
+    def op_roll():
+        #The item n back in the stack is moved to the top.
+
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        n = int(prog_stack.pop())
+
+        if(prog_stack.size() < n):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        stack_list = list()
+
+        for i in range(n):
+            stack_list.append(prog_stack.pop())
+
+        for i in range (n-1):
+            prog_stack.push(stack_list[n - 2 - i])
+
+        prog_stack.push(stack_list[n-1])
+
+        return
+
+    def op_rot():
+        #The top three items on the stack are rotated to the left.
+
+        if(prog_stack.size() < 3):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        x3 = prog_stack.pop()
+        
+        prog_stack.push(x1)
+        prog_stack.push(x3)
+        prog_stack.push(x2)
+
+        return
+
+    def op_swap():
+        #The top two items on the stack are swapped.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        
+        prog_stack.push(x1)
+        prog_stack.push(x2)
+
+        return
+
+    def op_tuck():
+        #The item at the top of the stack is copied
+        #and inserted before the second-to-top item.
+        #like cuddling :3 OwO
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        
+        prog_stack.push(x1)
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+
+        return
+
+    def op_2drop():
+        #Removes the top two stack items.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        prog_stack.pop()
+        prog_stack.pop()
+        return
+
+    def op_2dup():
+        #Duplicates the top two stack items.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+
+        return
+
+    def op_3dup():
+        #Duplicates the top three stack items.
+        if(prog_stack.size() < 3):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        x3 = prog_stack.pop()
+        
+        prog_stack.push(x3)
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+        prog_stack.push(x3)
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+
+        return
+
+    def op_2over():
+        #Copies the pair of items two spaces back in the stack to the front.
+        if(prog_stack.size() < 4):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        x3 = prog_stack.pop()
+        x4 = prog_stack.pop()
+        
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+        prog_stack.push(x4)
+        prog_stack.push(x3)
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+
+        return
+
+    def op_2rot():
+        #The fifth and sixth items back are moved to the top of the stack.
+        if(prog_stack.size() < 6):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        x3 = prog_stack.pop()
+        x4 = prog_stack.pop()
+        x5 = prog_stack.pop()
+        x6 = prog_stack.pop()
+        
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+        prog_stack.push(x6)
+        prog_stack.push(x5)
+        prog_stack.push(x4)
+        prog_stack.push(x3)
+
+        return
+
+    def op_2swap():
+        #Swaps the top two pairs of items.
+        if(prog_stack.size() < 4):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x1 = prog_stack.pop()
+        x2 = prog_stack.pop()
+        x3 = prog_stack.pop()
+        x4 = prog_stack.pop()
+        
+        prog_stack.push(x2)
+        prog_stack.push(x1)
+        prog_stack.push(x4)
+        prog_stack.push(x3)
+
+        return
+
+    def op_1add():
+        #1 is added to the input.
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x1 += 1
+        prog_stack.push(str(x1))
+        return
+
+    def op_1sub():
+        #1 is subtracted from the input.
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x1 -= 1
+        prog_stack.push(str(x1))
+        return
+
+    def op_2mul():
+        #The input is multiplied by 2. disabled at BITCOIN.
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x1 *= 2
+        prog_stack.push(str(x1))
+        return
+
+    def op_2div():
+        #The input is divided by 2. disabled at BITCOIN.
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        x1 /= 2
+        prog_stack.push(str(x1))
+        return
+
+    def op_not():
+        #If the input is 0 or 1, it is flipped. Otherwise the output will be 0.
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        
+        if(x1 == 0):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_0notequal():
+        #Returns 0 if the input is 0. 1 otherwise.
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        x1 = int(prog_stack.pop())
+        
+        if(x1 == 0):
+            prog_stack.push(str(0))
+            return
+
+        prog_stack.push(str(1))
+        return
+
+    def op_add():
+        #a is added to b.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        prog_stack.push(str(a + b))
+        return
+
+    def op_sub():
+        #b is subtracted from a.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        prog_stack.push(str(a - b))
+        return
+
+    def op_mul():
+        #a is multiplied by b. disabled at BITCOIN.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        prog_stack.push(str(a * b))
+        return
+
+    def op_div():
+        #a is divided by b. disabled at BITCOIN.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        prog_stack.push(str(a / b))
+        return
+
+    def op_mod():
+        #Returns the remainder after dividing a by b. disabled at BITCOIN.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        prog_stack.push(str(a % b))
+        return
+
+    def op_booland():
+        #If both a and b are not "" (null string), the output is 1. Otherwise 0.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = prog_stack.pop()
+        b = prog_stack.pop()
+
+        if(a != "" and b != ""):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_boolor():
+        #If a or b is not "" (null string), the output is 1. Otherwise 0.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = prog_stack.pop()
+        b = prog_stack.pop()
+
+        if(a != "" or b != ""):
+            prog_stack.push(str(1))
+            return
+
+        op_false()
+        return
+
+    def op_numequal():
+        #Returns 1 if the numbers are equal, 0 otherwise.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a == b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_numequalverify():
+        #Same as OP_NUMEQUAL, but runs OP_VERIFY afterward.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a == b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+
+        #verify
+        if(prog_stack.is_empty()):
+            logging.warning("Stack is empty")
+            invalidate()
+            return
+
+        if(not(int(prog_stack.pop()))):
+            logging.warning('Transaction not valid!')
+            invalidate()
+        return
+
+    def op_numnotequal():
+        #Returns 1 if the numbers are not equal, 0 otherwise.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a != b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_lessthan():
+        #Returns 1 if a is less than b, 0 otherwise.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a < b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_greaterthan():
+        #Returns 1 if a is greater than b, 0 otherwise.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a > b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_lessthanorequal():
+        #Returns 1 if a is less than or equal to b, 0 otherwise.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a <= b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_greaterthanorequal():
+        #Returns 1 if a is greater than or equal to b, 0 otherwise.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a >= b):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+    def op_min():
+        #Returns the smaller of a and b.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a < b):
+            prog_stack.push(str(a))
+            return
+
+        prog_stack.push(str(b))
+        return
+
+    def op_max():
+        #Returns the larger of a and b.
+        if(prog_stack.size() < 2):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        a = int(prog_stack.pop())
+        b = int(prog_stack.pop())
+
+        if(a > b):
+            prog_stack.push(str(a))
+            return
+
+        prog_stack.push(str(b))
+        return
+
+    def op_within():
+        #Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
+        if(prog_stack.size() < 3):
+            logging.warning("Not enough arguments")
+            invalidate()
+            return
+
+        x = int(prog_stack.pop())
+        mini = int(prog_stack.pop())
+        maxi = int(prog_stack.pop())
+
+        if(mini <= x and x < maxi):
+            prog_stack.push(str(1))
+            return
+
+        prog_stack.push(str(0))
+        return
+
+
+    
+
+    """
+    OPLIST:
+    This is the dictionary used by the fetch-and-execute loop to parse
+    opcode strings into proper operations. Proper operations will then be
+    executed. Any command string that matches one of the opcodes will be
+    treated like an operation.
+    """
+    global operations
+    operations = {
+        # opcode                     # proper operation
+        'OP_RIPEMD160':               op_ripemd160,
+        'OP_SHA1':                    op_sha1,
+        'OP_SHA256':                  op_sha256,
+        'OP_HASH256':                 op_hash256,
+        'OP_HASH160':                 op_hash160,
+        'OP_VERIFYSIG':               op_checksig,
+        'OP_CHECKSIG':                op_checksig,
+        'OP_CHECKSIGVERIFY':          op_checksigverify,
+
+        'OP_NOP':                     op_nop,
+        'OP_EQUAL':                   op_equal,
+        'OP_VERIFY':                  op_verify,
+        'OP_IF':                      op_if,
+        'OP_NOTIF':                   op_notif,
+        'OP_ELSE':                    op_else,
+        'OP_ENDIF':                   op_endif,
+        'OP_RETURN':                  op_return,
+
+        'OP_DUP':                     op_dup,
+        'OP_DROP':                    op_drop,
+
+        'OP_CHECKLOCKTIME':           op_checklocktime,
+        'OP_CHECKLOCKTIMEVERIFY':     op_checklocktime,
+        'OP_NOP2':                    op_checklocktime,
+
+        'OP_EQUALVERIFY':             op_equalverify,
+        'OP_INVERT':                  op_invert,
+        'OP_AND':                     op_and,
+        'OP_OR':                      op_or,
+        'OP_XOR':                     op_xor,
+        'OP_TRUE':                    op_true,
+        'OP_1':                       op_true,
+        'OP_FALSE':                   op_false,
+        'OP_0':                       op_false,
+        'OP_1NEGATE':                 op_1negate,
+
+        'OP_TOALTSTACK':              op_toaltstack,
+        'OP_FROMALTSTACK':            op_fromaltstack,
+        'OP_IFDUP':                   op_ifdup,
+        'OP_NIP':                     op_nip,
+        'OP_OVER':                    op_over,
+        'OP_PICK':                    op_pick,
+        'OP_ROLL':                    op_roll,
+        'OP_ROT':                     op_rot,
+        'OP_SWAP':                    op_swap,
+        'OP_TUCK':                    op_tuck,
+        'OP_2DROP':                   op_2drop,
+        'OP_2DUP':                    op_2dup,
+        'OP_3DUP':                    op_3dup,
+        'OP_2OVER':                   op_2over,
+        'OP_2ROT':                    op_2rot,
+        'OP_2SWAP':                   op_2swap,
+
+        'OP_1ADD':                    op_1add,
+        'OP_1SUB':                    op_1sub,
+        'OP_2MUL':                    op_2mul,
+        'OP_2DIV':                    op_2div,
+        'OP_NOT':                     op_not,
+        'OP_0NOTEQUAL':               op_0notequal,
+        'OP_ADD':                     op_add,
+        'OP_SUB':                     op_sub,
+        'OP_MUL':                     op_mul,
+        'OP_DIV':                     op_div,
+        'OP_MOD':                     op_mod,
+        'OP_BOOLAND':                 op_booland,
+        'OP_BOOLOR':                  op_boolor,
+        'OP_NUMEQUAL':                op_numequal,
+        'OP_NUMEQUALVERIFY':          op_numequalverify,
+        'OP_NUMNOTEQUAL':             op_numnotequal,
+        'OP_LESSTHAN':                op_lessthan,
+        'OP_GREATERTHAN':             op_greaterthan,
+        'OP_LESSTHANOREQUAL':         op_lessthanorequal,
+        'OP_GREATERTHANOREQUAL':      op_greaterthanorequal,
+        'OP_MIN':                     op_min,
+        'OP_MAX':                     op_max,
+        'OP_WITHIN':                  op_within,
+
+        'OP_NOP1':                    op_nop,
+        'OP_NOP4':                    op_nop,
+        'OP_NOP5':                    op_nop,
+        'OP_NOP6':                    op_nop,
+        'OP_NOP7':                    op_nop,
+        'OP_NOP8':                    op_nop,
+        'OP_NOP9':                    op_nop,
+        'OP_NOP10':                   op_nop,
+        }
+
+    def if_endif_syntax_check(self):
+        """
+        Checks whether the program contains proper if/endif syntax as
+        specified in the documentation. 
+        """
+        global operations
+
+        counter = 0
+
+        pq_copy = self.prog_queue.copy()
+        # go through the program queue and convert all opcodes into
+            # proper operations
+
+        ifs = {'OP_IF', 'OP_NOTIF'}
+        elses = {'OP_ELSE'}
+        endifs = {'OP_ENDIF'}
+
+        # Ifs increase the counter, endifs decrease it. Program is only valid
+        # when the counter is 0 at the end and all existing elses occured
+        # when the counter was greater than zero.
+        for command in pq_copy:
+            if command in ifs:
+                counter += 1
+            if command in endifs:
+                counter -= 1
+            if command in elses and counter == 0:
+                invalidate()
+
+        if counter != 0:
+            invalidate()
+
+
+    def execute_script(self, prog_stacky: LabStack, printstack = 0):
+        """
+        Calling this instance method upon a LabScript instance executes the
+        script. 
+        """
+        global prog_stack
+        prog_stack = prog_stacky
+        global validity
+        validity = True
+
+        global alt_stack
+        alt_stack = LabStack()  # program memory
+
+        global control_flow_stack
+        control_flow_stack = LabStack()
+
+        """
+            stack used for control flow. The topmost entry informs the program
+            whether the oncoming flow of operations can be executed, as allowed
+            or disallowed by any recent conditional operations. this acts like
+            a state machine. We need these flags to prevent execution of else
+            statements across layered if-blocks.
+        """
+
+        ALLOW_ALL=0 # Any operation can be executed.
+        ALLOW_IF_ELSE=1 # Nothing but ifs, elses and endifs can be executed.
+        ALLOW_IF=2  # Nothing but ifs and endifs can be executed. 
+        # To understand what all of this crap means, execute this code on
+        # paper and write down what flags are pushed when:
+        """    
+             OP_IF (true)
+             OP_NOP
+             OP_ELSE
+             OP_IF
+             OP_NOP
+             OP_ELSE
+             OP_NOP
+             OP_ENDIF
+             OP_ELSE
+             OP_NOP
+             OP_ENDIF
+        """
+        
+        control_flow_stack.push(ALLOW_ALL)
+
+        
+        #This loop keeps fetching commands from the prog_queue and tries to
+        #interpret and execute them, until end of program is reached.
+        while(self.pc <= self.pend):
+
+            #global validity
+            self.valid = validity
+            # Check for validity. If invalid, stop executing.
+            if not self.valid:
+                logging.warning("[!] Error: Invalid.")
+                return 0
+
+            next_item = self.prog_queue[self.pc-1] # Fetch next item
+            operationstring = next_item
+
+            pushed = 0
+
+            # Check if item is data or opcode. If data, push onto stack.
+            if (next_item not in operations):
+                if control_flow_stack.peek() == ALLOW_ALL:
+                    prog_stack.push(next_item)
+                    pushed = 1
+
+                #print
+                if(printstack):
+                    print("--------------------------")
+                    print("SCRIPT: \"" + self.to_string() + "\"")
+                    print("--------------------------")
+                    if(pushed):
+                        print("PUSHED:")
+                    else:
+                        print("DIDN'T PUSH:")
+                    print(str(operationstring) + "\n")
+                    print("COUNTER:")
+                    print("programm_end: " + str(self.pend))
+                    print("programm_step: " + str(self.pc))
+                    print("\n")
+                    print("STACK:")
+                    prog_stack.print_stack()
+                    print("\nCONTROL_FLOW_STACK:")
+                    control_flow_stack.print_stack()
+                    print("--------------------------")
+                    #input()
+
+                self.pc = self.pc + 1
+
+                continue
+            
+            op = operations[next_item] # Proper operation to be executed
+
+            # Check if op_code is op_if or op_endif. Always execute
+            # these operations.
+            if operationstring in {'OP_IF', 'OP_ENDIF'}:
+                op() # EXECUTION
+
+            # Check if op_code is op_else. Only allow this operation if
+            # the control flow flag is ALLOW_ELSE.
+            elif control_flow_stack.peek() == ALLOW_IF_ELSE:
+                if operationstring == 'OP_ELSE':
+                    op() # EXECUTION
+
+            # If op_code is any other proper operation, execute it only
+            # if the current control flow allows it. It won't get performed
+            # if we are in an if/else-block that was evaluated to false.
+            elif control_flow_stack.peek() == ALLOW_ALL:
+                op() # EXECUTION
+
+            #if the printstack argument is given, the stack is printed after each step.
+
+            if(printstack):
+                print("--------------------------")
+                print("SCRIPT: \"" + self.to_string() + "\"")
+                print("--------------------------")
+                print("OPERATION:")
+                print(str(operationstring) + "\n")
+                print("COUNTER:")
+                print("programm_end: " + str(self.pend))
+                print("programm_step: " + str(self.pc) + "\n")
+                print("STACK:")
+                prog_stack.print_stack()
+                print("\nCONTROL_FLOW_STACK:")
+                control_flow_stack.print_stack()
+                print("--------------------------")
+                #input()
+
+            self.pc= self.pc + 1
+
+        # If Progstack is empty or TRUE is on top return true otherwise
+        # invalidate and return FALSE
+        if (prog_stack.is_empty() or (prog_stack.size() == 1 and prog_stack.peek() == '1')):
+            return 1
+        else:
+            invalidate()
+            logging.warning("[!] Error: Invalid Tx.")
+            return 0
+
+        

+ 9 - 12
src/merkle.py

@@ -4,10 +4,10 @@ import json
 import os
 import os
 import sys
 import sys
 import signal
 import signal
-import time
+
 import select
 import select
 from threading import Thread, Condition
 from threading import Thread, Condition
-from typing import Optional, Callable, Tuple, List
+from typing import Callable, Tuple, List
 
 
 from .proof_of_work import ProofOfWork
 from .proof_of_work import ProofOfWork
 from .chainbuilder import ChainBuilder
 from .chainbuilder import ChainBuilder
@@ -16,10 +16,9 @@ from . import mining_strategy
 
 
 __all__ = ['Miner']
 __all__ = ['Miner']
 
 
-
-
 signal.signal(signal.SIGCHLD, signal.SIG_IGN)
 signal.signal(signal.SIGCHLD, signal.SIG_IGN)
 
 
+
 def exit_on_pipe_close(pipe):
 def exit_on_pipe_close(pipe):
     """ Waits until the pipe `pipe` can no longer be used, then kills this process. """
     """ Waits until the pipe `pipe` can no longer be used, then kills this process. """
     poller = select.poll()
     poller = select.poll()
@@ -38,11 +37,11 @@ def start_process(func: Callable) -> Tuple[int, int]:
     """
     """
     rx, wx = os.pipe()
     rx, wx = os.pipe()
     pid = os.fork()
     pid = os.fork()
-    if pid == 0: # child
+    if pid == 0:  # child
         try:
         try:
             os.close(0)
             os.close(0)
             os.closerange(3, wx)
             os.closerange(3, wx)
-            os.closerange(wx + 1, 2**16)
+            os.closerange(wx + 1, 2 ** 16)
 
 
             Thread(target=exit_on_pipe_close, args=(wx,), daemon=True).start()
             Thread(target=exit_on_pipe_close, args=(wx,), daemon=True).start()
 
 
@@ -55,10 +54,11 @@ def start_process(func: Callable) -> Tuple[int, int]:
             traceback.print_exc()
             traceback.print_exc()
             os._exit(1)
             os._exit(1)
         os._exit(0)
         os._exit(0)
-    else: # parent
+    else:  # parent
         os.close(wx)
         os.close(wx)
         return rx, pid
         return rx, pid
 
 
+
 def wait_for_result(pipes: List[int], cls: type):
 def wait_for_result(pipes: List[int], cls: type):
     """
     """
     Waits for one of the pipes in `pipes` to become ready, reads a JSON object from that pipe
     Waits for one of the pipes in `pipes` to become ready, reads a JSON object from that pipe
@@ -77,6 +77,7 @@ def wait_for_result(pipes: List[int], cls: type):
     with os.fdopen(ready[0], "r") as fp:
     with os.fdopen(ready[0], "r") as fp:
         return cls.from_json_compatible(json.load(fp))
         return cls.from_json_compatible(json.load(fp))
 
 
+
 class Miner:
 class Miner:
     """
     """
     Management of a background process that mines for new blocks.
     Management of a background process that mines for new blocks.
@@ -102,7 +103,7 @@ class Miner:
     :ivar _cur_miner_pids: Process ids of our worker processes.
     :ivar _cur_miner_pids: Process ids of our worker processes.
     :vartype _cur_miner_pids: List[int]
     :vartype _cur_miner_pids: List[int]
     :ivar reward_pubkey: The public key to which mining fees and block rewards should be sent to.
     :ivar reward_pubkey: The public key to which mining fees and block rewards should be sent to.
-    :vartype reward_pubkey: Signing
+    :vartype reward_pubkey: Key
     """
     """
 
 
     def __init__(self, proto, reward_pubkey):
     def __init__(self, proto, reward_pubkey):
@@ -187,7 +188,3 @@ class Miner:
             self._stop_mining_for_now()
             self._stop_mining_for_now()
             self._miner_cond.notify()
             self._miner_cond.notify()
         self.chainbuilder.chain_change_handlers.remove(self._chain_changed)
         self.chainbuilder.chain_change_handlers.remove(self._chain_changed)
-
-from .protocol import Protocol
-from .chainbuilder import ChainBuilder
-from .crypto import Signing

+ 20 - 12
src/mining_strategy.py

@@ -3,12 +3,18 @@
 from typing import List
 from typing import List
 
 
 from .block import Block
 from .block import Block
-from .transaction import Transaction, TransactionTarget
+
+from .labscript import *
+from .utils import compute_blockreward_next_block
+
+from .blockchain import Blockchain
+from .crypto import Key
 
 
 __all__ = ['create_block']
 __all__ = ['create_block']
 
 
+
 def create_block(blockchain: 'Blockchain', unconfirmed_transactions: 'List[Transaction]',
 def create_block(blockchain: 'Blockchain', unconfirmed_transactions: 'List[Transaction]',
-                 reward_pubkey: 'Signing') -> 'Block':
+                 reward_pubkey: 'Key') -> 'Block':
     """
     """
     Creates a new block that can be mined.
     Creates a new block that can be mined.
 
 
@@ -17,19 +23,21 @@ def create_block(blockchain: 'Blockchain', unconfirmed_transactions: 'List[Trans
                                      this block.
                                      this block.
     :param reward_pubkey: The key that should receive block rewards.
     :param reward_pubkey: The key that should receive block rewards.
     """
     """
+
+    # sort the uncorfirmed transactions by the transaction fee amount
+    sorted_unconfirmed_tx = sorted(unconfirmed_transactions,
+                                   key=lambda tx: tx.get_transaction_fee(blockchain.unspent_coins), reverse=True)
     transactions = set()
     transactions = set()
-    for t in unconfirmed_transactions:
-        if t.verify(blockchain, transactions):
-            # TODO: choose most profitable of conflicting transactions
+
+    for t in sorted_unconfirmed_tx:
+        if t.validate_tx(blockchain.unspent_coins) and not t.check_tx_collision(transactions):
             transactions.add(t)
             transactions.add(t)
 
 
+    reward = compute_blockreward_next_block(blockchain.head.height)
+    fees = sum(t.get_transaction_fee(blockchain.unspent_coins) for t in transactions)
 
 
-    reward = blockchain.compute_blockreward_next_block()
-    fees = sum(t.get_transaction_fee(blockchain) for t in transactions)
-    trans = Transaction([], [TransactionTarget(reward_pubkey, reward + fees)], [], iv=blockchain.head.hash)
+    trans = Transaction([], [TransactionTarget(TransactionTarget.pay_to_pubkey(reward_pubkey), fees + reward)],
+                        datetime.utcnow(), iv=blockchain.head.hash)
     transactions.add(trans)
     transactions.add(trans)
 
 
-    return Block.create(blockchain, list(transactions))
-
-from .blockchain import Blockchain
-from .crypto import Signing
+    return Block.create(blockchain.compute_difficulty_next_block(), blockchain.head, list(transactions))

+ 9 - 18
src/persistence.py

@@ -1,27 +1,18 @@
 """ Implementation and verification of the proof of work. """
 """ Implementation and verification of the proof of work. """
 
 
+import logging
+
 from datetime import timedelta
 from datetime import timedelta
 from typing import Optional
 from typing import Optional
+from datetime import datetime
 
 
-from .crypto import MAX_HASH
-
-__all__ = ['verify_proof_of_work', 'GENESIS_DIFFICULTY', 'ProofOfWork']
+from .block import Block
 
 
-def verify_proof_of_work(block: 'Block'):
-    """ Verify the proof of work on a block. """
-    return int.from_bytes(block.hash, byteorder='little', signed=False) > (MAX_HASH - MAX_HASH // block.difficulty)
+from .config import *
 
 
-GENESIS_DIFFICULTY = 1000
-"""
-The difficulty of the genesis block.
 
 
-Right now this is the average required number of hashes to compute one valid block.
-"""
+__all__ = ['GENESIS_DIFFICULTY', 'ProofOfWork']
 
 
-DIFFICULTY_BLOCK_INTERVAL = 30
-""" The number of blocks between difficulty changes. """
-DIFFICULTY_TARGET_TIMEDELTA = timedelta(minutes=1)
-""" The time span that it should approximately take to mine `DIFFICULTY_BLOCK_INTERVAL` blocks.  """
 
 
 class ProofOfWork:
 class ProofOfWork:
     """
     """
@@ -40,6 +31,7 @@ class ProofOfWork:
         self.stopped = False
         self.stopped = False
         self.block = block
         self.block = block
         self.success = False
         self.success = False
+        self.init_time = 0
 
 
     def abort(self):
     def abort(self):
         """ Aborts execution of this proof of work. """
         """ Aborts execution of this proof of work. """
@@ -50,13 +42,12 @@ class ProofOfWork:
         Perform the proof of work on a block, until `stopped` becomes True or the proof of
         Perform the proof of work on a block, until `stopped` becomes True or the proof of
         work was successful.
         work was successful.
         """
         """
+        self.init_time = datetime.now()
         hasher = self.block.get_partial_hash()
         hasher = self.block.get_partial_hash()
         while not self.stopped:
         while not self.stopped:
             for _ in range(1000):
             for _ in range(1000):
                 self.block.hash = self.block.finish_hash(hasher.copy())
                 self.block.hash = self.block.finish_hash(hasher.copy())
-                if verify_proof_of_work(self.block):
+                if self.block.verify_proof_of_work():
                     return self.block
                     return self.block
                 self.block.nonce += 1
                 self.block.nonce += 1
         return None
         return None
-
-from .block import Block

+ 14 - 10
src/protocol.py

@@ -24,11 +24,10 @@ from collections import namedtuple
 from threading import Thread, Lock
 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 uuid import UUID, uuid4
+from uuid import uuid4
 from typing import Callable, List, Optional
 from typing import Callable, List, Optional
 
 
-from .block import GENESIS_BLOCK_HASH
-
+from .blockchain import GENESIS_BLOCK_HASH
 
 
 __all__ = ['Protocol', 'PeerConnection', 'MAX_PEERS', 'HELLO_MSG']
 __all__ = ['Protocol', 'PeerConnection', 'MAX_PEERS', 'HELLO_MSG']
 
 
@@ -45,6 +44,7 @@ succeed.
 SOCKET_TIMEOUT = 30
 SOCKET_TIMEOUT = 30
 """ The socket timeout for P2P connections. """
 """ The socket timeout for P2P connections. """
 
 
+
 class PeerConnection:
 class PeerConnection:
     """
     """
     Handles the low-level socket connection to one other peer.
     Handles the low-level socket connection to one other peer.
@@ -59,7 +59,7 @@ class PeerConnection:
     :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: tuple, proto: 'Protocol', sock: socket.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 = sock
         self.socket = sock
@@ -214,6 +214,7 @@ class SocketServer(socketserver.TCPServer):
     def shutdown_request(self, request):
     def shutdown_request(self, request):
         pass
         pass
 
 
+
 class Protocol:
 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
@@ -235,7 +236,7 @@ class Protocol:
     """
     """
 
 
     def __init__(self, bootstrap_peers: 'List[tuple]',
     def __init__(self, bootstrap_peers: 'List[tuple]',
-                 primary_block: 'Block', listen_port: int=0, listen_addr: str=""):
+                 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
@@ -245,6 +246,7 @@ class Protocol:
 
 
         self.block_receive_handlers = []
         self.block_receive_handlers = []
         self.trans_receive_handlers = []
         self.trans_receive_handlers = []
+        self.opening_receive_handlers = []
         self.block_request_handlers = []
         self.block_request_handlers = []
         self._primary_block = primary_block.to_json_compatible()
         self._primary_block = primary_block.to_json_compatible()
         self.peers = []
         self.peers = []
@@ -252,10 +254,10 @@ class Protocol:
         self._callback_counter = 0
         self._callback_counter = 0
         self._callback_counter_lock = Lock()
         self._callback_counter_lock = Lock()
 
 
-
         class IncomingHandler(socketserver.BaseRequestHandler):
         class IncomingHandler(socketserver.BaseRequestHandler):
             """ Handler for incoming P2P connections. """
             """ Handler for incoming P2P connections. """
             proto = self
             proto = self
+
             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:
@@ -267,6 +269,7 @@ class Protocol:
 
 
                 conn = PeerConnection(self.client_address, self.proto, self.request)
                 conn = PeerConnection(self.client_address, self.proto, self.request)
                 self.proto.peers.append(conn)
                 self.proto.peers.append(conn)
+
         self.server = SocketServer((listen_addr, listen_port), IncomingHandler)
         self.server = SocketServer((listen_addr, listen_port), IncomingHandler)
         self.server.serve_forever_bg()
         self.server.serve_forever_bg()
 
 
@@ -296,7 +299,7 @@ class Protocol:
         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: str, msg_param, peer: Optional[PeerConnection], prio: int=1):
+    def received(self, msg_type: str, msg_param, peer: Optional[PeerConnection], prio: int = 1):
         """
         """
         Called by a PeerConnection when a new message was received.
         Called by a PeerConnection when a new message was received.
 
 
@@ -390,10 +393,10 @@ class Protocol:
 
 
     def received_transaction(self, transaction: dict, sender: PeerConnection):
     def received_transaction(self, transaction: dict, sender: PeerConnection):
         """ Someone sent us a transaction. """
         """ Someone sent us a transaction. """
-        transaction = Transaction.from_json_compatible(transaction)
-        logging.debug("%s < transaction %s", sender.peer_addr, hexlify(transaction.get_hash()))
+        tx = Transaction.from_json_compatible(transaction)
+        logging.debug("%s < transaction %s", sender.peer_addr, hexlify(tx.get_hash()))
         for handler in self.trans_receive_handlers:
         for handler in self.trans_receive_handlers:
-            handler(transaction)
+            handler(tx)
 
 
     def received_disconnected(self, _, peer: PeerConnection):
     def received_disconnected(self, _, peer: PeerConnection):
         """
         """
@@ -411,5 +414,6 @@ class Protocol:
         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 .block import Block
 from .transaction import Transaction
 from .transaction import Transaction

+ 25 - 17
src/rpc_client.py

@@ -1,12 +1,14 @@
 """ The RPC functionality used by the wallet to talk to the miner application. """
 """ The RPC functionality used by the wallet to talk to the miner application. """
 
 
 import json
 import json
+import sys
 from typing import List, Tuple, Iterator
 from typing import List, Tuple, Iterator
+from datetime import datetime
 
 
 import requests
 import requests
 
 
 from .transaction import Transaction, TransactionTarget, TransactionInput
 from .transaction import Transaction, TransactionTarget, TransactionInput
-from .crypto import Signing
+from .crypto import Key
 
 
 
 
 class RPCClient:
 class RPCClient:
@@ -19,7 +21,7 @@ class RPCClient:
     def send_transaction(self, transaction: Transaction):
     def send_transaction(self, transaction: Transaction):
         """ Sends a transaction to the miner. """
         """ Sends a transaction to the miner. """
         resp = self.sess.put(self.url + 'new-transaction', data=json.dumps(transaction.to_json_compatible()),
         resp = self.sess.put(self.url + 'new-transaction', data=json.dumps(transaction.to_json_compatible()),
-                        headers={"Content-Type": "application/json"})
+                             headers={"Content-Type": "application/json"})
         resp.raise_for_status()
         resp.raise_for_status()
 
 
     def network_info(self) -> List[Tuple[str, int]]:
     def network_info(self) -> List[Tuple[str, int]]:
@@ -28,40 +30,46 @@ class RPCClient:
         resp.raise_for_status()
         resp.raise_for_status()
         return [tuple(peer) for peer in resp.json()]
         return [tuple(peer) for peer in resp.json()]
 
 
-    def get_transactions(self, pubkey: Signing) -> List[Transaction]:
+    def get_transactions(self, pubkey: Key) -> List[Transaction]:
         """ Returns all transactions involving a certain public key. """
         """ Returns all transactions involving a certain public key. """
         resp = self.sess.post(self.url + 'transactions', data=pubkey.as_bytes(),
         resp = self.sess.post(self.url + 'transactions', data=pubkey.as_bytes(),
-                         headers={"Content-Type": "application/json"})
+                              headers={"Content-Type": "application/json"})
         resp.raise_for_status()
         resp.raise_for_status()
         return [Transaction.from_json_compatible(t) for t in resp.json()]
         return [Transaction.from_json_compatible(t) for t in resp.json()]
 
 
-    def show_balance(self, pubkeys: List[Signing]) -> Iterator[Tuple[Signing, int]]:
+    def show_balance(self, pubkeys: List[Key]) -> Iterator[Tuple[Key, int]]:
         """ Returns the balance of a number of public keys. """
         """ Returns the balance of a number of public keys. """
         resp = self.sess.post(self.url + "show-balance", data=json.dumps([pk.to_json_compatible() for pk in pubkeys]),
         resp = self.sess.post(self.url + "show-balance", data=json.dumps([pk.to_json_compatible() for pk in pubkeys]),
-                         headers={"Content-Type": "application/json"})
+                              headers={"Content-Type": "application/json"})
         resp.raise_for_status()
         resp.raise_for_status()
         return zip(pubkeys, resp.json())
         return zip(pubkeys, resp.json())
 
 
-    def build_transaction(self, source_keys: List[Signing], targets: List[TransactionTarget],
-                          change_key: Signing, transaction_fee: int) -> Transaction:
+    def build_transaction(self, source_keys: List[Key], targets: List[TransactionTarget],
+                          change_key: Key, transaction_fee: int, timestamp: datetime) -> Transaction:
         """
         """
         Builds a transaction sending money from `source_keys` to `targets`, sending any change to the
         Builds a transaction sending money from `source_keys` to `targets`, sending any change to the
         key `change_key` and a transaction fee `transaction_fee` to the miner.
         key `change_key` and a transaction fee `transaction_fee` to the miner.
         """
         """
         resp = self.sess.post(self.url + "build-transaction", data=json.dumps({
         resp = self.sess.post(self.url + "build-transaction", data=json.dumps({
-                "sender-pubkeys": [k.to_json_compatible() for k in source_keys],
-                "amount": sum(t.amount for t in targets) + transaction_fee,
-            }), headers={"Content-Type": "application/json"})
+            "sender-pubkeys": [k.to_json_compatible() for k in source_keys],
+            "amount": sum(t.amount for t in targets) + transaction_fee,
+        }), headers={"Content-Type": "application/json"})
         resp.raise_for_status()
         resp.raise_for_status()
         resp = resp.json()
         resp = resp.json()
         remaining = resp['remaining_amount']
         remaining = resp['remaining_amount']
         if remaining < 0:
         if remaining < 0:
-            print("You do not have sufficient funds for this transaction. ({} missing)".format(-remaining), file=sys.stderr)
+            print("You do not have sufficient funds for this transaction. ({} missing)".format(-remaining),
+                  file=sys.stderr)
             sys.exit(2)
             sys.exit(2)
         elif remaining > 0:
         elif remaining > 0:
-            targets = targets + [TransactionTarget(change_key, remaining)]
+            targets = targets + [TransactionTarget(TransactionTarget.pay_to_pubkey(change_key), remaining)]
 
 
-        inputs = [TransactionInput.from_json_compatible(i) for i in resp['inputs']]
-        trans = Transaction(inputs, targets)
-        trans.sign([source_keys[idx] for idx in resp['key_indices']])
-        return trans
+        temp_inputs = [TransactionInput.from_json_compatible(i) for i in resp['inputs']]
+        temp_trans = Transaction(temp_inputs, targets, timestamp)
+
+        keyidx = resp['key_indices']
+
+        inputs = [(TransactionInput(inp.transaction_hash, inp.output_idx, temp_trans.sign(source_keys[keyidx[i]])))
+                  for i, inp in enumerate(temp_inputs)]
+
+        return Transaction(inputs, targets, timestamp)

+ 557 - 77
src/rpc_server.py

@@ -1,93 +1,573 @@
-""" The RPC functionality the miner provides for the wallet. """
+""" The RPC functionality the miner provides for the wallet and the blockchain explorer.
+All REST-API calls are defined here. """
 
 
+import binascii
 import json
 import json
+import time
+from binascii import hexlify
+from datetime import datetime
+from sys import maxsize
 
 
 import flask
 import flask
+from flask_api import status
 
 
 from .chainbuilder import ChainBuilder
 from .chainbuilder import ChainBuilder
+from .crypto import Key
 from .persistence import Persistence
 from .persistence import Persistence
-from .crypto import Signing
+from .config import DIFFICULTY_BLOCK_INTERVAL
 from .transaction import TransactionInput
 from .transaction import TransactionInput
 
 
+time_format = "%d.%m.%Y %H:%M:%S"  # Defines the format string of timestamps in local time.
+app = flask.Flask(__name__)
+cb = None
+pers = None
+QUERY_PARAMETER_LIMIT = maxsize
+
+
+def datetime_from_utc_to_local(utc_datetime):
+    """ Converts UTC timestamp to local timezone. """
+    now_timestamp = time.time()
+    offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
+    return utc_datetime + offset
+
+
 def rpc_server(port: int, chainbuilder: ChainBuilder, persist: Persistence):
 def rpc_server(port: int, chainbuilder: ChainBuilder, persist: Persistence):
     """ Runs the RPC server (forever). """
     """ Runs the RPC server (forever). """
+    global cb
+    cb = chainbuilder
+    global pers
+    pers = persist
+
+    app.run(port=port)
+
+
+@app.route("/network-info", methods=['GET'])
+def get_network_info():
+    """ Returns the connected peers.
+    Route: `\"/network-info\"`.
+    HTTP Method: `'GET'`
+    """
+    return json.dumps([list(peer.peer_addr)[:2] for peer in cb.protocol.peers if peer.is_connected])
+
+
+@app.route("/new-transaction", methods=['PUT'])
+def send_transaction():
+    """
+    Sends a transaction to the network, and uses it for mining.
+    Route: `\"/new-transaction\"`.
+    HTTP Method: `'PUT'`
+    """
+    cb.protocol.received("transaction", flask.request.json, None, 0)
+    return b""
+
+
+@app.route("/show-balance", methods=['POST'])
+def show_balance():
+    """
+    Returns the balance of a number of public keys.
+    Route: `\"/show-balance\"`.
+    HTTP Method: `'POST'`
+    """
+    pubkeys = {Key.from_json_compatible(pk): i for (i, pk) in enumerate(flask.request.json)}
+    amounts = [0 for _ in pubkeys.values()]
+    for output in cb.primary_block_chain.unspent_coins.values():
+        if output.get_pubkey in pubkeys:
+            amounts[pubkeys[output.get_pubkey]] += output.amount
 
 
-    app = flask.Flask(__name__)
-
-    @app.route("/network-info", methods=['GET'])
-    def get_network_info():
-        """ Returns the connected peers. """
-        return json.dumps([list(peer.peer_addr)[:2] for peer in chainbuilder.protocol.peers if peer.is_connected])
-
-    @app.route("/new-transaction", methods=['PUT'])
-    def send_transaction():
-        """ Sends a transaction to the network, and uses it for mining. """
-        chainbuilder.protocol.received("transaction", flask.request.json, None, 0)
-        return b""
-
-    @app.route("/show-balance", methods=['POST'])
-    def show_balance():
-        """ Returns the balance of a number of public keys. """
-        pubkeys = {Signing.from_json_compatible(pk): i for (i, pk) in enumerate(flask.request.json)}
-        amounts = [0 for _ in pubkeys.values()]
-        for output in chainbuilder.primary_block_chain.unspent_coins.values():
-            if output.recipient_pk in pubkeys:
-                amounts[pubkeys[output.recipient_pk]] += output.amount
-
-        return json.dumps(amounts)
-
-    @app.route("/build-transaction", methods=['POST'])
-    def build_transaction():
-        """
-        Returns the transaction inputs that can be used to build a transaction with a certain
-        amount from some public keys.
-        """
-        sender_pks = {
-                Signing.from_json_compatible(o): i
-                    for i, o in enumerate(flask.request.json['sender-pubkeys'])
-        }
-        amount = flask.request.json['amount']
+    return json.dumps(amounts)
 
 
+
+@app.route("/build-transaction", methods=['POST'])
+def build_transaction():
+    """
+    Returns the transaction inputs that can be used to build a transaction with a certain
+    amount from some public keys.
+    Route: `\"/build-transaction\"`.
+    HTTP Method: `'POST'`
+    """
+    sender_pks = {
+        Key.from_json_compatible(o): i
+        for i, o in enumerate(flask.request.json['sender-pubkeys'])
+    }
+    amount = flask.request.json['amount']
+
+    # TODO maybe give preference to the coins that are already unlocked  when creating a transaction!
+
+    inputs = []
+    used_keys = []
+    for (inp, output) in cb.primary_block_chain.unspent_coins.items():
+        if (output.get_pubkey in sender_pks) and (
+        not output.is_locked):  # here we check is the amount is not locked before creating a Tx
+            amount -= output.amount
+            temp_input = TransactionInput(inp[0], inp[1], "empty sig_script")
+            inputs.append(temp_input.to_json_compatible())
+            used_keys.append(sender_pks[output.get_pubkey])
+            if amount <= 0:
+                break
+
+    if amount > 0:
         inputs = []
         inputs = []
         used_keys = []
         used_keys = []
-        for (inp, output) in chainbuilder.primary_block_chain.unspent_coins.items():
-            if output.recipient_pk in sender_pks:
-                amount -= output.amount
-                inputs.append(inp.to_json_compatible())
-                used_keys.append(sender_pks[output.recipient_pk])
-                if amount <= 0:
-                    break
-
-        if amount > 0:
-            inputs = []
-            used_keys = []
-
-        return json.dumps({
-            "inputs": inputs,
-            "remaining_amount": -amount,
-            "key_indices": used_keys,
-        })
-
-
-    @app.route("/transactions", methods=['POST'])
-    def get_transactions_for_key():
-        """ Returns all transactions involving a certain public key. """
-        key = Signing(flask.request.data)
-        transactions = set()
-        outputs = set()
-        chain = chainbuilder.primary_block_chain
-        for b in chain.blocks:
-            for t in b.transactions:
-                for i, target in enumerate(t.targets):
-                    if target.recipient_pk == key:
-                        transactions.add(t)
-                        outputs.add(TransactionInput(t.get_hash(), i))
-        for b in chain.blocks:
-            for t in b.transactions:
-                for inp in t.inputs:
-                    if inp in outputs:
-                        transactions.add(t)
-
-        return json.dumps([t.to_json_compatible() for t in transactions])
 
 
-    app.run(port=port)
+    return json.dumps({
+        "inputs": inputs,
+        "remaining_amount": -amount,
+        "key_indices": used_keys,
+    })
+
+
+@app.route("/transactions", methods=['POST'])
+def get_transactions_for_key():
+    """
+    Returns all transactions involving a certain public key.
+    Route: `\"/transactions\"`.
+    HTTP Method: `'POST'`
+    """
+    key = Key(flask.request.data)
+    transactions = set()
+    outputs = set()
+    chain = cb.primary_block_chain
+    for b in chain.blocks:
+        for t in b.transactions:
+            for i, target in enumerate(t.targets):
+                if target.get_pubkey == key:
+                    transactions.add(t)
+                    outputs.add((t.get_hash(), i))
+
+    for b in chain.blocks:
+        for t in b.transactions:
+            for inp in t.inputs:
+                if (inp.transaction_hash, inp.output_idx) in outputs:
+                    transactions.add(t)
+
+    return json.dumps([t.to_json_compatible() for t in transactions])
+
+
+@app.route("/explorer/sortedtransactions/<string:key>", methods=['GET'])
+def get_sorted_transactions_for_key(key):
+    """
+    Returns all transactions involving a certain public key.
+    Route: `\"/explorer/sortedtransactions/<string:key>\"`.
+    HTTP Method: `'GET'`
+    """
+    key = Key(binascii.unhexlify(key))
+    all_transactions = {}
+    received_transactions = []
+    sent_transactions = []
+
+    outputs = set()
+    chain = cb.primary_block_chain
+    for b in chain.blocks:
+        for t in b.transactions:
+            for i, target in enumerate(t.targets):
+                if target.get_pubkey == key:
+                    received_transactions.append(t.to_json_compatible())
+                    outputs.add((t.get_hash(), i))
+
+    for b in chain.blocks:
+        for t in b.transactions:
+            for inp in t.inputs:
+                if (inp.transaction_hash, inp.output_idx) in outputs:
+                    sent_transactions.append(t.to_json_compatible())
+
+    for t in sent_transactions:
+        t['timestamp'] = datetime_from_utc_to_local(datetime.strptime(t['timestamp'],
+                                                                      "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+            time_format)
+
+    for t in received_transactions:
+        t['timestamp'] = datetime_from_utc_to_local(datetime.strptime(t['timestamp'],
+                                                                      "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+            time_format)
+
+    all_transactions["sent"] = sent_transactions
+    all_transactions["received"] = received_transactions
+
+    return json.dumps(all_transactions)
+
+
+@app.route("/explorer/addresses", methods=['GET'])
+def get_addresses():
+    """
+    Returns all addresses in the blockchain.
+    Route: `\"/explorer/addresses\"`.
+    HTTP Method: `'GET'`
+    """
+    addresses = set()
+    chain = cb.primary_block_chain
+    for b in chain.blocks:
+        for t in b.transactions:
+            for i, target in enumerate(t.targets):
+                addresses.add(hexlify(target.get_pubkey.as_bytes()).decode())
+    if len(addresses) != 0:
+        return json.dumps([a for a in addresses])
+
+    return json.dumps("Resource not found."), status.HTTP_404_NOT_FOUND
+
+
+@app.route("/explorer/show-balance", methods=['POST'])
+def show_single_balance():
+    """
+    Returns the balance of a public key.
+    Route: `\"/explorer/show-balance\"`
+    HTTP Method: `'POST'`
+    """
+    key = Key(flask.request.data)
+    amount = 0
+    for output in cb.primary_block_chain.unspent_coins.values():
+        if output.get_pubkey == key:
+            amount += output.amount
+    result = {"credit": amount}
+
+    return json.dumps(result)
+
+
+@app.route("/explorer/lasttransactions/<int:amount>", methods=['GET'])
+def get_last_transactions(amount):
+    """
+    Returns last transactions. Number is specified in `amount`.
+    Route: `\"/explorer/lasttransactions/<int:amount>\"`
+    HTTP Method: `'GET'`
+    """
+    last_transactions = []
+    counter = 0
+
+    unconfirmed_tx = cb.unconfirmed_transactions
+
+    for (key, value) in unconfirmed_tx.items():
+        if counter < amount:
+            val = value.to_json_compatible()
+            val['block_id'] = "Pending.."
+            val['block_hash'] = ""
+            val['number_confirmations'] = 0
+            val['timestamp'] = datetime_from_utc_to_local(datetime.strptime(val['timestamp'],
+                                                                            "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+                time_format)
+            last_transactions.append(val)
+            counter += 1
+        else:
+            break
+
+    last_confirmed_transactions = []
+    chain = cb.primary_block_chain
+    for b in reversed(chain.blocks):
+        if not counter < amount:
+            break
+        for t in reversed(b.transactions):
+            if counter < amount:
+                trans = t.to_json_compatible()
+                block = b.to_json_compatible()
+                trans['block_id'] = block['id']
+                trans['block_hash'] = block['hash']
+                trans['number_confirmations'] = chain.head.id - int(block['id'])
+                trans['timestamp'] = datetime_from_utc_to_local(datetime.strptime(trans['timestamp'],
+                                                                                  "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+                    time_format)
+
+                last_confirmed_transactions.append(trans)
+                counter += 1
+            else:
+                break
+
+    last_transactions.extend(last_confirmed_transactions)
+    return json.dumps(last_transactions)
+
+
+@app.route("/explorer/transactions", methods=['GET'])
+def get_transactions():
+    """
+    Returns all transactions.
+    Route: `\"/explorer/transactions\"`
+    HTTP Method: `'GET'`
+    """
+    transactions = []
+    chain = cb.primary_block_chain
+    for b in reversed(chain.blocks):
+        for t in reversed(b.transactions):
+            trans = t.to_json_compatible()
+            block = b.to_json_compatible()
+            trans['block_id'] = block['id']
+            trans['block_hash'] = block['hash']
+            trans['number_confirmations'] = chain.head.id - int(block['id'])
+            transactions.append(trans)
+    for t in transactions:
+        t['timestamp'] = datetime_from_utc_to_local(datetime.strptime(t['timestamp'],
+                                                                      "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+            time_format)
+    return json.dumps(transactions)
+
+
+@app.route("/explorer/transaction/<string:hash>", methods=['GET'])
+def get_transaction_from_hash(hash):
+    """
+    Returns a transaction with specified hash.
+    Route: `\"/explorer/transaction/<string:hash>\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    for b in chain.blocks:
+        for t in b.transactions:
+            if hexlify(t.get_hash()).decode() == hash:
+                trans = t.to_json_compatible()
+                block = b.to_json_compatible()
+                trans['block_id'] = block['id']
+                trans['block_hash'] = block['hash']
+                trans['number_confirmations'] = chain.head.id - int(block['id'])
+                trans['timestamp'] = datetime_from_utc_to_local(datetime.strptime(trans['timestamp'],
+                                                                                  "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+                    time_format)
+                trans['fee'] = t.get_past_transaction_fee(chain)
+                return json.dumps(trans)
+
+    unconfirmed_tx = cb.unconfirmed_transactions
+    for (key, value) in unconfirmed_tx.items():
+        if hexlify(key).decode() == hash:
+            trans = value.to_json_compatible()
+            trans['block_id'] = ""
+            trans['block_hash'] = "Pending..."
+            trans['timestamp'] = datetime_from_utc_to_local(datetime.strptime(trans['timestamp'],
+                                                                              "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+                time_format)
+            trans['fee'] = t.get_past_transaction_fee(chain)
+            return json.dumps(trans)
+
+    return json.dumps("Resource not found."), status.HTTP_404_NOT_FOUND
+
+
+@app.route("/explorer/blocks", methods=['GET'])
+def get_blocks():
+    """
+    Returns all blocks in the blockchain.
+    Route: `\"/explorer/blocks\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    result = []
+    for o in reversed(chain.blocks):
+        block = o.to_json_compatible()
+        block['time'] = datetime_from_utc_to_local(datetime.strptime(block['time'],
+                                                                     "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+            time_format)
+        result.append(block)
+    return json.dumps(result)
+
+
+@app.route("/explorer/lastblocks/<int:amount>", methods=['GET'])
+def get_blocks_amount(amount):
+    """
+    Returns the latest number of blocks in the blockchain.
+    Route: `\"/explorer/lastblocks/<int:amount>\"`
+    HTTP Method: `'GET'`
+    """
+    result = []
+    chain = cb.primary_block_chain
+    counter = 0
+    for b in reversed(chain.blocks):
+        block = b.to_json_compatible()
+        block['time'] = datetime_from_utc_to_local(datetime.strptime(block['time'],
+                                                                     "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+            time_format)
+        result.append(block)
+        counter += 1
+        if counter >= amount:
+            break
+    return json.dumps(result)
+
+
+@app.route("/explorer/blockat/<int:at>", methods=['GET'])
+def get_block_at(at):
+    """
+    Returns block at postion from zero in the blockchain.
+    Route: `\"/explorer/blockat/<int:at>\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    result = chain.blocks[at].to_json_compatible()
+
+    result['time'] = datetime_from_utc_to_local(datetime.strptime(result['time'],
+                                                                  "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+        time_format)
+
+    return json.dumps(result)
+
+
+@app.route("/explorer/block/<string:hash>", methods=['GET'])
+def get_blocks_hash(hash):
+    """
+    Returns block with given hash from the blockchain.
+    Route: `\"/explorer/block/<string:hash>\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    for b in chain.blocks:
+        if hexlify(b.hash).decode() == hash:
+            block = b.to_json_compatible()
+            block['time'] = datetime_from_utc_to_local(datetime.strptime(block['time'],
+                                                                         "%Y-%m-%dT%H:%M:%S.%f UTC")).strftime(
+                time_format)
+            return json.dumps(block)
+
+    return json.dumps("Resource not found."), status.HTTP_404_NOT_FOUND
+
+
+@app.route("/explorer/statistics/hashrate", methods=['GET'])
+def get_hashrate():
+    """
+    Returns the total amount of blocks.
+    Route: `\"/explorer/statistics/hashrate\"`
+    HTTP Method: `'GET'`
+    """
+    parameter = flask.request.args.get('length')
+    if (parameter != None and isinstance(parameter, int)) and parameter > 0 and parameter < QUERY_PARAMETER_LIMIT:
+        user_input_length = parameter
+    else:
+        user_input_length = DIFFICULTY_BLOCK_INTERVAL
+    chain = cb.primary_block_chain
+
+    if chain.head.id <= user_input_length:
+        if (len(chain.blocks)) <= 2:
+            return json.dumps(0)
+        user_input_length = len(chain.blocks) - 1
+
+    block_hashrate = []
+
+    for i in range(user_input_length):
+        first_block = chain.blocks[-i - 1]
+        second_block = chain.blocks[-i - 2]
+        first_time = first_block.time
+        second_time = second_block.time
+        difficulty = first_block.difficulty
+        time_difference = abs((first_time - second_time).seconds)
+        if time_difference == 0:
+            time_difference = 1
+        hashrate = (difficulty / time_difference)
+        block_hashrate.append(hashrate)
+
+    block_hashrate_sum = 0
+    for i in block_hashrate:
+        block_hashrate_sum += i
+
+    block_hashrate_avg = block_hashrate_sum / len(block_hashrate)
+
+    if block_hashrate_avg >= 1000000000:
+        return json.dumps("%.1f" % (block_hashrate_avg / 1000000000) + " Gh/s")
+    if block_hashrate_avg >= 1000000:
+        return json.dumps("%.1f" % (block_hashrate_avg / 1000000) + " Mh/s")
+    if block_hashrate_avg >= 1000:
+        return json.dumps("%.1f" % (block_hashrate_avg / 1000) + " Kh/s")
+
+    return json.dumps("%.2f" % block_hashrate_avg)  # Returns float formatted with only 2 decimals
+
+
+@app.route("/explorer/statistics/tps", methods=['GET'])
+def get_tps():
+    """
+    Returns the average transaction rate over the last <length>- query parameter blocks.
+    Route: `\"/explorer/statistics/tps\"`
+    HTTP Method: `'GET'`
+    """
+    parameter = flask.request.args.get('length')
+    if (parameter != None and isinstance(parameter, int)) and parameter > 0 and parameter < QUERY_PARAMETER_LIMIT:
+        user_input_length = parameter
+    else:
+        user_input_length = DIFFICULTY_BLOCK_INTERVAL
+
+    chain = cb.primary_block_chain
+    if chain.head.id <= user_input_length:
+        # if only genesis block exists, no transacions have been made
+        if (len(chain.blocks)) <= 1:
+            return json.dumps(0)
+        first_block = chain.head
+        # use block after genesis block, because genesis block has hard-coded timestamp
+        second_block = chain.blocks[1]
+        first_time = first_block.time
+        second_time = second_block.time
+    else:
+        first_block = chain.head
+        second_block = chain.blocks[- 1 - user_input_length]
+        first_time = first_block.time
+        second_time = second_block.time
+
+    transactions = 0
+    for i in range(user_input_length):
+        transactions += len(chain.blocks[-1 - i].transactions)
+
+    time_difference = abs((first_time - second_time).seconds)
+    if time_difference == 0:
+        time_difference = 1
+
+    tps = transactions / time_difference
+
+    return json.dumps("%.2f" % tps)  # Returns float formatted with only 2 decimals
+
+
+@app.route("/explorer/statistics/totalblocks", methods=['GET'])
+def get_total_blocks():
+    """
+    Returns the total amount of blocks.
+    Route: `\"/explorer/statistics/totalblocks\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    total_blocks = len(chain.blocks)
+
+    return json.dumps(total_blocks)
+
+
+@app.route("/explorer/statistics/difficulty", methods=['GET'])
+def get_difficulty():
+    """
+    Returns the current difficulty.
+    Route: `\"/explorer/statistics/difficulty\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    current_difficulty = chain.head.difficulty
+
+    return json.dumps(current_difficulty)
+
+
+@app.route("/explorer/statistics/blocktime", methods=['GET'])
+def get_blocktime():
+    """
+    Returns the average time between two blocks.
+    Route: `\"/explorer/statistics/blocktime\"`
+    HTTP Method: `'GET'`
+    """
+    chain = cb.primary_block_chain
+    current_block = chain.head
+
+    if len(chain.blocks) < 2:
+        return json.dumps(0)
+
+    second_block = chain.blocks[1]
+
+    current_timestamp = current_block.time
+    second_timestamp = second_block.time
+    time_difference = abs((current_timestamp - second_timestamp).seconds)
+    total_blocks = int(get_total_blocks()) - 1
+
+    blocktime = time_difference / total_blocks
+
+    return json.dumps("%.2f" % blocktime)  # Returns float formatted with only 2 decimals
+
+
+@app.route('/shutdown', methods=['POST', 'GET'])
+def shutdown():
+    """
+    Shuts down the RPC-Server.
+    Route: `\"/shutdown\"`
+    HTTP Method: `'GET'/'POST'`
+    """
+    shutdown_server()
+    return 'Server shutting down...'
+
+
+def shutdown_server():
+    """
+    Shuts down flask. Needed for pytests.
+    """
+    func = flask.request.environ.get('werkzeug.server.shutdown')
+    if func is None:
+        raise RuntimeError('Not running with the Werkzeug Server')
+    func()

+ 233 - 206
src/transaction.py

@@ -1,206 +1,233 @@
-""" Defines transactions and their inputs and outputs. """
-
-import logging
-from collections import namedtuple
-from binascii import hexlify, unhexlify
-from typing import List, Set
-
-from .crypto import get_hasher, Signing
-
-__all__ = ['TransactionTarget', 'TransactionInput', 'Transaction']
-
-TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
-"""
-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
-"""
-
-
-class TransactionInput(namedtuple("TransactionInput", ["transaction_hash", "output_idx"])):
-    """
-    One transaction input (pointer to 'coin').
-
-    :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
-    """
-
-    @classmethod
-    def from_json_compatible(cls, obj):
-        """ Creates a new object of this class, from a JSON-serializable representation. """
-        return cls(unhexlify(obj['transaction_hash']), int(obj['output_idx']))
-
-    def to_json_compatible(self):
-        """ Returns a JSON-serializable representation of this object. """
-        return {
-            'transaction_hash': hexlify(self.transaction_hash).decode(),
-            'output_idx': self.output_idx,
-        }
-
-
-class Transaction:
-    """
-    A transaction as it was received from the network or a block. To check that this transaction is
-    valid on top of a block chain and in combination with a set of different transactions (from the
-    same block), you can call the `verify` method.
-
-    :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.targets = targets
-        self.signatures = signatures or []
-        self.iv = iv
-        self._hash = None
-
-    def to_json_compatible(self):
-        """ Returns a JSON-serializable representation of this object. """
-        val = {}
-        val['inputs'] = []
-        for inp in self.inputs:
-            val['inputs'].append(inp.to_json_compatible())
-        val['targets'] = []
-        for targ in self.targets:
-            val['targets'].append({
-                'recipient_pk': targ.recipient_pk.to_json_compatible(),
-                'amount': targ.amount,
-            })
-        val['signatures'] = []
-        for sig in self.signatures:
-            val['signatures'].append(hexlify(sig).decode())
-        if self.iv is not None:
-            val['iv'] = hexlify(self.iv).decode()
-        return val
-
-    @classmethod
-    def from_json_compatible(cls, obj: dict):
-        """ Creates a new object of this class, from a JSON-serializable representation. """
-        inputs = []
-        for inp in obj['inputs']:
-            inputs.append(TransactionInput.from_json_compatible(inp))
-        targets = []
-        for targ in obj['targets']:
-            if targ['amount'] <= 0:
-                raise ValueError("invalid amount")
-            targets.append(TransactionTarget(Signing.from_json_compatible(targ['recipient_pk']),
-                                             int(targ['amount'])))
-        signatures = []
-        for sig in obj['signatures']:
-            signatures.append(unhexlify(sig))
-
-        iv = unhexlify(obj['iv']) if 'iv' in obj else None
-        return cls(inputs, targets, signatures, iv)
-
-
-    def get_hash(self) -> bytes:
-        """ Hash this transaction. Returns raw bytes. """
-        if self._hash is None:
-            h = get_hasher()
-            if self.iv is not None:
-                h.update(self.iv)
-
-            h.update(Block._int_to_bytes(len(self.targets)))
-            for target in self.targets:
-                h.update(Block._int_to_bytes(target.amount))
-                h.update(target.recipient_pk.as_bytes())
-
-            h.update(Block._int_to_bytes(len(self.inputs)))
-            for inp in self.inputs:
-                h.update(inp.transaction_hash)
-                h.update(Block._int_to_bytes(inp.output_idx))
-
-            self._hash = h.digest()
-        return self._hash
-
-    def sign(self, private_keys: 'List[Signing]'):
-        """
-        Sign this transaction with the given private keys. The private keys need
-        to be in the same order as the inputs.
-        """
-        for private_key in private_keys:
-            self.signatures.append(private_key.sign(self.get_hash()))
-
-    def _verify_signatures(self, chain: 'Blockchain'):
-        """ Verifies that all inputs are signed and the signatures are valid. """
-        if len(self.signatures) != len(self.inputs):
-            logging.warning("wrong number of signatures")
-            return False
-
-        for (s, i) in zip(self.signatures, self.inputs):
-            if not self._verify_single_sig(s, i, chain):
-                return False
-        return True
-
-    def _verify_single_sig(self, sig: bytes, inp: TransactionInput, chain: 'Blockchain') -> bool:
-        """ Verifies the signature on a single input. """
-        outp = chain.unspent_coins.get(inp)
-        if outp is None:
-            logging.warning("Referenced transaction input could not be found.")
-            return False
-        if not outp.recipient_pk.verify_sign(self.get_hash(), sig):
-            logging.warning("Transaction signature does not verify.")
-            return False
-        return True
-
-    def _verify_single_spend(self, chain: 'Blockchain', other_trans: set) -> bool:
-        """ Verifies that all inputs have not been spent yet. """
-        inp_set = set(self.inputs)
-        if len(self.inputs) != len(inp_set):
-            logging.warning("Transaction may not spend the same coin twice.")
-            return False
-        other_inputs = {i for t in other_trans for i in t.inputs}
-        if other_inputs.intersection(inp_set):
-            logging.warning("Transaction may not spend the same coin as another transaction in the"
-                            " same block.")
-            return False
-
-        if any(i not in chain.unspent_coins for i in self.inputs):
-            logging.debug("Transaction refers to a coin that was already spent.")
-            return False
-        return True
-
-    def get_transaction_fee(self, chain: 'Blockchain'):
-        """ Computes the transaction fees this transaction provides. """
-        if not self.inputs:
-            return 0 # block reward transaction pays no fees
-
-        input_amount = sum(chain.unspent_coins[inp].amount for inp in self.inputs)
-        output_amount = sum(outp.amount for outp in self.targets)
-        return input_amount - output_amount
-
-    def _verify_amounts(self, chain: 'Blockchain') -> bool:
-        """
-        Verifies that transaction fees are non-negative and output amounts are positive.
-        """
-        if self.get_transaction_fee(chain) < 0:
-            logging.warning("Transferred amounts are larger than the inputs.")
-            return False
-        if any(outp.amount <= 0 for outp in self.targets):
-            logging.warning("Transferred amounts must be positive.")
-            return False
-        return True
-
-    def verify(self, chain: 'Blockchain', other_trans: 'Set[Transaction]') -> bool:
-        """ Verifies that this transaction is completely valid. """
-        return self._verify_single_spend(chain, other_trans) and \
-               self._verify_signatures(chain) and self._verify_amounts(chain)
-
-from .blockchain import Blockchain
-from .block import Block
+""" Defines transactions and their inputs and outputs. """
+
+import logging
+
+from datetime import datetime, timezone
+from collections import namedtuple
+from binascii import hexlify, unhexlify
+from typing import List
+
+import src.utils as utils
+
+from .labscript import interpreter
+
+from .crypto import get_hasher, Key
+
+__all__ = ['TransactionTarget', 'TransactionInput', 'Transaction']
+
+
+class TransactionTarget(namedtuple("TransactionTarget", ["pubkey_script", "amount"])):
+    """
+    The recipient of a transaction ('coin').
+    
+    :ivar pubkey_script: The output script of a transaction.
+    :vartype pubkey_script: string
+    :ivar amount: The amount sent.
+    :vartype amount: int
+    """
+
+    @classmethod
+    def from_json_compatible(cls, obj):
+        """ Creates a new object of this class, from a JSON-serializable representation. """
+        return cls(str(obj['pubkey_script']), int(obj['amount']))
+
+    def to_json_compatible(self):
+        """ Returns a JSON-serializable representation of this object. """
+        return {
+            'pubkey_script': self.pubkey_script,
+            'amount': self.amount
+        }
+
+    @classmethod
+    def pay_to_pubkey(self, recipient_pk: Key) -> str:
+        """ Returns a standard pay-to-pubkey script """
+        keystr = recipient_pk.to_json_compatible()
+        return keystr + " OP_CHECKSIG"
+
+    @classmethod
+    def pay_to_pubkey_lock(self, recipient_pk: Key, lock_time: datetime) -> str:
+        """ Returns a pay-to-pubkey script with a lock-time """
+        keystr = recipient_pk.to_json_compatible()
+        return str(lock_time.replace(tzinfo=timezone.utc).timestamp()) + " OP_CHECKLOCKTIME " + keystr + " OP_CHECKSIG"
+
+    @property
+    def get_pubkey(self) -> Key:
+        """ Returns the public key of the target for a standard PAY_TO_PUBKEY transaction"""
+        if self.is_pay_to_pubkey_lock:
+            return Key.from_json_compatible(self.pubkey_script[
+                                            self.pubkey_script.find("OP_CHECKLOCKTIME") + 17:self.pubkey_script.find(
+                                                "OP_CHECKSIG") - 1])
+        elif self.is_pay_to_pubkey:
+            return Key.from_json_compatible(self.pubkey_script[:self.pubkey_script.find(" ")])
+
+        return None
+
+    @property
+    def is_pay_to_pubkey(self) -> bool:
+        op = self.pubkey_script[self.pubkey_script.find(" ") + 1:]
+        return op == "OP_CHECKSIG"
+
+    @property
+    def is_pay_to_pubkey_lock(self) -> bool:
+        # TODO SHOULD! not maybe! SHOULD!  fix this later! it needs to check is the strings are in the correct position within the script
+        # op1 = self.pubkey_script[:self.pubkey_script.find(" "):]
+        # op2 = self.pubkey_script[self.pubkey_script.find("OP_CHECKLOCKTIME"):]
+        return ("OP_CHECKSIG" in self.pubkey_script) and ("OP_CHECKLOCKTIME" in self.pubkey_script)
+
+    @property
+    def is_locked(self) -> bool:
+        if self.is_pay_to_pubkey_lock:
+            timestamp = datetime.utcfromtimestamp(float(self.pubkey_script[:self.pubkey_script.find(" ")]))
+            return timestamp > datetime.utcnow()
+        return False
+
+
+class TransactionInput(namedtuple("TransactionInput", ["transaction_hash", "output_idx", "sig_script"])):
+    """
+    One transaction input (pointer to 'coin').
+
+    :ivar transaction_hash: The hash of the transaction that you are trying to spend.
+    :vartype transaction_hash: bytes
+    :ivar output_idx: The index into `Transaction.targets` of the `transaction_hash`.
+    :vartype output_idx: int
+    :ivar sig_script: The script redeeming the output point by output_idx of the transaction pointed by transaction_hash
+    :vartype sig_script: string
+    """
+
+    @classmethod
+    def from_json_compatible(cls, obj):
+        """ Creates a new object of this class, from a JSON-serializable representation. """
+        return cls(unhexlify(obj['transaction_hash']), int(obj['output_idx']), str(obj['sig_script']))
+
+    def to_json_compatible(self):
+        """ Returns a JSON-serializable representation of this object. """
+        return {
+            'transaction_hash': hexlify(self.transaction_hash).decode(),
+            'output_idx': self.output_idx,
+            'sig_script': self.sig_script
+        }
+
+
+class Transaction:
+    """
+    A transaction as it was received from the network or a block. To check that this transaction is
+    valid on top of a block chain and in combination with a set of different transactions (from the
+    same block), you can call the `verify` method.
+
+    :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 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
+    :ivar timestamp: The time when the transaction was created.
+    :vartype timestamp: datetime
+    """
+
+    def __init__(self, inputs: 'List[TransactionInput]', targets: 'List[TransactionTarget]', timestamp: 'datetime',
+                 iv: bytes = None):
+        self.inputs = inputs
+        self.targets = targets
+        self.timestamp = timestamp
+        self.iv = iv
+        self._hash = None
+
+    def to_json_compatible(self):
+        """ Returns a JSON-serializable representation of this object. """
+        val = {}
+        val['hash'] = hexlify(self.get_hash()).decode()
+        val['inputs'] = []
+        for inp in self.inputs:
+            val['inputs'].append(inp.to_json_compatible())
+        val['targets'] = []
+        for targ in self.targets:
+            val['targets'].append(targ.to_json_compatible())
+        val['timestamp'] = self.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%f UTC")
+        if self.iv is not None:
+            val['iv'] = hexlify(self.iv).decode()
+        return val
+
+    @classmethod
+    def from_json_compatible(cls, obj: dict):
+        """ Creates a new object of this class, from a JSON-serializable representation. """
+        inputs = []
+        for inp in obj['inputs']:
+            inputs.append(TransactionInput.from_json_compatible(inp))
+        targets = []
+        for targ in obj['targets']:
+            targets.append(TransactionTarget.from_json_compatible(targ))
+        timestamp = datetime.strptime(obj['timestamp'], "%Y-%m-%dT%H:%M:%S.%f UTC")
+        iv = unhexlify(obj['iv']) if 'iv' in obj else None
+        return cls(inputs, targets, timestamp, iv)
+
+    def get_hash(self) -> bytes:
+        """ Hash this transaction. Returns raw bytes. """
+        if self._hash is None:
+            h = get_hasher()
+            if self.iv is not None:
+                h.update(self.iv)
+
+            h.update(utils.int_to_bytes(len(self.targets)))
+            for target in self.targets:
+                h.update(utils.int_to_bytes(target.amount))
+                h.update(target.pubkey_script.encode())
+
+            h.update(utils.int_to_bytes(len(self.inputs)))
+            for inp in self.inputs:
+                h.update(inp.transaction_hash)
+                h.update(utils.int_to_bytes(inp.output_idx))
+
+            self._hash = h.digest()
+        return self._hash
+
+    def sign(self, signing_key: Key):
+        return hexlify(signing_key.sign(self.get_hash())).decode()
+
+    def get_transaction_fee(self, unspent_coins: dict):
+        """ Computes the transaction fees this transaction provides. """
+        if not self.inputs:
+            return 0  # block reward transaction pays no fees
+        try:
+            input_amount = sum(unspent_coins[(inp.transaction_hash, inp.output_idx)].amount for inp in self.inputs)
+            output_amount = sum(outp.amount for outp in self.targets)
+            return input_amount - output_amount
+        except:
+            logging.warning("Transaction input is not in unspent coins. Transaction is invalid or spent.")
+            # print(unspent_coins.items())
+            raise ValueError('Transaction input not found.')
+
+    def _verify_amounts(self) -> bool:
+        """
+        Verifies that transaction fees are non-negative and output amounts are positive.
+        """
+        if any(outp.amount < 0 for outp in self.targets):
+            return False
+        return True
+
+    def validate_tx(self, unspent_coins: dict) -> bool:
+        """
+        Validate the transaction
+        """
+
+        if not (self._verify_amounts()):
+            return False
+
+        for inp in self.inputs:
+            if ((inp.transaction_hash, inp.output_idx) not in unspent_coins):
+                return False  # ("The input is not in the unspent transactions database!")
+        for inp in self.inputs:
+            if not (
+            interpreter(inp.sig_script, unspent_coins[(inp.transaction_hash, inp.output_idx)].pubkey_script, self)):
+                return False  # ("Could not validate the Tx!")
+
+        return True
+
+    def check_tx_collision(self, other_tx):
+        for tx in other_tx:
+            for inp_other in tx.inputs:
+                for inp_self in self.inputs:
+                    if inp_self == inp_other:
+                        return True
+        return False

+ 23 - 0
src/utils.py

@@ -0,0 +1,23 @@
+from struct import pack
+from .config import *
+
+import datetime
+
+
+def int_to_bytes(val: int) -> bytes:
+    """ Turns an (arbitrarily long) integer into a bytes sequence. """
+    l = val.bit_length() + 1
+    # we need to include the length in the hash in some way, otherwise e.g.
+    # the numbers (0xffff, 0x00) would be encoded identically to (0xff, 0xff00)
+    return pack("<Q", l) + val.to_bytes(l, 'little', signed=True)
+
+
+def compute_blockreward_next_block(block_num: int) -> int:
+    """ Compute the block reward that is expected for the block following this chain's `head`. """
+    half_lives = block_num // REWARD_HALF_LIFE
+    reward = GENESIS_REWARD // (2 ** half_lives)
+    return reward
+
+
+def compute_lock_time(seconds_to_wait: int) -> datetime:
+    return datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds_to_wait)

+ 32 - 0
templates/about.html

@@ -0,0 +1,32 @@
+{% extends "layout.html" %}
+{% block body %}
+<div class=mycard>
+    <div class="mycard-header">
+        <h1>About the project</h1>
+    </div>
+    <div class="mycard-body about_mycard">
+        This project is a completely new blockchain-based coin, with P2P networking, a consensus mechanism and a wallet
+        interface.<br>
+        The goal of the project is to provide a framework that is easy to modify for people who want to develop
+        proof-of-concepts for blockchain-based technology.<br><br>
+        DO NOT USE THIS AS A REAL CURRENCY TO SEND, RETRIEVE, OR STORE ACTUAL MONEY! <br>
+        While we do not currently know of any way to do so, there are almost certainly bugs in this implementation that
+        would allow anyone to create money out of the blue or take yours away from you.
+        <br>
+        <br>
+        You can find the sourcecode on <a href="https://github.com/ChaAC-FAU/labchain">GitHub.</a>
+        And the documentation on <a href="http://labchain.readthedocs.io/en/latest/">Readthedocs.io.</a>
+        <br>
+        <br>
+        <a target="_blank" href="https://www.fau.de//"><img class="img-fluid" width="40%" src="{{ url_for('static', filename='Friedrich-Alexander-Universität_Erlangen-Nürnberg_logo.svg')}}"></a>
+        <a target="_blank" href="https://www.chaac.tf.fau.de/"><img class="img-fluid" width="40%" src="{{ url_for('static', filename='ChaAC-logo-black.png')}}"></a>
+        <br>
+        <br>
+        LabChain Explorer made by:<br>
+        ©2018 Patrick Dolinic, Jonas Heinrich, Lukas Mahlmeister, Martin Mecklenburg<br>
+        <br>
+        Based on the ChaAC Coin by:<br>
+        ©2017 Jonas Herzig, Malte Kraus
+    </div>
+</div>
+{% endblock %}

+ 103 - 0
templates/address.html

@@ -0,0 +1,103 @@
+{% from "macros.html" import make_short %}
+{% extends "layout.html" %}
+{% block body %}
+<div class=mycard>
+    <div class="mycard-header"><h1>Address</h1></div>
+    <table class=mycard-body>
+        <tr>
+            <th>Hash</th>
+            <td>{{make_short(data.hash[142:])}}</td>
+        </tr>
+        <tr>
+           <th>Credit</th> 
+           <td>{{data.credit}} CHaC</td>
+        </tr>
+        <tr>
+            <td colspan=2>
+                <div>
+                {% for transaction in data.sent -%}
+                <div class="mycard">
+                    <div class="mycard-header">
+                        <h3><a href="/transaction/{{transaction.hash}}">Sent</a></h3>
+                    </div>
+                    <table>
+                        {% if transaction.senders | length > 1%}
+                        <tr>
+                            <th>From:</th>
+                            <td>
+                                {% if transaction.senders %}
+                                    {% for sender in transaction.senders -%}
+                                    {{make_short(sender,"address")}}
+                                    {%- endfor %}
+                                {% else %}
+                                    Mining Reward
+                                {%endif %}
+                            </td>
+                        </tr>
+                        {% endif %}
+                        <tr>
+                            <th>Value:</th>
+                            <th>Address:</th>
+                            
+                        </tr>
+                        {% for target in transaction.targets -%}
+                        <tr>
+                            <td>{{target.amount}}</td>    
+                            <td>{{make_short(target.recipient_pk,"address")}}</td>
+                        </tr>
+                        {%- endfor %}
+                    </table>
+                </div>
+                    
+            {%- endfor %}
+            
+            </div>
+            
+            <div>
+                {% for transaction in data.received -%}
+                <div class="mycard">
+                        <div class="mycard-header">
+                            <h3><a href="/transaction/{{transaction.hash}}">Received</a></h3>
+                        </div>
+                        <table class="mycard-body">
+                                <tr>
+                                    <th>From:</th>
+                                    <td>
+                                        {% if transaction.senders %}
+                                            {% for sender in transaction.senders -%}
+                                            {{make_short(sender,"address")}}
+            
+                                            {%- endfor %}
+                                        {% else %}
+                                            Mining Reward
+                                        {%endif %}
+                                    </td>
+                                </tr>
+                                <tr>
+                                    <th>Value:</th>
+                                    <th>Address:</th>
+                                    
+                                </tr>
+                                    {% for target in transaction.targets -%}
+                                <tr>
+                                    <td>
+                                        {{target.amount}}
+                                    </td>
+                                    <td>
+                                        {{make_short(target.recipient_pk,"address")}}
+                                    </td>
+                                </tr>
+                                {%- endfor %}
+                            </table>
+                        </div>
+            
+                    {%- endfor %}
+                </div>
+            </td>
+        </tr>
+    </table>
+</div>
+    
+
+    
+{% endblock %}

+ 16 - 0
templates/addresses.html

@@ -0,0 +1,16 @@
+{% extends "layout.html" %}
+{% from "macros.html" import make_short %}
+{% block body %}
+
+{% for address in data -%}
+<div class="mycard">
+    <div class="mycard-header">
+        <h3>Address</h3>
+    </div>
+    <div class="card-body">
+        {{make_short(address,"address")}}
+    </div>
+</div>
+{%- endfor %}
+
+{% endblock %}

+ 56 - 0
templates/block.html

@@ -0,0 +1,56 @@
+{% extends "layout.html" %}
+{% block body %}
+
+    <div class="mycard">
+        <div class="mycard-header">
+            <h3>Block</h3>
+        </div>
+        <table class="mycard-body">
+            <tr>
+                <th>ID</th>
+                <td>{{data.id}}</td>
+            </tr>
+            <tr>
+                <th>Hash</th>
+                <td>{{data.hash}}</td>
+            </tr>
+            <tr>
+                <th>Previous Block</th>
+                <td><a href = "{{data.prev_block_hash}}">{{data.prev_block_hash}}</a></td>
+            </tr>
+            <tr>
+                <th>Time</th>
+                <td>{{data.time}}</td>
+            </tr>
+            <tr>
+                <th>Nonce</th>
+                <td>{{data.nonce}}</td>
+            </tr>    
+            <tr>
+                <th>Merkle Root</th>
+                <td>{{data.merkle_root_hash}}</td>
+            </tr>    
+            <tr>
+                <th>Height</th>
+                <td>{{data.height}}</td>
+            </tr>
+            <tr>
+                <th>Difficulty</th>
+                <td>{{data.difficulty}}</td>
+            </tr>
+        </table>       
+    </div>
+    
+    <div>
+        {% for transaction in data.transactions -%}
+    {% if not transaction.senders %}
+        {% include  "mining_transaction.html" %}
+    {% else %}
+        {% include "from_to_transaction.html" %}
+    {%endif %}
+
+        {%- endfor %}
+
+    </div>
+
+    {% endblock %}

+ 16 - 0
templates/blocklist.html

@@ -0,0 +1,16 @@
+{% extends "layout.html" %}
+{% block body %}
+{% for item in data -%}
+<div class="mycard mb-3">
+    <div class="mycard-header">Block {{item.id}}:</div>
+    <div class="card-body">
+        <a href = "/block/{{item.hash}}" ><p class="card-text">{{item.hash}}</p></a>
+    </div>
+</div>
+{%- endfor %}
+{% if nextamount %}
+<div class="showmore mycard">
+    <div class=mycard-header><a href="/blocks/{{nextamount}}"><h1>Show more</h1></a></div>
+</div>
+{% endif %}
+{% endblock %}

+ 76 - 0
templates/from_to_transaction.html

@@ -0,0 +1,76 @@
+{% from "macros.html" import make_short %}
+<div class="transaction mycard">
+        <div class="mycard-header">
+            <h3>Transaction</h3>
+        </div>
+        <table class="mycard-body"> 
+            <tr> 
+                <th>Hash</th>
+                <td>{{make_short(transaction.hash,"transaction")}}</td> 
+            </tr>
+            {% if transaction.block_id %}
+            <tr> 
+                <th>Block ID</th>
+                <td>{{transaction.block_id}}</td> 
+            </tr>
+            {%endif%}
+            <tr> 
+                <th>Block hash</th>
+                    <td>{{make_short(transaction.block_hash,"block")}}</td> 
+                </tr>
+            <tr> 
+                <th>Number of confirmations</th>
+                <td>{{transaction.number_confirmations}}</td> 
+            </tr>
+            {% if transaction.fee %}
+            <tr>
+                <th>Transaction Fee</th>
+                <td>{{transaction.fee}}</td>
+            </tr>
+            {%endif%}
+            <tr>
+                <th>Timestamp</th>
+                <td>{{transaction.timestamp}}</td>
+            </tr>
+            <tr> 
+                <th colspan="2">Sender</th>
+
+            </tr>
+    
+            {% for sender in transaction.senders-%}
+            <tr>
+                <td colspan="2">
+                    {{ make_short(sender,'address') }}
+                </td>
+            </tr>
+    
+            {%- endfor %}
+    
+    
+            <tr>
+                <th>Input Transaction</th>
+                <th>Transaction Signature</th>
+            </tr>
+        {% for i in transaction.inp-%}
+            <tr>
+                <td>{{make_short(i.input.transaction_hash,"transaction")}}</td>
+                <td>{{make_short(i.signature)}}</td>
+            <tr>
+        {%- endfor %}
+    
+    
+            <tr>
+                <th>Recipient Address</th>
+                <th>Amount</th>
+            </tr>
+        {% for target in transaction.targets-%}
+            <tr>
+                <td>{{make_short(target.recipient_pk,"address")}}</td>
+                <td>{{target.amount}}</td>
+            <tr>
+        {%- endfor %}
+    
+        </table>
+            
+    </div>
+<div >

+ 48 - 0
templates/index.html

@@ -0,0 +1,48 @@
+{% extends "layout.html" %}
+{% block body %}
+<div class="mycard">
+    <div class="mycard-header"><h1>Welcome to the LabChain Explorer</h1></div>
+</div>
+
+<div class="statistics mycard">
+    <div class="mycard-header"><h2>Statistics</h2></div>
+    <div class="mycard-body">
+        <div class="mycard stat"><div class="mycard-header">Block Time Average</div> <div class="card-body"> {{data.statistics.blocktime}} Seconds</div></div>
+        <div class="mycard stat"><div class="mycard-header">Total Blocks</div> <div class="card-body">{{data.statistics.totalblocks}}</div></div>
+        <div class="mycard stat"><div class="mycard-header">Current Difficulty</div> <div class="card-body">{{data.statistics.difficulty}}</div></div>
+        <div class="mycard stat"><div class="mycard-header">Hash Rate</div> <div class="card-body">{{data.statistics.hashrate}} Hashes per Second</div></div>
+        <div class="mycard stat"><div class="mycard-header">Transactions per Second</div> <div class="card-body">{{data.statistics.tps}}</div></div>
+    </div>
+</div>
+
+<div class=HorizontalGroup>
+    <div class=half>
+            <div = class="blocks mycard">
+                    <div class="mycard-header"><a href="/blocks"><h2>Recent Blocks</h2></a></div>
+                    {% for item in data.blocks -%}
+                    <div class="mycard mb-3">
+                        <div class="mycard-header">Block {{item.id}}:</div>
+                        <div class="card-body">
+                            <a href = "/block/{{item.hash}}" ><p class="card-text">{{item.hash}}</p></a>
+                        </div>
+                    </div>
+                    {%- endfor %}
+                </div>
+    </div>
+    <div class=half>
+            <div class="transactions mycard">
+                    <div class="mycard-header"><a href="/transactions"><h2>Recent Transactions</h2></a></div>
+                    <div class="mycard-body">
+                        {%for transaction in data.transactions -%}
+                            {% if not transaction.senders %}
+                                {% include "mining_transaction.html" %}
+                            {% else %}
+                                {% include "from_to_transaction.html" %}
+                            {%endif %}
+                        {%- endfor %}
+                    </div>
+                </div>
+    </div>
+</div>
+
+{% endblock %}

+ 39 - 0
templates/layout.html

@@ -0,0 +1,39 @@
+<!doctype html>
+
+<head>
+    <title>LabChain</title>
+    <meta http-equiv="refresh" content="5">
+    <link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='style.css') }}">
+    <link rel="stylesheet" href="{{url_for('static',filename='bootstrap-4.0.0-dist/css/bootstrap.min.css')}}">
+    <script type=text/javascript src="{{ url_for('static', filename='jquery-3.3.1.js') }}"></script>
+    <script src="{{url_for('static',filename='bootstrap-4.0.0-dist/js/bootstrap.min.js')}}"></script>
+</head>
+<body>
+    <div class=page>
+            <nav class="mynavbar navbar navbar-expand-lg navbar-dark navcolor">
+                <a target="_blank" href="https://www.chaac.tf.fau.de/"><img src="{{ url_for('static', filename='ChaAC-logo-white.png')}}"></a><a class="navbar-brand" href="/">LabChain</a>
+                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
+                    <span class="navbar-toggler-icon"></span>
+                </button>
+
+                <div class="collapse navbar-collapse" id="navbarColor01">
+                    <ul class="navbar-nav mr-auto">
+                        <li class="nav-item"><a class="nav-link" href="/blocks">Blocks</a></li> <!-- haben wir-->
+                        <li class="nav-item"><a class="nav-link" href="/transactions">Transactions</a></li> <!-- haben wir-->
+                        <li class="nav-item"><a class="nav-link" href="/addresses">Adresses</a></li>
+                        <li class="nav-item"><a class="nav-link" href="/about">About</a></li>
+
+                        <!--<li class="nav-item"><a class="nav-link" href="/">Wallet</a></li>-->
+                    </ul>
+                </div>
+            </nav>
+
+            <div class=content>
+                {% block body %}{% endblock %}
+            </div>
+    </div>
+    <footer class="footer">
+        ©2018 Patrick Dolinic, Jonas Heinrich, Lukas Mahlmeister, Martin Mecklenburg | The ChaAC Coin Blockchain Explorer
+        | <a class="footer" href="https://github.com/ChaAC-FAU/labchain">GitHub</a> | <a class="footer" href="http://labchain.readthedocs.io/en/latest/">Docs</a>
+    </footer>
+</body>

+ 12 - 0
templates/macros.html

@@ -0,0 +1,12 @@
+{% macro make_short(hash,address) %}
+{% set max_hash_length = 80 %}
+{% if address %}
+    {%if address == "address"%}
+        <a href = "/{{address}}/{{hash}}" >{{hash[142:]|truncate(max_hash_length)}}</a>
+    {%else%}
+        <a href = "/{{address}}/{{hash}}" >{{hash|truncate(max_hash_length)}}</a>
+    {%endif%}
+{% else %}
+    {{hash|truncate(max_hash_length)}}</a>
+{% endif %}
+{% endmacro %}

+ 29 - 0
templates/mining_transaction.html

@@ -0,0 +1,29 @@
+{% from "macros.html" import make_short %}
+
+
+{% if not transaction.senders %}
+<div class="transaction reward mycard" >
+    <div class="mycard-header"><h3>Mining Reward</h3> </div>
+    <table class="mycard-body"> 
+        <tr> 
+            <th>Hash</th>
+            <td>{{make_short(transaction.hash,"transaction")}}</td>
+        </tr>
+        <tr> 
+            <th>Timestamp</th>
+            <td>{{transaction.timestamp}}</td> 
+        </tr>
+        {% for target in transaction.targets-%}
+        <tr> 
+            <th>Miner</th>
+            <td>{{make_short(target.recipient_pk,"address")}}</td>
+        </tr>
+        <tr> 
+            <th>Amount</th>
+            <td>{{target.amount}}</td> 
+        </tr>
+        {%- endfor %}
+    </table>
+</div>
+
+{%endif %}

+ 6 - 0
templates/not_found.html

@@ -0,0 +1,6 @@
+{% extends "layout.html" %}
+{% block body %}
+<div >
+    {{data.type}} <b>{{data.hash}}</b> not found!
+</div>
+{% endblock %}

+ 10 - 0
templates/transaction.html

@@ -0,0 +1,10 @@
+{% extends "layout.html" %}
+{% block body %}
+<div id="transaction-display" class="display-all">
+    {% if not transaction.senders %}
+        {% include  "mining_transaction.html" %}
+    {% else %}
+        {% include "from_to_transaction.html" %}
+    {%endif %}
+</div>
+{% endblock %}

+ 19 - 0
templates/transactions.html

@@ -0,0 +1,19 @@
+{% extends "layout.html" %}
+{% block body %}
+
+<div id="transaction-display">
+{%for transaction in data_array -%}
+    {% if not transaction.senders %}
+        {% include  "mining_transaction.html" %}
+    {% else %}
+        {% include "from_to_transaction.html" %}
+    {%endif %}
+{%- endfor %}
+</div>
+
+{% if nextamount %}
+<div class="showmore mycard">
+    <div class=mycard-header><a href="/transactions/{{nextamount}}"><h1>Show more</h1></a></div>
+</div>
+{% endif %}
+{% endblock %}

+ 46 - 0
templates/unconfirmed_transactions.html

@@ -0,0 +1,46 @@
+
+{% extends "layout.html" %}
+{% block body %}
+
+
+<div>
+        {% for key, transaction in data.items() -%}
+          <div>
+              <h3><a href = "/transaction/{{transaction.hash}}" >{{transaction.hash}}</a></h3>
+          </div>
+
+        <table>
+              <tr>
+                  <th>From:</th>
+                  <th>To:</th>
+                  <th>Value:</th>
+              </tr>
+              <tr>
+                  <td>
+                      {% if transaction.senders %}
+                          {% for sender in transaction.senders -%}
+                                <a href = "/address/{{sender}}" >{{sender}}</a><br>
+                          {%- endfor %}
+                      {% else %}
+                            Mining Reward
+                      {%endif %}
+
+                  </td>
+                  <td>
+                      {% for target in transaction.targets -%}
+                            <a href = "/address/{{target.recipient_pk}}" >{{target.recipient_pk}}</a> <br>
+                      {%- endfor %}
+                  </td>
+                  <td>
+                      {% for target in transaction.targets -%}
+                            {{target.amount}} <br>
+                      {%- endfor %}
+                  </td>
+              </tr>
+             </table>
+
+        {%- endfor %}
+    </div>
+
+
+{% endblock %}

+ 11 - 6
tests/__init__.py

@@ -5,11 +5,12 @@ import logging
 from src.protocol import Protocol
 from src.protocol import Protocol
 from src.mining import Miner
 from src.mining import Miner
 from src.block import GENESIS_BLOCK
 from src.block import GENESIS_BLOCK
-from src.crypto import Signing
+from src.crypto import Key
 from src.transaction import Transaction, TransactionInput, TransactionTarget
 from src.transaction import Transaction, TransactionInput, TransactionTarget
+from datetime import datetime
 
 
 def test_proto():
 def test_proto():
-    reward_key = Signing.generate_private_key()
+    reward_key = Key.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)
@@ -20,18 +21,22 @@ def test_proto():
 
 
     try:
     try:
         sleep(5)
         sleep(5)
-        target_key = Signing.generate_private_key()
+        target_key = Key.generate_private_key()
         chain = miner1.chainbuilder.primary_block_chain
         chain = miner1.chainbuilder.primary_block_chain
         reward_trans = chain.blocks[20].transactions[0]
         reward_trans = 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)
 
 
-        trans = Transaction([trans_in], [trans_targ])
+        trans = Transaction([trans_in], [trans_targ], datetime.utcnow())
         trans.sign([reward_key])
         trans.sign([reward_key])
+
         assert trans.verify(chain, set()), "transaction should be valid"
         assert trans.verify(chain, set()), "transaction should be valid"
 
 
-        proto2.received('transaction', trans.to_json_compatible(), None)
-        sleep(5)
+        proto1.received('transaction', trans.to_json_compatible(), None)
+        print("============Transaction=============")
+
+        sleep(10)
 
 
         chain_len1 = len(miner1.chainbuilder.primary_block_chain.blocks)
         chain_len1 = len(miner1.chainbuilder.primary_block_chain.blocks)
         chain_len2 = len(miner2.chainbuilder.primary_block_chain.blocks)
         chain_len2 = len(miner2.chainbuilder.primary_block_chain.blocks)

+ 186 - 0
tests/test_rest_api.py

@@ -0,0 +1,186 @@
+from datetime import datetime
+
+
+from src.protocol import Protocol
+from src.chainbuilder import ChainBuilder
+from src.rpc_server import rpc_server
+from _thread import start_new_thread
+from tests.utils import *
+from src.rpc_server import shutdown_server
+from tests.test_verifications import block_test
+
+from Crypto.PublicKey import RSA
+
+import requests
+
+sess = requests.Session()
+host = "http://localhost:{}/"
+port = 2345
+
+NUMBER_FIELDS_IN_BLOCK = 9
+
+def start_server(chain):
+    """builds a chainbuilder from a Blockchain, appends and starts a rpc server"""
+    proto = Protocol([], GENESIS_BLOCK, 1337)
+    chainbuilder = ChainBuilder(proto)
+
+    chainbuilder.primary_block_chain = chain
+
+    return start_new_thread(rpc_server, (port, chainbuilder, None))
+
+def rpc_test(count):
+    """starts the rpc server, runs a test and stopps the server"""
+    def decorator(fn):
+        @block_test()
+        def wrapper(gen_chain):
+            chain = gen_chain
+            key = Key.generate_private_key()
+            for i in range(count):
+                reward_trans = Transaction([], [TransactionTarget(key, chain.compute_blockreward_next_block())], datetime.now())
+                chain = extend_blockchain(chain, [reward_trans])
+            start_server(chain)
+            fn(chain)
+            res = sess.get(host.format(port) + 'shutdown')
+            assert res.text == 'Server shutting down...'
+        return wrapper
+    return decorator
+
+
+def get_path(path):
+    """endpoint link"""
+    res = sess.get(host.format(port) + path)
+    assert (res.status_code == 200), "Could not reach endpoint " + path
+    return res
+
+def get_explorer(path):
+    """adds path to explorer"""
+    return get_path("explorer/"+path)
+
+def get_statistics(path):
+    """adds statistics to path"""
+    return get_explorer("statistics/"+path)
+
+@rpc_test(10)
+def test_explorer_availability(chain):
+    """explorer check for addresses, transcactions, blocks, lasttransactions, lastblocks, blockat"""
+    get_explorer('addresses')
+    get_explorer('transactions')
+    get_explorer('blocks')
+
+    get_explorer('lasttransactions/10')
+    get_explorer('lastblocks/10')
+    get_explorer('blockat/10')
+
+@rpc_test(10)
+def test_statistics_availability(chain):
+    """statistics check for hashrate, tps, totalblocks, difficulty, blocktime"""
+    get_statistics('hashrate')
+    get_statistics('tps')
+    get_statistics('totalblocks')
+    get_statistics('difficulty')
+    get_statistics('blocktime')
+    
+
+  
+
+def check_address_data(address):
+    """check for sender and receiver"""
+    assert 'received' in address, "response object does not contain a received"
+    assert 'sent' in address, "response object does not contain a sent"
+
+
+
+@rpc_test(1)
+def test_address_data(chain):
+    """gets the first address and checks the closer information"""
+    hash = get_explorer("addresses").json()[0]
+    res = get_explorer("sortedtransactions/" + hash)
+
+    res_json = res.json()
+
+    check_address_data(res_json)
+
+    
+def checkblock_data(block):
+    """blockcheck for nonce, id, difficulty, height, prev_block_hash, transactions, time, merkle_root_hash, hash"""
+    assert len(block) == NUMBER_FIELDS_IN_BLOCK, "Wrong number of Fields in response object"
+    assert 'nonce' in block, "response object does not contain a nonce"
+    assert 'id' in block, "response object does not contain a id"
+    assert 'difficulty' in block, "response object does not contain a difficulty"
+    assert 'height' in block, "response object does not contain a height"
+    assert 'prev_block_hash' in block, "response object does not contain a prev_block_hash"
+    assert 'transactions' in block, "response object does not contain a transactions"
+    assert 'time' in block, "response object does not contain a time"
+    assert 'merkle_root_hash' in block, "response object does not contain a merkle_root_hash"
+    assert 'hash' in block, "response object does not contain a hash"
+
+
+@rpc_test(1)
+def test_lastblocks_block_data(chain):
+    """checks the blockdata"""
+    res = get_explorer('lastblocks/10')
+
+    res_json = res.json()
+    assert len(res_json) == 2, "Incorrect count of Blocks"
+
+    block = res_json[0]
+    checkblock_data(block)
+
+
+
+@rpc_test(1)
+def test_transactions_data(chain):
+    """check in transaction for hash, block_id, targets, timestamp, number_confirmations, block_hash, signatures, inputs"""
+    res = sess.get(host.format(port) + 'explorer/transactions')
+    assert (res.status_code == 200)
+
+    res_json = res.json()
+    assert len(res_json) == 1, "Incorrect count of transactions"
+
+    transaction = res_json[0]
+    assert len(transaction) == 8, "Wrong number of Fields in resonse object"
+
+    assert 'hash' in transaction, "response object does not contain a hash"
+    assert 'block_id' in transaction, "response object does not contain a block_id"
+    assert 'targets' in transaction, "response object does not contain targets"
+    assert 'timestamp' in transaction, "response object does not contain a timestamp"
+    assert 'number_confirmations' in transaction, "response object does not contain a number_confirmations"
+    assert 'block_hash' in transaction, "response object does not contain a block_hash"
+    assert 'signatures' in transaction, "response object does not contain signatures"
+    assert 'inputs' in transaction, "response object does not contain inputs"
+
+    assert len(transaction["inputs"]) == len(transaction["signatures"]), "Difference in amount of signatures and inputs"
+
+
+
+def check_data_count(subpath):
+    """checks the correctness of the count from the subpath"""
+    @rpc_test(10)
+    def inner(chain):
+        path = "explorer/"+subpath
+        url = host.format(port) + path + "/" +str(5)
+        res = sess.get(url)
+        assert (res.status_code == 200), url
+
+        res_json = res.json()
+        assert len(res_json) == 5, "Incorrect count of " + subpath
+
+        url = host.format(port) + path + "/" + str(20)
+        res = sess.get(url)
+        assert (res.status_code == 200), url
+
+        res_json = res.json()
+        if(subpath == "lastblocks"):
+            assert len(res_json) == 11, "Incorrect count of " + subpath
+        if(subpath == "lasttransactions"):
+            assert len(res_json) == 10, "Incorrect count of " + subpath
+    inner()
+
+def test_blocks_count():
+    check_data_count("lastblocks")
+
+def test_transactions_count():
+    check_data_count("lasttransactions")
+
+
+

+ 19 - 19
tests/test_verifications.py

@@ -1,4 +1,4 @@
-from .utils import *
+from tests.utils import *
 
 
 def block_test(proof_of_work_res=True):
 def block_test(proof_of_work_res=True):
     """ Immediately runs a test that requires a blockchain. """
     """ Immediately runs a test that requires a blockchain. """
@@ -24,8 +24,8 @@ def trans_test(fn):
 
 
     @block_test()
     @block_test()
     def wrapper(gen_chain):
     def wrapper(gen_chain):
-        key = Signing.generate_private_key()
-        reward_trans = Transaction([], [TransactionTarget(key, gen_chain.compute_blockreward_next_block())])
+        key = Key.generate_private_key()
+        reward_trans = Transaction([], [TransactionTarget(key, gen_chain.compute_blockreward_next_block())],datetime.now())
         chain = extend_blockchain(gen_chain, [reward_trans])
         chain = extend_blockchain(gen_chain, [reward_trans])
 
 
         fn(chain, reward_trans)
         fn(chain, reward_trans)
@@ -50,7 +50,7 @@ def test_double_spend2(chain, reward_trans):
 
 
 @trans_test
 @trans_test
 def test_double_spend3(chain, reward_trans):
 def test_double_spend3(chain, reward_trans):
-    trans1 = Transaction([trans_as_input(reward_trans), trans_as_input(reward_trans)], [])
+    trans1 = Transaction([trans_as_input(reward_trans), trans_as_input(reward_trans)], [], datetime.now())
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     trans1.sign([key, key])
     trans1.sign([key, key])
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
@@ -60,7 +60,7 @@ def test_create_money1(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     # create a transaction where the receiver gets 1 more coin than the sender puts in
     # create a transaction where the receiver gets 1 more coin than the sender puts in
     target = TransactionTarget(key, reward_trans.targets[0].amount + 1)
     target = TransactionTarget(key, reward_trans.targets[0].amount + 1)
-    trans1 = Transaction([trans_as_input(reward_trans)], [target])
+    trans1 = Transaction([trans_as_input(reward_trans)], [target], datetime.now())
     trans1.sign([key])
     trans1.sign([key])
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
 
 
@@ -71,7 +71,7 @@ def test_create_money2(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     target1 = TransactionTarget(key, -10)
     target1 = TransactionTarget(key, -10)
     target2 = TransactionTarget(key, reward_trans.targets[0].amount + 10)
     target2 = TransactionTarget(key, reward_trans.targets[0].amount + 10)
-    trans1 = Transaction([trans_as_input(reward_trans)], [target1, target2])
+    trans1 = Transaction([trans_as_input(reward_trans)], [target1, target2],datetime.now())
     trans1.sign([key])
     trans1.sign([key])
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
 
 
@@ -79,15 +79,15 @@ def test_create_money2(chain, reward_trans):
 def test_dupl_block_reward(chain, reward_trans):
 def test_dupl_block_reward(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     target1 = TransactionTarget(key, 1)
     target1 = TransactionTarget(key, 1)
-    trans1 = Transaction([], [target1], iv=b"1")
-    trans2 = Transaction([], [target1], iv=b"2")
+    trans1 = Transaction([], [target1], datetime.now(), iv=b"1")
+    trans2 = Transaction([], [target1], datetime.now(), iv=b"2")
     extend_blockchain(chain, [trans1, trans2], verify_res=False)
     extend_blockchain(chain, [trans1, trans2], verify_res=False)
 
 
 @trans_test
 @trans_test
 def test_negative_block_reward(chain, reward_trans):
 def test_negative_block_reward(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     target1 = TransactionTarget(key, -1)
     target1 = TransactionTarget(key, -1)
-    trans1 = Transaction([], [target1], iv=b"1")
+    trans1 = Transaction([], [target1],datetime.now(), iv=b"1")
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
 
 
 @trans_test
 @trans_test
@@ -98,24 +98,24 @@ def test_zero_block_reward(chain, reward_trans):
 def test_too_large_block_reward(chain, reward_trans):
 def test_too_large_block_reward(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     target1 = TransactionTarget(key, chain.compute_blockreward_next_block() + 1)
     target1 = TransactionTarget(key, chain.compute_blockreward_next_block() + 1)
-    trans1 = Transaction([], [target1], iv=b"1")
+    trans1 = Transaction([], [target1], datetime.now(), iv=b"1")
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
 
 
     trans2 = new_trans(reward_trans, fee=1)
     trans2 = new_trans(reward_trans, fee=1)
     target2 = TransactionTarget(key, chain.compute_blockreward_next_block() + 2)
     target2 = TransactionTarget(key, chain.compute_blockreward_next_block() + 2)
-    trans3 = Transaction([], [target2], iv=b"2")
+    trans3 = Transaction([], [target2], datetime.now(), iv=b"2")
     extend_blockchain(chain, [trans2, trans3], verify_res=False)
     extend_blockchain(chain, [trans2, trans3], verify_res=False)
 
 
 @trans_test
 @trans_test
 def test_max_block_reward(chain, reward_trans):
 def test_max_block_reward(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     target1 = TransactionTarget(key, chain.compute_blockreward_next_block())
     target1 = TransactionTarget(key, chain.compute_blockreward_next_block())
-    trans1 = Transaction([], [target1], iv=b"1")
+    trans1 = Transaction([], [target1], datetime.now(), iv=b"1")
     extend_blockchain(chain, [trans1], verify_res=True)
     extend_blockchain(chain, [trans1], verify_res=True)
 
 
     trans2 = new_trans(reward_trans, fee=1)
     trans2 = new_trans(reward_trans, fee=1)
     target2 = TransactionTarget(key, chain.compute_blockreward_next_block() + 1)
     target2 = TransactionTarget(key, chain.compute_blockreward_next_block() + 1)
-    trans3 = Transaction([], [target2], iv=b"2")
+    trans3 = Transaction([], [target2], datetime.now(), iv=b"2")
     extend_blockchain(chain, [trans2, trans3], verify_res=True)
     extend_blockchain(chain, [trans2, trans3], verify_res=True)
 
 
 @trans_test
 @trans_test
@@ -128,12 +128,12 @@ def test_spend_too_much(chain, reward_trans):
 def test_spend_unknown_coin(chain, reward_trans):
 def test_spend_unknown_coin(chain, reward_trans):
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     inp1 = TransactionInput(reward_trans.get_hash(), len(reward_trans.targets))
     inp1 = TransactionInput(reward_trans.get_hash(), len(reward_trans.targets))
-    trans1 = Transaction([inp1], [])
+    trans1 = Transaction([inp1], [], datetime.now(),)
     trans1.sign([key])
     trans1.sign([key])
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
 
 
     inp2 = TransactionInput(b"invalid", 0)
     inp2 = TransactionInput(b"invalid", 0)
-    trans2 = Transaction([inp2], [])
+    trans2 = Transaction([inp2], [], datetime.now(),)
     trans2.sign([key])
     trans2.sign([key])
     extend_blockchain(chain, [trans2], verify_res=False)
     extend_blockchain(chain, [trans2], verify_res=False)
 
 
@@ -151,22 +151,22 @@ def test_invalid_signature(chain, reward_trans):
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans1], verify_res=False)
     extend_blockchain(chain, [trans2], verify_res=False)
     extend_blockchain(chain, [trans2], verify_res=False)
 
 
-    trans3 = Transaction(trans1.inputs, trans1.targets, signatures=trans1.signatures+trans2.signatures)
+    trans3 = Transaction(trans1.inputs, trans1.targets, datetime.now(), signatures=trans1.signatures+trans2.signatures)
     extend_blockchain(chain, [trans3], verify_res=False)
     extend_blockchain(chain, [trans3], verify_res=False)
 
 
-    trans4 = Transaction(trans1.inputs, trans1.targets, signatures=[])
+    trans4 = Transaction(trans1.inputs, trans1.targets, datetime.now(), signatures=[])
     extend_blockchain(chain, [trans4], verify_res=False)
     extend_blockchain(chain, [trans4], verify_res=False)
 
 
     # too few signatures:
     # too few signatures:
     key = reward_trans.targets[0].recipient_pk
     key = reward_trans.targets[0].recipient_pk
     target1 = TransactionTarget(key, 1)
     target1 = TransactionTarget(key, 1)
     target2 = TransactionTarget(key, 1)
     target2 = TransactionTarget(key, 1)
-    trans5 = Transaction(trans1.inputs, [target1, target2])
+    trans5 = Transaction(trans1.inputs, [target1, target2],datetime.now())
     trans5.sign([key])
     trans5.sign([key])
     extend_blockchain(chain, [trans5], verify_res=True)
     extend_blockchain(chain, [trans5], verify_res=True)
     input1 = TransactionInput(trans5.get_hash(), 0)
     input1 = TransactionInput(trans5.get_hash(), 0)
     input2 = TransactionInput(trans5.get_hash(), 1)
     input2 = TransactionInput(trans5.get_hash(), 1)
-    trans6 = Transaction([input1, input2], [])
+    trans6 = Transaction([input1, input2], [], datetime.now())
     trans6.sign([key, key])
     trans6.sign([key, key])
     trans6.signatures.pop()
     trans6.signatures.pop()
     extend_blockchain(chain, [trans6], verify_res=False)
     extend_blockchain(chain, [trans6], verify_res=False)

+ 2 - 2
tests/utils.py

@@ -35,8 +35,8 @@ def trans_as_input(trans, out_idx=0):
 
 
 def new_trans(old_trans, out_idx=0, fee=0):
 def new_trans(old_trans, out_idx=0, fee=0):
     amount = old_trans.targets[out_idx].amount - fee
     amount = old_trans.targets[out_idx].amount - fee
-    key = Signing.generate_private_key()
+    key = Key.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)],datetime.now())
     trans.sign([old_trans.targets[out_idx].recipient_pk])
     trans.sign([old_trans.targets[out_idx].recipient_pk])
     return trans
     return trans

+ 39 - 30
wallet.py

@@ -9,43 +9,48 @@ __all__ = []
 
 
 import argparse
 import argparse
 import sys
 import sys
+from datetime import datetime
 from binascii import hexlify
 from binascii import hexlify
 from io import IOBase
 from io import IOBase
 from typing import List, Union, Callable, Tuple, Optional
 from typing import List, Union, Callable, Tuple, Optional
 
 
 import logging
 import logging
+
 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
 
 
-from src.block import Block
-from src.blockchain import Blockchain
-from src.transaction import Transaction, TransactionTarget, TransactionInput
-from src.crypto import Signing
+from src.transaction import TransactionTarget
+from src.crypto import Key
 from src.rpc_client import RPCClient
 from src.rpc_client import RPCClient
 
 
-def parse_targets() -> Callable[[str], Union[Signing, int]]:
+
+def parse_targets() -> Callable[[str], Union[Key, int]]:
     """
     """
     Parses transaction targets from the command line: the first value is a path to a key, the
     Parses transaction targets from the command line: the first value is a path to a key, the
     second an amount and so on.
     second an amount and so on.
     """
     """
     start = True
     start = True
+
     def parse(val):
     def parse(val):
         nonlocal start
         nonlocal start
         if start:
         if start:
-            val = Signing.from_file(val)
+            val = Key.from_file(val)
         else:
         else:
             val = int(val)
             val = int(val)
         start = not start
         start = not start
         return val
         return val
+
     return parse
     return parse
 
 
-def private_signing(path: str) -> Signing:
+
+def private_signing(path: str) -> Key:
     """ Parses a path to a private key from the command line. """
     """ Parses a path to a private key from the command line. """
-    val = Signing.from_file(path)
+    val = Key.from_file(path)
     if not val.has_private:
     if not val.has_private:
         raise ValueError("The specified key is not a private key.")
         raise ValueError("The specified key is not a private key.")
     return val
     return val
 
 
-def wallet_file(path: str) -> Tuple[List[Signing], str]:
+
+def wallet_file(path: str) -> Tuple[List[Key], str]:
     """
     """
     Parses the wallet from the command line.
     Parses the wallet from the command line.
 
 
@@ -57,13 +62,14 @@ def wallet_file(path: str) -> Tuple[List[Signing], str]:
             contents = f.read()
             contents = f.read()
     except FileNotFoundError:
     except FileNotFoundError:
         return [], path
         return [], path
-    return list(Signing.read_many_private(contents)), path
+    return list(Key.read_many_private(contents)), path
+
 
 
 def main():
 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("--wallet", type=wallet_file, default=([],None),
+    parser.add_argument("--wallet", type=wallet_file, default=([], None),
                         help="The wallet file containing the private keys to use.")
                         help="The wallet file containing the private keys to use.")
     subparsers = parser.add_subparsers(dest="command")
     subparsers = parser.add_subparsers(dest="command")
 
 
@@ -75,12 +81,12 @@ def main():
     balance = subparsers.add_parser("show-balance",
     balance = subparsers.add_parser("show-balance",
                                     help="Shows the current balance of the public key "
                                     help="Shows the current balance of the public key "
                                          "stored in the specified file.")
                                          "stored in the specified file.")
-    balance.add_argument("key", nargs="*", type=Signing.from_file)
+    balance.add_argument("key", nargs="*", type=Key.from_file)
 
 
     trans = subparsers.add_parser("show-transactions",
     trans = subparsers.add_parser("show-transactions",
                                   help="Shows all transactions involving the public key "
                                   help="Shows all transactions involving the public key "
                                        "stored in the specified file.")
                                        "stored in the specified file.")
-    trans.add_argument("key", nargs="*", type=Signing.from_file)
+    trans.add_argument("key", nargs="*", type=Key.from_file)
 
 
     subparsers.add_parser("show-network",
     subparsers.add_parser("show-network",
                           help="Prints networking information about the miner.")
                           help="Prints networking information about the miner.")
@@ -89,31 +95,32 @@ def main():
     transfer.add_argument("--private-key", type=private_signing,
     transfer.add_argument("--private-key", type=private_signing,
                           default=[], action="append", required=False,
                           default=[], action="append", required=False,
                           help="The private key(s) whose coins should be used for the transfer.")
                           help="The private key(s) whose coins should be used for the transfer.")
-    transfer.add_argument("--change-key", type=Signing.from_file, required=False,
+    transfer.add_argument("--change-key", type=Key.from_file, required=False,
                           help="The private key where any remaining coins are sent to.")
                           help="The private key where any remaining coins are sent to.")
-    transfer.add_argument("--transaction-fee", type=int, default=0,
+    transfer.add_argument("--transaction-fee", type=int, default=1,
                           help="The transaction fee you want to pay to the miner.")
                           help="The transaction fee you want to pay to the miner.")
     transfer.add_argument("target", nargs='*', metavar=("TARGET_KEY AMOUNT"),
     transfer.add_argument("target", nargs='*', metavar=("TARGET_KEY AMOUNT"),
                           type=parse_targets(),
                           type=parse_targets(),
                           help="The private key(s) whose coins should be used for the transfer.")
                           help="The private key(s) whose coins should be used for the transfer.")
+
     args = parser.parse_args()
     args = parser.parse_args()
 
 
     rpc = RPCClient(args.miner_port)
     rpc = RPCClient(args.miner_port)
 
 
-    def show_transactions(keys: List[Signing]):
+    def show_transactions(keys: List[Key]):
         for key in keys:
         for key in keys:
             for trans in rpc.get_transactions(key):
             for trans in rpc.get_transactions(key):
                 print(trans.to_json_compatible())
                 print(trans.to_json_compatible())
             print()
             print()
 
 
-    def create_address(wallet_keys: List[Signing], wallet_path: str, output_files: List[IOBase]):
-        keys = [Signing.generate_private_key() for _ in output_files]
-        Signing.write_many_private(wallet_path, wallet_keys + keys)
+    def create_address(wallet_keys: List[Key], wallet_path: str, output_files: List[IOBase]):
+        keys = [Key.generate_private_key() for _ in output_files]
+        Key.write_many_private(wallet_path, wallet_keys + keys)
         for fp, key in zip(output_files, keys):
         for fp, key in zip(output_files, keys):
             fp.write(key.as_bytes())
             fp.write(key.as_bytes())
             fp.close()
             fp.close()
 
 
-    def show_balance(keys: List[Signing]):
+    def show_balance(keys: List[Key]):
         total = 0
         total = 0
         for pubkey, balance in rpc.show_balance(keys):
         for pubkey, balance in rpc.show_balance(keys):
             print("{}: {}".format(hexlify(pubkey.as_bytes()), balance))
             print("{}: {}".format(hexlify(pubkey.as_bytes()), balance))
@@ -125,17 +132,18 @@ def main():
         for k, v in rpc.network_info():
         for k, v in rpc.network_info():
             print("{}\t{}".format(k, v))
             print("{}\t{}".format(k, v))
 
 
-    def transfer(targets: List[TransactionTarget], change_key: Optional[Signing],
-                 wallet_keys: List[Signing], wallet_path: str, priv_keys: List[Signing]):
-        if not change_key:
-            change_key = Signing.generate_private_key()
-            Signing.write_many_private(wallet_path, wallet_keys + [change_key])
+    def transfer(tx_targets: List[TransactionTarget], change_key: Optional[Key],
+                 wallet_keys: List[Key], wallet_path: str, priv_keys: List[Key]):
 
 
-        trans = rpc.build_transaction(priv_keys, targets, change_key, args.transaction_fee)
-        rpc.send_transaction(trans)
+        if not change_key:
+            change_key = Key.generate_private_key()
+            Key.write_many_private(wallet_path, wallet_keys + [change_key])
 
 
+        timestamp = datetime.utcnow()
+        tx = rpc.build_transaction(priv_keys, tx_targets, change_key, args.transaction_fee, timestamp)
+        rpc.send_transaction(tx)
 
 
-    def get_keys(keys: List[Signing]) -> List[Signing]:
+    def get_keys(keys: List[Key]) -> List[Key]:
         """
         """
         Returns a combined list of keys from the `keys` and the wallet. Shows an error if empty.
         Returns a combined list of keys from the `keys` and the wallet. Shows an error if empty.
         """
         """
@@ -151,7 +159,6 @@ def main():
         if not args.wallet[1]:
         if not args.wallet[1]:
             print("no wallet specified", file=sys.stderr)
             print("no wallet specified", file=sys.stderr)
             parser.parse_args(["--help"])
             parser.parse_args(["--help"])
-
         create_address(*args.wallet, args.file)
         create_address(*args.wallet, args.file)
     elif args.command == 'show-balance':
     elif args.command == 'show-balance':
         show_balance(get_keys(args.key))
         show_balance(get_keys(args.key))
@@ -164,11 +171,13 @@ def main():
         if not args.change_key and not args.wallet[0]:
         if not args.change_key and not args.wallet[0]:
             print("You need to specify either --wallet or --change-key.\n", file=sys.stderr)
             print("You need to specify either --wallet or --change-key.\n", file=sys.stderr)
             parser.parse_args(["--help"])
             parser.parse_args(["--help"])
-        targets = [TransactionTarget(k, a) for k, a in zip(args.target[::2], args.target[1::2])]
+        targets = [TransactionTarget(TransactionTarget.pay_to_pubkey(k), a) for k, a in
+                   zip(args.target[::2], args.target[1::2])]
         transfer(targets, args.change_key, *args.wallet, get_keys(args.private_key))
         transfer(targets, args.change_key, *args.wallet, get_keys(args.private_key))
     else:
     else:
         print("You need to specify what to do.\n", file=sys.stderr)
         print("You need to specify what to do.\n", file=sys.stderr)
         parser.parse_args(["--help"])
         parser.parse_args(["--help"])
 
 
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     main()
     main()

+ 235 - 0
website.py

@@ -0,0 +1,235 @@
+#!/usr/bin/env python3
+
+"""
+The website starts the blockchain explorer, reachable as default under `localhost:80`. The blockchain explorer launches
+its own non-mining miner to get access to the blockchain.
+"""
+
+import argparse
+import requests
+import binascii
+import miner
+from flask import Flask, render_template
+from _thread import start_new_thread
+from flask import abort
+
+app = Flask(__name__)
+sess = requests.Session()
+host = "http://localhost:{}/"
+url = ""
+QUERY_PARAMETER_AVERAGE_LENGTH = 10
+
+
+def main():
+    """
+    Takes arguments: `rpc-port`: The port number where the Blockchain Explorer can find an RPC server. Default is: `40203`
+    `bootstrap-peer`: Address of other P2P peers in the network. If not supplied, no non-mining miner will be started.
+    `listen-address`: The IP address where the P2P server should bind to. `listen-port`: The port where the P2P server should listen.
+    Defaults a dynamically assigned port.
+    """
+    parser = argparse.ArgumentParser(description="Blockchain Explorer.")
+    parser.add_argument("--rpc-port", type=int, default=40203,
+                        help="The port number where the Blockchain Explorer can find an RPC server.")
+    parser.add_argument("--bootstrap-peer",
+                        help="Address of other P2P peers in the network.")
+    parser.add_argument("--listen-address", default="",
+                        help="The IP address where the P2P server should bind to.")
+    parser.add_argument("--listen-port", default=0, type=int,
+                        help="The port where the P2P server should listen. Defaults a dynamically assigned port.")
+
+    args = parser.parse_args()
+
+    global url
+    url = host.format(args.rpc_port)
+
+    if (args.bootstrap_peer):
+        start_new_thread(miner.start_listener, (args.rpc_port, args.bootstrap_peer, args.listen_port, args.listen_address))  # Starts the miner.
+        app.run(host='0.0.0.0', port=80)  # Starts the flask server for the blockchain explorer
+    else:
+        parser.parse_args(["--help"])
+
+
+def append_sender_to_transaction(transaction):
+    """ Reads the transaction inputs for the supplied transaction and adds the senders to the JSON objects. """
+    pks = []
+    for inp in transaction["inputs"]:
+        res = sess.get(url + 'explorer/transaction/' + inp["transaction_hash"])
+        output_idx = inp["output_idx"]
+        pks.append(res.json()["targets"][output_idx]["recipient_pk"])
+
+    counter = 0
+    result = []
+    for inp in transaction["inputs"]:
+        dict = {"input": inp, "signature": transaction["signatures"][counter]}
+        result.append(dict)
+        counter += 1
+
+    transaction["inp"] = result
+    transaction["senders"] = pks
+
+
+@app.route("/")
+def index():
+    """ Index page of the blockchain explorer. Shows statistics, last blocks and last transactions.
+        Route: `\"/\"`. """
+    data = {}
+    data["blocks"] = sess.get(url + 'explorer/lastblocks/10').json()
+    data["statistics"] = get_statistics()
+    transactions = sess.get(url + 'explorer/lasttransactions/5').json()
+    for transaction in transactions:
+        append_sender_to_transaction(transaction)
+    data["transactions"] = transactions
+    return render_template('index.html', data=data)
+
+
+@app.route("/blocks")
+@app.route("/blocks/<int:amount>")
+def blocklist(amount=10):
+    """ Lists all blocks from the blockchain. Optional takes argument as amount of blocks to just return
+     the last amount blocks.
+        Route: `\"/blocks/<optional: int:amount>\"`. """
+    blocks = sess.get(url + 'explorer/lastblocks/' + str(amount)).json()
+    if len(blocks) < amount:
+        return render_template('blocklist.html', data=blocks)
+    else:
+        return render_template('blocklist.html', data=blocks, nextamount=amount * 2)
+
+
+@app.route("/block/<string:hash>")
+def block(hash):
+    """ Lists information about the specified block.
+    Route: `\"/block/<string:hash>\"`. """
+    resp = sess.get(url + 'explorer/block/' + hash)
+    if resp.status_code == 404:
+        return render_template('not_found.html', data={'type': 'Block', 'hash': hash})
+    resp.raise_for_status()
+    json_obj = resp.json()
+
+    for transaction in json_obj["transactions"]:
+        append_sender_to_transaction(transaction)
+
+    return render_template('block.html', data=json_obj)
+
+
+@app.route("/addresses/")
+def addresses():
+    """ Lists all addresses found in the blockchain as sender or receiver of a transaction.
+    Route: `\"/addresses\"`. """
+    resp = sess.get(url + 'explorer/addresses')
+    resp.raise_for_status()
+
+    return render_template('addresses.html', data=resp.json())
+
+
+def try_get_json(addr):
+    try:
+        resp = sess.get(url + addr)
+    except:
+        abort(404)
+        # return render_template('not_found.html', data={'type': 'Address', 'hash': addr})
+
+    if resp.status_code == 404:
+        abort(404)
+        # return render_template('not_found.html', data={'type': 'Address', 'hash': addr})
+    resp.raise_for_status()
+    json_obj = resp.json()
+    return json_obj
+
+
+@app.route("/address/<string:addr>")
+def address(addr):
+    """ Lists information about the specified address. """
+
+    json_obj = try_get_json('explorer/sortedtransactions/' + addr)
+
+    for tr in json_obj["sent"]:
+        append_sender_to_transaction(tr)
+
+    for tr in json_obj["received"]:
+        for target in tr["targets"]:
+            if (target["recipient_pk"]) != addr:
+                tr["targets"].remove(target)
+        append_sender_to_transaction(tr)
+
+    resp_credit = sess.post(url + 'explorer/show-balance', data=binascii.unhexlify(addr),
+                            headers={"Content-Type": "application/json"})
+    resp_credit.raise_for_status()
+
+    json_obj["credit"] = resp_credit.json()["credit"]
+    json_obj["hash"] = addr
+    return render_template('address.html', data=json_obj)
+
+
+@app.route("/transactions")
+@app.route("/transactions/<int:amount>")
+def transactions(amount=10):
+    """ Lists all transactions from the blockchain. Optional takes argument as amount of transactions to just return
+     the last amount transactions.
+    Route: `\"/transactions/<optional: int:amount>\"`. """
+    resp = sess.get(url + 'explorer/lasttransactions/' + str(amount))
+    resp.raise_for_status()
+    transactions = resp.json()
+
+    for transaction in transactions:
+        append_sender_to_transaction(transaction)
+
+    if (len(transactions) < amount):
+        return render_template('transactions.html', data_array=transactions)
+    else:
+        return render_template('transactions.html', data_array=transactions, nextamount=amount * 2)
+
+
+@app.route("/transaction/<string:hash>")
+def transaction(hash):
+    """ Lists information about the specified transaction.
+    Route: `\"/transaction/<string:hash>\"`. """
+    resp = sess.get(url + 'explorer/transaction/' + hash)
+    if resp.status_code == 404:
+        return render_template('not_found.html', data={'type': 'Transaction', 'hash': hash})
+
+    resp.raise_for_status()
+
+    json_obj = resp.json()
+
+    append_sender_to_transaction(json_obj)
+
+    return render_template('transaction.html', transaction=json_obj)
+
+
+def get_statistics():
+    """ Lists all calculated statistics about the blockchain and its network. """
+    resp_blocktime = sess.get(url + 'explorer/statistics/blocktime')
+    resp_blocktime.raise_for_status()
+
+    resp_totalblocks = sess.get(url + 'explorer/statistics/totalblocks')
+    resp_totalblocks.raise_for_status()
+
+    resp_difficulty = sess.get(url + 'explorer/statistics/difficulty')
+    resp_difficulty.raise_for_status()
+
+    resp_hashrate = sess.get(url + 'explorer/statistics/hashrate?length=' + str(QUERY_PARAMETER_AVERAGE_LENGTH))
+    resp_hashrate.raise_for_status()
+
+    resp_tps = sess.get(url + 'explorer/statistics/tps?length=' + str(QUERY_PARAMETER_AVERAGE_LENGTH))
+    resp_tps.raise_for_status()
+
+    result = {"blocktime": resp_blocktime.json(), "totalblocks": resp_totalblocks.json(),
+              "difficulty": resp_difficulty.json(), "hashrate": resp_hashrate.json(), "tps": resp_tps.json()}
+    return result
+
+
+@app.route("/statistics")
+def statistics():
+    """ Shows all calculated statistics about the blockchain and its network.
+    Route: `\"/statistics\"`. """
+    return render_template('statistics.html', data=get_statistics())
+
+
+@app.route("/about")
+def about():
+    """ Shows the 'About'-Page.
+    Route: `\"/about\"`. """
+    return render_template('about.html')
+
+if __name__ == '__main__':
+    main()