Files
SHiNE-server/docs/Archive/07_ARCHIVE_IMPORT_AND_VIEWER.md

304 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Импорт доверенных SHINE-ARCHIVE и Blockchain Viewer
Этот документ описывает вторую половину архивной системы: как обычный SHiNE-сервер узнаёт о новых archive head других серверов, кому доверяет, как импортирует недостающие SHiNE-блоки и как UI получает ссылку на историю конкретной `blockchain_name`.
## 1. Источник archive head
Archive importer **не делает отдельные Solana RPC-запросы**.
Уже существующий Solana Users Sync разбирает User PDA block type `100` и копирует его в локальную таблицу:
```text
solana_user_pda_current.archive_head_tx_id
solana_user_pda_current.archive_head_hash
```
Для локального состояния importer v23 добавляет туда же:
```text
archive_imported BOOLEAN
archive_last_imported_tx_id TEXT
```
Это локальные поля сервера, в Solana они не записываются.
Когда обычный Solana Users Sync видит тот же archive head повторно, `archive_imported` сохраняется как есть.
Когда `archive_head_tx_id` или `archive_head_hash` изменился:
```text
archive_imported = false
```
а `archive_last_imported_tx_id` сохраняет последнюю успешно обработанную точку и позволяет продолжить после сбоя.
## 2. Whitelist доверенных publisher-ов
В `application.properties` задаётся список логинов серверов, архивы которых разрешено принимать:
```properties
archive.import.allowedPublishers=archive-server-1,archive-server-2
archive.import.intervalMinutes=60
archive.import.workDir=data/archive-import
```
Правила:
- логины разделяются запятыми;
- сравнение без учёта регистра;
- пустое `archive.import.allowedPublishers=` полностью выключает importer;
- принимаются только строки `solana_user_pda_current` с `is_server=true`;
- whitelist является только первым фильтром, криптографические проверки всё равно обязательны.
## 3. Периодическая проверка
После запуска сервера importer делает первую проверку примерно через 10 секунд, затем по умолчанию раз в 60 минут.
Каждый цикл — дешёвый запрос только к локальной PostgreSQL:
```text
approved publisher
AND is_server=true
AND archive_head_tx_id != ''
AND archive_imported=false
```
Если таких строк нет, Arweave не вызывается.
## 4. Проверки archive block
Для каждого pending publisher сервер проверяет:
1. publisher находится в whitelist;
2. `archive_head_tx_id/archive_head_hash` уже пришли через обычный User PDA sync;
3. SHA-256 скачанного файла совпадает с `archive_head_hash`;
4. `creator_login == closer_login == publisher login`;
5. Ed25519 archive signature проверяется root key publisher-а из User PDA;
6. каждый вложенный raw SHiNE block проходит обычную SHiNE-проверку через существующий `AddBlock` path.
Подпись большого архива защищает контейнер и навигацию, а подписи обычных SHiNE blocks защищают сами пользовательские данные.
## 5. Как определяется, что head новый
Отдельная таблица обработанных TX для основной логики не нужна.
Текущий User PDA snapshot уже содержит:
```text
archive_head_tx_id
archive_head_hash
archive_imported
archive_last_imported_tx_id
```
Пример:
```text
archive_head_tx_id = TX100
archive_imported = false
archive_last_imported_tx_id = TX97
```
Это означает: Solana уже объявила `TX100` текущей головой publisher-а, но локальный сервер успел импортировать только до `TX97`.
После полной успешной обработки `TX100`:
```text
archive_imported = true
archive_last_imported_tx_id = TX100
```
При следующем новом PDA head Users Sync сам сбросит `archive_imported=false`.
## 6. Догон пропущенных больших блоков
Если сервер был выключен и вместо `TX97` сразу увидел `TX100`, он скачивает и проверяет `TX100`, читает его FULL reference table и находит `TX97`.
После этого импортирует только:
```text
TX98
TX99
TX100
```
После каждого полностью импортированного большого блока `archive_last_imported_tx_id` сдвигается вперёд.
Если сервер впервые видит publisher и `archive_last_imported_tx_id` пустой, импортируются все previous refs от старых к новым, затем текущий head.
Если непустой `archive_last_imported_tx_id` отсутствует в FULL history текущего head, importer останавливается: это рассматривается как возможная смена/fork archive chain, а не как повод молча забыть старый cursor.
## 7. Crash recovery
Если процесс упал после `TX98`, но до `TX100`:
```text
archive_imported = false
archive_last_imported_tx_id = TX98
```
Следующий часовой цикл продолжит с `TX99`.
Если процесс успел импортировать head и записать `archive_last_imported_tx_id = TX100`, но упал до установки `archive_imported=true`, следующий цикл просто завершит отметку без повторной загрузки всей цепочки.
Все cursor updates выполняются условно по ожидаемому `archive_head_tx_id`. Если обычный Solana Users Sync успел заменить head во время импорта, старый процесс не сможет пометить новый head импортированным.
## 8. Импорт `UserBlockchainChunk`
Для каждого chunk:
1. берётся lock этой `blockchain_name`;
2. если локального `blockchain_state` нет, identity создаётся по синхронизированному User PDA;
3. raw records разбираются как обычные `BchBlockEntry`;
4. уже существующий block допускается только при совпадении hash;
5. новый block должен идти строго `localLast + 1`;
6. новый block добавляется существующим validator/write path;
7. конфликт hash или gap останавливает импорт этой archive chain.
## 9. Индекс последнего archive chunk
Таблица:
```text
archive_blockchain_location
```
содержит для каждой `blockchain_name`:
```text
blockchain_name
publisher_login
arweave_tx_id
archive_hash
big_block_number
chunk_offset
chunk_size
source_last_block_number
updated_at_ms
```
Если blockchain встретилась в новом archive block, её location обновляется. Если не встретилась — старая ссылка остаётся.
Эту таблицу заполняют как trusted importer, так и локальный archive publisher.
## 10. API для UI
WS operation:
```text
GetArchiveBlockchainLocation
```
Request:
```json
{
"op": "GetArchiveBlockchainLocation",
"blockchainName": "alice-001"
}
```
Response содержит:
```text
blockchainName
publisherLogin
arweaveTxId
archiveHash
bigBlockNumber
chunkOffset
chunkSize
sourceLastBlockNumber
```
## 11. UI и ссылка Viewer
В настройках пользователя есть экран `Архив блокчейна`.
Viewer-файл:
```text
shine-UI/Blockchain-Viewer.html
```
Основные параметры ссылки:
```text
/Blockchain-Viewer.html
?tx=<ARWEAVE_TX_ID>
&offset=<CHUNK_OFFSET>
&size=<CHUNK_SIZE>
&blockchain=<BLOCKCHAIN_NAME>
```
Дополнительно:
```text
&channel=<CHANNEL_NAME>
&message=<BLOCK_NUMBER>
```
`blockchain` используется также для проверки: если загруженный chunk имеет другое имя blockchain, Viewer прекращает обработку.
`channel` открывает нужный канал, а `message` прокручивает к указанному сообщению/block number и выделяет его.
## 12. Как Viewer собирает всю цепочку
Viewer начинает с последнего `TX + offset + size`:
```text
последний UserBlockchainChunk
PreviousBlockchainChunkRef
FULL reference table текущего big block
TX предыдущего big block
Range предыдущего chunk
следующий backlink
до NO_REFERENCE
```
Чужие chunks скачивать не требуется.
## 13. Минимальная настройка принимающего сервера
```properties
archive.publish.enabled=false
archive.import.allowedPublishers=server-a,server-b
archive.import.intervalMinutes=60
archive.import.workDir=data/archive-import
```
Если импорт архивов не нужен:
```properties
archive.import.allowedPublishers=
```
Тогда importer вообще не запускается.
## 14. Диагностика PostgreSQL
Pending archive heads:
```sql
SELECT login, archive_head_tx_id, archive_imported, archive_last_imported_tx_id
FROM solana_user_pda_current
WHERE is_server = TRUE
AND archive_head_tx_id <> ''
ORDER BY login;
```
Последние известные пользовательские chunks:
```sql
SELECT blockchain_name, publisher_login, arweave_tx_id,
big_block_number, chunk_offset, chunk_size, source_last_block_number
FROM archive_blockchain_location
ORDER BY updated_at_ms DESC;
```