def test_fake_ownership(self): # Trying to spend someone else's output # (Re)Starting from GenBlock genesis_block = classes.GenesisBlock(self.address) fullnode_api.add_genesis_block(genesis_block) # Tx1 -> We send it to an address belonging to seed dict_of_inputs = {genesis_block.block_content[0].txhash: 0} dict_of_outputs = {self.address: 100} first_tx = classes.Transaction(dict_of_inputs, dict_of_outputs) first_tx.sign(self.seed) # Block1 first_block = classes.Block([first_tx]) first_mined_block = fullnode_api.mine_block(first_block) fullnode_api.add_block_to_db(first_mined_block) # Tx2 -> we try to spend someone else's output since we sign with seed2 dict_of_inputs2 = {first_mined_block.block_content[0].txhash: 0} dict_of_outputs2 = {self.address3: 100} second_tx = classes.Transaction(dict_of_inputs2, dict_of_outputs2) second_tx.sign(self.seed2) # Block2 second_block = classes.Block([second_tx]) second_mined_block = fullnode_api.mine_block(second_block) fullnode_api.add_block_to_db(second_mined_block) with self.assertRaises(exceptions.ValidationError): validation.validate_block(second_mined_block)
def test_wrong_input(self): # Trying to spend inexistant input # (Re)Starting from GenBlock genesis_block = classes.GenesisBlock(self.address) fullnode_api.add_genesis_block(genesis_block) # Tx1 dict_of_inputs = {genesis_block.block_content[0].txhash: 0} dict_of_outputs = {self.address2: 100} first_tx = classes.Transaction(dict_of_inputs, dict_of_outputs) first_tx.sign(self.seed) # Block1 first_block = classes.Block([first_tx]) first_mined_block = fullnode_api.mine_block(first_block) fullnode_api.add_block_to_db(first_mined_block) # Tx2 -> we try to spend an unexisting input dict_of_inputs2 = {first_mined_block.block_content[0].txhash: 1} dict_of_outputs2 = {self.address3: 100} second_tx = classes.Transaction(dict_of_inputs2, dict_of_outputs2) second_tx.sign(self.seed2) # Block2 second_block = classes.Block([second_tx]) second_mined_block = fullnode_api.mine_block(second_block) fullnode_api.add_block_to_db(second_mined_block) with self.assertRaises( exceptions.APIError ): # Since the exceptions comes from one level deeper. validation.validate_block(second_mined_block)
def test_incorrect_amounts(self): # Trying to spend more than what we have in the inputs # (Re)Starting from GenBlock genesis_block = classes.GenesisBlock(self.address) fullnode_api.add_genesis_block(genesis_block) # Tx1 dict_of_inputs = {genesis_block.block_content[0].txhash: 0} dict_of_outputs = {self.address2: 100} first_tx = classes.Transaction(dict_of_inputs, dict_of_outputs) first_tx.sign(self.seed) # Block1 first_block = classes.Block([first_tx]) first_mined_block = fullnode_api.mine_block(first_block) fullnode_api.add_block_to_db(first_mined_block) # Tx2 -> We try to put 101 as output amount dict_of_inputs2 = {first_mined_block.block_content[0].txhash: 0} dict_of_outputs2 = {self.address3: 101} second_tx = classes.Transaction(dict_of_inputs2, dict_of_outputs2) second_tx.sign(self.seed2) # Block2 second_block = classes.Block([second_tx]) second_mined_block = fullnode_api.mine_block(second_block) fullnode_api.add_block_to_db(second_mined_block) with self.assertRaises(exceptions.ValidationError): validation.validate_block(second_mined_block)
def setUp(self): # Seed&Address (2 pairs) self.seed = crypto.new_seed() self.address = crypto.get_address(self.seed) self.verifying_key_string = crypto._get_verifying_key_string(self.seed) self.seed2 = crypto.new_seed() self.address2 = crypto.get_address(self.seed2) self.verifying_key_string2 = crypto._get_verifying_key_string( self.seed2) self.address3 = self.address2 # For testing purposes only # Db self.db_path = 'database/db_test' database.init_database_path(self.db_path) # GenBlock self.genesis_block = classes.GenesisBlock(self.address) fullnode_api.add_genesis_block(self.genesis_block) # Tx1 self.dict_of_inputs = {self.genesis_block.block_content[0].txhash: 0} self.dict_of_outputs = {self.address2: 100} self.first_tx = classes.Transaction(self.dict_of_inputs, self.dict_of_outputs) self.first_tx.sign(self.seed) # Block1 self.first_block = classes.Block([self.first_tx]) self.first_mined_block = fullnode_api.mine_block(self.first_block) fullnode_api.add_block_to_db(self.first_mined_block) # Tx2 self.dict_of_inputs2 = { self.first_mined_block.block_content[0].txhash: 0 } self.dict_of_outputs2 = {self.address3: 100} self.second_tx = classes.Transaction(self.dict_of_inputs2, self.dict_of_outputs2) self.second_tx.sign(self.seed2) # Block2 self.second_block = classes.Block([self.second_tx]) self.second_mined_block = fullnode_api.mine_block(self.second_block) fullnode_api.add_block_to_db(self.second_mined_block)
def test_double_spend(self): # (Re)Starting from GenBlock genesis_block = classes.GenesisBlock(self.address) fullnode_api.add_genesis_block(genesis_block) # Tx1 dict_of_inputs = {genesis_block.block_content[0].txhash: 0} dict_of_outputs = {self.address2: 100} first_tx = classes.Transaction(dict_of_inputs, dict_of_outputs) first_tx.sign(self.seed) # Block1 first_block = classes.Block([first_tx]) first_mined_block = fullnode_api.mine_block(first_block) fullnode_api.add_block_to_db(first_mined_block) # Tx2 dict_of_inputs2 = {first_mined_block.block_content[0].txhash: 0} dict_of_outputs2 = {self.address3: 100} second_tx = classes.Transaction(dict_of_inputs2, dict_of_outputs2) second_tx.sign(self.seed2) # Block2 second_block = classes.Block([second_tx]) second_mined_block = fullnode_api.mine_block(second_block) fullnode_api.add_block_to_db(second_mined_block) # Tx3 -> We try to double spend the same input as we spent in the previous block dict_of_inputs3 = {first_mined_block.block_content[0].txhash: 0} dict_of_outputs3 = {self.address3: 100} third_tx = classes.Transaction(dict_of_inputs3, dict_of_outputs3) third_tx.sign(self.seed2) third_block = classes.Block([third_tx]) third_mined_block = fullnode_api.mine_block(third_block) with self.assertRaises(exceptions.ValidationError): validation.validate_block(third_mined_block)
def process(connection, neighbors_selector): """This function takes a connection as input and processes the information it yields. It manages the state of the fullnode. It requires the neighbors selector to instantiate the gossip.""" global received_transactions_stack global received_databases_stack with open('network/config.json') as cfg_file: cfg = json.load(cfg_file) neighbor_address = cfg["NeighborsInfo"]["neighbor_address"] neighbor_port = cfg["NeighborsInfo"]["neighbor_port"] # ---------- Clients Processing------------------ # Processing received transaction if hasattr(connection, "transaction_received"): # Is it a ClientConnection ? if connection.transaction_received is not None: received_transactions_stack.append( pickle.loads(connection.transaction_received)) print("New transaction received.") # ---------- Block Creation ------------------ if len(received_transactions_stack ) >= 1: # A block contains 10 transactions print("Creating a new block") # We mine a block new_block = classes.Block(received_transactions_stack) # We check if the transactions of the block are valid if validation.validate_block(new_block): print("Block is valid, now mining.") mined_new_block = fullnode_api.mine_block(new_block) fullnode_api.add_block_to_db(mined_new_block) received_transactions_stack = [] else: print("Invalid new block, discarding transactions") received_transactions_stack = [] # We start the broadcasting procedure with the serialized database database_bytes = pickle.dumps(fullnode_api.get_database()) fsm.start_gossip(address_tuple=(neighbor_address, neighbor_port), database_bytes=database_bytes, selector=neighbors_selector) # ---------- Neighbors Processing------------------ # Processing received database if hasattr(connection, "database_received"): # Is it a NeighborConnection ? # Every NeighborConnection has a "database_received" property but it is only different # from None when we have successfully received a database message if connection.database_received is not None: received_databases_stack.append( pickle.loads(connection.database_received)) print("New database received from a neighbor.") # Checking if a database has been successfully sent if hasattr(connection, "database_sent"): # Every NeighborConnection has a "database_sent" property but it is only True # when we have successfully sent a database message if connection.database_sent: # We now know that the sending process is over, we can close the connection and empty stack connection.close() received_transactions_stack = [] # Consensus : choosing the longest chain if len(received_databases_stack) == 1: if len(received_databases_stack[0]) > len(fullnode_api.get_database()): # The received database replaces our database print("Received database is the longest chain, copying.") database.write_to_db(received_databases_stack[0]) else: print("Received database is not the longest chain, discarding.") # In any case we empty the stack received_databases_stack = []