Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.0 -> main] Harden ordered_diff; Version bump v1.0.0 final #703

Merged
merged 15 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
73 changes: 73 additions & 0 deletions libraries/libfc/test/test_ordered_diff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include <fc/container/ordered_diff.hpp>
#include <fc/exception/exception.hpp>

#include <algorithm>
#include <random>

using namespace fc;

BOOST_AUTO_TEST_SUITE(ordered_diff_tests)
Expand Down Expand Up @@ -39,20 +42,41 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try {
source = ordered_diff<char, int>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // All elements removed, size 1
vector<char> source = {'a'};
vector<char> target;
auto result = ordered_diff<char, int>::diff(source, target);
source = ordered_diff<char, int>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // All elements inserted
vector<char> source;
vector<char> target = {'a', 'b', 'c', 'd', 'e'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // All elements inserted, size 1
vector<char> source;
vector<char> target = {'a'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // No change
vector<char> source = {'a', 'b', 'c', 'd', 'e'};
vector<char> target = source;
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // No change, size 1
vector<char> source = {'a'};
vector<char> target = source;
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // Mix of removals and inserts
vector<char> source = {'a', 'b', 'c', 'd', 'e'};
vector<char> target = {'a', 'c', 'e', 'f', 'g', 'h'};
Expand All @@ -74,13 +98,41 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try {
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // Complete change, size 1
vector<char> source = {'a'};
vector<char> target = {'f'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // Complete change equal sizes
vector<char> source = {'a', 'b', 'c', 'd'};
vector<char> target = {'f', 'g', 'h', 'i'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // Diff order
vector<char> source = {'a', 'b', 'c', 'd', 'e'};
vector<char> target = {'e', 'd', 'c', 'b', 'a'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // Diff order, size 2
vector<char> source = {'a', 'b'};
vector<char> target = {'b', 'a'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // Diff order, size 2
vector<char> source = {'b', 'a'};
vector<char> target = {'a', 'b'};
auto result = ordered_diff<char>::diff(source, target);
source = ordered_diff<char>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // shift left
vector<char> source = {'a', 'b', 'c', 'd', 'e'};
vector<char> target = {'b', 'c', 'd', 'e', 'f'};
Expand Down Expand Up @@ -119,6 +171,27 @@ BOOST_AUTO_TEST_CASE(ordered_diff_test) try {
source = ordered_diff<uint8_t, uint8_t>::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<uint8_t> dist {0, std::numeric_limits<uint8_t>::max()};
auto gen = [&](){ return dist(mersenne_engine); };
vector<uint8_t> source(std::numeric_limits<uint8_t>::max()+1);
std::generate(source.begin(), source.end(), gen);
vector<uint8_t> target(source.size());
std::reverse_copy(source.begin(), source.end(), target.begin());
auto result = ordered_diff<uint8_t, uint8_t>::diff(source, target);
source = ordered_diff<uint8_t, uint8_t>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
target.clear();
result = ordered_diff<uint8_t, uint8_t>::diff(source, target);
source = ordered_diff<uint8_t, uint8_t>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
source.clear();
result = ordered_diff<uint8_t, uint8_t>::diff(source, target);
source = ordered_diff<uint8_t, uint8_t>::apply_diff(std::move(source), result);
BOOST_TEST(source == target);
}
{ // non-unique full
vector<uint8_t> source(std::numeric_limits<uint8_t>::max()*2);
std::iota(source.begin(), source.begin()+std::numeric_limits<uint8_t>::max(), 0);
Expand Down