Последният ъпгрейд на Starknet (v0.13.2), наречен Bolt, носи две големи промени: паралелно изпълнение и пакетиране на блокове . Въпреки че са независими една от друга, и двете функции поддържат стремежа към целта за бързо, евтино блоково пространство, криптографски защитено от Ethereum.
Паралелното изпълнение позволява неконфликтни транзакции (т.е. транзакции, които не засягат едно и също състояние) да се изпълняват едновременно. Чрез прилагане на паралелно изпълнение, L2s като Starknet могат да намалят времето за изпълнение, без да увеличават използването на ресурси. Това означава по-ниски такси за транзакции за потребителите и значително подобрени времена за потвърждение на транзакциите.
Пакетирането на блокове оптимизира използването на Starknet на blobspace на Ethereum L1: с пакетирането на блокове секвенсорите могат да генерират едно доказателство за проверка на множество блокове Starknet L2 едновременно. Това отделя използването на blobspace от честотата на производство на L2 блок и амортизира разходите за проверка на доказателство. И двете намаляват оперативните разходи за секвенсера Starknet, което означава, че потребителите плащат по-малко на транзакция.
Както казахме, Bolt прави Starknet по-евтин и по-бърз! Този доклад ще предостави подробен анализ на надстройката на Bolt – фокусирайки се върху паралелното изпълнение и пакетирането на блокове – и ще проучи последиците за производителността на Starknet.
Сборните пакети са решения за мащабиране на ниво две (L2) , които имат за цел да мащабират основната блокова верига на слой едно (L1) чрез преместване на изчисленията извън веригата. Чрез преместване на изпълнението извън веригата, сборните пакети могат да оптимизират за мащабируемост (евтини и бързи транзакции), докато L1 осигурява сигурност за L2 транзакции.
Често се казва, че сборните пакети „наследяват сигурността от L1“. Това по същество означава, че те наследяват гаранциите за консенсус и наличност на данни, предоставени от L1. В допълнение към това, L1 предоставя и гаранция за сигурност под формата на сигурен мост между него и сборния пакет.
Когато секвенсорите публикуват L2 блокове в L1, L1 предоставя гаранции за наличността, както и за подреждането на тази информация. Оттук L2 възлите могат безнадеждно да изчислят каноничната L2 верига с тази информация заедно с правилата на сбора около извеждането на веригата и прехода на състоянието, описани от изпълнението на възела.
За да се улесни сигурното свързване между L1 и L2, L1 изисква доказателство, че веригата L2, която следва в момента, е правилна и не включва незаконни промени в състоянието (напр. двойно изразходване). Тази необходимост от сборни пакети за доказване на валидността на промените в състоянието гарантира, че L1 не разрешава тегления от сборния пакет въз основа на незаконно състояние.
Сборните пакети се различават в зависимост от това как доказват валидността на промените в състоянието на L1:
Сборните пакети също предоставят на основния слой достатъчно данни за заинтересованите страни, за да реконструират състоянието L2. Докато оптимистичните сборни данни трябва да публикуват пълни данни за транзакциите, за да могат претендентите да изчислят доказателства за измами, сборните пакети за валидност нямат такива изисквания (доказателствата за валидност гарантират правилното изпълнение). Но публикуването на пълни данни за транзакциите на L1 все още е полезно от гледна точка на минимизиране на доверието (ненадеждна реконструкция на състояние и тегления без разрешение).
Starknet е сборен пакет за валидност, който използва S скалируем, T прозрачен AR gument of K knowledge (STARKs), за да докаже валидността на промените в състоянието. Най-новото надграждане на Starknet с кодово име Bolt добавя паралелно изпълнение и пакетиране на блокове. В следващите раздели ще обясним как работят двете функции и какви подобрения носят за потребителите на Starknet.
На високо ниво надстройката на Bolt промени механизмите за изпълнение, доказване и наличност на данни на Starknet.
Преди надграждането на Bolt транзакциите на Starknet се изпълняваха последователно – една след друга – от секвенсера. Последователното изпълнение е просто, но и много неефективно. Това е неефективно, защото не се възползва от множеството независими процесорни единици, които съвременните компютри предлагат, и възможността за паралелизиране на набор от транзакции.
Паралелността е мярка за това колко независими са транзакциите в даден набор. Например, разгледайте набора от три транзакции по-долу:
Транзакция 1: Алис иска да изпрати на Боб 1 STRK
Транзакция 2: Кейтлин иска да изпрати на Дани 100 ETH
Транзакция 3: Кейтлин иска да изпрати на Ела 100 ETH
Транзакция 1 е напълно независима от транзакции 2 и 3 – тъй като има достъп до различна част от състоянието (салдото на Алис) – и може да се изпълнява едновременно. Транзакция 2 и 3 обаче са в конфликт, защото искат достъп до едно и също състояние — ETH баланса на Кейтлин. Тези транзакции не могат да се изпълняват едновременно или в крайна сметка ще получим противоречиви резултати.
За да илюстрирам:
Избягването на тези видове конфликти (и сложната природа на механизмите за смекчаване) е причината Ethereum да избере последователно изпълнение. Въпреки това, докато последователното изпълнение намалява сложността и подобрява сигурността, то води до неефективно използване на хардуера. Още по-лошо, тенденцията в хардуерния дизайн предполага, че последователното изпълнение ще стане все по-неефективно през следващите години.
Фигура 4 показва тенденцията в дизайна на хардуера през последните 50 години. Съответният извод? Производителността на една нишка (лилави кръгове) е на плато от средата на 2000-те, докато броят на логическите ядра се е увеличил приблизително по същото време. Въз основа на тези данни можем да направим два извода:
Дизайнерите на хардуер мащабират компютърните чипове, като добавят повече независими процесорни единици, вместо да подобряват производителността на една единица.
Всяка система, която продължава да разчита на повишена производителност на единична процесорна единица, ще претърпи забавяне в повишаването на производителността дори на по-нов хардуер.
През последните години се появиха сложни алгоритми за управление на конфликти на транзакции и осигуряване на коректност на паралелното изпълнение. Block-STM (базиран на документ от Fikunmi et al*) е един такъв алгоритъм и формира основната част от новата машина за паралелно изпълнение на Starknet. Анализираме алгоритъма Block-STM в следващите раздели.
SHARP на Starknet (съкратено от Shared Prover) винаги се е възползвал от рекурсивните доказателства, за да намали разходите за проверка възможно най-много. Рекурсивното доказателство е по същество „доказателство за доказателство“, където едно доказателство потвърждава, че едно или повече доказателства са верни. По-долу е дадена скица на това как SHARP генерира рекурсивно доказателство:
Системата SHARP приема набор от програми за изпълнение („задача“) като вход и генерира доказателство за изпълнение за задачата. Тези „програми“ са L2 блокове и доказателството удостоверява коректността на транзакциите.
Доказателството се изпраща до друга програма, която проверява доказателството и преобразува програмата за проверка на доказателство в задание. SHARP приема новото задание като вход и генерира друго доказателство (това доказателство потвърждава валидността на предишното доказателство).
Процесът (доказателство → задание → доказателство) се рестартира и продължава до достигане на целта, в който момент окончателното доказателство (което сега е силно компресирана версия на оригиналното доказателство) се публикува в L1
Този дизайн значително амортизира разходите по две основни причини:
Въпреки че системата за доказване беше добра, бяха пропуснати шансове за допълнително спестяване на разходи. Например, всяко задание беше единичен блок на Starknet и всеки от тези блокове беше проектиран да заема един петно на L1. Това доведе до определени неефективности, както е описано по-долу:
Блоковото пакетиране се справя с тези проблеми чрез използване на двоично дърво от рекурсивни доказателства. Обсъждаме пакетирането на блокове в по-късен раздел на статията.
Както беше обсъдено по-рано, последователното изпълнение е неефективно (и ще бъде само по-неефективно с течение на времето), а простото паралелно изпълнение води до невалидни резултати. Машините за паралелно изпълнение на производството обаче се грижат да предотвратят непоследователни резултати.
Има два подхода за справяне с паралелното изпълнение: песимистичен контрол на едновременността (PCC) и оптимистичен контрол на едновременността (OCC) . PCC и OCC са единици за обработка на транзакции (TPU). По-долу е дадена дефиниция на модул за обработка на транзакции от Block-STM срещу SVM: Сравнение на машини за паралелно изпълнение:
TPU обикновено е свързан с виртуалната машина (VM), но е различен от нея. Blockchain VM като EVM, SVM и MoveVM са езикови VM от високо ниво… TPU, който обикновено е обект на интерес, включва VM. Той е натоварен с управлението на целия тръбопровод за изпълнение на транзакции, включително създаване и управление на екземпляри на VM.
Песимистичният контрол на паралелността е проектиран въз основа на предположението, че много от транзакциите в набора от транзакции, които трябва да бъдат изпълнени, ще бъдат в конфликт, т.е. те ще докоснат едно и също състояние. PCC предотвратява тези конфликти.
За да предотврати конфликти, PCC изисква транзакцията да декларира предварително до какви части от състоянието ще има достъп по време на операции за четене/запис. Модулът за обработка на транзакции може да използва тази информация, за да планира транзакции по начин, който гарантира, че конфликтните транзакции се изпълняват последователно (вместо едновременно). Някои TPU също използват ключалки, за да наложат това поведение (заключването (известно още като mutex) е механизъм, използван за предотвратяване на едновременен достъп до място в паметта).
Въпреки това изпълнението, базирано на PCC, води до определени компромиси. Първо, изискването за предоставяне на списъци за достъп (които идентифицират слота за памет, до който се докосва транзакцията) влошава опита на разработчиците и намалява обхвата на възможните приложения. Второ, планирането на транзакции може да доведе до ненужни разходи – особено когато няма конфликти.
Оптимистичният контрол на паралелността е проектиран с предположението, че много от транзакциите в дадения набор няма да са в конфликт, т.е. те няма да пишат в едно и също състояние. Като такива, OCC TPU изпълняват набора от транзакции с всички налични ресурси и се опитват само да открият конфликти. Ако бъде открит конфликт, транзакциите, които са в конфликт, се изпълняват и проверяват отново, докато целият набор премине и може да бъде ангажиран.
OCC TPU не поемат допълнителни разходи от планирането, така че те са склонни да работят по-добре, когато има малко конфликти. Базираните на OCC единици за обработка на транзакции също имат по-добър опит за разработчици и по-широк набор от случаи на употреба, тъй като не е необходимо зависимостите на състоянието да бъдат известни предварително.
Въпреки това, когато наборът от транзакции е много спорен, OCC се представя по-зле от PCC. Ние разглеждаме дизайна на TPU (много по-подробно) и сравняваме подходите на OCC и PCC в нашата статия за паралелно изпълнение.
Новият TPU на Starknet използва OCC подхода. По-конкретно, това е реализация на алгоритъма Block-STM. Block-STM изпълнява транзакциите оптимистично с всички налични ресурси, като приема, че нито един от тях няма да бъде в конфликт и проверява след изпълнението, че няма конфликтни транзакции, изпълнени едновременно. Преди да се потопим в новата архитектура на Starknet, е важно да прегледаме някои ключови дефиниции:
txj
е зависима от (или зависимост от) транзакция txi
тогава и само ако и двете транзакции записват в едно и също място в паметта и txj
идва след txi
в серийното подреждане. Ако txi
дойде след txj
, txi
ще зависи от txj
.След като дефинициите са премахнати, можем да преминем към това как работи Block-STM.
Входът към Block-STM е опашка (подреден списък) от транзакции, този списък често се нарича БЛОК. Този списък може да бъде подреден по всякакъв начин; единственото изискване е да има ясно определен ред. И така, даден набор от транзакции T, съдържащи транзакции {t0…tn}
, транзакциите са сортирани така, че {t0 > t1 > t2 … > tn}
(да се чете като t0
е с по-висок приоритет от t1
, t1
е с по-висок приоритет от t2
и т.н. .)
В началото на процеса на изпълнение се създават два комплекта – набор за изпълнение E и набор за валидиране V. E проследява транзакции, които тепърва ще бъдат изпълнени, докато V проследява транзакции, които са били изпълнени, но предстои да бъдат валидирани. Всяка транзакция също е свързана с номер на въплъщение n, за да се проследи колко пъти е била изпълнена (и повторно изпълнена). Първоначалното състояние на комплектите е, че E съдържа всички транзакции и V е празно, т.е. E = {t0,1 > t1,1 > t2,1 > … > tn,1}
и V = {}
.
С тези подредени набори от транзакции всяка нишка, използвана за изпълнение, преминава през триетапен цикъл:
По време на тази стъпка нишката проверява както V, така и E. Ако и двете са празни и не се изпълнява транзакция, тогава текущата партида транзакции е изпълнена напълно и резултатите могат да бъдат ангажирани за съхранение.
Ако V или E съдържа транзакции, Block-STM избира транзакцията с най-нисък индекс (не номер на въплъщение) от двата набора транзакции, т.е. ако E съдържа {t1,3 , t3,1 and t5,2}
и V съдържа {t0,1, t2,4, t4,3}
, задачата за валидиране за транзакция t0
ще бъде избрана като следваща задача.
След като бъде идентифицирана и избрана следващата задача, тя се изпълнява. В края на тази стъпка алгоритъмът се връща обратно към Check Done. Този процес продължава, докато и двата набора транзакции са празни.
Нека да разгледаме какво се случва по време на изпълнение и валидиране:
По време на изпълнение на транзакция алгоритъмът Block-STM попълва два набора (на транзакция); набор за четене ( Ri,n
) и набор за запис ( Wn,i
). Наборът за четене съдържа всички места в паметта, от които една транзакция е прочела по време на нейното изпълнение, докато наборът за запис съдържа всички места в паметта, в които е записала. По време на изпълнение транзакциите прилагат записите си към многоверсионната структура на данните, но четенето е малко нюансирано.
В Block-STM, когато транзакция иска да прочете от структурата на данните, тя проверява за стойността, записана от транзакцията с най-нисък приоритет, която има по-висок приоритет. Например, ако всички tx1
, tx2
и tx7
са записали в място в паметта и tx5
иска да чете от това място, той чете версията на структурата от данни, съответстваща на tx2
.
Това се прави, за да се наложи сериализация; тъй като tx5
трябва да се изпълнява след tx2
и преди tx7
, той трябва да използва стойностите, записани от tx2
а не tx7
. В този пример tx7
ще трябва да се изпълни отново, защото трябва да е прочел стойностите, записани от tx5
, а не от tx2
или други транзакции с по-висок приоритет. Ако се използва структура от данни с една версия, стойността, записана от tx2
ще бъде недостъпна и със сигурност ще възникне конфликт.
За задача за валидиране наборът за четене на транзакцията се сравнява с текущите стойности в местата на паметта, от които се чете по време на изпълнение. Например, ако tx2
чете акаунт B по време на изпълнение, по време на валидиране, местоположението на паметта за акаунт B се чете (като се има предвид определението за четене, което установихме по-рано). Ако двете стойности са еднакви, това означава, че нито една транзакция с по-висок приоритет (да речем tx0
или tx1
) не е записала на това място по време на изпълнението на tx2
. Това води до tx2
маркиран като валидиран, но не е безопасен за ангажиране.
Транзакцията не се счита за безопасна за ангажиране, тъй като транзакция с по-нисък приоритет може, поради различни причини, да бъде изпълнена, след като транзакцията е била валидирана. В нашия работещ пример, ако tx1
докосне акаунт B и го докосне само след това, tx2
преминава проверка, тогава tx2
трябва да се изпълни отново.
За да се предвиди това, всеки път, когато транзакция завърши изпълнението, всички транзакции с по-нисък приоритет, които са преминали валидиране, се проверяват повторно, за да се гарантира, че не са в конфликт с транзакцията. Например, ако tx1
, tx3
и tx4
са валидирани и tx2
завърши изпълнението, tx3
и tx4
трябва да бъдат повторно валидирани, за да се гарантира, че не са в конфликт с (както и зависимостите от) tx2
.
Ако транзакция не премине проверката, т.е. транзакция с по-висок приоритет, която записва в същото състояние, е била изпълнена едновременно с нея, тогава записите, направени от транзакцията, са мръсни (защото стойностите са грешни.) Но вместо да изтриете тези стойности от базата данни, напълно, те са маркирани с флаг ESTIMATE.
Флагът ESTIMATE казва на всяка транзакция, която чете това място в паметта, че стойностите там не са правилни и транзакциите спират изпълнението си. Това се прави вместо изтриване, тъй като повторното изпълнение на транзакцията, която не е преминала валидирането, вероятно ще доведе до запис в същите места в паметта като предишното изпълнение.
Чрез маркиране на местоположението в паметта като приблизителна стойност, вместо изтриването му, зависимостите (на транзакцията, която не е преминала валидирането) могат да бъдат уловени дори преди повторното изпълнение, предотвратявайки ненужни повторни изпълнение. Тази евристика значително намалява загубата на работа.
Пълният преглед на начина, по който Block-STM подхожда към паралелизирането, може да бъде обобщен като:
BLOCK
от транзакции започва като подреден списък с ясно дефиниран сериен ред. Тези транзакции се изпълняват на всички налични ресурси в приоритетен ред.
По-долу е показан пример:
Това е общ преглед на това как работи Block-STM, повече подробности можете да намерите в нашия доклад тук и в оригиналния документ на Block-STM тук .
За да определим количествено значението на добавянето на Block-STM, изпълнихме няколко сравнителни теста, за да оценим подобренията в производителността, които предоставя в сравнение с последователното изпълнение, и резултатите са показани по-долу.
Резултатите показват, че с увеличаване на броя на използваните нишки (аналогични на независими процесорни единици), производителността също се увеличава. Подобренията са по-изразени с по-големи блокове, които дават до 9 пъти подобрения на производителността в сравнение с последователно изпълнение само с 16 нишки. Установихме, че резултатите са по-изразени при по-големи блокове.
Нашите тестове показват, че производителността на Block-STM значително се влошава при силно оспорвано натоварване, но стандартната практика в индустрията е да се върне към последователно изпълнение през такива периоди. Препоръчваме същия механик на Starknet, за да запази пропускателната способност при много спорни натоварвания. Но като цяло добавянето на Block-STM значително ще подобри и ще подобри Starknet в бъдеще.
Втората основна промяна, включена в надстройката v0.13.2, е пакетирането на блокове и ще разгледаме това по-нататък.
Както беше обсъдено по-рано, преди Bolt, всеки блок Starknet беше собствена работа, което води до фиксирана цена на блок за всеки блок. В допълнение, системата е проектирана така, че всеки блок изисква свой собствен blob, независимо от това колко данни всъщност са били консумирани от блока.
В свят, в който винаги е имало голямо търсене, това не би било проблем, но Starknet в момента предлага повече блоково пространство, отколкото има търсене и затова има много пропилени ресурси, което може да доведе до загуба на стотици ETH в течение на един месец. За да поставим допълнително в контекста на сериозността на ситуацията преди Bolt, това са разходите, свързани с публикуването на блок на L1:
Това възлиза на 215k газ на блок и тази цена е равна, т.е. същата е независимо от това колко данни съдържа всеки блок и е свързана с броя на блоковете по $Cost = брой блокове * 215000$. Идеалното решение на този проблем би било разходите да бъдат свързани с количеството публикувани данни, вместо с количеството блокове. И точно това постига пакетирането на блокове чрез SNAR дървета.
Starknet Applicative Recursive (SNAR) дървета са нов тип двоично дърво, въведено в Bolt за справяне с проблемите, подчертани по-горе. Дървото на SNAR има следната структура: всеки лист е блок на Starknet, а възлите на всички други нива са рекурсивни доказателства за своите деца. Основният възел, който е рекурсивно доказателство за всички други доказателства, е окончателното задание, което се изпраща на SHARP Prover (SHARP).
Основното предимство на SNAR Tree е, че вместо да публикувате един блок на доказателство, много блокове на Starknet могат да бъдат амортизирани в една и съща L1 актуализация. Корените на дървото на SNAR вече се публикуват в L1 само когато бъде достигнато едно от двете конфигурируеми ограничения: или ограничението на DA (данни на стойност 6 петна) или след добавяне на определен брой листа към дървото (където листът е блок) .
Този дизайн отделя разходите за транзакции от броя на блоковете. Сега все още има някаква фиксирана цена за всеки блок, която възниква от стартирането на StarkNet OS (SNOS) във всеки блок – но като цяло разходите са отделени. Това са числата сега:
Графиката на фигура 11 по-долу показва как разходите за газ варират в зависимост от номера на блока в предишния дизайн и сега (под болт):
Съвсем очевидно е, че пакетирането на блокове значително намалява разходите за проверка на L1, което несъмнено ще доведе до по-ниски цени на бензина за потребителите на Starknet.
Ефектът от промените, направени в Bolt: оптимистично паралелно изпълнение чрез Block-STM и пакетиране на блокове чрез собствения SNAR може да се обобщи като: по-бързо и по-евтино.
Паралелното изпълнение намалява времето за изпълнение и чрез разширяване задръстванията, което ще намали таксите за газ по време на периоди на голям трафик, докато SNAR дърветата се справят със свързаните DA и разходи за доказване. Интересното е, че това надграждане прави Starknet първия L2 с паралелно изпълнение и го превръща в основен претендент в L2 пространството. Важно е да се отбележи, че е малко вероятно ефектът от тези промени да бъде незабавно очевиден, особено този от паралелното изпълнение, но те са от решаващо значение за бъдещата защита на Starknet и цялата екосистема на Ethereum като цяло.
Бележка на автора: Версия на тази статия беше публикувана преди това тук .