# Синхронизация gwat-beacon network
## Intro
Решаемая задача сделать синхронизуцию узла связки gwat-beacon более отказоустойчивой.
Достигается за счет разбиения процесса синхранизации на этапы:
1. __Чекпоинт-синхронизация__ Автономная синхронизация gwat и beacon до состояния последнего чекпоинта. Выполняются паралельно и не предусматриваент взаимодействия. Gwat и Beacon ноды соединяется с удаленными пирами и запускют процесс синхронизации финализированной цепи и синхронизируется до последнего чекпоинта.
1. __Даг-синхронизация__ Загрузка блоков следующих после последнего чекпоинта. Тоже выполняется паралельно.
1. __Хед-синхронизация__ предполагает два этапа
1. Переход в режим готовности хед-синхронизации. Нода связки, которая первой завершила синхронизацию, ждет вторую. В это время принимает и с соответствующими ограничениями обрабатывает входящие блоки из своей сети. Бикон в этом режиме переодически шлет апи запросы в живат о готовности.
1. Выполнение хед-синхронизации. Как только Бикон по апи получает сигнал готовности живат - запускает хед-синхронизацию. Бикон отправляет в живат данные по финализации вплоть до последнего рабочего сотояния.
1. Переход в рабочий режим.
> __последний чекпоинт__ - __`current_justified`__
>
> В общем виде слот `current_justified` блока по отношению к текущему хеду при нормальной работе можно определить как
> (`<текущая эпоха> - 2`)*SlotsPerEpoch + 1
>
> бикон при загрузке с существующей бд устанавливает этот чекпоинт как хед цепи.
>
> __Note:__ расчет эпохи по слоту `floor(slot/SlotsPerEpoch)`
## GWAT
### Чекпоинт-синхронизация
Особых изменений не требуется, текущей реализации __синхронизации финализированной цепи__ достаточно.
В качестве опциональной доработки можно сделать:
1. Прекращение финализации блоков при достижении последнего чекпоинта
### Даг-синхронизация
Особых изменений не требуется, текущей реализации __синхронизации даг цепи__ вполне хватает.
По доработкам сделать:
1. ~~Добавить дополнительное состояние даг-синхронизации.~~
> в настоящий момент состояние синхронизации определяется только для __синхронизации финализированной цепи__ (метод Synchronising, статистика, апи), при синхронизации дага IsSync==false, нода параллельно работает в штатном режиме (обрабатывает пропагейт etc.). Такое положение дел вызывает ряд сложностей, в частности потенциальнию возможность криейта блоков, запроса кандидатов и финализации, что в связке с биконом может привести к нарушению работы сети.
1. ~~По аналогии методом `func (d *Downloader) Synchronising() bool` добавить метод `DagSynchronising`, который возвращает true на время даг-синка~~
2. ~~Посмотреть апи по синхронизации и реализовать аналогичное для даг-синка или дополнить существующее~~
3. ~~Блокировать работу даг-апи (sync, getCandidates, finalize) при даг-синхронизации. Возвращайть ошибку как и при обычной синхронизации (проверить)~~
4. ~~Проверить/реализовать корректное создание BlockDag для загружаемых блоков и заполнение типсов (по аналогии с пропагейтом блоков)~~
1. ~~Проверить параллельную работу пропагейта блоков при даг-синке~~
1. ~~Валидация загружаемых блоков идентично этому [Block propagation](https://hackmd.io/hk6NZnAsS-yRudunJpqKtA?both#Block-propagation)~~
### Хед-синхронизация
#### Режим готовности хед-синхронизации
1. ~~Добавить дополнительное состояние хед-синхронизации.~~
1. По аналогии методом `func (d *Downloader) Synchronising() bool` добавить метод `HeadSynchronising`, который возвращает true на время хед-синка
2. Посмотреть апи по синхронизации и реализовать аналогичное для хед-синка или дополнить существующее
3. Блокировать работу даг-апи (sync, getCandidates, finalize) при хед-синхронизации. Возвращайть ошибку как и при обычной синхронизации (проверить)
1. ~~Добавить даг-апи для сигнализации о готовности синхронизации от бикона Бикона `dag_headSyncReady` (активно при хед-синке)~~
1. __params:__ { checkpoint: ConsensusInfo } // Slot, Creators, Finalizing
1. __response:__ success bool or error
1. __handling:--
1. получить блок последнего кандидата
1. провалидировать как спайн:
- проверить финализацию
- равенство номера и высоты
- стейт (получить по руту)
1. если есть финализированные блоки после чекпоинта - перестроить даг-цепь (либо провалидировать на этапе выполнеия хед-синка, что проще или оптимальнее)
- собрать все финализированные блоки с номером больше чекпоинта
- очистить данные финализации
- создать BlockDag
- заполнить заново типсы
1. [Опционально] если долго нет запросов от бикона на апи хед-синхронизации, либо финализации, что приводит к росу дага, после заданного кол-ва слотов запускать процесс синхронизации с удаленными нодами.
#### ~~Выполнение хед-синхронизации~~
4. ~~Добавить даг-апи для получения данных от бикона Бикона~~ `dag_headSync`
1. __params:__ { data: []ConsensusInfo } //наверное разбивать на чанки смысла нет
1. __response:__ success bool or error
1. __handling:-- для каждого ConsensusInfo
- сохранить слот-криейтор
- собрать цепочки финализации для каждого спайна
- провалидировать
- если блок не финализирован выполнить финализацию (на эту тему см. `dag_headSyncReady` выше)
1. Разработать поведение при ошибке выполнения
- прерываем синхронизацию, возвращаем ошибку
### ~~Переход в рабочий режим~~
1. Сбросить все статусы синхронизаций. Должны заблокироватся все апи синхронизации и разблокироватся апи штатной работы
### func (d *Downloader) syncWithPeerUnknownDagBlocks
исправить
--------------------------------
## BEACON
> Note: в биконе есть `package sync` в `beacon-chain/sync/`:
> >Package sync includes all chain-synchronization logic for the beacon node, including gossip-sub validators for blocks, attestations, and other p2p messages, as well as ability to process and respond to block requests by peers.
>
> нас интересует подпакет `package initialsync` в `beacon-chain/sync/initial-sync`:
> > Package initialsync includes all initial block download and processing logic for the beacon node, using a round robin strategy and a finite-state-machine to handle edge-cases in a beacon node's sync status.
>
### Чекпоинт-синхронизация
Изменений не требуется в текущей реализации.
### Даг-синхронизация
Предварительно особых изменений не требуется.
1. Требуется ресерч по процедурам на момент завершения синхронизации для запуска хед-синка (там кажется есть таймер который проверяет синхронизацию/готовность gwat, там наверное нужна доработка)
`beacon-chain/sync/initial-sync/service.go:73`
````go
// Start the initial sync service.
func (s *Service) Start() {
...
if err := s.roundRobinSync(genesis); err != nil {
// main sync process ^^^^^^^^^^^^^^^^^^^^^^^
}
log.Infof("Synced up to slot %d", s.cfg.Chain.HeadSlot())
s.markSynced(genesis)
}
````
`beacon-chain/sync/initial-sync/round_robin.go:40`
````go
// Round Robin sync looks at the latest peer statuses and syncs up to the highest known epoch.
//
// Step 1 - Sync to finalized epoch.
// Sync with peers having the majority on best finalized epoch greater than node's head state.
//
// Step 2 - Sync to head from finalized epoch.
// Using enough peers (at least, MinimumSyncPeers*2, for example) obtain best non-finalized epoch,
// known to majority of the peers, and keep fetching blocks, up until that epoch is reached.
func (s *Service) roundRobinSync(genesis time.Time) error {
...
// Step 1 - Sync to end of finalized epoch.
if err := s.syncToFinalizedEpoch(ctx, genesis); err != nil {
return err
}
// Already at head, no need for 2nd phase.
if s.cfg.Chain.HeadSlot() == slots.Since(genesis) {
return nil
}
// Step 2 - sync to head from majority of peers (from no less than MinimumSyncPeers*2 peers)
// having the same world view on non-finalized epoch.
return s.syncToNonFinalizedEpoch(ctx, genesis)
}
// syncToFinalizedEpoch sync from head to best known finalized epoch.
func (s *Service) syncToFinalizedEpoch(ctx context.Context, genesis time.Time) error {
...
for data := range queue.fetchedData {
s.processFetchedData(ctx, genesis, s.cfg.Chain.HeadSlot(), data)
}
// Здесь выполняется подкключение к GWAT
/*
...
time="2022-09-23 20:14:29" level=info msg="Processing block batch of size 64 starting from 0x6fb24d00... 448/583 - estimated time remaining 4s" blocksPerSecond=28.8 peers=1 prefix=initial-sync
time="2022-09-23 20:14:30" level=info msg="Processing block batch of size 64 starting from 0x8d31a3d8... 512/584 - estimated time remaining 2s" blocksPerSecond=31.9 peers=1 prefix=initial-sync
time="2022-09-23 20:14:37" level=info msg="Connected to new endpoint: /***" prefix=powchain
time="2022-09-23 20:14:38" level=info msg="Synced to finalized epoch - now syncing blocks up to current head" currentSlot=586 prefix=initial-sync syncedSlot=575
time="2022-09-23 20:14:39" level=info msg="Processing block 0x0ca5017f... 576/586 - estimated time remaining 0s" blocksPerSecond=32.0 peers=1 prefix=initial-sync
*/
log.WithFields(logrus.Fields{
"syncedSlot": s.cfg.Chain.HeadSlot(),
"currentSlot": slots.Since(genesis),
}).Info("Synced to finalized epoch - now syncing blocks up to current head")
...
return nil
}
// syncToNonFinalizedEpoch sync from head to best known non-finalized epoch supported by majority
// of peers (no less than MinimumSyncPeers*2 peers).
func (s *Service) syncToNonFinalizedEpoch(ctx context.Context, genesis time.Time) error {
for data := range queue.fetchedData {
s.processFetchedDataRegSync(ctx, genesis, s.cfg.Chain.HeadSlot(), data)
}
// Тут уже работает стандартная обработка логов "onBlock" (beacon-chain/blockchain/process_block.go:91)
// "Calculation of finalization sequence" - лог из Спайнов
/*
...
time="2022-09-23 20:14:39" level=info msg="Calculation of finalization sequence" lastFinHash=0x028065daab563d75cedcd0abca879cf4afa4aa8072182e37cb49f79708daf7cd lastFinSlot=574 prefix=blockchain spines=[0xfd78e3d36a0b635072ef98a88a5ca46cad23764e4ffb181c90c166edf7507bd0 0x2c488a216172b279deb518893b7d5f195f7400e3f5a78b0e6fe9f06a2a58c83d]
time="2022-09-23 20:14:39" level=info msg="Synced new block" block=0x3bc8c603... epoch=18 finalizedEpoch=16 finalizedRoot=0x8d31a3d8... parentRoot=0x072a7efb... prefix=blockchain slot=586 slotInEpoch=10 version=altair
time="2022-09-23 20:14:39" level=info msg="Finished applying state transition" attestations=1 prefix=blockchain slot=586 syncBitsCount=512
time="2022-09-23 20:14:39" level=info msg="Synced to head of chain" currentSlot=586 prefix=initial-sync syncedSlot=586
time="2022-09-23 20:14:39" level=info msg="Synced up to slot 586" prefix=initial-sync
*/
log.WithFields(logrus.Fields{
"syncedSlot": s.cfg.Chain.HeadSlot(),
"currentSlot": slots.Since(genesis),
}).Info("Synced to head of chain")
if err := queue.stop(); err != nil {
log.WithError(err).Debug("Error stopping queue")
}
return nil
}
````
### Хед-синхронизация
1. ~~Добавить методы `ExecutionDagHeadSyncReady` и`ExecutionDagHeadSync` в `beacon-chain/powchain/dag_client.go`, которые вызывают gwat апи `dag_headSyncReady` и `dag_headSync` соответственно.~~
#### Режим готовности хед-синхронизации. запуск процедуры после завершения даг-синка:
1. Запуск цикла по таймеру (наверное 1 сек)
1. Подготовить параметры запроса `dag_headSyncReady`
- проверить соединение с gwat (`beacon-chain/powchain/service.go:335` `func (s *Service) IsConnectedToETH1() bool`), если нет - continue
- получить стейт/блок `current_justified` чекпоинта
- получить
- slot
- Eth1Data.finalizing (спайны финализации)
- Creators (пока не очень понятно как это реализовать, возможно можно вычислить. см. `func BeaconCommitteeFromState(ctx context.Context, state state.ReadOnlyBeaconState, slot types.Slot, committeeIndex types.CommitteeIndex) ([]types.ValidatorIndex, error)`, но вызов затратный, и надо проверить работает ли в прошлом. Текущая логика назначения криейторов тут `beacon-chain/blockchain/creators.go:27` `func (s *Service) GetCurrentCreators() ([]gwatCommon.Address, error)`. Еще как вариант сохранять в блок.)
- отправить запрос, получить ответ:
- fail - continue
- success - завершить процесс и переход к следующему этапу
#### Выполнение хед-синхронизации
1. Подготовить параметры запроса `dag_headSync`
- получить все стейты(как вариант блоки, но сейчас работаем по стейтам) от текущего слота до `current_justified` чекпоинта
- для каждого стейта получить
- slot
- Eth1Data.finalizing
- Creators
- отправить запрос, получить ответ:
- fail - надо понять что может быть не так, разработать обработку
- success - завершить процесс и переход в рабочий режим
### Переход в рабочий режим
1. Проверить переход к штатному режиму работы
### Misc
#### основная проверка синхронизированности бикона
`beacon-chain/sync/rpc_status.go:110`
````go
// shouldReSync returns true if the node is not syncing and falls behind two epochs.
func (s *Service) shouldReSync() bool {
syncedEpoch := slots.ToEpoch(s.cfg.chain.HeadSlot())
currentEpoch := slots.ToEpoch(s.cfg.chain.CurrentSlot())
prevEpoch := types.Epoch(0)
if currentEpoch > 1 {
prevEpoch = currentEpoch - 1
}
return s.cfg.initialSync != nil && !s.cfg.initialSync.Syncing() && syncedEpoch < prevEpoch
}
````