Skip to content

Commit

Permalink
Merge remote-tracking branch 'spring/release/1.0' into merge-1.0-main
Browse files Browse the repository at this point in the history
  • Loading branch information
heifner committed Sep 5, 2024
2 parents 680ef95 + a8159fe commit b6b4a8d
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 55 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ nodeos --full-version
```
You should see a [semantic version](https://semver.org) string followed by a `git` commit hash with no errors. For example:
```
v3.1.2-0b64f879e3ebe2e4df09d2e62f1fc164cc1125d1
v1.0.1-9026a03c09c9b4f93edca696b5eef259f0ab96b3
```

## Build and Install from Source
Expand Down Expand Up @@ -84,7 +84,7 @@ cd spring
```

### Step 2 - Checkout Release Tag or Branch
Choose which [release](https://github.com/AntelopeIO/spring/releases) or [branch](#branches) you would like to build, then check it out. If you are not sure, use the [latest release](https://github.com/AntelopeIO/spring/releases/latest). For example, if you want to build release 3.1.2 then you would check it out using its tag, `v3.1.2`. In the example below, replace `v0.0.0` with your selected release tag accordingly:
Choose which [release](https://github.com/AntelopeIO/spring/releases) or [branch](#branches) you would like to build, then check it out. If you are not sure, use the [latest release](https://github.com/AntelopeIO/spring/releases/latest). For example, if you want to build release 1.0.1 then you would check it out using its tag, `v1.0.1`. In the example below, replace `v0.0.0` with your selected release tag accordingly:
```bash
git fetch --all --tags
git checkout v0.0.0
Expand Down
165 changes: 118 additions & 47 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3882,62 +3882,44 @@ 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 ) {
void verify_proper_block_exts( const block_id_type& id, const signed_block_ptr& b, const block_header_state& prev ) {
assert(b->is_proper_svnn_block());

auto qc_ext_id = quorum_certificate_extension::extension_id();
auto f_ext_id = finality_extension::extension_id();

// extract current block extension and previous header extension
auto block_exts = b->validate_and_extract_extensions();
const finality_extension* prev_finality_ext = prev.header_extension<finality_extension>();
std::optional<block_header_extension> header_ext = b->extract_header_extension(f_ext_id);
std::optional<block_header_extension> finality_ext = b->extract_header_extension(f_ext_id);

bool qc_extension_present = block_exts.count(qc_ext_id) != 0;
uint32_t block_num = b->block_num();

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,
invalid_qc_claim,
"Block #${b} includes a QC block extension, but doesn't have a finality header extension",
("b", block_num) );

EOS_ASSERT( !prev_finality_ext,
invalid_qc_claim,
"Block #${b} doesn't have a finality header extension even though its predecessor does.",
("b", block_num) );
// This function is called only in Savanna. Finality block header
// extension must exist
EOS_ASSERT( finality_ext, block_validate_exception,
"Proper Savanna block #${b} does not have a finality header extension",
("b", block_num) );

dlog("received block: #${bn} ${t} ${prod} ${id}, no qc claim, previous: ${p}",
("bn", block_num)("t", b->timestamp)("prod", b->producer)("id", id)("p", b->previous));
return;
}

assert(header_ext);
const auto& f_ext = std::get<finality_extension>(*header_ext);
assert(finality_ext);
const auto& f_ext = std::get<finality_extension>(*finality_ext);
const auto new_qc_claim = f_ext.qc_claim;

dlog("received block: #${bn} ${t} ${prod} ${id}, qc claim: ${qc}, previous: ${p}",
("bn", block_num)("t", b->timestamp)("prod", b->producer)("id", id)
("qc", new_qc_claim)("p", b->previous));

// 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 block_num
// of the current block’s number and that it is a claim of a weak QC. Then return early.
// The only time a block should have a finality block header extension but
// its parent block does not, is if it is a Savanna Genesis block (which is
// necessarily a Transition block). Since verify_proper_block_exts will not be called
// on Transition blocks, previous block may not be a Legacy block
// -------------------------------------------------------------------------------------------------
if (!prev_finality_ext) {
EOS_ASSERT( !qc_extension_present && new_qc_claim.block_num == block_num && new_qc_claim.is_strong_qc == false,
invalid_qc_claim,
"Block #${b}, which is the finality transition block, doesn't have the expected extensions",
("b", block_num) );
return;
}

// at this point both current block and its parent have IF extensions, and we are past the
// IF transition block
// ----------------------------------------------------------------------------------------
assert(header_ext && prev_finality_ext);
EOS_ASSERT( !prev.header.is_legacy_block(), block_validate_exception,
"Proper Savanna block #${b} may not have previous block that is a Legacy block",
("b", block_num) );

assert(prev_finality_ext);
const auto& prev_qc_claim = prev_finality_ext->qc_claim;

// validate QC claim against previous block QC info
Expand Down Expand Up @@ -3999,18 +3981,107 @@ struct controller_impl {
bsp->verify_qc(qc_proof);
}

void verify_legacy_block_exts( const signed_block_ptr& b, const block_header_state_legacy& prev ) {
assert(b->is_legacy_block());

uint32_t block_num = b->block_num();
auto block_exts = b->validate_and_extract_extensions();
auto qc_ext_id = quorum_certificate_extension::extension_id();
bool qc_extension_present = block_exts.count(qc_ext_id) != 0;

EOS_ASSERT( !qc_extension_present, invalid_qc_claim,
"Legacy block #${b} includes a QC block extension",
("b", block_num) );

EOS_ASSERT( !b->is_proper_svnn_block(), block_validate_exception,
"Legacy block #${b} has invalid schedule_version",
("b", block_num) );

// Verify we don't go back from Savanna (Transition or Proper) block to Legacy block
EOS_ASSERT( prev.header.is_legacy_block(), block_validate_exception,
"Legacy block #${b} must have previous block that is also a Legacy block",
("b", block_num) );
}

void verify_transition_block_exts( const signed_block_ptr& b, const block_header_state_legacy& prev ) {
assert(!b->is_legacy_block() && !b->is_proper_svnn_block());

uint32_t block_num = b->block_num();
auto block_exts = b->validate_and_extract_extensions();
auto qc_ext_id = quorum_certificate_extension::extension_id();
bool qc_extension_present = block_exts.count(qc_ext_id) != 0;

EOS_ASSERT( !qc_extension_present, invalid_qc_claim,
"Transition block #${b} includes a QC block extension",
("b", block_num) );

EOS_ASSERT( !prev.header.is_proper_svnn_block(), block_validate_exception,
"Transition block #${b} may not have previous block that is a Proper Savanna block",
("b", block_num) );

auto f_ext_id = finality_extension::extension_id();
std::optional<block_header_extension> finality_ext = b->extract_header_extension(f_ext_id);
assert(finality_ext);
const auto& f_ext = std::get<finality_extension>(*finality_ext);

EOS_ASSERT( !f_ext.new_proposer_policy_diff, block_validate_exception,
"Transition block #${b} has new_proposer_policy_diff",
("b", block_num) );

if (auto it = prev.header_exts.find(finality_extension::extension_id()); it != prev.header_exts.end()) {
// Transition block other than Genesis Block
const auto& prev_finality_ext = std::get<finality_extension>(it->second);
EOS_ASSERT( f_ext.qc_claim == prev_finality_ext.qc_claim, invalid_qc_claim,
"Non Genesis Transition block #${b} QC claim ${this_qc_claim} not equal to previous QC claim ${prev_qc_claim}",
("b", block_num)("this_qc_claim", f_ext.qc_claim)("prev_qc_claim", prev_finality_ext.qc_claim) );
EOS_ASSERT( !f_ext.new_finalizer_policy_diff, block_validate_exception,
"Non Genesis Transition block #${b} finality block header extension may not have new_finalizer_policy_diff",
("b", block_num) );
} else {
// Savanna Genesis Block
qc_claim_t genesis_qc_claim {.block_num = block_num, .is_strong_qc = false};
EOS_ASSERT( f_ext.qc_claim == genesis_qc_claim, invalid_qc_claim,
"Savanna Genesis block #${b} has invalid QC claim ${qc_claim}",
("b", block_num)("qc_claim", f_ext.qc_claim) );
EOS_ASSERT( f_ext.new_finalizer_policy_diff, block_validate_exception,
"Savanna Genesis block #${b} finality block header extension misses new_finalizer_policy_diff",
("b", block_num) );

// apply_diff will FC_ASSERT if new_finalizer_policy_diff is malformed
try {
finalizer_policy no_policy;
finalizer_policy genesis_policy = no_policy.apply_diff(*f_ext.new_finalizer_policy_diff);
EOS_ASSERT( genesis_policy.generation == 1, block_validate_exception,
"Savanna Genesis block #${b} finalizer policy generation (${g}) not 1",
("b", block_num)("g", genesis_policy.generation) );
} EOS_RETHROW_EXCEPTIONS(block_validate_exception, "applying diff of Savanna Genesis Block")
}
}

// thread safe, expected to be called from thread other than the main thread
template<typename ForkDB, typename BS>
block_handle create_block_state_i( ForkDB& forkdb, const block_id_type& id, const signed_block_ptr& b, const BS& prev ) {
constexpr bool savanna_mode = std::is_same_v<typename std::decay_t<BS>, block_state>;
if constexpr (savanna_mode) {
// Verify claim made by finality_extension in block header extension and
// quorum_certificate_extension in block extension are valid.
// This is the only place the evaluation is done.
verify_qc_claim(id, b, prev);
constexpr bool is_proper_savanna_block = std::is_same_v<typename std::decay_t<BS>, block_state>;
assert(is_proper_savanna_block == b->is_proper_svnn_block());

if constexpr (is_proper_savanna_block) {
EOS_ASSERT( b->is_proper_svnn_block(), block_validate_exception,
"create_block_state_i cannot be called on block #${b} which is not a Proper Savanna block unless the prev block state provided is of type block_state",
("b", b->block_num()) );
verify_proper_block_exts(id, b, prev);
} else {
EOS_ASSERT( !b->is_proper_svnn_block(), block_validate_exception,
"create_block_state_i cannot be called on block #${b} which is a Proper Savanna block unless the prev block state provided is of type block_state_legacy",
("b", b->block_num()) );

if (b->is_legacy_block()) {
verify_legacy_block_exts(b, prev);
} else {
verify_transition_block_exts(b, prev);
}
}

auto trx_mroot = calculate_trx_merkle( b->transactions, savanna_mode );
auto trx_mroot = calculate_trx_merkle( b->transactions, is_proper_savanna_block );
EOS_ASSERT( b->transaction_mroot == trx_mroot,
block_validate_exception,
"invalid block transaction merkle root ${b} != ${c}", ("b", b->transaction_mroot)("c", trx_mroot) );
Expand All @@ -4030,14 +4101,14 @@ struct controller_impl {
EOS_ASSERT( id == bsp->id(), block_validate_exception,
"provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) );

if constexpr (savanna_mode) {
if constexpr (is_proper_savanna_block) {
integrate_received_qc_to_block(bsp); // Save the received QC as soon as possible, no matter whether the block itself is valid or not
consider_voting(bsp, use_thread_pool_t::no);
}

if (!should_terminate(bsp->block_num())) {
forkdb.add(bsp, ignore_duplicate_t::yes);
if constexpr (savanna_mode)
if constexpr (is_proper_savanna_block)
vote_processor.notify_new_block(async_aggregation);
}

Expand Down Expand Up @@ -4095,7 +4166,7 @@ struct controller_impl {
return fork_db.apply<std::optional<block_handle>>(unlinkable, f);
}

// thread safe, QC already verified by verify_qc_claim
// thread safe, QC already verified by verify_proper_block_exts
void integrate_received_qc_to_block(const block_state_ptr& bsp_in) {
// extract QC from block extension
assert(bsp_in->block);
Expand Down
3 changes: 3 additions & 0 deletions libraries/chain/include/eosio/chain/block_header.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ namespace eosio::chain {
// finality extension must exist.
bool is_proper_svnn_block() const { return ( schedule_version == proper_svnn_schedule_version ); }

// Returns true if the block is a pure Legacy block
bool is_legacy_block() const { return !contains_header_extension(finality_extension::extension_id()); }

header_extension_multimap validate_and_extract_header_extensions()const;
std::optional<block_header_extension> extract_header_extension(uint16_t extension_id)const;
template<typename Ext> Ext extract_header_extension()const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

namespace eosio::chain {

static_assert(std::numeric_limits<uint16_t>::max() >= config::max_finalizers - 1);
using finalizers_differ = fc::ordered_diff<finalizer_authority, uint16_t>;
// Verify finalizers_differ::size_type can represent all index values in the
// diff between two policies that could each hold up to max_finalizers entries.
static_assert(std::numeric_limits<finalizers_differ::size_type>::max() >= config::max_finalizers - 1);
using finalizers_diff_t = finalizers_differ::diff_result;

struct finalizer_policy_diff {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

namespace eosio::chain {

static_assert(std::numeric_limits<uint16_t>::max() >= config::max_proposers - 1);
using producer_auth_differ = fc::ordered_diff<producer_authority, uint16_t>;
// Verify producer_auth_differ::size_type can represent all index values in the
// diff between two policies that could each hold up to max_proposers entries.
static_assert(std::numeric_limits<producer_auth_differ::size_type>::max() >= config::max_proposers - 1);
using producer_auth_diff_t = producer_auth_differ::diff_result;

struct proposer_policy_diff {
Expand Down
23 changes: 19 additions & 4 deletions libraries/libfc/include/fc/container/ordered_diff.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#pragma once

#include <fc/exception/exception.hpp>
#include <fc/utility.hpp>

#include <vector>
#include <utility>

Expand All @@ -9,6 +12,8 @@ namespace fc {
* @class ordered_diff
* @brief Provides ability to generate and apply diff of containers of type T
*
* NOTE: Part of Spring Consensus. Used for finalizer and proposer policies.
*
* Example use:
* std::vector<char> source = { 'a', 'b', 'f', 'c', 'd' };
* std::vector<char> target = { 'b', 'f', 'c', 'd', 'e', 'h' };
Expand All @@ -24,6 +29,8 @@ template <typename T, typename SizeType = size_t, template<typename Y, typename.
requires std::equality_comparable<T> && std::random_access_iterator<typename Container<T>::iterator>
class ordered_diff {
public:
using size_type = SizeType;

struct diff_result {
Container<SizeType> remove_indexes;
Container<std::pair<SizeType, T>> insert_indexes;
Expand All @@ -34,13 +41,16 @@ class ordered_diff {
size_t s = 0;
size_t t = 0;

FC_ASSERT(source.empty() || (source.size() - 1) <= std::numeric_limits<SizeType>::max());
FC_ASSERT(target.empty() || (target.size() - 1) <= std::numeric_limits<SizeType>::max());

diff_result result;
while (s < source.size() || t < target.size()) {
assert(s <= source.size());
assert(t <= target.size());
if (s < source.size() && t < target.size()) {
if (source[s] == target[t]) {
// nothing to do, skip over
assert(s <= std::numeric_limits<SizeType>::max());
assert(t <= std::numeric_limits<SizeType>::max());
++s;
++t;
} else { // not equal
Expand All @@ -65,8 +75,9 @@ class ordered_diff {
assert(t <= std::numeric_limits<SizeType>::max());
result.insert_indexes.emplace_back(t, target[t]);
++t;
} else { // source[s + 1] == target[t]
// target matches next source, remove current source
} else {
// not misalignment by one and source not equal to next target, so remove from source
// may be inserted later by other conditions if needed
assert(s <= std::numeric_limits<SizeType>::max());
result.remove_indexes.push_back(s);
++s;
Expand Down Expand Up @@ -97,12 +108,16 @@ class ordered_diff {
// Remove from the source based on diff.remove_indexes
std::ptrdiff_t offset = 0;
for (SizeType index : diff.remove_indexes) {
FC_ASSERT(index + offset < container.size(), "diff.remove_indexes index ${idx} + offset ${o} not in range ${s}",
("idx", index)("o", offset)("s", container.size()));
container.erase(container.begin() + index + offset);
--offset;
}

// Insert into the source based on diff.insert_indexes
for (auto& [index, value] : diff.insert_indexes) {
FC_ASSERT(index <= container.size(), "diff.insert_indexes index ${idx} not in range ${s}",
("idx", index)("s", container.size()));
container.insert(container.begin() + index, std::move(value));
}
return container;
Expand Down
Loading

0 comments on commit b6b4a8d

Please sign in to comment.