@Override
  public List<PendingTransaction> promote(
      final Predicate<PendingTransaction> promotionFilter,
      final long freeSpace,
      final int freeSlots,
      final int[] remainingPromotionsPerType) {
    long accumulatedSpace = 0;
    final List<PendingTransaction> promotedTxs = new ArrayList<>();

    final var zeroGapSenders = orderByGap.get(0);

    search:
    for (final var sender : zeroGapSenders) {
      final var senderSeqTxs = getSequentialSubset(txsBySender.get(sender));

      for (final var candidateTx : senderSeqTxs.values()) {
        final var txType = candidateTx.getTransaction().getType();
        if (promotionFilter.test(candidateTx) && remainingPromotionsPerType[txType.ordinal()] > 0) {
          accumulatedSpace += candidateTx.memorySize();
          if (promotedTxs.size() < freeSlots && accumulatedSpace <= freeSpace) {
            promotedTxs.add(candidateTx);
            --remainingPromotionsPerType[txType.ordinal()];
          } else {
            // no room for more txs the search is over exit the loops
            break search;
          }
        } else {
          // skip remaining txs for this sender
          break;
        }
      }
    }

    // remove promoted txs from this layer
    promotedTxs.forEach(
        promotedTx -> {
          final var sender = promotedTx.getSender();
          final var senderTxs = txsBySender.get(sender);
          senderTxs.remove(promotedTx.getNonce());
          processRemove(senderTxs, promotedTx.getTransaction(), PROMOTED);
          if (senderTxs.isEmpty()) {
            txsBySender.remove(sender);
            orderByGap.get(0).remove(sender);
            gapBySender.remove(sender);
          } else {
            final long firstNonce = senderTxs.firstKey();
            final int newGap = (int) (firstNonce - (promotedTx.getNonce() + 1));
            if (newGap != 0) {
              updateGap(sender, 0, newGap);
            }
          }
        });

    if (!promotedTxs.isEmpty()) {
      // since we removed some txs we can try to promote from next layer
      promoteTransactions();
    }

    return promotedTxs;
  }
We only want to promote transactions that have gap == 0, so there will be no gap in the prev
layers. A promoted transaction is removed from this layer, and the gap data is updated for its
sender.

@param promotionFilter the prev layer's promotion filter
@param freeSpace max amount of memory promoted txs can occupy
@param freeSlots max number of promoted txs
@return a list of transactions promoted to the prev layer
ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java