This is an RFC for a consensus breaking change in the mining protocol to distance Stacks miner selection from Bitcoin miner. There are 2 options proposed that are dependent on functional feasibility. I am not overly familiar with the mining calls and miner selection code
Allow miners to submit a transaction for PoX that is designated for a future Stacks block.
During miner selection, smooth PoX transactions across a window of Bitcoin blocks.
Transactions for Stacks mining be allowed to be submitted prior to the block they are intended to be attached to. e.g., If Bitcoin block 1005 would normally have all of the transactions for Stacks block 955, the proposed solution would be to allow miners to have transactions in any Bitcoin blocks 1001-1005 that are designated for Stacks block 955.
For this solution to work, a miner must be able to indicate that a particular Bitcoin transaction is intended to be attached to a specific Stacks block. The mechanism is unclear to me (which might be why this is not currently suggested). Some indication within the Bitcoin transaction, referencing the Bitcoin transaction in the block. If this is completely off-base, some reading for better understanding how this process works would be greatly appreciated.
Instead of transactions in Bitcoin being designated for specific Stacks blocks, expand the number of Bitcoin blocks considered for each Stacks block. If Bitcoin block 1005 is normally the block for Stacks block 955, instead, consider all transactions from Bitcoin blocks 1002-1005 for Stacks block 955.
Smoothing seems like a more viable solution as it keeps a static link between Bitcoin blocks to Stacks blocks. The association becomes 1-to-N instead of 1-to-1. The idea here is to replace taking the transactions for a single block to taking transactions from N blocks (we’ll say 4 for now). This is like the miner ramp up. The sortition would use the transfers from the current and 3 previous blocks to arrive at the probability for each miner. A Bitcoin miner would have to win 4 (N) blocks in a row to completely eliminate competition. 1/4 (or 1/N) of the sum of transactions would go to the payouts that happen for the block (Stackers, …)
In the example above, the function assumes a simple sum of transactions for each miner across the Bitcoin blocks. This function could instead taper off in the previous blocks. Or the function could proportionally take into account the sum or count of bids for the block. Summing seems like the simplest cognitively and computationally.
A few thoughts about the unified mining pool (UMP) suggested in your link.
UMP feels like a move to PoS with extra steps. The stackers/miners relationship becomes a complex way to transfer coinbase reward value to stackers. This is a Stack direction thing and well above my place in the Stacks community. (The second point is more valid to the conversation)
How so? To my eyes, there’s nothing about the UMP approach that isn’t already possible today with PoX. The UMP just adds a layer of indirection between the act of producing a block and the act to announcing the winning block. But I’d love to know if I’ve missed something.
(EDIT: Just want to say, I like the acronym UMP. I think I’ll use it!)
This could just be my misunderstanding of how PoX works and what is necessary for the protocol to ensure security. We could continue that part of the discussion on the UMP thread as it is more peripheral to this thread. Smoothing feels like a quick fix if it is going to take longer to get to UMP.
From the perspective of a miner, in UMP, I put X amount of money in and expect to get X+Y amount in return. Additionally, the miner expects to get block decision contribution proportional to its share of the total contributions.
The UMP decides on the block and makes the transaction on the Bitcoin chain to pay the stackers. In this situation, the decision made by at least 66% of the BTC contributing parties. If one entity contributes over 66%, that one entity controls the blocks that are being added to the chain. There is no longer a chance for a smaller miner to win the block transactions decision.
unrelated but… UMP could allow for a “miner bribe” attack against contracts on Stacks. As an example, let’s say an auction contract on a jude NFT (laser eyes, bow tie, nyan cat background) is nearing close. An entity could buyout the UMP 66+% (as any number of miners) and prevent any other bids to the contract with roughly 2x the current miner total. Currently, with 2x the miner total, a nefarious miner could win each block with a 66% chance likelihood. 0.66 ^ N goes down pretty fast. It would take a much larger amount of BTC to ensure wining a range of blocks as it is currently.
There are always tradeoff. UMP seems to handle the current set of attacks well. Just thinking through what might be the next round.
I think smoothing makes sense. The PoX algo is already doing some form of lookback, since a given miner’s contribution to their chance to win a given block is:
Miner Contribution = min(0,median(last_6_block_sats_sent)) Miner Win Chance = Miner Contribution / Sum of All Miner Contributions
So this could potentially be implemented very quickly, simply by dropping the min() portion of the lookback, which would basically immediately implement a 6-block smoothing.
Given the Bitcoin miner in question is winning about >30% of all BTC blocks, a 6-block smoothing window is likely not large enough to entirely eliminate the profitability of the MEV, since they could win 2-3 blocks in a row a non-trivial amount of times and thus still win blocks with tiny bids, but it would certainly reduce it.
The main downside I can see to removing the min() logic would be that miners wouldn’t have to consistently mine every block, which is not great for encouraging a situation where everyone is actively and frequently participating in consensus, but with a median() vs an average() you still have to be mining at least 50% of blocks, and likely will try to mine more since that reduces the chances of you randomly missing a block and your contribution dropping to zero.
@jude do you know what the primary motivation was for including the min() element in the first place? Would help to understand tradeoffs of potentially removing it.
I pulled sats committed for each block for each miner from Onstacks for 30 stacks blocks 106651-106622. There were 6 active miners across those 30 blocks. With respect to Bitcoin MEV, 4 seemingly trustworthy, 2 seemingly malicious (…AFCR, …73FE).
I calculated their probability of winning a block Pwin = miner_commited_sats / total_commited_sats for each miner for each of the 30 blocks. I plotted the Pwin for each miner across the past 26 (ignoring the last 4 to account for smoothing later). The left side of each chart is the most recent block 106651.
Many miners get a 100% Pwin during this time period. Miner AFCR seems malicious as they have a consistent commitment of 2000 sats and only win when they are the lone commitment. They appear to potentially be a Bitcoin miner that successfully excludes other Stacks mining transactions but doesn’t have the coordination to only place their bid when that will happen. Miner 73FE seems malicious in that they only mine occasionally and when they are likely to win. They appear to have their mining coordinated with their block wins. In the first graph you can see this in the orange and teal peaks
For the smoothed chart I calculated Pwin = miner_sum_commited_5_blocks / total_sum_commited_5_blocks where miner_sum_commited_5_blocks is the sum of sats committed by the miner in this block and previous 4 blocks. total_sum_commited_5_blocks is similarly defined but for all of the sats across all miners. These are then plotted in the next chart, most recent block on the left. You might be able to see the “malicious” miner lines. They are neighboring the pixels of the axis. The 4 trustworthy miners are hovering around the 25% line based on their sats commitment and consistency of getting their transaction in.
We still need to sort out where the PoX sats go. I’m not as familiar with the stacking side of this process. I will assume that some portion of the PoX stacks goes to stackers. We don’t want to leave them with 400 or 2000 sats commitments. So lets smooth that side as well. Sats payout from the mining process is calculated: current_payout = total_sats_committed_for_block smoothed_payout = total_sats_committed_across_last_5_blocks / 5
Plotting these 2 values for the 26 blocks gives the following chart with the latest block on the left. 586200 is the lowest the smoothed payout goes even with single miners for 4 out of 5 blocks.
Note: Why did I pick smoothing over 5 blocks instead of 6? It is due to how the reward smoothing is calculated. If you divide by a multiple of 3 (or 7), you are likely to get a repeating decimal. Keeping denominators 2,4,5,8,10,… helps to keep the numbers clean.
Caveat: Miners will need to create a block in order to win, even if they didn’t have a sats commitment get through Bitcoin in the current block.