def test_is_valid_transaction_chain_multiple_rewards(blockchain_three_blocks): reward_1 = Transaction.reward_transaction(Wallet()).to_json() reward_2 = Transaction.reward_transaction(Wallet()).to_json() blockchain_three_blocks.add_block([reward_1, reward_2]) with pytest.raises(Exception, match='one mining reward per block'): Blockchain.is_valid_transaction_chain(blockchain_three_blocks.chain)
def is_valid_transaction_chain(chain): """ Enforce the rules of a chain composed of blocks of transactions - each transaction must only appear once in the chain - There can be only one mining reward per block - Each transaction must be valid """ transaction_ids = set() for i in range(len(chain)): block = chain[i] has_mining_reward = False for transaction_json in block.data: transaction = Transaction.from_json(transaction_json) if transaction.id in transaction_ids: raise Exception(f'Transaction: {transaction.id} is not unique') transaction_ids.add(transaction.id) if transaction.input == MINING_REWARD_INPUT: if has_mining_reward: raise Exception(f'There can be only one mining reward per block check this block: {block.hash}') has_mining_reward = True else: historic_blockchain = Blockchain() historic_blockchain.chain = chain[0:i] historic_balance = Wallet.calculate_balance(historic_blockchain, transaction.input['address']) if historic_balance != transaction.input['amount']: raise Exception(f'Transaction {transaction.id} has '\ 'invalid input amount') Transaction.transaction_is_valid(transaction)
def is_valid_transaction_chain_bad_transaction(blockchain_three_blocks): bad_transaction = Transaction(Wallet(), 'recipient', 1) bad_transaction.input['signature'] = Wallet().sign(bad_transaction.output) blockchain_three_blocks.add_block([bad_transaction.to_json()]) with pytest.raises(Exception): Blockchain.is_valid_transaction_chain(blockchain_three_blocks.chain)
def test_valid_transaction_with_invalid_outputs(): sender_wallet = Wallet() transaction = Transaction(sender_wallet, 'reciepient', 50) transaction.output[sender_wallet.address] = 1998 with pytest.raises(Exception, match='Invalid transaction output values'): Transaction.transaction_is_valid(transaction)
def test_calculate_balance(): blockchain = Blockchain() wallet = Wallet() assert Wallet.calculate_balance(blockchain, wallet.address) == STARTING_BALANCE amount = 50 transaction = Transaction(wallet, 'reciepient', amount) blockchain.add_block([transaction.to_json()]) assert Wallet.calculate_balance( blockchain, wallet.address) == STARTING_BALANCE - amount recieved_amount_1 = 25 recieved_transaction_1 = Transaction(Wallet(), wallet.address, recieved_amount_1) recieved_amount_2 = 43 recieved_transaction_2 = Transaction(Wallet(), wallet.address, recieved_amount_2) blockchain.add_block( [recieved_transaction_1.to_json(), recieved_transaction_2.to_json()]) assert Wallet.calculate_balance( blockchain, wallet.address ) == STARTING_BALANCE - amount + recieved_amount_1 + recieved_amount_2
def test_is_valid_transaction_chain_bad_historic_balance(blockchain_three_blocks): wallet = Wallet() bad_transaction = Transaction(wallet, 'recipient', 1) bad_transaction.output[wallet.address] = 9000 bad_transaction.input['amount'] = 9001 bad_transaction.input['signature'] = wallet.sign(bad_transaction.output) blockchain_three_blocks.add_block([bad_transaction.to_json()]) with pytest.raises(Exception, match='has invalid input amount'): Blockchain.is_valid_transaction_chain(blockchain_three_blocks.chain)
def route_wallet_transact(): transaction_data = request.get_json() transaction = transaction_pool.existing_transaction(wallet.address) if transaction: transaction.update(wallet, transaction_data['reciepient'], transaction_data['amount']) else: transaction = Transaction(wallet, transaction_data['reciepient'], transaction_data['amount']) pubsub.broadcast_transaction(transaction) return jsonify(transaction.to_json())
def route_blockchain_mine(): transaction_data = transaction_pool.transaction_data() transaction_data.append(Transaction.reward_transaction(wallet).to_json()) blockchain.add_block(transaction_data) block = blockchain.chain[-1] pubsub.broadcast_block(block) transaction_pool.clear_blockchain_transactions(blockchain) return jsonify(block.to_json())
def test_transaction_update(): sender_wallet = Wallet() first_recipient = 'first_person' first_amount = 50 transaction = Transaction(sender_wallet, first_recipient, first_amount) next_reciepient = 'other_person' next_amount = 80 transaction.update(sender_wallet, next_reciepient, next_amount) assert transaction.output[next_reciepient] == next_amount assert transaction.output[ sender_wallet. address] == sender_wallet.balance - first_amount - next_amount assert Wallet.verify(transaction.input['public_key'], transaction.output, transaction.input['signature']) to_first_again_amount = 25 transaction.update(sender_wallet, first_recipient, to_first_again_amount) assert transaction.output[ first_recipient] == first_amount + to_first_again_amount assert transaction.output[ sender_wallet. address] == sender_wallet.balance - first_amount - next_amount - to_first_again_amount assert Wallet.verify(transaction.input['public_key'], transaction.output, transaction.input['signature'])
def test_transaction(): sender_wallet = Wallet() reciepient = 'reciepient' amount = 50 transaction = Transaction(sender_wallet, reciepient, amount) assert transaction.output[reciepient] == amount assert transaction.output[ sender_wallet.address] == sender_wallet.balance - amount assert 'timestamp' in transaction.input assert transaction.input['amount'] == sender_wallet.balance assert transaction.input['address'] == sender_wallet.address assert transaction.input['public_key'] == sender_wallet.public_key assert Wallet.verify(transaction.input['public_key'], transaction.output, transaction.input['signature'])
def message(self, pubnub, msg_obj): print( f'\n-- Incoming Channel: {msg_obj.channel} | message: {msg_obj.message}' ) if msg_obj.channel == CHANNELS['BLOCK']: block = Block.from_json(msg_obj.message) potential_chain = self.blockchain.chain[:] potential_chain.append(block) try: self.blockchain.replace_chain(potential_chain) self.transaction_pool.clear_blockchain_transactions( self.blockchain) print('successfullt replaced the chaihn') except Exception as e: print(f'\n -- Did not replace chain {e}') elif msg_obj.channel == CHANNELS['TRANSACTION']: transaction = Transaction.from_json(msg_obj.message) self.transaction_pool.set_transaction(transaction) print('\n-- Set the new transaction in the transaction pool')
def test_clear_blockchain_transaction(): transaction_pool = TransactionPool() transaction_1 = Transaction(Wallet(), 'reciepient', 1) transaction_2 = Transaction(Wallet(), 'reciepient', 2) transaction_pool.set_transaction(transaction_1) transaction_pool.set_transaction(transaction_2) blockchain = Blockchain() blockchain.add_block([transaction_1.to_json(), transaction_2.to_json()]) assert transaction_1.id in transaction_pool.transaction_map assert transaction_2.id in transaction_pool.transaction_map transaction_pool.clear_blockchain_transactions(blockchain) assert not transaction_1.id in transaction_pool.transaction_map assert not transaction_2.id in transaction_pool.transaction_map
def test_invalid_reward_transaction_extra_reciepient(): reward_transaction = Transaction.reward_transaction(Wallet()) reward_transaction.output['extra_reciepient'] = 60 with pytest.raises(Exception, match='Invalid Mining Reward'): Transaction.transaction_is_valid(reward_transaction)
def test_valid_reward_transaction(): reward_transaction = Transaction.reward_transaction(Wallet()) Transaction.transaction_is_valid(reward_transaction)
def test_reward_transaction(): miner_wallet = Wallet() transaction = Transaction.reward_transaction(miner_wallet) assert transaction.input == MINING_REWARD_INPUT assert transaction.output[miner_wallet.address] == MINING_REWARD
def test_valid_transaction_with_invalid_signature(): transaction = Transaction(Wallet(), 'reciepient', 50) transaction.input['signature'] = Wallet().sign(transaction.output) with pytest.raises(Exception, match='Invalid signature'): Transaction.transaction_is_valid(transaction)
def test_set_transaction(): transaction_pool = TransactionPool() transaction = Transaction(Wallet(), 'reciepient', 1) transaction_pool.set_transaction(transaction) assert transaction_pool.transaction_map[transaction.id] == transaction
def blockchain_three_blocks(): blockchain = Blockchain() for i in range(3): blockchain.add_block([Transaction(Wallet(), 'recipient', i).to_json()]) return blockchain
def test_valid_transaction(): Transaction.transaction_is_valid(Transaction(Wallet(), 'reciepient', 50))
def test_is_valid_transaction_chain_duplicate_transactions(blockchain_three_blocks): transaction = Transaction(Wallet(), 'recipient', 1).to_json() blockchain_three_blocks.add_block([transaction, transaction]) with pytest.raises(Exception, match='is not unique'): Blockchain.is_valid_transaction_chain(blockchain_three_blocks.chain)
def test_transaction_update_exceeds_balance(): sender_wallet = Wallet() transaction = Transaction(sender_wallet, 'reciepient', 50) with pytest.raises(Exception, match='Amount exceeds balance'): transaction.update(sender_wallet, 'new_reciepient', 1998)
def test_transaction_exceeds_balance(): with pytest.raises(Exception, match='Amount exceeds the Balance'): Transaction(Wallet(), 'reciepient', 1998)
def test_invalid_reward_transaction_invalid_amount(): miner_wallet = Wallet() reward_transaction = Transaction.reward_transaction(miner_wallet) reward_transaction.output[miner_wallet.address] = 1998 with pytest.raises(Exception, match='Invalid Mining Reward'): Transaction.transaction_is_valid(reward_transaction)
ROOT_PORT = 5000 PORT = ROOT_PORT if os.environ.get('PEER') == 'True': PORT = random.randint(5001, 6000) result = requests.get(f'http://localhost:{ROOT_PORT}/Blockchain') result_blockchain = Blockchain.from_json(result.json()) try: blockchain.replace_chain(result_blockchain) print('\n-- Successfully synchronized the chain') except Exception as e: print(f'\n-- Error synchronizing the chain: {e}') if os.environ.get('SEED_DATA') == 'True': for i in range(10): blockchain.add_block([ Transaction(Wallet(), Wallet().address, random.randint(2, 50)).to_json(), Transaction(Wallet(), Wallet().address, random.randint(2, 50)).to_json() ]) for i in range(3): transaction_pool.set_transaction( Transaction(Wallet(), Wallet().address, random.randint(2, 50))) app.run(port=PORT)