Skip to content

Instantly share code, notes, and snippets.

@instagibbs
Last active January 2, 2024 10:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save instagibbs/4d356793b97bf566d78bfced3e0b3c41 to your computer and use it in GitHub Desktop.
Save instagibbs/4d356793b97bf566d78bfced3e0b3c41 to your computer and use it in GitHub Desktop.
Bitcoin Mempool and Free Relay

Bitcoin has a KYC problem. We don't know any customers. One man's spammer is another man's marketing genius.

This plays into how Bitcoin software handles transactions that are gossiped to it on the network. Your local node does a list of anti-DoS checks, as well as miner compatibility checks, caches the transaction, and forwards it to any nodes connected to yours.

For miner incentives, this is not exactly straight forward, but easy enough to inuit. If you can "make the mempool better", you accept a new transaction, and kick out whatever it replaced.

This isn't the whole story. What it it makes the mempool contents better for miners, but is worse for me as a local node? There's an inherent tension here that has to be balanced.

One key anti-DoS metric is what we call "free relay". It can be summed up as roughly:

(total_sats_in_mempool + total_sats_mined) / total_bytes_relayed < minrelay

Let's define these terms:

total_sats_in_mempool: Self-explanatory; how much fees in sats that's up for grabs in your local mempool

total_sats_mined: during whatever time period you're measuring, how many sats entered the mempool then left in a block

total_bytes_relayed: during the same time interval, how many vbytes did you accept into your mempool (then relay to others)

minrelay: the minimum sat/vbyte rate we'd like to consider doing all the required CPU and bandwidth usage to relay a transaction. This is 1 sat/vbyte today

If more fees are in the mempool, there is potentially more for miners to take, paying the way for vybtes being relayed. If fees are mined, they have paid retrospectively.

total_sats_in_mempool are intentionally allowed to drop in two specific circumstances:

  1. transaction timeout: If a transaction has been in the local mempool for two weeks and not mined, it's likely not going to be mined, and is dropped. This is "free relay" limited by long time-intervals.
  2. transaction evicted due to full mempool: If a new transaction is entered into the mempool, causing the mempool memory usage to exceed the configured maximum, eviction occurs. We remove the lowest descendant score transaction packages from the mempool until we reach our memory target. As soon as the memory target is hit, we look at the highest scoring removed package, add "incremental relay" feerate to that, then set this as our new dynamic mempoolminfee. This "raises the bar" for the next set of vbytes to be considered, keeping any free relay bounded in time and size.
  3. mined: a miner picks it up, no free relay.
  4. RBF: a new transaction directly conflicts with it, and pays "enough fees" to avoid free relay

BIP125 rule#3

The last way a transaction can be dropped is by replacement. To ensure total_sats_in_mempool doesn't go down in appreciable amounts, the total transaction fees of the replaces transactions must be "paid for" by the new transaction. total_sats_in_mempool also goes up by the equivalent amount of total_bytes_relayed that the replacing transaction(s) trigger.

Rule#3 Pinning

This all looks pretty reasonable, and has to this date resulted in no obvious relay amplification attacks. However, when considering newer smart contract constructions such as the Lightning Network, an issue arises. If your utxo is being held with a potentially malicious counter-party, what happens if they put a transaction package in the mempool that is very large, but also too-low fee to be mined in the desired timeframe.

In other words, the counter-party increases total_sats_in_mempool, but unlikely to be mined expeditiously, and you are now on the hook for replacing that increased value for your RBF replacement. In the worst case scenario, the attacker has put a DEFAULT_ANCESTOR_SIZE_LIMIT_KVB=101 set of dependant transactions into the mempool, that your "efficient" <1kvB RBF must pay to replace. The inherent tension is between this DEFAULT_ANCESTOR_SIZE_LIMIT_KVB is, and what an "honest user" needs to make an RBF. Depending on how you comapre these values with tolerances, that at least puts the "pinning" potential up to 200-500x, meaning if an honest RBF would take X sats, you're not spending up to 500X sats to do so.

"Prior restraint" Relay Less Stuff solutions

Not going into a deep dive here, but this method of getting around rule#3 pinning is to have pre-signed transactions commit(in relay policy) to not have the overall transaction package be "too big" or "low priority". One example is the "V3" transaction proposal.

As long as enough nodes update and there's a path from the transaction maker and a supporting mining node, this can result in drastically reduced pinning as we're directly avoiding the pinning vector in an opt-in method. It doesn't help in every situation imagined, and also needs additional verification that these proposals are "incentive compatible". Left as an exercise for the reader.

The effect of this kind of system is pushing down total_bytes_relayed, but only in an opt-in manner.

Some more stuff I wrote for a small audience: https://gist.github.com/instagibbs/f729399d571504d51d0143824f00098b

Just Relay More Stuff, Just a Bit

What if in pinning situations, we let the "obviously not a pin" replacement get into our mempool even though it doesn't pay for the replaced mempool fees?

It seems intractible in that any heuristic we decide relies on exceptions to the rule that we can't rate-limit just to honest users. If it's accesible to the honest users, it's also accessible to attackers.

In other words, if we try to "price" free relay, whatever price we pick it's either too cheap for attackers, or too expensive for honest users. In a setting with HTLCs, time is money, and attackers will clearly be willing to blow the "free relay" budget in an amortized attack against as many smart contracts as possible.

There are a million variants you can try and there's no proof none of them can work, but unless KYC is instituted in the mempool, it strains credulity.

Relay Less Stuff (Open Questions)

This is the more promising direction, which is similar to "prior restraint", but not in an opt-in fashion. Is there a way we can more smartly relay "pin" transactions, such that we can drop total_bytes_relayed to bound our free relay?

Two trivial yet unsatisfying examples:

  1. Restrict local mempool to 1MvB("top block"). Clearly anything you relay is likely to be mined, causing pinning attacks to be basically irrelevant. The downsides here are there's no backlog for miners to build additional templates, it hurts block relay, and users will say "why isn't my transaction showing up on explorer.info?"

  2. Only relay ancestor packages of total size 1kvB. If we never relay large packages, the pinning is gone. Again, there are at least a couple issues with this. It's likely that people want to make larger packages than 1kvB for general commerce, such as consolidations. We can't retroactively restrict relay of these packages entirely, as that breaks existing uses, potentially causing theft of money.

Is there a middle-ground mixture of these that can work, where transaction is "next block(s)", or is part of a "small" package outside of "next block(s)", and if not either of those, probabilistic relay applies?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment