stuffy stuff

This commit is contained in:
counterweight 2026-10-25 18:54:44 +01:00
parent 679b7a27a6
commit 15f5f10038
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
9 changed files with 1084 additions and 59 deletions

View file

@ -1,30 +0,0 @@
# Beware of those who don't speak plainly
When I was young, I would frequently not understand what someone was trying to explain to me. This happened frequently when listening to engineers, economists, journalits, business leaders, scientists, and other sorts of "experts". As a young and humble padawan, I often assumed I was dumb and whatever was being presented was just above my pay grade.
As life progressed, I kept on expanding my brain, which led me to become knowledgeable in areas I previously wasn't and were the explanations of "experts" were flying over my head. This lead me to observe that, often times, the blame was not on dumb young me when I didn't understand the allegedly experts. Instead, they were either explaining stupid gibberish that sounded great but was wrong, or they were lying, bullshiting or pretending.
On the other hand, sometimes I stumbled upon people who explained important, brilliant and relevant truths in extremely simple and understandable ways. Young dumb me used to not appreciate this people, because being young and dumb, I mistakenly assumed if something was simple and easy to understand, surely it wouldn't merit attention nor would it be relevant. School taught us that learning is hard and boring, so the harder and the more boring something is, the more we should focus on it. Because otherwise... why would through the ordeal of traditional education?
Now I'm old enough to appreciate the value of simplicity. It took many spins around the sun to come to this, but I'm grateful I eventually did it. I've learned that most things can be explained if done with care and time, and thus whenever I encounter someone explaining something convoluted, I immediately become skeptical and try to read the situation to understand on whose end is the fault sitting. I obviously still consider that the fault may be on my end, and I also think that many things to be known will forever be above my paygrade. But often times, the problem is on the other end.
Here's a few situations that showcase this in specific examples.
## Confusing business models
It shouldn't be hard to explain how a business makes money. Typically it's as simple as explaining the pains and needs of your customers and how you solve them. Sometimes it needs a long-ish explanation if the audience is unfamiliar with the context of your customers, but once a bit of context is provided, the value should be easy to explain.
My experience when someone has a hard time explaining how their business makes money is that one of these is likely happening:
- It is not making money but there is a need to pretend it does.
- It is making money in a dishonest way and they want to put make up on it.
-
## Economics
## Management gurus
## Academia
## Politicians

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -151,6 +151,10 @@
<h2 id="writings-header">Writings</h2>
<p>Sometimes I like to jot down ideas and drop them here.</p>
<ul>
<li>
<a href="writings/how-much-hashrate-does-a-fork-chain-need.html" target="_blank"
rel="noopener noreferrer">How much hashrate does a fork chain need?</a>
</li>
<li>
<a href="writings/conways-law-cuts-both-ways.html" target="_blank"
rel="noopener noreferrer">Conway's Law cuts both ways</a>

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

View file

@ -0,0 +1,433 @@
<!DOCTYPE html>
<html>
<head>
<title>Pablo here</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="../styles.css" />
<style>
table.numbers {
border-collapse: collapse;
margin: 16px auto;
font-variant-numeric: tabular-nums;
}
table.numbers th,
table.numbers td {
padding: 4px 12px;
border-bottom: 1px solid #ddd;
text-align: left;
}
table.numbers th {
border-bottom: 2px solid #000;
}
</style>
</head>
<body>
<main>
<h1>Hi, Pablo here</h1>
<p><a href="../index.html">back to home</a></p>
<hr />
<section>
<h2>How much hashrate does a fork chain need?</h2>
<p><em>Published: 2026-10-25</em></p>
<p>
Ever since the BIP-110 chain failed, a few concerns have been bouncing around in my head (to be clear, they have
nothing to do with the ideas behind BIP-110). What caught my attention was how it
failed, and what that says about the power miners hold over Bitcoin now.
</p>
<p>
This post is the current state of my thinking. It is work in progress, and I'd love to be told where I'm wrong.
</p>
<h3>BIP-110 tried and died</h3>
<p>
When BIP-110 split off, its chain got so little hashrate that not even the fanatics stayed for
long. It mined 8 blocks and was definitively abandoned within 20 days. On the first day, poolcoin
(I'll call "normal" Bitcoin that for fun) already got ahead significantly. By day 3 it was pretty much a closed
case.
</p>
<p>
The interesting part is that this behaviour was very decoupled from the social acceptance of BIP-110. BIP-110
was far from any majority of proactive support, but I personally estimate somewhere around 5-20% of the network
was supportive of it, and the rest was mostly neutral. But its brutal death after the chainsplit was not
reflective of this. Why didn't all the fanatics stick around? Because there was no shortage of them. We know
because now they've all moved on to Blakecoin. Those guys were willing to run BIP-110 to the ultimate
consequence of splitting from the world, but still, they left the BIP-110 chain. Why? Because the chain was
simply unusable:
</p>
<ul>
<li>blocks took days to arrive, not minutes, and</li>
<li>the difficulty adjustment that could have fixed that sat years in the future.</li>
</ul>
<p>Believers walked away from the chain they believed in, because using it was impossible.</p>
<h3>What actually concerns me</h3>
<p>
That episode made something concrete for me: hashrate is decoupled from nodes and users, and
hashrate (or the lack of it) can be used to make a chain unusable, whatever its users think of it
ideologically/politically.
</p>
<p>Put that together and you get a loop:</p>
<ol>
<li>Low hashrate → long blocktimes, and a long wait for the difficulty adjustment.</li>
<li>Long blocktimes → the chain is too slow to transact on.</li>
<li>Too slow to transact → users who hold and depend on bitcoin leave the chain.</li>
<li>Fewer users → less reason to mine it → even less hashrate.</li>
</ol>
<p>
Mining is heavily centralized in a handful of pools. That means a mining cartel could trigger this loop on any
chain it chooses to starve.
</p>
<p>So I set out to answer some questions with numbers:</p>
<ol>
<li>
In a chainsplit, how does a chain's share of hashrate affect the experience of the people
using it? What counts as "too little" hashrate, and what counts as "too long" a blocktime?
</li>
<li>
Given today's mining pool concentration, which combinations of pools have the power to keep
a fork chain alive, or to kill one?
</li>
</ol>
<h3>Why a minority chain is so slow</h3>
<p>
When a chain splits, both sides keep the difficulty they had at the moment of the split. Suppose one side ends
up with a share <code>h</code> of the hashrate. Then:
</p>
<ul>
<li>
Blocks arrive <code>1/h</code> times slower. Half the hashrate means 20-minute blocks; a tenth means
100-minute blocks.
</li>
<li>
It stays that slow until you mine 2016 blocks, the next adjustment. At <code>1/h</code> the usual speed, that
takes <code>14/h</code> days instead of the usual two weeks.
</li>
<li>
Even then, relief is partial: each adjustment can cut difficulty by at most 4×. A very weak
chain needs several adjustments, each one arriving sooner than the last, before it is back to 10 minutes.
</li>
</ul>
<p>Here is what that looks like:</p>
<table class="numbers">
<thead>
<tr>
<th>Hashrate</th>
<th>Time between blocks</th>
<th>First adjustment</th>
<th>Back to 10 min</th>
</tr>
</thead>
<tbody>
<tr>
<td>50%</td>
<td>20 min</td>
<td>28 days</td>
<td>28 days</td>
</tr>
<tr>
<td>25%</td>
<td>40 min</td>
<td>56 days</td>
<td>56 days</td>
</tr>
<tr>
<td>10%</td>
<td>1.7 hours</td>
<td>140 days</td>
<td>175 days</td>
</tr>
<tr>
<td>5%</td>
<td>3.3 hours</td>
<td>280 days</td>
<td>1.0 years</td>
</tr>
<tr>
<td>1%</td>
<td>16.7 hours</td>
<td>3.8 years</td>
<td>5.1 years</td>
</tr>
</tbody>
</table>
<figure style="width: 97.5%; margin: 10px auto;">
<img width="100%" height="auto" src="../static/chainsplit-blocktimes.png"
alt="Blocktimes grow as hashrate shrinks">
<figcaption>Blocktimes grow as hashrate shrinks</figcaption>
</figure>
<p>
The second column is what hurts, and the third is for how long it's going to hurt. Things become worse given
the importance of expectations: if users think a chain won't be usable in the future, even if it's barely
usable now, they will drop it anyways due to expectations, which triggers a doom loop.
</p>
<h3>Who uses the chain, and how long will they wait?</h3>
<p>
"Usable" means different things to different people, so I modelled three personas, each with a tolerance for
how long they're willing to wait for a transaction to reach 3 confirmations, which for
simplicity we will define as settled.
</p>
<table class="numbers">
<thead>
<tr>
<th>Persona</th>
<th>Who they are</th>
<th>Will wait for 3 confirmations</th>
</tr>
</thead>
<tbody>
<tr>
<td>Hodler</td>
<td>Holds, rarely moves coins</td>
<td>1 week</td>
</tr>
<tr>
<td>Medium of exchange</td>
<td>Pays and gets paid</td>
<td>1 day</td>
</tr>
<tr>
<td>Institution</td>
<td>Settles business on a schedule</td>
<td>3 hours</td>
</tr>
</tbody>
</table>
<p>
Since block arrival is random, a wait isn't a single number but a distribution. Sometimes you get lucky,
sometimes you don't. I settle that with a service level: at p90, 90% of a
persona's transactions must reach 3 confirmations within its tolerance. On the regular 10 min schedule, that p90
wait is about 53 minutes (read as: 90% of times, you will see three blocks confirmed in under
53 min).
</p>
<h4>Assumptions</h4>
<p>To keep this tractable, I assume:</p>
<ul>
<li>3 confirmations count as final.</li>
<li>Static hashrate: each side keeps its share of hashrate for good.</li>
<li>
Empty mempool: no fee competition, no queueing. This is the best case scenario: if you start
facing a clogged mempool, waits would be even longer for some.
</li>
<li>
The split starts at the beginning of a difficulty period, so the minority chain has a full
2016 blocks to grind through before any relief. Note that changing this can greatly influence the delay to the
first difficulty adjustment, but I won't explore this further since for now it seems canonical that fork
activations happen on the start of difficulty periods.
</li>
<li>
Out of scope: introducing the effects of hard forks of any kind, or more generally anything
that tampers with the difficulty adjustment.
</li>
</ul>
<h3>The numbers</h3>
<p>
Turning tolerances into hashrate is simple division. If a persona tolerates waits <code>k</code> times longer
than mainnet's, the chain can be <code>k</code> times slower, so it needs <code>1/k</code> of the hashrate. This
is the share of total hashrate a chain needs to keep each persona on it:
</p>
<table class="numbers">
<thead>
<tr>
<th>Persona</th>
<th>Tolerance</th>
<th>p50</th>
<th>p90</th>
<th>p95</th>
</tr>
</thead>
<tbody>
<tr>
<td>Institution</td>
<td>3 hours</td>
<td>14.86%</td>
<td>29.57%</td>
<td>34.98%</td>
</tr>
<tr>
<td>Medium of exchange</td>
<td>1 day</td>
<td>1.86%</td>
<td>3.70%</td>
<td>4.37%</td>
</tr>
<tr>
<td>Hodler</td>
<td>1 week</td>
<td>0.27%</td>
<td>0.53%</td>
<td>0.62%</td>
</tr>
</tbody>
</table>
<p>
Institutions are demanding: a chain needs roughly 30% of all hashrate before they'll use it. Everyday spenders
need under 4%, and hodlers need about half a percent.
</p>
<h4>Defensive coalitions: who can keep a fork chain alive?</h4>
<p>
Now map those bars onto today's mining pools. If a group of pools wants to keep a minority chain usable, how
many does it take?
</p>
<table class="numbers">
<thead>
<tr>
<th>Persona</th>
<th>Needs</th>
<th>Smallest coalition today</th>
</tr>
</thead>
<tbody>
<tr>
<td>Institution</td>
<td>29.57%</td>
<td>2 pools (e.g. Foundry USA + AntPool)</td>
</tr>
<tr>
<td>Medium of exchange</td>
<td>3.70%</td>
<td>1 pool (any of the top 7)</td>
</tr>
<tr>
<td>Hodler</td>
<td>0.53%</td>
<td>1 pool (any of the top 14)</td>
</tr>
</tbody>
</table>
<p>
A single mid-sized pool committing to a chain is enough to keep it usable for hodlers and everyday spenders. A
couple make it workable even for the hypothetical institutions.
</p>
<h4>Offensive coalitions: who can force users onto a softfork?</h4>
<p>Now flip it around, because this is the scenario that worries me.</p>
<p>
A coalition of pools activates a softfork that users don't want. The users reject it and stay
on the chain without it, or even push for a URSF. That chain is left with whatever hashrate the coalition didn't
take. If that falls below the users' bar, the chain they want becomes unusable by their own standards, and
they're forced to either jump across to the cartel softfork, or hardfork away like the Blake boys did.
</p>
<table class="numbers">
<thead>
<tr>
<th>Persona</th>
<th>Softfork side must hold</th>
<th>Smallest coalition today</th>
</tr>
</thead>
<tbody>
<tr>
<td>Institution</td>
<td>over 70.43%</td>
<td>4 pools (the top four, 72.34%)</td>
</tr>
<tr>
<td>Medium of exchange</td>
<td>over 96.30%</td>
<td>11 of 18 pools</td>
</tr>
<tr>
<td>Hodler</td>
<td>over 99.47%</td>
<td>14 of 18 pools</td>
</tr>
</tbody>
</table>
<figure style="width: 97.5%; margin: 10px auto;">
<img width="100%" height="auto" src="../static/chainsplit-coalitions.png" alt="Pool coalitions">
<figcaption>Pool coalitions</figcaption>
</figure>
<p>
If you consider AntPool to actually be AntPool and friends, then you only need the three top pools today to bend
institutions into a softfork, which you could argue already creates enough gravity across miners and users that
everyone will end up in the softfork chain.
</p>
<h3>The scenario that scares me</h3>
<p>Here's the nightmare, using the numbers above:</p>
<ul>
<li>
Foundry, AntPool &amp; friends, and F2Pool, about 72% of hashrate between them, sponsor a softfork that sets
the minimum fee to 5 sat/vB.
</li>
<li>
They go hard on propaganda: "stand aside, lady, it's for the security and future of the network."
</li>
<li>They set a short deadline that barely gives the community time to react, say four weeks.</li>
<li>
The rest of the network doesn't want this. But the rest of the network doesn't have the hashrate to keep a
user-activated chain usable, at least not for anyone who needs transactions to settle within hours.
</li>
<li>
For most users, paying 5 sat/vB is a lesser evil than risking the run-for-your-life scenario of jumping into a
hard fork (to make matters worse... which one?). And staying in the "old" chain is not feasible since it will
die starved of hashrate.
</li>
</ul>
<p>
Nothing in that scenario requires breaking Bitcoin's rules. It only takes a few pools agreeing with each other,
and the loop from the start of this post does the rest. It's a fuck up in Bitcoin's design, and as far as my
knowledge goes, it's been here since the chain got started. We're just starting to see it emerge now.
</p>
<h3>The solution</h3>
<p>
I have no solution for this. It somehow feels all the forces are pointed at making this worse. The only "happy
scenario" is Bitcoin gets slowly destroyed like email did, we lose all the usefulness it was giving us, and
we're back at square one. The community with the ethos (supply capped, permissionless, decentralized freedom
money) needs to start from scratch with some new coin/solution, with all the challenges that come with it. We
could be locked in fiat hell for decades before we pull it off.
</p>
<p>Or perhaps the Blake boys will somehow make it. I have little faith but I wish them the best.</p>
<h3>Code</h3>
<p>
You can find the repo where I worked this out <a href="../static/hashwars-musings.zip">here</a>.
</p>
<hr />
<p><a href="../index.html">back to home</a></p>
</section>
</main>
</body>
</html>

View file

@ -1,29 +0,0 @@
Throughout most of my career, the standard choice of tooling to run local dev stacks has been Docker/Podman. You make a compose file and conveniently start/stop services as needed.
It surely works. But lately I've been slightly bothered by it. Many times I develop simple services that don't go through convoluted containerization and deployment patterns, such as Docker images + Helm + Kubernets. Instead, they simply end up as regular processes in a Debian box (typically a VM). What bothers me is that this made me end up with two quite different setups: I would use Docker for local dev, then bare metal deploys in production. This is annoying because you have relevant differences in how things run in each environment, which leaves you with no way to test locally certain deployment quirks.
You could thing for yourself: sure, but you can have a staging/testflight environment where you tests deploys as closely as possible as in production. That is certainly and option (and what I usually do nowadays), but why not do it already locally?
To scratch my itch, I decided to experiment with a toy project I was fooling around. I decided I would come up with a way to run it locally with a fully isolated VM instead of the existint docker compose driven approach.
## Tooling choice
I decided to use KVM + QEMU + libvirt + inqus
KVM (Kernel Virtual Machine) is a Linux kernel module. It's job is to expose CPU extensions to userspace, which allows the VMs you start to run CPU commands directly in the CPU, and not in an emulated way. Technically, you can run this stack without KVM, but then all CPU workload will be emulated and take many orders of magnitude longer to run, which sucks. One constraint to notice is that you can only leverage KVM if the VMs that you run have the same chip architecture as the host. Obviously, this is because you can't send, for instance, ARM operations to a RISC-V processor. QEMU can run (much slower) without KVM, and can also emulate other CPU architectures without it.
QEMU (Quick EMUlator) is the actual Virtual Machine emulator. Technically, the VM running on your computer is a QEMU process. The process is taking care of simulating all of the physical components of the VM (CPU, RAM, disk, etc). QEMU holds the VM memory in the host RAM memory, handles CPU commands being passed to the host CPU (or emulates them itself if no KVM), and maps a virtual disk to actual disk space in one of the hosts disks. Overall, what QEMU does is to create virtual interfaces that emulate the real interfaces offered by the parts of a computer and then, behind the curtains, map those interfaces to backend processes that either do the thing on the host (writing to a QEMU disk will actually write to the host disk) or on a completely in-process simulation of the part (BIOS/UEFI firmware).
Now, to start a QEMU VM you need some initial state. Your options resemble pretty much the same you would face when working with a real computer.
Your first option is to grab an OS installer image and install the OS from scratch. This works just like it would if you were setting up a real computer. QEMU can emulate a CD/DVD reader and pretend the ISO is being read from it. This is an option, but rather painful if you plan on frequently recreating VMs. And it rarely gets used because there is a much better alternative.
But if you had a computer, you could get up to speed much faster if you already had a drive with an OS installed and ready to run, and you simply plugged it to your motherboard and told it to boot from there, right? The same approach is what gets used the most when running QEMU VMs. There is this format called `qcow2`, which is basically a byte-per-byte clone of the disk of a VM. Large providers like Canonical will ship ready to use `qcow2`, which are pretty much the state of the OS after having been installed but before having done a first boot. Combined with `cloud-init`, these images let you spin a ready to use + minimally configured to your needs VMs fast without having to go through the OS install.
You can also build your own `qcow2` images from a running VM, although making generic images that are useful in many situations has certain complexity. It is nevertheless a good option for backups or for some spots where you need to start a VM in a reproducible state repeteadly. And, if you're willing to learn how to do it, you can actually also build your own custom (as in, with whatever setup you need) yet generic (as in, stripped of host specific details that would give you headaches if you spin it repeteadly) images.
Now, you can use QEMU directly in a standalone way, but the interfaces are rather raw. QEMU focuses heavily on doing its job (running a VM) well, and doesn't offer convenient tooling to make managing many VMs or automating VM management ergonomic.
To solve this, we add Incus to the stack. Incus is a management tool for Virtual Machines. It doesn't actually run the machines, that's QEMU's job. Incus runs a daemon on the host which can be interacted with via CLI.