# Импорт доверенных 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= &offset= &size= &blockchain= ``` Дополнительно: ```text &channel= &message= ``` `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; ```