Require real proof of work in headers and link every advance to the tip (B-74, ELC-11, ELC-12, ELC-22, ELC-23)
A header only had to meet the target it declared for itself, so a fabricated one with `bits = 0x207fffff` passed half the time, a jump of several blocks was accepted without being linked to the tip (a fake header at a huge height locked `tip_height` and froze every draw), and an ordinary one-block reorganisation closed the session on every server. - draw.py: MAINNET_POW_LIMIT (00000000ffff...ffff); header_meets_its_own_target also refuses a claimed target easier than POW_LIMIT. - ElectrumListener._apply_header is async and takes the active client. A single block must chain by prev_hash; a jump downloads the headers in between (blockchain.block.headers, at most 2016 per request, ElectrumClient.get_block_headers) and each must carry valid work and chain, otherwise HeaderValidationError ends the session. - The listener keeps the last 10 headers (`_window`), built from the process's first header and its 9 predecessors (which must chain into it). A header that does not join the tip is walked back to its fork point inside the window and the server's branch is followed instead of ending the session; a fork older than the window, or a branch that does not end at the announced header, still ends it. The audit row and the pause for deep reorgs are B-85; a competing header at the tip's own height is still ignored until then. - _header_at also serves older heights from the window. Tests changed because the spec changed: the mainnet powLimit cannot be mined in a test, so tests/unit/conftest.py swaps in a regtest limit at import time (the new tests put the real one back); the _apply_header tests are async; the two _run_once tests serve the first header's 9 predecessors; test_apply_header_skips_linkage_check_across_a_height_gap is replaced by the jump tests, since that behaviour is exactly what the finding removed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a3c51719bd
commit
ca50ef62f3
16 files changed
+494
-159
No files matched your search
@@ -151,10 +151,6 @@ flowchart TB
|
||||
F -- "Non trovato" --> X
|
||||
```
|
||||
|
||||
*Comportamento atteso.* Oggi un salto di più blocchi non viene collegato, la
|
||||
difficoltà non è confrontata con il powLimit, e un header che non si aggancia
|
||||
chiude la sessione.
|
||||
|
||||
Tre casi vengono semplicemente **ignorati**, con un avviso nei log e senza
|
||||
chiudere la sessione, perché non indicano un server ostile:
|
||||
|
||||
@@ -197,11 +193,6 @@ passare a un altro server.
|
||||
([ELC-15]).
|
||||
*Codice:* `app/rounds/draw.py:header_meets_its_own_target`, `app/electrum/listener.py:ElectrumListener._apply_header` ·
|
||||
*Test:* `tests/unit/test_draw.py`, `tests/unit/test_electrum_listener.py`
|
||||
*Stato:* da implementare (B-74), per il confronto con il powLimit ·
|
||||
*Oggi:* si verifica solo che l'hash rispetti il bersaglio che l'header
|
||||
stesso dichiara. Poiché è chi fabbrica l'header a scegliere `bits`, il
|
||||
controllo non respinge un header mai minato: con `bits = 0x207fffff`
|
||||
(bersaglio `0x7fffff · 2²³²`, appena sotto 2²⁵⁵) lo passa un header casuale con probabilità 1/2.
|
||||
- **[ELC-12]** **Ogni** avanzamento della punta deve essere collegato alla
|
||||
punta corrente. Un avanzamento di un blocco deve avere come `prev_hash`
|
||||
l'hash della punta. Per un avanzamento di più blocchi, dalla punta `T` a
|
||||
@@ -213,16 +204,8 @@ passare a un altro server.
|
||||
(`T+1`, o `H` stesso per un avanzamento di un blocco) a non agganciarsi
|
||||
alla punta, non si chiude la sessione: è una possibile riorganizzazione e
|
||||
si segue [ELC-23].
|
||||
*Codice:* `app/rounds/draw.py:header_prev_hash`, `app/electrum/listener.py:ElectrumListener._apply_header`, `app/electrum/client.py:ElectrumClient.request` ·
|
||||
*Codice:* `app/rounds/draw.py:header_prev_hash`, `app/electrum/listener.py:ElectrumListener._apply_header`, `app/electrum/listener.py:ElectrumListener._fetch_headers`, `app/electrum/client.py:ElectrumClient.get_block_headers` ·
|
||||
*Test:* `tests/unit/test_draw.py`, `tests/unit/test_electrum_listener.py`
|
||||
*Stato:* da implementare (B-74) · *Oggi:* il collegamento si verifica solo
|
||||
per un avanzamento di un blocco; un salto di più blocchi è accettato con il
|
||||
solo controllo di difficoltà. Un avanzamento di un blocco che non si
|
||||
aggancia chiude la sessione: per questo una normale riorganizzazione di un
|
||||
blocco (la nostra punta `N` diventa orfana) fa chiudere la sessione su
|
||||
**ogni** server — ciascuno annuncia `N+1` sul nuovo ramo, che non si
|
||||
aggancia alla nostra `N` — finché l'arrivo di `N+2` non rende l'avanzamento
|
||||
un salto di due blocchi, accettato senza controllo di collegamento.
|
||||
- **[ELC-13]** Un header accettato aggiorna insieme altezza e contenuto della
|
||||
punta (mai uno senza l'altro) e la finestra di [ELC-22]. Ogni notifica di
|
||||
nuovi header invia una notifica SSE, perché è proprio ciò che l'estrazione
|
||||
@@ -236,9 +219,8 @@ passare a un altro server.
|
||||
superare [ELC-11] ed essere collegati fra loro e al primo, altrimenti la
|
||||
sessione si chiude. La finestra sopravvive alle riconnessioni, non ai
|
||||
riavvii.
|
||||
*Codice:* `app/electrum/listener.py:ElectrumListener._apply_header`, `app/electrum/listener.py:ElectrumListener._run_once` · *Test:* —
|
||||
*Stato:* da implementare (B-85) · *Oggi:* si tiene solo la punta
|
||||
(`tip_height`, `tip_header_hex`).
|
||||
*Codice:* `app/electrum/listener.py:ElectrumListener._apply_header`, `app/electrum/listener.py:ElectrumListener._adopt_first_header`, `app/electrum/listener.py:ElectrumListener._extend_window` ·
|
||||
*Test:* `tests/unit/test_electrum_listener.py`
|
||||
|
||||
### Riorganizzazioni
|
||||
|
||||
@@ -252,9 +234,11 @@ passare a un altro server.
|
||||
il punto di biforcazione e il nuovo header diventa la punta. Se non lo
|
||||
trova entro 10 header (una biforcazione più profonda, o un server su
|
||||
un'altra catena), la sessione si chiude come per [ELC-11].
|
||||
*Codice:* `app/electrum/listener.py:ElectrumListener._apply_header` · *Test:* —
|
||||
*Stato:* da implementare (B-74, B-85) · *Oggi:* nessuna riorganizzazione è
|
||||
rilevata né seguita; vedi l'*Oggi* di [ELC-12].
|
||||
*Codice:* `app/electrum/listener.py:ElectrumListener._follow_reorg` ·
|
||||
*Test:* `tests/unit/test_electrum_listener.py`
|
||||
*Stato:* da implementare (B-85), per l'header con hash diverso alla stessa
|
||||
altezza della punta · *Oggi:* un header concorrente alla stessa altezza è
|
||||
ignorato ([ELC-09]); gli altri casi sono seguiti.
|
||||
- **[ELC-24]** Ogni riorganizzazione seguita scrive l'evento
|
||||
`chain_reorg_detected` con la profondità (numero dei nostri blocchi
|
||||
sostituiti, cioè vecchia altezza della punta meno altezza del punto di
|
||||
|
||||
Reference in new issue
Block a user