-
Notifications
You must be signed in to change notification settings - Fork 345
Description
I think that the implementation to enforce a maximum number of transactions predated the addition of operations.
How this works today:
the "surge pricing" logic kicks in when a validator encounters more transaction candidates than maxTxPerLedger
(defined in the ledger header). The first pass at filtering is done by sorting transaction candidates by fee ratio (defined as fee/min_fee
where min_fee
is base_fee*nb_operations
).
The idea is that in a situation where too many transactions are pending, we prioritize the ones with higher average fees.
If fees are equal, the sourceID
of the transactions is used as a tie breaker.
Proposal for new behavior:
- build a transaction set that maximizes the number of operations in a ledger, up to the maximum
maxOperationsPerLedger
defined by the network - sort transactions using the average fee per operation as the way to compare transaction fees (done simply by comparing
left.fee * right.nb_ops
withright.fee * left.nb_ops
) - break ties using a deterministic yet randomizing scheme, giving priority to different accounts for each ledger. Using
sourceID XOR hash_ofPreviousLedger
seems to fit the bill.
In order to implement 1 and 2, the candidate transaction set is build as follows:
- trim the transaction set such that it only includes valid transactions (including sequence number and fee)
- perform a lexicographical sort the candidate transactions by fee (descending), xored
source ID
(ascending), sequence number (ascending) - iterate over that sorted list:
- if the transaction can fit in the candidate set (ie,
newTxSetOpCount <= maxOperationsPerLedger
), add it to the set - otherwise, skip the transaction and any other transactions from the same source account
- if the transaction can fit in the candidate set (ie,
Something else to discuss here is what to do with the ledger header.
Right now the XDR is:
struct LedgerHeader
{
uint32 ledgerVersion; // the protocol version of the ledger
Hash previousLedgerHash; // hash of the previous ledger header
StellarValue scpValue; // what consensus agreed to
Hash txSetResultHash; // the TransactionResultSet that led to this ledger
Hash bucketListHash; // hash of the ledger state
uint32 ledgerSeq; // sequence number of this ledger
int64 totalCoins; // total number of stroops in existence.
// 10,000,000 stroops in 1 XLM
int64 feePool; // fees burned since last inflation run
uint32 inflationSeq; // inflation sequence number
uint64 idPool; // last used global ID, used for generating objects
uint32 baseFee; // base fee per operation in stroops
uint32 baseReserve; // account base reserve in stroops
uint32 maxTxSetSize; // maximum size a transaction set can be
Hash skipList[4]; // hashes of ledgers in the past. allows you to jump back
// in time without walking the chain back ledger by ledger
// each slot contains the oldest ledger that is mod of
// either 50 5000 50000 or 500000 depending on index
// skipList[0] mod(50), skipList[1] mod(5000), etc
// reserved for future use
union switch (int v)
{
case 0:
void;
}
ext;
};
in particular the line
uint32 maxTxSetSize; // maximum size a transaction set can be
There are two options on how we would change the ledger header:
- change the name of
maxTxSetSize
to be more generic, something liketxSetSize
, that maps to "number of transactions" in older versions of the protocol and "number of operations" in newer versions of the protocol (binary format stays the same). - make the
ext
union supportv = 1
, we would add the new field
// reserved for future use
union switch (int v)
{
case 0:
void;
case 1:
uint32 maxOperationsPerLedger;
}
ext;
maxTxSize
would be set to numeric_limits<uint32>::max()
I prefer option 1 as it keeps the ledger header tidy.
There are very few consumers of this field, and for the most part only use it for reporting. Code that actually cares about it can, just like core's implementation, use it based on what the protocol version is.
This issue was first opened as a core issue stellar/stellar-core#1030