46 KiB
SHiNE Archive Protocol v1.0
Полная спецификация серверной архивации SHiNE-блоков в Arweave и фиксации головы архива в Solana
Статус: согласованная спецификация v1.0
Язык: русский
Magic: SHINE-ARCHIVE
Версия протокола: 1.0
Endian: big-endian для всех фиксированных целых чисел
1. Назначение
Этот документ описывает механизм, по которому SHiNE-сервер периодически:
- синхронизирует обычные SHiNE-блоки существующим механизмом;
- определяет новые локальные блоки, ещё не попавшие в архив;
- фиксирует неизменяемую дельту;
- группирует все новые записи одной
blockchain_nameв один блок пользователя внутри большого архивного блока; - добавляет к каждому такому пользовательскому блоку одну ссылку на предыдущий пользовательский блок этой же
blockchain_name; - формирует один большой бинарный блок
SHINE-ARCHIVE; - загружает его в Arweave;
- ждёт закрепления/подтверждения Arweave-транзакции;
- обычным обновлением своего существующего User PDA записывает новый archive head в Solana;
- только после Solana
finalizedпереводит локальные курсоры на новую точку.
На первом этапе предполагается один архивный server, но формат изначально не должен запрещать появление других архиваторов.
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 имела записи:
BigBlock 70:
AliceChunk = 2 записи
BigBlock 81:
AliceChunk = 5 записей
BigBlock 95:
AliceChunk = 3 записи
BigBlock 100:
AliceChunk = 4 записи
Тогда история alice-001 выглядит:
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. Необходимые ключи архивного сервера
Архивный 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. Расписание публикации
Архивная публикация запускается один раз в сутки в заданное локальное время. Она НЕ использует интервал «каждые N минут».
Пример:
archive.publish.enabled=true
archive.publish.time=00:00
archive.publish.zoneId=
Для v1.0 значение по умолчанию:
00:00
То есть новый snapshot и новый большой архивный блок создаются один раз в сутки в полночь.
Если archive.publish.zoneId пуст, используется системная timezone сервера. При необходимости её можно задать явно, например Europe/Warsaw. Это сохраняет публикацию ровно в указанное локальное время даже при переходах летнего/зимнего времени.
Незавершённый archive job после рестарта не ждёт следующей полуночи: сервер продолжает именно его сразу. Новый snapshot при старте вне назначенного времени не создаётся.
6.1. Первая архивная публикация
Если у данного archive publisher ещё нет подтверждённых архивных курсоров, первая публикация берёт ВСЁ локально известное состояние:
для каждой blockchain_name:
source block 0 .. current local head
То есть первый большой архивный блок содержит все SHiNE-блоки, которые сервер успел узнать к моменту первого суточного snapshot. После успешной публикации следующие большие блоки содержат только дельту относительно подтверждённых курсоров.
6.2. Локальная папка и имена файлов
Перед любой сетевой загрузкой большой блок сначала полностью создаётся на локальном диске. По умолчанию каталог:
data/archive/
До получения Arweave TX ID файл имеет временное имя:
<login>.<00001>.<дд.мм.гг>.tmp.SHiNE-archive
Например:
archive01.00001.11.09.26.tmp.SHiNE-archive
Дата — реальная дата создания/freeze snapshot большого блока в timezone archive publisher-а. Номер имеет минимальную ширину 5 цифр. Пять цифр — форматирование, а не лимит: блок 100000 получает шестизначный номер.
После успешной загрузки Arweave возвращает реальный TX ID. Сервер сначала надёжно сохраняет TX ID в БД, затем атомарно переименовывает тот же локальный файл в:
<login>.<00001>.<дд.мм.гг>.<ARWEAVE_TX_ID>.SHiNE-archive
Например:
archive01.00001.11.09.26.Xm32...kP9.SHiNE-archive
Поле <ARWEAVE_TX_ID> — не слово trx, а настоящий Base64URL TX ID загруженного объекта в Arweave. Финальный файл остаётся локально как постоянная копия.
Crash recovery обязан продолжать работу с этим же файлом. Если TX ID уже сохранён, но процесс упал до rename, при следующем запуске сервер вычисляет финальное имя из сохранённого TX ID и завершает переименование без повторной сборки дельты.
7. Состояние, относительно которого считается дельта
Нужно различать:
- уже окончательно опубликованное состояние;
- текущий замороженный job, который ещё проходит Arweave/Solana.
Нельзя считать дельту от живой головы во время уже запущенной публикации.
8. Таблица archive_chain_cursor
Хранит окончательно подтверждённое архивное состояние для каждой blockchain_name.
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 каждой отдельной записи в истории.
Для следующей публикации достаточно помнить:
предыдущий big block
предыдущий big block hash
chunk offset
chunk size
10. Таблица archive_publish_job
Хранит crash-safe состояние одной архивной публикации.
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
);
Рекомендуемые статусы:
SNAPSHOT_CREATED
FILE_BUILT
ARWEAVE_UPLOADED
ARWEAVE_CONFIRMED
SOLANA_SUBMITTED
SOLANA_FINALIZED
CURSORS_COMMITTED
FAILED
Завершённые rows этой таблицы одновременно дают локальную историю больших блоков:
big_block_number
archive_hash
arweave_tx_id
По ним строится FULL-список предыдущих больших блоков следующей публикации.
11. Таблица archive_publish_job_chain
Хранит frozen range каждой blockchain в текущем job и координаты создаваемого chunk.
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. Заморозка дельты
Пример:
локально:
alice-001 = source block 180
bob-001 = source block 94
опубликовано:
alice-001 = 160
bob-001 = 90
Новый job фиксирует:
alice-001: 161..180
bob-001: 91..94
Если во время загрузки появятся:
alice-001 181..185
bob-001 95..97
они попадут только в следующий job.
13. Проверка курсора перед публикацией
Перед созданием frozen range сервер проверяет:
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.
Рекомендуемый запас:
archive.maxFileBytes=4000000000
Если собранный frozen job превышает этот лимит, v1.0 останавливает публикацию с явной ошибкой ArchiveTooLargeException и не двигает курсоры. Практически лимит очень велик; для такого сервера следует уменьшить объём данных между суточными закрытиями или реализовать деление snapshot на несколько big blocks. Автоматическое деление одного snapshot на несколько big blocks оставлено как совместимое будущее расширение.
15. Magic и версия
Файл начинается ASCII:
SHINE-ARCHIVE
Размер: 13 байт.
Далее:
u8 version_major
u8 version_minor
Для v1.0:
1
0
16. Общий layout большого блока
+------------------------------------------+
| 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 пользователя/сервера, который сформировал большой блок.
u8 creator_login_length
bytes[N] creator_login UTF-8
Этот login находится в начале файла, поэтому его можно узнать без скачивания payload.
19. big_block_number
u32
Рекомендуемая нумерация опубликованных больших блоков:
first = 1
next = 2
next = 3
...
Первый реально публикуемый большой блок имеет номер 1, поэтому его локальное имя содержит 00001. Неудачная незавершённая попытка не становится частью опубликованной archive-цепочки.
20. created_at_ms
u64
Unix Epoch UTC milliseconds.
Это время заморозки содержимого big block.
21. Режим ссылок на предыдущие большие блоки
references_mode u8
Значения:
0 = FULL
1 = PARTIAL, зарезервировано на будущее
Writer v1.0 MUST использовать FULL.
22. FULL previous-block table
Новый big block содержит ссылки на ВСЕ предыдущие большие блоки своей ветки.
Пример:
BigBlock #365
References:
#1
#2
#3
...
#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 |
Семантика:
big_block_number = логический номер
big_block_hash = криптографическая идентичность
arweave_tx_id = где скачать блок
24. references_count
Количество entries в таблице ссылок.
u32
25. reference_entry_size
u16
Для v1.0:
68
Поле оставлено для будущего расширения entry.
26. parent_reference_index
u32
Индекс непосредственного родителя внутри reference table.
Для первого большого блока (#1), у которого нет родителя:
0xFFFFFFFF
В простой FULL-линейной ветке обычно это последний entry.
27. Быстрое скачивание начала блока
Клиент сначала скачивает fixed header + creator login.
Из него узнаёт:
header_size
references_count
reference_entry_size
Затем скачивает только reference table и уже имеет адреса/hash всех предыдущих больших блоков.
Payload пользователей можно пока не скачивать.
28. blockchain_chunks_count
u32
Количество UserBlockchainChunk в текущем big block.
Одна blockchain_name встречается максимум один раз.
29. total_user_records_count
u32
Общее количество исходных SHiNE-записей во всех chunks.
30. Порядок chunks и records
Для детерминированной сериализации chunks SHOULD сортироваться по blockchain_name ASC.
Внутри chunk исходные записи MUST идти по возрастанию номера исходного SHiNE-блока.
31. Формат UserBlockchainChunk
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
u32
Полный размер chunk от первого байта chunk_size до последнего байта previous_chunk_size включительно.
Это позволяет:
- быстро пропустить chunk;
- скачать его Range-запросом;
- сохранить его точные координаты для следующего backlink.
33. blockchain_name внутри chunk
Хранится только blockchain_name.
Отдельный login не нужен.
34. records_count
u32
Если за период у alice-001 появилось 7 записей:
records_count = 7
Все 7 находятся в одном chunk.
35. Raw records
Каждая исходная запись сериализуется:
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
u32
Byte offset от начала предыдущего SHINE-ARCHIVE до первого байта предыдущего chunk этой blockchain.
Первый байт chunk — поле chunk_size.
40. previous_chunk_size
u32
Полный размер предыдущего chunk.
Поэтому клиент может Range-запросом забрать сразу всю предыдущую пачку записей пользователя.
41. Первая публикация blockchain
Если у данной blockchain_name нет предыдущего архивного chunk:
previous_big_block_ref = 0xFFFFFFFF
previous_chunk_offset = 0
previous_chunk_size = 0
42. Пример backlink-цепочки
В BigBlock #100 есть alice-001 chunk из 4 записей.
Его backlink:
previous_big_block_ref = 94
previous_chunk_offset = 8123
previous_chunk_size = 1770
ref[94] содержит:
big_block_number = 95
big_block_hash = ...
arweave_tx_id = ...
Клиент:
- получает TX big block #95;
- проверяет hash;
- делает Range на
8123 .. 8123+1770; - получает весь предыдущий
alice-001chunk; - читает его записи;
- берёт следующий backlink.
43. Почему TX ID не хранится в каждом chunk
TX ID и hash предыдущих больших блоков уже один раз записаны в общей reference table.
Поэтому в chunk достаточно компактного u32 previous_big_block_ref.
44. Footer большого блока
После последнего chunk:
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:
closer_login MUST == creator_login
Login повторяется намеренно:
- header даёт автора сразу;
- footer явно фиксирует того, кто закрыл блок;
- hash покрывает footer login;
- verifier проверяет совпадение.
46. block_hash
bytes[32]
SHA-256
Хэшируется всё от первого байта magic до последнего байта closer_login включительно.
Не входят:
block_hash
closer_signature
Псевдокод:
hash_input =
fixed_header
+ creator_login
+ references
+ all UserBlockchainChunks
+ closer_login_length
+ closer_login
block_hash = SHA256(hash_input)
47. closer_signature
bytes[64]
Ed25519
Подписывается:
ASCII("SHINE-ARCHIVE-V1") || block_hash
Для v1.0 используется root private key аккаунта creator_login.
48. Проверка подписи
Verifier:
- читает
creator_login; - проверяет
creator_login == closer_login; - получает root public key аккаунта из User PDA / исторического состояния Solana;
- пересчитывает
block_hash; - проверяет 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-а.
Формат:
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. Полный порядок публикации
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:
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 удаляется из активного протокола; archive publisher использует отдельный ArweaveArchiveService.
Его следует переделать/переименовать, например в:
ArweaveArchiveService
Он должен:
- использовать Arweave JWK;
- подписывать и оплачивать upload;
- поддерживать большие/chunked uploads;
- возвращать TX ID;
- ждать подтверждения;
- опрашивать confirmations.
Рекомендуемые настройки:
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 сервер должен дождаться:
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
Проверить:
PDA.archive_tx_id == expected_tx
PDA.archive_hash == expected_hash
После этого безопасно выполнить cursor commit.
57. Нет новых данных
Если новых записей нет ни в одной blockchain:
не создавать пустой SHINE-ARCHIVE
не загружать Arweave
не обновлять Solana
58. FULL references как стартовая стратегия
v1.0 сознательно хранит ВСЕ previous big blocks в каждом новом big block.
Плюсы:
- актуальный блок сразу даёт карту всей предыдущей истории;
- не нужно идти parent-by-parent;
- backlink может использовать компактный ref index;
- reader очень простой.
При одном big block в сутки:
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, клиент:
- читает все records chunk;
- читает
PreviousBlockchainChunkRef; - если ref =
0xFFFFFFFF, история закончилась; - иначе берёт соответствующий
PreviousBigBlockReference; - получает TX ID + hash previous big block;
- Range-запросом читает
previous_chunk_offset/previous_chunk_size; - получает сразу всю предыдущую пачку записей
alice-001; - повторяет.
Так можно читать историю конкретной blockchain, не скачивая чужие chunks.
61. Дубликаты
Если исходная SHiNE-запись уже есть у получателя, она пропускается/дедуплицируется стандартной логикой.
62. Конфликты исходной blockchain
Архивная ветка сама по себе не является пользовательским конфликтом.
Конфликт возникает, когда одна blockchain_name имеет два несовместимых валидно подписанных продолжения одной предыдущей точки.
Для v1 безопасно:
сохранить обе ветки
пометить blockchain CONFLICTED
остановить автоматическое продолжение
Механизм разрешения fork можно добавить позже.
63. Авторство большого блока
Автор указан дважды:
Header
creator_login
Footer
closer_login
block_hash
closer_signature
Для v1.0:
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-а хранит только:
current archive TX ID
current archive SHA-256
Детальная история находится внутри big blocks.
67. Startup preflight
Перед запуском archive publisher сервер SHOULD проверить:
derivePublic(root_private) == UserPDA.root_public_key
и:
derivePublic(client_private) == UserPDA.client_key
При несовпадении archive publisher не запускается.
68. Рекомендуемые настройки
archive.publish.enabled=false
archive.publish.time=00:00
archive.publish.zoneId=
archive.workDir=data/archive
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
# Отдельного archive.solana.rpcUrl нет.
# Используется solana.users.sync.rpcUrl, а если он пуст — обычный solana.rpcUrl.
69. Требование к AppConfig
Archive settings должны читаться из:
- Java system properties;
- environment variables;
application.properties.
getInt() и getBoolean() должны использовать общую логику getParam().
70. Что переиспользуется из текущего сервера
Существующие механизмы:
BlockchainStateDAO.listAll()
BlocksDAO.listRangeByNumber(...)
дают необходимые heads и raw block ranges.
Используются существующие:
block_bytes
block_hash
block_signature
blockchain_name
block_number
Server-to-server sync в v1.0 не меняется.
71. Блокировки
Можно использовать существующий blockchain lock только на коротком этапе:
проверить cursor hash
прочитать frozen range
проверить конечный hash
сохранить/сериализовать frozen data
Lock не удерживается во время Arweave/Solana ожиданий.
72. Реализационный checklist
Database
archive_chain_cursorarchive_publish_jobarchive_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
- local
.tmp.SHiNE-archive -> .<ArweaveTX>.SHiNE-archivelifecycle indata/archive
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.enabledarchive.publish.timearchive.publish.zoneId- no concurrent jobs
- future: автоматическое split > max size (v1.0 сейчас безопасно останавливается без cursor commit)
- skip when no new blocks
73. Итоговая бинарная схема v1.0
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. Основная архитектура одним рисунком
SOLANA USER PDA
|
| block type 100
| TX + HASH
v
BigBlock #100
|
+-- Header
| creator = server-A
|
+-- References
| ref[0] -> BigBlock #1 / hash / TX
| ref[1] -> BigBlock #2 / hash / TX
| ...
| ref[98] -> 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.
Trusted archive reader / importer
Бинарный формат v1.0 не меняется. Обычный сервер MAY использовать archive head из User PDA для восстановления недостающих SHiNE-блоков, но SHOULD принимать архивы только от явно разрешённых server logins.
Рекомендуемая политика v1:
publisher login ∈ local whitelist
AND UserPDA.is_server = true
AND SHA256(file) == UserPDA.archive_head_hash / reference hash
AND creator_login == closer_login == publisher login
AND Ed25519 archive signature valid for publisher root key
AND every raw SHiNE block passes normal AddBlock validation
FULL reference table head-блока позволяет поздно подключившемуся серверу импортировать всю неизвестную историю publisher-а от старых big blocks к новым.
Для быстрого пользовательского доступа сервер SHOULD поддерживать локальный индекс:
blockchain_name -> arweave_tx_id + chunk_offset + chunk_size
Он указывает на последний известный UserBlockchainChunk. Backlink внутри chunk делает эту одну ссылку достаточной для обхода всей архивной истории конкретной blockchain.