# SHiNE Archive Protocol v1.0 ## Полная спецификация серверной архивации SHiNE-блоков в Arweave и фиксации головы архива в Solana **Статус:** согласованная спецификация v1.0 **Язык:** русский **Magic:** `SHINE-ARCHIVE` **Версия протокола:** `1.0` **Endian:** big-endian для всех фиксированных целых чисел --- # 1. Назначение Этот документ описывает механизм, по которому SHiNE-сервер периодически: 1. синхронизирует обычные SHiNE-блоки существующим механизмом; 2. определяет новые локальные блоки, ещё не попавшие в архив; 3. фиксирует неизменяемую дельту; 4. группирует все новые записи одной `blockchain_name` в один блок пользователя внутри большого архивного блока; 5. добавляет к каждому такому пользовательскому блоку одну ссылку на предыдущий пользовательский блок этой же `blockchain_name`; 6. формирует один большой бинарный блок `SHINE-ARCHIVE`; 7. загружает его в Arweave; 8. ждёт закрепления/подтверждения Arweave-транзакции; 9. обычным обновлением своего существующего User PDA записывает новый archive head в Solana; 10. только после Solana `finalized` переводит локальные курсоры на новую точку. На первом этапе предполагается один архивный Raspberry-сервер, но формат изначально не должен запрещать появление других архиваторов. --- # 2. Основные понятия ## 2.1. Исходный SHiNE-блок / запись Это существующие в серверной БД оригинальные `block_bytes`, относящиеся к конкретной `blockchain_name`. Они уже содержат пользовательские данные и существующую криптографию SHiNE. Архиватор: - не изменяет их; - не переподписывает их; - не переводит их в Base64; - не пересобирает их содержимое. ## 2.2. `blockchain_name` В архивном формате основной идентификатор пользовательской цепочки — именно `blockchain_name`. Отдельно хранить `login` для пользовательского блока не требуется. `blockchain_name` уже однозначно идентифицирует конкретную пользовательскую blockchain и логически включает пользовательскую идентичность + номер/вариант цепочки. ## 2.3. Большой архивный блок Один файл/объект `SHINE-ARCHIVE`, который сервер формирует за один цикл архивной публикации. Он содержит: - header; - список предыдущих больших архивных блоков; - по одному `UserBlockchainChunk` для каждой `blockchain_name`, у которой есть новые записи; - footer; - SHA-256 большого блока; - подпись сервера, который этот большой блок сформировал и закрыл. Один большой архивный блок после публикации соответствует одной Arweave-транзакции. ## 2.4. `UserBlockchainChunk` В одном большом архивном блоке для одной `blockchain_name` существует максимум один такой chunk. Если за текущую дельту у пользователя/цепочки накопилось 1, 3, 5 или 20 записей, все они помещаются внутрь одного `UserBlockchainChunk`. Навигационная ссылка одна на весь chunk. --- # 3. Главная модель пользовательской истории Допустим `alice-001` имела записи: ```text BigBlock 70: AliceChunk = 2 записи BigBlock 81: AliceChunk = 5 записей BigBlock 95: AliceChunk = 3 записи BigBlock 100: AliceChunk = 4 записи ``` Тогда история `alice-001` выглядит: ```text BigBlock 100 AliceChunk 4 записи | +--> PreviousBlockchainChunkRef | v BigBlock 95 AliceChunk 3 записи | +--> PreviousBlockchainChunkRef | v BigBlock 81 AliceChunk 5 записей | v BigBlock 70 ``` Навигация идёт пачками записей одной blockchain, а не по одной записи за раз. --- # 4. Сервер сам добавляет навигацию Пользователь не обязан знать: - в какой большой архивный блок попадут его записи; - какой будет Arweave TX ID; - какие будут offsets; - где находится предыдущий chunk. Все эти данные сервер добавляет при формировании большого блока. Исходные подписанные пользовательские `block_bytes` остаются неизменными. `PreviousBlockchainChunkRef` является внешней серверной архивной метаинформацией и защищается hash + подписью всего большого архивного блока. --- # 5. Необходимые ключи архивного сервера Архивный Raspberry/server хранит: ## 5.1. SHiNE root private key Используется: - для подписания новой версии User PDA при обычном `update_user_pda`; - в v1.0 — для подписи закрытого большого архивного блока. ## 5.2. SHiNE client private key Используется как Solana transaction signer / fee payer в соответствии с текущей логикой `shine_users`. ## 5.3. Arweave JWK / private wallet key Используется для загрузки и оплаты Arweave-транзакции. ## 5.4. Blockchain private key Для архивной публикации не требуется. Архиватор не создаёт новые пользовательские blockchain-блоки. --- # 6. Периодичность Частота архивной публикации конфигурируется отдельно от существующей межсерверной синхронизации. Пример: ```properties archive.publish.enabled=true archive.publish.intervalMinutes=720 archive.publish.initialDelayMinutes=15 ``` `720` минут = раз в 12 часов. --- # 7. Состояние, относительно которого считается дельта Нужно различать: 1. уже окончательно опубликованное состояние; 2. текущий замороженный job, который ещё проходит Arweave/Solana. Нельзя считать дельту от живой головы во время уже запущенной публикации. --- # 8. Таблица `archive_chain_cursor` Хранит окончательно подтверждённое архивное состояние для каждой `blockchain_name`. ```sql CREATE TABLE archive_chain_cursor ( blockchain_name TEXT PRIMARY KEY, last_archived_source_block_number BIGINT NOT NULL, last_archived_source_block_hash BYTEA NOT NULL, last_archive_big_block_number BIGINT, last_archive_big_block_hash BYTEA, last_chunk_offset BIGINT, last_chunk_size BIGINT, updated_at_ms BIGINT NOT NULL ); ``` Назначение: - `last_archived_source_block_number` — до какого исходного SHiNE-блока этой blockchain всё успешно опубликовано; - `last_archived_source_block_hash` — hash этого исходного блока; - `last_archive_big_block_number` и `last_archive_big_block_hash` — в каком большом `SHINE-ARCHIVE` находится последний chunk этой blockchain; - `last_chunk_offset` и `last_chunk_size` — точное положение последнего chunk внутри большого блока. Этого достаточно, чтобы следующая публикация создала один backlink на предыдущий chunk. --- # 9. Отдельный индекс по каждой записи не нужен Поскольку все новые записи одной `blockchain_name` агрегируются в один `UserBlockchainChunk`, не нужно хранить offsets каждой отдельной записи в истории. Для следующей публикации достаточно помнить: ```text предыдущий big block предыдущий big block hash chunk offset chunk size ``` --- # 10. Таблица `archive_publish_job` Хранит crash-safe состояние одной архивной публикации. ```sql CREATE TABLE archive_publish_job ( id BIGSERIAL PRIMARY KEY, big_block_number BIGINT NOT NULL, status TEXT NOT NULL, created_at_ms BIGINT NOT NULL, local_archive_path TEXT, archive_hash BYTEA, arweave_tx_id BYTEA, arweave_confirmations INTEGER, solana_signature TEXT, error_text TEXT, updated_at_ms BIGINT NOT NULL ); ``` Рекомендуемые статусы: ```text SNAPSHOT_CREATED FILE_BUILT ARWEAVE_UPLOADED ARWEAVE_CONFIRMED SOLANA_SUBMITTED SOLANA_FINALIZED CURSORS_COMMITTED FAILED ``` Завершённые rows этой таблицы одновременно дают локальную историю больших блоков: ```text big_block_number archive_hash arweave_tx_id ``` По ним строится FULL-список предыдущих больших блоков следующей публикации. --- # 11. Таблица `archive_publish_job_chain` Хранит frozen range каждой blockchain в текущем job и координаты создаваемого chunk. ```sql CREATE TABLE archive_publish_job_chain ( job_id BIGINT NOT NULL, blockchain_name TEXT NOT NULL, from_source_block_number BIGINT NOT NULL, to_source_block_number BIGINT NOT NULL, previous_source_block_hash BYTEA NOT NULL, last_source_block_hash BYTEA NOT NULL, previous_archive_big_block_number BIGINT, previous_archive_big_block_hash BYTEA, previous_chunk_offset BIGINT, previous_chunk_size BIGINT, new_chunk_offset BIGINT, new_chunk_size BIGINT, PRIMARY KEY (job_id, blockchain_name) ); ``` --- # 12. Заморозка дельты Пример: ```text локально: alice-001 = source block 180 bob-001 = source block 94 опубликовано: alice-001 = 160 bob-001 = 90 ``` Новый job фиксирует: ```text alice-001: 161..180 bob-001: 91..94 ``` Если во время загрузки появятся: ```text alice-001 181..185 bob-001 95..97 ``` они попадут только в следующий job. --- # 13. Проверка курсора перед публикацией Перед созданием frozen range сервер проверяет: ```text local hash(last_archived_source_block_number) == archive_chain_cursor.last_archived_source_block_hash ``` При несовпадении публикация этой chain останавливается до resync/fork handling. --- # 14. Ограничение размера большого блока Offsets и sizes внутри `SHINE-ARCHIVE v1.0` используют `u32`. Поэтому один большой archive-файл MUST быть меньше `4 GiB`. Рекомендуемый запас: ```properties archive.maxFileBytes=4000000000 ``` Если данных больше, один scheduler-run формирует несколько последовательных больших блоков. --- # 15. Magic и версия Файл начинается ASCII: ```text SHINE-ARCHIVE ``` Размер: 13 байт. Далее: ```text u8 version_major u8 version_minor ``` Для v1.0: ```text 1 0 ``` --- # 16. Общий layout большого блока ```text +------------------------------------------+ | FIXED HEADER | +------------------------------------------+ | creator_login | +------------------------------------------+ | PREVIOUS BIG BLOCK REFERENCES | | ref[0] | | ref[1] | | ... | +------------------------------------------+ | UserBlockchainChunk #1 | | UserBlockchainChunk #2 | | ... | +------------------------------------------+ | FOOTER: closer_login | | block_hash | | signature | +------------------------------------------+ ``` --- # 17. Fixed Header v1.0 Все integers — big-endian. | Поле | Тип | Размер | |---|---:|---:| | magic | bytes[13] | 13 | | version_major | u8 | 1 | | version_minor | u8 | 1 | | header_size | u32 | 4 | | big_block_number | u32 | 4 | | created_at_ms | u64 | 8 | | creator_login_length | u8 | 1 | | references_mode | u8 | 1 | | references_count | u32 | 4 | | reference_entry_size | u16 | 2 | | parent_reference_index | u32 | 4 | | blockchain_chunks_count | u32 | 4 | | total_user_records_count | u32 | 4 | После fixed header немедленно идут `creator_login` bytes. `header_size` — byte offset первого `PreviousBigBlockReference`. --- # 18. `creator_login` SHiNE login пользователя/сервера, который сформировал большой блок. ```text u8 creator_login_length bytes[N] creator_login UTF-8 ``` Этот login находится в начале файла, поэтому его можно узнать без скачивания payload. --- # 19. `big_block_number` ```text u32 ``` Рекомендуемая нумерация: ```text genesis = 0 next = 1 next = 2 ... ``` --- # 20. `created_at_ms` ```text u64 ``` Unix Epoch UTC milliseconds. Это время заморозки содержимого big block. --- # 21. Режим ссылок на предыдущие большие блоки ```text references_mode u8 ``` Значения: ```text 0 = FULL 1 = PARTIAL, зарезервировано на будущее ``` Writer v1.0 MUST использовать `FULL`. --- # 22. FULL previous-block table Новый big block содержит ссылки на ВСЕ предыдущие большие блоки своей ветки. Пример: ```text BigBlock #365 References: #0 #1 #2 ... #364 ``` При одном big block в день через год таблица занимает примерно 24.8 KiB. --- # 23. `PreviousBigBlockReference` Фиксированный размер: 68 байт. | Поле | Тип | Размер | |---|---:|---:| | big_block_number | u32 | 4 | | big_block_hash | bytes[32] | 32 | | arweave_tx_id | bytes[32] | 32 | Семантика: ```text big_block_number = логический номер big_block_hash = криптографическая идентичность arweave_tx_id = где скачать блок ``` --- # 24. `references_count` Количество entries в таблице ссылок. ```text u32 ``` --- # 25. `reference_entry_size` ```text u16 ``` Для v1.0: ```text 68 ``` Поле оставлено для будущего расширения entry. --- # 26. `parent_reference_index` ```text u32 ``` Индекс непосредственного родителя внутри reference table. Для genesis: ```text 0xFFFFFFFF ``` В простой FULL-линейной ветке обычно это последний entry. --- # 27. Быстрое скачивание начала блока Клиент сначала скачивает fixed header + creator login. Из него узнаёт: ```text header_size references_count reference_entry_size ``` Затем скачивает только reference table и уже имеет адреса/hash всех предыдущих больших блоков. Payload пользователей можно пока не скачивать. --- # 28. `blockchain_chunks_count` ```text u32 ``` Количество `UserBlockchainChunk` в текущем big block. Одна `blockchain_name` встречается максимум один раз. --- # 29. `total_user_records_count` ```text u32 ``` Общее количество исходных SHiNE-записей во всех chunks. --- # 30. Порядок chunks и records Для детерминированной сериализации chunks SHOULD сортироваться по `blockchain_name ASC`. Внутри chunk исходные записи MUST идти по возрастанию номера исходного SHiNE-блока. --- # 31. Формат `UserBlockchainChunk` ```text u32 chunk_size u8 blockchain_name_length bytes[N] blockchain_name UTF-8 u32 records_count repeat records_count: u32 record_size bytes[M] raw_record_bytes u32 previous_big_block_ref u32 previous_chunk_offset u32 previous_chunk_size ``` --- # 32. `chunk_size` ```text u32 ``` Полный размер chunk от первого байта `chunk_size` до последнего байта `previous_chunk_size` включительно. Это позволяет: - быстро пропустить chunk; - скачать его Range-запросом; - сохранить его точные координаты для следующего backlink. --- # 33. `blockchain_name` внутри chunk Хранится только `blockchain_name`. Отдельный `login` не нужен. --- # 34. `records_count` ```text u32 ``` Если за период у `alice-001` появилось 7 записей: ```text records_count = 7 ``` Все 7 находятся в одном chunk. --- # 35. Raw records Каждая исходная запись сериализуется: ```text u32 record_size bytes[record_size] raw_record_bytes ``` `raw_record_bytes` — оригинальные данные SHiNE из БД. Сервер не меняет их содержимое. --- # 36. Один backlink на весь chunk После всех raw records находится ровно один `PreviousBlockchainChunkRef`. Он относится ко всей пачке записей blockchain. Он не повторяется у отдельных записей. --- # 37. `PreviousBlockchainChunkRef` | Поле | Тип | Размер | |---|---:|---:| | previous_big_block_ref | u32 | 4 | | previous_chunk_offset | u32 | 4 | | previous_chunk_size | u32 | 4 | Итого 12 байт на весь chunk. --- # 38. `previous_big_block_ref` Индекс в `PreviousBigBlockReferences[]` текущего big block. Он указывает на тот предыдущий big block, где находится предыдущий chunk этой же `blockchain_name`. Это именно reference index, а не номер блока. --- # 39. `previous_chunk_offset` ```text u32 ``` Byte offset от начала предыдущего `SHINE-ARCHIVE` до первого байта предыдущего chunk этой blockchain. Первый байт chunk — поле `chunk_size`. --- # 40. `previous_chunk_size` ```text u32 ``` Полный размер предыдущего chunk. Поэтому клиент может Range-запросом забрать сразу всю предыдущую пачку записей пользователя. --- # 41. Первая публикация blockchain Если у данной `blockchain_name` нет предыдущего архивного chunk: ```text previous_big_block_ref = 0xFFFFFFFF previous_chunk_offset = 0 previous_chunk_size = 0 ``` --- # 42. Пример backlink-цепочки В `BigBlock #100` есть `alice-001` chunk из 4 записей. Его backlink: ```text previous_big_block_ref = 94 previous_chunk_offset = 8123 previous_chunk_size = 1770 ``` `ref[94]` содержит: ```text big_block_number = 95 big_block_hash = ... arweave_tx_id = ... ``` Клиент: 1. получает TX big block #95; 2. проверяет hash; 3. делает Range на `8123 .. 8123+1770`; 4. получает весь предыдущий `alice-001` chunk; 5. читает его записи; 6. берёт следующий backlink. --- # 43. Почему TX ID не хранится в каждом chunk TX ID и hash предыдущих больших блоков уже один раз записаны в общей reference table. Поэтому в chunk достаточно компактного `u32 previous_big_block_ref`. --- # 44. Footer большого блока После последнего chunk: ```text u8 closer_login_length bytes[N] closer_login UTF-8 bytes[32] block_hash bytes[64] closer_signature ``` --- # 45. `closer_login` Login пользователя/сервера, который сформировал и закрыл big block. В v1.0: ```text closer_login MUST == creator_login ``` Login повторяется намеренно: - header даёт автора сразу; - footer явно фиксирует того, кто закрыл блок; - hash покрывает footer login; - verifier проверяет совпадение. --- # 46. `block_hash` ```text bytes[32] SHA-256 ``` Хэшируется всё от первого байта magic до последнего байта `closer_login` включительно. Не входят: ```text block_hash closer_signature ``` Псевдокод: ```text hash_input = fixed_header + creator_login + references + all UserBlockchainChunks + closer_login_length + closer_login block_hash = SHA256(hash_input) ``` --- # 47. `closer_signature` ```text bytes[64] Ed25519 ``` Подписывается: ```text ASCII("SHINE-ARCHIVE-V1") || block_hash ``` Для v1.0 используется root private key аккаунта `creator_login`. --- # 48. Проверка подписи Verifier: 1. читает `creator_login`; 2. проверяет `creator_login == closer_login`; 3. получает root public key аккаунта из User PDA / исторического состояния Solana; 4. пересчитывает `block_hash`; 5. проверяет Ed25519 signature. Старые архивы должны оставаться проверяемыми после ротации root key, поэтому исторические PDA-состояния нужно сохранять/уметь получать. --- # 49. Собственный Arweave TX ID текущего блока Текущий файл не может заранее знать свой будущий TX ID. Поэтому собственный TX ID: - не входит в сам текущий файл; - сохраняется после upload; - записывается в Solana PDA; - появляется как previous-block reference уже в следующем big block. --- # 50. User PDA block type `100` Archive head хранится прямо в существующем User PDA publisher-а. Формат: ```text u8 block_type = 100 u8 block_version = 0 bytes[32] archive_tx_id bytes[32] archive_hash ``` Итого 66 байт. `archive_hash` = `block_hash` последнего успешно опубликованного big block. --- # 51. Обновление Solana Отдельная новая Solana instruction не требуется. Используется существующий `update_user_pda`. Нужно расширить существующие serializer/parser/codec так, чтобы block type 100: - читался; - записывался; - сохранялся обычными обновлениями; - мог быть изменён архивным сервером обычным update User PDA. --- # 52. Полный порядок публикации ```text 1. Существующая server-to-server sync независимо обновляет локальные blockchains. 2. Scheduler запускает archive job. 3. Проверяется отсутствие другого активного archive job. 4. Читаются archive_chain_cursor. 5. Читаются текущие локальные blockchain heads. 6. Для каждой blockchain вычисляется frozen delta. 7. Создаётся archive_publish_job. 8. Создаются archive_publish_job_chain rows. 9. Загружается история всех успешно finalized previous big blocks. 10. Формируется fixed header. 11. Записывается creator_login. 12. Записывается FULL PreviousBigBlockReferences. 13. Для каждой blockchain_name: - читается frozen range; - создаётся ровно один UserBlockchainChunk; - внутрь кладутся все новые records; - добавляется один PreviousBlockchainChunkRef; - сохраняются new_chunk_offset/new_chunk_size. 14. Записывается closer_login. 15. Вычисляется SHA-256 block_hash. 16. Создаётся Ed25519 closer_signature. 17. Файл закрывается. 18. Файл загружается в Arweave. 19. TX ID сохраняется в archive_publish_job. 20. Сервер ждёт требуемое число Arweave confirmations. 21. Обычным update_user_pda обновляется block type 100: archive_tx_id archive_hash 22. Сервер ждёт Solana finalized. 23. В одной DB transaction обновляются archive_chain_cursor. 24. Job получает CURSORS_COMMITTED. ``` --- # 53. Cursor commit Для каждой blockchain из job: ```text last_archived_source_block_number = to_source_block_number last_archived_source_block_hash = last_source_block_hash last_archive_big_block_number = current big_block_number last_archive_big_block_hash = current block_hash last_chunk_offset = new_chunk_offset last_chunk_size = new_chunk_size ``` Следующая публикация сразу знает, куда должен вести backlink. --- # 54. Arweave service Старый `TestFreeAvatarArweaveService` больше не нужен как avatar-specific сервис. Его следует переделать/переименовать, например в: ```text ArweaveArchiveService ``` Он должен: - использовать Arweave JWK; - подписывать и оплачивать upload; - поддерживать большие/chunked uploads; - возвращать TX ID; - ждать подтверждения; - опрашивать confirmations. Рекомендуемые настройки: ```properties archive.arweave.gateway=https://arweave.net archive.arweave.walletJwkPath=/opt/shine/secrets/archive-wallet.json archive.arweave.minConfirmations=1 archive.arweave.confirmPollSeconds=30 archive.arweave.confirmTimeoutMinutes=180 ``` --- # 55. Solana finalization После обычного `update_user_pda` сервер должен дождаться: ```text finalized ``` Только после этого разрешается сдвинуть локальные курсоры. --- # 56. Crash recovery ## После `SNAPSHOT_CREATED` Возобновить тот же frozen job. ## После `FILE_BUILT` Использовать тот же локальный файл. ## После `ARWEAVE_UPLOADED` Не загружать второй раз. Продолжить по сохранённому TX ID. ## После `ARWEAVE_CONFIRMED` Продолжить Solana update. ## После `SOLANA_SUBMITTED` Сначала проверить transaction / текущий PDA. ## После `SOLANA_FINALIZED`, но до cursor commit Проверить: ```text PDA.archive_tx_id == expected_tx PDA.archive_hash == expected_hash ``` После этого безопасно выполнить cursor commit. --- # 57. Нет новых данных Если новых записей нет ни в одной blockchain: ```text не создавать пустой SHINE-ARCHIVE не загружать Arweave не обновлять Solana ``` --- # 58. FULL references как стартовая стратегия v1.0 сознательно хранит ВСЕ previous big blocks в каждом новом big block. Плюсы: - актуальный блок сразу даёт карту всей предыдущей истории; - не нужно идти parent-by-parent; - backlink может использовать компактный ref index; - reader очень простой. При одном big block в сутки: ```text 1 год: ~24.8 KiB 5 лет: ~121 KiB 20 лет: ~485 KiB ``` --- # 59. PARTIAL references `references_mode = 1` зарезервирован на будущее. v1.0 writer его не реализует. В будущем PARTIAL может означать: - только реально используемые previous blocks; - последние N blocks; - checkpoint references; - смешанную стратегию. --- # 60. Чтение истории одной blockchain Имея актуальный chunk `alice-001`, клиент: 1. читает все records chunk; 2. читает `PreviousBlockchainChunkRef`; 3. если ref = `0xFFFFFFFF`, история закончилась; 4. иначе берёт соответствующий `PreviousBigBlockReference`; 5. получает TX ID + hash previous big block; 6. Range-запросом читает `previous_chunk_offset/previous_chunk_size`; 7. получает сразу всю предыдущую пачку записей `alice-001`; 8. повторяет. Так можно читать историю конкретной blockchain, не скачивая чужие chunks. --- # 61. Дубликаты Если исходная SHiNE-запись уже есть у получателя, она пропускается/дедуплицируется стандартной логикой. --- # 62. Конфликты исходной blockchain Архивная ветка сама по себе не является пользовательским конфликтом. Конфликт возникает, когда одна `blockchain_name` имеет два несовместимых валидно подписанных продолжения одной предыдущей точки. Для v1 безопасно: ```text сохранить обе ветки пометить blockchain CONFLICTED остановить автоматическое продолжение ``` Механизм разрешения fork можно добавить позже. --- # 63. Авторство большого блока Автор указан дважды: ## Header ```text creator_login ``` ## Footer ```text closer_login block_hash closer_signature ``` Для v1.0: ```text creator_login == closer_login ``` Это означает: именно этот SHiNE-аккаунт сформировал и криптографически закрыл big block. --- # 64. Подпись сервера не заменяет подписи пользователей `closer_signature` доказывает авторство большого archive block. Каждая пользовательская raw record всё равно проверяется существующей криптографией SHiNE. --- # 65. Независимость от Arweave Arweave TX ID — location. SHA-256 — identity. Поэтому архивы можно перенести на другой storage и всё равно проверять. --- # 66. Block type 100 в Solana — текущая голова User PDA publisher-а хранит только: ```text current archive TX ID current archive SHA-256 ``` Детальная история находится внутри big blocks. --- # 67. Startup preflight Перед запуском archive publisher сервер SHOULD проверить: ```text derivePublic(root_private) == UserPDA.root_public_key ``` и: ```text derivePublic(client_private) == UserPDA.client_key ``` При несовпадении archive publisher не запускается. --- # 68. Рекомендуемые настройки ```properties archive.publish.enabled=false archive.publish.intervalMinutes=720 archive.publish.initialDelayMinutes=15 archive.maxFileBytes=4000000000 archive.arweave.gateway=https://arweave.net archive.arweave.walletJwkPath=/opt/shine/secrets/archive-wallet.json archive.arweave.minConfirmations=1 archive.arweave.confirmPollSeconds=30 archive.arweave.confirmTimeoutMinutes=180 archive.solana.rootKeyPath=/opt/shine/secrets/root.key archive.solana.clientKeyPath=/opt/shine/secrets/client.key archive.solana.commitment=finalized ``` --- # 69. Требование к `AppConfig` Archive settings должны читаться из: - Java system properties; - environment variables; - `application.properties`. `getInt()` и `getBoolean()` должны использовать общую логику `getParam()`. --- # 70. Что переиспользуется из текущего сервера Существующие механизмы: ```text BlockchainStateDAO.listAll() BlocksDAO.listRangeByNumber(...) ``` дают необходимые heads и raw block ranges. Используются существующие: ```text block_bytes block_hash block_signature blockchain_name block_number ``` Server-to-server sync в v1.0 не меняется. --- # 71. Блокировки Можно использовать существующий blockchain lock только на коротком этапе: ```text проверить cursor hash прочитать frozen range проверить конечный hash сохранить/сериализовать frozen data ``` Lock не удерживается во время Arweave/Solana ожиданий. --- # 72. Реализационный checklist ## Database - [ ] `archive_chain_cursor` - [ ] `archive_publish_job` - [ ] `archive_publish_job_chain` - [ ] schema migration - [ ] crash recovery ## Archive writer - [ ] magic `SHINE-ARCHIVE` - [ ] major/minor version - [ ] fixed header - [ ] creator login - [ ] FULL previous big block table - [ ] one chunk per `blockchain_name` - [ ] many raw records inside one chunk - [ ] one backlink per chunk - [ ] closer login - [ ] SHA-256 - [ ] Ed25519 signature - [ ] max file size < 4 GiB ## Arweave - [ ] rename/refactor `TestFreeAvatarArweaveService` - [ ] remove avatar-specific logic - [ ] large/chunked upload - [ ] confirmation polling ## Solana - [ ] add PDA block type `100` - [ ] update Rust codec - [ ] update Java codec - [ ] update JS codec/UI writer - [ ] use ordinary `update_user_pda` - [ ] server-side transaction writer - [ ] root signature - [ ] client fee payer - [ ] wait for `finalized` ## Scheduler - [ ] `archive.publish.enabled` - [ ] `archive.publish.intervalMinutes` - [ ] `archive.publish.initialDelayMinutes` - [ ] no concurrent jobs - [ ] split files > max size - [ ] skip when no new blocks --- # 73. Итоговая бинарная схема v1.0 ```text SHINE-ARCHIVE version_major u8 version_minor u8 header_size u32 big_block_number u32 created_at_ms u64 creator_login_length u8 references_mode u8 references_count u32 reference_entry_size u16 parent_reference_index u32 blockchain_chunks_count u32 total_user_records_count u32 creator_login bytes PreviousBigBlockReference[references_count]: big_block_number u32 big_block_hash [32] arweave_tx_id [32] UserBlockchainChunk[blockchain_chunks_count]: chunk_size u32 blockchain_name_length u8 blockchain_name bytes records_count u32 repeat records_count: record_size u32 raw_record_bytes previous_big_block_ref u32 previous_chunk_offset u32 previous_chunk_size u32 FOOTER: closer_login_length u8 closer_login bytes block_hash [32] closer_signature [64] ``` --- # 74. Основная архитектура одним рисунком ```text SOLANA USER PDA | | block type 100 | TX + HASH v BigBlock #100 | +-- Header | creator = server-A | +-- References | #0 -> BigBlock #0 / hash / TX | #1 -> BigBlock #1 / hash / TX | ... | #99 -> BigBlock #99 / hash / TX | +-- alice-001 chunk | records x4 | | | +--> BigBlock #95 / AliceChunk | +-- bob-001 chunk | records x2 | | | +--> BigBlock #98 / BobChunk | +-- kate-002 chunk | records x10 | | | +--> BigBlock #74 / KateChunk | +-- Footer closer = server-A SHA-256 Ed25519 signature ``` --- # 75. Итоговые решения v1.0 - один большой блок = одна архивная публикация; - в начале лежит полный список всех предыдущих больших блоков; - каждый previous block reference содержит `number + SHA-256 + Arweave TX ID`; - одна `blockchain_name` встречается в большом блоке максимум одним chunk; - все новые записи этой blockchain объединяются в этот chunk; - chunk имеет одну ссылку на предыдущий chunk этой же blockchain; - backlink состоит из `previous big block ref + chunk offset + chunk size`; - сервер сам создаёт backlink; - пользовательские raw records не меняются; - login сервера есть в header и footer; - большой блок закрывается SHA-256 + Ed25519 signature сервера; - Solana User PDA block `100` хранит текущий TX ID + archive hash; - PDA обновляется обычным `update_user_pda`; - Arweave используется как долговременное хранилище; - публикация считается завершённой только после Arweave confirmation + Solana finalized + cursor commit.