# стейт-финализация (план имплементации)
## Intro
Изначально, блок создаваемый криейтором, является контейнером транзакций и стейт для него не вычисляется.
Хэш блока высчитывается по "базовому набору полей" (определенных в момент создания блока) и используется как идентификатор блока.
> Базовые поля:
ParentHashes
Slot
Height
Coinbase
Time
Extra
TxHash
GasLimit
LFNumber - номер последнего финализированного блока на момент создания
LFHash - стейт-хэш последнего финализированного блока на момент создания
В процессе финализации вычисляется стейт блока и определяются "поля состояния" и сохраняются в заголовок блока
> Поля состояния
BaseFee
ReceiptHash
Number
GasUsed
Bloom
Root
Стейт-хэш - результат хэширования всех полей заголовка, используется для контроля целостности цепи и валидации пропагейт-блоков. Стейт-хэш последнего финализированного блока сетится в новые блоки при создании.
## Implementation
### Block structure
1. ~~Удалить поля~~
````
MixDigest common.Hash `json:"mixHash"`
FinNumber *uint64 `json:"finNumber" gencodec:"required"`
FinRoot common.Hash `json:"finStateRoot" gencodec:"required"`
FinReceiptHash common.Hash `json:"finReceiptsRoot" gencodec:"required"`
````
1. ~~Добавить новые поля в заголовок блока~~
````
LFNumber uint64 `json:"lfNumber" gencodec:"required"`
LFHash common.Hash `json:"lfHash" gencodec:"required"`
````
1. ~~Исправить маршалинг в соответствии с изменениями~~ `/home/mezin/go/src/gwat/core/types/gen_header_json.go`
1. ~~Исправить КРУД в соответствии с изменениями~~ `core/rawdb/accessors_chain.go`
- Number - не сохраняем в хидер, используем текущую логику (связано с обеспечением доступа к блокам во фризере).
- Обратьть внимание, что везде где вызываются функции записи и удаления Number, скорее всего должно происходить запись удаление стейт-полей соответственно. Оттрекать и проанализировать вызовы `func WriteFinalizedHashNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64)` и `func DeleteFinalizedHashNumber(db ethdb.KeyValueWriter, hash common.Hash, number uint64)`, добавить подзадачи.
1. ~~Пройтись по коду проверить вспомогательные структуры типа~~ `type headerMarshaling struct`,
1. ~~Проверить в апи, реализации протоколов, и js(web3.js, перегенерировать бинд-код)~~
Итоговая структура блока:
````go
type Header struct {
ParentHashes common.HashArray `json:"parentHashes" gencodec:"required"`
Slot uint64 `json:"slot" gencodec:"required"`
Height uint64 `json:"height" gencodec:"required"`
LFNumber *uint64 `json:"lfNumber" gencodec:"required"`
LFHash common.Hash `json:"lfStateRoot" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
Time uint64 `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
Bloom Bloom `json:"logsBloom" rlp:"optional"`
GasUsed uint64 `json:"gasUsed" rlp:"optional"`
Root common.Hash `json:"stateRoot" rlp:"optional"`
ReceiptHash common.Hash `json:"receiptsRoot" rlp:"optional"`
// not saving to header due to supporting access to block in freezer (ro-db)
Number *uint64 `json:"number" rlp:"optional"`
}
````
### Block functionality
1. ~~Исправить/добавить/удалить функционал Хидера/Блока соответствующий изменениям структуры~~
1. ~~Функция хэша `func (h *Header) Hash() common.Hash` - сетить нуль-значения для всех стейт полей по аналогии с Number~~
2. ~~Добавить блоку и хидеру функцию `func (h *Header) FinalizedHash() common.Hash`, которая будет высчитывать хэш с учетом и стейт-полей. Если блок не финализирован - возвращает `common.Hash{}`.~~
3. Создание нового блока `NewBlock` уделить особое внимание
### Block creation
1. ~~__ParentHashes__ - при установке использовать сортировку asc~~
1. ~~__LFNumber__ - номер последнего финализированного блока~~
1. ~~__LFHash__ - вызов `FinalizedHash()` последнего финализированного блока~~
1. При добавлении транзакций высчитывать для каждой тх кол-во требуемого газа и добавлять в общую сумму `cumutativeGas`, используя функции
- `IntrinsicGas`
- `RunPrecompiledContract` (тут надо посмотреть насколько это затратно по ресурсам и времени) в качестве параметра `suppliedGas` использовать разницу `GasLimit - cumutativeGas`
- Если газ закончился - больше не добавляем тх
- логика аналогична расчету `GasUsed`
1. Убрать весь функционал вычисления стейта (выбор стейта, рекоммит транзакций, силинг, етц.)
1. Реализовать функцию сохранения блока без стейта (взять текущую и выпилить все что связанно со стейтом)
### Block propagation
1. Новый функционал валидации
1. ~~мог ли валидатор создавать этот блок~~
2. ~~мог ли валидатор включить эти транзакции в этот блок (назначение адресов)~~
3. ~~проверка по газ-лимиту(под вопросом)~~
4. проверка правильности значений LFNumber и LFHash (берм блок по LFNumber, высчитываем `FinalizedHash()` - должен быть равен LFHash)
5. ~~Проверяем наличие парентов~~
- если нет - запросить у подключенных нод (запуск синхронизации) и запустить добавление блока по новой;
- если есть - проверить спайн-сортировку парентов
6. ~~Проверка парентов~~
- ParentHashes не может быть пустым
- слоты парентов должны быть меньше слота блока
- Высоты парентов должны быть меньше высоты блока (возможно реализовать точную проверку высоты)
- проверка "parent-ancestor" парентов (G{slot:0}<-A{slot:1, parents:[G]}<-B{slot:2, parents:[**G**A]} : **G** - parent-ancestor, т.е. ссылка на блок, который уже есть в прошлом парентов)
7. __Проверка нонсов транзакций__ (надо продумать)
1. Если блок невалидный - отбросить. Отбросить так, чтобы сеть и нода не завалились и продолжили работу в штатном режиме (проанализировать поведение этериума в подобных ситуацих, предложить решение)
1. Убрать весь функционал вычисления стейта (выбор стейта, рекоммит транзакций, силинг, етц.)
1. Реализовать функцию сохранения блока без стейта (скорее всего таже самая что и при [создании блока](https://hackmd.io/hk6NZnAsS-yRudunJpqKtA?both#Block-creation))
### Block finalization
1. Проводим вычисление стейта для каждого блока в порядке финализации
- за основу используем стейт блока в LFNumber
- накатываем блоки в порядке финализации на базе спайнов
- после вычисления стейта сетим стейт-поля и сохраняем блок
### Misc
1. ~~Сейчас неправильно выполняется расчетет значений `GasUsed` и соответствующих значений в ресипте. Скорее всего из-за сквозного расчета при рекоммите транзакций. Проверить/исправить это при расчете стейта (накатывание тх и создание ресиптов)~~.
2. Убрать рекурсивные вызовы построения дерева предков при штатном режиме работы.
- ~~везде где создается новый BlockDag (криейт, пропагейт, синхронизация дага) проверить возможность корректного заполнения `DagChainHashes`, т.е. наличие всех предков до финализированного блока (достигается конкатенацией и юником этих полей у парентов + фильтрация нефинализированных блоков)~~
- ~~изучить все места обновления `BlockDag` (`ReviseTips()`) вполне возможно, что нам это в большинстве случаев не нужно.~~
- ~~Наверное в `BlockDag.LastFinalizedHash` и `BlockDag.LastFinalizedHeight` можем сетить значения `LFHash` и `LFNumber` соответственно, и в течении всего времени они не меняются и использование построения графа предков излишне~~
- оставить использование рекурсивного функционала при загрузке ноды, при обнаружении незагруженных предков и прочих особых случаех, где это оправдано.
- Построение цепочек спайн-финализации тоже кажется можно реализовать на базе `DagChainHashes`
3. ~~Переделать метод `syncWithPeerUnknownDagBlocks` синхронизация только даг части при одинаковой финализации. Там старая схема ордеринга и построения типсов, сделать по аналогии с `syncWithPeerDagChain`, но надо быть внимательным, могут быть "подводные камни"~~