# Transaction ordering Some thoughts about how a node can order transactions in a block. ### Preliminaries These ideas would not be enforced by consensus, but would be a implemented in the block-sealing logic of a client. This means, of course, that any ordering must maximize miner revenue -- otherwise miners will use a modified version of the client which does maximize the revenue. So what about enshrining the rules in consensus? Let's take it one step at a time. Let's analyze the problem "outside" of the consensus-rules, and see what possible schemas there are, and what the problems are. It may very well be that the same problems are present regardless of whether the rules are enforced by consensus or not. I think it's very hard to get around 'sell slot' problem. In the extreme, a miner can always mine a block with only two transactions, the 'target' and the 'buyer', and whatever ordering schema we come up with doesn't matter: it can be front- or backrun. The slot buyer could give the miner N transactions, and say "hey one of these should be in front (or back) of target, ok?" ### What does this proposal aim to do? The goal with this proposal is not to stop frontrunning/backrunning, but to make it so the most efficient way to do them is to not use the gossip network, and to not have miners include failed frontruns/backruns. The primary concerns are: - We don't want huge amounts of txs bloating the network, just to hit a certain slots. Sending 100 txs where one would suffice (old problem before we used first-come first serve) - We don't want to have useless 'nodes' which are empty shells, who peers with a lot of other nodes and only ever broadcast the player's transactions, but doesn't participate otherwise (the current problem with ordering by first-seen) - We also don't want a scheme which incentivises these actors to pre-mine a lot of accounts for later use. So this proposal revolves around how geth (or any other node) would order transactions in a block by default. ### Schemas 1. Tie-break random sorting - Order transactions in block by price. - On price-ties (same gasPrice), order by random. - This was how geth behaved previously, before changing to "first-come first-serve" (see [PR](https://github.com/ethereum/go-ethereum/pull/21358)) - Drawback: Each actor is incentivized to get as many transactions as possible into the relevant pricepoint, as each tx represents one 'ticket' in the lottery. 2. Tie-break first-come, first serve. - Order transactions in block by price. - On price-ties (same gasPrice), order by 'first seen'. - Implemented in Go-ethereum [here](https://github.com/ethereum/go-ethereum/pull/21358) - Drawback: Shifts the "game area" towards being a network propagation game: incentivizing people to run a large amount of "nodes", which simulata a real node, ignores incoming transactions and shoves (a'la eth64) transactions on their peers. - Upside: Only one transaction (per actor) to hit a given target slot. 3. Tie-break mined sorting. - Order transactions in block by price. - On price-ties (same gasPrice), order by transaction hash. - Does not alleviate front-running - Drawback: This introduce an incentive to send _mutiple_ txs to hit a target slot during backrunning. 4. Full-block random sorting - The block is sealed by picking the `N` most valuable transactions, and order all transactions by 'random'. - The consequences here are a bit difficult to tell. There's a high chance that actors who wish to hit a certain slot will take the 'shotgun' approach and just send a bunch of transactions, hoping that one of them lands on the right spot. 5. Full-block mined sorting - The block is sealed by picking the `N` most valuable transactions, and order all transactions by lowest 'hash'. - This has some nice characteristics: Anyone who does not want to be subjected to front-running just needs to mine a sufficiently 'low' hash to make it intractable for anyone else to front-run the transaction before it's inclusion in a block. - For back-running, there's still the problem that if someone wants to target a slot directly after a given target, they may do multiple attempts a 'landing' a good enough hash. - A difference in this schema, compared to "tie-break mined sorting" is that the actor is now forced to compete with _all_ the transactions going into the block, not only others playing the same game. ### Full-block mined sorting More details on 'full-block mined sorting': On a given list with `N` transactions, where `N` is `256`: ``` [tx1, tx2, ... , tx256] ``` Let's say the actor wishes to land a transaction at `127`. If all other transactions are normal transactions, they would typically have a `hash` distribution like so: ``` [0x00..., 0x01..., 0x02.., ... , 0xff...] ``` And the actor now needs to mine a transaction with a hash `0x80...`, which means performing a sign-operation ~256 times. In the "tie-break mined sorting" scheme, it can be estimated that the first transaction can be sent out after `2` attempts, and a second (replacement) after `4` attemps, and a third after `8` attempts, and so on, when a slot-war is going on against another actor. With the "full-block mined sorting" approach, if a slot-war is going on, then - Actor A needs `256` signing operations to make the first tx - Actor B needs `512` signing operations to beat the first tx. - Actor C needs `1024` signing operations to beat the second tx. It can also be noted, that if a lot of users submit transactions which are 'somewhat mined' to prevent frontrunning, these transactions will make the low hashspace more dense, and thus further make it harder for a front/back-runner to target a particular one of them. This type of herd-protection can be coordinated, but it would also arise organically. ### Nonce ordering One complicating factor (with all schemes) is that nonce-ordering needs to be respected. Nonce-ordering can be done in a few ways. Example, where there are `24` transactions, sorted the way we want them. The `bx` at position `20` has been placed before `ax` at position `22`, but nonce `b` is higher than nonce `a`. ``` [tx0, tx1, .. bx20, tx21, ax22, tx23 , tx24] ``` The first way to resolve this situation is to flip them: ``` [tx0, tx1, .. ax20, tx21, bx22, tx23 , tx24] ``` A different way is to move the out-of-order transaction to the end: ``` [tx0, tx1, .. tx20, ax21, tx22 , tx23, bx24] ``` However, both these introduces mechanisms to 'sandwich' transactions. A third way -- and probably the best approach -- is to move the high-nonce-tx downwards, to be in sequence with the low-nonce tx: ``` [tx0, tx1, .. tx20, ax21, bx22, tx23 , tx24] ``` ### Weak points If a backrunner publish one very high-pricex tx worth a full block, `10M`, it would be alone in the block. However, the tx only consumes `21K`, so the rest of the contents of the block would be filled with the 'pop from txpool to fill the slack', which would therefore follow other rules -- specifically, price-sorting. Which would degrade this schema. In order to counter this, it might be needed to re-sort the complete block after filling. This makes block construction more resource intensive.