Як ми знайшли й виправили транзакції, що губилися за розривом 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-адреси.