Всё началось с того, что ко мне обратился Евгений из Prizm, с просьбой помочь реанимировать его graphene-ноду. В активных заверителях он состоит, но поднятой ноды не было, шли пропуски блоков. В текущих реалиях запускать ноду на устаревшей Ubuntu 18.04 - безумная затея, а обновлённого graphene-репозитория не было от слова "совсем". Поэтому было решено убить сразу двух зайцев: обновить Graphene и на нём запустить ноду-заверителя.
Текущая официальная ветка graphene-core не имеет явной версии. По крайней мере, я нигде не нашёл. Поэтому её обозначил условно версией 1.0 от которой и будем отталкиваться.
В ходе работ было подготовлено обновление graphene-core 1.1, которое собирается и работает на современном Linux. Раньше для этого были препятствия: 1.0 жила на Ubuntu 18.04–20.04, а на всём, что новее, сборка разваливалась, не доходя до бинаря. В 1.1 это исправлено, протокол при этом не затронут. Правила консенсуса, формат блоков и сериализация те же самые: это переезд на новый тулчейн плюс четыре run-time исправления, а не новая версия сети.
Зачем понадобился выпуск
Кодовая база Графена родом из BitShares-core, ветка develop, сентябрь 2019 года. Её мир состоит из GCC 7.5, Boost 1.65 и OpenSSL 1.1, и ничего новее она не переваривает. На практике это выглядело так: человек в 2026 году поднимает машину, ставит свежую Ubuntu, запускает сборку и получает стену ошибок компиляции. До работающей ноды он не добирался вообще.
Версия 1.1 это препятствие снимает. Работа по обновлению состояла из трёх частей:
- Переезд на новый тулчейн. CMake 4, Boost 1.89+ (компонент
systemи часть API Asio просто исчезли), OpenSSL 3, GCC 15, autoconf 2.72. - Исправление четырёх run-time ошибок. Компилятор их не ловил, они вылезли позже, когда нода заработала на новом стеке. Одна из них живёт и в 1.0.
- Исправление и прогон тестов. Они достались в наследство от BitShares вместе с параметрами чужой цепи. Теперь тесты проверяют именно нашу сеть.
Что осталось нетронутым: правила консенсуса, формат блоков, сериализация, протокол P2P. Хардфорка нет, договариваться о согласованном обновлении сети не нужно.
Сборка
Собирать надо на той самой машине, где нода будет жить: бинарники линкуются с системными libssl.so.3 и libcurl.so.4.
Было, версия 1.0:
- Проверенная ОС: Ubuntu 18.04
- Компилятор: GCC 7.5
- Boost: 1.65.1
- OpenSSL: 1.1.1
- CMake: 3.x
- Autoconf: 2.69
Стало, версия 1.1:
- Проверенная ОС: Ubuntu 26.04.1
- Компилятор: GCC 15.2
- Boost: 1.90
- OpenSSL: 3.5.5
- CMake: 4.2.3 (минимум 3.5)
- Autoconf: 2.72
Ubuntu 26.04.1 это единственная платформа, на которой 1.1 собиралась и тестировалась. Всё, что между 18.04 и 24.04, не проверялось.
Далее процесс состоит из трёх шагов: поставить зависимости, клонировать репозиторий вместе с саб-модулями, собрать.
Установка зависимостей, которые вам понадобятся в Ubuntu 26.04:
sudo apt-get install build-essential cmake git autoconf automake libtool pkg-config libboost-all-dev libssl-dev libreadline-dev zlib1g-dev libbz2-dev libcurl4-openssl-dev libzstd-dev libncurses-dev libicu-dev liblzma-dev doxygen
libicu-dev и liblzma-dev в этом списке новые, по сравнению с 1.0. Статический Boost требует их при линковке.
Вариант 1, Debug-версия. Берётся, когда ноду предстоит разбирать отладчиком или читать её стектрейсы строка в строку:
- witness_node 366 МБ
- cli_wallet 426 МБ
С параметрами -O3 -g: оптимизация та же, что в Release, плюс полная отладочная информация. Платите за неё размером: witness_node растёт до 366 МБ, cli_wallet до 426 МБ, сборка идёт дольше и требует больше места на диске. На скорость работы это практически не влияет: синхронизация с нуля заняла 1 ч 56 мин против 1 ч 59 мин у Release. Чистый -DCMAKE_BUILD_TYPE=Debug даст -O0 и заметно более медленный бинарник; в этой работе такая сборка не проверялась.
git clone --recurse-submodules -b fix/modern-toolchain https://github.com/carbon-witness/graphene-core.git
cd graphene-core && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG"
make -j2 witness_node cli_wallet
Вариант 2, ужатая версия. Берётся, когда от ноды требуется только работать: маленький VPS, образ контейнера, несколько одинаковых машин. Две последние строки с strip необязательны:
- witness_node 27 МБ, 20 МБ после strip
- cli_wallet 33 МБ до strip
git clone --recurse-submodules -b fix/modern-toolchain https://github.com/carbon-witness/graphene-core.git
cd graphene-core && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j2 witness_node cli_wallet
strip programs/witness_node/witness_node
strip programs/cli_wallet/cli_wallet
Без strip бинарники весят 27 МБ и 33 МБ, с strip нода сжимается до 20 МБ (размер кошелька после strip не замерялся). Цена есть: без таблицы символов стектрейсы в логе снова превращаются в голые адреса, поэтому для ноды, которую придётся диагностировать, strip лучше пропустить.
Оба варианта собирают только ноду и кошелёк, остальные девять программ из programs/ пропускаются. make без целей соберёт всё, включая тесты.
Сабмодули libraries/fc, fc/vendor/websocketpp и fc/vendor/editline теперь смотрят на форки carbon-witness. Если клон у вас уже есть, после обновления обязательно выполните git submodule sync --recursive && git submodule update --init --recursive. Иначе git молча подтянет старые адреса, и вы будете собирать не то.
Компилятору нужно 2–4 ГБ памяти на каждый параллельный поток, и он их честно возьмёт. На небольшом VPS собирайте с -j1 или -j2 и заранее добавьте своп, иначе сборку оборвёт OOM killer где-нибудь на середине.
Исправленные run-time ошибки
1) Нода зависала или падала при выходе. Стоило во время остановки прилететь RPC-запросу, и дальше было два сценария: SIGSEGV или вечное зависание. Дело в порядке разрушения: RPC-сервер умирал последним, уже после закрытия базы, и запрос, попавший в это окно, стучался в закрытую БД. Вторая половина беды в websocket: обработчики захватывали соединение по ссылке и ждали поток, которому уже нечего было выполнять. В 1.1 RPC-серверы останавливаются первыми, раньше плагинов, P2P и базы, а websocket-сервер считает незавершённую работу обработчиков и дожидается её, прежде чем всё сносить. Проверено двадцатью остановками под RPC-нагрузкой: все чистые, по 1–2 секунды. До исправления было три падения из трёх.
2) Слепые переводы из cli_wallet сеть отвергала. Этот баг есть и в 1.0. Кошелёк строил range proof с 49-битной мантиссой, константой, приехавшей из BitShares с его эмиссией 10¹⁵. Здесь GRAPHENE_MAX_SHARE_SUPPLY равен 10¹³, и blind_transfer_operation::validate честно отклонял любой слепой перевод со сдачей. Теперь размер доказательства выводится из константы самой цепочки (43 бита) и закреплён static_assert, чтобы история не повторилась.
3) Падение в Diffie–Hellman. OpenSSL 3 отказывается генерировать параметры DH короче 512 бит, а fc::diffie_hellman::generate_params после отказа шёл разыменовывать нулевой указатель. Рядом нашлась вещь потише, но неприятнее: без q OpenSSL 3 принимает любой генератор, то есть проверка параметров незаметно ослабла. Теперь generate_params возвращает false вместо падения, а validate пускает только генераторы 2 и 5, как было на OpenSSL 1.1. Сама нода DH не использует.
4) Стектрейсы без имён функций. Под Boost 1.90 трейсы, которые fc пишет в лог при падении, состояли из голых адресов. Теперь Boost.Stacktrace линкуется с libbacktrace от GCC, и в трейсах снова есть имена функций и строки исходников. Это ровно то, чего отчаянно не хватает в момент, когда ноду приходится анализировать в бою.
Проверка и тесты
Наборы тестов: было в 1.0 на новом тулчейне, стало в 1.1:
chain_test: было 5 из 304 | стало 304 из 304fc all_tests: было 60 из 65 | стало 65 из 65cli_test: было 14 из 15 | стало 15 из 15app_test: было 6 из 6 | стало 6 из 6
Набор chain_test давал 5 из 304 по прозаичной причине: genesis-время фикстуры не делится на трёхсекундный интервал блока этой цепочки, и почти все тесты падали на подготовке, так и не дойдя до собственной логики.
В этом и состоит характер почти всей работы по тестам. Набор приехал из BitShares и продолжал жить в его мире: интервал блока 5 секунд (в апстримных тестах эта пятёрка была зашита прямо в проверки, у нас же в genesis стоит 3), эмиссия 10¹⁵, адреса с префиксом BTS. Правились только тесты, не цепочка: время genesis округляется вниз до интервала блока, ожидания по интервалу взяты из GRAPHENE_DEFAULT_BLOCK_INTERVAL вместо числа 5, выплаты витнесам и бюджеты пересчитаны, force_settle_test получил задержку в 20 интервалов, ключи пересобраны с GRAPHENE_ADDRESS_PREFIX, а cli_confidential_tx_test теперь блайндит 10 млн вместо 100 млн, потому что 100 млн это вся эмиссия сети.
Отдельное замечание для тех, кто будет гонять тесты. /tmp на многих системах это tmpfs, то есть оперативная память. Тесты создают каталоги в /tmp/graphene-tmp/ и при прерывании не убирают за собой. Пары оборванных прогонов хватило, чтобы забить его целиком, а дальше посыпались десятки ошибок, на вид не связанных ни с чем: basic_ios::clear: iostream error и chain_test, убитый OOM. Запускайте с TMPDIR на настоящем диске, сэкономите себе вечер.
Производительность
Полная синхронизация с нуля, плагины по умолчанию, замерено дважды на 2 vCPU / 3,8 ГБ RAM:
- Ужатая версия: 1 ч 59 мин, скорость ~7800 блоков/с (замер до strip), пик RSS 230 МБ, данных 9,0 ГБ
- Debug-версия: 1 ч 56 мин, скорость ~7900 блоков/с, пик RSS 233 МБ, данных 9,0 ГБ
witness_node в ужатой версии весит 27 МБ и 20 МБ после strip.
Также был отдельный прогон на VPS с 2 vCPU / 2 ГБ и старым Xeon дал 2100–2600 блоков/с, который занял около 11 часов: оба ядра загружены на ~86%, iowait нулевой, входящего трафика около 1 МБ/с. Так что если синхронизация ползёт медленно, смотрите на однопоточную производительность ядра, а не на пиры и канал. Скорость синхронизации упирается в процессор, а не в сеть или диск.
В режиме по умолчанию (partial-operations = 1, max-ops-per-account = 100) памяти нужно немного: около 90 МБ на старте, 500–700 МБ во время синхронизации с нуля и около 480 МБ сразу после неё. Нода, запущенная на готовом каталоге данных, держится около 170 МБ: столько занимает наша нода на голове сети, каталог при этом весит 8,9 ГБ.
А вот полная история аккаунтов, когда нода хранит все операции по каждому аккаунту, это совсем другой разговор: требования, вероятно, намного выше, замеры не проводились. Включается partial-operations = 0, а max-ops-per-account из конфига надо убрать.
Обновление
Хардфорка нет, согласовывать переход не требуется. Ноды 1.1 и 1.0 говорят на одном протоколе и спокойно живут в одной сети.
Переход на новую версию.
Рекомендуется делать полностью на новой ноде с Ubuntu 26.04.1 LTS.
Нода без настроенных seed-nodes находит сеть по сидам, зашитым в бинарник, и это работает. Правда, на момент выпуска 1.1 из 14 адресов в libraries/egenesis/seed-nodes.txt отвечали ровно 3. Если у вас есть нода, доступная снаружи, имеет смысл опубликовать её адрес: список пора обновлять.
Если рабочий список нужен прямо сейчас, вот что отвечало на 20 сентября 2026 года, проверено TCP-подключением с живой ноды:
195.201.86.214:4646, есть вseed-nodes.txt78.46.200.101:1666, есть вseed-nodes.txtseed.graphene.fans:1776, есть вseed-nodes.txt37.27.115.162:177665.109.67.61:465295.217.59.180:4646
Последние три адреса в файл не входят: их нашла работающая нода через обмен адресами с пирами.
Остальные 11 записей из seed-nodes.txt не отвечают вовсе, а gph1.lexai.host больше не резолвится.
При настройке собственной ноды значение p2p-endpoint в конфиге лучше задать явно. Иначе нода на каждом запуске берёт случайный порт, и входящие пиры теряют её после каждого рестарта. p2p-endpoint = 0.0.0.0:1776 делает адрес постоянным, а 1776 слушает большая часть сети.
Найдено уже после выпуска и отложено до следующей версии, в код ничего не вносилось:
- База пиров стирается при каждом штатном завершении.
application::shutdown()закрывает P2P-ноду, но не обнуляет указатель, и деструктор закрывает её во второй раз. При закрытии база записывает содержимое и очищает его, так что вторая запись урезаетp2p/peers.jsonдо[]. Итог: любая нода стартует с холодного листа по зашитым сидам, а запоминание пиров не работает вовсе. witness_node --versionпечатает строкуgit describeот древнего тега BitShares (2.0.171025-minor-fix-1-…), а не версию Graphene. Требует актуализации.cli_walletне подключается поwss://. Тут два независимых дефекта в fc. TLS-контекст жёстко задан как TLS 1.0 (boost::asio::ssl::context::tlsv1выставляет и минимальную, и максимальную версию), и не выставляется SNI, поэтому хосты с несколькими сертификатами на одном адресе рвут рукопожатие.- Исходящие websocket-кадры уходят с нулевой маской. RFC 6455 требует, чтобы всё, что клиент шлёт серверу, маскировалось: клиент берёт случайный четырёхбайтовый ключ, кладёт его в заголовок кадра и XOR-ит им полезную нагрузку. Смысл не в криптографии, а в защите промежуточных прокси: без маскирования подобранное содержимое кадра кеширующий прокси может принять за отдельный HTTP-запрос и отравить кеш. В fc клиентские эндпоинты построены на серверных конфигах websocketpp, а там генератор случайных чисел подменён заглушкой:
config/core.hppобъявляетrng_typeкакrandom::none::int_generator, а егоoperator()всегда возвращает ноль. Серверу это не мешает, он кадры не маскирует, а вот клиент на том же конфиге получает ключ00 00 00 00: бит mask выставлен, но XOR с нулями ничего не меняет, и данные идут открытым текстом с предсказуемым ключом. Собеседники, снимающие маску без лишних вопросов, это глотают, поэтому с обычной нодой всё работает и баг не виден. Более строгие посредники такие кадры отбрасывают, и соединение встаёт. Корень тот же, что у истории с SNI, и лечение одно на оба случая: перевести клиентские эндпоинты fc наconfig::asio_clientиconfig::asio_tls_client.
Все четыре разобраны, исправления найдены. Придержали их намеренно, версия 1.1 уже запущена на ноде Евгения. И хотелось, чтобы этот выпуск обсуждался как один неизменный артефакт, без путаницы с разными сборками.
Код
Всего обновлено четыре репозитория, все форкнуты в аккаунт carbon-witness, ветка fix/modern-toolchain, 16 коммитов.
Graphene подключает часть зависимостей как git-сабмодули, и правки понадобились в трёх из них. Ссылки в .gitmodules ведут на форки. Коммиты разбиты по темам, чтобы каждое изменение можно было проверить или откатить отдельно.
- carbon-witness/graphene-core
e34d05b, 9 коммитов, 13 файлов, +114 / −28. Нода, кошелёк, блокчейн, тесты: CMake, остановка, кошелёк, адаптация тестов. - carbon-witness/graphene-fc
a108c38, 5 коммитов, 19 файлов, +275 / −116. Базовая библиотека: порт Asio, OpenSSL 3, websocket-сервер, тесты. - carbon-witness/websocketpp
c8a7a54, 1 коммит, 12 файлов, +151 / −161. Websocket-транспорт RPC: порт на Boost.Asio 1.87+. - carbon-witness/editline
a92d593, 1 коммит, 1 файл, +8 / −8. Строка вводаcli_wallet: configure под autoconf 2.72.
Порт websocketpp основан на zaphoyd/websocketpp#1164. Полный разбор по коммитам, включая все правки под новый тулчейн, лежит в RELEASE_NOTES_1.1.md.
Коммиты
Коммиты перечислены в том порядке, в котором вносились, ссылки ведут в форки carbon-witness. Коммиты graphene-core с заголовком fc: bump просто переводят ссылку сабмодуля на новый коммит graphene-fc.
graphene-core, 9 коммитов
88905cbbuild: support CMake 4 and Boost >= 1.89. Минимальная версия CMake поднята до 3.5, из списка компонентов Boost убран удалённыйsystem. После этого проект конфигурируется на Ubuntu 26.04.914ed91build: GCC 14+ template-body errors, submodules on carbon-witness forks. Для GCC 14 и новее отключена ошибка-Wtemplate-body. Сабмодульlibraries/fcпереведён на форк и первый коммит порта fc.343df6bfc: bump to uint128 endian buffer cast fix. Обновление fc: исправлена упаковка 128-битных чисел, из-за которой не собиралсяgraphene_chain.091e494app: include Boost.Range headers explicitly. Вapi.cppявно подключены заголовки Boost.Range: в Boost 1.90 они больше не приходят транзитивно. Последняя правка перед первой успешной сборкойwitness_node.119a45bfc: bump to Boost.Test static link fix. Обновление fc: тесты fc линкуются со статическим Boost.Test.e897485fc: bump to OpenSSL 3 DH fixes and symbolized stack traces. Обновление fc: исправлен Diffie–Hellman под OpenSSL 3, стектрейсы снова показывают имена функций.1702df2app: stop RPC servers before plugins, P2P and chain database on shutdown. При остановке серверы RPC закрываются первыми, до плагинов, P2P и базы. Устраняет падение ноды, когда запрос приходил во время выхода. Вместе с этим fc обновлён до исправления websocket-сервера.4d845bawallet: size range proofs to GRAPHENE_MAX_SHARE_SUPPLY; fix cli_confidential_tx_test. Разрядность доказательства диапазона в кошельке считается из запаса монет сети (43 бита вместо 49), и скрытые переводы снова проходят проверку. В тесте сумма уменьшена до 10 млн.e34d05btests: adapt chain_test to this chain's parameters.chain_testадаптирован под параметры нашей сети: блоки по 3 секунды, запас монет 10¹³ и префиксGPH: время генезиса, ожидаемые бюджеты, задержка погашения, выплаты воркерам. Результат 304 из 304.
graphene-fc, 5 коммитов
a4de83ebuild: support Ubuntu 26.04 toolchain (GCC 15, CMake 4, Boost 1.90, OpenSSL 3.5). Основной порт fc: Asio наio_context, новый резолвер,make_address_v4,host_name_verification,depth(),FIPS_mode_setтолько для OpenSSL ниже 3, недостающие<cstdint>, резервfwd<tcp_socket::impl>, флаг-std=gnu17для editline. Сабмодулиwebsocketppиeditlineпереведены на форки.37db5a8raw: cast uint128 endian buffer data() to const char*.endian_buffer::data()теперь возвращаетunsigned char*. Добавлено приведение типа, записываемые байты не меняются.b044859tests: fix Boost.Test build with static Boost 1.90. ФлагBOOST_TEST_DYN_LINKставится только при динамическом Boost. Вtime_testдобавлен заголовок дляBOOST_VERSION_NUMBER.516266ecrypto, stacktrace: OpenSSL 3 DH and symbolized stack traces. DH не падает при отказе OpenSSL и принимает только генераторы 2 и 5, тесты DH переведены на 512 бит. Стектрейсы используютlibbacktraceиз GCC.a108c38websocket: fix hang and crash when a server is destroyed during requests. Обработчики websocket-сервера захватывают соединение по значению. Сервер считает работу, отданную в основной поток, и при уничтожении ждёт закрытия сокета, соединений и незавершённых обработчиков. Проверено 20 остановками под нагрузкой RPC.
websocketpp, 1 коммит
c8a7a54asio: support Boost >= 1.87. Транспорт переведён на новый Asio:io_context,executor_work_guard,bind_executor,asio::post,expiry(),results_type. За основу взят PR #1164 из оригинального websocketpp.
editline, 1 коммит
a92d593build: quote AS_IF bodies for autoconf 2.72. ТелаAS_IFвconfigure.acвзяты в скобки, иначе autoconf 2.72 генерирует сломанныйconfigure.
Сейчас все изменения лежат в форках на github-аккаунте carbon-witness, но я готов оформить пул-реквесты в родительские репозитории. Их четыре, по числу форков:
carbon-witness/graphene-core→ graphene-blockchain/graphene-core: 9 коммитов, сборка, остановка ноды, кошелёк и тестыcarbon-witness/graphene-fc→ graphene-blockchain/graphene-fc: 5 коммитов, порт Asio и OpenSSL 3, статический Boost.Test, DH и стектрейсы, websocket-серверcarbon-witness/websocketpp→ bitshares/websocketpp, именно оттуда брался сабмодуль: 1 коммит, поддержка Boost ≥ 1.87 на основе PR #1164 из оригинальногоzaphoyd/websocketppcarbon-witness/editline→ troglobit/editline: 1 коммит, кавычки в телахAS_IFдля autoconf 2.72
Порядок здесь связанный, снизу вверх: сначала editline и websocketpp, потом fc, и только затем core. Пока правки в саб-модулях не приняты, .gitmodules в core вынуждена смотреть на мои форки. Коммиты разбиты по темам именно для такого ревью: каждый можно рассмотреть и принять отдельно. Если со стороны проекта есть интерес, напишите, и я оформлю всё в удобном виде: одной веткой целиком или отдельными PR.
Ждать PR с моей стороны при этом необязательно: форки публичные, ветка во всех четырёх одна и та же, так что коммиты можно забрать самостоятельно:
git remote add carbon-witness https://github.com/carbon-witness/graphene-core.git
git fetch carbon-witness fix/modern-toolchain
git log --oneline graphene..carbon-witness/fix/modern-toolchain
git cherry-pick <нужные коммиты>
Базовая ветка апстрима называется graphene, от неё и считается диапазон. Вместо cherry-pick можно взять ветку целиком через merge. Посмотреть дифф прямо на GitHub, без клонирования, можно по compare-ссылке между форками, для сабмодулей то же самое со своими парами репозиториев. PR сверх этого даёт разве что место для обсуждения и запуск их CI.
Планы на будущее
- Версия 1.2. Исправление ошибок, найденных после выпуска 1.1.
- Docker-сборка ноды. Чтобы нода поднималась одной командой.
- Windows-сборка ноды.
- Обновление web-UI.
- Современное мобильное приложение.
Спасибо, что дочитали до конца. Предлагаю всем желающим тестировать обновление и оставлять свои комментарии. Ведь самое полезное сейчас - это проверка дистрибутива другими участниками сообщества и отзывы от них.
Работа проводилась при содействии Claude Code.






