Skip to content

Commit

Permalink
Merge pull request #1923 from AntelopeIO/block_header_state_core
Browse files Browse the repository at this point in the history
IF: implement block_header_state_core and its state transition
  • Loading branch information
linh2931 authored Nov 22, 2023
2 parents 50bda01 + 87c7cbd commit a05428a
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
47 changes: 47 additions & 0 deletions libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,53 @@ namespace eosio { namespace chain {
}
}

block_header_state_core::block_header_state_core( uint32_t last_final_block_height,
std::optional<uint32_t> final_on_strong_qc_block_height,
std::optional<uint32_t> last_qc_block_height )
:
last_final_block_height(last_final_block_height),
final_on_strong_qc_block_height(final_on_strong_qc_block_height),
last_qc_block_height(last_qc_block_height) {}

block_header_state_core block_header_state_core::next( uint32_t last_qc_block_height,
bool is_last_qc_strong) {
// no state change if last_qc_block_height is the same
if( last_qc_block_height == this->last_qc_block_height ) {
return {*this};
}

EOS_ASSERT( last_qc_block_height > this->last_qc_block_height, block_validate_exception, "new last_qc_block_height must be greater than old last_qc_block_height" );

auto old_last_qc_block_height = this->last_qc_block_height;
auto old_final_on_strong_qc_block_height = this->final_on_strong_qc_block_height;

block_header_state_core result{*this};

if( is_last_qc_strong ) {
// last QC is strong. We can progress forward.

// block with old final_on_strong_qc_block_height becomes irreversible
if( old_final_on_strong_qc_block_height.has_value() ) {
result.last_final_block_height = *old_final_on_strong_qc_block_height;
}

// next block which can become irreversible is the block with
// old last_qc_block_height
if( old_last_qc_block_height.has_value() ) {
result.final_on_strong_qc_block_height = *old_last_qc_block_height;
}
} else {
// new final_on_strong_qc_block_height should not be present
result.final_on_strong_qc_block_height.reset();

// new last_final_block_height should be the same as the old last_final_block_height
}

// new last_qc_block_height is always the input last_qc_block_height.
result.last_qc_block_height = last_qc_block_height;

return result;
}
producer_authority block_header_state::get_scheduled_producer( block_timestamp_type t )const {
auto index = t.slot % (active_schedule.producers.size() * config::producer_repetitions);
index /= config::producer_repetitions;
Expand Down
25 changes: 25 additions & 0 deletions libraries/chain/include/eosio/chain/block_header_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,31 @@ struct pending_block_header_state : public detail::block_header_state_common {
const vector<digest_type>& )>& validator )&&;
};

/**
* @struct block_header_state_core
*
* A data structure holding hotstuff core information
*/
struct block_header_state_core {
// the block height of the last irreversible (final) block.
uint32_t last_final_block_height = 0;

// the block height of the block that would become irreversible (final) if the
// associated block header was to achieve a strong QC.
std::optional<uint32_t> final_on_strong_qc_block_height;

// the block height of the block that is referenced as the last QC block
std::optional<uint32_t> last_qc_block_height;

block_header_state_core() = default;

explicit block_header_state_core( uint32_t last_final_block_height,
std::optional<uint32_t> final_on_strong_qc_block_height,
std::optional<uint32_t> last_qc_block_height );

block_header_state_core next( uint32_t last_qc_block_height,
bool is_last_qc_strong);
};
/**
* @struct block_header_state
*
Expand Down
109 changes: 109 additions & 0 deletions unittests/block_header_state_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include <eosio/chain/block_header_state.hpp>

#include <boost/test/unit_test.hpp>

using namespace eosio::chain;

BOOST_AUTO_TEST_SUITE(block_header_state_tests)

// test for block_header_state_core constructor
BOOST_AUTO_TEST_CASE(block_header_state_core_constructor_test)
{
// verifies members are constructed correctly
block_header_state_core bhs_core1(1, 2, 3);
BOOST_REQUIRE_EQUAL(bhs_core1.last_final_block_height, 1u);
BOOST_REQUIRE_EQUAL(*bhs_core1.final_on_strong_qc_block_height, 2u);
BOOST_REQUIRE_EQUAL(*bhs_core1.last_qc_block_height, 3u);

// verifies optional arguments work as expected
block_header_state_core bhs_core2(10, std::nullopt, {});
BOOST_REQUIRE_EQUAL(bhs_core2.last_final_block_height, 10u);
BOOST_REQUIRE(!bhs_core2.final_on_strong_qc_block_height.has_value());
BOOST_REQUIRE(!bhs_core2.last_qc_block_height.has_value());
}

// comprehensive state transition test
BOOST_AUTO_TEST_CASE(block_header_state_core_state_transition_test)
{
constexpr auto old_last_final_block_height = 1u;
constexpr auto old_final_on_strong_qc_block_height = 2u;
constexpr auto old_last_qc_block_height = 3u;
block_header_state_core old_bhs_core(old_last_final_block_height, old_final_on_strong_qc_block_height, old_last_qc_block_height);

// verifies the state is kept the same when old last_final_block_height
// and new last_final_block_height are the same
for (bool is_last_qc_strong: { true, false }) {
auto new_bhs_core = old_bhs_core.next(old_last_qc_block_height, is_last_qc_strong);
BOOST_REQUIRE_EQUAL(new_bhs_core.last_final_block_height, old_bhs_core.last_final_block_height);
BOOST_REQUIRE_EQUAL(*new_bhs_core.final_on_strong_qc_block_height, *old_bhs_core.final_on_strong_qc_block_height);
BOOST_REQUIRE_EQUAL(*new_bhs_core.last_qc_block_height, *old_bhs_core.last_qc_block_height);
}

// verifies state cannot be transitioned to a smaller last_qc_block_height
for (bool is_last_qc_strong: { true, false }) {
BOOST_REQUIRE_THROW(old_bhs_core.next(old_last_qc_block_height - 1, is_last_qc_strong), block_validate_exception);
}

// verifies state transition works when is_last_qc_strong is true
constexpr auto input_last_qc_block_height = 4u;
auto new_bhs_core = old_bhs_core.next(input_last_qc_block_height, true);
// old final_on_strong_qc block became final
BOOST_REQUIRE_EQUAL(new_bhs_core.last_final_block_height, old_final_on_strong_qc_block_height);
// old last_qc block became final_on_strong_qc block
BOOST_REQUIRE_EQUAL(*new_bhs_core.final_on_strong_qc_block_height, old_last_qc_block_height);
// new last_qc_block_height is the same as input
BOOST_REQUIRE_EQUAL(*new_bhs_core.last_qc_block_height, input_last_qc_block_height);

// verifies state transition works when is_last_qc_strong is false
new_bhs_core = old_bhs_core.next(input_last_qc_block_height, false);
// last_final_block_height should not change
BOOST_REQUIRE_EQUAL(new_bhs_core.last_final_block_height, old_last_final_block_height);
// new final_on_strong_qc_block_height should not be present
BOOST_REQUIRE(!new_bhs_core.final_on_strong_qc_block_height.has_value());
// new last_qc_block_height is the same as input
BOOST_REQUIRE_EQUAL(*new_bhs_core.last_qc_block_height, input_last_qc_block_height);
}

// A test to demonstrate 3-chain state transitions from the first
// block after hotstuff activation
BOOST_AUTO_TEST_CASE(block_header_state_core_3_chain_transition_test)
{
// block2: initial setup
constexpr auto block2_last_final_block_height = 1u;
block_header_state_core block2_bhs_core(block2_last_final_block_height, {}, {});

// block2 --> block3
constexpr auto block3_input_last_qc_block_height = 2u;
auto block3_bhs_core = block2_bhs_core.next(block3_input_last_qc_block_height, true);
// last_final_block_height should be the same as old one
BOOST_REQUIRE_EQUAL(block3_bhs_core.last_final_block_height, block2_last_final_block_height);
// final_on_strong_qc_block_height should be same as old one
BOOST_REQUIRE(!block3_bhs_core.final_on_strong_qc_block_height.has_value());
// new last_qc_block_height is the same as input
BOOST_REQUIRE_EQUAL(*block3_bhs_core.last_qc_block_height, block3_input_last_qc_block_height);
auto block3_last_qc_block_height = *block3_bhs_core.last_qc_block_height;

// block3 --> block4
constexpr auto block4_input_last_qc_block_height = 3u;
auto block4_bhs_core = block3_bhs_core.next(block4_input_last_qc_block_height, true);
// last_final_block_height should not change
BOOST_REQUIRE_EQUAL(block4_bhs_core.last_final_block_height, block2_last_final_block_height);
// final_on_strong_qc_block_height should be block3's last_qc_block_height
BOOST_REQUIRE_EQUAL(*block4_bhs_core.final_on_strong_qc_block_height, block3_last_qc_block_height);
// new last_qc_block_height is the same as input
BOOST_REQUIRE_EQUAL(*block4_bhs_core.last_qc_block_height, block4_input_last_qc_block_height);
auto block4_final_on_strong_qc_block_height = *block4_bhs_core.final_on_strong_qc_block_height;
auto block4_last_qc_block_height = *block4_bhs_core.last_qc_block_height;

// block4 --> block5
constexpr auto block5_input_last_qc_block_height = 4u;
auto block5_bhs_core = block4_bhs_core.next(block5_input_last_qc_block_height, true);
// last_final_block_height should have a new value
BOOST_REQUIRE_EQUAL(block5_bhs_core.last_final_block_height, block4_final_on_strong_qc_block_height);
// final_on_strong_qc_block_height should be block4's last_qc_block_height
BOOST_REQUIRE_EQUAL(*block5_bhs_core.final_on_strong_qc_block_height, block4_last_qc_block_height);
// new last_qc_block_height is the same as input
BOOST_REQUIRE_EQUAL(*block5_bhs_core.last_qc_block_height, block5_input_last_qc_block_height);
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit a05428a

Please sign in to comment.