Browse Source

several fixes

Bernardo Magri 7 years ago
parent
commit
6e9f6ba66f
15 changed files with 610 additions and 2009 deletions
  1. 42 30
      src/block.py
  2. 32 41
      src/blockchain.py
  3. 15 20
      src/chainbuilder.py
  4. 8 8
      src/config.py
  5. 4 1
      src/crypto.py
  6. 0 1662
      src/labscript.py
  7. 0 1
      src/mining.py
  8. 9 6
      src/mining_strategy.py
  9. 1 1
      src/proof_of_work.py
  10. 7 0
      src/rpc_client.py
  11. 17 3
      src/rpc_server.py
  12. 180 0
      src/scriptinterpreter.py
  13. 264 233
      src/transaction.py
  14. 29 1
      wallet.py
  15. 2 2
      website.py

+ 42 - 30
src/block.py

@@ -14,7 +14,6 @@ from .crypto import get_hasher
 
 
 __all__ = ['Block']
 __all__ = ['Block']
 
 
-
 class Block:
 class Block:
     """
     """
     A block: a container for all the data associated with a block.
     A block: a container for all the data associated with a block.
@@ -34,17 +33,19 @@ class Block:
     :vartype time: datetime
     :vartype time: datetime
     :ivar nonce: The nonce in this block that was required to achieve the proof of work.
     :ivar nonce: The nonce in this block that was required to achieve the proof of work.
     :vartype nonce: int
     :vartype nonce: int
-    :ivar height: The height (accumulated difficulty) of this block.
+    :ivar height: The height (accumulated target) of this block.
     :vartype height: int
     :vartype height: int
     :ivar received_time: The time when we received this block.
     :ivar received_time: The time when we received this block.
     :vartype received_time: datetime
     :vartype received_time: datetime
-    :ivar difficulty: The difficulty of this block.
-    :vartype difficulty: int
+    :ivar target: The target of this block.
+    :vartype target: int
     :ivar transactions: The list of transactions in this block.
     :ivar transactions: The list of transactions in this block.
     :vartype transactions: List[Transaction]
     :vartype transactions: List[Transaction]
     """
     """
 
 
-    def __init__(self, prev_block_hash, time, nonce, height, received_time, difficulty, transactions,
+    # TODO: Check if  "id" is really needed. Should be the same as "height".
+
+    def __init__(self, prev_block_hash, time, nonce, height, received_time, target, transactions,
                  merkle_root_hash=None, id=None):
                  merkle_root_hash=None, id=None):
         self.id = id
         self.id = id
         self.prev_block_hash = prev_block_hash
         self.prev_block_hash = prev_block_hash
@@ -53,21 +54,29 @@ class Block:
         self.nonce = nonce
         self.nonce = nonce
         self.height = height
         self.height = height
         self.received_time = received_time
         self.received_time = received_time
-        self.difficulty = difficulty
+        self.target = target
         self.transactions = transactions
         self.transactions = transactions
-        self.hash = self._get_hash()
+        self._hash = self._get_hash()
+
+    @property
+    def hash(self):
+        return self._hash
+
+    @hash.setter
+    def hash(self, value):
+        self._hash = value
 
 
     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['id'] = self.id
-        val['hash'] = hexlify(self.hash).decode()
+        val['hash'] = hexlify(self._hash).decode()
         val['prev_block_hash'] = hexlify(self.prev_block_hash).decode()
         val['prev_block_hash'] = hexlify(self.prev_block_hash).decode()
         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")
         val['nonce'] = self.nonce
         val['nonce'] = self.nonce
         val['height'] = self.height
         val['height'] = self.height
-        val['difficulty'] = self.difficulty
+        val['target'] = self.target
         val['transactions'] = [t.to_json_compatible() for t in self.transactions]
         val['transactions'] = [t.to_json_compatible() for t in self.transactions]
         return val
         return val
 
 
@@ -80,7 +89,7 @@ class Block:
                    int(val['nonce']),
                    int(val['nonce']),
                    int(val['height']),
                    int(val['height']),
                    datetime.utcnow(),
                    datetime.utcnow(),
-                   int(val['difficulty']),
+                   int(val['target']),
                    [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']))
                    int(val['id']))
@@ -113,7 +122,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(utils.int_to_bytes(self.difficulty))
+        hasher.update(utils.int_to_bytes(self.target))
         return hasher
         return hasher
 
 
     def finish_hash(self, hasher):
     def finish_hash(self, hasher):
@@ -136,24 +145,23 @@ class Block:
 
 
     def verify_proof_of_work(self):
     def verify_proof_of_work(self):
         """ Verify the proof of work on a block. """
         """ Verify the proof of work on a block. """
-        return int.from_bytes(self.hash, 'big') < self.difficulty
+        return int.from_bytes(self._hash, 'big') < self.target
 
 
     def verify_difficulty(self):
     def verify_difficulty(self):
-        """ Verifies that the hash value is correct and fulfills its difficulty promise. """
-        if self.height == 0:  # can be removed?!
-            return True
+        """ Verifies that the hash value is correct and fulfills its target promise. """
         if not self.verify_proof_of_work():
         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, 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. """
+    def verify_prev_block(self, prev_block: 'Block', chain_target: int):
+        """ Verifies that the previous block pointer points to the head of the given blockchain and target and height are correct. """
         if prev_block.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_difficulty:
-            logging.warning("Block has wrong difficulty.")
+
+        if self.target != chain_target:
+            logging.warning("Block has wrong target.")
             return False
             return False
         if prev_block.height + 1 != self.height:
         if prev_block.height + 1 != self.height:
             logging.warning("Block has wrong height.")
             logging.warning("Block has wrong height.")
@@ -161,14 +169,12 @@ class Block:
         return True
         return True
 
 
     def verify_block_transactions(self, unspent_coins: dict, reward: int):
     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 blockchain. """
         mining_rewards = []
         mining_rewards = []
         all_inputs = []
         all_inputs = []
-
         for t in self.transactions:
         for t in self.transactions:
-
             all_inputs += t.inputs
             all_inputs += t.inputs
-            if not t.inputs:
+            if t.inputs[0].is_coinbase:
                 if len(mining_rewards) > 1:
                 if len(mining_rewards) > 1:
                     logging.warning("block has more than one coinbase transaction")
                     logging.warning("block has more than one coinbase transaction")
                     return False
                     return False
@@ -177,17 +183,23 @@ class Block:
             if not t.validate_tx(unspent_coins):
             if not t.validate_tx(unspent_coins):
                 return False
                 return False
 
 
-            if reward != sum(t.amount for t in mining_rewards[0].targets):
-                logging.warning("reward is different than specified")
-                return False
+        fees = sum(t.get_transaction_fee(unspent_coins) for t in self.transactions)
+        actual_reward_and_fees = sum(t.amount for t in mining_rewards[0].targets)
 
 
-            if not self._verify_input_consistency(all_inputs):
-                return False
+        if actual_reward_and_fees > reward + fees:
+            warn = "{} is different than specified({})".format(actual_reward_and_fees, reward+fees)
+            logging.error(warn)
+            return False
+
+        if not self._verify_input_consistency(all_inputs):
+            return False
 
 
         return True
         return True
 
 
     def _verify_input_consistency(self, tx_inputs: 'List[TransactionInputs]'):
     def _verify_input_consistency(self, tx_inputs: 'List[TransactionInputs]'):
-        return len(tx_inputs) == len(set(tx_inputs))
+        """"Verify that all the transactions in the transaction list are not spending from a same input transaction and index"""
+        tx_inp = [(i.transaction_hash, i.output_idx) for i in tx_inputs]
+        return len(tx_inp) == len(set(tx_inp))
 
 
     def verify_time(self, head_time: datetime):
     def verify_time(self, head_time: datetime):
         """
         """
@@ -207,7 +219,7 @@ class Block:
         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_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

+ 32 - 41
src/blockchain.py

@@ -3,6 +3,10 @@
 __all__ = ['Blockchain', 'GENESIS_BLOCK']
 __all__ = ['Blockchain', 'GENESIS_BLOCK']
 import logging
 import logging
 
 
+from binascii import hexlify
+
+from collections import namedtuple
+
 from typing import Optional
 from typing import Optional
 
 
 from datetime import datetime
 from datetime import datetime
@@ -13,12 +17,10 @@ from .block import Block
 from .config import *
 from .config import *
 from .utils import compute_blockreward_next_block
 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)
+# If you want to add transactions to the genesis block you can create a Transaction object and include it in the list below (after GENESIS_TARGET)
+GENESIS_BLOCK = Block("None; {} {}".format(DIFFICULTY_BLOCK_INTERVAL, DIFFICULTY_TIMEDELTA).encode(),
+                      datetime(2017, 3, 3, 10, 35, 26, 922898), 0, 0, datetime.utcnow(), GENESIS_TARGET,
+                      [], merkle_tree([]).get_hash(), 0)
 
 
 GENESIS_BLOCK_HASH = GENESIS_BLOCK.hash
 GENESIS_BLOCK_HASH = GENESIS_BLOCK.hash
 
 
@@ -45,7 +47,7 @@ class Blockchain:
         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}
         self.unspent_coins = {}
         self.unspent_coins = {}
-        self.total_difficulty = GENESIS_BLOCK.difficulty
+        self.total_difficulty = 0
 
 
     def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
     def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
         """
         """
@@ -53,7 +55,7 @@ class Blockchain:
         Otherwise, it returns `None`.
         Otherwise, it returns `None`.
         """
         """
 
 
-        if not block.verify(self.head, self.compute_difficulty_next_block(), self.unspent_coins, self.block_indices,
+        if not block.verify(self.head, self.compute_target_next_block(), self.unspent_coins, self.block_indices,
                             compute_blockreward_next_block(self.head.height)):
                             compute_blockreward_next_block(self.head.height)):
             return None
             return None
 
 
@@ -61,11 +63,12 @@ class Blockchain:
 
 
         for t in block.transactions:
         for t in block.transactions:
             for inp in t.inputs:
             for inp in t.inputs:
-                try:
-                    del unspent_coins[inp.transaction_hash, inp.output_idx]
-                except KeyError:
-                    logging.info("Input was already spent in this block!")
-                    return None
+                if inp.is_coinbase:
+                    continue
+
+                # the checks for tx using the same inputs are already done in the block.verify method
+                unspent_coins.pop((inp.transaction_hash, inp.output_idx), None)
+
             for i, target in enumerate(t.targets):
             for i, target in enumerate(t.targets):
                 if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
                 if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
                     unspent_coins[(t.get_hash(), i)] = target
                     unspent_coins[(t.get_hash(), i)] = target
@@ -75,7 +78,7 @@ class Blockchain:
         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
+        chain.total_difficulty = self.total_difficulty + GENESIS_TARGET - block.target
 
 
         return chain
         return chain
 
 
@@ -95,34 +98,22 @@ class Blockchain:
         """
         """
         return self.blocks[-1]
         return self.blocks[-1]
 
 
-    def compute_difficulty_next_block(self) -> int:
-        """ Compute the desired difficulty for the block following this chain's `head`. """
-        should_duration = DIFFICULTY_TARGET_TIMEDELTA.total_seconds()
+    def compute_target_next_block(self) -> int:
+        """ Compute the desired target for the block following this chain's `head`. """
+        should_duration = DIFFICULTY_TIMEDELTA.total_seconds()
 
 
         if (self.head.height % DIFFICULTY_BLOCK_INTERVAL != 0) or (self.head.height == 0):
         if (self.head.height % DIFFICULTY_BLOCK_INTERVAL != 0) or (self.head.height == 0):
-            return self.head.difficulty
+            return self.head.target
 
 
-        last_duration = (
-                self.head.time - self.blocks[self.head.height - DIFFICULTY_BLOCK_INTERVAL].time).total_seconds()
+        past_block = self.blocks[self.head.height - DIFFICULTY_BLOCK_INTERVAL]
+        last_duration = (self.head.time - past_block.time).total_seconds()
         diff_adjustment_factor = last_duration / should_duration
         diff_adjustment_factor = last_duration / should_duration
-        prev_difficulty = self.head.difficulty
-
-        new_difficulty = prev_difficulty * diff_adjustment_factor
-
-        # the genesis difficulty was very easy, dropping below it means there was a pause
-        # in mining, so let's start with a new difficulty!
-        if new_difficulty > self.blocks[0].difficulty:
-            new_difficulty = self.blocks[0].difficulty
-
-        return int(new_difficulty)
-
-    # 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
+        prev_target = self.head.target
+        new_target = prev_target * diff_adjustment_factor
+
+        # the genesis target was very easy, making it easier means there was a pause
+        # in mining, so we start over with the initial target
+        if new_target > self.blocks[0].target:
+            new_target = self.blocks[0].target
+
+        return int(new_target)

+ 15 - 20
src/chainbuilder.py

@@ -26,12 +26,10 @@ of length `N`, the number of checkpoints is always kept between `2*log_2(N)` and
 most checkpoints being relatively recent. There also is always one checkpoint with only the genesis
 most checkpoints being relatively recent. There also is always one checkpoint with only the genesis
 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, Optional
 from datetime import datetime
 from datetime import datetime
 
 
 from .config import *
 from .config import *
@@ -71,7 +69,6 @@ class BlockRequest:
         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),
         logging.debug("asking for another block %d (attempt %d)", max(len(r) for r in self.partial_chains),
                       self._request_count)
                       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 > BLOCK_REQUEST_RETRY_COUNT
         return self._request_count > BLOCK_REQUEST_RETRY_COUNT
@@ -129,9 +126,6 @@ class ChainBuilder:
                 if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
                 if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
                     self.primary_block_chain.unspent_coins[(tx.get_hash(), i)] = target
                     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 two lists, one with known valid, unapplied transactions, the other with all known transactions (with some limit)
-
         self.chain_change_handlers = []
         self.chain_change_handlers = []
         self.transaction_change_handlers = []
         self.transaction_change_handlers = []
 
 
@@ -170,8 +164,8 @@ 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 chain:  height %d -  target %10.2e", len(chain.blocks),
+                     chain.total_difficulty)
         self._assert_thread_safety()
         self._assert_thread_safety()
         self.primary_block_chain = chain
         self.primary_block_chain = chain
         todelete = set()
         todelete = set()
@@ -187,8 +181,6 @@ class ChainBuilder:
         self._retry_expired_requests()
         self._retry_expired_requests()
         self._clean_block_requests()
         self._clean_block_requests()
 
 
-        # TODO: restore valid transactions from the old primary block chain
-
         self.protocol.broadcast_primary_block(chain.head)
         self.protocol.broadcast_primary_block(chain.head)
 
 
     def _build_blockchain(self, checkpoint: 'Blockchain', blocks: 'List[Block]'):
     def _build_blockchain(self, checkpoint: 'Blockchain', blocks: 'List[Block]'):
@@ -208,12 +200,14 @@ class ChainBuilder:
             next_chain = chain.try_append(b)
             next_chain = chain.try_append(b)
             if next_chain is None:
             if next_chain is None:
                 logging.warning("invalid block")
                 logging.warning("invalid block")
+                for handler in self.chain_change_handlers:
+                    handler() # this is a hot fix! that keeps the miner mining
                 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
 
 
-        if chain.head.height <= self.primary_block_chain.head.height:
+        if chain.total_difficulty < self.primary_block_chain.total_difficulty:
             logging.warning("discarding shorter chain")
             logging.warning("discarding shorter chain")
             return
             return
 
 
@@ -251,21 +245,21 @@ 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()
+        bl_hash = block.hash
 
 
-        if (block.hash in self.block_cache) or (not block.verify_difficulty()) or (not block.verify_merkle()):
+        if (bl_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[bl_hash] = block
 
 
         self._retry_expired_requests()
         self._retry_expired_requests()
 
 
-        if block.hash not in self._block_requests:
-            self._block_requests[block.hash] = BlockRequest()
+        if bl_hash not in self._block_requests:
+            self._block_requests[bl_hash] = BlockRequest()
         else:
         else:
             return
             return
 
 
-        request = self._block_requests[block.hash]
-        del self._block_requests[block.hash]
+        request = self._block_requests[bl_hash]
+        del self._block_requests[bl_hash]
 
 
         while True:
         while True:
             for partial_chain in request.partial_chains:
             for partial_chain in request.partial_chains:
@@ -289,3 +283,4 @@ 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)
+

+ 8 - 8
src/config.py

@@ -6,21 +6,21 @@ GENESIS_REWARD = 1000
 REWARD_HALF_LIFE = 10000
 REWARD_HALF_LIFE = 10000
 """ The number of blocks until the block reward is halved. """
 """ The number of blocks until the block reward is halved. """
 
 
-BLOCK_REQUEST_RETRY_INTERVAL = timedelta(minutes=1)
+BLOCK_REQUEST_RETRY_INTERVAL = timedelta(seconds=30)
 """ The approximate interval after which a block request will be retried. """
 """ The approximate interval after which a block request will be retried. """
 BLOCK_REQUEST_RETRY_COUNT = 3
 BLOCK_REQUEST_RETRY_COUNT = 3
 """ The number of failed requests of a block until we give up and delete the depending partial chains. """
 """ The number of failed requests of a block until we give up and delete the depending partial chains. """
 
 
-GENESIS_DIFFICULTY = (1 << 256) - 1
+GENESIS_TARGET = (1 << 256) - 1
 """
 """
-The difficulty of the genesis block.
+The target 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.
+It is directly used for comparison with the hash and thus we start with the smallest possible target which is the maximal possible hash value.
 Thus, everything bellow is a valid proof of work
 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.  """
+DIFFICULTY_BLOCK_INTERVAL = 10
+""" The number of blocks between target changes. """
 
 
+DIFFICULTY_TIMEDELTA = timedelta(seconds=6)
+""" The time span that it should approximately take to mine `DIFFICULTY_BLOCK_INTERVAL` blocks.  """

+ 4 - 1
src/crypto.py

@@ -79,7 +79,10 @@ class Key:
         return cls(unhexlify(obj))
         return cls(unhexlify(obj))
 
 
     def __eq__(self, other: 'Key'):
     def __eq__(self, other: 'Key'):
-        return self.rsa.e == other.rsa.e and self.rsa.n == other.rsa.n
+        if not other:
+            return False
+        else:
+            return self.rsa.e == other.rsa.e and self.rsa.n == other.rsa.n
 
 
     def __hash__(self):
     def __hash__(self):
         return hash((self.rsa.e, self.rsa.n))
         return hash((self.rsa.e, self.rsa.n))

+ 0 - 1662
src/labscript.py

@@ -1,1662 +0,0 @@
-#! /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
-
-        

+ 0 - 1
src/mining.py

@@ -1,5 +1,4 @@
 """ Functionality for mining new blocks. """
 """ Functionality for mining new blocks. """
-
 import json
 import json
 import os
 import os
 import sys
 import sys

+ 9 - 6
src/mining_strategy.py

@@ -4,12 +4,14 @@ from typing import List
 
 
 from .block import Block
 from .block import Block
 
 
-from .labscript import *
+from datetime import datetime
 from .utils import compute_blockreward_next_block
 from .utils import compute_blockreward_next_block
 
 
 from .blockchain import Blockchain
 from .blockchain import Blockchain
 from .crypto import Key
 from .crypto import Key
 
 
+from .transaction import Transaction, TransactionTarget, TransactionInput
+
 __all__ = ['create_block']
 __all__ = ['create_block']
 
 
 
 
@@ -27,17 +29,18 @@ def create_block(blockchain: 'Blockchain', unconfirmed_transactions: 'List[Trans
     # sort the uncorfirmed transactions by the transaction fee amount
     # sort the uncorfirmed transactions by the transaction fee amount
     sorted_unconfirmed_tx = sorted(unconfirmed_transactions,
     sorted_unconfirmed_tx = sorted(unconfirmed_transactions,
                                    key=lambda tx: tx.get_transaction_fee(blockchain.unspent_coins), reverse=True)
                                    key=lambda tx: tx.get_transaction_fee(blockchain.unspent_coins), reverse=True)
-    transactions = set()
 
 
+    transactions = []
     for t in sorted_unconfirmed_tx:
     for t in sorted_unconfirmed_tx:
         if t.validate_tx(blockchain.unspent_coins) and not t.check_tx_collision(transactions):
         if t.validate_tx(blockchain.unspent_coins) and not t.check_tx_collision(transactions):
-            transactions.add(t)
+            transactions.append(t)
 
 
     reward = compute_blockreward_next_block(blockchain.head.height)
     reward = compute_blockreward_next_block(blockchain.head.height)
     fees = sum(t.get_transaction_fee(blockchain.unspent_coins) for t in transactions)
     fees = sum(t.get_transaction_fee(blockchain.unspent_coins) for t in transactions)
 
 
-    trans = Transaction([], [TransactionTarget(TransactionTarget.pay_to_pubkey(reward_pubkey), fees + reward)],
+    trans = Transaction([TransactionInput(bytes(32), -1, "")], [TransactionTarget(TransactionTarget.pay_to_pubkey(reward_pubkey), fees + reward)],
                         datetime.utcnow(), iv=blockchain.head.hash)
                         datetime.utcnow(), iv=blockchain.head.hash)
-    transactions.add(trans)
 
 
-    return Block.create(blockchain.compute_difficulty_next_block(), blockchain.head, list(transactions))
+    transactions.insert(0, trans)  # add coinbase tx to the first position
+
+    return Block.create(blockchain.compute_target_next_block(), blockchain.head, transactions)

+ 1 - 1
src/proof_of_work.py

@@ -11,7 +11,7 @@ from .block import Block
 from .config import *
 from .config import *
 
 
 
 
-__all__ = ['GENESIS_DIFFICULTY', 'ProofOfWork']
+__all__ = ['GENESIS_TARGET', 'ProofOfWork']
 
 
 
 
 class ProofOfWork:
 class ProofOfWork:

+ 7 - 0
src/rpc_client.py

@@ -37,6 +37,12 @@ class RPCClient:
         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 get_transaction(self, tx_hash: bytes) -> Transaction:
+        """ Returns the transaction with hash tx_hash """
+        resp = self.sess.post(self.url + 'transaction', data=tx_hash,          headers={"Content-Type": "application/json"})
+        resp.raise_for_status()
+        return Transaction.from_json_compatible(resp.json())
+
     def show_balance(self, pubkeys: List[Key]) -> Iterator[Tuple[Key, 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]),
@@ -73,3 +79,4 @@ class RPCClient:
                   for i, inp in enumerate(temp_inputs)]
                   for i, inp in enumerate(temp_inputs)]
 
 
         return Transaction(inputs, targets, timestamp)
         return Transaction(inputs, targets, timestamp)
+

+ 17 - 3
src/rpc_server.py

@@ -115,6 +115,20 @@ def build_transaction():
         "key_indices": used_keys,
         "key_indices": used_keys,
     })
     })
 
 
+@app.route("/transaction", methods=['POST'])
+def get_transaction_for_hash():
+    """
+    Returns the transaction for provided hash.
+    Route: `\"/transaction\"`.
+    HTTP Method: `'POST'`
+    """
+    tx_hash = flask.request.data
+    chain = cb.primary_block_chain
+    for b in chain.blocks:
+        for t in b.transactions:
+            if t.get_hash() == tx_hash:
+                return json.dumps(t.to_json_compatible())
+    return json.dumps("")
 
 
 @app.route("/transactions", methods=['POST'])
 @app.route("/transactions", methods=['POST'])
 def get_transactions_for_key():
 def get_transactions_for_key():
@@ -514,11 +528,11 @@ def get_total_blocks():
     return json.dumps(total_blocks)
     return json.dumps(total_blocks)
 
 
 
 
-@app.route("/explorer/statistics/difficulty", methods=['GET'])
+@app.route("/explorer/statistics/target", methods=['GET'])
 def get_difficulty():
 def get_difficulty():
     """
     """
-    Returns the current difficulty.
-    Route: `\"/explorer/statistics/difficulty\"`
+    Returns the current target.
+    Route: `\"/explorer/statistics/target\"`
     HTTP Method: `'GET'`
     HTTP Method: `'GET'`
     """
     """
     chain = cb.primary_block_chain
     chain = cb.primary_block_chain

+ 180 - 0
src/scriptinterpreter.py

@@ -0,0 +1,180 @@
+#! /usr/bin/env/python3
+import hashlib
+import logging
+from .crypto import *
+from binascii import hexlify, unhexlify
+from datetime import datetime
+
+    
+class ScriptInterpreter:
+    """
+    ScriptInterpreter 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:
+
+        Crypto:
+            OP_SHA256
+            OP_CHECKSIG
+            OP_RETURN
+
+
+        Locktime:
+
+            OP_CHECKLOCKTIME
+
+    """
+    operations = {
+        'OP_SHA256',
+        'OP_CHECKSIG',
+        'OP_RETURN',
+        'OP_CHECKLOCKTIME'
+    }
+
+    def __init__(self, input_script: str, output_script: str, tx_hash: bytes):
+        self.output_script = output_script
+        self.input_script = input_script
+        self.tx_hash = tx_hash
+        self.stack = []
+
+
+    def to_string(self):
+        return " ".join(self.stack)
+        
+
+    # operation implementations
+
+    def op_sha256(self):
+        #The input is hashed using SHA-256.
+
+        if not self.stack:
+            logging.warning("Stack is empty")
+            return False
+
+        sha256 = hashlib.sha256()
+        sha256.update(str(self.stack.pop()).encode('utf-8'))
+        sha256 = hexlify(sha256.digest())
+        self.stack.append(sha256.decode('utf-8'))
+        return True
+
+ 
+    def op_checksig(self):
+        # 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(len(self.stack) < 2):
+            logging.warning("Not enough arguments")
+            self.stack.append(str(0))
+            return False
+
+        pubKey = Key.from_json_compatible(self.stack.pop())
+
+        sig = unhexlify(self.stack.pop())
+
+        if pubKey.verify_sign(self.tx_hash, sig):
+            self.stack.append(str(1))
+            return True
+
+        logging.warning("Signature not verified")
+        self.stack.append(str(0))
+        return False
+
+
+    def op_return(self):
+
+        """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
+        self.stack.append(str(0))
+        logging.warning('Transaction can not be spent!')
+        return False
+
+    def op_checklocktime(self):
+
+        #Error Indicator
+        error = 0
+
+        #Error if Stack is empty
+        if not self.stack or len(self.stack) < 2:
+            error = 1
+
+        #if top stack item is greater than the transactions nLockTime field ERROR
+        temp = float(self.stack.pop())
+        try:
+            timestamp = datetime.fromtimestamp(temp)
+        except TypeError:
+            logging.error("A timestamp needs to be supplied after the OP_CHECKLOCKTIME operation!")
+            self.stack.append(str(0))
+            return False
+
+        #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")
+            error = 3
+
+        if(error):
+            #errno = 1 Stack is empty
+            if(error == 1):
+                logging.warning('Stack is empty!')
+                self.stack.append(str(0))
+                return False
+
+            #errno = 2 Top-Stack-Value < 0
+            if(error == 2):
+                logging.warning('Top-Stack-Value < 0')
+                self.stack.append(str(0))
+                return False
+
+            #errno = 3 top stack item is greater than the transactions
+            if(error == 3):
+                #logging.warning('you need to wait more to unlock the funds!')
+                self.stack.append(str(0))
+                return False
+
+        return True
+
+
+    def execute_script(self):
+        """
+            Run the script with the input and output scripts
+        """
+        script = self.input_script.split() + self.output_script.split()
+
+        while script:
+
+            next_item = script.pop(0) # Fetch next item (start reading from beginning of script)
+
+            # Check if item is data or opcode. If data, push onto stack.
+            if (next_item not in ScriptInterpreter.operations):
+                self.stack.append(next_item)  # if it's data we add it to the stack
+            else:
+                op = getattr(self, next_item.lower())  # Proper operation to be executed
+                op()  # execute the command!
+
+
+        if (len(self.stack)==1 and self.stack[-1] == '1'):
+            return True
+        else:
+            logging.warning("[!] Error: Invalid Tx.")
+            return False
+

+ 264 - 233
src/transaction.py

@@ -1,233 +1,264 @@
-""" 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
+""" 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, Optional
+
+import src.utils as utils
+
+from .scriptinterpreter import ScriptInterpreter
+
+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 burn(self, data:bytes) -> str:
+        """ Returns a OP_RETURN script"""
+        data = hexlify(data).decode()
+        return data + " OP_RETURN"
+
+    @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) -> Optional[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 it needs to check if 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 has_data(self) -> bool:
+        op = self.pubkey_script[self.pubkey_script.find(" ") + 1:]
+        return op == "OP_RETURN"
+
+    @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
+    """
+
+    def collides(self, other):
+        if self.transaction_hash == other.transaction_hash:
+            return self.output_idx == other.output_idx
+
+    @property
+    def is_coinbase(self):
+        return self.output_idx == -1
+
+    @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 self.inputs[0].is_coinbase:
+            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.")
+            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:
+            coinbase = inp.is_coinbase
+            if coinbase and len(self.inputs) > 1:
+                logging.warning("A coinbase transaction can only have one coinbase.")
+                return False
+            elif coinbase:
+                return True
+
+            if (inp.transaction_hash, inp.output_idx) not in unspent_coins:
+                return False  # ("The input is not in the unspent transactions database!")
+
+
+            script = ScriptInterpreter(inp.sig_script,
+                                           unspent_coins[(inp.transaction_hash, inp.output_idx)].pubkey_script,
+                                           self.get_hash())
+
+            if not script.execute_script():
+                return False
+
+        # ensures that can't spend more coins than there are input coins
+        return self.get_transaction_fee(unspent_coins) >= 0
+
+
+
+    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.collides(inp_other):
+                        return True
+        return False

+ 29 - 1
wallet.py

@@ -10,7 +10,7 @@ __all__ = []
 import argparse
 import argparse
 import sys
 import sys
 from datetime import datetime
 from datetime import datetime
-from binascii import hexlify
+from binascii import hexlify, unhexlify
 from io import IOBase
 from io import IOBase
 from typing import List, Union, Callable, Tuple, Optional
 from typing import List, Union, Callable, Tuple, Optional
 
 
@@ -88,6 +88,9 @@ def main():
                                        "stored in the specified file.")
                                        "stored in the specified file.")
     trans.add_argument("key", nargs="*", type=Key.from_file)
     trans.add_argument("key", nargs="*", type=Key.from_file)
 
 
+    single_trans = subparsers.add_parser("show-transaction", help="Show transaction for hash")
+    single_trans.add_argument("hash", type=str, help="the hash")
+
     subparsers.add_parser("show-network",
     subparsers.add_parser("show-network",
                           help="Prints networking information about the miner.")
                           help="Prints networking information about the miner.")
 
 
@@ -103,10 +106,22 @@ def main():
                           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.")
 
 
+    data = subparsers.add_parser("burn", help="An unspendable transaction with random data attached")
+    data.add_argument("--private-key", type=private_signing, default=[], action="append", required=False,
+                          help="The private key(s) whose coins should be used for the transfer.")
+    data.add_argument("--change-key", type=Key.from_file, required=False,
+                          help="The private key where any remaining coins are sent to.")
+    data.add_argument("--transaction-fee", type=int, default=1,
+                          help="The transaction fee you want to pay to the miner.")
+
+
     args = parser.parse_args()
     args = parser.parse_args()
 
 
     rpc = RPCClient(args.miner_port)
     rpc = RPCClient(args.miner_port)
 
 
+    def show_transaction(tx_hash: bytes):
+        print(rpc.get_transaction(tx_hash).to_json_compatible())
+
     def show_transactions(keys: List[Key]):
     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):
@@ -141,6 +156,7 @@ def main():
 
 
         timestamp = datetime.utcnow()
         timestamp = datetime.utcnow()
         tx = rpc.build_transaction(priv_keys, tx_targets, change_key, args.transaction_fee, timestamp)
         tx = rpc.build_transaction(priv_keys, tx_targets, change_key, args.transaction_fee, timestamp)
+        print(hexlify(tx.get_hash()).decode())
         rpc.send_transaction(tx)
         rpc.send_transaction(tx)
 
 
     def get_keys(keys: List[Key]) -> List[Key]:
     def get_keys(keys: List[Key]) -> List[Key]:
@@ -155,6 +171,8 @@ def main():
 
 
     if args.command == 'show-transactions':
     if args.command == 'show-transactions':
         show_transactions(get_keys(args.key))
         show_transactions(get_keys(args.key))
+    elif args.command == 'show-transaction':
+        show_transaction(unhexlify(args.hash))
     elif args.command == "create-address":
     elif args.command == "create-address":
         if not args.wallet[1]:
         if not args.wallet[1]:
             print("no wallet specified", file=sys.stderr)
             print("no wallet specified", file=sys.stderr)
@@ -174,6 +192,16 @@ def main():
         targets = [TransactionTarget(TransactionTarget.pay_to_pubkey(k), a) for k, a in
         targets = [TransactionTarget(TransactionTarget.pay_to_pubkey(k), a) for k, a in
                    zip(args.target[::2], args.target[1::2])]
                    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))
+
+    elif args.command == 'burn':
+        if not args.change_key and not args.wallet[0]:
+            print("You need to specify either --wallet or --change-key.\n", file=sys.stderr)
+            parser.parse_args(["--help"])
+        import random
+        randomness = random.randint(0, 128)
+        target = TransactionTarget(TransactionTarget.burn(randomness.to_bytes(40, 'big')), 0)
+        transfer([target], 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"])

+ 2 - 2
website.py

@@ -204,7 +204,7 @@ def get_statistics():
     resp_totalblocks = sess.get(url + 'explorer/statistics/totalblocks')
     resp_totalblocks = sess.get(url + 'explorer/statistics/totalblocks')
     resp_totalblocks.raise_for_status()
     resp_totalblocks.raise_for_status()
 
 
-    resp_difficulty = sess.get(url + 'explorer/statistics/difficulty')
+    resp_difficulty = sess.get(url + 'explorer/statistics/target')
     resp_difficulty.raise_for_status()
     resp_difficulty.raise_for_status()
 
 
     resp_hashrate = sess.get(url + 'explorer/statistics/hashrate?length=' + str(QUERY_PARAMETER_AVERAGE_LENGTH))
     resp_hashrate = sess.get(url + 'explorer/statistics/hashrate?length=' + str(QUERY_PARAMETER_AVERAGE_LENGTH))
@@ -214,7 +214,7 @@ def get_statistics():
     resp_tps.raise_for_status()
     resp_tps.raise_for_status()
 
 
     result = {"blocktime": resp_blocktime.json(), "totalblocks": resp_totalblocks.json(),
     result = {"blocktime": resp_blocktime.json(), "totalblocks": resp_totalblocks.json(),
-              "difficulty": resp_difficulty.json(), "hashrate": resp_hashrate.json(), "tps": resp_tps.json()}
+              "target": resp_difficulty.json(), "hashrate": resp_hashrate.json(), "tps": resp_tps.json()}
     return result
     return result