def _purge_sender(self, sender_public_key): logger.info("Purging sender %s from pool wallet manager", sender_public_key) PoolTransaction.delete().where( PoolTransaction.sender_public_key == sender_public_key ).execute() self.wallets.delete_by_public_key(sender_public_key)
def _purge_expired(self): current_time = time.get_time() expired_transactions = list( PoolTransaction.select().where(PoolTransaction.expires_at <= current_time) ) ids = [] for trans in expired_transactions: transaction = from_object(trans) sender_wallet = self.find_by_public_key(transaction.sender_public_key) transaction.revert_for_sender_wallet(sender_wallet) ids.append(transaction.id) delete_query = PoolTransaction.delete().where(PoolTransaction.id.in_(ids)) delete_query.execute()
def has_sender_exceeded_max_transactions(self, sender_public_key): """Checks whether sender or transaction has exceeded max transactions in queue :param str sender_public_key: Sender's public key """ self._purge_expired() if sender_public_key in config.pool["allowed_senders"]: logger.info( ( "Sender with public key %s is an allowed sender, thus skipping " "throttling" ), sender_public_key, ) return False count = ( PoolTransaction.select() .where(PoolTransaction.sender_public_key == sender_public_key) .count() ) return count > config.pool["max_transactions_per_sender"]
def build_wallets(self): self.wallets.clear_wallets() self._purge_expired() last_block = self.database.get_last_block() for trans in PoolTransaction.select(): transaction = from_object(trans) sender_wallet = self.wallets.find_by_public_key( transaction.sender_public_key ) if transaction.can_be_applied_to_wallet( sender_wallet, self.walelts, self, last_block.height ): transaction.apply_to_sender_wallet(sender_wallet) self.wallets.save_wallet(sender_wallet) else: logger.warning( "Transaction %s can't be applied to wallet %s", transaction.id, sender_wallet.address, ) self._purge_sender(transaction.sender_public_key) logger.info("Transaction pool wallets have been successfully built")
def validate_for_transaction_pool(self, pool, transactions): if pool.sender_has_transactions_of_type(self): return ( "Sender {} already has a transaction of type {} in the " "pool".format(self.sender_public_key, self.type) ) # Reject all delegate registration transactions with the same delegate # name if there are more than one with the same name num_same_delegate_registration_in_payload = [ trans for trans in transactions if trans.asset["delegate"]["username"] == self.asset["delegate"]["username"] ] if len(num_same_delegate_registration_in_payload) > 1: return ( "Multiple delegate registrations for {} in transaction " "payload".format(self.asset["delegate"]["username"]) ) # Reject a transaction if delegate registration transaction for the same # username is already in the pool exists_in_pool = ( PoolTransaction.select() .where( PoolTransaction.type == TRANSACTION_TYPE_DELEGATE_REGISTRATION, PoolTransaction.asset["delegate"]["username"] == self.asset["delegate"]["username"], ) .exists() ) if exists_in_pool: return "Delegate registration for {} already in the pool".format( self.asset["delegate"]["username"] ) return None
def _clear_db(): Round.delete().execute() Transaction.delete().execute() Block.delete().execute() PoolTransaction.delete().execute()
def remove_transaction_by_id(self, transaction_id): query = PoolTransaction.delete().where(PoolTransaction.id == transaction_id) query.execute()
def transaction_exists(self, transaction_id): query = PoolTransaction.select().where(PoolTransaction.id == transaction_id) return query.exists()
def process_transactions(self, transactions_data): self._purge_expired() errors = {} excess = [] accepted = [] broadcasted = [] transactions = [] last_block = self.database.get_last_block() # TODO: Transaction sequence is currently growing until transaction pool is # completely empty last_transaction_sequence = ( PoolTransaction.select(PoolTransaction.sequence) .order_by(PoolTransaction.sequence.desc()) .first() ) sequence = 0 if last_transaction_sequence: sequence = last_transaction_sequence.sequence for transaction_data in transactions_data: # NOTE: This check should really check if transaction is not bigger than # maximum BYTES and not characters, but the implementation in core is wrong # and we need to be consistent with their implementation. Also this should # probably be checked after the payload is converted to CryptoTransaction json_transaction = len(json.dumps(transaction_data)) if json_transaction > config.pool["max_transaction_characters"]: errors[ transaction_data["id"] ] = "Transaction {} is larger than {} characters".format( transaction_data["id"], config.pool["max_transaction_characters"] ) continue transaction = from_dict(transaction_data) sequence += 1 transaction.sequence = sequence transactions.append(transaction) for transaction in transactions: if self.has_sender_exceeded_max_transactions(transaction.sender_public_key): excess.append(transaction.id) continue validation_error = self._validate_transaction( transaction, last_block.height, transactions ) if validation_error: errors[transaction.id] = validation_error continue if not self.wallets.can_apply_to_sender(transaction, last_block.height): errors[ transaction.id ] = "Transaction {} can't be applied to senders wallet".format( transaction.id ) continue valid_for_pool = valid_fee_for_pool(transaction, last_block.height) valid_for_broadcast = valid_fee_for_broadcast( transaction, last_block.height ) if not valid_for_broadcast and not valid_for_pool: errors[ transaction.id ] = "Fee is too low to broadcast and accept the transaction {}".format( transaction.id ) continue if valid_for_pool: count = PoolTransaction.select().count() if count > config.pool["max_transactions_in_pool"]: lowest_pool = ( PoolTransaction.select(PoolTransaction.fee) .order_by(PoolTransaction.fee.asc()) .first() ) lowest = from_object(lowest_pool) if lowest.fee < transaction.fee: lowest_sender = self.wallets.find_by_public_key( lowest.sender_public_key ) lowest.revert_for_sender_wallet(lowest_sender) self.wallets.save_wallet(lowest_sender) lowest_pool.delete_instance() else: errors[transaction.id] = ( "Pool is full (has {} transactions) and this transaction's " "fee {} is not higher than the lowest fee already in the " "pool {}".format(count, transaction.fee, lowest_pool.fee) ) continue # Add transaction to the pool sender_wallet = self.wallets.find_by_public_key( transaction.sender_public_key ) transaction.apply_to_sender_wallet(sender_wallet) self.wallets.save_wallet(sender_wallet) pool_transaction = PoolTransaction.from_crypto(transaction) pool_transaction.save() accepted.append(transaction.id) if valid_for_broadcast: broadcasted.append(transaction.id) # TODO: # if (result.broadcast.length > 0) { # app.resolvePlugin<P2P.IMonitor>("p2p").broadcastTransactions # (guard.getBroadcastTransactions()); # } return { "accepted": accepted, "broadcasted": broadcasted, "excess": excess, "errors": errors, "invalid": list(errors.keys()), }
def sender_has_any_transactions(self, sender_public_key): query = PoolTransaction.select().where( PoolTransaction.sender_public_key == sender_public_key ) return query.exists()
def sender_has_transactions_of_type(self, transaction): query = PoolTransaction.select().where( PoolTransaction.sender_public_key == transaction.sender_public_key, PoolTransaction.type == transaction.type, ) return query.exists()