Further improvement on tiered fees

The proposed design could be implemented even with the auto-adjustments still missing, afterall the current system doesn’ty adjust itself either, the proposed system would set up a modular and expansable foundation for multiple tiers with multiple rules. Later forks could then add auto-adjustment mechanics to the existing tiers as well as add entirely new tiers with new rules how to validate them. The first Custom tier can continue to be the final fallback “otherwise” tier, where every fee value is allowed, with no rules.
(except the absolute minimum a+b*size fee that is enforced even in the custom, but only as a minimum, to prevent spamming the storage for no cost).

I’ve got an idea, maybe we dont need to even implement any autoadjustments, if we just cover th entire logarithmic range with fixed tiers:

Tier 1(Custom): Fee >= a + b * size;
Tier 2: Fee == a + b * size
Tier 3: Fee == a + b * size * 10
Tier 4: Fee == a + b * size * 100
Tier 5: Fee == a + b * size * 1000
Tier 6: Fee == a + b * size * 10000
Tier 7: Fee == a + b * size * 10^5
Tier 8: Fee == a + b * size * 10^6
… all the way to fees that would even exceed the maximum supply of ADA, it would be allowed, it would technically have allocated extra blockspace for those, but no transaction could ever fit there.

Then we don’t need autoadjustments as long as the jump between two tier is not too extreme.
Everything will be covered forever. If the demand shifts, and the blockchain would then operate better with all tiers 10x more or less expensive? It already is good to go, people will just shift tiers.
And in such a case, even tha validation algoriths could get much simpler, there would basically be just 2 tiers, one custom, and one logarithmically stepped, except the stepped one would have an allowed blockspace for every step. Of course most of the tiers would be empty, but we would alwas get the block just the right size for the demand to keep it stable and fair.

So if the a and b remained the same (it shouldn’t, it should later get voted down to be lower, but that can wait), the fee system would be like this:
You could pay 0.17 ADA per your simple transaction and enter the queue for Tier 2.
Or you could pay 1.7 ADA and get into the faster less congested Tier 3. Each tier would have spearate allowance in blockspace, so both would be proccesed at the same rate, but the expensive ones will of course be less congested because people simply like to pay as little as possible to meet their settlement time needs.
Or you could pay 17 ADA, to enter yet another even less congested mempool.
And so on.
Or you could pay anything higehr than 0.17 that doesnt match the pattern, and you would enter yet another custom mempool, that acts jusyt like the one in bitcoin, with frontrunning,
As long as voting keeps the a and b parameters low enough to allow everyone to use the chain, no further automatic adjustments would be needed. Also with such a pattern for the tiers, we would not even need custom selected blocksizes for each, let’s just give the same blocksize to every tier in the pattern, because it can shift anyway, maybe only the custom tier could have a different allowed blocksize.

Here is the new procedural code for infinite tiers, stepping with base 4 instead of 10:

// Voteable constants
float a = 0.00155381;
float b = 0.00000043946;
int BlockSizeCustom = 32000;
int BlockSizeTiered = 32000;

// Mempool is a list of pending valid transactions sorted by age, [0] the oldest
List<Tx> MakeBlock(List<Tx>& Mempool) {

 List<Tx> AO; // Allowed Output = Allowed transactions
 List<Tx> PC; // Pending Custom = Custom transactions (doesn't match any tier AND is at least minimum: fee >= a+b*size)
 List<int> U; // Unfilled allowed Block Partition Bytes for each tier
 int Unfilled = BlockSizeCustom; // initialize block space allowance for custom tier

 for(Tx& tx : Txs) 
 {
  int i = 0, e = 1, f = tx.fee - a, s = tx.size; // i = tier, e = 4^i = tiered b multiplier, f = baseless fee
  float ft = b * s;
  for(i = 0; ft * e < f; ++i) 
   e *= 4; // find tier
  while(U.Length <= i) 
   U.Add(BlockSizeTiered); // initialize block space allowance for tiers up to this one
  if(ft * e == f && 0 <= U[e] - s) { // fee matches the found tier exactly and the transaction size still fits into allowed blockspace
   U[e] -= s; // Fill the allowed blockspace with the transaction size
   AO.Add(tx); // Allow the transaction into the block
  }
  else if(ft <= f) // Doesnt match exactly any tier, but still above minimum - pending custom
   PC.Add(tx);
 }

 // Selection Sort the Pending Custom list to add as many maximum fee transactions as allowed into allowed Custom fee blockspace 
 int x, max, pending = PC.Length;
 while(0 <= --pending) {
  max = sort = pending;
  while(0 <= --x)
   if(TxFeePerByte(PC[sort]) > TxFeePerByte(PC[max]))
    max = sort;
  if(0 <= (Unfilled -= PC[max]))
   AO.Add(PC[max]);
  PC[max] = PC[pending];
 }

 Mempool.Remove(AO); // Remove the allowed transactions from the mempool (if we want to start making another block)
 return AO; // Return list of allowed transactions into the block
}

bool IsValidBlock(Tx[] Block) {

 List<int> U; // Unfilled allowed Block Partition Bytes for each tier
 int Unfilled = BlockSizeCustom; // initialize block space allowance for custom tier

 for(Tx& tx : Block) 
 {
  int i = 0, e = 1, f = tx.fee - a, s = tx.size;
  float ft = b * s;
  for(i = 0; ft * e < f; ++i) 
   e *= 4; 
  while(U.Length <= i) 
   U.Add(BlockSizeTiered); 
  if(ft * e == f && 0 <= U[e] - s) 
    U[e] -= s;
  else if(ft > f || 0 > (Unfilled -= s))
   return false; // Some transaction didn't pay minimum fee, or some tier was overfilled!
 }
 return true; // all transactions could be allowed into a block by our rules, it's a valid block
}

Here is the same code, but compressed (no comments, less lines):

float a = 0.00155381;
float b = 0.00000043946;
int BlockSizeCustom = 32000;
int BlockSizeTiered = 32000;

List<Tx> MakeBlock(List<Tx>& Mempool) {
 List<Tx> AO, PC;
 List<int> U; 
 int Unfilled = BlockSizeCustom;
 for(Tx& tx : Txs) {
  int i = 0, e = 1, f = tx.fee - a, s = tx.size;
  float ft = b * s;
  for(i = 0; ft * e < f; ++i) e *= 4;
  while(U.Length <= i) U.Add(BlockSizeTiered); 
  if(ft * e == f && 0 <= U[e] - s) { U[e] -= s; AO.Add(tx); }
  else if(ft <= f) PC.Add(tx);
 }
 int x, max, pending = PC.Length;
 while(0 <= --pending) {
  max = sort = pending;
  while(0 <= --x) if(TxFeePerByte(PC[sort]) > TxFeePerByte(PC[max])) max = sort;
  if(0 <= (Unfilled -= PC[max])) AO.Add(PC[max]);
  PC[max] = PC[pending];
 }
 Mempool.Remove(AO); 
 return AO; 
}

bool IsValidBlock(Tx[] Block) {
 List<int> U;
 int Unfilled = BlockSizeCustom; 
 for(Tx& tx : Block) {
  int i = 0, e = 1, f = tx.fee - a, s = tx.size;
  float ft = b * s;
  for(i = 0; ft * e < f; ++i) e *= 4; 
  while(U.Length <= i) U.Add(BlockSizeTiered); 
  if(ft * e == f && 0 <= U[e] - s) U[e] -= s;
  else if(ft > f || 0 > (Unfilled -= s)) return false;
 }
 return true;
}

So which tier would then be the Fair, Balanced or Custom if there are infinite like this?
Fair would be the Tier 2, the lowest one that match a + b*size exactly.
Immediate would be the lowest tier that is not congested.
Balanced would be the tier with measued congestion for user’s desired settlement time.
Custom would be tier 1, user is free to choose any fee that is above the fair one, and enters the frontrunning custom queue.

For best backwards compatibility - the new a and b parameters should be chosen to match exactly one of the fixed tier steps, in my example, I chose a and b to be 100x smaller than their current value, which would make all legacy transactions fit Tier 4 (and also custom on top of that). If the allowed blockspace would be at least half on the current maximum blocksize limit, then backwards compatibility would be achieved, also wallets that didn’t update yet and still prepare fees the old-fashioned way would still make valid transactions prioritized by age just like before.

Here is an image that depicts which transactions from mempool would be allowed into a block (picture simplified to absolute fees instead of fees per byte, and stepping base is 10 as in first idea):