# Accepted-accounting check compares rows in two different sort orders: false "legacy credits differ from intent", restart loop, and a submission latch that discarded 21 found blocks

**Stack:** `stack-dev-v2.1.0-rc.2` (linux-amd64), pool binary `BlockDAG Pool 2.1.0-rc.1 source=53ccde3c1c2de14b591fcae47dddffa7b4b68884`
**Database:** `postgres:15-bookworm` from the stack's compose file, defaults → `bdagpool` is `UTF8`, `LC_COLLATE=en_US.utf8`
**Network:** BlockDAG Community, chain ID 1404
**Reported:** 24 September 2026 · **Reporter:** DAGCore (dagcore.net), independent community pool
**Follows:** our report of 21 September, "Pool refuses to start against its own database after a restart". This is the cause of that one.

All times UTC. Every claim below was measured; the one thing we could not determine is marked as such.

---

## Cause

The accepted-accounting verification compares, per block, the intent's credit lines with the legacy `credits` rows,
**position by position**, but the two lists are sorted differently:

- intent lines (`accepted_block_accounting_credit_lines_v1`, by `line_index`) are in **byte order** — the binary even
  checks this ("accepted accounting credit lines are not strictly sorted");
- legacy credits are read with `SELECT miner_address,amount::text FROM credits WHERE block_hash=$1 ORDER BY miner_address,id`,
  i.e. sorted by **PostgreSQL under the database collation**, `en_US.utf8` by default.

Miner addresses are EIP-55 checksummed (mixed case). Byte order puts `0xC814…` (0x43) before `0xb36E…` (0x62);
`en_US` puts `0xb36E…` first. The rows are identical, the order is not, and the comparison fails.

Block 17401801 (`e419cb6b…0501000000`), the block where our pool latched:

| | order of the 4 miners |
|---|---|
| intent lines (byte order) | `0x2a7b…3197`, **`0xC814…6E28`, `0xb36E…B99d`**, `0xd901…7428` |
| `ORDER BY miner_address` under en_US.utf8 | `0x2a7b…3197`, **`0xb36E…B99d`, `0xC814…6E28`**, `0xd901…7428` |

Same four miners, same amounts to the wei, same `credit_line_count`, same digests.

In our database of 221 accepted blocks: miner/amount multisets of intent lines and legacy credits are **identical for
all 221**; intent order equals byte order for **all 221**; intent order equals the `en_US` `ORDER BY` for only **70** —
the other **151** are every block in which both `0xb36E…` and `0xC814…` earned credit, starting with the database's
very first block.

## Proof (copies of the production database, never production)

The pool binary run against restored copies, conflict row deleted, stratum/metrics on other local ports, no key:

| run | database | result |
|---|---|---|
| 3 | copy in a database with `LC_COLLATE=C LC_CTYPE=C` | **starts** (`Stratum server is listening`), 30 s, no error |
| 4 | `en_US` copy with the 151 order-mismatching blocks removed | **starts** |
| 5 (control) | `en_US` copy with the 70 order-matching blocks removed | **fails**: `accepted accounting readiness failed: accepted accounting v2 legacy credits differ from intent` |

And on the unmodified copy (conflict row present) the startup message is
`accepted accounting v2 verification failed conflicts=1 missing_intents=0 orphan_lines=0 bad_line_counts=0`;
with only the conflict row deleted it is `legacy credits differ from intent`. The data counters are all zero — only
the order comparison fails.

## What it did to us

**At startup (deterministic):** any restart against a database holding one block with such a miner pair fails, and
poolworker loops. This is our 16 and 21 September reports; the only way out was deleting the pool database.

**At runtime (once, trigger unknown):** the pool ran for two days with 151 such blocks without complaint. At
23:34:35 on 23 September, right after finalising block 17401801, a reconciliation ran and failed; the conflict row it
wrote latches submission for good:

```
23:34:35 🎯 BLOCK FOUND height(le)=17401801 job=231405-18d8174dd48ae90c_1c0b0000 hash=e419cb6b5604cd9a54b6c78ad93294ed1f3ab781f6925f8ffc9dae0501000000
23:34:35 accepted accounting materialized candidate=bd3cbcc44645f8a663e9d5c025e9f76bd5079ccc143c82b423bcf0beaea20399 credit_lines=4
23:34:35 ✅ Block submission finalized candidate=bd3cbcc4…0399 first_accepted=true
23:34:35 accepted accounting reconciliation failed: accepted accounting v2 legacy credits differ from intent
23:38:50 🎯 BLOCK FOUND height(le)=17402037 … hash=26082c2d3a25ee5822ae9350741db7969de865d5475e5ebc1517cb3300000000
23:38:50 [SUBMIT] durable candidate finalization unavailable hash=26082c2d…0000: submission conflict latch is set
```

`accepted_block_accounting_conflicts_v1`: `conflict_kind=applied_effect_drift`, both digests
`30e71335…5fc9`, `observed_at 2026-09-23 23:34:35.437308+00`. Before every submission the binary checks
`SELECT EXISTS (SELECT 1 FROM block_submission_conflicts_v1) OR EXISTS (SELECT 1 FROM accepted_block_accounting_conflicts_v1)`.

Stratum kept answering, shares kept being accepted, `pool_status` said `ok`. Over the next 5 h 20 min the pool
found **21 blocks and submitted none** (list below; gross reward per block in our accepted intents: 231.78–232.67 BDAG,
so roughly 4,870 BDAG), until we
noticed at 07:30 and recreated the database. **We could not determine what triggers the runtime reconciliation** —
it did not run (or did not fail) for the four identical-order-mismatch blocks at 23:03, 23:04, 23:17 and 23:22.

## Reproduce

1. Stack defaults (`postgres:15-bookworm`, en_US.utf8). Two miners whose checksummed addresses differ in case at the
   first differing character, e.g. `0xC8…` and `0xb3…`, both earning credit in one accepted block.
2. Restart the pool → `accepted accounting readiness failed: … legacy credits differ from intent`, every retry.
3. Same database copied into one created with `LC_COLLATE 'C' LC_CTYPE 'C'` → starts.

Check a database without running anything:
```sql
SELECT count(*) FILTER (WHERE a <> b) AS order_mismatch, count(*) AS blocks FROM (
  SELECT (SELECT array_agg(miner_address ORDER BY line_index) FROM accepted_block_accounting_credit_lines_v1 l WHERE l.candidate_id = i.candidate_id) a,
         (SELECT array_agg(miner_address ORDER BY miner_address, id) FROM credits c WHERE c.block_hash = encode(i.legacy_block_hash,'hex')) b
  FROM accepted_block_accounting_intents_v1 i) x;
```

## Suggested fix

Either of these makes the comparison collation-independent:

- `SELECT miner_address,amount::text FROM credits WHERE block_hash=$1 ORDER BY miner_address COLLATE "C", id`, or
- sort the legacy rows in Go with the same byte comparison used for the intent lines (or compare as a map keyed by
  address) before comparing.

And, separately: a false positive here should not be able to discard found blocks. A conflict row latches submission
permanently, with no operator path out except deleting the database; a found block is worth more than the accounting
check that stops it being sent. At minimum, submit and record, or expose a documented repair for the conflict.

**Workaround we are applying:** create the pool database with `POSTGRES_INITDB_ARGS="--encoding=UTF8 --lc-collate=C --lc-ctype=C"`
for the `pool-db` service. `sql/pool-schema.sql` applied to such a database gives a schema identical to the en_US one
(`pg_dump -s` equal).

## Available on request

`pg_dump` of the failing database (221 blocks, the conflict row), the scripts that ran the three tests, full pool log
from 23:30 to 07:40.

## The 21 blocks found and not submitted (UTC · height · hash)

```
2026-09-23 23:38:50  17402037  26082c2d3a25ee5822ae9350741db7969de865d5475e5ebc1517cb3300000000
2026-09-23 23:51:37  17402818  1fcc7b0836eed0ee1a803b2be1cfdd3acd704987044342f2077dc81f01000000
2026-09-24 00:13:44  17404141  c16293b620b191d8d250438969bafd65c7ef8c2680b6639caddfd61300000000
2026-09-24 00:19:46  17404492  ed907b0f951040e76adccda648757dbd7cf68432cb125023827f1e5c01000000
2026-09-24 00:36:31  17405477  6bb597e403c0a4ed93f16a481bde3f8531742a1eecd599825c2cf32e01000000
2026-09-24 00:38:24  17405613  fc001184f14ca70e25af44b8b4e7a024d03f40c1c38f6f7dbf18eda200000000
2026-09-24 00:47:43  17406185  9a0b35d174e4e58cd259a4109bd0b68ab36fc6a294ce9cf071b8be2200000000
2026-09-24 01:32:31  17408808  86534eadd37e1480e202f604a6cd0f0a76b083674852e3ad3af8a24700000000
2026-09-24 01:34:36  17408932  c82e6bde755bbcd9dc053567a4595de5da31d612843b38ba33efe62000000000
2026-09-24 01:35:36  17409007  3e3b3685d96751f798675c8f661595bc672b25695fd5c082e5c45d7701000000
2026-09-24 01:43:09  17409497  57f9572a9814be737cfa530cbfd278c7282aa55eb38faad9d9d80f3b00000000
2026-09-24 02:14:12  17411336  f2547c63cace2147a0e005cfe555868fc332f75849fb5767c3d098de00000000
2026-09-24 02:32:28  17412432  b9873098cbdbe233b402feb78556fcdb5491d2def1dc27a822941ac400000000
2026-09-24 02:35:06  17412568  7932c85a557cbf88dbf20af7d679e6b8d6b9d539ff38d83c4499c72a01000000
2026-09-24 02:53:22  17413667  1e82c94f881cba9ba2c21dd48c68c6cf3d20a53f0527f991d9461a2101000000
2026-09-24 03:18:46  17415182  380dbd0670b99c65bbfbf7338eac81806740190b40f11a4639756a9200000000
2026-09-24 03:37:07  17416283  9294506b66afc176193dd7eb16af56a50643dbd52784b9848fd1d94d01000000
2026-09-24 03:37:58  17416342  e1f49fb6ca97941c311fbbf9b0b72951fe71b5c7b72cb58b9e27882700000000
2026-09-24 03:56:57  17417479  3e5cc1ba9d4a94a18013f49901ccfb87a14127984c12b408bf5f969200000000
2026-09-24 04:54:31  17420929  f9d21f514a0b8080818fc450cdf5d88710eec91facc9951f3b2ae43601000000
2026-09-24 04:54:41  17420938  9ebc69cff4bc8b2fec4b2add75b358dc3533b31cc01948f8520508e400000000
```
