Pool-uri 18
Mineri N/A
Workeri N/A

< Toate articolele

Bug-ul KAWPOW din Ravencoin: un header de bloc care și-a ales singur verificarea proof of work

Un bloc Ravencoin minat la înălțimea 4.487.776 a scris o înălțime mai mică în propriul header. Pentru că acel număr era sub ultimul checkpoint de la 2.383.550, validarea a avut încredere în mix_hash-ul aflat deja în header, în loc să ruleze verificarea completă, intensivă în memorie.

Ravencoin și-a petrecut a doua săptămână din august dând chain-ul înapoi cu aproximativ trei zile. Cauza nu a fost un cartel de mining, o vulnerabilitate de wallet sau o funcție hash defectă. A fost un singur câmp din header-ul blocului, pe care nodul îl credea pentru a decide care dintre două verificări proof of work să ruleze. Fiecare fragment de cod citat mai jos este citit din sursa de consens a Ravencoin, nu dintr-un anunț, iar acolo unde anunțurile spun ceva ce sursa nu arată, acest articol o spune în loc să ghicească.

Ce conține un header KAWPOW

KAWPOW este proof of work-ul Ravencoin, o variantă ProgPoW la care chain-ul a trecut în mai 2020 pentru a păstra moneda pe GPU-uri obișnuite. Această trecere a schimbat header-ul blocului. Înainte de momentul activării, header-ul se termină cu nonce-ul obișnuit pe 32 de biți pe care îl folosește orice chain derivat din Bitcoin. După el, serializatorul scrie în acel loc trei câmpuri, în această ordine: nHeight, apoi nNonce64, apoi mix_hash.

Două dintre ele nu au nimic deosebit. nNonce64 este pur și simplu un nonce mai lat. mix_hash este rezultatul intermediar al părții memory hard a algoritmului, purtat în header pentru ca un verificator să aibă o valoare de comparat, nu un calcul de repetat de la zero. Al treilea este cel interesant. nHeight este blocul care declară, în propriul header, la ce înălțime crede că se află.

Poziția reală a unui bloc în chain nu este o chestiune de opinie. Este fixată de hashPrevBlock: blocul pe care construiți decide unde ajungeți și nu puteți minți despre asta și totuși să vă atașați la vârf. nHeight este o a doua copie, care poate fi scrisă, a aceluiași fapt, iar o copie a unui fapt valorează exact cât verificarea care o compară cu originalul.

Două funcții hash, dintre care una nu verifică nimic

Fișierul hash.cpp din Ravencoin definește două puncte de intrare KAWPOW, iar semnăturile lor spun cea mai mare parte a poveștii înainte să citiți vreo linie din corpul lor. Prima este uint256 KAWPOWHash(const CBlockHeader& blockHeader, uint256& mix_hash). A doua este uint256 KAWPOWHash_OnlyMix(const CBlockHeader& blockHeader).

Prima primește mix_hash ca parametru de ieșire, pentru că produce unul. Alege setul de date cu ethash::get_epoch_number(blockHeader.nHeight), construiește contextul acelei epoci dacă nu îl are deja și apoi rulează progpow::hash peste el. Mix-ul pe care îl întoarce este un produs secundar al faptului că munca a fost făcută. Aceasta este calea memory hard, iar caracterul memory hard este întregul motiv pentru care KAWPOW există, în loc de un hash simplu.

A doua nu primește un astfel de parametru, pentru că nu produce nimic. Consumă mix-ul aflat deja în header, prin progpow::hash_no_verify(blockHeader.nHeight, header_hash, to_hash256(blockHeader.mix_hash.GetHex()), blockHeader.nNonce64). Biblioteca upstream a numit această funcție hash_no_verify, iar numele este exact. Execută pasul final de amestecare peste o valoare primită și întoarce răspunsul. Nu construiește niciodată setul de date, deci nu stabilește niciodată că valoarea primită putea proveni doar din efectuarea muncii.

Niciuna dintre funcții nu este o greșeală. Cea ieftină există dintr-un motiv bun: un nod care sincronizează milioane de blocuri vechi, deja stabilite de un checkpoint scris în cod, nu ar trebui să reconstruiască un set de date de mai mulți gigaocteți pentru fiecare epocă ca să reconfirme un istoric pe care nu îl contestă nimeni. Singura întrebare la care trebuie să răspundă un astfel de design este ce blocuri au voie să o folosească.

Poarta și în ce a avut încredere

La această întrebare răspunde CheckBlockHeader din validation.cpp, iar comentariul de deasupra răspunsului spune intenția limpede: „If we are checking a KAWPOW block below a know checkpoint height. We can validate the proof of work using the mix_hash”. Intenția este corectă. Implementarea pune poarta pe block.nHeight <= (uint32_t)pcheckpoint->nHeight, iar block.nHeight este numărul pe care blocul l-a scris despre sine.

Linia de dedesubt apelează funcția generică CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&). Observați ce nu conține această semnătură. Nu există mix, nici nonce, nici header. Primește un hash gata calculat și o țintă și le compară, fără nicio cale de a ști care dintre cele două funcții a produs numărul primit. Pentru un bloc KAWPOW, CBlockHeader::GetHash() întoarce KAWPOWHash_OnlyMix(*this), deci numărul primit este cel ieftin.

Pus cap la cap, un bloc care declara un nHeight suficient de mic era validat întrebând dacă valoarea derivată din propriul mix hash era sub țintă. Nicăieri pe acest drum nu întreba nimic de unde provine mix-ul și nicăieri pe acest drum nu compara nimic înălțimea declarată cu poziția pe care blocul o ocupa de fapt.

Cât de mic este suficient de mic se dovedește a nu fi ceva subtil. Cel mai înalt checkpoint din parametrii chain-ului mainnet lansați este 2.383.550. Primul bloc falsificat se afla la 4.487.776. Header-ul lui trebuia să pretindă o poziție cu aproximativ 2,1 milioane de blocuri în urma celei reale, iar ramura pe care a ales-o astfel nu s-a uitat niciodată la diferență.

Ce a adus scurtătura și ce nu

Merită să fim preciși aici, pentru că majoritatea relatărilor au fost vagi, iar versiunea vagă face bug-ul să pară atât mai simplu, cât și mai ciudat decât este.

Scurtătura nu a scăzut dificultatea. nBits este validat față de ce cere blocul anterior real, iar blocul anterior real este ales prin hashPrevBlock, pe care un atacator nu îl poate falsifica și totuși să ajungă la vârf. Un bloc falsificat trebuia să treacă exact aceeași țintă ca un bloc onest minat în același minut.

Ce s-a schimbat este prețul unei singure încercări. Un miner onest plătește fiecare încercare cu o trecere completă peste un set de date măsurat în gigaocteți, ceea ce este tot rostul unui algoritm memory hard și motivul pentru care KAWPOW a ținut ASIC-urile departe de Ravencoin timp de șase ani. Scurtătura costă un hash al header-ului plus un pas final de amestecare peste o valoare aleasă liber de atacator. Aceeași loterie, aceleași șanse pe fiecare bilet, bilete mai ieftine cu ordine de mărime. 2Miners a descris rezultatele ca blocuri cu muncă aproape zero și, raportat la cât ar trebui să coste KAWPOW, este o descriere corectă.

Ce s-a întâmplat în rețea

Primul bloc invalid cunoscut este la înălțimea 4.487.776, cu marcajul de timp 2026-08-07 15:44:01 UTC. Exploatarea a continuat până la versiunea de urgență din 10 august. Pe o porțiune examinată de 2.089 de blocuri, 2Miners a numărat 96 de blocuri afectate, deci blocurile falsificate sunt împrăștiate prin ramură, nu grupate într-o singură serie, motiv pentru care remediul a trebuit să fie o reorganizare, nu o eliminare chirurgicală.

Pentru operatorii de noduri, simptomul vizibil a fost mai rău decât acceptarea discretă a unui bloc invalid. Nodurile se blocau pe aceste blocuri, iar după repornire nu mai sincronizau. Tron Black a descris de atunci acest mecanism și este un exemplu clar de efect colateral. Indexul de blocuri stochează înălțimea la care se află de fapt un bloc, nu înălțimea declarată de header. La repornire, nodul reconstruiește fiecare header din index și îi recalculează hash-ul, deci pentru un bloc falsificat face hash cu înălțimea reală acolo unde header-ul purtase una falsă. Hash-ul nu mai corespunde, verificarea proof of work eșuează, iar LoadBlockIndexGuts se oprește. Blocul a fost acceptat o dată și apoi nu a mai putut fi încărcat, motiv pentru care al doilea simptom a durat mai mult decât primul.

Mai multe exchange-uri au suspendat depunerile și retragerile de RVN cât timp ramura nu era rezolvată. Este reacția obișnuită și corectă la un chain al cărui istoric recent urmează să fie rescris și merită așteptată, nu citită ca un verdict asupra monedei.

Patch-ul și de unde a venit

Prima corecție a apărut pe 10 august ca versiunea 4.6.1.1-hf1 și a venit de la 2Miners, un pool de mining, nu de la proiectul Ravencoin. A oprit pierderile pentru oricine a instalat-o. A fost înlocuită o zi mai târziu.

Versiunea 4.8.0, publicată pe 11 august în repository-ul Ravencoin, este versiunea de rulat. Nu vă mulțumiți cu nimic mai vechi. Respinge blocurile a căror înălțime declarată în header nu corespunde poziției reale începând cu 4.487.776, fixează 4.487.775 ca ultimul bloc curat printr-un checkpoint, reconstruiește automat starea coruptă a chain-ului în loc să se oprească și include o corecție pentru un overflow la transferul de active pe care hotfix-ul de urgență nu o conținea, alături de alte lucrări upstream.

Doar prima dintre acestea este reparația. Respingeți un bloc a cărui înălțime declarată nu corespunde locului în care se află de fapt și poarta se închide. Checkpoint-ul și reconstruirea automată sunt recuperare, iar un checkpoint în special este un instrument grosier: este o afirmație scrisă în cod că un anumit bloc este cel corect, ceva de care un chain vrea să aibă nevoie cât mai rar. Este aici pentru că ramura onestă trebuia făcută stabilă mai repede decât ar fi făcut-o hashrate-ul singur.

Punctul de recuperare însuși a fost o decizie de judecată, nu una evidentă. Tron Black a scris că a rugat pool-urile să ia în considerare o tăietură mai recentă, pentru a face reorganizarea mai puțin adâncă și a trata blocurile falsificate ca excepții. Ele au refuzat și au ales linia cea mai curată posibilă. Într-un sistem proof of work, această dispută se rezolvă singură: când majoritatea hashrate-ului onest refuză un istoric, acel istoric pierde, iar cele mai mari două pool-uri aveau împreună majoritatea.

Ce înseamnă de fapt un reorg de trei zile

Pentru că nodurile vulnerabile au acceptat blocurile falsificate, ramura care le conținea a acumulat deasupra un chain real. Renunțarea la ea înseamnă o reorganizare, iar la ținta Ravencoin de un minut pe bloc, porțiunea de la 4.487.776 până la patch reprezintă aproximativ trei zile de blocuri.

Dacă dețineți RVN, sensul practic este că tranzacțiile confirmate pe ramura abandonată nu mai sunt confirmate. Majoritatea revin în mempool și sunt minate din nou pe chain-ul care rămâne, dar acest lucru nu este garantat, iar o categorie dintre ele nu poate reveni deloc. O tranzacție care a cheltuit coinbase-ul unui bloc abandonat cheltuie monede care nu mai există pe chain-ul rămas, la fel ca orice este construit peste ea. Acestea sunt pur și simplu invalide, nu în așteptare. De aceea suspendările de la exchange-uri au fost decizia corectă, nu o reacție exagerată, și de aceea activitatea de după 4.487.775 merita prudență până la stabilirea unui singur vârf.

Un detaliu merită știut dacă ați văzut un nod refuzând să avanseze în acea săptămână. Nodurile Ravencoin au o regulă împotriva reorg-urilor lungi, care există pentru a opri un atac surpriză al unei majorități ce rescrie un istoric adânc. Majoritatea nodurilor vechi care rulau continuu au scăzut în cele din urmă sub numărul de peeri de care depinde acea regulă și apoi au acceptat chain-ul mai lung, așa cum era prevăzut. Câteva nu au făcut-o, pentru că nu au pierdut niciodată destui peeri și pentru că, după regulile scrise, nimic nu fusese încălcat. Este o protecție care funcționează exact cum a fost specificată, pe o problemă pentru care nu a fost niciodată concepută. Ruptura a venit dintr-un defect de validare, nu dintr-un atac asupra majorității, iar regula nu le poate deosebi.

Ce să faceți dacă minați RVN

Dacă rulați propriul nod Ravencoin, rulați 4.8.0, și anume nu una dintre versiunile anterioare care au rezolvat doar o parte din problemă. Opriți nodul, înlocuiți binarul, porniți-l din nou. Nu există reindexare de rulat și niciun pas manual: software-ul reconstruiește singur ce are nevoie. Un nod care vine de la hotfix-ul de urgență repornește repede. Un nod care vine de la un software fără patch trebuie să reia și să reconstruiască, ceea ce durează câteva ore pe hardware obișnuit, iar erorile bad-blk-height din această perioadă înseamnă că patch-ul funcționează, nu că are vreun defect.

Dacă minați printr-un pool, nu aveți nimic de schimbat. Nicio adresă nouă, niciun port nou, nicio actualizare a minerului. KAWPOW însuși nu a fost niciodată problema aici: algoritmul a făcut exact ce a fost conceput să facă, iar defectul era în codul care decidea când să îl ruleze. Hashrate-ul pe care l-ați îndreptat spre ramura veche a fost muncă reală. Doar că a ajuns pe blocuri care au fost abandonate împreună cu cele falsificate împrăștiate printre ele.

Există o modalitate dintr-o singură linie de a confirma că ați ajuns pe chain-ul corect și merită folosită, nu presupusă. Verificați hash-ul blocului 4.494.143. Pe chain-ul canonic se termină în 4c67d6a4, iar exploratoarele Ravencoin independente arătau toate același chain când proiectul a declarat rețeaua recuperată.

Forma bug-ului

Dați la o parte detaliile și rămâne o formă care se repetă constant, în chain-uri și mult în afara lor. O valoare venită din exterior a fost folosită pentru a decide cât de atent va fi examinată chiar acea valoare. Header-ul își declara propria înălțime, iar nodul folosea această declarație pentru a alege ce verificare proof of work trebuia apoi să treacă header-ul.

Versiunea corectă nu este complicată, iar patch-ul este în esență o singură linie din ea: comparați înălțimea declarată cu poziția pe care o ocupă de fapt blocul înainte de a-l lăsa să decidă orice. De ce lipsea este jumătatea mai interesantă. Scurtătura a fost scrisă pentru blocuri deja de încredere, iar în cadrul acestei presupuneri poarta se citește ca o constatare, nu ca o întrebare la care cineva ar putea răspunde necinstit. Încetează să se citească așa în clipa în care unui bloc fără încredere i se permite să completeze câmpul, și nimic din cod nu a marcat momentul în care asta s-a schimbat.

Două lucruri despre modul în care a fost găsit merită consemnate. Codul Ravencoin a trecut prin audituri de securitate profesionale, iar acestea nu au prins nici acest bug, nici overflow-ul de active corectat împreună cu el. Tron Black a scris că suspectează puternic că ambele au fost scoase la iveală de o analiză de securitate făcută de un model AI de ultimă generație. Oricare ar fi uneltele, se potrivește cu forma: defectul este invizibil cât timp citiți scurtătura în propriii ei termeni și apare doar când vă întrebați cine poate completa câmpul în care are încredere scurtătura.

Ultimul lucru demn de observat este cine a acționat și în ce ordine. Un pool de mining a citit calea de validare, a înțeles-o și a livrat un binar de urgență în mai puțin de trei zile. Cele mai mari două pool-uri au acceptat apoi să mineze un chain care renunța la întreaga fereastră a atacului în loc să aleagă tăietura convenabilă, iar când li s-a cerut să o aleagă pe cea mai puțin adâncă, au refuzat. Proiectul a urmat o zi mai târziu cu versiunea care înlocuiește hotfix-ul și repară mai mult decât el. Care dintre ei a fost primul contează mai puțin decât faptul că ambele s-au întâmplat și că ceea ce a decis de fapt situația nu a fost niciun anunț, ci greutatea acumulată a muncii reale de o parte a rupturii. Așa arată descentralizarea într-o zi în care trebuie să ducă o greutate, nu să stea într-un whitepaper.

De unde provin aceste cifre

Ca oricine să poată verifica în loc să ne creadă pe cuvânt. Structura header-ului, cele două funcții hash și semnăturile lor, alegerea epocii și ramura de validare cu comentariul ei sunt citite din sursa Ravencoin, în src/primitives/block.h, src/primitives/block.cpp, src/hash.cpp, src/hash.h, src/pow.h și src/validation.cpp. Lista de checkpoint-uri, inclusiv 2.383.550 ca cel mai înalt de pe mainnet, este citită din src/chainparams.cpp. Prima înălțime invalidă și marcajul ei de timp provin din anunțul de rețea al proiectului Ravencoin. Calea de blocare a indexului de blocuri, tranzacțiile dependente de coinbase care nu pot reveni, comportamentul împotriva reorg-urilor lungi, punctul de recuperare mai puțin adânc refuzat, observațiile despre audit și analiza AI, conținutul versiunii 4.8.0 și verificarea blocului 4.494.143 provin din relatarea lui Tron Black pentru proiectul Ravencoin. Versiunea hotfix-ului de urgență și numărul de 96 de blocuri afectate din 2.089 provin din versiunea lansată de 2Miners și din relatările din jurul ei. Tot ce sursele nu afirmă a fost lăsat în afara acestui articol, nu completat.