def test_block_basic():
    """Test the definition of a basic Block"""

    block = Block(0, 'block data')

    assert block.index == 0
    assert block.previous_hash is None
    assert block.difficulty == 0
    assert block.data == 'block data'
    assert block.proof == 0

    block = Block(1, 'block data', timestamp=0)

    assert block.index == 1
    assert block.previous_hash is None
    assert block.difficulty == 0
    assert block.data == 'block data'
    assert block.proof == 0
    assert block.timestamp == 0
    assert block.hash == 'bc9ef25d3e98e4fd29add80d59b5ffc315176b8de85167569be118c9f3f42c2b'

    # Next block evaluation
    block = Block(block, 'Second block', timestamp=1)

    assert block.index == 2
    assert block.previous_hash == 'bc9ef25d3e98e4fd29add80d59b5ffc315176b8de85167569be118c9f3f42c2b'
    assert block.difficulty == 0
    assert block.data == 'Second block'
    assert block.proof == 0
    assert block.timestamp == 1
    assert block.hash == '5641942dc909b7a89f79a0d8bc022aca797cc66001194d82711cc508e90e997b'

    # Validate hash calculation
    assert Block.calculate_hash(block) == block.hash
def test_block_valid():
    """Test validation of a block"""

    block = Block(0, 'Test Data', timestamp=0)

    assert block.hash_satisfies_difficulty
    assert block.is_valid

    block.difficulty = 4

    assert block.hash_satisfies_difficulty is False
    assert block.is_valid is False

    block.proof = 49

    assert block.proof == 49
    assert block.hash_satisfies_difficulty
    assert block.is_valid

    # Introduce a fake hash
    block.hash = '0f59bbd5a22a6484cc206b7ee9d4bc1744bed9c5649ff332f88a0eec461f58c8'

    assert block.hash == '0f59bbd5a22a6484cc206b7ee9d4bc1744bed9c5649ff332f88a0eec461f58c8'
    assert block.hash_satisfies_difficulty
    assert block.is_valid is False
def test_block_comparisons():
    """Test the comparison of different blocks"""

    block_0 = Block(0, 'block data', timestamp=0)
    block_1 = Block(0, 'block data', timestamp=0)
    block_2 = Block(1, 'block data', timestamp=0)

    assert block_0 == block_0
    assert block_0 == block_1
    assert block_0 != block_2
    def __init__(self, block=None):
        """BlockChain object"""

        # Difficulty update parameters
        # 59 Seconds - the minimum interval between blocks is a minute
        self.minimum_interval = 59

        # 10 minutes - the objective interval between blocks
        self.block_interval = 600

        # 144 block - the time for evaluate intervals (one per day 6 * 24)
        self.difficulty_interval = 144

        # Currency values
        self.__unspent = None
        self.amount_mining = 0

        # Validate the inputs
        if block is None:
            self.__candidate = Block.genesis_block()
        elif isinstance(block, Block):
            self.__candidate = block
        else:
            raise Exception("The input parameter must be a Block object")

        if self.__candidate.is_valid:
            self.chain = [self.__candidate]
            self.__candidate = None
        else:
            self.chain = []
Ejemplo n.º 5
0
def test_update_difficulty():
    """Test increase and decrease difficulty values"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1, 0, 0, 0), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    # Change the interval for tests
    blockchain.difficulty_interval = 2

    # Blocks generates in a minute: the difficulty must increase
    for minute in range(1, 7):
        blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1, 0, minute, 0))
        blockchain.mining_candidate()

    assert blockchain.chain[3].difficulty == 4
    assert blockchain.chain[4].difficulty == 5
    assert blockchain.chain[5].difficulty == 5
    assert blockchain.chain[6].difficulty == 6

    # Blocks generates in any eleven minutes: the difficulty must decrease
    for minute in range(17, 60, 11):
        blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1, 0, minute, 0))
        blockchain.mining_candidate()

    blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1, 1, 1, 0))
    blockchain.mining_candidate()

    assert blockchain.chain[9].difficulty == 7
    assert blockchain.chain[10].difficulty == 6
Ejemplo n.º 6
0
def test_replace_chain():
    """Test replace a chain"""

    # Generate the genesis block
    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)

    # Generate a old chain
    old_chain = BlockChain(block)

    # Generate longer chain
    blockchain = BlockChain(block)

    # Cannot be replace the chain
    assert old_chain.replace_chain(blockchain) is False

    # Add new block
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 2))
    assert blockchain.candidate_proof(4)

    # Replace the chain
    assert old_chain.replace_chain(blockchain)

    # Check the chain values
    assert old_chain.num_blocks == 2
    assert old_chain.candidate_block is None
    def new_cryptocurrency(address,
                           amount,
                           timestamp=None,
                           proof=0,
                           difficulty=0,
                           mining=False):
        """Create a new cryptocurrency

        Args:
            address (string): the address of the first user
            amount (Double): the amount for the first user
            timestamp (String): the genesis block time
            proof (Integer): the proof
            difficulty (Integer): the number of zeros in the hash to validate the block
            mining (Boolean): logical value indicating if the block must be mined

        Return:
            (BlockChain): A new blockchain
        """

        output = OutputTransaction(address, amount)
        transaction = Transaction(timestamp, output)
        block = Block.genesis_block([transaction],
                                    timestamp=timestamp,
                                    proof=proof,
                                    difficulty=difficulty,
                                    mining=mining)

        blokchain = BlockChain(block)
        blokchain.amount_mining = amount

        return blokchain
Ejemplo n.º 8
0
def test_add_candidates():
    """Validate to include a new candidate"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    assert blockchain.last_block == block
    assert blockchain.candidate_block is None
    assert blockchain.num_blocks == 1
    assert blockchain.is_valid

    # Add a new candidate
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 2))

    assert blockchain.last_block == block
    assert blockchain.candidate_block is not None
    assert blockchain.num_blocks == 1
    assert blockchain.is_valid

    # Fail in the mining process
    assert blockchain.mining_candidate(maximum_iter=0) is False

    # Mining the candidate block
    assert blockchain.mining_candidate()

    # The blockchain is now valid
    assert blockchain.last_block != block
    assert blockchain.candidate_block is None
    assert blockchain.num_blocks == 2
    assert blockchain.is_valid
Ejemplo n.º 9
0
def test_basic_blockchain():
    """Test the basic blockchain"""

    # BlockChain without values
    blockchain = BlockChain()

    assert blockchain.last_block is not None
    assert blockchain.candidate_block is None
    assert blockchain.num_blocks == 1
    assert blockchain.is_valid

    # Manual definition of the genesis block
    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4)
    blockchain = BlockChain(block)

    assert blockchain.last_block is None
    assert blockchain.candidate_block == block
    assert blockchain.num_blocks == 0
    assert blockchain.is_valid is False

    # it is not possible to add a new candidate
    assert blockchain.add_candidate(None) is False

    # Mining the candidate block
    assert blockchain.mining_candidate()

    # The blockchain is now valid
    assert blockchain.last_block == block
    assert blockchain.candidate_block is None
    assert blockchain.num_blocks == 1
    assert blockchain.is_valid
Ejemplo n.º 10
0
def test_validate_blockchain():
    """Test BlockChain validation"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    # Add a new candidate
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 2))
    assert blockchain.candidate_proof(4)

    assert blockchain.is_valid

    # Alter the index
    blockchain.chain[1].index = 2

    assert blockchain.chain[1].is_valid is False
    assert blockchain.is_valid is False

    # Mining new id
    blockchain.chain[1].mining()

    assert blockchain.chain[1].is_valid
    assert blockchain.is_valid is False

    # Change the block
    blockchain.chain[1] = block

    assert blockchain.is_valid is False
def test_block_difficulty():
    """Test proof-of-work validation"""

    block = Block(0, 'Test Data', timestamp=0)

    assert block.hash_satisfies_difficulty

    block.difficulty = 4

    assert block.hash_satisfies_difficulty is False

    block.proof = 49

    assert block.proof == 49
    assert block.hash_satisfies_difficulty

    block.proof = 5

    assert block.proof == 5
    assert block.hash_satisfies_difficulty is False
    def add_candidate(self, data, timestamp=None, proof=0):
        """Insert a new candidate in the chain

        Return:
            (logical): true where the candidate can be assigned
        """

        if self.__candidate is not None:
            return False
        else:
            # Get the timestamp value where it is None
            if timestamp is None:
                timestamp = datetime.now()

            # Validate minimum timestamp period
            interval = timestamp - self.last_block.timestamp
            if interval.total_seconds() < self.minimum_interval:
                return False

            # Evaluate the difficulty
            if self.num_blocks > self.difficulty_interval and self.num_blocks % self.difficulty_interval == 0:
                interval = self.chain[-1].timestamp - self.chain[
                    -self.difficulty_interval - 1].timestamp
                interval = interval.total_seconds() / self.difficulty_interval

                if interval > self.block_interval:
                    difficulty = self.last_block.difficulty - 1
                elif interval < self.block_interval:
                    difficulty = self.last_block.difficulty + 1

            else:
                difficulty = self.last_block.difficulty

            self.__candidate = Block(self.last_block.index + 1,
                                     data,
                                     previous_hash=self.last_block.hash,
                                     timestamp=timestamp,
                                     proof=proof,
                                     difficulty=difficulty)

            return True
Ejemplo n.º 13
0
def test_minimum_interval():
    """Test minimum interval period"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1, 0, 0, 0), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    # A block in a minute
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1, 0, 1, 0))
    assert blockchain.mining_candidate()

    # A block in 30 seconds is not valid
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1, 0, 1, 30)) is False
def test_mining():
    """Test the mining process in the Block"""

    block = Block(0, 'Test data', timestamp=0)

    block.difficulty = 8

    assert block.hash_satisfies_difficulty is False
    assert block.is_valid is False

    # Calculate a valid proof
    assert block.mining()

    assert block.hash_satisfies_difficulty
    assert block.is_valid

    # Set a value which cannot get a valid result
    assert block.mining(init=0, maximum_iter=100) is False

    assert block.hash_satisfies_difficulty is False
    assert block.is_valid is False
def test_genesis_block():
    """Validate the genesis block test"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1),
                                difficulty=6,
                                mining=True)

    assert block.index == 0
    assert block.previous_hash == '5e9fe54187feed1f12324ffa7bd9dc3d662706e1fd66a97eafbbefa912262aa2'
    assert block.difficulty == 6
    assert block.hash == '01392e12860c25f46adad36d001d9c9aeafc280cf8be290021e61c2ec2cb54de'
    assert block.timestamp == datetime(2000, 1, 1)
    assert block.data is None
    assert block.proof == 46
Ejemplo n.º 16
0
def test_mining_candidate():
    """Test mining candidate """

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    # Add a new candidate
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 2))
    assert blockchain.mining_candidate()

    # Add an empty candidate
    assert blockchain.add_candidate(None)
    assert blockchain.mining_candidate()

    # Add an invalid candidate (same date)
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1)) is False
    assert blockchain.mining_candidate() is False
Ejemplo n.º 17
0
def test_change_block_contains():
    """Test different attacks to the blockchain"""

    data = ["Avocado", "Apple", "Cherry", "Orange", "Strawberry"]

    block = Block.genesis_block(data[0], timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    for minute in range(1, len(data)):
        assert blockchain.add_candidate(data[minute], timestamp=datetime(2000, 1, 1, 0, minute, 0))
        assert blockchain.mining_candidate()

    assert blockchain.is_valid

    # Alter the blockchain data
    blockchain.chain[2].data = 'Khaki'

    assert blockchain.is_valid is False
Ejemplo n.º 18
0
def test_block_repr():
    """Test the report function"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    assert repr(blockchain) == \
           "Block: 0 (2000-01-01 00:00:00) - Hash: 015921ac58652b4e01d109cbb3ad10b55836e700a93e761036343ce8a82023ae\n"

    for minute in range(1, 6):
        blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1, 0, minute, 0))
        blockchain.mining_candidate()

    assert repr(blockchain) == \
           "Block: 5 (2000-01-01 00:05:00) - Hash: 0332726221951b73e5fb945914341aa1e92014151680375cdf8c632256dc9a54\n" + \
           "Block: 4 (2000-01-01 00:04:00) - Hash: 0bf9e27631d08c326b15f7b459b47f91d9202c07ede76ce8503d636e09070402\n" + \
           "Block: 3 (2000-01-01 00:03:00) - Hash: 07ded3c477dc38fe3d0bc434b2924430f81231a0d7878f906e2a655c1f17d444\n" + \
           "Block: 2 (2000-01-01 00:02:00) - Hash: 0f8ed5b1cc4dca95c0c04b5b64a1ac409d636dbfd294282092f579d3872b1c15\n" + \
           "Block: 1 (2000-01-01 00:01:00) - Hash: 037eeba6b1779e5edd02e4a2b4909260193adaab7ef8abaf04ddc8ab2fc39c95\n" + \
           "\nand 1 block hidden"
Ejemplo n.º 19
0
def test_add_candidates_prof():
    """Test to update the proof to a candidate"""

    block = Block.genesis_block(timestamp=datetime(2000, 1, 1), difficulty=4, mining=True)
    blockchain = BlockChain(block)

    # Add a new candidate
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 2))

    # Add proof to candidate
    assert blockchain.candidate_proof(3) is False
    assert blockchain.candidate_proof(4)

    # Add an empty candidate
    assert blockchain.add_candidate(None)

    # Add an invalid candidate (same date)
    assert blockchain.add_candidate(None, timestamp=datetime(2000, 1, 1)) is False

    assert blockchain.candidate_proof(3) is False
    assert blockchain.candidate_proof(4) is False
def test_creation_block():
    """Test errors during the construcion of an object"""

    with pytest.raises(Exception):
        Block()
class BlockChain:
    """BlockChain object"""
    def __init__(self, block=None):
        """BlockChain object"""

        # Difficulty update parameters
        # 59 Seconds - the minimum interval between blocks is a minute
        self.minimum_interval = 59

        # 10 minutes - the objective interval between blocks
        self.block_interval = 600

        # 144 block - the time for evaluate intervals (one per day 6 * 24)
        self.difficulty_interval = 144

        # Currency values
        self.__unspent = None
        self.amount_mining = 0

        # Validate the inputs
        if block is None:
            self.__candidate = Block.genesis_block()
        elif isinstance(block, Block):
            self.__candidate = block
        else:
            raise Exception("The input parameter must be a Block object")

        if self.__candidate.is_valid:
            self.chain = [self.__candidate]
            self.__candidate = None
        else:
            self.chain = []

    def __repr__(self):
        """ Return repr(self). """

        last_block = max(0, self.num_blocks - 5)
        result = ""

        for step in reversed(range(last_block, self.num_blocks)):
            result = "%sBlock: %d (%s) - Hash: %s\n" % \
                     (result, self.chain[step].index, self.chain[step].timestamp,
                      self.chain[step].hash)

        if last_block > 0:
            result = '%s\nand %d block hidden' % (result, last_block)

        return result

    @property
    def is_valid(self):
        """Indicate if the blockchain is valid

        The blockchain is valid when
            1) The blocks are valid
            2) All blocks has previous block hash and a valid id
            3) All blocks are valid and

        Return:
             (Logical): True if BlockChain is valid
        """

        if self.chain:
            for step in range(self.num_blocks - 1, 0, -1):
                if not self.chain[step].is_valid:
                    return False
                elif self.chain[step].previous_hash != self.chain[step -
                                                                  1].hash:
                    return False
                elif self.chain[step].index != self.chain[step - 1].index + 1:
                    return False

            return self.chain[0].is_valid

        return False

    @property
    def candidate_block(self):
        """Return the candidate for block for the chain"""

        return self.__candidate

    @property
    def last_block(self):
        """Return the last block in the BlockChain"""

        if self.chain:
            return self.chain[-1]

        return None

    @property
    def num_blocks(self):
        """Return the number of blocks in the chain"""

        return len(self.chain)

    def add_candidate(self, data, timestamp=None, proof=0):
        """Insert a new candidate in the chain

        Return:
            (logical): true where the candidate can be assigned
        """

        if self.__candidate is not None:
            return False
        else:
            # Get the timestamp value where it is None
            if timestamp is None:
                timestamp = datetime.now()

            # Validate minimum timestamp period
            interval = timestamp - self.last_block.timestamp
            if interval.total_seconds() < self.minimum_interval:
                return False

            # Evaluate the difficulty
            if self.num_blocks > self.difficulty_interval and self.num_blocks % self.difficulty_interval == 0:
                interval = self.chain[-1].timestamp - self.chain[
                    -self.difficulty_interval - 1].timestamp
                interval = interval.total_seconds() / self.difficulty_interval

                if interval > self.block_interval:
                    difficulty = self.last_block.difficulty - 1
                elif interval < self.block_interval:
                    difficulty = self.last_block.difficulty + 1

            else:
                difficulty = self.last_block.difficulty

            self.__candidate = Block(self.last_block.index + 1,
                                     data,
                                     previous_hash=self.last_block.hash,
                                     timestamp=timestamp,
                                     proof=proof,
                                     difficulty=difficulty)

            return True

    def add_transaction(self, key, address, amount):
        """Add a transaction in the blockchain

        Add a transaction to the candidate list from the signer's account to the destination account for the indicated
        amount.

        Args:
            key (String): the private key of the user
            address (String): the destination address
            amount (Double): the amount of the

        Return:
            (Boolean): True if the transaction can be done
        """
        wallet = self.get_wallet(key)
        transaction = wallet.generate_transaction_to(address, amount)

        if transaction is None:
            return False

        unspent = self.get_unspent_list()

        return unspent.append_unconfirmed(transaction)

    def candidate_proof(self, proof):
        """Set the proof for a candidate"""

        self.__candidate.proof = proof

        if self.__candidate.is_valid:
            self.chain.append(self.__candidate)
            self.__candidate = None
            self.__unspent = None
            return True

        return False

    def generate_candidate(self, address, timestamp=None, proof=0):
        """Generate a new candidate block with a reward to the miner

        Args:
            address (String): the address fo the miner
            timestamp (String): the genesis block time
            proof (Integer): the proof

        Return:
            (logical): true where the candidate can be assigned
        """

        output = OutputTransaction(address, self.amount_mining)
        transaction = Transaction(timestamp, output)

        self.__unspent.append_unconfirmed(transaction)
        data = self.__unspent.unconfirmed

        return self.add_candidate(data, timestamp=timestamp, proof=proof)

    def get_unspent_list(self):
        """Get the list of unspent transaction

        Return:
            (UnspentList): the list unspent transactions
        """

        if self.__unspent is None:
            self.__unspent = UnspentList()

            for block in self.chain:
                for transaction in block.data:
                    assert self.__unspent.append_unconfirmed(transaction)

                if block.is_valid:
                    self.__unspent.confirm_unconfirmed()

        return self.__unspent

    def get_wallet(self, key=None):
        """Get the wallet of a user

        Args:
            key (String): the private key of the user

        Return:
             (Wallet): the wallet of the user
        """

        return Wallet(key, self)

    def mining_candidate(self, init=None, maximum_iter=1000):
        """Mining the Candidate Block

        Implements the search of an integer for the proof which satisficed the
        required difficult. The initial value can be configured using the
        `init` property. The maximum number of values to tried can be also
        configured using the `init` property.

        Args:
            self (Block): A Block object
            init (Integer): The values to use in the first proof
            maximum_iter (Integer): The maximum number of iterations in the mining process

        Return:
             (Logical): True if a valid proof has been found
        """

        if self.__candidate is None:
            return False

        if self.__candidate.mining(init, maximum_iter):
            self.chain.append(self.__candidate)
            self.__candidate = None
            self.__unspent = None
            return True

        return False

    @staticmethod
    def new_cryptocurrency(address,
                           amount,
                           timestamp=None,
                           proof=0,
                           difficulty=0,
                           mining=False):
        """Create a new cryptocurrency

        Args:
            address (string): the address of the first user
            amount (Double): the amount for the first user
            timestamp (String): the genesis block time
            proof (Integer): the proof
            difficulty (Integer): the number of zeros in the hash to validate the block
            mining (Boolean): logical value indicating if the block must be mined

        Return:
            (BlockChain): A new blockchain
        """

        output = OutputTransaction(address, amount)
        transaction = Transaction(timestamp, output)
        block = Block.genesis_block([transaction],
                                    timestamp=timestamp,
                                    proof=proof,
                                    difficulty=difficulty,
                                    mining=mining)

        blokchain = BlockChain(block)
        blokchain.amount_mining = amount

        return blokchain

    def replace_chain(self, new_chain):
        """Replace this chair for a longest one

        Return:
            (logical): True is the replacement is valid
        """

        if isinstance(new_chain, BlockChain):
            if new_chain.num_blocks > self.num_blocks:
                if new_chain.chain[0] == self.chain[0]:
                    if new_chain.is_valid:
                        self.chain = new_chain.chain
                        self.__candidate = new_chain.candidate_block

                        return True

        return False
def test_block_repr():
    """Test the report function"""

    assert repr(Block(0, ' data', timestamp=0)) == \
           'Block: 0 (0)\n Hash: d8345cd456a98ac9533f1c9f662685318413d76333741100ecdf34b112488e5e\n Previous: None'