# SHiNE Archive Protocol v1.0 ## Полная русская спецификация серверной фиксации больших блоков, Arweave, Solana User PDA и навигации по предыдущим записям пользователя **Статус:** рабочая спецификация v1.0 **Формат:** бинарный **Magic:** `SHINE-ARCHIVE` **Версия:** два отдельных байта `major` / `minor` --- # 1. Назначение Протокол предназначен для периодической долговременной фиксации новых SHiNE-блоков. Стартовая схема: ```text Основной SHiNE-сервер | | существующая межсерверная синхронизация v Raspberry / архивный SHiNE-сервер | +--> видит все новые локальные SHiNE-блоки +--> фиксирует стабильную дельту +--> собирает большой SHINE-ARCHIVE блок +--> загружает его в Arweave +--> ждёт закрепления Arweave-транзакции +--> обычным update своего User PDA записывает новый archive head ``` Существующую межсерверную синхронизацию в v1 менять не требуется. Архивный сервер не создаёт и не переподписывает пользовательские сообщения. Он берёт уже существующие пользовательские записи и добавляет только серверную навигационную метаинформацию большого блока. --- # 2. Основные принципы 1. Публикуется только новая дельта. 2. Дельта замораживается до начала загрузки. 3. Новые данные, пришедшие во время публикации, попадают в следующий большой блок. 4. После загрузки сервер ждёт закрепления Arweave-транзакции. 5. Только после закрепления Arweave сервер обновляет свой User PDA в Solana. 6. Только после `finalized` в Solana локальные курсоры считаются окончательно сдвинутыми. 7. Большой блок имеет собственный SHA-256 и подпись составившего его SHiNE-аккаунта. 8. Каждый большой блок содержит список предыдущих больших блоков с номером, hash и Arweave TX ID. 9. В v1 в начало каждого нового большого блока записывается полный список всех известных предыдущих больших блоков данной ветки. 10. Для каждого пользователя сервер строит обратную цепочку не на одну предыдущую запись, а на **предыдущий большой блок, где были записи этого пользователя, плюс список всех его записей внутри того блока**. --- # 3. Ключи архивного сервера Для первой реализации серверу требуются: - `root private key` SHiNE-аккаунта; - `client private key` SHiNE-аккаунта; - Arweave JWK/private wallet key. `blockchain private key` для самой архивной публикации не требуется: пользовательские записи уже существуют и уже подписаны. В v1 большой блок может подписываться root key того SHiNE-аккаунта, который его сформировал. --- # 4. Локальное состояние дельты Нужно различать: 1. уже окончательно опубликованное состояние; 2. текущую замороженную дельту, которая ещё проходит Arweave/Solana. Рекомендуются три таблицы. ## 4.1. `archive_chain_cursor` ```sql CREATE TABLE archive_chain_cursor ( blockchain_name TEXT PRIMARY KEY, last_archived_block_number BIGINT NOT NULL, last_archived_block_hash BYTEA NOT NULL, updated_at_ms BIGINT NOT NULL ); ``` Она хранит точку, до которой конкретная локальная blockchain уже окончательно вошла в опубликованный и зафиксированный archive head. ## 4.2. `archive_publish_job` ```sql CREATE TABLE archive_publish_job ( id BIGSERIAL PRIMARY KEY, archive_number BIGINT NOT NULL, status TEXT NOT NULL, parent_arweave_tx_id BYTEA, parent_archive_hash BYTEA, 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 ``` ## 4.3. `archive_publish_job_chain` ```sql CREATE TABLE archive_publish_job_chain ( job_id BIGINT NOT NULL, blockchain_name TEXT NOT NULL, from_block_number BIGINT NOT NULL, to_block_number BIGINT NOT NULL, previous_block_hash BYTEA NOT NULL, last_block_hash BYTEA NOT NULL, PRIMARY KEY (job_id, blockchain_name) ); ``` Сырые блоки в staging-таблицы копировать не нужно. В job фиксируются только точные диапазоны и крайние hash. --- # 5. Заморозка дельты Пример: ```text локально сейчас: Alice = 180 Bob = 94 уже опубликовано: Alice = 160 Bob = 90 ``` Новый job фиксирует: ```text Alice: 161..180 Bob: 91..94 ``` Если во время Arweave upload локальное состояние стало: ```text Alice = 185 Bob = 97 ``` текущий job НЕ меняется. Следующая публикация начнётся с: ```text Alice: 181..185 Bob: 95..97 ``` --- # 6. Общая структура большого блока Большой опубликованный файл одновременно является: - архивной дельтой; - индексом предыдущих больших блоков; - контейнером пользовательских записей; - навигационным узлом для истории каждого пользователя; - подписанным объектом того SHiNE-аккаунта, который его сформировал. Структура: ```text +------------------------------------------------+ | PREFIX / HEADER | | magic = SHINE-ARCHIVE | | version major/minor | | размеры/счётчики | | номер большого блока | | время | | LOGIN составителя | | режим таблицы предыдущих блоков | | число предыдущих блоков | +------------------------------------------------+ | PREVIOUS BLOCK REFERENCES TABLE | | | | block_number + block_hash + arweave_tx_id | | block_number + block_hash + arweave_tx_id | | ... | +------------------------------------------------+ | USER RECORDS / SERVER NAVIGATION METADATA | | ... | +------------------------------------------------+ | FOOTER | | LOGIN закрывшего блок | | block_hash | | signature | +------------------------------------------------+ ``` Логин в header и footer должен быть одинаковым. Дублирование логина намеренное: - header позволяет сразу при скачивании начала файла увидеть, кто составил блок; - footer делает закрытие блока человекочитаемым рядом с hash и подписью. --- # 7. Magic и версия Magic: ```text SHINE-ARCHIVE ``` Это ровно 13 ASCII-байт: ```text 53 48 49 4E 45 2D 41 52 43 48 49 56 45 ``` Версия: ```text u8 version_major u8 version_minor ``` Для этой спецификации: ```text 1 0 ``` то есть `v1.0`. --- # 8. Порядок байтов Все fixed-size integer-поля v1 хранятся в: ```text big-endian ``` --- # 9. Header большого блока v1.0 Header должен быть устроен так, чтобы клиент мог сначала скачать только небольшой префикс файла, узнать размер всей начальной индексной области и затем докачать только её. Формат: ```text bytes[13] magic = "SHINE-ARCHIVE" u8 version_major u8 version_minor u32 header_size u32 block_number u64 created_at_ms u8 creator_login_length bytes[N] creator_login UTF-8 u8 references_mode u32 references_count u16 reference_entry_size u32 records_count ``` ## 9.1. `header_size` `header_size` — абсолютный byte offset от начала файла до первого байта области пользовательских записей. То есть включает: ```text header prefix + creator_login + всю таблицу PreviousBlockReference ``` Клиент может: 1. скачать первые условные 64–128 байт; 2. прочитать `header_size`; 3. докачать `0 .. header_size-1`; 4. уже иметь все адреса предыдущих больших блоков; 5. после этого скачивать только нужные user records. ## 9.2. `block_number` ```text u32 ``` Номер большого итогового блока данной ветки. Диапазон: ```text 0 .. 4 294 967 295 ``` Для предполагаемой частоты закрытия это более чем достаточный запас. ## 9.3. `created_at_ms` ```text u64 ``` Unix Epoch UTC в миллисекундах. Это время фиксации snapshot текущего большого блока. ## 9.4. `creator_login` Логин SHiNE-пользователя/сервера, который сформировал этот большой блок. Пример: ```text raspberry-archive ``` Это поле находится именно в header, поэтому клиент видит автора уже после скачивания начала блока. `creator_login` входит в hash большого блока и тем самым криптографически привязан к его подписи. ## 9.5. `references_mode` ```text u8 ``` Зарезервированные значения: ```text 0 = FULL_HISTORY 1 = PARTIAL_HISTORY ``` Для v1 publisher MUST использовать: ```text FULL_HISTORY ``` То есть каждый новый большой блок содержит ссылки на все известные предыдущие большие блоки своей ветки. `PARTIAL_HISTORY` резервируется на будущее, если когда-нибудь полный список станет слишком большим. ## 9.6. `references_count` ```text u32 ``` Количество элементов в таблице предыдущих больших блоков. ## 9.7. `reference_entry_size` ```text u16 ``` Для v1: ```text 68 ``` Поле оставлено явно, чтобы будущая minor/major версия могла расширить `PreviousBlockReference`, а клиент всё ещё мог корректно пропускать неизвестные дополнительные байты. ## 9.8. `records_count` ```text u32 ``` Количество пользовательских record-контейнеров внутри большого блока. --- # 10. Таблица всех предыдущих больших блоков Сразу после header идёт: ```text PreviousBlockReference[references_count] ``` В v1 это **полная история предыдущих больших блоков данной ветки**. При одном закрытии в сутки даже через год это около 365 элементов, поэтому простота полного списка важнее преждевременной оптимизации. --- # 11. `PreviousBlockReference v1` Формат одного элемента: ```text u32 block_number bytes[32] block_hash bytes[32] arweave_tx_id ``` Размер: ```text 4 + 32 + 32 = 68 байт ``` Смысл: ```text block_number -> логический номер большого блока block_hash -> SHA-256 / канонический hash точного содержимого большого блока arweave_tx_id -> raw 32-byte TX ID, по которому этот большой блок можно скачать из Arweave ``` То есть: ```text HASH = идентичность содержимого TXID = место хранения ``` Оба поля обязательны. Внешнее строковое Base64URL-представление TX ID внутрь бинарного файла не записывается. Таблица v1 SHOULD быть отсортирована детерминированно по `block_number`, затем по `block_hash`. --- # 12. Почему в v1 записываются все предыдущие блоки Если закрывать примерно один большой блок в сутки: ```text 365 × 68 ≈ 24.8 KB в год ``` Даже через много лет это остаётся небольшим overhead по сравнению с полезными данными. Главное преимущество: любой новый большой блок сразу является индексом всей предыдущей ветки. Клиент не обязан идти: ```text 365 -> 364 -> 363 -> ... -> 95 ``` Чтобы получить адрес блока 95. Он получает его из начала текущего блока. --- # 13. Пользовательская обратная навигация Навигация пользователя строится не на одну предыдущую запись. Правило v1: > Для каждого пользователя сервер находит **предыдущий большой блок**, в котором существовала хотя бы одна запись этого пользователя, и сохраняет ссылку на этот большой блок вместе со списком `offset + size` **всех записей этого пользователя в том предыдущем большом блоке**. Например: ```text Большой block 100: Alice сегодня имеет 2 записи. Предыдущий большой блок, где Alice присутствовала: block 95. В block 95 у Alice было 3 записи: offset 8123 / size 417 offset 9001 / size 351 offset 14020 / size 692 ``` Тогда server-generated ссылка Alice из block 100 содержит все три диапазона. --- # 14. `PreviousUserRecordsRef v1` Формат: ```text u32 previous_block_ref u32 previous_records_count repeat previous_records_count times: u32 previous_record_offset u32 previous_record_size ``` Минимальный размер: ```text 8 байт ``` Плюс: ```text 8 байт на каждую предыдущую запись пользователя ``` Пример для 3 сообщений: ```text previous_block_ref = 94 previous_records_count = 3 8123 417 9001 351 14020 692 ``` Если `PreviousBlockReference[94]` описывает big block 95, клиент получает: ```text block number = 95 block hash = ... Arweave TXID = ... ``` и может HTTP Range-запросами забрать только три нужных диапазона. --- # 15. `previous_block_ref` `previous_block_ref` — индекс в таблице `PreviousBlockReference` **текущего большого блока**. То есть пользовательская ссылка не повторяет 32-byte TX ID и 32-byte hash. Она хранит только компактный индекс. Специальное значение: ```text 0xFFFFFFFF ``` означает: ```text у пользователя ещё не было записей в предыдущих больших блоках ``` В этом случае: ```text previous_records_count = 0 ``` --- # 16. Смещение и размер записи `previous_record_offset`: ```text u32 ``` Абсолютный offset от первого байта соответствующего большого `SHINE-ARCHIVE` файла до первого байта нужной архивированной user-record. `previous_record_size`: ```text u32 ``` Полный размер этой архивированной user-record в байтах. Таким образом, зная: ```text TX ID block hash offset size ``` клиент может скачать ровно нужную пользовательскую запись. --- # 17. Как сервер добавляет пользовательскую ссылку `PreviousUserRecordsRef` создаёт **сервер, который формирует большой блок**, а не пользовательский клиент. Оригинальные подписанные пользователем bytes изменять нельзя. Поэтому server navigation metadata хранится как часть контейнера большого блока вокруг/после оригинальных user-record bytes и покрывается: - `block_hash` большого блока; - подписью сервера/пользователя, который закрыл большой блок. Для каждого пользователя в текущем большом блоке достаточно одного `PreviousUserRecordsRef`, связанного с последней записью этого пользователя в текущем блоке. Если у пользователя сегодня в текущем большом блоке две записи, сервер после их сериализации добавляет один navigation tail, который указывает на предыдущий большой блок пользователя и перечисляет все его записи там. При чтении предыдущего большого блока та же схема даёт следующий переход назад. Получается: ```text Alice in block 100 | | PreviousUserRecordsRef | -> block 95 | -> offsets of ALL Alice records in block 95 v Alice records in block 95 | | server navigation metadata из block 95 | -> block 81 | -> offsets of ALL Alice records in block 81 v ... ``` --- # 18. Рекомендуемый контейнер архивированной пользовательской записи Чтобы сервер мог добавлять свою навигационную метаинформацию, не изменяя исходную пользовательскую подпись, v1 SHOULD использовать серверный envelope. Рекомендуемая модель: ```text u32 archived_record_size u32 raw_user_record_size bytes[N] raw_user_record u8 navigation_flags [optional server navigation data] ``` Где: ```text navigation_flags bit 0 = PreviousUserRecordsRef присутствует ``` У большинства записей: ```text navigation_flags = 0 ``` У последней записи конкретного пользователя в текущем большом блоке: ```text navigation_flags bit0 = 1 ``` и далее сериализован `PreviousUserRecordsRef`. `archived_record_size` позволяет Range-reader получить и полностью декодировать конкретный archived record. Исходный `raw_user_record` остаётся байт-в-байт неизменным. --- # 19. Пример пользовательской цепочки ```text BIG BLOCK 100 Alice record A Alice record B + PreviousUserRecordsRef: previous_block_ref -> block 95 previous_records_count = 3 [8123,417] [9001,351] [14020,692] | v BIG BLOCK 95 Alice old record #1 Alice old record #2 Alice old record #3 + PreviousUserRecordsRef: previous_block_ref -> block 81 previous_records_count = 1 [4412,380] | v BIG BLOCK 81 ... ``` Так клиент получает всю историю пользователя назад блок за блоком, но скачивает только конкретные нужные byte ranges. --- # 20. Hash большого блока В footer записывается: ```text bytes[32] block_hash ``` `block_hash` вычисляется как: ```text SHA256( все байты большого блока от magic через header через creator_login через PreviousBlockReference table через все user records и server navigation metadata через footer closer_login_length + closer_login ) ``` Само поле `block_hash` и `closer_signature` в hash НЕ входят. --- # 21. Footer: кто закрыл блок, hash и подпись В конце каждого большого блока MUST быть: ```text u8 closer_login_length bytes[N] closer_login UTF-8 bytes[32] block_hash bytes[64] closer_signature ``` Логические значения: ```text 1. кто сформировал/закрыл блок; 2. hash точных bytes блока; 3. подпись закрывающего аккаунта. ``` `closer_login` MUST точно совпадать с `creator_login` из header. Если значения отличаются — блок невалиден. --- # 22. Подпись большого блока Алгоритм v1: ```text Ed25519 ``` Подписываемый payload: ```text ASCII("SHINE-ARCHIVE-V1") || block_hash ``` То есть: ```text closer_signature = Ed25519Sign( closer_private_key, "SHINE-ARCHIVE-V1" || block_hash ) ``` Для первой реализации `closer_private_key` может быть root private key SHiNE-аккаунта `creator_login` / `closer_login`. Public key для проверки берётся из соответствующего User PDA в Solana. --- # 23. Archive head в Solana User PDA Отдельный PDA создавать не требуется. Используется тот же существующий User PDA. Новый block type: ```text 100 ``` Формат: ```text u8 block_type = 100 u8 block_version = 0 bytes[32] archive_tx_id bytes[32] archive_hash ``` Размер: ```text 66 байт ``` Solana тем самым хранит: ```text где находится текущая голова -> Arweave TX ID какие exact bytes являются правильными -> SHA-256 ``` --- # 24. Обновление Solana — обычный User PDA update **Отдельная Solana instruction для archive head не требуется.** Архивный сервер использует существующий обычный механизм обновления собственного User PDA: 1. читает текущий PDA; 2. сохраняет все существующие поля; 3. меняет/добавляет только block type `100`; 4. пересобирает новую PDA-запись; 5. выполняет существующую root-signature проверку; 6. `client key` подписывает/оплачивает Solana transaction; 7. ждёт `finalized`. Важно: все новые Java/JS/Rust сериализаторы обязаны понимать block type `100` и сохранять его при обычных обновлениях PDA. --- # 25. `TestFreeAvatarArweaveService` Старый сервис больше не нужен как avatar test service. Его следует переименовать, например, в: ```text ArweaveArchiveService ``` и переделать только под серверную фиксацию. Нужно оставить/переиспользовать: - JWK parsing; - wallet address; - reward calculation; - balance check; - RSA-PSS signing; - Arweave HTTP. Удалить avatar-specific части: - PNG/JPEG/WebP проверки; - avatar quota; - лимит 128 KiB; - avatar DAO; - avatar-specific API. Новый сервис должен уметь: ```text publishArchive(...) waitForConfirmation(...) ``` и корректно работать с большими/chunked uploads. --- # 26. Ожидание закрепления Arweave После получения TX ID сервер НЕ обновляет Solana сразу. Он ждёт заданное число подтверждений. Рекомендуемые настройки: ```properties archive.arweave.minConfirmations=1 archive.arweave.confirmPollSeconds=30 archive.arweave.confirmTimeoutMinutes=180 ``` Только после `ARWEAVE_CONFIRMED` разрешён update User PDA. --- # 27. Порядок публикации Строго: ```text 1. Синхронизировать/иметь актуальные локальные блоки. 2. Прочитать archive_chain_cursor. 3. Зафиксировать snapshot новых диапазонов. 4. Создать archive_publish_job. 5. Собрать большой SHINE-ARCHIVE v1.0. 6. В header записать creator_login. 7. В начало блока записать FULL_HISTORY список всех предыдущих больших блоков: block_number + hash + Arweave TX ID. 8. Для каждого пользователя сформировать server navigation metadata: previous big block + список всех offset/size его записей там. 9. Посчитать block_hash. 10. В footer записать closer_login, block_hash, signature. 11. Загрузить файл в Arweave. 12. Сохранить TX ID в publish job. 13. Дождаться закрепления Arweave. 14. Обычным User PDA update записать block type 100: archive_tx_id + archive_hash. 15. Дождаться Solana finalized. 16. В одной локальной DB transaction сдвинуть archive_chain_cursor. 17. Отметить job CURSORS_COMMITTED. ``` Если новых данных нет — пустой большой блок создавать не нужно. --- # 28. Crash recovery ## Crash после snapshot Продолжить с теми же frozen ranges. ## Crash после построения файла Использовать тот же файл и тот же hash. ## Crash после Arweave upload Не загружать второй раз. Использовать сохранённый TX ID. ## Crash после Arweave confirmation Продолжить с User PDA update. ## Crash после Solana submit Сначала проверить текущий PDA/transaction. ## Crash после Solana finalized, но до cursor commit Если PDA уже содержит ожидаемые: ```text archive_tx_id archive_hash ``` то безопасно выполнить локальный cursor commit. --- # 29. Настройки scheduler ```properties archive.publish.enabled=false archive.publish.intervalMinutes=720 archive.publish.initialDelayMinutes=15 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 ``` Для первой версии разумно начать с: ```text 720 минут = раз в 12 часов ``` Частота обычной межсерверной синхронизации меняется независимо. --- # 30. Проверка большого блока клиентом Клиент SHOULD выполнять: ```text 1. Скачать начало файла. 2. Проверить magic. 3. Прочитать major/minor. 4. Прочитать header_size. 5. Прочитать creator_login. 6. Докачать весь header + PreviousBlockReference table. 7. При необходимости получить нужные previous block TXID/hash. 8. Скачать нужные user record ranges. 9. При полной проверке файла пересчитать block_hash. 10. Проверить closer_login == creator_login. 11. Получить public key автора из User PDA. 12. Проверить Ed25519 signature. ``` --- # 31. Пример навигации Alice Допустим клиент уже имеет актуальную запись Alice из block 100. Server navigation metadata говорит: ```text previous_block_ref = 94 previous_records_count = 3 [8123, 417] [9001, 351] [14020, 692] ``` Из начала block 100: ```text PreviousBlockReference[94]: block_number = 95 block_hash = H95 arweave_tx_id = TX95 ``` Клиент: ```text 1. идёт в TX95; 2. проверяет H95; 3. делает Range на 8123/417; 4. Range на 9001/351; 5. Range на 14020/692; 6. получает все три Alice records из block 95; 7. из server navigation metadata последней Alice record получает следующий переход, например на block 81; 8. повторяет. ``` То есть история читается: ```text 100 -> 95 -> 81 -> 63 -> ... ``` но на каждом шаге скачиваются только конкретные записи данного пользователя. --- # 32. Почему hash и TX ID нужны одновременно Нельзя оставить только TX ID. Правильная модель: ```text block_number -> логический номер block_hash -> криптографическая идентичность arweave_tx_id -> текущее физическое расположение ``` Если в будущем большой блок будет перенесён из Arweave в другое хранилище, его hash останется тем же. --- # 33. Будущее `PARTIAL_HISTORY` В первой реализации используется только: ```text references_mode = FULL_HISTORY ``` На будущее уже зарезервирован: ```text PARTIAL_HISTORY ``` Будущая версия сможет, например, хранить: - последние N больших блоков; - checkpoint-блоки; - только реально используемые ссылки; - многоуровневый индекс. Но это НЕ требуется для v1. --- # 34. Итоговые бинарные структуры v1.0 ## Header ```text bytes[13] magic = "SHINE-ARCHIVE" u8 version_major u8 version_minor u32 header_size u32 block_number u64 created_at_ms u8 creator_login_length bytes[N] creator_login u8 references_mode u32 references_count u16 reference_entry_size u32 records_count ``` ## PreviousBlockReference ```text u32 block_number bytes[32] block_hash bytes[32] arweave_tx_id ``` Размер v1: ```text 68 bytes ``` ## PreviousUserRecordsRef ```text u32 previous_block_ref u32 previous_records_count repeat previous_records_count: u32 previous_record_offset u32 previous_record_size ``` ## User archived envelope — рекомендуемая форма ```text u32 archived_record_size u32 raw_user_record_size bytes[N] raw_user_record u8 navigation_flags [optional PreviousUserRecordsRef] ``` ## Footer ```text u8 closer_login_length bytes[N] closer_login bytes[32] block_hash bytes[64] closer_signature ``` ## Solana User PDA block 100 ```text u8 block_type = 100 u8 block_version = 0 bytes[32] archive_tx_id bytes[32] archive_hash ``` --- # 35. Обязательные проверки v1 Publisher MUST: - не изменять frozen job после snapshot; - проверять cursor hash; - не двигать cursor до Solana finalized; - не публиковать пустую дельту; - записывать полный список предыдущих больших блоков; - хранить hash и TX ID каждого previous block; - записывать creator login в header; - повторять тот же login в footer; - подписывать hash большого блока; - добавлять для пользователя ссылку на предыдущий большой блок и offsets/sizes всех его записей там. Reader MUST: - проверять magic/version; - проверять границы offsets; - проверять hash скачанного большого блока; - проверять `closer_login == creator_login`; - проверять подпись; - проверять, что скачанные user records действительно принадлежат ожидаемому пользователю; - не считать Arweave TX ID самостоятельным доказательством корректности содержимого. --- # 36. Реализационный checklist ## Solana - [ ] Добавить User PDA block type `100`. - [ ] Добавить `archive_tx_id [32]`. - [ ] Добавить `archive_hash [32]`. - [ ] Научить обычный User PDA update читать и сохранять block 100. - [ ] Серверу дать возможность обычным update менять block 100. - [ ] Ждать `finalized`. ## Database - [ ] `archive_chain_cursor`. - [ ] `archive_publish_job`. - [ ] `archive_publish_job_chain`. ## Arweave - [ ] Переименовать `TestFreeAvatarArweaveService` в `ArweaveArchiveService`. - [ ] Удалить avatar-specific код. - [ ] Реализовать большие/chunked uploads. - [ ] Реализовать ожидание confirmations. - [ ] Сохранять TX ID до ожидания подтверждений. ## Большой блок - [ ] Magic `SHINE-ARCHIVE`. - [ ] `version_major u8`. - [ ] `version_minor u8`. - [ ] `header_size`. - [ ] `block_number u32`. - [ ] `created_at_ms u64`. - [ ] `creator_login` в header. - [ ] `FULL_HISTORY` previous block table. - [ ] `block_number + block_hash + txid` для каждого previous block. - [ ] Server-side `PreviousUserRecordsRef`. - [ ] Список всех `offset + size` пользовательских записей из предыдущего big block. - [ ] `closer_login + block_hash + signature` в footer. ## Scheduler / recovery - [ ] Настраиваемый interval. - [ ] Initial delay. - [ ] Один active job. - [ ] Resume после restart. - [ ] Cursor commit только после Solana finalized. --- # 37. Краткая итоговая модель ```text SOLANA USER PDA | +--> block 100 archive TX ID archive SHA-256 | v SHINE-ARCHIVE block N | | HEADER: | version | block number | time | CREATOR LOGIN | | PREVIOUS BLOCK TABLE: | #0 number/hash/TXID | #1 number/hash/TXID | ... все предыдущие в v1 | | USER DATA: | original signed user records | server navigation metadata | previous block ref | ALL previous user record offsets/sizes | | FOOTER: | same creator/closer login | hash | Ed25519 signature v SHINE-ARCHIVE block N-1 | v ... ``` Это завершает согласованную спецификацию `SHiNE Archive Protocol v1.0`.