cryptofoundry

Связаться с cryptofoundry

Расскажите, что вы хотите создать или автоматизировать.

Электронная почта
[email protected]
ADAMANT Messenger
Открыть в ADAMANT
Ecosystem & Integrations

Push-уведомления без канала «клиент-сервер»: архитектура, компромиссы и известная проблема гонки при дерегистрации

Push-уведомления — это аспект, где приватный мессенджер наиболее уязвим. Кто-то должен узнать о доставке сообщения, и в iOS этот «кто-то» — Apple. В этой заметке описывается, как ADAMANT выстраивает этот процесс, чтобы сервис уведомлений получал минимум информации, каковы издержки такой архитектуры и один известный режим сбоя, с которым проект решил смириться, вместо того чтобы маскировать его.

Архитектура системы

В процессе участвуют четыре стороны: устройство пользователя, узел ADAMANT, сервис Apple Push Notification (APNs) и сервис уведомлений ADAMANT (ANS), управляемый cryptofoundry.

Регистрация происходит через блокчейн, а не через API. Устройство сначала запрашивает у APNs push-токен. Затем приложение шифрует {token, provider, action} открытым ключом ANS и отправляет это как сигнальное сообщение (тип чата 3, AIP-6) на ADM-адрес аккаунта ANS через выбранный пользователем узел. ANS опрашивает узлы на предмет транзакций, адресованных ему, расшифровывает их и сохраняет пару ADM-адрес → push-токен.

Доставка работает зеркально. ANS опрашивает сеть на наличие транзакций перевода (тип 0) и чата (тип 8), проверяет получателя по своему реестру и запрашивает у APNs доставку push-уведомления.

Что на самом деле узнает каждая сторона

Полезная нагрузка уведомления не содержит текста сообщения:

{
  "aps": {
    "alert": { "loc-key": "NotificationsService.NewMessage.BodySingle" },
    "badge": 1,
    "mutable-content": 1,
    "sound": "notification.mp3"
  },
  "push-recipient": "U1234567890123456",
  "txn-id": "7175005690801347553"
}

Тело уведомления — это ключ локализации, а не само сообщение. Notification Service Extension приложения берет txn-id, извлекает транзакцию из узла и расшифровывает её локально с помощью закрытого ключа пользователя перед отображением уведомления. Apple видит токен устройства, ADM-адрес, ID транзакции и время — но никогда не видит контент. Это реальное раскрытие данных, которое стоит признать прямо: если вы используете push-уведомления Apple, Apple узнает, что этот адрес получил что-то, и когда именно.

Более интересный аспект касается серверной стороны. Приложение никогда не открывает соединение с ANS. Сервис токенов iOS-клиента выполняет ровно два типа сетевых вызовов, оба к узлам ADM, и ни одно имя хоста push-сервиса не фигурирует в приложении. Таким образом, ANS видит только то, что уже является публичным в блокчейне, и, что критически важно, никогда не видит IP-адрес устройства. Пользователь, работающий через собственный узел или через Tor, вообще не взаимодействует с инфраструктурой cryptofoundry.

Это свойство — главная цель, и оно хрупко. Очевидная удобная функция — небольшой HTTPS-эндпоинт на ANS, чтобы клиент мог спросить «есть ли у вас еще мой токен?» — тихо уничтожила бы его. Это передало бы ANS IP-адрес устройства, превратило бы каждый запуск приложения в сигнал доступности устройства, обесценило бы выбор узла пользователем и создало бы единый блокируемый хост там, где сейчас есть подключаемый список узлов. Этот эндпоинт не будет добавлен.

Жизненный цикл регистрации и причины сбоев

Поскольку каналом является блокчейн, add (добавление) и remove (удаление) — это транзакции. Каждое сигнальное сообщение стоит как обычное сообщение в чате: constants.fees.chat_message = 100000 при fixedPoint = 1e8, то есть 0.001 ADM. Это важнее, чем кажется: пользователь с нулевым балансом не может отправить такое сообщение, что исключает стратегию «просто перерегистрироваться периодически» как метод обеспечения надежности.

Клиент хранит локальную копию токена, который, как он считает, зарегистрирован, и перерегистрируется только тогда, когда iOS выдает ему другой токен. Он никогда не спрашивает сервер, существует ли регистрация, потому что не может сделать это, не жертвуя приватностью.

Это порождает известный и воспроизводимый режим сбоя, который не требует потери данных на сервере. Пользователь выходит из системы, находясь в офлайне или работая через нестабильный узел; клиент очищает кэшированный токен и отправляет remove(T), но отправка не удается, поэтому транзакция сохраняется и повторяется при каждом последующем запуске приложения. Затем пользователь снова входит в систему на том же устройстве. iOS предоставляет тот же токен T. Локальный кэш пуст, поэтому клиент отправляет add(T), и это проходит успешно — состояние сервера теперь корректно. Но поставленная в очередь транзакция remove(T) в конечном итоге тоже проходит, попадая в блокчейн после add, поэтому ANS удаляет регистрацию, которую только что создал. В кэше клиента записано T, iOS продолжает предоставлять T, поэтому проверка «изменился ли токен?» никогда не срабатывает. Устройство дерегистрировано, и у него нет способа это заметить.

Уведомления молча перестают приходить. Единственный способ восстановления — ручное переключение Уведомления → Выкл → Push в приложении, что очищает локальный кэш и принудительно запускает новую регистрацию.

Почему это не исправляется в спешке

Это реальная ошибка, но узкоспецифичная: она требует неудачной дерегистрации, за которой следует повторная регистрация того же токена. Два быстрых решения, которые могли бы скрыть её, хуже, чем сама ошибка. HTTP-проверка меняет редкий скрытый сбой на постоянную утечку метаданных. Периодическая перерегистрация расходует средства пользователя по расписанию и просто не работает для тех, у кого 0 ADM.

Правильное решение — упорядочивание на стороне клиента, и оно полностью укладывается в архитектуру на базе блокчейна: сделать очередь повторных попыток «токенозависимой», чтобы ожидающая транзакция remove(T) отменялась, если позже проходит add(T), перестать считать «кэш пуст» состоянием регистрации и сохранять add с той же тщательностью, что и remove. Эта работа должна быть выполнена в клиентах.

Связанное следствие той же асимметрии: unregister может быть отправлен только для токена, который приложение еще помнит, поэтому переустановка приложения навсегда оставляет предыдущую регистрацию «сиротой». В настоящее время реестр содержит около 2600 строк для примерно 1700 уникальных адресов, причем один адрес занимает 251 из них. Строки удаляются, когда APNs сообщает, что токен недействителен, поэтому реестр самоочищается для тех, кто продолжает получать push-уведомления, но адрес, на который никто не пишет, никогда не проверяется и не очищается.

Развитие системы

Два вывода для тех, кто интегрируется с уведомлениями ADAMANT или создает клиент. Во-первых, не добавляйте обратный вызов от устройства к сервису уведомлений — это естественная архитектура, но именно она нарушает гарантию приватности. Во-вторых, рассматривайте регистрацию и дерегистрацию как упорядоченную пару. Они асинхронны, требуют повторных попыток, происходят в блокчейне и могут приходить в неправильном порядке. Упорядочивайте их явно, а не выводя состояние из локального кэша.