mirror of https://github.com/voidlizard/hbs2
264 lines
12 KiB
Plaintext
264 lines
12 KiB
Plaintext
# Библиотека и приложение для шаринга данных через 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)
|
||
|
||
- с более длинной цепочкой (тогда в каждой публикции файла ---
|
||
ссылка на предыдущее значение)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|