Files
SHiNE-server/docs/SHINE_ARCHIVE_PROTOCOL_v1.0_RU.md
T

38 KiB
Raw Blame History

SHiNE Archive Protocol v1.0

Полная русская спецификация серверной фиксации больших блоков, Arweave, Solana User PDA и навигации по предыдущим записям пользователя

Статус: рабочая спецификация v1.0
Формат: бинарный
Magic: SHINE-ARCHIVE
Версия: два отдельных байта major / minor


1. Назначение

Протокол предназначен для периодической долговременной фиксации новых SHiNE-блоков.

Стартовая схема:

Основной 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

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

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
);

Рекомендуемые состояния:

SNAPSHOT_CREATED
FILE_BUILT
ARWEAVE_UPLOADED
ARWEAVE_CONFIRMED
SOLANA_SUBMITTED
SOLANA_FINALIZED
CURSORS_COMMITTED
FAILED

4.3. archive_publish_job_chain

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. Заморозка дельты

Пример:

локально сейчас:
Alice = 180
Bob   = 94

уже опубликовано:
Alice = 160
Bob   = 90

Новый job фиксирует:

Alice: 161..180
Bob:    91..94

Если во время Arweave upload локальное состояние стало:

Alice = 185
Bob   = 97

текущий job НЕ меняется.

Следующая публикация начнётся с:

Alice: 181..185
Bob:    95..97

6. Общая структура большого блока

Большой опубликованный файл одновременно является:

  • архивной дельтой;
  • индексом предыдущих больших блоков;
  • контейнером пользовательских записей;
  • навигационным узлом для истории каждого пользователя;
  • подписанным объектом того SHiNE-аккаунта, который его сформировал.

Структура:

+------------------------------------------------+
| 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:

SHINE-ARCHIVE

Это ровно 13 ASCII-байт:

53 48 49 4E 45 2D 41 52 43 48 49 56 45

Версия:

u8 version_major
u8 version_minor

Для этой спецификации:

1
0

то есть v1.0.


8. Порядок байтов

Все fixed-size integer-поля v1 хранятся в:

big-endian

9. Header большого блока v1.0

Header должен быть устроен так, чтобы клиент мог сначала скачать только небольшой префикс файла, узнать размер всей начальной индексной области и затем докачать только её.

Формат:

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 от начала файла до первого байта области пользовательских записей.

То есть включает:

header prefix
+ creator_login
+ всю таблицу PreviousBlockReference

Клиент может:

  1. скачать первые условные 64–128 байт;
  2. прочитать header_size;
  3. докачать 0 .. header_size-1;
  4. уже иметь все адреса предыдущих больших блоков;
  5. после этого скачивать только нужные user records.

9.2. block_number

u32

Номер большого итогового блока данной ветки.

Диапазон:

0 .. 4 294 967 295

Для предполагаемой частоты закрытия это более чем достаточный запас.

9.3. created_at_ms

u64

Unix Epoch UTC в миллисекундах.

Это время фиксации snapshot текущего большого блока.

9.4. creator_login

Логин SHiNE-пользователя/сервера, который сформировал этот большой блок.

Пример:

raspberry-archive

Это поле находится именно в header, поэтому клиент видит автора уже после скачивания начала блока.

creator_login входит в hash большого блока и тем самым криптографически привязан к его подписи.

9.5. references_mode

u8

Зарезервированные значения:

0 = FULL_HISTORY
1 = PARTIAL_HISTORY

Для v1 publisher MUST использовать:

FULL_HISTORY

То есть каждый новый большой блок содержит ссылки на все известные предыдущие большие блоки своей ветки.

PARTIAL_HISTORY резервируется на будущее, если когда-нибудь полный список станет слишком большим.

9.6. references_count

u32

Количество элементов в таблице предыдущих больших блоков.

9.7. reference_entry_size

u16

Для v1:

68

Поле оставлено явно, чтобы будущая minor/major версия могла расширить PreviousBlockReference, а клиент всё ещё мог корректно пропускать неизвестные дополнительные байты.

9.8. records_count

u32

Количество пользовательских record-контейнеров внутри большого блока.


10. Таблица всех предыдущих больших блоков

Сразу после header идёт:

PreviousBlockReference[references_count]

В v1 это полная история предыдущих больших блоков данной ветки.

При одном закрытии в сутки даже через год это около 365 элементов, поэтому простота полного списка важнее преждевременной оптимизации.


11. PreviousBlockReference v1

Формат одного элемента:

u32       block_number
bytes[32] block_hash
bytes[32] arweave_tx_id

Размер:

4 + 32 + 32 = 68 байт

Смысл:

block_number
-> логический номер большого блока

block_hash
-> SHA-256 / канонический hash точного содержимого большого блока

arweave_tx_id
-> raw 32-byte TX ID, по которому этот большой блок можно скачать из Arweave

То есть:

HASH = идентичность содержимого
TXID = место хранения

Оба поля обязательны.

Внешнее строковое Base64URL-представление TX ID внутрь бинарного файла не записывается.

Таблица v1 SHOULD быть отсортирована детерминированно по block_number, затем по block_hash.


12. Почему в v1 записываются все предыдущие блоки

Если закрывать примерно один большой блок в сутки:

365 × 68 ≈ 24.8 KB в год

Даже через много лет это остаётся небольшим overhead по сравнению с полезными данными.

Главное преимущество: любой новый большой блок сразу является индексом всей предыдущей ветки.

Клиент не обязан идти:

365 -> 364 -> 363 -> ... -> 95

Чтобы получить адрес блока 95.

Он получает его из начала текущего блока.


13. Пользовательская обратная навигация

Навигация пользователя строится не на одну предыдущую запись.

Правило v1:

Для каждого пользователя сервер находит предыдущий большой блок, в котором существовала хотя бы одна запись этого пользователя, и сохраняет ссылку на этот большой блок вместе со списком offset + size всех записей этого пользователя в том предыдущем большом блоке.

Например:

Большой 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

Формат:

u32 previous_block_ref
u32 previous_records_count

repeat previous_records_count times:
    u32 previous_record_offset
    u32 previous_record_size

Минимальный размер:

8 байт

Плюс:

8 байт на каждую предыдущую запись пользователя

Пример для 3 сообщений:

previous_block_ref      = 94
previous_records_count  = 3

8123   417
9001   351
14020  692

Если PreviousBlockReference[94] описывает big block 95, клиент получает:

block number = 95
block hash   = ...
Arweave TXID = ...

и может HTTP Range-запросами забрать только три нужных диапазона.


15. previous_block_ref

previous_block_ref — индекс в таблице PreviousBlockReference текущего большого блока.

То есть пользовательская ссылка не повторяет 32-byte TX ID и 32-byte hash.

Она хранит только компактный индекс.

Специальное значение:

0xFFFFFFFF

означает:

у пользователя ещё не было записей в предыдущих больших блоках

В этом случае:

previous_records_count = 0

16. Смещение и размер записи

previous_record_offset:

u32

Абсолютный offset от первого байта соответствующего большого SHINE-ARCHIVE файла до первого байта нужной архивированной user-record.

previous_record_size:

u32

Полный размер этой архивированной user-record в байтах.

Таким образом, зная:

TX ID
block hash
offset
size

клиент может скачать ровно нужную пользовательскую запись.


17. Как сервер добавляет пользовательскую ссылку

PreviousUserRecordsRef создаёт сервер, который формирует большой блок, а не пользовательский клиент.

Оригинальные подписанные пользователем bytes изменять нельзя.

Поэтому server navigation metadata хранится как часть контейнера большого блока вокруг/после оригинальных user-record bytes и покрывается:

  • block_hash большого блока;
  • подписью сервера/пользователя, который закрыл большой блок.

Для каждого пользователя в текущем большом блоке достаточно одного PreviousUserRecordsRef, связанного с последней записью этого пользователя в текущем блоке.

Если у пользователя сегодня в текущем большом блоке две записи, сервер после их сериализации добавляет один navigation tail, который указывает на предыдущий большой блок пользователя и перечисляет все его записи там.

При чтении предыдущего большого блока та же схема даёт следующий переход назад.

Получается:

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.

Рекомендуемая модель:

u32       archived_record_size
u32       raw_user_record_size
bytes[N]  raw_user_record
u8        navigation_flags
[optional server navigation data]

Где:

navigation_flags bit 0 = PreviousUserRecordsRef присутствует

У большинства записей:

navigation_flags = 0

У последней записи конкретного пользователя в текущем большом блоке:

navigation_flags bit0 = 1

и далее сериализован PreviousUserRecordsRef.

archived_record_size позволяет Range-reader получить и полностью декодировать конкретный archived record.

Исходный raw_user_record остаётся байт-в-байт неизменным.


19. Пример пользовательской цепочки

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 записывается:

bytes[32] block_hash

block_hash вычисляется как:

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 быть:

u8        closer_login_length
bytes[N]  closer_login UTF-8
bytes[32] block_hash
bytes[64] closer_signature

Логические значения:

1. кто сформировал/закрыл блок;
2. hash точных bytes блока;
3. подпись закрывающего аккаунта.

closer_login MUST точно совпадать с creator_login из header.

Если значения отличаются — блок невалиден.


22. Подпись большого блока

Алгоритм v1:

Ed25519

Подписываемый payload:

ASCII("SHINE-ARCHIVE-V1") || block_hash

То есть:

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:

100

Формат:

u8        block_type = 100
u8        block_version = 0
bytes[32] archive_tx_id
bytes[32] archive_hash

Размер:

66 байт

Solana тем самым хранит:

где находится текущая голова -> 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.

Его следует переименовать, например, в:

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.

Новый сервис должен уметь:

publishArchive(...)
waitForConfirmation(...)

и корректно работать с большими/chunked uploads.


26. Ожидание закрепления Arweave

После получения TX ID сервер НЕ обновляет Solana сразу.

Он ждёт заданное число подтверждений.

Рекомендуемые настройки:

archive.arweave.minConfirmations=1
archive.arweave.confirmPollSeconds=30
archive.arweave.confirmTimeoutMinutes=180

Только после ARWEAVE_CONFIRMED разрешён update User PDA.


27. Порядок публикации

Строго:

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 уже содержит ожидаемые:

archive_tx_id
archive_hash

то безопасно выполнить локальный cursor commit.


29. Настройки scheduler

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

Для первой версии разумно начать с:

720 минут = раз в 12 часов

Частота обычной межсерверной синхронизации меняется независимо.


30. Проверка большого блока клиентом

Клиент SHOULD выполнять:

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 говорит:

previous_block_ref = 94
previous_records_count = 3

[8123, 417]
[9001, 351]
[14020, 692]

Из начала block 100:

PreviousBlockReference[94]:
    block_number = 95
    block_hash = H95
    arweave_tx_id = TX95

Клиент:

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. повторяет.

То есть история читается:

100 -> 95 -> 81 -> 63 -> ...

но на каждом шаге скачиваются только конкретные записи данного пользователя.


32. Почему hash и TX ID нужны одновременно

Нельзя оставить только TX ID.

Правильная модель:

block_number
-> логический номер

block_hash
-> криптографическая идентичность

arweave_tx_id
-> текущее физическое расположение

Если в будущем большой блок будет перенесён из Arweave в другое хранилище, его hash останется тем же.


33. Будущее PARTIAL_HISTORY

В первой реализации используется только:

references_mode = FULL_HISTORY

На будущее уже зарезервирован:

PARTIAL_HISTORY

Будущая версия сможет, например, хранить:

  • последние N больших блоков;
  • checkpoint-блоки;
  • только реально используемые ссылки;
  • многоуровневый индекс.

Но это НЕ требуется для v1.


34. Итоговые бинарные структуры v1.0

Header

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

u32       block_number
bytes[32] block_hash
bytes[32] arweave_tx_id

Размер v1:

68 bytes

PreviousUserRecordsRef

u32 previous_block_ref
u32 previous_records_count

repeat previous_records_count:
    u32 previous_record_offset
    u32 previous_record_size

User archived envelope — рекомендуемая форма

u32       archived_record_size
u32       raw_user_record_size
bytes[N]  raw_user_record
u8        navigation_flags
[optional PreviousUserRecordsRef]
u8        closer_login_length
bytes[N]  closer_login
bytes[32] block_hash
bytes[64] closer_signature

Solana User PDA block 100

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. Краткая итоговая модель

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.