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']
 
-
 class Block:
     """
     A block: a container for all the data associated with a block.
@@ -34,17 +33,19 @@ class Block:
     :vartype time: datetime
     :ivar nonce: The nonce in this block that was required to achieve the proof of work.
     :vartype nonce: int
-    :ivar height: The height (accumulated difficulty) of this block.
+    :ivar height: The height (accumulated target) of this block.
     :vartype height: int
     :ivar received_time: The time when we received this block.
     :vartype received_time: datetime
-    :ivar difficulty: The difficulty of this block.
-    :vartype difficulty: int
+    :ivar target: The target of this block.
+    :vartype target: int
     :ivar transactions: The list of transactions in this block.
     :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):
         self.id = id
         self.prev_block_hash = prev_block_hash
@@ -53,21 +54,29 @@ class Block:
         self.nonce = nonce
         self.height = height
         self.received_time = received_time
-        self.difficulty = difficulty
+        self.target = target
         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):
         """ Returns a JSON-serializable representation of this object. """
         val = {}
         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['merkle_root_hash'] = hexlify(self.merkle_root_hash).decode()
         val['time'] = self.time.strftime("%Y-%m-%dT%H:%M:%S.%f UTC")
         val['nonce'] = self.nonce
         val['height'] = self.height
-        val['difficulty'] = self.difficulty
+        val['target'] = self.target
         val['transactions'] = [t.to_json_compatible() for t in self.transactions]
         return val
 
@@ -80,7 +89,7 @@ class Block:
                    int(val['nonce']),
                    int(val['height']),
                    datetime.utcnow(),
-                   int(val['difficulty']),
+                   int(val['target']),
                    [Transaction.from_json_compatible(t) for t in list(val['transactions'])],
                    unhexlify(val['merkle_root_hash']),
                    int(val['id']))
@@ -113,7 +122,7 @@ class Block:
         hasher.update(self.prev_block_hash)
         hasher.update(self.merkle_root_hash)
         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
 
     def finish_hash(self, hasher):
@@ -136,24 +145,23 @@ class Block:
 
     def verify_proof_of_work(self):
         """ 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):
-        """ 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():
             logging.warning("block does not satisfy proof of work")
             return False
         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:
             logging.warning("Previous block is not head of the block chain.")
             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
         if prev_block.height + 1 != self.height:
             logging.warning("Block has wrong height.")
@@ -161,14 +169,12 @@ class Block:
         return True
 
     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 = []
         all_inputs = []
-
         for t in self.transactions:
-
             all_inputs += t.inputs
-            if not t.inputs:
+            if t.inputs[0].is_coinbase:
                 if len(mining_rewards) > 1:
                     logging.warning("block has more than one coinbase transaction")
                     return False
@@ -177,17 +183,23 @@ class Block:
             if not t.validate_tx(unspent_coins):
                 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
 
     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):
         """
@@ -207,7 +219,7 @@ class Block:
         Verifies that this block contains only valid data and can be applied on top of the block
         chain `chain`.
         """
-        assert self.hash not in chain_indices
+        assert self._hash not in chain_indices
         if self.height == 0:
             logging.warning("only the genesis block may have height=0")
             return False

+ 32 - 41
src/blockchain.py

@@ -3,6 +3,10 @@
 __all__ = ['Blockchain', 'GENESIS_BLOCK']
 import logging
 
+from binascii import hexlify
+
+from collections import namedtuple
+
 from typing import Optional
 
 from datetime import datetime
@@ -13,12 +17,10 @@ from .block import Block
 from .config import *
 from .utils import compute_blockreward_next_block
 
-# If you want to add transactions in the genesis block you can create a Transaction object and include it in the list below (after GENESIS_DIFFICULTY)
-GENESIS_BLOCK = Block("None; {} {}".format(DIFFICULTY_BLOCK_INTERVAL,
-                                           DIFFICULTY_TARGET_TIMEDELTA).encode(),
-                      datetime(2017, 3, 3, 10, 35, 26, 922898), 0, 0,
-                      datetime.utcnow(), GENESIS_DIFFICULTY, [], merkle_tree([]).get_hash(),
-                      0)
+# 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
 
@@ -45,7 +47,7 @@ class Blockchain:
         assert self.blocks[0].height == 0
         self.block_indices = {GENESIS_BLOCK_HASH: 0}
         self.unspent_coins = {}
-        self.total_difficulty = GENESIS_BLOCK.difficulty
+        self.total_difficulty = 0
 
     def try_append(self, block: 'Block') -> 'Optional[Blockchain]':
         """
@@ -53,7 +55,7 @@ class Blockchain:
         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)):
             return None
 
@@ -61,11 +63,12 @@ class Blockchain:
 
         for t in block.transactions:
             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):
                 if target.is_pay_to_pubkey or target.is_pay_to_pubkey_lock:
                     unspent_coins[(t.get_hash(), i)] = target
@@ -75,7 +78,7 @@ class Blockchain:
         chain.blocks = self.blocks + [block]
         chain.block_indices = self.block_indices.copy()
         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
 
@@ -95,34 +98,22 @@ class Blockchain:
         """
         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):
-            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
-        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
 block.
 """
-
-import binascii
 import threading
 import logging
 import math
-from typing import List, Dict, Callable, Optional
+from typing import List, Optional
 from datetime import datetime
 
 from .config import *
@@ -71,7 +69,6 @@ class BlockRequest:
         protocol.send_block_request(self.partial_chains[0][-1].prev_block_hash)
         logging.debug("asking for another block %d (attempt %d)", max(len(r) for r in self.partial_chains),
                       self._request_count)
-
     def timeout_reached(self) -> bool:
         """ Returns a bool indicating whether all attempts to download this block have failed. """
         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:
                     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.transaction_change_handlers = []
 
@@ -170,8 +164,8 @@ class ChainBuilder:
 
     def _new_primary_block_chain(self, chain: 'Blockchain'):
         """ 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.primary_block_chain = chain
         todelete = set()
@@ -187,8 +181,6 @@ class ChainBuilder:
         self._retry_expired_requests()
         self._clean_block_requests()
 
-        # TODO: restore valid transactions from the old primary block chain
-
         self.protocol.broadcast_primary_block(chain.head)
 
     def _build_blockchain(self, checkpoint: 'Blockchain', blocks: 'List[Block]'):
@@ -208,12 +200,14 @@ class ChainBuilder:
             next_chain = chain.try_append(b)
             if next_chain is None:
                 logging.warning("invalid block")
+                for handler in self.chain_change_handlers:
+                    handler() # this is a hot fix! that keeps the miner mining
                 break
-                # TODO we need to figure out why the miner stops after an invalid block!
+
             chain = next_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")
             return
 
@@ -251,21 +245,21 @@ class ChainBuilder:
     def new_block_received(self, block: 'Block'):
         """ Event handler that is called by the network layer when a block is received. """
         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
-
-        self.block_cache[block.hash] = block
+        self.block_cache[bl_hash] = block
 
         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:
             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:
             for partial_chain in request.partial_chains:
@@ -289,3 +283,4 @@ class ChainBuilder:
             for partial_chain in request.partial_chains:
                 self._build_blockchain(checkpoint, partial_chain[::-1])
         request.checked_retry(self.protocol)
+

+ 8 - 8
src/config.py

@@ -6,21 +6,21 @@ GENESIS_REWARD = 1000
 REWARD_HALF_LIFE = 10000
 """ 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. """
 BLOCK_REQUEST_RETRY_COUNT = 3
 """ The number of failed requests of a block until we give up and delete the depending partial chains. """
 
-GENESIS_DIFFICULTY = (1 << 256) - 1
+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
 """
 
-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))
 
     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):
         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. """
-
 import json
 import os
 import sys

+ 9 - 6
src/mining_strategy.py

@@ -4,12 +4,14 @@ from typing import List
 
 from .block import Block
 
-from .labscript import *
+from datetime import datetime
 from .utils import compute_blockreward_next_block
 
 from .blockchain import Blockchain
 from .crypto import Key
 
+from .transaction import Transaction, TransactionTarget, TransactionInput
+
 __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
     sorted_unconfirmed_tx = sorted(unconfirmed_transactions,
                                    key=lambda tx: tx.get_transaction_fee(blockchain.unspent_coins), reverse=True)
-    transactions = set()
 
+    transactions = []
     for t in sorted_unconfirmed_tx:
         if t.validate_tx(blockchain.unspent_coins) and not t.check_tx_collision(transactions):
-            transactions.add(t)
+            transactions.append(t)
 
     reward = compute_blockreward_next_block(blockchain.head.height)
     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)
-    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 *
 
 
-__all__ = ['GENESIS_DIFFICULTY', 'ProofOfWork']
+__all__ = ['GENESIS_TARGET', 'ProofOfWork']
 
 
 class ProofOfWork:

+ 7 - 0
src/rpc_client.py

@@ -37,6 +37,12 @@ class RPCClient:
         resp.raise_for_status()
         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]]:
         """ 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]),
@@ -73,3 +79,4 @@ class RPCClient:
                   for i, inp in enumerate(temp_inputs)]
 
         return Transaction(inputs, targets, timestamp)
+

+ 17 - 3
src/rpc_server.py

@@ -115,6 +115,20 @@ def build_transaction():
         "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'])
 def get_transactions_for_key():
@@ -514,11 +528,11 @@ def get_total_blocks():
     return json.dumps(total_blocks)
 
 
-@app.route("/explorer/statistics/difficulty", methods=['GET'])
+@app.route("/explorer/statistics/target", methods=['GET'])
 def get_difficulty():
     """
-    Returns the current difficulty.
-    Route: `\"/explorer/statistics/difficulty\"`
+    Returns the current target.
+    Route: `\"/explorer/statistics/target\"`
     HTTP Method: `'GET'`
     """
     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 sys
 from datetime import datetime
-from binascii import hexlify
+from binascii import hexlify, unhexlify
 from io import IOBase
 from typing import List, Union, Callable, Tuple, Optional
 
@@ -88,6 +88,9 @@ def main():
                                        "stored in the specified 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",
                           help="Prints networking information about the miner.")
 
@@ -103,10 +106,22 @@ def main():
                           type=parse_targets(),
                           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()
 
     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]):
         for key in keys:
             for trans in rpc.get_transactions(key):
@@ -141,6 +156,7 @@ def main():
 
         timestamp = datetime.utcnow()
         tx = rpc.build_transaction(priv_keys, tx_targets, change_key, args.transaction_fee, timestamp)
+        print(hexlify(tx.get_hash()).decode())
         rpc.send_transaction(tx)
 
     def get_keys(keys: List[Key]) -> List[Key]:
@@ -155,6 +171,8 @@ def main():
 
     if args.command == 'show-transactions':
         show_transactions(get_keys(args.key))
+    elif args.command == 'show-transaction':
+        show_transaction(unhexlify(args.hash))
     elif args.command == "create-address":
         if not args.wallet[1]:
             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
                    zip(args.target[::2], args.target[1::2])]
         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:
         print("You need to specify what to do.\n", file=sys.stderr)
         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.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_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()
 
     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