def test_unconfirmed_minner(): """Test operation with wallets""" # List of unspent transactions unspent = UnspentList() # Wallets wallet_1 = Wallet('aedc3975fa118bec4a1d203cd2b996c4ceb5aa398b7f7518') wallet_2 = Wallet('7d6433bcc63f973580dc7562d2ca79fcb12bb4e08c7e7333') wallet_3 = Wallet('3d66f0ea52a2c5cf42893560d5522e82621790edeb7f609b') # Link unspent to the wallets wallet_1.unspent = unspent wallet_2.unspent = unspent assert wallet_1.get_balance() == 0 assert wallet_2.get_balance() == 0 # Add transaction for miner inputs = datetime(2000, 1, 1, 0, 0, 0) outputs = OutputTransaction(wallet_1.public, 5) transaction = Transaction(inputs, outputs, wallet_1.private) assert unspent.append_unconfirmed(transaction) assert unspent.append_unconfirmed(transaction) is False assert unspent.confirm_unconfirmed() assert wallet_1.get_balance() == 5 assert wallet_2.get_balance() == 0
def test_unconfirmed(): """Test operation with wallets""" wallet_1 = Wallet('aedc3975fa118bec4a1d203cd2b996c4ceb5aa398b7f7518') wallet_2 = Wallet('7d6433bcc63f973580dc7562d2ca79fcb12bb4e08c7e7333') # List of unspent transactions unspent = UnspentList() # Assign 250 coins to the wallet #1 unspent.unspent.append( UnspentTransaction( 'd749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0, wallet_1.get_account(), 250)) assert unspent.address_amount(wallet_1.get_account()) == 250 assert unspent.address_amount(wallet_2.get_account()) == 0 # Link unspent to the wallets wallet_1.unspent = unspent wallet_2.unspent = unspent # Validate accounts assert wallet_1.get_balance() == 250 assert wallet_2.get_balance() == 0 # Try to transfer 900 coins form 1 to 2 assert wallet_1.generate_transaction_to(wallet_2.get_account(), 900) is None # Generate a transfer of 200 coins form 1 to 2 transaction = wallet_1.generate_transaction_to(wallet_2.get_account(), 200) # The operation can be append to the unconfirmed transactions assert unspent.append_unconfirmed(transaction) # Cannot add to the unconfirmed list again assert unspent.append_unconfirmed(transaction) is False # Validate accounts (unconfirmed cannot be spend again) assert wallet_1.get_balance() == 0 assert wallet_2.get_balance() == 0 # Confirm unconfirmed transactions assert unspent.confirm_unconfirmed() # Validate accounts assert wallet_1.get_balance() == 50 assert wallet_2.get_balance() == 200 # Spend more transactions assert unspent.append_unconfirmed( wallet_1.generate_transaction_to(wallet_2.get_account(), 25)) assert unspent.append_unconfirmed( wallet_2.generate_transaction_to(wallet_1.get_account(), 10)) assert unspent.confirm_unconfirmed() # Validate accounts assert wallet_1.get_balance() == 35 assert wallet_2.get_balance() == 215
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 test_wallet_operation(): """Test operation with wallets""" wallet_1 = Wallet('aedc3975fa118bec4a1d203cd2b996c4ceb5aa398b7f7518') wallet_2 = Wallet('7d6433bcc63f973580dc7562d2ca79fcb12bb4e08c7e7333') wallet_3 = Wallet('3d66f0ea52a2c5cf42893560d5522e82621790edeb7f609b') # List of unspent transactions unspent = UnspentList() # Assign 100 coins to the wallet #1 unspent.unspent.append(UnspentTransaction('d749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0, wallet_1.get_account(), 100)) assert unspent.address_amount(wallet_1.get_account()) == 100 assert unspent.address_amount(wallet_2.get_account()) == 0 assert unspent.address_amount(wallet_3.get_account()) == 0 # Link unspent to the wallets wallet_1.unspent = unspent wallet_2.unspent = unspent wallet_3.unspent = unspent # Validate accounts assert wallet_1.get_balance() == 100 assert wallet_2.get_balance() == 0 assert wallet_3.get_balance() == 0 # Try to transfer 900 coins form 1 to 2 assert wallet_1.generate_transaction_to(wallet_2.get_account(), 900) is None # Transfer 90 coins form 1 to 2 transaction = wallet_1.generate_transaction_to(wallet_2.get_account(), 90) assert transaction is not None assert transaction.hash_id == transaction.generate_transaction_id() # Spend the transaction assert unspent.validate_transaction(transaction) assert unspent.spend_transaction(transaction) assert unspent.validate_transaction(transaction) is False assert wallet_1.get_balance() == 10 assert wallet_2.get_balance() == 90 assert wallet_3.get_balance() == 0 # Transfer 50 form 1 to 3 assert wallet_1.transfer_to(wallet_3.get_account(), 50) is False # Transfer 5 form 1 to 3 assert wallet_1.transfer_to(wallet_3.get_account(), 5) assert wallet_1.get_balance() == 5 assert wallet_2.get_balance() == 90 assert wallet_3.get_balance() == 5 # Transfer 0.5 form 2 to 3 assert wallet_2.transfer_to(wallet_3.get_account(), 0.5) assert wallet_1.get_balance() == 5 assert wallet_2.get_balance() == 89.5 assert wallet_3.get_balance() == 5.5
def test_basic_transaction(): """"Test basic transactions""" # List of users accounts private_1 = 'aedc3975fa118bec4a1d203cd2b996c4ceb5aa398b7f7518' private_2 = '7d6433bcc63f973580dc7562d2ca79fcb12bb4e08c7e7333' private_3 = '3d66f0ea52a2c5cf42893560d5522e82621790edeb7f609b' public_1 = generate_public_key(private_1) public_2 = generate_public_key(private_2) public_3 = generate_public_key(private_3) # List of unspent transactions unspent = UnspentList() # Assign 10 coins to user 1 unspent.unspent.append( UnspentTransaction( 'd749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0, public_1, 10)) # Assign 10 coins to user 2 unspent.unspent.append( UnspentTransaction( 'fa34b15a4eb8e91ebeff64edba51d21905e1ac8ce2bff8865da08055bced0dd4', 0, public_2, 10)) # Validate users amount assert unspent.address_amount(public_1) == 10 assert unspent.address_amount(public_2) == 10 assert unspent.address_amount(public_3) == 0 # Move 10 coins from account 1 to account 3 transaction_input = InputTransaction( 'd749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0) transaction_output = OutputTransaction(public_3, 10) transaction = Transaction(transaction_input, transaction_output) # The transaction is no signed assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) is False assert unspent.validate_transaction(transaction) is False assert unspent.spend_transaction(transaction) is False # The transaction is signed by user 3 transaction.sign(private_3) assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) is False assert is_signature_valid(transaction.hash_id, transaction.signature, public_3) assert unspent.validate_transaction(transaction) is False assert unspent.spend_transaction(transaction) is False # The transaction is signed by user 1 transaction.sign(private_1) assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) assert unspent.validate_transaction(transaction) assert unspent.spend_transaction(transaction) assert unspent.validate_transaction(transaction) is False # Validate users amount assert unspent.address_amount(public_1) == 0 assert unspent.address_amount(public_2) == 10 assert unspent.address_amount(public_3) == 10
def test_multiple_transaction(): """"Test multiple transactions""" # List of users accounts private_1 = 'aedc3975fa118bec4a1d203cd2b996c4ceb5aa398b7f7518' private_2 = '7d6433bcc63f973580dc7562d2ca79fcb12bb4e08c7e7333' private_3 = '3d66f0ea52a2c5cf42893560d5522e82621790edeb7f609b' public_1 = generate_public_key(private_1) public_2 = generate_public_key(private_2) public_3 = generate_public_key(private_3) # List of unspent transactions unspent = UnspentList() # Assign 10 coins to user 1 unspent.unspent.append( UnspentTransaction( 'd749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0, public_1, 10)) # Assign 10 coins to user 2 unspent.unspent.append( UnspentTransaction( 'fa34b15a4eb8e91ebeff64edba51d21905e1ac8ce2bff8865da08055bced0dd4', 0, public_2, 10)) # Validate users amount assert unspent.address_amount(public_1) == 10 assert unspent.address_amount(public_2) == 10 assert unspent.address_amount(public_3) == 0 # Cannot validate None transactions assert unspent.validate_transaction(None) is False assert unspent.spend_transaction(None) is False # Cannot validate other validations assert unspent.validate_transaction('transactions') is False assert unspent.spend_transaction('transactions') is False # Account 1 move 5 to account 2 input = InputTransaction( 'd749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0) output = OutputTransaction(public_3, 5) transaction = Transaction(input, output) # The transaction is no signed assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) is False assert unspent.validate_transaction(transaction) is False assert unspent.spend_transaction(transaction) is False # The transaction is signed by user 2 transaction.sign(private_2) assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) is False assert is_signature_valid(transaction.hash_id, transaction.signature, public_2) assert unspent.validate_transaction(transaction) is False assert unspent.spend_transaction(transaction) is False # The transaction is signed by user 1 transaction.sign(private_1) # The transaction is not valid because he don't spend all currencies assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) assert unspent.validate_transaction(transaction) is False assert unspent.spend_transaction(transaction) is False # Account 1 move 5 to account 2 ans 5 to himself input = InputTransaction( 'd749929fe94b9a37dbe5d74cb297ad24b46e467898545f4e109eb21835046ac4', 0) output = [OutputTransaction(public_1, 5), OutputTransaction(public_2, 5)] transaction = Transaction(input, output) # The transaction is signed by user 1 transaction.sign(private_1) # The transaction is valid and can be spend assert is_signature_valid(transaction.hash_id, transaction.signature, public_1) assert unspent.validate_transaction(transaction) assert unspent.spend_transaction(transaction) assert unspent.validate_transaction(transaction) is False # Validate users amount assert unspent.address_amount(public_1) == 5 assert unspent.address_amount(public_2) == 15 assert unspent.address_amount(public_3) == 0 # User 2 move 12 coins to 3 and 3 to himself user_transactions = unspent.address_transactions(public_2) input_transactions = [] for user in user_transactions: input_transactions.append(InputTransaction(user.hash_id, user.index)) output = [OutputTransaction(public_2, 3), OutputTransaction(public_3, 12)] transaction = Transaction(input_transactions, output) # The transaction is signed by user 2 transaction.sign(private_2) # The transaction is valid and can be spend assert is_signature_valid(transaction.hash_id, transaction.signature, public_2) assert unspent.validate_transaction(transaction) assert unspent.spend_transaction(transaction) assert unspent.validate_transaction(transaction) is False assert unspent.address_amount(public_1) == 5 assert unspent.address_amount(public_2) == 3 assert unspent.address_amount(public_3) == 12 # User 3 spend 3 in 1 and 2 input_transactions = [] user_transactions = unspent.address_transactions(public_3) for user in user_transactions: input_transactions.append(InputTransaction(user.hash_id, user.index)) output = [ OutputTransaction(public_1, 3), OutputTransaction(public_2, 3), OutputTransaction(public_3, 6) ] transaction = Transaction(input_transactions, output) # The transaction is signed by user 3 transaction.sign(private_3) # The transaction is valid and can be spend assert is_signature_valid(transaction.hash_id, transaction.signature, public_3) assert unspent.validate_transaction(transaction) assert unspent.spend_transaction(transaction) assert unspent.validate_transaction(transaction) is False assert unspent.address_amount(public_1) == 8 assert unspent.address_amount(public_2) == 6 assert unspent.address_amount(public_3) == 6
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