This commit is contained in:
Dmitry Zuikov 2023-07-17 06:33:16 +03:00
parent 5b052e8a56
commit 32a7ecfd70
3 changed files with 203 additions and 2 deletions

200
docs/todo/refchan.txt Normal file
View File

@ -0,0 +1,200 @@
NOTE: on-refchans-1
Написал, что фиксация владельцем истории не меняется, и это инвариант.
Смешно получилось.
Коротко:
1) Нам хочется изобразить что-то, желательно CRDT, без
полноценного онлайн-консенсуса, но при этом с возможностью
управлять правами. В идеале и чтения и записи в журнал для
многих авторов.
2) (опущены промежуточные выкладки, плоды, между прочим,
дней страданий и выброшенного на помойку кода)
CRDT такой пишется, но даёт возможность зловредным авторам и
корумпированным ими узлам отравлять историю, поскольку даже
если автора удалили из ACL, он может на своём узле не принять
это обновление и несмотря на то, что его обновления не будут
принимать на добросовестных узлах --- если кто-то придёт
за журналом на его узел, он сможет невозбранно писать
"исторические" записи, ссылаясь на любой ACL/HEAD, где он
еще был
3) CRDT в целом не стоек к византийскому поведению, атаки
на него придумываются легко и быстро
4) Не хочется заставлять владельца канала делать какие-то
действия оперативно, по замыслу -- владелец это просто ключ,
который хранится в сейфе, и на свет выходит, только когда
нужно поменять какую-то существенную метаинформацию, например,
инициирвать форк ну или ACL поменять
5) В общем, нам надо придумать какие-то однозначные правила,
которые бы позволили бы принимать транзакции только относительно
валидного "в сейчас" ACL/HEAD и дать какие-то однозначные правила
интерпретации журнала, которые бы могли при прочтении лога
дать возможность установить, что записи были сделаны в соответствии
с актуальными в моменте ACL.
Решение:
1. Для каждой транзакции требовать нескольких подтверждений от разных
пиров (нод).
1. Писать транзакции в журнал, если есть несколько
подтверждений.
3. ACL устанавливает не только набор авторов, но и набор пиров,
которые могут подтверждать транзакции. Эти пиры подписывают
транзы и постят PROPOSE/ACCEPT. Остальные пиры могут только
транслировать их через Gossip. Неприятное следствие:
журнал будут писать только авторизованные пиры, значит,
количество его экземпляров будет ограниченно ими. Но может,
это и к лучшему.
4. Каждая транзакция подписывается и ключом автора, и ключом
пира и содержит ссылку на актуальный ACL/HEAD, подписанный
владельцем канала/журнала/ссылки
5. При чтении журнала учитывать только те транзакции,
которые удовлетворяют правилам, установленным в HEAD (число
подтверждений)
6. Остаётся проблема, что делать, если приехали записи задним
числом --- если их отвергать, то журналы могут не сойтись,
а если принимать --- то остаётся проблема с отравлением
истории.
Единственное, что тут вижу --- всем нодам принимать
"исторические" записи в течение какого-то периода после
обновления HEAD и не принимать после. Ну типа, за минуту-то
всяко все транзы разойдутся, а позже не принимаем.
Как можно видеть --- этот подход смесь CRDT (однозначные
правила интерпретации журнала) и "онлайн" консенсуса,
тут примерно половина его. "Полусенсус" -- по сути,
есть PROPOSE и VOTE (у нас: ACCEPT), остальные выводы
относительно того, что делать с этими транзакциями
можно делать, или интерпретируя журнал, или вводя
дополнительные транзакции, которые уже обрабатываются
сторонними приложениями. Т.е тут опущены фазы
VALIDATE/PRECOMMIT/COMMIT. Предполагается, что "авторы"
постят уже валидные транзакции, а договориться, какая
транзакция являестся валидной, они должны отдельно,
если им надо, при помощи "эфемерных" транзакций.
NOTE: refchan-log-syncronization-1
что мы тут делаем.
1. Получили merkle лога
2. Надо читать лог, если мы его еще не обработали.
Каждую транзу из него валидировать,
и если мы его еще не обработали --
писать вон в refChanWriteQ
Теперь вот прикол: авторы траз будут верифицированы относительно
"сейчас", а не "тогда". То есть, если к нам едут старые записи,
а мы не знаем, в каком они контексте -- то они будут отвергнуты
относительно "сейчас". То есть еще раз: лог едет, допустим,
старый и некоторым авторам в него писать было можно.
А сейчас --- нельзя. Такие транзакции не пройдут валидацию.
Верно и обратное: если добавлены пермишены для каких-то авторов,
но мы эту информацию еще не получили --- то такие транзакции
будут отвергнуты.
Какое видится решение:
1. Любую метаинформацию обрабатывать отдельно и сразу.
2. Транзакции откладывать, если они валидные, но не прошли
проверку.
Источник метаинформации всегда один --- это владелец ссылки.
"Рано или поздно" метаинформация у всех обновится, и мы перенакатим
отложенные транзакции.
Второй вариант. Поскольку лог транзакций приезжает всегда весь, пусть и по
частям.
Мы можем обрабатывать транзакции из него в контексте тех ACL, что есть в нём
же, а не глобальных. Соответственно, сначала принимаем только те
транзакции, что соответствуют логу.
принимаем все транзакции на обновление метаинформации. Объединяем этот лог и
свой.
Какую мы задачу решаем:
Как принимать или отвергать транзакции в условиях частичной несинхронности
состояния?
Метаинформация (ACL) не имеет контекста сама по себе.
Но всегда имеет контекст в плане принятого журнала -- т.е та
информация, что там определена, та и влияет на приём транзакций
из этого журнала.
Таким образом, у нас есть следующие варианты обработки транзакций.
Вариант 1
1. Принять журнал
2. Обновить сначала метаинформацию из него
3. С обновленной метаинформацией принимать транзакции из него
4. Непринятые транзакции откладывать и пытаться принять их потом
Замечание: если узел прислал журнал, в котором есть транзакции,
которые не соответствуют метаинформации (acl) --- это вызывает
вопросы к этому узлу. Такие транзации нужно отвергать.
Таким образом, мы:
В контексте одного присланного лога:
- Извлекаем метаинформацию
- Обрабатываем транзакции с **этой** метаинформацией
- Добавляем только транзации, которые прошли верификацию
- Помечаем лог обработанным
- Конец.
Итого, как же нам обработать присланный журнал?
1. Скачали журнал
2. Поставили в очередь на обработку, если еще не обработан
3. В процессе обработки:
3.1 Достаём метаинформацию и обновляем стейт
3.2 С сохранённой метаинформацией (из стейта) проигрываем
журнал транзакций.
Что тут плохо:
Стейт глобальный. Если метаинформацию держать глобально,
то она не будет соответствовать тому контексту, в котором существует присланный лог.
Нужно каждый лог обрабатывать отдельно в его контексте.
В чём минусы: он может быть огромный и нас можно легко
зафлудить.
Как решать?
1. Не обрабатывать то состояние, которое у нас уже было.
Мы должны вести историю.
2. Не запрашивать (fetch) лог просто так.
Сейчас инструкция poll ведёт именно к периодическому
запросу. В принципе, если не обрабатывать те логи,
что мы уже обработали, это должно быть достаточно
безвредно, если не очень часто.

View File

@ -45,8 +45,8 @@ import Data.Text qualified as Text
import Data.Text.Encoding qualified as TE
import Data.Time.Clock (NominalDiffTime)
import Data.Heap qualified as Heap
import Data.Heap (Heap,Entry(..))
import Data.Time.Clock
import Data.Heap (Entry(..))
-- import Data.Time.Clock
data PeerInfo e =
PeerInfo

View File

@ -68,6 +68,7 @@ common common-deps
, unliftio
, unix
, heaps
, psqueues
common shared-properties
ghc-options: