hbs2/docs/hbs2-share/design.txt

264 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Библиотека и приложение для шаринга данных через refchans
В библиотеке обобщаются примитивы, необходимые для шаринга
данных.
## Контекст
hbs2 -- P2P CAS + стек протоколов, hbs2-peer - приложение,
обрабатывающее протоколы, refchan (RefChan, рефчаны) --- единица
подписки и контроля доступа. Канал с множеством писателей и
читателей, которые определяются контрольным блоком (RefChanHead,
содержит ACL для записи (публичные ключи подписи), ACL для
чтения (публичные ключи шифрования), а так же ключи пиров
(hbs2-peer) которые имеют право генерировать транзакции в данный
рефчан.
Генерировать и пересылать не одно и то же, при создании новой
транзакции генерируется новый блок, подписанный пиром.
Так же транзакция содержит подпись писателя, это не одно и то
же.
В результате право постить в рефчан может быть отозвано как у
автора, так и у пира, в случае злонамеренного поведения пира
(игнорирование ACL / обновлений версий RefChanHead, флуд и так
далее).
Пересылать уже подписанные транзакции может любой пир сети по
желанию.
```
data RefChanUpdate = Propose ... | Accept ...
```
Таким образом, каждая транзакция Propose подтверждается
подписью пира и подписью автора.
Кроме того, каждая такая транзакция подтверждается сообщениями
Accept, минимальное количество которых (quorum) указывается в
блоке RefChanHead.
В рефчан записываются только те транзакции, которые набрали
минимальный кворум подтверждений.
Сообщения Accept имеют таймстемп. Таким образом, для сообщения,
записываемого в рефчан можно вычислить время, например, как
медиану времени по всем Accept. Таким образом, существует
способ упорядочить сообщения в рефчане.
Существуют транзакции RefChanNotify, которые не записываются
в рефчан, а служат для нотификации/рассылки сообщений
произвольного характера (в зависимости от приложения).
Такие нотификации будут доставлены только тем пирам, которые
находятся онлайн, гарантии доставки нет, сообщения не пишутся
в рефчан и не рассылаются повторно.
Таким образом, рефчан это множество сообщений Propose | Accept,
упорядоченное по хэшам и записанное в виде дерева меркла, где
каждый хэш -- ссылается на соответствующую транзакцию
RefChanUpdate.
Каждое дерево Меркля может быть зашифровано с использованием
группового ключа GK0.
GK0 -- групповой ключ, представляе собой секрет, зашифрованный
публичными ключами участников.
GK1 --- пара (GK0, секрет из GK0, зашифрованный ключами
участников).
Смысл GK1 -- для уже опубликованных данных, зашифрованных
ключами GK0 --- опубликовать ключ, которыми новые участники
могут так же расшифровать данные без перешифровки
непосредственно данных.
# Кейсы
## Шаринг каталогов
Начинаем с него, так как он требует реализации большинства
необходимых функций.
Функция: получить синхронизируемый между участниками рефчана
каталог, т.е множество файлов.
Предпосылки:
- Запускается время от времени
- Каждый запуск -- акт синхронизации
- Каждый запуск приводит к вычислению нового стейта и его
публикацию в рефчане
- Все подписчики должны видеть одинаковое состояние каталога
с точностью до заданных правил мержа
- Правило мержа указывает маску файла и *стратегию*
- Существуют стратегии ours и theirs, предпочитать собственную
версию или версию из рефчана
- Стейт представляет собой БД (sqlite), состояние которой
вычисляется при сканировании рефчана идемпотентным способом,
т.е повторное сканирование одних и тех же транзакций
не приведёт к изменению состояния
- Уже опубликованные зашифрованные данные не перешифровываются,
вместо этого перешифровываются ключи, которыми они изначально
зашифрованы
- Полагаемся на локальное время модификации, как на самый
быстрый способ определить, что файл менялся
- Для файлов с измененным (относительно последнего известного)
времени модификации --- вычисляем локальный хэш
- Если существуют файлы, локальный хэш которых изменился ---
генерируем и публикуем новый стейт.
Вопросы для дизайна:
### Один каталог -- один стейт?
Да, так как sqlite блокируется на уровне файла БД. Если стейт
будет общий для всех каталогов, но одновременная синхронизация
разных каталогов будет приводить к блокировкам.
Таким образом, стейт это файл БД sqlite, который лежит в каталоге
**локально**.
Минусы:
- Дублирование данных, если на хосте присутствует несколько
экземпляров одного разделяемого каталога
Плюсы:
- Не будет блокировок sqlite при запуске приложения для разных
каталогов
### Один рефчан -- один каталог?
Скорее, да:
Проще, меньше сущностей (не нужно вводить куки для каталога, как
это было в предыдущих дизайнах) для конфигурирования и
понимания.
Меньше транзакций.
### State-based CRDT vs Operation-based CRDT
#### State-based CRDT
При каждой сихронизации публикуем стейт целиком, для каждого
файла.
Стейт содержит GK1 для всех читателей из RefChanHead, если файл
уже существующий, или GK0 если файл новый.
E1 (Файл существует): ∃ Fn ∈ {Tx ∈ RefChan}
Если GK0 -- несколько, берём самый последний, упорядочиваем
транзакции по времени из Accept.
Таким образом, транзакция представляет собой блоб, в котором
присутствуют:
1. все файлы
2. ключи GK1 для этих файлов либо ссылки на эти ключи (TBD).
Плюсы:
1. Стейт всегда однозначно определяет структуру каталога,
является транзакцией и содержит все ключи для чтения.
1. Возможен онлайн консенсус относительно стейта, например,
по вопросам удаления файла
Минусы:
1. стейт -- большой, публикуется долго.
Но можно сделать его дифференциальным относительно какого-то
другого стейта (ссылка на предыдущий стейт и только отличающиеся
записи -- измененные или удалённые элементы)
2. время подтверждения стейта не является достаточным основанием
для его выбора
Пример: все пользователи работают и синхронизируются, в каком-то
момент появляется новый пользователь и публикует пустой стейт.
Должны ли все удалять все файлы? Очевидно, нет. Т.е критериями
является высота (рефчана) и время.
#### Operation-based CRDT
При каждой синхронизации берём все измененные файлы (включая
удалённые).
Стейт (из предыдущего пункта) вычисляется только локально
(и сохраняется).
Файл обновился: время поменялось, локальных хэш поменялся.
Для каждого файла - генерируем транзакцию, транзакция
ссылается на ключ GK0 или GK1.
Если рефчан поменялся: то публикуем все файлы в виде транзакций,
если требуется --- то генерируем GK1.
Файл удалён: E1 && файл отсутствует в каталоге... TBD.
Плюсы:
- Не требуется постить большой стейт каждый раз
Минусы:
- Явно более сложная и неоднозначная история
Таким образом, пока что видно, что без рассмотрения конкретных
случаев --- мы не можем выбратьть State vs Operation модель.
Видно, что наиболее проблематичными случаями является
удаление файла и конфликтующее переименование файла, т.е
каталог становится файлом или наоборот, файл -- каталогом.
Добавление нового файла с неконфликтующим именем:
все включают в свой локальный стейт.
Добавление нового содержимого для уже известного файла:
Вопрос: какую версию выбирать?
Варианты:
- самую последнюю по времени (из Accept)
- с более длинной цепочкой (тогда в каждой публикции файла ---
ссылка на предыдущее значение)