NOTE: on-refchans-tldr CRDT с контролем прав доступа, но без полного онлайн-консенсуса. Проблема: участники могут искажать историю после их удаления из списка авторов. Решение: Транзакции требуют подтверждений от нескольких пиров. Только авторизованные пиры могут подписывать и публиковать транзакции. Транзакции подписываются автором и пиром, ссылаясь на актуальный ACL/HEAD. Учитываем только транзакции, удовлетворяющие правилам HEAD. Принимаем "запоздалые" записи в течение короткого периода после обновления HEAD. Подход: комбинация CRDT и консенсуса. У нас есть PROPOSE и VOTE, решения делаются через интерпретацию журнала или дополнительные транзакции. "Авторы" публикуют только валидные транзакции, достигая валидности при помощи сообщений эмеферного протокола (фазы VALIDATE/PRECOMMIT). 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 ведёт именно к периодическому запросу. В принципе, если не обрабатывать те логи, что мы уже обработали, это должно быть достаточно безвредно, если не очень часто. TODO: dont-spam-with-notifications Не рассылать нотификации, если контрагент отказывается их принимать. Для этого: сделать протокол (?) отказа (?) от рассылки --- допустим, из одной команды: RejectNotification Key ? И в госсипе (?) или рефчане/рефлоге с одной стороны --- присылать команду, что бы заткнулся, с другой стороны -- какой-то кэш устанавливать, и если ключ не истёк -- то не рассылать сообщения в эту ссылку. Потребует модификации gossip/broadcast