diff --git a/README.md b/README.md index e9490780f2..3ebe2c7268 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp b/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp index 66abe98841..1fa8dbfe3f 100644 --- a/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp +++ b/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp @@ -6,8 +6,10 @@ namespace eosio::chain { - static_assert(std::numeric_limits::max() >= config::max_finalizers - 1); using finalizers_differ = fc::ordered_diff; + // 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::max() >= config::max_finalizers - 1); using finalizers_diff_t = finalizers_differ::diff_result; struct finalizer_policy_diff { diff --git a/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp b/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp index 3b5ebc7b29..207dec2973 100644 --- a/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp +++ b/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp @@ -5,8 +5,10 @@ namespace eosio::chain { -static_assert(std::numeric_limits::max() >= config::max_proposers - 1); using producer_auth_differ = fc::ordered_diff; +// 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::max() >= config::max_proposers - 1); using producer_auth_diff_t = producer_auth_differ::diff_result; struct proposer_policy_diff { diff --git a/libraries/libfc/include/fc/container/ordered_diff.hpp b/libraries/libfc/include/fc/container/ordered_diff.hpp index e2440d6ab9..d028ad7a97 100644 --- a/libraries/libfc/include/fc/container/ordered_diff.hpp +++ b/libraries/libfc/include/fc/container/ordered_diff.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include #include @@ -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 source = { 'a', 'b', 'f', 'c', 'd' }; * std::vector target = { 'b', 'f', 'c', 'd', 'e', 'h' }; @@ -24,6 +29,8 @@ template && std::random_access_iterator::iterator> class ordered_diff { public: + using size_type = SizeType; + struct diff_result { Container remove_indexes; Container> insert_indexes; @@ -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::max()); + FC_ASSERT(target.empty() || (target.size() - 1) <= std::numeric_limits::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::max()); - assert(t <= std::numeric_limits::max()); ++s; ++t; } else { // not equal @@ -65,8 +75,9 @@ class ordered_diff { assert(t <= std::numeric_limits::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::max()); result.remove_indexes.push_back(s); ++s; @@ -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; diff --git a/libraries/libfc/test/test_ordered_diff.cpp b/libraries/libfc/test/test_ordered_diff.cpp index 6477c3f8a3..eeb9687dd3 100644 --- a/libraries/libfc/test/test_ordered_diff.cpp +++ b/libraries/libfc/test/test_ordered_diff.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include + using namespace fc; BOOST_AUTO_TEST_SUITE(ordered_diff_tests) @@ -39,6 +42,13 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try { source = ordered_diff::apply_diff(std::move(source), result); BOOST_TEST(source == target); } + { // All elements removed, size 1 + vector source = {'a'}; + vector target; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } { // All elements inserted vector source; vector target = {'a', 'b', 'c', 'd', 'e'}; @@ -46,6 +56,13 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try { source = ordered_diff::apply_diff(std::move(source), result); BOOST_TEST(source == target); } + { // All elements inserted, size 1 + vector source; + vector target = {'a'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } { // No change vector source = {'a', 'b', 'c', 'd', 'e'}; vector target = source; @@ -53,6 +70,13 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try { source = ordered_diff::apply_diff(std::move(source), result); BOOST_TEST(source == target); } + { // No change, size 1 + vector source = {'a'}; + vector target = source; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } { // Mix of removals and inserts vector source = {'a', 'b', 'c', 'd', 'e'}; vector target = {'a', 'c', 'e', 'f', 'g', 'h'}; @@ -74,6 +98,20 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try { source = ordered_diff::apply_diff(std::move(source), result); BOOST_TEST(source == target); } + { // Complete change, size 1 + vector source = {'a'}; + vector target = {'f'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Complete change equal sizes + vector source = {'a', 'b', 'c', 'd'}; + vector target = {'f', 'g', 'h', 'i'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } { // Diff order vector source = {'a', 'b', 'c', 'd', 'e'}; vector target = {'e', 'd', 'c', 'b', 'a'}; @@ -81,6 +119,20 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try { source = ordered_diff::apply_diff(std::move(source), result); BOOST_TEST(source == target); } + { // Diff order, size 2 + vector source = {'a', 'b'}; + vector target = {'b', 'a'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Diff order, size 2 + vector source = {'b', 'a'}; + vector target = {'a', 'b'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } { // shift left vector source = {'a', 'b', 'c', 'd', 'e'}; vector target = {'b', 'c', 'd', 'e', 'f'}; @@ -119,6 +171,27 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try { source = ordered_diff::apply_diff(std::move(source), result); BOOST_TEST(source == target); } + { // full, random + std::random_device rnd_device; + std::mt19937 mersenne_engine {rnd_device()}; + std::uniform_int_distribution dist {0, std::numeric_limits::max()}; + auto gen = [&](){ return dist(mersenne_engine); }; + vector source(std::numeric_limits::max()+1); + std::generate(source.begin(), source.end(), gen); + vector target(source.size()); + std::reverse_copy(source.begin(), source.end(), target.begin()); + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + target.clear(); + result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + source.clear(); + result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } { // non-unique full vector source(std::numeric_limits::max()*2); std::iota(source.begin(), source.begin()+std::numeric_limits::max(), 0);