Ravencoin KAWPOW грешка: заглавље блока које је само бирало своју проверу доказа рада
Ravencoin је другу недељу августа провео враћајући свој ланац уназад за отприлике три дана. Узрок није био картел рудара, напад на новчаник нити покварена хеш функција. Било је то једно поље у заглављу блока коме је чвор веровао да му каже коју од две провере доказа рада да покрене. Сваки делић кода цитиран испод прочитан је из самог Ravencoin изворног кода консензуса, а не из саопштења, а где саопштења тврде нешто што изворни код не показује, овај чланак то каже уместо да нагађа.
Шта носи KAWPOW заглавље
KAWPOW је Ravencoin-ов доказ рада, варијанта ProgPoW на коју је ланац прешао у мају 2020. да би новчић остао на обичним GPU картицама. Тај прелазак је променио заглавље блока. Пре времена активације заглавље се завршава обичним 32-битним nonce-ом који користи сваки ланац изведен из Bitcoin-а. После њега серијализатор на то место уписује три поља, овим редом: nHeight, затим nNonce64, затим mix_hash.
Два од њих нису ништа посебно. nNonce64 је једноставно шири nonce. mix_hash је међурезултат меморијски захтевног дела алгоритма, који се носи у заглављу да би проверивач имао вредност са којом пореди, а не прорачун који мора да понови од нуле. Треће је занимљиво. nHeight је блок који у сопственом заглављу наводи на којој висини верује да се налази.
Стварни положај блока у ланцу није ствар мишљења. Одређује га hashPrevBlock: блок на који градите одлучује где ћете се наћи, и о томе не можете лагати, а да се и даље надовежете на врх. nHeight је друга копија исте чињенице у коју се може писати, а копија чињенице вреди тачно онолико колико провера која је пореди са оригиналом.
Две хеш функције, од којих једна ништа не проверава
Ravencoin датотека hash.cpp дефинише две KAWPOW улазне тачке, и њихови потписи носе већи део приче пре него што прочитате и један ред њихових тела. Прва је uint256 KAWPOWHash(const CBlockHeader& blockHeader, uint256& mix_hash). Друга је uint256 KAWPOWHash_OnlyMix(const CBlockHeader& blockHeader).
Прва прима mix_hash као излазни параметар, јер га производи. Скуп података бира са ethash::get_epoch_number(blockHeader.nHeight), гради контекст те епохе ако га већ нема, а затим над њим покреће progpow::hash. Mix који враћа је нуспроизвод обављеног посла. Ово је меморијски захтевна путања, а меморијска захтевност је цео разлог што KAWPOW постоји уместо обичног хеша.
Друга не прима такав параметар, јер ништа не производи. Она троши mix који већ стоји у заглављу, преко progpow::hash_no_verify(blockHeader.nHeight, header_hash, to_hash256(blockHeader.mix_hash.GetHex()), blockHeader.nNonce64). Изворна библиотека је ту функцију назвала hash_no_verify и име је тачно. Она обавља последњи корак мешања над вредношћу која јој је дата и враћа одговор. Никада не гради скуп података, па никада не утврђује да је вредност која јој је дата могла настати само обављањем посла.
Ниједна функција није грешка. Јефтина постоји с добрим разлогом: чвор који синхронизује милионе старих блокова, које је чврсто уписана контролна тачка већ потврдила, не би требало да за сваку епоху поново гради скуп података од више гигабајта да би поново потврдио историју коју нико не оспорава. Једино питање на које такав дизајн мора да одговори јесте који блокови смеју да је користе.
Капија, и чему је веровала
На то питање одговара CheckBlockHeader у validation.cpp, а коментар изнад одговора јасно наводи намеру: „If we are checking a KAWPOW block below a know checkpoint height. We can validate the proof of work using the mix_hash”. Намера је исправна. Имплементација пушта кроз капију по услову block.nHeight <= (uint32_t)pcheckpoint->nHeight, а block.nHeight је број који је блок уписао о себи.
Ред испод позива општу CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&). Погледајте шта тај потпис не садржи. Нема mix-а, нема nonce-а и нема заглавља. Прима готов хеш и циљ и пореди их, и нема начина да зна која је од две функције произвела број који је добила. За KAWPOW блок, CBlockHeader::GetHash() враћа KAWPOWHash_OnlyMix(*this), па је број који добија онај јефтини.
Када се све споји, блок који је навео довољно ниску nHeight проверен је питањем да ли је вредност изведена из његовог сопственог mix хеша испод циља. Нигде на тој путањи ништа није питало одакле mix потиче, и нигде на тој путањи ништа није поредило наведену висину са положајем који блок заиста заузима.
Колико је ниско довољно ниско испоставља се као нимало суптилно. Највиша контролна тачка у објављеним параметрима mainnet ланца је 2.383.550. Први фалсификовани блок налазио се на 4.487.776. Његово заглавље морало је да тврди положај око 2,1 милион блокова иза стварног, а грана коју је тиме изабрао никада није погледала разлику.
Шта је пречица донела, а шта није
Ту вреди бити прецизан, јер је већина извештавања о томе била непрецизна, а непрецизна верзија чини да грешка звучи и једноставније и чудније него што јесте.
Пречица није снизила тежину. nBits се проверава према ономе што захтева стварни претходни блок, а стварни претходни блок бира hashPrevBlock, који нападач не може да фалсификује, а да и даље стигне на врх. Фалсификовани блок морао је да пређе потпуно исти циљ као поштен блок рударен у истом минуту.
Променила се цена једног покушаја. Поштен рудар сваки покушај плаћа пуним пролазом кроз скуп података мерен гигабајтима, што је цела поента меморијски захтевног алгоритма и разлог што KAWPOW шест година држи ASIC уређаје даље од Ravencoin-а. Пречица кошта хеш заглавља плус један завршни корак мешања над вредношћу коју је нападач слободно изабрао. Иста лутрија, исте шансе по листићу, листићи јефтинији за неколико редова величине. 2Miners је резултате описао као блокове скоро без посла, и у поређењу са оним што KAWPOW треба да кошта, то је поштен опис.
Шта се догодило на мрежи
Први познати неважећи блок је на висини 4.487.776, са временском ознаком 2026-08-07 15:44:01 UTC. Искоришћавање је трајало до хитног издања 10. августа. У једном испитаном низу од 2.089 блокова, 2Miners је избројао 96 погођених, па су фалсификовани блокови расути кроз грану, а не скупљени у једном низу, због чега је лек морао бити реорганизација, а не хируршко уклањање.
За оператере чворова видљиви симптом био је гори од тихог прихватања неважећег блока. Чворови су се рушили на овим блоковима, а после поновног покретања престајали су да се синхронизују. Tron Black је од тада описао тај механизам, и то је уредан пример колатералне штете. Индекс блокова чува висину на којој блок заиста стоји, а не висину коју је заглавље навело. При поновном покретању чвор поново гради свако заглавље из индекса и поново рачуна његов хеш, па за фалсификовани блок хешира стварну висину тамо где је заглавље носило лажну. Хеш се више не поклапа, провера доказа рада пада, и LoadBlockIndexGuts прекида рад. Блок је једном прихваћен, а затим постао неучитљив, због чега је други симптом надживео први.
Неколико берзи је обуставило RVN уплате и исплате док грана није била разрешена. То је уобичајен и исправан одговор на ланац чија ће скорашња историја бити преписана, и вреди га очекивати, а не читати као пресуду о новчићу.
Закрпа, и одакле је дошла
Прва исправка изашла је 10. августа као верзија 4.6.1.1-hf1, и дошла је од 2Miners, рударског базена, а не од Ravencoin пројекта. Зауставила је штету за свакога ко ју је преузео. Дан касније заменила ју је нова.
Верзија 4.8.0, објављена 11. августа преко Ravencoin репозиторијума, јесте издање које треба покренути. Немојте се задовољити ничим ранијим. Она одбацује блокове чија наведена висина у заглављу не одговара њиховом стварном положају од 4.487.776 надаље, поставља контролну тачку на 4.487.775 као последњи чист блок, аутоматски поново гради оштећено стање ланца уместо да прекине рад, и укључује исправку прекорачења при преносу асета коју хитна закрпа није имала, уз други рад из изворног пројекта.
Само прва од тих ствари је поправка. Одбаците блок чија наведена висина не одговара месту где заиста стоји, и капија се затвара. Контролна тачка и аутоматска поновна изградња су опоравак, а контролна тачка је посебно грубо средство: то је чврсто уписана тврдња да је један одређени блок исправан, а тога ланац жели што мање може. Ту је зато што је поштену грану требало учврстити брже него што би то сама хеш стопа учинила.
Сама тачка опоравка била је ствар процене, а не очигледан избор. Tron Black је написао да је замолио базене да размотре скорији рез, да би реорганизација била плића, а фалсификовани блокови решени као изузеци. Они су одбили и изабрали најчистију могућу линију. У систему са доказом рада тај спор се сам решава: када већина поштене хеш стопе одбије неку историју, та историја губи, а два највећа базена заједно су имала већину.
Шта заправо значи реорганизација од три дана
Пошто су рањиви чворови прихватили фалсификоване блокове, грана која их садржи нагомилала је прави ланац изнад њих. Њено одбацивање значи реорганизацију, а уз Ravencoin циљ од једног минута по блоку, низ од 4.487.776 до закрпе износи отприлике три дана блокова.
Ако држите RVN, практично значење је да трансакције потврђене на одбаченој грани престају да буду потврђене. Већина се враћа у mempool и поново се рудари на преживелом ланцу, али то није загарантовано, а једна врста уопште не може да се врати. Трансакција која је трошила coinbase одбаченог блока троши новчиће који више не постоје на ланцу који преживљава, као и све што је изграђено на њој. Те трансакције су просто неважеће, а не на чекању. Зато су обуставе на берзама биле исправна одлука, а не претеривање, и зато је активност после 4.487.775 захтевала опрез док се не утврди један врх.
Један детаљ вреди знати ако сте те недеље гледали чвор који одбија да се помери. Ravencoin чворови имају правило против дугих реорганизација, које постоји да спречи да изненадни напад већине препише дубоку историју. Већина старих чворова који су стално радили на крају је пала испод броја пирова од ког то правило зависи и онда прихватила дужи ланац, како је и замишљено. Неколико није, јер никада нису изгубили довољно пирова и јер, по правилима како су написана, ништа није било прекршено. То је заштита која ради тачно како је прописано, на проблему за који никада није била намењена. Раскол је настао из пропуста у провери, а не из напада на већину, и правило те две ствари не може да разликује.
Шта да радите ако рударите RVN
Ако водите сопствени Ravencoin чвор, покрените 4.8.0, и то не неко од ранијих издања која су решила само део овога. Зауставите чвор, замените бинарну датотеку, поново га покрените. Нема reindex-а нити ручног корака: софтвер сам поново гради шта му треба. Чвор који долази са хитне закрпе брзо се поново покреће. Чвор који долази са незакрпљеног софтвера мора да понови и поново изгради стање, што на обичном хардверу траје неколико сати, а bad-blk-height грешке током тог периода значе да закрпа ради, а не да у њој постоји квар.
Ако рударите преко базена, на Вашој страни нема шта да се мења. Нема нове адресе, новог порта ни ажурирања програма за рударење. Сам KAWPOW овде никада није био проблем: алгоритам је радио тачно оно за шта је направљен, а грешка је била у коду који одлучује када да га покрене. Хеш стопа коју сте усмерили ка старој грани била је прави посао. Само је завршила на блоковима који су одбачени заједно са фалсификованим блоковима расутим међу њима.
Постоји начин у једном реду да потврдите да сте завршили на правом ланцу, и вреди то урадити уместо претпоставити. Проверите хеш блока 4.494.143. На каноничком ланцу он се завршава са 4c67d6a4, а сви независни Ravencoin прегледачи приказивали су тај исти ланац када је пројекат прогласио мрежу опорављеном.
Облик грешке
Када се уклоне појединости, остаје облик који се стално понавља, у ланцима и далеко ван њих. Вредност која је стигла споља коришћена је да се одлучи колико пажљиво ће се та иста вредност испитати. Заглавље је навело сопствену висину, а чвор је ту изјаву искористио да изабере коју ће проверу доказа рада заглавље затим морати да прође.
Исправна верзија није компликована, а закрпа је у суштини један њен ред: упоредите наведену висину са положајем који блок заиста заузима пре него што му дозволите да било шта одлучи. Зашто је недостајала, занимљивија је половина. Пречица је писана за блокове којима се већ верује, и унутар те претпоставке капија се чита као изјава чињенице, а не као питање на које би неко могао нечасно да одговори. Престаје тако да се чита оног тренутка када се неповерљивом блоку дозволи да попуни поље, а ништа у коду није обележило тренутак када се то променило.
Две ствари о томе како је откривена вреди забележити. Ravencoin је имао професионалне безбедносне ревизије свог кода, и оне нису ухватиле ни ово ни прекорачење код асета које је исправљено заједно с тим. Tron Black је написао да снажно сумња да је обе открио безбедносни преглед помоћу најнапреднијег AI модела. Какав год да је био алат, то се уклапа у облик: пропуст је невидљив док пречицу читате по њеним сопственим условима, а појављује се тек када питате ко сме да попуни поље коме пречица верује.
Последње што вреди приметити јесте ко се покренуо и којим редом. Један рударски базен је прочитао путању провере, разумео је и за мање од три дана објавио хитну бинарну датотеку. Два највећа базена су затим пристала да рударе ланац који одбацује цео прозор напада уместо да прихвате згоднији рез, а када су замољени да прихвате плићи, одбили су. Пројекат је дан касније објавио издање које замењује хитну закрпу и исправља више од ње. Ко је био први мање је важно од тога што се десило и једно и друго, и од тога што ствар на крају није решило ниједно саопштење, већ нагомилана тежина стварног посла на једној страни раскола. Тако изгледа децентрализација оног дана када мора да носи терет, а не да стоји у белој књизи.
Одакле потичу ови бројеви
Да би свако могао да провери, уместо да нам верује на реч. Распоред заглавља, две хеш функције и њихови потписи, избор епохе и грана провере са својим коментаром прочитани су из самог Ravencoin изворног кода, у src/primitives/block.h, src/primitives/block.cpp, src/hash.cpp, src/hash.h, src/pow.h и src/validation.cpp. Листа контролних тачака, укључујући 2.383.550 као највишу на mainnet-у, прочитана је из src/chainparams.cpp. Прва неважећа висина и њена временска ознака потичу из обавештења о мрежи самог Ravencoin пројекта. Путања рушења индекса блокова, трансакције зависне од coinbase-а које не могу да се врате, понашање правила против дугих реорганизација, одбијена плића тачка опоравка, напомене о ревизији и AI прегледу, садржај верзије 4.8.0 и провера блока 4.494.143 потичу из извештаја који је Tron Black написао за Ravencoin пројекат. Верзија хитне закрпе и број од 96 погођених блокова у 2.089 потичу из 2Miners издања и извештавања о њему. Све што извори не наводе изостављено је из овог чланка, а не попуњено.