Where this lands
Sessions 1 and 2 read the chain as a dataset and found the indexer where the product turned out to be. Sessions 3 to 5 took apart what an address is and what an active user count is worth once somebody is paid to move it. Sessions 6 and 7 put a reorganisation through a warehouse and designed for retraction. Session 8 put a model on top of all of it, and session 9 is the record of this bench being wrong. Everything below runs from the same assets/w3-live.js, whose five properties are checked on load, including the one that only exists because it caught a fault the other four could not see.
★ What you walk out with today. Two measured exchange rates you can quote from memory, both obtained by moving a control rather than by being told. A feel for which inputs move the answer and which do nothing at all, which is the difference between understanding a pipeline and having read its documentation. And a short written brief for a chain-sourced dataset, in the form somebody else could check.
LiveWhat the two benches recompute on every press4 min▶
The first bench builds a population of holders and operators, generates transactions from it, then runs the standard co-spend heuristic over those transactions with a union-find and scores the result against the truth it happens to know. It also re-runs the whole thing with slightly larger operators, so the slope it reports is a measurement taken there and then rather than a number quoted from a table.
The second bench runs a loader over a chain that reorganises, at whatever depth and lag you set, and counts the rows left describing history the chain no longer has. Nothing here describes a real network, and none of these figures should be quoted as though it did. The population is generated, the reorganisations are placed on purpose, and the only real things in the page are the algorithms run over them.
Who the count is counting
15 min hands onThree inputs. Change one at a time and predict the direction before you press. An input whose sign you cannot predict is one you do not yet understand, and finding that out here costs a minute rather than a quarter.
...
LiveThe four runs worth doing first5 min▶
- Leave everything alone. World A, 60 addresses each, 97 percent discipline. 982 addresses seen, 304 entities in truth, 696 after clustering. Wrongly merged: 0. The naive count is 3.2 times the truth and the cleaned one is 2.3 times, so the standard heuristic removes about half the error and leaves the rest.
- Drag the discipline slider from 97 down to 50. The clustered count falls from 696 to 666, a swing of 31 against an excess of 392, which is the sweep session 5 is built on. Now take it to 0, well past anything a real operator would do, and it only reaches 643. The entire travel of the slider is worth 54 of the 392.
- Now drag the address slider instead, 60 to 90. The world gains 120 addresses and the clustered count gains 120. The reported slope stays at 1.00. Nothing about the cleaning step resists this, because there is nothing for it to read.
- Set discipline to 0 and addresses to 10. The slope drops to 0.55 on World A and stays below 0.75 on the other four. A small operator that co-spends freely is a normal holder with more addresses, and the heuristic finds it. Both conditions have to fail at once.
Self-studyWhy the error only runs one way, and what that licenses4 min▶
Across every seed and every setting in the box above, the wrongly merged count is 0. The heuristic never fuses two real holders, because the only thing it acts on is a spend that genuinely had two inputs, and a spend with two inputs genuinely had one signer. It fails in one direction only: it leaves addresses split that belong together.
That asymmetry is worth more than the number itself. A clustered entity count is a ceiling on the true entity count and a floor on nothing at all. You may say "at most 696 distinct parties" and be correct. You may not say "about 696 parties", and the difference between those two sentences is the entire professional content of the method. Write the ceiling, state the method, and refuse to be talked into the second phrasing by anybody who wants a headline.
A second reason the count overstates is worth naming alongside the first: on World A only about 787 of the 982 addresses ever appear as a spend input at all. The rest are seen only as recipients, so no evidence about them can exist and no heuristic reading spends can ever merge them. That is a separate mechanism from operator discipline, and it means a share of the gap is permanently out of reach of any amount of cleverness on the co-spend side.
Self-studyThe share flip, which is the result people find hardest to accept4 min▶
At address level the operators hold 240 of 982, which is 24.4 percent. After clustering they hold 239 of 696 clusters, which is 34.3 percent. The truth is 4 parties out of 304, which is 1.3 percent. So the cleaning step raised the contamination as a proportion, by ten points.
The mechanism is exactly the slope you just measured. Ordinary holders merge, so their entity count falls. Operator addresses do not merge, so theirs does not. Shrinking the honest population while leaving the manufactured one intact raises the manufactured share by arithmetic, no matter how good the heuristic is at what it does. Any model, metric or sample drawn from clustered entities is more operator-weighted than one drawn from raw addresses.
Be careful about the blame version of this. Decomposed on World A, the 392 excess is 235 from the operators and 157 from ordinary holders, so operators are the larger share. But only 31 of that 392 responds to operator behaviour across the plausible half of the range, and only 54 across the whole slider, because most of their excess comes from simply holding many addresses that never need to be spent together. The defensible sentence is about behaviour, not about who is at fault: the part of the gap you could close by catching careless operators is small, and the part you cannot close is structural.
What waiting actually buys
16 min hands onTwo dials that are usually discussed as one. How deep the chain reorganises, which is a property of the world and not a choice, and how long the loader waits, which is the only choice you have. Set them independently and a very simple exchange rate appears.
...
LiveThe five runs worth doing first5 min▶
- Depth 3, lag 0, no retraction. 511 orphaned rows, 5.75 percent of the warehouse. No job failed, no check fired, and every one of those rows describes history the chain no longer has.
- Lag to 1. 346 rows, 3.99 percent. This is the dangerous setting, because it looks like a precaution and leaves two thirds of the damage. A defence shallower than the thing it defends against is not partial safety, it is the same failure with a better story attached.
- Lag to 3. Zero. 125 rows held back. That is the whole price, and it is a freshness price rather than a correctness one.
- Lag to 6, then 12. Still zero, and 249 then 497 rows held back. You are now paying about 42 rows a block for nothing at all. Going deeper than the depth is pure cost.
- Put the lag back to 3 and drag the depth to 6. 508 rows wrong, 5.80 percent, from a lag you had just proved was sufficient. Nothing about your loader changed. The world did, and the loader had no way to notice.
LiveReading the shortfall column instead of the lag4 min▶
The box reports a shortfall, which is the depth minus the lag, floored at zero. Every row in the grid is predicted by that one number to within a few percent, which is why the two-dimensional table people usually build is misleading: it invites you to hunt for an interaction that is not there.
Read it as a rate. Roughly 169 wrong rows per block of shortfall, roughly 1.9 percent of the warehouse per block, roughly 42 rows held back per block of lag whatever the depth. Those three numbers let you answer the operational question in your head, in a meeting, without the bench: someone says the chain reorganised deeper than usual last week, by two blocks, and you can say what that did to the reporting layer before anyone opens a laptop.
The direction of ignorance matters too. If you do not know the depth, you cannot know whether your lag is sufficient, and there is no signal in your own pipeline that will tell you, because the failure mode is silent by construction. The number has to come from the chain you are reading, not from your warehouse.
Self-studySwitch retraction on and watch what it hides4 min▶
Set the lag to 1 against depth 3 and switch retraction on. The orphaned count goes to zero and 346 rows are corrected instead. The warehouse is clean and the lag did almost none of the work, which is exactly the appearance that an earlier version of this bench produced by accident and reported as a finding.
The cost has not gone away, it has changed shape. Retraction means figures that were published move afterwards, so the question stops being an engineering one and becomes a question about what your consumers were told. A daily report that quietly restates last Tuesday is a different product from one that is late, and neither is wrong, but only one of them can be chosen by accident.
The habit worth keeping is the one that caught the fault: when a defence produces a clean result, check that it is the defence you think is doing the work. Turn the other one off and re-run. If the result does not change, your mental model of your own pipeline is wrong in a way that will surface at the worst possible time.
What you write down
11 min liveBoth benches point at the same discipline, which is that the number you publish has to arrive with the thing that bounds it. Ten minutes, on a dataset you actually have.
State the unit of analysis and the direction of its error
Not "users" but "addresses clustered by co-spend, which is a ceiling on parties and never merges two real ones". One sentence. If you cannot say which direction your count is wrong in, you do not yet have a count, you have a query result.
State the load discipline and the depth it assumes
"Loaded at a lag of N blocks, which is sufficient for a reorganisation of depth N or less, and we do not retract." The second half of that sentence is the one that gets omitted, and it is the one that fails.
Name the party that benefits from the number moving
If there is one, the metric is a target and will behave like one. That is not cynicism, it is the same reasoning as the operator slope: a population that is rewarded for looking a certain way will look that way, and no amount of cleaning reads intent.
Say what would change your mind
A measured reorganisation depth from the chain itself. An off-chain identity source that lets you check the clustering against something. Write the trigger down now, while nothing is on fire, because the version you write during an incident will be the version that protects the number rather than the reader.
LiveThe two exchange rates, on one card3 min▶
Clustering. Every manufactured address becomes one more counted entity, a slope of 1, and the only inputs that change it are ones you do not control. Catching careless operators recovers 31 of a 392 excess, and 54 even if they stop trying entirely. The output is a ceiling, and it is a ceiling that gets proportionally more contaminated the better your cleaning gets.
Loading. Every block of shortfall costs about 169 wrong rows and about 1.9 percent of the warehouse. Every block of lag costs about 42 held rows and buys nothing past the depth. One subtraction decides the whole thing, and one of its two terms is a property of a world you are not running.
Both have the same shape. A quantity you can measure precisely, governed by a parameter you do not own, with a silent failure mode in between. That is the honest summary of working with this class of data, and it is a good deal more useful than either enthusiasm or dismissal.
Homework
About an hour, on something real.
Find the depth
For whatever chain your organisation reads, find a published figure for how deep reorganisations go and where it comes from. If nobody can produce one, that is the finding, and it means the lag in your loader was chosen by whoever wrote it first.
Re-derive one slope yourself
Take any clustering or deduplication step you own and measure what one more duplicate does to its output. If the slope is 1, the step is not doing what its name says, and the name is what people are trusting.
Turn one defence off
Pick a control that is producing a clean result in a pipeline you own and disable the other one. If the result does not move, you have found out something true about your own system for the price of one run.
Three questions
Adding 10 addresses to each of the 4 operators adds 40 addresses to the world and 40 to the clustered entity count. What does a slope of exactly 1 tell you about the co-spend heuristic?
A slope of 1 means the step removed nothing. Option A describes what the answer would look like if the addresses were 40 real parties, which is precisely the mistake the count invites. The heuristic is not failing at its job here, it is being run against a population that emits none of the evidence it consumes.
Your loader waits 3 blocks and has been clean for a year. Reorganisations at the source deepen to 6 blocks. What happens in your warehouse?
The measured grid gives 508 orphaned rows at depth 6 with a lag of 3. The dangerous feature is that nothing about your side changed, so there is no signal on your side to detect it. The depth has to be monitored where it happens, which is not in your warehouse.
Clustering raises the operator share of the population from 24.4 percent of addresses to 34.3 percent of entities, against a truth of 1.3 percent. Why does a cleaning step make the contamination worse as a proportion?
It is arithmetic on two populations shrinking at different rates, not a defect in the heuristic. Option B is ruled out by measurement: the wrongly merged count is 0 on every seed and every setting. Option C describes an adaptation the bench does not model and does not claim.