def create_transaction(self, to, amount, fee, prev_hash): self.check_peers() transaction = Transaction(self.get_public_key(), to, amount, fee, prev_hash=prev_hash) transaction.sign(self.get_private_key()) return self.api_client.broadcast_transaction(transaction)
def request_transaction(self, node, port, tx_hash): url = self.TRANSACTIONS_URL.format(node, port, tx_hash) try: response = requests.get(url) if response.status_code == 200: tx_dict = response.json() transaction = Transaction( tx_dict['source'], tx_dict['destination'], tx_dict['amount'], tx_dict['fee'], tx_dict['prev_hash'], tx_dict['tx_type'], tx_dict['timestamp'], tx_dict['tx_hash'], tx_dict['asset'], tx_dict['data'], tx_dict['signature'] ) if transaction.tx_hash != tx_dict['tx_hash']: logger.warn("Invalid transaction hash: {} should be {}. Transaction ignored." .format(tx_dict['tx_hash'], transaction.tx_hash)) return None return transaction except requests.exceptions.RequestException as re: logger.warn("Request Exception with host: {}".format(node)) self.peers.record_downtime(node) return None
def get_all_unconfirmed_transactions_iter(self): sql = 'SELECT * FROM unconfirmed_transactions' with sqlite3.connect(self.POOL_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for transaction in cursor: yield Transaction(transaction[1], transaction[2], transaction[3], transaction[4], transaction[10], transaction[7], transaction[5], transaction[0], transaction[8], transaction[9], transaction[6])
def get_unconfirmed_transaction(self, tx_hash): sql = "SELECT * FROM unconfirmed_transactions WHERE hash='{}'".format( tx_hash) with sqlite3.connect(self.POOL_DB) as conn: cursor = conn.cursor() cursor.execute(sql) data = cursor.fetchone() if data is None: return None transaction = data[0] return Transaction(transaction[1], transaction[2], transaction[3], transaction[4], transaction[10], transaction[7], transaction[5], transaction[0], transaction[8], transaction[9], transaction[6])
def get_unconfirmed_transactions_chunk(self, chunk_size=None): sql = 'SELECT * FROM unconfirmed_transactions ORDER BY fee DESC LIMIT {}'.format( chunk_size) transactions = [] with sqlite3.connect(self.POOL_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for transaction in cursor: transactions.append( Transaction(transaction[1], transaction[2], transaction[3], transaction[4], transaction[10], transaction[7], transaction[5], transaction[0], transaction[8], transaction[9], transaction[6])) return transactions
def from_dict(cls, block_dict): return cls(block_dict['height'], [ Transaction(transaction['source'], transaction['destination'], transaction['amount'], transaction['fee'], tx_type=transaction['tx_type'], timestamp=transaction['timestamp'], asset=transaction['asset'], data=transaction['data'], prev_hash=transaction['prev_hash'], signature=transaction['signature']) for transaction in block_dict['transactions'] ], block_dict['previous_hash'], block_dict['timestamp'], block_dict['nonce'])
def mine_block(self): latest_block = self.blockchain.get_tallest_block_header() if latest_block is not None: latest_block_header = latest_block[0] latest_block_height = latest_block[2] new_block_height = latest_block_height + 1 previous_hash = latest_block_header.hash else: new_block_height = 1 previous_hash = "" transactions = self.mempool.get_unconfirmed_transactions_chunk( self.MAX_TRANSACTIONS_PER_BLOCK) if transactions is None or len(transactions) == 0: fees = 0 else: fees = sum(t.fee for t in transactions) coinbase_prev_hash = "0" if new_block_height == 1 \ else self.blockchain.get_coinbase_hash_by_block_hash(previous_hash) # coinbase coinbase = Transaction("0", self.REWARD_ADDRESS, self.blockchain.get_reward(new_block_height) + fees, 0, prev_hash=coinbase_prev_hash, tx_type=TransactionType.COINBASE.value, signature="") transactions.insert(0, coinbase) timestamp = int(time.time()) i = 0 block = Block(new_block_height, transactions, previous_hash, timestamp) while block.block_header.hash_difficulty < self.blockchain.calculate_hash_difficulty( ): latest_block = self.blockchain.get_tallest_block_header() if latest_block is not None: latest_block_header = latest_block[0] latest_block_height = latest_block[2] if latest_block_height >= new_block_height or latest_block_header.hash != previous_hash: # Next block in sequence was mined by another node. Stop mining current block. return None i += 1 block.block_header.nonce = i return block
def post_transactions(): mempool = Mempool() validator = Validator() body = request.json transaction = Transaction.from_dict(body['transaction']) if transaction.tx_hash != body['transaction']['tx_hash']: logger.info("Invalid transaction hash: {} should be {}".format( body['transaction']['tx_hash'], transaction.tx_hash)) response.status = 406 return json.dumps({'message': 'Invalid transaction hash'}) if mempool.get_unconfirmed_transaction(transaction.tx_hash) is None \ and validator.validate_transaction(transaction) \ and mempool.push_unconfirmed_transaction(transaction): response.status = 200 return json.dumps({'success': True, 'tx_hash': transaction.tx_hash}) response.status = 406 return json.dumps({'success': False, 'reason': 'Invalid transaction'})
def get_transaction_by_hash(self, transaction_hash, branch=0): sql = "SELECT * FROM transactions WHERE hash='{}' AND branch={}".format( transaction_hash, branch) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) transaction = cursor.fetchone() return Transaction(transaction[1], transaction[2], transaction[3], transaction[4], tx_type=transaction[7], timestamp=transaction[5], tx_hash=transaction[0], signature=transaction[6], asset=transaction[8], data=transaction[9])
def get_transactions_by_block_hash(self, block_hash): transactions = [] sql = "SELECT * FROM transactions WHERE blockHash='{}' ORDER BY hash ASC".format( block_hash) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for transaction in cursor: transactions.append( Transaction(transaction[1], transaction[2], transaction[3], transaction[4], tx_type=transaction[7], timestamp=transaction[5], tx_hash=transaction[0], signature=transaction[6], asset=transaction[9], data=transaction[10], prev_hash=transaction[12])) return transactions
def get_transaction_history(self, address, branch=0): # TODO: convert this to return a generator transactions = [] sql = "SELECT * FROM transactions WHERE (src='{}' OR dest='{}') AND branch={}".format( address, address, branch) with sqlite3.connect(self.CHAIN_DB) as conn: cursor = conn.cursor() cursor.execute(sql) for transaction in cursor: transactions.append( Transaction(transaction[1], transaction[2], transaction[3], transaction[4], tx_type=transaction[7], timestamp=transaction[5], tx_hash=transaction[0], signature=transaction[6], asset=transaction[9], data=transaction[10], prev_hash=transaction[12])) return transactions
def worker(self): while True: msg = Queue.dequeue() sender = msg.get('host', '') msg_type = MessageType(msg.get('type')) data = msg.get('data') if msg_type == MessageType.BLOCK_HEADER: block_header = BlockHeader.from_dict(json.loads(data)) if sender == self.HOST: self.api_client.broadcast_block_inv([block_header.hash], self.HOST) else: self.__process_block_header(block_header, sender) continue elif msg_type == MessageType.UNCONFIRMED_TRANSACTION: unconfirmed_transaction = Transaction.from_dict(data) if sender == self.HOST: # transaction already validated before being enqueued valid = True else: valid = self.validator.validate_transaction( unconfirmed_transaction) if valid: self.api_client.broadcast_unconfirmed_transaction_inv( [unconfirmed_transaction.tx_hash], self.HOST) continue elif msg_type == MessageType.BLOCK_INV: missing_block_headers = [] for block_hash in data: # aggregate unknown block header hashes block_header = self.blockchain.get_block_header_by_hash( block_hash) if block_header is None: missing_block_headers.append(block_hash) for block_hash in missing_block_headers: # We don't have these blocks in our database. Fetch them from the sender block_header = self.api_client.request_block_header( sender, self.FULL_NODE_PORT, block_hash=block_hash) self.__process_block_header(block_header, sender) continue elif msg_type == MessageType.UNCONFIRMED_TRANSACTION_INV: missing_transactions = [] new_unconfirmed_transactions = [] for tx_hash in data: # skip known unconfirmed transactions transaction = self.blockchain.get_transaction_by_hash( tx_hash) if transaction: continue unconfirmed_transaction = self.mempool.get_unconfirmed_transaction( tx_hash) if unconfirmed_transaction: continue missing_transactions.append(tx_hash) for tx_hash in missing_transactions: # retrieve unknown unconfirmed transactions transaction = self.api_client.request_transaction( sender, self.FULL_NODE_PORT, tx_hash) valid = self.validator.validate_transaction(transaction) if valid: # validate and store retrieved unconfirmed transactions self.mempool.push_unconfirmed_transaction(transaction) new_unconfirmed_transactions.append(tx_hash) if len(new_unconfirmed_transactions): # broadcast new unconfirmed transactions self.api_client.broadcast_unconfirmed_transaction_inv( new_unconfirmed_transactions) continue else: logger.warn("Encountered unknown message type %s from %s", msg_type, sender) pass