Як ми знайшли й виправили транзакції, що губилися за розривом nonce

Під сталим навантаженням у мережі з чотирьох валідаторів ми виявили, що відправник, який надсилає транзакції одна за одною, може втратити більшість із них. В одному 90-секундному запуску підтвердилося лише 14 з 291 переказу. Консенсус жодного разу не був під загрозою: блоки, корені стану й баланси були ідентичні на кожному вузлі. Губилися транзакції користувачів.

Як ми це знайшли

Ми перенесли мережеві тести в репозиторій (tools/quorum-net). Кожен запуск стартує чотири валідатори з генезису і зберігає свої параметри, логи кожного вузла та підсумок. Перший запуск на чистій збірці пройшов без помилок. Лише серія повторів показала, що дефект нестабільний і старший за поточну роботу. Логи дали точну картину: транзакція з nonce N ще не дійшла до пропозера блоку, тоді як N+1, N+2… вже потрапили в блок, не пройшли перевірку nonce і були викинуті з пулу.

Перша причина: як транзакції відбиралися в блок

Мемпул сортував транзакції відправника за nonce, але ніколи не перевіряв, що вони продовжують зафіксований. Ми подивилися, як із цим працюють go-ethereum (його набори pending і queued), Cosmos EVM, Aptos і Polygon Bor, що натрапив майже на той самий баг. У них спільне правило, і ми його перейняли: у блок іде лише безперервна послідовність nonce, а транзакція за розривом чекає в пулі. Відтоді кожен запуск мав нуль помилок nonce.

Друга причина, яку виявило виправлення: втрати в дорозі

Тепер валідатор, який так і не отримав транзакцію N, чесно чекав, і його блоки виходили порожніми. Вимірювання показали, що кожному пірові бракувало 2–5 транзакцій за запуск. Кожне повідомлення відкривало власне TCP-з'єднання, і наш захист від DoS — обмеження кількості з'єднань з однієї адреси — обривав частину з них. Виправлення наслідує Bitcoin, що повторює оголошення до доставки, і Solana, що повторно надсилає кожні дві секунди: вузол повторно надсилає свої очікувані транзакції одним пакетом, доки їх не включать у блок.

Третій крок: постійні з'єднання між пірами

Те саме обмеження обривало й повідомлення консенсусу, тож приблизно половина висот ухвалювалася лише в другому раунді, із зупинками до 12 секунд. Слідом за CometBFT і libp2p кожен вузол тепер тримає одне довготривале з'єднання з кожним піром. Захист від DoS не змінився; чесним пірам просто більше не потрібно відкривати сотні з'єднань.

Результат

За тих самих обмежень: підтверджено 296 з 296 переказів, найдовша зупинка — 1,8 секунди замість 12, і жодного розірваного з'єднання. Далі — рукостискання між пірами, підписане ML-DSA, щоб обмеження застосовувалися до ідентичності валідатора, а не до IP-адреси.

Усі новини