From c3f700a0248fb1ca1d40742e9b5ef379fc6ac428 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Thu, 25 Jan 2024 16:31:53 -0500 Subject: [PATCH 01/14] Always include `qc_info` in IF extension. --- libraries/chain/block_header_state.cpp | 14 +++--- libraries/chain/controller.cpp | 47 ++++++++----------- .../eosio/chain/block_header_state.hpp | 1 + .../hotstuff/instant_finality_extension.hpp | 4 +- unittests/block_header_tests.cpp | 8 ++-- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 2a2c440452..5f2c5e398e 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -127,17 +127,19 @@ block_header_state block_header_state::next(block_header_state_input& input) con // ++input.new_finalizer_policy->generation; - // add IF block header extension - // ----------------------------- uint16_t if_ext_id = instant_finality_extension::extension_id(); - auto if_entry = header_exts.lower_bound(if_ext_id); - auto& if_ext = std::get(if_entry->second); - - instant_finality_extension new_if_ext {if_ext.qc_info, + instant_finality_extension new_if_ext {qc_info_t{input.lib, false}, // use lib if we don't have anything better std::move(input.new_finalizer_policy), std::move(input.new_proposer_policy)}; if (input.qc_info) new_if_ext.qc_info = *input.qc_info; + else { + // copy previous qc_claim if we are not provided with a new one + // ------------------------------------------------------------ + auto if_entry = header_exts.lower_bound(if_ext_id); + if (if_entry != header_exts.end()) + new_if_ext.qc_info = std::get(if_entry->second).qc_info;; + } emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); result.header_exts.emplace(if_ext_id, std::move(new_if_ext)); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 0858dec20b..97c910a4cf 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -680,7 +680,8 @@ struct building_block { block_header_state_input bhs_input{ bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy), - qc_data ? qc_data->qc_info : std::optional{} + qc_data ? qc_data->qc_info : std::optional{}, + qc_data ? 0 : fork_db.apply([&](const auto& forkdb) { return forkdb.root()->block_num(); }) }; assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input), @@ -2836,16 +2837,17 @@ struct controller_impl { static std::optional extract_qc_data(const signed_block_ptr& b) { std::optional qc_data; - auto exts = b->validate_and_extract_extensions(); - if (auto entry = exts.lower_bound(quorum_certificate_extension::extension_id()); entry != exts.end()) { - auto& qc_ext = std::get(entry->second); - - // get the matching header extension... should always be present - auto hexts = b->validate_and_extract_header_extensions(); - auto if_entry = hexts.lower_bound(instant_finality_extension::extension_id()); - assert(if_entry != hexts.end()); + auto hexts = b->validate_and_extract_header_extensions(); + if (auto if_entry = hexts.lower_bound(instant_finality_extension::extension_id()); if_entry != hexts.end()) { auto& if_ext = std::get(if_entry->second); - return qc_data_t{ std::move(qc_ext.qc), *if_ext.qc_info }; + + // get the matching qc extension if present + auto exts = b->validate_and_extract_extensions(); + if (auto entry = exts.lower_bound(quorum_certificate_extension::extension_id()); entry != exts.end()) { + auto& qc_ext = std::get(entry->second); + return qc_data_t{ std::move(qc_ext.qc), if_ext.qc_info }; + } + return qc_data_t{ {}, if_ext.qc_info }; } return {}; } @@ -3089,18 +3091,9 @@ struct controller_impl { } const auto& if_ext = std::get(*header_ext); - if( !if_ext.qc_info ) { - EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, - block_validate_exception, - "A block must have QC claim if it provides QC block extension" ); - - // If header extension does not have QC claim, - // do not continue. - return; - } // extract QC claim - qc_info_t qc_claim{ *if_ext.qc_info }; + qc_info_t qc_claim{ if_ext.qc_info }; // A block should not be able to claim there was a QC on a block that // is prior to the transition to IF. @@ -3112,15 +3105,15 @@ struct controller_impl { auto prev_qc_info = prev_if_ext.qc_info; // validate QC claim against previous block QC info - if( prev_qc_info ) { + { // new claimed QC block nubmber cannot be smaller than previous block's - EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_info->last_qc_block_num, + EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_info.last_qc_block_num, block_validate_exception, "claimed last_qc_block_num (${n1}) must be equal to or greater than previous block's last_qc_block_num (${n2})", - ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_info->last_qc_block_num) ); + ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_info.last_qc_block_num) ); - if( qc_claim.last_qc_block_num == prev_qc_info->last_qc_block_num ) { - if( qc_claim.is_last_qc_strong == prev_qc_info->is_last_qc_strong ) { + if( qc_claim.last_qc_block_num == prev_qc_info.last_qc_block_num ) { + if( qc_claim.is_last_qc_strong == prev_qc_info.is_last_qc_strong ) { // QC block extension is redundant EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, block_validate_exception, @@ -3132,10 +3125,10 @@ struct controller_impl { } // new claimed QC must be stricter than previous if block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_info->is_last_qc_strong, + EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_info.is_last_qc_strong, block_validate_exception, "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same", - ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_info->is_last_qc_strong) ); + ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_info.is_last_qc_strong) ); } } diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 0e9185152e..57efe7f1c6 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -33,6 +33,7 @@ struct block_header_state_input : public building_block_input { std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy std::optional qc_info; // Comes from traversing branch from parent and calling get_best_qc() // assert(qc->block_num <= num_from_id(previous)); + uint32_t lib; // to be used when qc_info is not provided and we don't have an if extension in prev. }; struct block_header_state_core { diff --git a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp index a535414285..59af4fa8b6 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp @@ -15,7 +15,7 @@ struct instant_finality_extension : fc::reflect_init { static constexpr bool enforce_unique() { return true; } instant_finality_extension() = default; - instant_finality_extension(std::optional qc_info, + instant_finality_extension(qc_info_t qc_info, std::optional new_finalizer_policy, std::shared_ptr new_proposer_policy) : qc_info(qc_info), @@ -25,7 +25,7 @@ struct instant_finality_extension : fc::reflect_init { void reflector_init(); - std::optional qc_info; + qc_info_t qc_info; std::optional new_finalizer_policy; std::shared_ptr new_proposer_policy; }; diff --git a/unittests/block_header_tests.cpp b/unittests/block_header_tests.cpp index ae7d31421c..909637a964 100644 --- a/unittests/block_header_tests.cpp +++ b/unittests/block_header_tests.cpp @@ -31,8 +31,8 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) BOOST_REQUIRE( !!ext ); const auto& if_extension = std::get(*ext); - BOOST_REQUIRE_EQUAL( if_extension.qc_info->last_qc_block_num, last_qc_block_num ); - BOOST_REQUIRE_EQUAL( if_extension.qc_info->is_last_qc_strong, is_last_qc_strong ); + BOOST_REQUIRE_EQUAL( if_extension.qc_info.last_qc_block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_info.is_last_qc_strong, is_last_qc_strong ); BOOST_REQUIRE( !if_extension.new_finalizer_policy ); BOOST_REQUIRE( !if_extension.new_proposer_policy ); } @@ -91,8 +91,8 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) const auto& if_extension = std::get(*ext); - BOOST_REQUIRE_EQUAL( if_extension.qc_info->last_qc_block_num, last_qc_block_num ); - BOOST_REQUIRE_EQUAL( if_extension.qc_info->is_last_qc_strong, is_last_qc_strong ); + BOOST_REQUIRE_EQUAL( if_extension.qc_info.last_qc_block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_info.is_last_qc_strong, is_last_qc_strong ); BOOST_REQUIRE( !!if_extension.new_finalizer_policy ); BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->generation, 1u); From 84aeb006300860def5ff338048d0ce75485472fa Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 11:18:34 -0500 Subject: [PATCH 02/14] Make it work by updating the ih header extension in the block_state after transition. --- libraries/chain/block_header_state.cpp | 35 ++++++++++++------- libraries/chain/block_state.cpp | 7 +++- libraries/chain/controller.cpp | 3 +- .../eosio/chain/block_header_state.hpp | 1 - 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 5f2c5e398e..f8e2744092 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -88,10 +88,6 @@ block_header_state block_header_state::next(block_header_state_input& input) con result.activated_protocol_features = activated_protocol_features; } - // block_header_state_core - // ----------------------- - result.core = input.qc_info ? core.next(*input.qc_info) : core; - // proposal_mtree and finality_mtree // --------------------------------- // [greg todo] ?? @@ -127,20 +123,33 @@ block_header_state block_header_state::next(block_header_state_input& input) con // ++input.new_finalizer_policy->generation; + qc_info_t qc_info; uint16_t if_ext_id = instant_finality_extension::extension_id(); - instant_finality_extension new_if_ext {qc_info_t{input.lib, false}, // use lib if we don't have anything better - std::move(input.new_finalizer_policy), - std::move(input.new_proposer_policy)}; - if (input.qc_info) - new_if_ext.qc_info = *input.qc_info; - else { + + if (input.qc_info) { + qc_info = *input.qc_info; + dlog("qc_info from input -> final value: ${qci}",("qci", qc_info)); + } else { // copy previous qc_claim if we are not provided with a new one // ------------------------------------------------------------ - auto if_entry = header_exts.lower_bound(if_ext_id); - if (if_entry != header_exts.end()) - new_if_ext.qc_info = std::get(if_entry->second).qc_info;; + auto if_entry = header_exts.lower_bound(if_ext_id); + if (if_entry != header_exts.end()) { + const auto& qci = std::get(if_entry->second).qc_info; + qc_info = qci; + dlog("qc_info from existing extension -> final value: ${qci}",("qci",qc_info)); + } else { + assert(0); // we should always get a previous if extension when in IF mode. + } } + instant_finality_extension new_if_ext {qc_info, + std::move(input.new_finalizer_policy), + std::move(input.new_proposer_policy)}; + + // block_header_state_core + // ----------------------- + result.core = core.next(new_if_ext.qc_info); + emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); result.header_exts.emplace(if_ext_id, std::move(new_if_ext)); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 7773dafc64..916fe1a907 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -39,7 +39,9 @@ block_state::block_state(const block_state_legacy& bsp) { header = bsp.header; core.last_final_block_num = bsp.block_num(); // [if todo] instant transition is not acceptable activated_protocol_features = bsp.activated_protocol_features; - std::optional ext = bsp.block->extract_header_extension(instant_finality_extension::extension_id()); + + auto if_ext_id = instant_finality_extension::extension_id(); + std::optional ext = bsp.block->extract_header_extension(if_ext_id); assert(ext); // required by current transition mechanism const auto& if_extension = std::get(*ext); assert(if_extension.new_finalizer_policy); // required by current transition mechanism @@ -48,6 +50,9 @@ block_state::block_state(const block_state_legacy& bsp) { active_proposer_policy->active_time = bsp.timestamp(); active_proposer_policy->proposer_schedule = bsp.active_schedule; header_exts = bsp.header_exts; + + auto& updated_if_extension = std::get(header_exts.lower_bound(if_ext_id)->second); + updated_if_extension.qc_info = { bsp.block_num(), false }; block = bsp.block; validated = bsp.is_valid(); pub_keys_recovered = bsp._pub_keys_recovered; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 1847dbe7e0..d43d768ef8 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -684,8 +684,7 @@ struct building_block { block_header_state_input bhs_input{ bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy), - qc_data ? qc_data->qc_info : std::optional{}, - qc_data ? 0 : fork_db.apply([&](const auto& forkdb) { return forkdb.root()->block_num(); }) + qc_data ? qc_data->qc_info : std::optional{} }; assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input), diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 42ca68fbb5..e4696a06f7 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -33,7 +33,6 @@ struct block_header_state_input : public building_block_input { std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy std::optional qc_info; // Comes from traversing branch from parent and calling get_best_qc() // assert(qc->block_num <= num_from_id(previous)); - uint32_t lib; // to be used when qc_info is not provided and we don't have an if extension in prev. }; struct block_header_state_core { From 7b28ddd1325916567a0748f37c427cf8b7230974 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 11:28:30 -0500 Subject: [PATCH 03/14] Indentation and typo. --- libraries/chain/controller.cpp | 45 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 717d537b47..8f62544f30 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3126,32 +3126,31 @@ struct controller_impl { auto prev_qc_info = prev_if_ext.qc_info; // validate QC claim against previous block QC info - { - // new claimed QC block nubmber cannot be smaller than previous block's - EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_info.last_qc_block_num, - block_validate_exception, - "claimed last_qc_block_num (${n1}) must be equal to or greater than previous block's last_qc_block_num (${n2}). Block number: ${b}", - ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_info.last_qc_block_num)("b", b->block_num()) ); - - if( qc_claim.last_qc_block_num == prev_qc_info.last_qc_block_num ) { - if( qc_claim.is_last_qc_strong == prev_qc_info.is_last_qc_strong ) { - // QC block extension is redundant - EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, - block_validate_exception, - "A block should not provide QC block extension if QC claim is the same as previous block. Block number: ${b}", - ("b", b->block_num()) ); - - // if previous block's header extension has the same claim, just return - // (previous block already validated the claim) - return; - } - // new claimed QC must be stricter than previous if block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_info.is_last_qc_strong, + // new claimed QC block number cannot be smaller than previous block's + EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_info.last_qc_block_num, + block_validate_exception, + "claimed last_qc_block_num (${n1}) must be equal to or greater than previous block's last_qc_block_num (${n2}). Block number: ${b}", + ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_info.last_qc_block_num)("b", b->block_num()) ); + + if( qc_claim.last_qc_block_num == prev_qc_info.last_qc_block_num ) { + if( qc_claim.is_last_qc_strong == prev_qc_info.is_last_qc_strong ) { + // QC block extension is redundant + EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, block_validate_exception, - "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", - ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_info.is_last_qc_strong)("b", b->block_num()) ); + "A block should not provide QC block extension if QC claim is the same as previous block. Block number: ${b}", + ("b", b->block_num()) ); + + // if previous block's header extension has the same claim, just return + // (previous block already validated the claim) + return; } + + // new claimed QC must be stricter than previous if block number is the same + EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_info.is_last_qc_strong, + block_validate_exception, + "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", + ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_info.is_last_qc_strong)("b", b->block_num()) ); } if( block_exts.count(quorum_certificate_extension::extension_id()) == 0 ) { From 8b3b02be04afd4df91481ee374e402238f99215e Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 12:34:28 -0500 Subject: [PATCH 04/14] change `qc_info` -> `qc_claim` and `block_height` -> `block_num`. --- libraries/chain/block_header_state.cpp | 22 ++++++------ libraries/chain/block_state.cpp | 2 +- libraries/chain/controller.cpp | 36 +++++++++---------- .../eosio/chain/block_header_state.hpp | 8 ++--- .../include/eosio/chain/hotstuff/hotstuff.hpp | 4 +-- .../hotstuff/instant_finality_extension.hpp | 14 ++++---- unittests/block_header_tests.cpp | 16 ++++----- 7 files changed, 51 insertions(+), 51 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index f8e2744092..3c0da4bbfb 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -22,7 +22,7 @@ const vector& block_header_state::get_new_protocol_feature_activati #warning Add last_proposed_finalizer_policy_generation to snapshot_block_header_state_v3, see header file TODO -block_header_state_core block_header_state_core::next(qc_info_t incoming) const { +block_header_state_core block_header_state_core::next(qc_claim_t incoming) const { // no state change if last_qc_block_num is the same if (incoming.last_qc_block_num == this->last_qc_block_num) { return {*this}; @@ -123,32 +123,32 @@ block_header_state block_header_state::next(block_header_state_input& input) con // ++input.new_finalizer_policy->generation; - qc_info_t qc_info; + qc_claim_t qc_claim; uint16_t if_ext_id = instant_finality_extension::extension_id(); - if (input.qc_info) { - qc_info = *input.qc_info; - dlog("qc_info from input -> final value: ${qci}",("qci", qc_info)); + if (input.qc_claim) { + qc_claim = *input.qc_claim; + dlog("qc_claim from input -> final value: ${qci}",("qci", qc_claim)); } else { // copy previous qc_claim if we are not provided with a new one // ------------------------------------------------------------ auto if_entry = header_exts.lower_bound(if_ext_id); if (if_entry != header_exts.end()) { - const auto& qci = std::get(if_entry->second).qc_info; - qc_info = qci; - dlog("qc_info from existing extension -> final value: ${qci}",("qci",qc_info)); + const auto& qci = std::get(if_entry->second).qc_claim; + qc_claim = qci; + dlog("qc_claim from existing extension -> final value: ${qci}",("qci",qc_claim)); } else { assert(0); // we should always get a previous if extension when in IF mode. } } - instant_finality_extension new_if_ext {qc_info, + instant_finality_extension new_if_ext {qc_claim, std::move(input.new_finalizer_policy), std::move(input.new_proposer_policy)}; // block_header_state_core // ----------------------- - result.core = core.next(new_if_ext.qc_info); + result.core = core.next(new_if_ext.qc_claim); emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); result.header_exts.emplace(if_ext_id, std::move(new_if_ext)); @@ -213,7 +213,7 @@ block_header_state block_header_state::next(const signed_block_header& h, const block_header_state_input bhs_input{ bb_input, h.transaction_mroot, h.action_mroot, if_ext.new_proposer_policy, if_ext.new_finalizer_policy, - if_ext.qc_info }; + if_ext.qc_claim }; return next(bhs_input); } diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 171b659d1d..d41324c9d9 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -52,7 +52,7 @@ block_state::block_state(const block_state_legacy& bsp) { header_exts = bsp.header_exts; auto& updated_if_extension = std::get(header_exts.lower_bound(if_ext_id)->second); - updated_if_extension.qc_info = { bsp.block_num(), false }; + updated_if_extension.qc_claim = { bsp.block_num(), false }; block = bsp.block; validated = bsp.is_valid(); pub_keys_recovered = bsp._pub_keys_recovered; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 8f62544f30..4c4912df8d 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -660,13 +660,13 @@ struct building_block { for( auto it = branch.begin(); it != branch.end(); ++it ) { auto qc = (*it)->get_best_qc(); if( qc ) { - EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception, + EOS_ASSERT( qc->block_num <= block_header::num_from_id(parent_id()), block_validate_exception, "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", - ("a", qc->block_height)("p", block_header::num_from_id(parent_id())) ); + ("a", qc->block_num)("p", block_header::num_from_id(parent_id())) ); if( bb.parent.is_needed(*qc) ) { - qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }}; + qc_data = qc_data_t{ *qc, qc_claim_t{ qc->block_num, qc->qc.is_strong() }}; } else { - qc_data = qc_data_t{ {}, qc_info_t{ qc->block_height, qc->qc.is_strong() }}; + qc_data = qc_data_t{ {}, qc_claim_t{ qc->block_num, qc->qc.is_strong() }}; } break; } @@ -684,7 +684,7 @@ struct building_block { block_header_state_input bhs_input{ bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy), - qc_data ? qc_data->qc_info : std::optional{} + qc_data ? qc_data->qc_claim : std::optional{} }; assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input), @@ -2862,9 +2862,9 @@ struct controller_impl { auto exts = b->validate_and_extract_extensions(); if (auto entry = exts.lower_bound(quorum_certificate_extension::extension_id()); entry != exts.end()) { auto& qc_ext = std::get(entry->second); - return qc_data_t{ std::move(qc_ext.qc), if_ext.qc_info }; + return qc_data_t{ std::move(qc_ext.qc), if_ext.qc_claim }; } - return qc_data_t{ {}, if_ext.qc_info }; + return qc_data_t{ {}, if_ext.qc_claim }; } return {}; } @@ -3058,7 +3058,7 @@ struct controller_impl { const auto& qc_ext = std::get(block_exts. lower_bound(quorum_certificate_extension::extension_id())->second); const auto& received_qc = qc_ext.qc.qc; - const auto bsp = fork_db_fetch_bsp_by_num( bsp_in->previous(), qc_ext.qc.block_height ); + const auto bsp = fork_db_fetch_bsp_by_num( bsp_in->previous(), qc_ext.qc.block_num ); if( !bsp ) { return; } @@ -3113,7 +3113,7 @@ struct controller_impl { const auto& if_ext = std::get(*header_ext); // extract QC claim - qc_info_t qc_claim{ if_ext.qc_info }; + qc_claim_t qc_claim{ if_ext.qc_claim }; // A block should not be able to claim there was a QC on a block that // is prior to the transition to IF. @@ -3123,18 +3123,18 @@ struct controller_impl { ("b", b->block_num()) ); auto prev_if_ext = std::get(*prev_header_ext); - auto prev_qc_info = prev_if_ext.qc_info; + auto prev_qc_claim = prev_if_ext.qc_claim; // validate QC claim against previous block QC info // new claimed QC block number cannot be smaller than previous block's - EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_info.last_qc_block_num, + EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_claim.last_qc_block_num, block_validate_exception, "claimed last_qc_block_num (${n1}) must be equal to or greater than previous block's last_qc_block_num (${n2}). Block number: ${b}", - ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_info.last_qc_block_num)("b", b->block_num()) ); + ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_claim.last_qc_block_num)("b", b->block_num()) ); - if( qc_claim.last_qc_block_num == prev_qc_info.last_qc_block_num ) { - if( qc_claim.is_last_qc_strong == prev_qc_info.is_last_qc_strong ) { + if( qc_claim.last_qc_block_num == prev_qc_claim.last_qc_block_num ) { + if( qc_claim.is_last_qc_strong == prev_qc_claim.is_last_qc_strong ) { // QC block extension is redundant EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, block_validate_exception, @@ -3147,10 +3147,10 @@ struct controller_impl { } // new claimed QC must be stricter than previous if block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_info.is_last_qc_strong, + EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_claim.is_last_qc_strong, block_validate_exception, "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", - ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_info.is_last_qc_strong)("b", b->block_num()) ); + ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", b->block_num()) ); } if( block_exts.count(quorum_certificate_extension::extension_id()) == 0 ) { @@ -3194,10 +3194,10 @@ struct controller_impl { const auto& qc_proof = qc_ext.qc; // Check QC information in header extension and block extension match - EOS_ASSERT( qc_proof.block_height == qc_claim.last_qc_block_num, + EOS_ASSERT( qc_proof.block_num == qc_claim.last_qc_block_num, block_validate_exception, "QC block number (${n1}) in block extension does not match last_qc_block_num (${n2}) in header extension. Block number: ${b}", - ("n1", qc_proof.block_height)("n2", qc_claim.last_qc_block_num)("b", b->block_num()) ); + ("n1", qc_proof.block_num)("n2", qc_claim.last_qc_block_num)("b", b->block_num()) ); // Verify claimed strictness is the same as in proof EOS_ASSERT( qc_proof.qc.is_strong() == qc_claim.is_last_qc_strong, diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index e4696a06f7..ee5f9cb118 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -22,7 +22,7 @@ struct building_block_input { struct qc_data_t { std::optional qc; // Comes from traversing branch from parent and calling get_best_qc() // assert(qc->block_num <= num_from_id(previous)); - qc_info_t qc_info; // describes the above qc + qc_claim_t qc_claim; // describes what the above qc proves. }; // this struct can be extracted from a building block @@ -31,7 +31,7 @@ struct block_header_state_input : public building_block_input { digest_type action_mroot; // Compute root from building_block::action_receipt_digests std::shared_ptr new_proposer_policy; // Comes from building_block::new_proposer_policy std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy - std::optional qc_info; // Comes from traversing branch from parent and calling get_best_qc() + std::optional qc_claim; // Comes from traversing branch from parent and calling get_best_qc() // assert(qc->block_num <= num_from_id(previous)); }; @@ -41,7 +41,7 @@ struct block_header_state_core { std::optional last_qc_block_num; // uint32_t finalizer_policy_generation; // - block_header_state_core next(qc_info_t incoming) const; + block_header_state_core next(qc_claim_t incoming) const; }; struct block_header_state { @@ -81,7 +81,7 @@ struct block_header_state { // block descending from this need the provided qc in the block extension bool is_needed(const quorum_certificate& qc) const { - return !core.last_qc_block_num || qc.block_height > *core.last_qc_block_num; + return !core.last_qc_block_num || qc.block_num > *core.last_qc_block_num; } flat_set get_activated_protocol_features() const { return activated_protocol_features->protocol_features; } diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp index 9573637ad5..1e58b5e5f6 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -154,7 +154,7 @@ namespace eosio::chain { // -------------------- quorum_certificate ------------------------------------------------------- struct quorum_certificate { - uint32_t block_height; + uint32_t block_num; valid_quorum_certificate qc; }; @@ -256,4 +256,4 @@ FC_REFLECT(eosio::chain::hs_new_view_message, (high_qc)); FC_REFLECT(eosio::chain::finalizer_state, (b_leaf)(b_lock)(b_exec)(b_finality_violation)(block_exec)(pending_proposal_block)(v_height)(high_qc)(current_qc)(schedule)(proposals)); FC_REFLECT(eosio::chain::hs_message, (msg)); FC_REFLECT(eosio::chain::valid_quorum_certificate, (_proposal_id)(_proposal_digest)(_strong_votes)(_weak_votes)(_sig)); -FC_REFLECT(eosio::chain::quorum_certificate, (block_height)(qc)); +FC_REFLECT(eosio::chain::quorum_certificate, (block_num)(qc)); diff --git a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp index 59af4fa8b6..449c98b4cc 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp @@ -5,32 +5,32 @@ namespace eosio::chain { -struct qc_info_t { +struct qc_claim_t { uint32_t last_qc_block_num; // The block height of the most recent ancestor block that has a QC justification bool is_last_qc_strong; // Whether the QC for the block referenced by last_qc_block_height is strong or weak. }; struct instant_finality_extension : fc::reflect_init { - static constexpr uint16_t extension_id() { return 2; } + static constexpr uint16_t extension_id() { return 2; } static constexpr bool enforce_unique() { return true; } instant_finality_extension() = default; - instant_finality_extension(qc_info_t qc_info, + instant_finality_extension(qc_claim_t qc_claim, std::optional new_finalizer_policy, std::shared_ptr new_proposer_policy) : - qc_info(qc_info), + qc_claim(qc_claim), new_finalizer_policy(std::move(new_finalizer_policy)), new_proposer_policy(std::move(new_proposer_policy)) {} void reflector_init(); - qc_info_t qc_info; + qc_claim_t qc_claim; std::optional new_finalizer_policy; std::shared_ptr new_proposer_policy; }; } /// eosio::chain -FC_REFLECT( eosio::chain::qc_info_t, (last_qc_block_num)(is_last_qc_strong) ) -FC_REFLECT( eosio::chain::instant_finality_extension, (qc_info)(new_finalizer_policy)(new_proposer_policy) ) +FC_REFLECT( eosio::chain::qc_claim_t, (last_qc_block_num)(is_last_qc_strong) ) +FC_REFLECT( eosio::chain::instant_finality_extension, (qc_claim)(new_finalizer_policy)(new_proposer_policy) ) diff --git a/unittests/block_header_tests.cpp b/unittests/block_header_tests.cpp index 909637a964..b53541d42d 100644 --- a/unittests/block_header_tests.cpp +++ b/unittests/block_header_tests.cpp @@ -24,15 +24,15 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_info_t{last_qc_block_num, is_last_qc_strong}, std::optional{}, std::shared_ptr{}} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_last_qc_strong}, std::optional{}, std::shared_ptr{}} ) ); std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); BOOST_REQUIRE( !!ext ); const auto& if_extension = std::get(*ext); - BOOST_REQUIRE_EQUAL( if_extension.qc_info.last_qc_block_num, last_qc_block_num ); - BOOST_REQUIRE_EQUAL( if_extension.qc_info.is_last_qc_strong, is_last_qc_strong ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.last_qc_block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_last_qc_strong, is_last_qc_strong ); BOOST_REQUIRE( !if_extension.new_finalizer_policy ); BOOST_REQUIRE( !if_extension.new_proposer_policy ); } @@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_uniqueness_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_info_t{0, false}, {std::nullopt}, std::shared_ptr{}} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{0, false}, {std::nullopt}, std::shared_ptr{}} ) ); std::vector finalizers { {"test description", 50, fc::crypto::blslib::bls_public_key{"PUB_BLS_MPPeebAPxt/ibL2XPuZVGpADjGn+YEVPPoYmTZeBD6Ok2E19M8SnmDGSdZBf2qwSuJim+8H83EsTpEn3OiStWBiFeJYfVRLlEsZuSF0SYYwtVteY48n+KeE1IWzlSAkSyBqiGA==" }} }; @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_uniqueness_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_info_t{100, true}, new_finalizer_policy, new_proposer_policy} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{100, true}, new_finalizer_policy, new_proposer_policy} ) ); BOOST_CHECK_THROW(header.validate_and_extract_header_extensions(), invalid_block_header_extension); @@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_info_t{last_qc_block_num, is_last_qc_strong}, new_finalizer_policy, new_proposer_policy} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_last_qc_strong}, new_finalizer_policy, new_proposer_policy} ) ); std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); @@ -91,8 +91,8 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) const auto& if_extension = std::get(*ext); - BOOST_REQUIRE_EQUAL( if_extension.qc_info.last_qc_block_num, last_qc_block_num ); - BOOST_REQUIRE_EQUAL( if_extension.qc_info.is_last_qc_strong, is_last_qc_strong ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.last_qc_block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_last_qc_strong, is_last_qc_strong ); BOOST_REQUIRE( !!if_extension.new_finalizer_policy ); BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->generation, 1u); From 5779b6d0d4e9cb43307b044be99e7472d178b407 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 14:51:50 -0500 Subject: [PATCH 05/14] Remove no longer necessary check in `verify_qc_claim`. --- libraries/chain/controller.cpp | 44 ++++------------------------------ 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4c4912df8d..56ac2d0305 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3124,7 +3124,7 @@ struct controller_impl { auto prev_if_ext = std::get(*prev_header_ext); auto prev_qc_claim = prev_if_ext.qc_claim; - + auto qc_ext_id = quorum_certificate_extension::extension_id(); // validate QC claim against previous block QC info // new claimed QC block number cannot be smaller than previous block's @@ -3136,7 +3136,7 @@ struct controller_impl { if( qc_claim.last_qc_block_num == prev_qc_claim.last_qc_block_num ) { if( qc_claim.is_last_qc_strong == prev_qc_claim.is_last_qc_strong ) { // QC block extension is redundant - EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, + EOS_ASSERT( block_exts.count(qc_ext_id) == 0, block_validate_exception, "A block should not provide QC block extension if QC claim is the same as previous block. Block number: ${b}", ("b", b->block_num()) ); @@ -3153,44 +3153,10 @@ struct controller_impl { ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", b->block_num()) ); } - if( block_exts.count(quorum_certificate_extension::extension_id()) == 0 ) { - // If claim is a strong QC and there wasn't already an identical claim - // in the previous block (checked earlier), QC proof must be provided - EOS_ASSERT( !qc_claim.is_last_qc_strong, - block_validate_exception, - "QC block extension must be provided if the claimed QC block is strong. Block number: ${b}", - ("b", b->block_num()) ); - - // Conditions: - // * the claim is that the last QC is a weak QC, - // * it wasn't already satisfied by the claim in the prior block, - // * and there is no block extension - // Actions: - // * if it claims a block number lower than that of the current - // last irreversible block, then the new block should be rejected; - // * if it claims a block number greater than that of the current last - // irreversible block, then the new block must have a corresponding - // QC in the extension that must be validated; - // * if it claims a block number exactly equal to that of the current - // last irreversible block number, then the claim of the QC being - // weak can be accepted without a block extension. - // Notes: - // This block wouldn't advance LIB as it has no QC. - // So the LIB from that branch's POV should be the same as the - // last_final_block_num in the core of the block state it is building. - // It is safer to use that rather than if_irreversible_block_num - // because if_irreversible_block_num changes in non-deterministic ways - // as other blocks are received and validated. - // - EOS_ASSERT( qc_claim.last_qc_block_num == prev.core.last_final_block_num, - block_validate_exception, - "QC block extension must be included if the claimed QC block is not current irreversible block. Block number: ${b}", - ("b", b->block_num()) ); - - return; - } + EOS_ASSERT( block_exts.count(qc_ext_id) != 0, block_validate_exception, + "quorum_certificate_extension missing in block ${b} despite being claimed.",("b", b->block_num())); - const auto& qc_ext = std::get(block_exts.lower_bound(quorum_certificate_extension::extension_id())->second); + const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); const auto& qc_proof = qc_ext.qc; // Check QC information in header extension and block extension match From 73687683f3925bc2cce3a9bf6634c3765b015e79 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 15:52:55 -0500 Subject: [PATCH 06/14] Revert "Remove no longer necessary check in `verify_qc_claim`." This reverts commit 5779b6d0d4e9cb43307b044be99e7472d178b407. --- libraries/chain/controller.cpp | 43 ++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 56ac2d0305..94085d1748 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3090,10 +3090,11 @@ struct controller_impl { // extract current block extension and previous header extension auto block_exts = b->validate_and_extract_extensions(); std::optional prev_header_ext = prev.header.extract_header_extension(instant_finality_extension::extension_id()); + auto qc_ext_id = quorum_certificate_extension::extension_id(); std::optional header_ext = b->extract_header_extension(instant_finality_extension::extension_id()); if( !header_ext ) { - EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) == 0, + EOS_ASSERT( block_exts.count(qc_ext_id) == 0, block_validate_exception, "A block must have QC header extension if it provides QC block extension. Block number: ${b}", ("b", b->block_num()) ); @@ -3124,7 +3125,7 @@ struct controller_impl { auto prev_if_ext = std::get(*prev_header_ext); auto prev_qc_claim = prev_if_ext.qc_claim; - auto qc_ext_id = quorum_certificate_extension::extension_id(); + // validate QC claim against previous block QC info // new claimed QC block number cannot be smaller than previous block's @@ -3153,8 +3154,42 @@ struct controller_impl { ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", b->block_num()) ); } - EOS_ASSERT( block_exts.count(qc_ext_id) != 0, block_validate_exception, - "quorum_certificate_extension missing in block ${b} despite being claimed.",("b", b->block_num())); + if( block_exts.count(qc_ext_id) == 0 ) { + // If claim is a strong QC and there wasn't already an identical claim + // in the previous block (checked earlier), QC proof must be provided + EOS_ASSERT( !qc_claim.is_last_qc_strong, + block_validate_exception, + "QC block extension must be provided if the claimed QC block is strong. Block number: ${b}", + ("b", b->block_num()) ); + + // Conditions: + // * the claim is that the last QC is a weak QC, + // * it wasn't already satisfied by the claim in the prior block, + // * and there is no block extension + // Actions: + // * if it claims a block number lower than that of the current + // last irreversible block, then the new block should be rejected; + // * if it claims a block number greater than that of the current last + // irreversible block, then the new block must have a corresponding + // QC in the extension that must be validated; + // * if it claims a block number exactly equal to that of the current + // last irreversible block number, then the claim of the QC being + // weak can be accepted without a block extension. + // Notes: + // This block wouldn't advance LIB as it has no QC. + // So the LIB from that branch's POV should be the same as the + // last_final_block_num in the core of the block state it is building. + // It is safer to use that rather than if_irreversible_block_num + // because if_irreversible_block_num changes in non-deterministic ways + // as other blocks are received and validated. + // + EOS_ASSERT( qc_claim.last_qc_block_num == prev.core.last_final_block_num, + block_validate_exception, + "QC block extension must be included if the claimed QC block is not current irreversible block. Block number: ${b}", + ("b", b->block_num()) ); + + return; + } const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); const auto& qc_proof = qc_ext.qc; From ba9b519d13907ebd2b7bd44f0586595c29ed5a02 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 16:12:15 -0500 Subject: [PATCH 07/14] Fix eror text. --- libraries/chain/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 94085d1748..e1ed2677ed 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3096,7 +3096,7 @@ struct controller_impl { if( !header_ext ) { EOS_ASSERT( block_exts.count(qc_ext_id) == 0, block_validate_exception, - "A block must have QC header extension if it provides QC block extension. Block number: ${b}", + "A block must have a finality header extension if it provides QC block extension. Block number: ${b}", ("b", b->block_num()) ); // If the previous block has the QC header extension, From 4cbdb1eb16bd5609442d3a3489d93d1b94e3666a Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 18:17:34 -0500 Subject: [PATCH 08/14] Update `verify_qc_claim` as discussed with Areg. --- libraries/chain/controller.cpp | 187 +++++++++++++++------------------ 1 file changed, 82 insertions(+), 105 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index e1ed2677ed..bf4e22e338 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3025,29 +3025,6 @@ struct controller_impl { } } - // thread safe, expected to be called from thread other than the main thread - block_handle create_block_state_i( const block_id_type& id, const signed_block_ptr& b, const block_header_state_legacy& prev ) { - auto trx_mroot = calculate_trx_merkle( b->transactions, false ); - EOS_ASSERT( b->transaction_mroot == trx_mroot, block_validate_exception, - "invalid block transaction merkle root ${b} != ${c}", ("b", b->transaction_mroot)("c", trx_mroot) ); - - const bool skip_validate_signee = false; - auto bsp = std::make_shared( - prev, - b, - protocol_features.get_protocol_feature_set(), - [this]( block_timestamp_type timestamp, - const flat_set& cur_features, - const vector& new_features ) - { check_protocol_features( timestamp, cur_features, new_features ); }, - skip_validate_signee - ); - - EOS_ASSERT( id == bsp->id(), block_validate_exception, - "provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) ); - return block_handle{bsp}; - } - // expected to be called from application thread as it modifies bsp->valid_qc, void integrate_received_qc_to_block(const block_state_ptr& bsp_in) { // extract QC from block extension @@ -3086,132 +3063,109 @@ struct controller_impl { // and quorum_certificate_extension in block extension are valid. // Called from net-threads. It is thread safe as signed_block is never modified // after creation. + // ----------------------------------------------------------------------------- void verify_qc_claim( const block_id_type& id, const signed_block_ptr& b, const block_header_state& prev ) { - // extract current block extension and previous header extension - auto block_exts = b->validate_and_extract_extensions(); - std::optional prev_header_ext = prev.header.extract_header_extension(instant_finality_extension::extension_id()); auto qc_ext_id = quorum_certificate_extension::extension_id(); + auto if_ext_id = instant_finality_extension::extension_id(); - std::optional header_ext = b->extract_header_extension(instant_finality_extension::extension_id()); - if( !header_ext ) { - EOS_ASSERT( block_exts.count(qc_ext_id) == 0, - block_validate_exception, - "A block must have a finality header extension if it provides QC block extension. Block number: ${b}", - ("b", b->block_num()) ); + // extract current block extension and previous header extension + auto block_exts = b->validate_and_extract_extensions(); + std::optional prev_header_ext = prev.header.extract_header_extension(if_ext_id); + std::optional header_ext = b->extract_header_extension(if_ext_id); - // If the previous block has the QC header extension, - // then the current block must also have the header extension. - EOS_ASSERT( !prev_header_ext, - block_validate_exception, - "A block must have QC header extension because its previous block has the extension. Block number: ${b}", - ("b", b->block_num()) ); + bool qc_extension_present = block_exts.count(qc_ext_id) != 0; + uint32_t block_num = b->block_num(); - // If header extension does not have instant_finality_extension, - // do not continue. + if( !header_ext ) { + // If there is no header extension, ensure the block does not have a QC and also the previous + // block doesn't have a header extension either. Then return early. + // ------------------------------------------------------------------------------------------ + EOS_ASSERT( !qc_extension_present, block_validate_exception, + "Block #${b} includes a QC block extension, but doesn't have a finality header extension", + ("b", block_num) ); + + EOS_ASSERT( !prev_header_ext, block_validate_exception, + "Block #${b} doesn't have a finality header extension even though its predecessor does. Block number: ${b}", + ("b", block_num) ); return; } - const auto& if_ext = std::get(*header_ext); + assert(header_ext); + const auto& if_ext = std::get(*header_ext); + const auto qc_claim = if_ext.qc_claim; - // extract QC claim - qc_claim_t qc_claim{ if_ext.qc_claim }; + // If there is a header extension, but the previous block does not have a header extension, + // ensure the block does not have a QC and the QC claim of the current block has a last_qc_block_num + // of the current block’s number and that it is a claim of a weak QC. Then return early. + // ------------------------------------------------------------------------------------------------- + if (!prev_header_ext) { + EOS_ASSERT( !qc_extension_present && qc_claim.last_qc_block_num == block_num && qc_claim.is_last_qc_strong == false, + block_validate_exception, + "Block #${b}, which is the finality transition block, doesn't have the expected extensions", + ("b", block_num) ); + return; + } - // A block should not be able to claim there was a QC on a block that - // is prior to the transition to IF. - EOS_ASSERT( prev_header_ext, - block_validate_exception, - "Previous header extension must include instant_finality_extension. Block number: ${b}", - ("b", b->block_num()) ); + // at this point both current block and its parent have IF extensions, and we are past the + // IF transition block + // ---------------------------------------------------------------------------------------- + assert(header_ext && prev_header_ext); - auto prev_if_ext = std::get(*prev_header_ext); - auto prev_qc_claim = prev_if_ext.qc_claim; + const auto& prev_if_ext = std::get(*prev_header_ext); + const auto prev_qc_claim = prev_if_ext.qc_claim; // validate QC claim against previous block QC info // new claimed QC block number cannot be smaller than previous block's EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_claim.last_qc_block_num, block_validate_exception, - "claimed last_qc_block_num (${n1}) must be equal to or greater than previous block's last_qc_block_num (${n2}). Block number: ${b}", - ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_claim.last_qc_block_num)("b", b->block_num()) ); + "Block #${b} claims a last_qc_block_num (${n1}) less than the previous block's (${n2})", + ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_claim.last_qc_block_num)("b", block_num) ); if( qc_claim.last_qc_block_num == prev_qc_claim.last_qc_block_num ) { if( qc_claim.is_last_qc_strong == prev_qc_claim.is_last_qc_strong ) { // QC block extension is redundant - EOS_ASSERT( block_exts.count(qc_ext_id) == 0, - block_validate_exception, - "A block should not provide QC block extension if QC claim is the same as previous block. Block number: ${b}", - ("b", b->block_num()) ); + EOS_ASSERT( !qc_extension_present, block_validate_exception, + "Block #${b} should not provide a QC block extension since its QC claim is the same as the previous block's", + ("b", block_num) ); // if previous block's header extension has the same claim, just return // (previous block already validated the claim) return; } - // new claimed QC must be stricter than previous if block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong || !prev_qc_claim.is_last_qc_strong, - block_validate_exception, + // new claimed QC must be stronger than previous if the claimed block number is the same + EOS_ASSERT( qc_claim.is_last_qc_strong && qc_extension_present, block_validate_exception, "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", - ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", b->block_num()) ); + ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", block_num) ); } - if( block_exts.count(qc_ext_id) == 0 ) { + if( !qc_extension_present ) { // If claim is a strong QC and there wasn't already an identical claim // in the previous block (checked earlier), QC proof must be provided - EOS_ASSERT( !qc_claim.is_last_qc_strong, - block_validate_exception, - "QC block extension must be provided if the claimed QC block is strong. Block number: ${b}", - ("b", b->block_num()) ); - - // Conditions: - // * the claim is that the last QC is a weak QC, - // * it wasn't already satisfied by the claim in the prior block, - // * and there is no block extension - // Actions: - // * if it claims a block number lower than that of the current - // last irreversible block, then the new block should be rejected; - // * if it claims a block number greater than that of the current last - // irreversible block, then the new block must have a corresponding - // QC in the extension that must be validated; - // * if it claims a block number exactly equal to that of the current - // last irreversible block number, then the claim of the QC being - // weak can be accepted without a block extension. - // Notes: - // This block wouldn't advance LIB as it has no QC. - // So the LIB from that branch's POV should be the same as the - // last_final_block_num in the core of the block state it is building. - // It is safer to use that rather than if_irreversible_block_num - // because if_irreversible_block_num changes in non-deterministic ways - // as other blocks are received and validated. - // - EOS_ASSERT( qc_claim.last_qc_block_num == prev.core.last_final_block_num, - block_validate_exception, - "QC block extension must be included if the claimed QC block is not current irreversible block. Block number: ${b}", - ("b", b->block_num()) ); - + EOS_ASSERT( !qc_claim.is_last_qc_strong, block_validate_exception, + "Block #${b} has a strong qc claim, but no qc block extension", ("b", block_num) ); return; } - const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); + const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); const auto& qc_proof = qc_ext.qc; // Check QC information in header extension and block extension match - EOS_ASSERT( qc_proof.block_num == qc_claim.last_qc_block_num, - block_validate_exception, - "QC block number (${n1}) in block extension does not match last_qc_block_num (${n2}) in header extension. Block number: ${b}", - ("n1", qc_proof.block_num)("n2", qc_claim.last_qc_block_num)("b", b->block_num()) ); + EOS_ASSERT( qc_proof.block_num == qc_claim.last_qc_block_num, block_validate_exception, + "Block #${b}: Mismatch between qc.block_num (${n1}) in block extension and last_qc_block_num (${n2}) in header extension", + ("n1", qc_proof.block_num)("n2", qc_claim.last_qc_block_num)("b", block_num) ); // Verify claimed strictness is the same as in proof - EOS_ASSERT( qc_proof.qc.is_strong() == qc_claim.is_last_qc_strong, - block_validate_exception, - "QC is_strong (${s1}) in block extension does not match is_last_qc_strong (${22}) in header extension. Block number: ${b}", - ("s1", qc_proof.qc.is_strong())("s2", qc_claim.is_last_qc_strong)("b", b->block_num()) ); + EOS_ASSERT( qc_proof.qc.is_strong() == qc_claim.is_last_qc_strong, block_validate_exception, + "QC is_strong (${s1}) in block extension does not match is_last_qc_strong (${s2}) in header extension. Block number: ${b}", + ("s1", qc_proof.qc.is_strong())("s2", qc_claim.is_last_qc_strong)("b", block_num) ); // find the claimed block's block state on branch of id auto bsp = fork_db_fetch_bsp_by_num( prev.id, qc_claim.last_qc_block_num ); - EOS_ASSERT( bsp, - block_validate_exception, + EOS_ASSERT( bsp, block_validate_exception, "Block state was not found in forkdb for last_qc_block_num ${q}. Block number: ${b}", - ("q", qc_claim.last_qc_block_num)("b", b->block_num()) ); + ("q", qc_claim.last_qc_block_num)("b", block_num) ); // verify the QC proof against the claimed block bsp->verify_qc(qc_proof.qc); @@ -3246,6 +3200,29 @@ struct controller_impl { return block_handle{bsp}; } + // thread safe, expected to be called from thread other than the main thread + block_handle create_block_state_i( const block_id_type& id, const signed_block_ptr& b, const block_header_state_legacy& prev ) { + auto trx_mroot = calculate_trx_merkle( b->transactions, false ); + EOS_ASSERT( b->transaction_mroot == trx_mroot, block_validate_exception, + "invalid block transaction merkle root ${b} != ${c}", ("b", b->transaction_mroot)("c", trx_mroot) ); + + const bool skip_validate_signee = false; + auto bsp = std::make_shared( + prev, + b, + protocol_features.get_protocol_feature_set(), + [this]( block_timestamp_type timestamp, + const flat_set& cur_features, + const vector& new_features ) + { check_protocol_features( timestamp, cur_features, new_features ); }, + skip_validate_signee + ); + + EOS_ASSERT( id == bsp->id(), block_validate_exception, + "provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) ); + return block_handle{bsp}; + } + std::future create_block_handle_future( const block_id_type& id, const signed_block_ptr& b ) { EOS_ASSERT( b, block_validate_exception, "null block" ); From 2e0783ab58e0589a3224047c3f2a831e33ac305d Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 19:30:14 -0500 Subject: [PATCH 09/14] Fix `verify_qc_claim` according to Areg's feedback, and store correct claim in transitional IF extension. --- libraries/chain/block_header_state_legacy.cpp | 5 +++-- libraries/chain/block_state.cpp | 3 --- libraries/chain/controller.cpp | 12 ++++-------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/libraries/chain/block_header_state_legacy.cpp b/libraries/chain/block_header_state_legacy.cpp index 7595d2d5f2..392770601c 100644 --- a/libraries/chain/block_header_state_legacy.cpp +++ b/libraries/chain/block_header_state_legacy.cpp @@ -208,9 +208,10 @@ namespace eosio::chain { } if (new_finalizer_policy) { - new_finalizer_policy->generation = 1; // TODO: do we allow more than one set during transition + new_finalizer_policy->generation = 0; + // set current block_num as qc_claim.last_qc_block_num in the IF extension emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack(instant_finality_extension{ {}, std::move(new_finalizer_policy), {} })); + fc::raw::pack(instant_finality_extension{ { block_num, false }, std::move(new_finalizer_policy), {} })); } return h; diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index d41324c9d9..83bdec97dc 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -50,9 +50,6 @@ block_state::block_state(const block_state_legacy& bsp) { active_proposer_policy->active_time = bsp.timestamp(); active_proposer_policy->proposer_schedule = bsp.active_schedule; header_exts = bsp.header_exts; - - auto& updated_if_extension = std::get(header_exts.lower_bound(if_ext_id)->second); - updated_if_extension.qc_claim = { bsp.block_num(), false }; block = bsp.block; validated = bsp.is_valid(); pub_keys_recovered = bsp._pub_keys_recovered; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index bf4e22e338..5115be8da8 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3135,18 +3135,14 @@ struct controller_impl { } // new claimed QC must be stronger than previous if the claimed block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong && qc_extension_present, block_validate_exception, + EOS_ASSERT( qc_claim.is_last_qc_strong, block_validate_exception, "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", block_num) ); } - if( !qc_extension_present ) { - // If claim is a strong QC and there wasn't already an identical claim - // in the previous block (checked earlier), QC proof must be provided - EOS_ASSERT( !qc_claim.is_last_qc_strong, block_validate_exception, - "Block #${b} has a strong qc claim, but no qc block extension", ("b", block_num) ); - return; - } + // At this point, we are making a new claim in this block, so it better include a QC to justify this claim. + EOS_ASSERT( qc_extension_present, block_validate_exception, + "Block #${b} is making a new finality claim, but doesn't include a qc to justify this claim", ("b", block_num) ); const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); const auto& qc_proof = qc_ext.qc; From d47e0c22e2a355c31bb37d2f5574152e29071fcc Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 26 Jan 2024 20:23:00 -0500 Subject: [PATCH 10/14] Update test accordingly to change for first finalizer generation == 0. --- unittests/api_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 7c7935c234..46204542fc 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -3883,7 +3883,7 @@ BOOST_AUTO_TEST_CASE(set_finalizer_test) { try { std::optional fin_policy = std::get(*ext).new_finalizer_policy; BOOST_TEST(!!fin_policy); BOOST_TEST(fin_policy->finalizers.size() == finalizers.size()); - BOOST_TEST(fin_policy->generation == 1); + BOOST_TEST(fin_policy->generation == 0); BOOST_TEST(fin_policy->threshold == finalizers.size() / 3 * 2 + 1); // currently transition happens immediately after set_finalizer block From 7fc28e48d40b29ec4fa0217c12528625585203ab Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 27 Jan 2024 00:50:05 -0500 Subject: [PATCH 11/14] Revert "Update test accordingly to change for first finalizer generation == 0." This reverts commit d47e0c22e2a355c31bb37d2f5574152e29071fcc. --- unittests/api_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 46204542fc..7c7935c234 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -3883,7 +3883,7 @@ BOOST_AUTO_TEST_CASE(set_finalizer_test) { try { std::optional fin_policy = std::get(*ext).new_finalizer_policy; BOOST_TEST(!!fin_policy); BOOST_TEST(fin_policy->finalizers.size() == finalizers.size()); - BOOST_TEST(fin_policy->generation == 0); + BOOST_TEST(fin_policy->generation == 1); BOOST_TEST(fin_policy->threshold == finalizers.size() / 3 * 2 + 1); // currently transition happens immediately after set_finalizer block From ed97bf66363fb6d6309fb985bd73b2b33f3c4657 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 27 Jan 2024 00:50:45 -0500 Subject: [PATCH 12/14] Revert initial finalizer generation to 1, as we don't want to change the reference-contracts tests just now., --- .clang-format | 23 +++++++++++++++++++ libraries/chain/block_header_state_legacy.cpp | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..53620b112b --- /dev/null +++ b/.clang-format @@ -0,0 +1,23 @@ +BasedOnStyle: LLVM +BraceWrapping: + SplitEmptyFunction: false + SplitEmptyRecord: false +IndentWidth: 3 +ColumnLimit: 120 +PointerAlignment: Left +AccessModifierOffset: -3 +AlwaysBreakTemplateDeclarations: Yes +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignEscapedNewlines: Left +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Inline +BreakBeforeBraces: Custom +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +ConstructorInitializerIndentWidth: 3 +ContinuationIndentWidth: 3 +MaxEmptyLinesToKeep: 3 +SortIncludes: Never \ No newline at end of file diff --git a/libraries/chain/block_header_state_legacy.cpp b/libraries/chain/block_header_state_legacy.cpp index 392770601c..cd4a8db829 100644 --- a/libraries/chain/block_header_state_legacy.cpp +++ b/libraries/chain/block_header_state_legacy.cpp @@ -208,7 +208,7 @@ namespace eosio::chain { } if (new_finalizer_policy) { - new_finalizer_policy->generation = 0; + new_finalizer_policy->generation = 1; // set current block_num as qc_claim.last_qc_block_num in the IF extension emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), fc::raw::pack(instant_finality_extension{ { block_num, false }, std::move(new_finalizer_policy), {} })); From 2b3f787016a424cc3583439df310795aa020e9e7 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 28 Jan 2024 21:30:54 -0500 Subject: [PATCH 13/14] Simplify redundent error message in `FC_ASSERT`, and other formatting changes. --- libraries/chain/controller.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 5115be8da8..c9196fab90 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3080,12 +3080,14 @@ struct controller_impl { // If there is no header extension, ensure the block does not have a QC and also the previous // block doesn't have a header extension either. Then return early. // ------------------------------------------------------------------------------------------ - EOS_ASSERT( !qc_extension_present, block_validate_exception, + EOS_ASSERT( !qc_extension_present, + block_validate_exception, "Block #${b} includes a QC block extension, but doesn't have a finality header extension", ("b", block_num) ); - EOS_ASSERT( !prev_header_ext, block_validate_exception, - "Block #${b} doesn't have a finality header extension even though its predecessor does. Block number: ${b}", + EOS_ASSERT( !prev_header_ext, + block_validate_exception, + "Block #${b} doesn't have a finality header extension even though its predecessor does.", ("b", block_num) ); return; } @@ -3125,7 +3127,8 @@ struct controller_impl { if( qc_claim.last_qc_block_num == prev_qc_claim.last_qc_block_num ) { if( qc_claim.is_last_qc_strong == prev_qc_claim.is_last_qc_strong ) { // QC block extension is redundant - EOS_ASSERT( !qc_extension_present, block_validate_exception, + EOS_ASSERT( !qc_extension_present, + block_validate_exception, "Block #${b} should not provide a QC block extension since its QC claim is the same as the previous block's", ("b", block_num) ); @@ -3135,31 +3138,36 @@ struct controller_impl { } // new claimed QC must be stronger than previous if the claimed block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong, block_validate_exception, + EOS_ASSERT( qc_claim.is_last_qc_strong, + block_validate_exception, "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", block_num) ); } // At this point, we are making a new claim in this block, so it better include a QC to justify this claim. - EOS_ASSERT( qc_extension_present, block_validate_exception, + EOS_ASSERT( qc_extension_present, + block_validate_exception, "Block #${b} is making a new finality claim, but doesn't include a qc to justify this claim", ("b", block_num) ); const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); const auto& qc_proof = qc_ext.qc; // Check QC information in header extension and block extension match - EOS_ASSERT( qc_proof.block_num == qc_claim.last_qc_block_num, block_validate_exception, + EOS_ASSERT( qc_proof.block_num == qc_claim.last_qc_block_num, + block_validate_exception, "Block #${b}: Mismatch between qc.block_num (${n1}) in block extension and last_qc_block_num (${n2}) in header extension", ("n1", qc_proof.block_num)("n2", qc_claim.last_qc_block_num)("b", block_num) ); // Verify claimed strictness is the same as in proof - EOS_ASSERT( qc_proof.qc.is_strong() == qc_claim.is_last_qc_strong, block_validate_exception, + EOS_ASSERT( qc_proof.qc.is_strong() == qc_claim.is_last_qc_strong, + block_validate_exception, "QC is_strong (${s1}) in block extension does not match is_last_qc_strong (${s2}) in header extension. Block number: ${b}", ("s1", qc_proof.qc.is_strong())("s2", qc_claim.is_last_qc_strong)("b", block_num) ); // find the claimed block's block state on branch of id auto bsp = fork_db_fetch_bsp_by_num( prev.id, qc_claim.last_qc_block_num ); - EOS_ASSERT( bsp, block_validate_exception, + EOS_ASSERT( bsp, + block_validate_exception, "Block state was not found in forkdb for last_qc_block_num ${q}. Block number: ${b}", ("q", qc_claim.last_qc_block_num)("b", block_num) ); @@ -3175,7 +3183,8 @@ struct controller_impl { verify_qc_claim(id, b, prev); auto trx_mroot = calculate_trx_merkle( b->transactions, true ); - EOS_ASSERT( b->transaction_mroot == trx_mroot, block_validate_exception, + EOS_ASSERT( b->transaction_mroot == trx_mroot, + block_validate_exception, "invalid block transaction merkle root ${b} != ${c}", ("b", b->transaction_mroot)("c", trx_mroot) ); const bool skip_validate_signee = false; From 68f3f3e63051331a4a190ab435e58dd7d50148c5 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 29 Jan 2024 09:28:50 -0500 Subject: [PATCH 14/14] Remove `.clang-format` which I added by mistake. --- .clang-format | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .clang-format diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 53620b112b..0000000000 --- a/.clang-format +++ /dev/null @@ -1,23 +0,0 @@ -BasedOnStyle: LLVM -BraceWrapping: - SplitEmptyFunction: false - SplitEmptyRecord: false -IndentWidth: 3 -ColumnLimit: 120 -PointerAlignment: Left -AccessModifierOffset: -3 -AlwaysBreakTemplateDeclarations: Yes -AlignArrayOfStructures: Left -AlignConsecutiveAssignments: Consecutive -AlignConsecutiveDeclarations: Consecutive -AlignEscapedNewlines: Left -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: Empty -AllowShortFunctionsOnASingleLine: Inline -BreakBeforeBraces: Custom -BreakConstructorInitializers: BeforeComma -BreakInheritanceList: BeforeComma -ConstructorInitializerIndentWidth: 3 -ContinuationIndentWidth: 3 -MaxEmptyLinesToKeep: 3 -SortIncludes: Never \ No newline at end of file