From b3f31bee4d08c7e35f4e3f15afde6f9b25407e2f Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Sun, 10 Jan 2016 17:22:39 -0800 Subject: [PATCH 1/7] Add BitsetEnumerator. --- src/util/BitsetEnumerator.cpp | 322 +++++++++++++++++++++++++++++ src/util/BitsetEnumerator.h | 234 +++++++++++++++++++++ src/util/BitsetEnumeratorTests.cpp | 231 +++++++++++++++++++++ 3 files changed, 787 insertions(+) create mode 100644 src/util/BitsetEnumerator.cpp create mode 100644 src/util/BitsetEnumerator.h create mode 100644 src/util/BitsetEnumeratorTests.cpp diff --git a/src/util/BitsetEnumerator.cpp b/src/util/BitsetEnumerator.cpp new file mode 100644 index 0000000000..a0be5a5b10 --- /dev/null +++ b/src/util/BitsetEnumerator.cpp @@ -0,0 +1,322 @@ +// Copyright 2016 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#include "util/BitsetEnumerator.h" +#include + +namespace stellar +{ + +/////////////////////////////////////////////////////////////////////////// +// ConstantEnumerator +/////////////////////////////////////////////////////////////////////////// + +ConstantEnumerator::ConstantEnumerator(std::bitset<64> bits) + : mBits(bits) + , mDone(false) +{ +} + +std::shared_ptr +ConstantEnumerator::bitNumber(size_t n) +{ + assert(n < 64); + std::bitset<64> bits; + bits.set(n); + return std::make_shared(bits); +} + +std::vector> +ConstantEnumerator::bitNumbers(std::vector ns) +{ + std::vector> ret; + for (auto n : ns) + { + ret.push_back(bitNumber(n)); + } + return ret; +} + +void +ConstantEnumerator::reset() +{ + mDone = false; +} + +ConstantEnumerator::operator bool() const +{ + return !mDone; +} + +std::bitset<64> +ConstantEnumerator::operator*() const +{ + return mBits; +} + +void +ConstantEnumerator::operator++() +{ + mDone = true; +} + +/////////////////////////////////////////////////////////////////////////// +// PermutationEnumerator +/////////////////////////////////////////////////////////////////////////// + +PermutationEnumerator::PermutationEnumerator(size_t nSet, size_t nTotal) + : mCur(0) + , mSet(nSet) + , mTot(nTotal) +{ + assert(mSet <= mTot); + assert(mSet > 0 && mSet <= 64); + assert(mTot > 0 && mTot <= 64); + while (nSet-- > 0) + { + mCur <<= 1; + mCur |= 1; + } +} + +void +PermutationEnumerator::reset() +{ + mCur = 0; + auto nSet = mSet; + while (nSet-- > 0) + { + mCur <<= 1; + mCur |= 1; + } +} + +PermutationEnumerator::operator bool() const +{ + uint64_t one = 1; + return !(mCur & ~((one << mTot) - 1)); +} + +std::bitset<64> +PermutationEnumerator::operator*() const +{ + std::bitset<64> bits(mCur); + assert(bits.count() == mSet); + return bits; +} + +void +PermutationEnumerator::operator++() +{ + // Next bit-permutation. See: + // https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation + uint64_t t = (mCur | (mCur - 1)) + 1; + mCur = t | ((((t & -t) / (mCur & -mCur)) >> 1) - 1); +} + +/////////////////////////////////////////////////////////////////////////// +// PowersetEnumerator +/////////////////////////////////////////////////////////////////////////// + +PowersetEnumerator::PowersetEnumerator(size_t nBits) + : mCur(1) + , mLim(1 << nBits) +{ + assert(nBits < 64); +} + +void +PowersetEnumerator::reset() +{ + mCur = 1; +} + +PowersetEnumerator::operator bool() const +{ + return mCur < mLim; +} + +std::bitset<64> +PowersetEnumerator::operator*() const +{ + return std::bitset<64>(mCur); +} + +void +PowersetEnumerator::operator++() +{ + ++mCur; +} + +/////////////////////////////////////////////////////////////////////////// +// CartesianProductEnumerator +/////////////////////////////////////////////////////////////////////////// + +CartesianProductEnumerator::CartesianProductEnumerator( + std::vector> innerEnums) + : mInnerEnums(innerEnums) +{ + for (auto const& e : mInnerEnums) + { + e->reset(); + } +} + +void +CartesianProductEnumerator::reset() +{ + for (auto& e : mInnerEnums) + { + e->reset(); + } +} + +CartesianProductEnumerator::operator bool() const +{ + for (auto const& e : mInnerEnums) + { + if (*e) + { + return true; + } + } + return false; +} + +std::bitset<64> +CartesianProductEnumerator::operator*() const +{ + std::bitset<64> tmp; + for (auto const& e : mInnerEnums) + { + tmp |= **e; + } + return tmp; +} + +void +CartesianProductEnumerator::operator++() +{ + // Want to walk along the array looking for the first + // element that wasn't done, but becomes done when we + // increment it. + for (size_t i = 0; i < mInnerEnums.size(); ++i) + { + auto curr = mInnerEnums[i]; + if (!(*curr)) + { + continue; + } + // enumerator i is 'true', so now advance it and see if it + // went false. + ++(*curr); + if (*curr) + { + // It's still got life in it, stop let it go. + return; + } + else + { + // We just exhausted enumerator i, meaning we need to + // "carry" the advance to the next one, and if it + // remains live after the carry, reset enumerator i and + // all the previous ones. + for (size_t carry = i+1; carry < mInnerEnums.size(); ++carry) + { + auto next = mInnerEnums[carry]; + ++(*next); + if (*next) + { + for (size_t reset = 0; reset <= i; ++reset) + { + mInnerEnums[i]->reset(); + } + return; + } + } + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// SelectionEnumerator +/////////////////////////////////////////////////////////////////////////// + +SelectionEnumerator::SelectionEnumerator( + std::shared_ptr index, + std::vector> const& innerEnums) + : mInnerEnums(innerEnums) + , mIndexEnum(index), + mProduct(select(index, mInnerEnums)) +{ + for (auto const& e : mInnerEnums) + { + e->reset(); + } +} + +std::shared_ptr +SelectionEnumerator::bitNumbers(size_t nSel, std::vector ns) +{ + auto idx = std::make_shared(nSel, ns.size()); + auto ces = ConstantEnumerator::bitNumbers(ns); + return std::make_shared(idx, ces); +} + +CartesianProductEnumerator +SelectionEnumerator::select( + std::shared_ptr index, + std::vector> const& from) +{ + std::bitset<64> bits(**index); + std::vector> active; + for (size_t i = 0; i < 64; ++i) + { + if (i >= from.size()) + { + break; + } + if (bits[i]) + { + active.push_back(from[i]); + } + } + return CartesianProductEnumerator(active); +} + +std::bitset<64> +SelectionEnumerator::operator*() const +{ + return *mProduct; +} + +void +SelectionEnumerator::reset() +{ + mIndexEnum->reset(); + mProduct = select(mIndexEnum, mInnerEnums); +} + +SelectionEnumerator::operator bool() const +{ + return mProduct || *mIndexEnum; +} + +void +SelectionEnumerator::operator++() +{ + if (mProduct) + { + ++mProduct; + } + if (!mProduct) + { + ++(*mIndexEnum); + if (*mIndexEnum) + { + mProduct = select(mIndexEnum, mInnerEnums); + } + } +} +} diff --git a/src/util/BitsetEnumerator.h b/src/util/BitsetEnumerator.h new file mode 100644 index 0000000000..591d76dae7 --- /dev/null +++ b/src/util/BitsetEnumerator.h @@ -0,0 +1,234 @@ +#pragma once + +// Copyright 2016 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#include +#include +#include + +namespace stellar +{ + +// Abstract enumerator type for sets-of-bitsets. +class BitsetEnumerator +{ +public: + virtual void reset() = 0; + virtual void operator++() = 0; + virtual std::bitset<64> operator*() const = 0; + virtual operator bool() const = 0; +}; + +// Enumerates a single bitset once, then stops. +class ConstantEnumerator : public BitsetEnumerator +{ + std::bitset<64> const mBits; + bool mDone; +public: + ConstantEnumerator(std::bitset<64> bits); + void reset() override; + operator bool() const override; + std::bitset<64> operator*() const override; + void operator++() override; + + static std::shared_ptr bitNumber(size_t n); + static std::vector> + bitNumbers(std::vector ns); +}; + +/* + * Enumerates the permutations of N bits of T total (<= 64) bits. + * + * (That is, it returns "T choose N" bitsets, each with N 1-bits) + * + * For example, if configured to emit 4 of 6 bits, it'll output: + * + * 0000000000000000000000000000000000000000000000000000000000001111 + * 0000000000000000000000000000000000000000000000000000000000010111 + * 0000000000000000000000000000000000000000000000000000000000011011 + * 0000000000000000000000000000000000000000000000000000000000011101 + * 0000000000000000000000000000000000000000000000000000000000011110 + * 0000000000000000000000000000000000000000000000000000000000100111 + * 0000000000000000000000000000000000000000000000000000000000101011 + * 0000000000000000000000000000000000000000000000000000000000101101 + * 0000000000000000000000000000000000000000000000000000000000101110 + * 0000000000000000000000000000000000000000000000000000000000110011 + * 0000000000000000000000000000000000000000000000000000000000110101 + * 0000000000000000000000000000000000000000000000000000000000110110 + * 0000000000000000000000000000000000000000000000000000000000111001 + * 0000000000000000000000000000000000000000000000000000000000111010 + * 0000000000000000000000000000000000000000000000000000000000111100 + */ + +class PermutationEnumerator : public BitsetEnumerator +{ + uint64_t mCur; // Current permutation of bits. + size_t const mSet; // Number of bits that should be set. + size_t const mTot; // Number of bits to select from. +public: + PermutationEnumerator(size_t nSet, size_t nTotal); + void reset() override; + operator bool() const override; + std::bitset<64> operator*() const override; + void operator++() override; +}; + + +/* + * Enumerates the nonempty powerset of a number of bits. This is + * just the set of numbers from 1 to (2< operator*() const override; + void operator++() override; +}; + + +/* + * Enumerates the cartesion product of N enumerators, OR-ing together the + * bitsets returned from each, for each call. + * + * For example, if configured with two constant enumerators for bits 1 and + * 3, will enumerate a single entry: + * + * 0000000000000000000000000000000000000000000000000000000000001010 + * + * Alternatively if configured with two selection enumerators (see below) + * 1-of-{0,1} and 1-of-{4,5}, will enumerate: + * + * 0000000000000000000000000000000000000000000000000000000011000011 + * 0000000000000000000000000000000000000000000000000000000011000101 + * 0000000000000000000000000000000000000000000000000000000011000110 + * 0000000000000000000000000000000000000000000000000000000101000011 + * 0000000000000000000000000000000000000000000000000000000101000101 + * 0000000000000000000000000000000000000000000000000000000101000110 + * 0000000000000000000000000000000000000000000000000000000110000011 + * 0000000000000000000000000000000000000000000000000000000110000101 + * 0000000000000000000000000000000000000000000000000000000110000110 + * + */ +class CartesianProductEnumerator : public BitsetEnumerator +{ + std::vector> mInnerEnums; +public: + CartesianProductEnumerator( + std::vector> innerEnums); + void reset() override; + operator bool() const override; + std::bitset<64> operator*() const override; + void operator++() override; +}; + +/* + * Uses an "index" BitsetEnumerator to repeatedly select subsets of + * a set of user-provided inner enumerators, forms a cartesian product + * enumerator over each subset, and enumerates those cartesian products. + * + * For example, if constructed with 6 inner enumerators, and told to take + * 4-element subsets, the auxiliary index enumerator is configured to + * emit 4-of-6 bit permutations, as such: + * + * 0000000000000000000000000000000000000000000000000000000000001111 + * 0000000000000000000000000000000000000000000000000000000000010111 + * 0000000000000000000000000000000000000000000000000000000000011011 + * 0000000000000000000000000000000000000000000000000000000000011101 + * 0000000000000000000000000000000000000000000000000000000000011110 + * 0000000000000000000000000000000000000000000000000000000000100111 + * 0000000000000000000000000000000000000000000000000000000000101011 + * 0000000000000000000000000000000000000000000000000000000000101101 + * 0000000000000000000000000000000000000000000000000000000000101110 + * 0000000000000000000000000000000000000000000000000000000000110011 + * 0000000000000000000000000000000000000000000000000000000000110101 + * 0000000000000000000000000000000000000000000000000000000000110110 + * 0000000000000000000000000000000000000000000000000000000000111001 + * 0000000000000000000000000000000000000000000000000000000000111010 + * 0000000000000000000000000000000000000000000000000000000000111100 + * + * If the set of provided inner enumerators are (say) ConstantEnumerators + * over the bit numbers {0, 8, 0x10, 0x18, 0x20, 0x28}, then the composite + * SelectionEnumerator will selectively emit 4-enumerator combinations, + * such as: + * + * 0000000000000000000000000000000000000001000000010000000100000001 + * 0000000000000000000000000000000100000000000000010000000100000001 + * 0000000000000000000000000000000100000001000000000000000100000001 + * 0000000000000000000000000000000100000001000000010000000000000001 + * 0000000000000000000000000000000100000001000000010000000100000000 + * 0000000000000000000000010000000000000000000000010000000100000001 + * 0000000000000000000000010000000000000001000000000000000100000001 + * 0000000000000000000000010000000000000001000000010000000000000001 + * 0000000000000000000000010000000000000001000000010000000100000000 + * 0000000000000000000000010000000100000000000000000000000100000001 + * 0000000000000000000000010000000100000000000000010000000000000001 + * 0000000000000000000000010000000100000000000000010000000100000000 + * 0000000000000000000000010000000100000001000000000000000000000001 + * 0000000000000000000000010000000100000001000000000000000100000000 + * 0000000000000000000000010000000100000001000000010000000000000000 + * + * The inner enumerators need not, of course, be constants: selective + * enumeration can nest, as in this case of taking 2-of-3 enumerators, + * each of which is itself a 2-of-3 constant enumerator: + * + * 0000000000000000000000000000000000000000000000110000000000000011 + * 0000000000000000000000000000000000000000000000110000000000000101 + * 0000000000000000000000000000000000000000000000110000000000000110 + * 0000000000000000000000000000000000000000000001010000000000000011 + * 0000000000000000000000000000000000000000000001010000000000000101 + * 0000000000000000000000000000000000000000000001010000000000000110 + * 0000000000000000000000000000000000000000000001100000000000000011 + * 0000000000000000000000000000000000000000000001100000000000000101 + * 0000000000000000000000000000000000000000000001100000000000000110 + * 0000000000000000000000000000001100000000000000000000000000000011 + * 0000000000000000000000000000001100000000000000000000000000000101 + * 0000000000000000000000000000001100000000000000000000000000000110 + * 0000000000000000000000000000010100000000000000000000000000000011 + * 0000000000000000000000000000010100000000000000000000000000000101 + * 0000000000000000000000000000010100000000000000000000000000000110 + * 0000000000000000000000000000011000000000000000000000000000000011 + * 0000000000000000000000000000011000000000000000000000000000000101 + * 0000000000000000000000000000011000000000000000000000000000000110 + * 0000000000000000000000000000001100000000000000110000000000000000 + * 0000000000000000000000000000001100000000000001010000000000000000 + * 0000000000000000000000000000001100000000000001100000000000000000 + * 0000000000000000000000000000010100000000000000110000000000000000 + * 0000000000000000000000000000010100000000000001010000000000000000 + * 0000000000000000000000000000010100000000000001100000000000000000 + * 0000000000000000000000000000011000000000000000110000000000000000 + * 0000000000000000000000000000011000000000000001010000000000000000 + * 0000000000000000000000000000011000000000000001100000000000000000 + */ + +class SelectionEnumerator : public BitsetEnumerator +{ + std::vector> const mInnerEnums; + std::shared_ptr mIndexEnum; + CartesianProductEnumerator mProduct; + + static CartesianProductEnumerator select( + std::shared_ptr index, + std::vector> const& from); + +public: + SelectionEnumerator( + std::shared_ptr index, + std::vector> const& innerEnums); + std::bitset<64> operator*() const override; + void reset() override; + operator bool() const override; + void operator++() override; + + static std::shared_ptr + bitNumbers(size_t nSel, std::vector ns); +}; + +} diff --git a/src/util/BitsetEnumeratorTests.cpp b/src/util/BitsetEnumeratorTests.cpp new file mode 100644 index 0000000000..d1a08697ef --- /dev/null +++ b/src/util/BitsetEnumeratorTests.cpp @@ -0,0 +1,231 @@ +// Copyright 2016 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#include "lib/catch.hpp" +#include "util/Logging.h" +#include "util/BitsetEnumerator.h" +#include + +using namespace stellar; + +static void +expect_bitsets(BitsetEnumerator& be, + std::vector> const& expect) +{ + std::vector> tmp; + while (be) + { + tmp.emplace_back(*be); + ++be; + } + REQUIRE(expect.size() == tmp.size()); + bool same = true; + for (size_t i = 0; i < tmp.size(); ++i) + { + if (expect[i] != tmp[i]) + { + same = false; + std::cerr << "Bitsets differ at entry " << i << std::endl; + std::cerr << "expected " << expect[i] << std::endl; + std::cerr << " got " << tmp[i] << std::endl; + break; + } + } + if (!same) + { + std::cerr << "Bitsets differ"<< std::endl;; + std::cerr << " got: " << std::endl; + for (auto const& b : tmp) + { + std::cerr << " " << b << std::endl; + } + std::cerr << "expected: " << std::endl; + for (auto const& b : expect) + { + std::cerr << " " << b << std::endl; + } + } + REQUIRE(same); +} + +TEST_CASE("ConstantEnumerator", "[bitset]") +{ + ConstantEnumerator ce(std::bitset<64>(0xff)); + expect_bitsets(ce, { 0xff }); +} + +TEST_CASE("PermutationEnumerator", "[bitset]") +{ + PermutationEnumerator pe(4, 6); + expect_bitsets(pe, + { + 0b0000000000000000000000000000000000000000000000000000000000001111ULL, + 0b0000000000000000000000000000000000000000000000000000000000010111ULL, + 0b0000000000000000000000000000000000000000000000000000000000011011ULL, + 0b0000000000000000000000000000000000000000000000000000000000011101ULL, + 0b0000000000000000000000000000000000000000000000000000000000011110ULL, + 0b0000000000000000000000000000000000000000000000000000000000100111ULL, + 0b0000000000000000000000000000000000000000000000000000000000101011ULL, + 0b0000000000000000000000000000000000000000000000000000000000101101ULL, + 0b0000000000000000000000000000000000000000000000000000000000101110ULL, + 0b0000000000000000000000000000000000000000000000000000000000110011ULL, + 0b0000000000000000000000000000000000000000000000000000000000110101ULL, + 0b0000000000000000000000000000000000000000000000000000000000110110ULL, + 0b0000000000000000000000000000000000000000000000000000000000111001ULL, + 0b0000000000000000000000000000000000000000000000000000000000111010ULL, + 0b0000000000000000000000000000000000000000000000000000000000111100ULL, + }); +} + +TEST_CASE("CartesianProductEnumerator-of-constants", "[bitset]") +{ + CartesianProductEnumerator cpe( + { + std::make_shared(std::bitset<64>(0x0000ff)), + std::make_shared(std::bitset<64>(0xff0000)) + }); + expect_bitsets(cpe,{ 0xff00ff }); +} + +TEST_CASE("SelectionEnumerator-of-constants", "[bitset]") +{ + SelectionEnumerator spe(std::make_shared(2, 3), + { + std::make_shared(std::bitset<64>(0xff0000)), + std::make_shared(std::bitset<64>(0x00ff00)), + std::make_shared(std::bitset<64>(0x0000ff)), + }); + expect_bitsets( + spe, + { + 0b0000000000000000000000000000000000000000111111111111111100000000ULL, + 0b0000000000000000000000000000000000000000111111110000000011111111ULL, + 0b0000000000000000000000000000000000000000000000001111111111111111ULL + }); +} + +TEST_CASE("SelectionEnumerator-of-bit-numbers", "[bitset]") +{ + auto se = SelectionEnumerator::bitNumbers(4, {1, 2, 7, 8, 30, 31}); + expect_bitsets( + *se, + { + 0b0000000000000000000000000000000000000000000000000000000110000110ULL, + 0b0000000000000000000000000000000001000000000000000000000010000110ULL, + 0b0000000000000000000000000000000001000000000000000000000100000110ULL, + 0b0000000000000000000000000000000001000000000000000000000110000010ULL, + 0b0000000000000000000000000000000001000000000000000000000110000100ULL, + 0b0000000000000000000000000000000010000000000000000000000010000110ULL, + 0b0000000000000000000000000000000010000000000000000000000100000110ULL, + 0b0000000000000000000000000000000010000000000000000000000110000010ULL, + 0b0000000000000000000000000000000010000000000000000000000110000100ULL, + 0b0000000000000000000000000000000011000000000000000000000000000110ULL, + 0b0000000000000000000000000000000011000000000000000000000010000010ULL, + 0b0000000000000000000000000000000011000000000000000000000010000100ULL, + 0b0000000000000000000000000000000011000000000000000000000100000010ULL, + 0b0000000000000000000000000000000011000000000000000000000100000100ULL, + 0b0000000000000000000000000000000011000000000000000000000110000000ULL + }); +} + +TEST_CASE("CartesianProductEnumerator-of-selections", "[bitset]") +{ + CartesianProductEnumerator cpe( + { + SelectionEnumerator::bitNumbers(2, std::vector({0,1,2})), + SelectionEnumerator::bitNumbers(5, std::vector({6,7,8,32,35,54})) + }); + expect_bitsets(cpe, + { + 0b0000000000000000000000000000100100000000000000000000000111000011ULL, + 0b0000000000000000000000000000100100000000000000000000000111000101ULL, + 0b0000000000000000000000000000100100000000000000000000000111000110ULL, + 0b0000000001000000000000000000000100000000000000000000000111000011ULL, + 0b0000000001000000000000000000000100000000000000000000000111000101ULL, + 0b0000000001000000000000000000000100000000000000000000000111000110ULL, + 0b0000000001000000000000000000100000000000000000000000000111000011ULL, + 0b0000000001000000000000000000100000000000000000000000000111000101ULL, + 0b0000000001000000000000000000100000000000000000000000000111000110ULL, + 0b0000000001000000000000000000100100000000000000000000000011000011ULL, + 0b0000000001000000000000000000100100000000000000000000000011000101ULL, + 0b0000000001000000000000000000100100000000000000000000000011000110ULL, + 0b0000000001000000000000000000100100000000000000000000000101000011ULL, + 0b0000000001000000000000000000100100000000000000000000000101000101ULL, + 0b0000000001000000000000000000100100000000000000000000000101000110ULL, + 0b0000000001000000000000000000100100000000000000000000000110000011ULL, + 0b0000000001000000000000000000100100000000000000000000000110000101ULL, + 0b0000000001000000000000000000100100000000000000000000000110000110ULL + }); +} + +TEST_CASE("CartesianProductEnumerator-of-mixture", "[bitset]") +{ + CartesianProductEnumerator cpe( + { + SelectionEnumerator::bitNumbers(2, std::vector({0,1,2})), + std::make_shared(std::bitset<64>(0x00ff00)), + SelectionEnumerator::bitNumbers(5, std::vector({6,7,8,32,35,54})) + }); + expect_bitsets(cpe, + { + 0b0000000000000000000000000000100100000000000000001111111111000011ULL, + 0b0000000000000000000000000000100100000000000000001111111111000101ULL, + 0b0000000000000000000000000000100100000000000000001111111111000110ULL, + 0b0000000001000000000000000000000100000000000000001111111111000011ULL, + 0b0000000001000000000000000000000100000000000000001111111111000101ULL, + 0b0000000001000000000000000000000100000000000000001111111111000110ULL, + 0b0000000001000000000000000000100000000000000000001111111111000011ULL, + 0b0000000001000000000000000000100000000000000000001111111111000101ULL, + 0b0000000001000000000000000000100000000000000000001111111111000110ULL, + 0b0000000001000000000000000000100100000000000000001111111111000011ULL, + 0b0000000001000000000000000000100100000000000000001111111111000101ULL, + 0b0000000001000000000000000000100100000000000000001111111111000110ULL, + 0b0000000001000000000000000000100100000000000000001111111101000011ULL, + 0b0000000001000000000000000000100100000000000000001111111101000101ULL, + 0b0000000001000000000000000000100100000000000000001111111101000110ULL, + 0b0000000001000000000000000000100100000000000000001111111110000011ULL, + 0b0000000001000000000000000000100100000000000000001111111110000101ULL, + 0b0000000001000000000000000000100100000000000000001111111110000110ULL + }); +} + + +TEST_CASE("SelectionEnumerator-of-selections", "[bitset]") +{ + auto a = SelectionEnumerator::bitNumbers(2, {0, 1, 2}); + auto b = SelectionEnumerator::bitNumbers(2, {0x10, 0x11, 0x12}); + auto c = SelectionEnumerator::bitNumbers(2, {0x20, 0x21, 0x22}); + SelectionEnumerator composite(std::make_shared(2, 3), + { a, b, c }); + expect_bitsets(composite, + { + 0b0000000000000000000000000000000000000000000000110000000000000011ULL, + 0b0000000000000000000000000000000000000000000000110000000000000101ULL, + 0b0000000000000000000000000000000000000000000000110000000000000110ULL, + 0b0000000000000000000000000000000000000000000001010000000000000011ULL, + 0b0000000000000000000000000000000000000000000001010000000000000101ULL, + 0b0000000000000000000000000000000000000000000001010000000000000110ULL, + 0b0000000000000000000000000000000000000000000001100000000000000011ULL, + 0b0000000000000000000000000000000000000000000001100000000000000101ULL, + 0b0000000000000000000000000000000000000000000001100000000000000110ULL, + 0b0000000000000000000000000000001100000000000000000000000000000011ULL, + 0b0000000000000000000000000000001100000000000000000000000000000101ULL, + 0b0000000000000000000000000000001100000000000000000000000000000110ULL, + 0b0000000000000000000000000000010100000000000000000000000000000011ULL, + 0b0000000000000000000000000000010100000000000000000000000000000101ULL, + 0b0000000000000000000000000000010100000000000000000000000000000110ULL, + 0b0000000000000000000000000000011000000000000000000000000000000011ULL, + 0b0000000000000000000000000000011000000000000000000000000000000101ULL, + 0b0000000000000000000000000000011000000000000000000000000000000110ULL, + 0b0000000000000000000000000000001100000000000000110000000000000000ULL, + 0b0000000000000000000000000000001100000000000001010000000000000000ULL, + 0b0000000000000000000000000000001100000000000001100000000000000000ULL, + 0b0000000000000000000000000000010100000000000000110000000000000000ULL, + 0b0000000000000000000000000000010100000000000001010000000000000000ULL, + 0b0000000000000000000000000000010100000000000001100000000000000000ULL, + 0b0000000000000000000000000000011000000000000000110000000000000000ULL, + 0b0000000000000000000000000000011000000000000001010000000000000000ULL, + 0b0000000000000000000000000000011000000000000001100000000000000000ULL, + }); +} From f66a2c0b0b194c9a1787ed6e817d5c19745acc8c Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Tue, 12 Jan 2016 13:36:06 -0800 Subject: [PATCH 2/7] Modify Config to indicate when short name is alias. --- src/main/Config.cpp | 15 ++++++++++++++- src/main/Config.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/Config.cpp b/src/main/Config.cpp index eb5e010cae..d9a0b36379 100644 --- a/src/main/Config.cpp +++ b/src/main/Config.cpp @@ -779,14 +779,27 @@ Config::toShortString(PublicKey const& pk) const } std::string -Config::toStrKey(PublicKey const& pk) const +Config::toStrKey(PublicKey const& pk, bool& isAlias) const { std::string ret = PubKeyUtils::toStrKey(pk); auto it = VALIDATOR_NAMES.find(ret); if (it == VALIDATOR_NAMES.end()) + { + isAlias = false; return ret; + } else + { + isAlias = true; return it->second; + } +} + +std::string +Config::toStrKey(PublicKey const& pk) const +{ + bool isAlias; + return toStrKey(pk, isAlias); } bool diff --git a/src/main/Config.h b/src/main/Config.h index 4a4cd55478..6069aaba10 100644 --- a/src/main/Config.h +++ b/src/main/Config.h @@ -179,6 +179,7 @@ class Config : public std::enable_shared_from_this void load(std::string const& filename); std::string toShortString(PublicKey const& pk) const; + std::string toStrKey(PublicKey const& pk, bool& isAlias) const; std::string toStrKey(PublicKey const& pk) const; bool resolveNodeID(std::string const& s, PublicKey& retKey) const; }; From 7f1535bdfba767e93725bf6df1b45beafb9eb83e Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Tue, 12 Jan 2016 13:36:51 -0800 Subject: [PATCH 3/7] Add InferredQuorum. --- src/history/InferredQuorum.cpp | 376 ++++++++++++++++++++++++++++ src/history/InferredQuorum.h | 31 +++ src/history/InferredQuorumTests.cpp | 275 ++++++++++++++++++++ 3 files changed, 682 insertions(+) create mode 100644 src/history/InferredQuorum.cpp create mode 100644 src/history/InferredQuorum.h create mode 100644 src/history/InferredQuorumTests.cpp diff --git a/src/history/InferredQuorum.cpp b/src/history/InferredQuorum.cpp new file mode 100644 index 0000000000..93d0615247 --- /dev/null +++ b/src/history/InferredQuorum.cpp @@ -0,0 +1,376 @@ +#include "history/InferredQuorum.h" +#include +#include +#include "xdrpp/marshal.h" +#include "crypto/SHA.h" +#include "util/BitsetEnumerator.h" +#include "util/Logging.h" + +namespace stellar { + +void +InferredQuorum::noteSCPHistory(SCPHistoryEntry const& hist) +{ + for (auto const& qset : hist.v0().quorumSets) + { + noteQset(qset); + } + for (auto const& msg : hist.v0().ledgerMessages.messages) + { + auto pk = msg.statement.nodeID; + notePubKey(pk); + auto const& pledges = msg.statement.pledges; + switch (pledges.type()) + { + case SCP_ST_PREPARE: + noteQsetHash(pk, pledges.prepare().quorumSetHash); + break; + case SCP_ST_CONFIRM: + noteQsetHash(pk, pledges.confirm().quorumSetHash); + break; + case SCP_ST_EXTERNALIZE: + noteQsetHash(pk, pledges.externalize().commitQuorumSetHash); + break; + case SCP_ST_NOMINATE: + noteQsetHash(pk, pledges.nominate().quorumSetHash); + break; + } + } +} + +void +InferredQuorum::noteQsetHash(PublicKey const& pk, Hash const& qsetHash) +{ + auto range = mQsetHashes.equal_range(pk); + for (auto i = range.first; i != range.second; ++i) + { + if (i->second == qsetHash) + { + // Already noted, quit now. + return; + } + } + mQsetHashes.insert(std::make_pair(pk, qsetHash)); +} + +void +InferredQuorum::noteQset(SCPQuorumSet const& qset) +{ + Hash qSetHash = sha256(xdr::xdr_to_opaque(qset)); + if (mQsets.find(qSetHash) == mQsets.end()) + { + mQsets.insert(std::make_pair(qSetHash, qset)); + } + for (auto const& pk : qset.validators) + { + notePubKey(pk); + } + for (auto const& inner : qset.innerSets) + { + noteQset(inner); + } +} + +void +InferredQuorum::notePubKey(PublicKey const& pk) +{ + mPubKeys[pk]++; +} + +static std::shared_ptr +makeQsetEnumerator(SCPQuorumSet const& qset, + std::unordered_map const& nodeNumbers) +{ + std::vector> innerEnums; + for (auto const& v : qset.validators) + { + auto i = nodeNumbers.find(v); + assert(i != nodeNumbers.end()); + innerEnums.push_back(ConstantEnumerator::bitNumber(i->second)); + } + for (auto const& s : qset.innerSets) + { + innerEnums.push_back(makeQsetEnumerator(s, nodeNumbers)); + } + return std::make_shared( + std::make_shared(qset.threshold, + innerEnums.size()), + innerEnums); +} + +static std::shared_ptr +makeSliceEnumerator(InferredQuorum const& iq, + PublicKey const& pk, + std::unordered_map const& nodeNumbers) +{ + // Enumerating a slice is the cartesian product enumeration of a + // constant enumerator (for the node itself) and a selection enumerator + // that does n-of-k for its validators and subqsets. + std::vector> innerEnums; + + auto i = nodeNumbers.find(pk); + assert(i != nodeNumbers.end()); + innerEnums.push_back(ConstantEnumerator::bitNumber(i->second)); + + auto qsh = iq.mQsetHashes.find(pk); + assert(qsh != iq.mQsetHashes.end()); + + auto qs = iq.mQsets.find(qsh->second); + assert(qs != iq.mQsets.end()); + + innerEnums.push_back(makeQsetEnumerator(qs->second, nodeNumbers)); + return std::make_shared(innerEnums); +} + +static bool +isQuorum(std::bitset<64> const& q, + InferredQuorum const& iq, + std::unordered_map const& nodeNumbers, + std::vector const& revNodeNumbers) +{ + for (size_t i = 0; i < q.size(); ++i) + { + if (q.test(i)) + { + auto e = makeSliceEnumerator(iq, revNodeNumbers.at(i), nodeNumbers); + if (!e) + { + return false; + } + bool containsSliceForE = false; + while (*e) + { + // If we find _any_ slice in e's slices that + // is covered by q, we're good. + if ((q | **e) == q) + { + containsSliceForE = true; + break; + } + ++(*e); + } + if (!containsSliceForE) + { + return false; + } + } + } + return true; +} + +bool +InferredQuorum::checkQuorumIntersection(Config const& cfg) const +{ + // Definition (quorum). A set of nodes U ⊆ V in FBAS ⟨V,Q⟩ is a quorum + // iff U =/= ∅ and U contains a slice for each member -- i.e., ∀ v ∈ U, + // ∃ q ∈ Q(v) such that q ⊆ U. + // + // Definition (quorum intersection). An FBAS enjoys quorum intersection + // iff any two of its quorums share a node—i.e., for all quorums U1 and + // U2, U1 ∩ U2 =/= ∅. + + // Assign a bit-number to each node + std::unordered_map nodeNumbers; + std::vector revNodeNumbers; + for (auto const& n : mPubKeys) + { + nodeNumbers.insert(std::make_pair(n.first, nodeNumbers.size())); + revNodeNumbers.push_back(n.first); + } + + // We're (only) going to scan the powerset of the nodes we _have_ qsets + // for, which might be significantly fewer than the total set of nodes; + // we can't really tell how nodes we don't have qsets for will behave + // in a network; we exclude them. + std::unordered_set nodesWithQsets; + std::vector> nodeEnumerators; + for (auto const& n : mQsetHashes) + { + assert(mQsets.find(n.second) != mQsets.end()); + auto i = nodeNumbers.find(n.first); + assert(i != nodeNumbers.end()); + nodesWithQsets.insert(i->second); + } + for (auto nwq : nodesWithQsets) + { + nodeEnumerators.push_back(ConstantEnumerator::bitNumber(nwq)); + } + + // Build an enumerator for the powerset of the nodes we have qsets for; + // this will thus return _candidate_ quorums, each of which we'll check + // for quorum-ness. + SelectionEnumerator quorumCandidateEnumerator( + std::make_shared(nodeEnumerators.size()), + nodeEnumerators); + + assert(nodeEnumerators.size() < 64); + uint64_t lim = 1ULL << nodeEnumerators.size(); + CLOG(INFO, "History") << "Scanning: " << lim + << " possible node subsets (of " + << nodeEnumerators.size() + << " nodes with qsets)"; + + // Enumerate all the quorums, de-duplicating into a hashset + std::unordered_set allQuorums; + while (quorumCandidateEnumerator) + { + auto bv = *quorumCandidateEnumerator; + if (isQuorum(bv, *this, nodeNumbers, revNodeNumbers)) + { + CLOG(INFO, "History") << "Quorum: " << bv; + allQuorums.insert(bv.to_ullong()); + } + ++quorumCandidateEnumerator; + } + + // Report what we found. + for (auto const& pk : mPubKeys) + { + if (mQsetHashes.find(pk.first) == mQsetHashes.end()) + { + CLOG(WARNING, "History") + << "Node without qset: " << cfg.toShortString(pk.first); + } + } + CLOG(INFO, "History") << "Found " << nodeNumbers.size() << " nodes total"; + CLOG(INFO, "History") << "Found " + << nodeEnumerators.size() << " nodes with qsets"; + CLOG(INFO, "History") << "Found " << allQuorums.size() << " quorums"; + + bool allOk = true; + for (auto const& q : allQuorums) + { + for (auto const& v : allQuorums) + { + if (q != v) + { + if (!(q & v)) + { + allOk = false; + CLOG(WARNING, "History") + << "Warning: found pair of non-intersecting quorums"; + CLOG(WARNING, "History") + << std::bitset<64>(q); + CLOG(WARNING, "History") + << "vs."; + CLOG(WARNING, "History") + << std::bitset<64>(v); + } + } + } + } + + if (allOk) + { + CLOG(INFO, "History") << "Network of " << nodeEnumerators.size() + << " nodes enjoys quorum intersection: "; + } + else + { + CLOG(WARNING, "History") << "Network of " << nodeEnumerators.size() + << " nodes DOES NOT enjoy quorum intersection: "; + } + for (auto n : nodesWithQsets) + { + auto isAlias = false; + auto name = cfg.toStrKey(revNodeNumbers.at(n), isAlias); + if (allOk) + { + CLOG(INFO, "History") + << " \"" << (isAlias ? "$" : "") << name << '"'; + } + else + { + CLOG(WARNING, "History") + << " \"" << (isAlias ? "$" : "") << name << '"'; + } + } + return allOk; +} + +std::string +InferredQuorum::toString(Config const& cfg) const +{ + std::ostringstream out; + + // By default we will emit only those keys involved in half or + // more of the quorums we've observed them in. This could be made + // more clever. + size_t thresh = 0; + for (auto const& pair : mPubKeys) + { + thresh = pair.second > thresh ? pair.second : thresh; + } + thresh >>= 2; + + for (auto const& pair : mPubKeys) + { + auto isAlias = false; + auto name = cfg.toStrKey(pair.first, isAlias); + if (pair.second < thresh) + { + out << "# skipping unreliable " + << "(" << pair.second << "/" << thresh << ") node: " + << '"' << (isAlias ? "$" : "") << name << '"' + << std::endl; + } + } + + out << "[QUORUM_SET]" << std::endl; + out << "[" << std::endl; + auto first = true; + for (auto const& pair : mPubKeys) + { + if (pair.second < thresh) + { + continue; + } + auto isAlias = false; + auto name = cfg.toStrKey(pair.first, isAlias); + if (first) + { + first = false; + } + else + { + out << "," << std::endl; + } + out << '"' << (isAlias ? "$" : "") << name << '"'; + } + out << std::endl << "]" << std::endl; + return out.str(); +} + +void +InferredQuorum::writeQuorumGraph(Config const& cfg, + std::string const& filename) const +{ + std::ofstream out(filename); + out << "digraph {" << std::endl; + for (auto const& pkq : mQsetHashes) + { + auto qp = mQsets.find(pkq.second); + if (qp != mQsets.end()) + { + auto src = cfg.toShortString(pkq.first); + for (auto const& dst : qp->second.validators) + { + out << src << " -> " + << cfg.toShortString(dst) + << ";" << std::endl; + } + for (auto const& iqs : qp->second.innerSets) + { + for (auto const& dst : iqs.validators) + { + out << src << " -> " + << cfg.toShortString(dst) + << ";" << std::endl; + } + } + } + } + out << "}" << std::endl; +} + +} diff --git a/src/history/InferredQuorum.h b/src/history/InferredQuorum.h new file mode 100644 index 0000000000..99fe44ca82 --- /dev/null +++ b/src/history/InferredQuorum.h @@ -0,0 +1,31 @@ +#pragma once + +// Copyright 2016 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#include +#include +#include "overlay/StellarXDR.h" +#include "crypto/SecretKey.h" +#include "util/HashOfHash.h" +#include "main/Config.h" + +namespace stellar { + +struct InferredQuorum +{ + std::unordered_map mQsets; + std::unordered_multimap mQsetHashes; + std::unordered_map mPubKeys; + void noteSCPHistory(SCPHistoryEntry const& hist); + void noteQset(SCPQuorumSet const& qset); + void noteQsetHash(PublicKey const& pk, Hash const& hash); + void notePubKey(PublicKey const& pk); + std::string toString(Config const& cfg) const; + void writeQuorumGraph(Config const& cfg, + std::string const& filename) const; + bool checkQuorumIntersection(Config const& cfg) const; +}; + +} diff --git a/src/history/InferredQuorumTests.cpp b/src/history/InferredQuorumTests.cpp new file mode 100644 index 0000000000..0fa5afab15 --- /dev/null +++ b/src/history/InferredQuorumTests.cpp @@ -0,0 +1,275 @@ +// Copyright 2016 Stellar Development Foundation and contributors. Licensed +// under the Apache License, Version 2.0. See the COPYING file at the root +// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 + +#include "history/InferredQuorum.h" +#include "main/test.h" +#include "main/Config.h" +#include "xdrpp/marshal.h" +#include "crypto/SHA.h" +#include "crypto/Hex.h" +#include "lib/catch.hpp" +#include "util/Logging.h" +#include + +using namespace stellar; + +TEST_CASE("InferredQuorum intersection", "[history][inferredquorum]") +{ + InferredQuorum iq; + + SecretKey skA = SecretKey::random(); + SecretKey skB = SecretKey::random(); + SecretKey skC = SecretKey::random(); + SecretKey skD = SecretKey::random(); + + PublicKey pkA = skA.getPublicKey(); + PublicKey pkB = skB.getPublicKey(); + PublicKey pkC = skC.getPublicKey(); + PublicKey pkD = skD.getPublicKey(); + + xdr::xvector emptySet; + SCPQuorumSet qsA(2, xdr::xvector({ pkB, pkC, pkD }), emptySet); + SCPQuorumSet qsB(2, xdr::xvector({ pkA, pkC, pkD }), emptySet); + SCPQuorumSet qsC(2, xdr::xvector({ pkA, pkB, pkD }), emptySet); + SCPQuorumSet qsD(2, xdr::xvector({ pkA, pkB, pkC }), emptySet); + + Hash qshA = sha256(xdr::xdr_to_opaque(qsA)); + Hash qshB = sha256(xdr::xdr_to_opaque(qsB)); + Hash qshC = sha256(xdr::xdr_to_opaque(qsC)); + Hash qshD = sha256(xdr::xdr_to_opaque(qsD)); + + iq.mPubKeys[pkA]++; + iq.mPubKeys[pkB]++; + iq.mPubKeys[pkC]++; + iq.mPubKeys[pkD]++; + + iq.mQsetHashes.insert(std::make_pair(pkA, qshA)); + iq.mQsetHashes.insert(std::make_pair(pkB, qshB)); + iq.mQsetHashes.insert(std::make_pair(pkC, qshC)); + iq.mQsetHashes.insert(std::make_pair(pkD, qshD)); + + iq.mQsets[qshA] = qsA; + iq.mQsets[qshB] = qsB; + iq.mQsets[qshC] = qsC; + iq.mQsets[qshD] = qsD; + + Config cfg(getTestConfig(0, Config::TESTDB_IN_MEMORY_SQLITE)); + CHECK(iq.checkQuorumIntersection(cfg)); +} + +TEST_CASE("InferredQuorum intersection w/ subquorums", "[history][inferredquorum][subquorum]") +{ + InferredQuorum iq; + + SecretKey skA = SecretKey::random(); + SecretKey skB = SecretKey::random(); + SecretKey skC = SecretKey::random(); + SecretKey skD = SecretKey::random(); + SecretKey skE = SecretKey::random(); + SecretKey skF = SecretKey::random(); + + PublicKey pkA = skA.getPublicKey(); + PublicKey pkB = skB.getPublicKey(); + PublicKey pkC = skC.getPublicKey(); + PublicKey pkD = skD.getPublicKey(); + PublicKey pkE = skE.getPublicKey(); + PublicKey pkF = skF.getPublicKey(); + + xdr::xvector noKeys; + xdr::xvector emptySet; + + SCPQuorumSet qsABC(2, xdr::xvector({ pkA, pkB, pkC }), emptySet); + SCPQuorumSet qsABD(2, xdr::xvector({ pkA, pkB, pkD }), emptySet); + SCPQuorumSet qsABE(2, xdr::xvector({ pkA, pkB, pkE }), emptySet); + SCPQuorumSet qsABF(2, xdr::xvector({ pkA, pkB, pkF }), emptySet); + + SCPQuorumSet qsACD(2, xdr::xvector({ pkA, pkC, pkD }), emptySet); + SCPQuorumSet qsACE(2, xdr::xvector({ pkA, pkC, pkE }), emptySet); + SCPQuorumSet qsACF(2, xdr::xvector({ pkA, pkC, pkF }), emptySet); + + SCPQuorumSet qsADE(2, xdr::xvector({ pkA, pkD, pkE }), emptySet); + SCPQuorumSet qsADF(2, xdr::xvector({ pkA, pkD, pkF }), emptySet); + + SCPQuorumSet qsBDC(2, xdr::xvector({ pkB, pkD, pkC }), emptySet); + SCPQuorumSet qsBDE(2, xdr::xvector({ pkB, pkD, pkE }), emptySet); + SCPQuorumSet qsCDE(2, xdr::xvector({ pkC, pkD, pkE }), emptySet); + + SCPQuorumSet qsA(2, noKeys, xdr::xvector({ qsBDC, qsBDE, qsCDE })); + SCPQuorumSet qsB(2, noKeys, xdr::xvector({ qsACD, qsACE, qsACF })); + SCPQuorumSet qsC(2, noKeys, xdr::xvector({ qsABD, qsABE, qsABF })); + + SCPQuorumSet qsD(2, noKeys, xdr::xvector({ qsABC, qsABE, qsABF })); + SCPQuorumSet qsE(2, noKeys, xdr::xvector({ qsABC, qsABD, qsABF })); + SCPQuorumSet qsF(2, noKeys, xdr::xvector({ qsABC, qsABD, qsABE })); + + Hash qshA = sha256(xdr::xdr_to_opaque(qsA)); + Hash qshB = sha256(xdr::xdr_to_opaque(qsB)); + Hash qshC = sha256(xdr::xdr_to_opaque(qsC)); + Hash qshD = sha256(xdr::xdr_to_opaque(qsD)); + Hash qshE = sha256(xdr::xdr_to_opaque(qsE)); + Hash qshF = sha256(xdr::xdr_to_opaque(qsF)); + + iq.mPubKeys[pkA]++; + iq.mPubKeys[pkB]++; + iq.mPubKeys[pkC]++; + iq.mPubKeys[pkD]++; + iq.mPubKeys[pkE]++; + iq.mPubKeys[pkF]++; + + iq.mQsetHashes.insert(std::make_pair(pkA, qshA)); + iq.mQsetHashes.insert(std::make_pair(pkB, qshB)); + iq.mQsetHashes.insert(std::make_pair(pkC, qshC)); + iq.mQsetHashes.insert(std::make_pair(pkD, qshD)); + iq.mQsetHashes.insert(std::make_pair(pkE, qshE)); + iq.mQsetHashes.insert(std::make_pair(pkF, qshF)); + + iq.mQsets[qshA] = qsA; + iq.mQsets[qshB] = qsB; + iq.mQsets[qshC] = qsC; + iq.mQsets[qshD] = qsD; + iq.mQsets[qshE] = qsE; + iq.mQsets[qshF] = qsF; + + Config cfg(getTestConfig(0, Config::TESTDB_IN_MEMORY_SQLITE)); + CHECK(iq.checkQuorumIntersection(cfg)); +} + + +TEST_CASE("InferredQuorum non-intersection", "[history][inferredquorum]") +{ + InferredQuorum iq; + + SecretKey skA = SecretKey::random(); + SecretKey skB = SecretKey::random(); + SecretKey skC = SecretKey::random(); + SecretKey skD = SecretKey::random(); + SecretKey skE = SecretKey::random(); + SecretKey skF = SecretKey::random(); + + PublicKey pkA = skA.getPublicKey(); + PublicKey pkB = skB.getPublicKey(); + PublicKey pkC = skC.getPublicKey(); + PublicKey pkD = skD.getPublicKey(); + PublicKey pkE = skE.getPublicKey(); + PublicKey pkF = skF.getPublicKey(); + + xdr::xvector emptySet; + SCPQuorumSet qsA(2, xdr::xvector({ pkB, pkC, pkD, pkE, pkF }), emptySet); + SCPQuorumSet qsB(2, xdr::xvector({ pkA, pkC, pkD, pkE, pkF }), emptySet); + SCPQuorumSet qsC(2, xdr::xvector({ pkA, pkB, pkD, pkE, pkF }), emptySet); + SCPQuorumSet qsD(2, xdr::xvector({ pkA, pkB, pkC, pkE, pkF }), emptySet); + SCPQuorumSet qsE(2, xdr::xvector({ pkA, pkB, pkC, pkD, pkF }), emptySet); + SCPQuorumSet qsF(2, xdr::xvector({ pkA, pkB, pkC, pkD, pkE }), emptySet); + + Hash qshA = sha256(xdr::xdr_to_opaque(qsA)); + Hash qshB = sha256(xdr::xdr_to_opaque(qsB)); + Hash qshC = sha256(xdr::xdr_to_opaque(qsC)); + Hash qshD = sha256(xdr::xdr_to_opaque(qsD)); + Hash qshE = sha256(xdr::xdr_to_opaque(qsE)); + Hash qshF = sha256(xdr::xdr_to_opaque(qsF)); + + iq.mPubKeys[pkA]++; + iq.mPubKeys[pkB]++; + iq.mPubKeys[pkC]++; + iq.mPubKeys[pkD]++; + iq.mPubKeys[pkE]++; + iq.mPubKeys[pkF]++; + + iq.mQsetHashes.insert(std::make_pair(pkA, qshA)); + iq.mQsetHashes.insert(std::make_pair(pkB, qshB)); + iq.mQsetHashes.insert(std::make_pair(pkC, qshC)); + iq.mQsetHashes.insert(std::make_pair(pkD, qshD)); + iq.mQsetHashes.insert(std::make_pair(pkE, qshE)); + iq.mQsetHashes.insert(std::make_pair(pkF, qshF)); + + iq.mQsets[qshA] = qsA; + iq.mQsets[qshB] = qsB; + iq.mQsets[qshC] = qsC; + iq.mQsets[qshD] = qsD; + iq.mQsets[qshE] = qsE; + iq.mQsets[qshF] = qsF; + + Config cfg(getTestConfig(0, Config::TESTDB_IN_MEMORY_SQLITE)); + CHECK(!iq.checkQuorumIntersection(cfg)); +} + + +TEST_CASE("InferredQuorum non-intersection w/ subquorums", "[history][inferredquorum][subquorum]") +{ + InferredQuorum iq; + + SecretKey skA = SecretKey::random(); + SecretKey skB = SecretKey::random(); + SecretKey skC = SecretKey::random(); + SecretKey skD = SecretKey::random(); + SecretKey skE = SecretKey::random(); + SecretKey skF = SecretKey::random(); + + PublicKey pkA = skA.getPublicKey(); + PublicKey pkB = skB.getPublicKey(); + PublicKey pkC = skC.getPublicKey(); + PublicKey pkD = skD.getPublicKey(); + PublicKey pkE = skE.getPublicKey(); + PublicKey pkF = skF.getPublicKey(); + + xdr::xvector noKeys; + xdr::xvector emptySet; + + SCPQuorumSet qsABC(2, xdr::xvector({ pkA, pkB, pkC }), emptySet); + SCPQuorumSet qsABD(2, xdr::xvector({ pkA, pkB, pkD }), emptySet); + SCPQuorumSet qsABE(2, xdr::xvector({ pkA, pkB, pkE }), emptySet); + SCPQuorumSet qsABF(2, xdr::xvector({ pkA, pkB, pkF }), emptySet); + + SCPQuorumSet qsACD(2, xdr::xvector({ pkA, pkC, pkD }), emptySet); + SCPQuorumSet qsACE(2, xdr::xvector({ pkA, pkC, pkE }), emptySet); + SCPQuorumSet qsACF(2, xdr::xvector({ pkA, pkC, pkF }), emptySet); + + SCPQuorumSet qsADE(2, xdr::xvector({ pkA, pkD, pkE }), emptySet); + SCPQuorumSet qsADF(2, xdr::xvector({ pkA, pkD, pkF }), emptySet); + + SCPQuorumSet qsBDC(2, xdr::xvector({ pkB, pkD, pkC }), emptySet); + SCPQuorumSet qsBDE(2, xdr::xvector({ pkB, pkD, pkE }), emptySet); + SCPQuorumSet qsBDF(2, xdr::xvector({ pkB, pkD, pkF }), emptySet); + SCPQuorumSet qsCDE(2, xdr::xvector({ pkC, pkD, pkE }), emptySet); + SCPQuorumSet qsCDF(2, xdr::xvector({ pkC, pkD, pkF }), emptySet); + + SCPQuorumSet qsA(2, noKeys, xdr::xvector({ qsABC, qsABD, qsABE })); + SCPQuorumSet qsB(2, noKeys, xdr::xvector({ qsBDC, qsABD, qsABF })); + SCPQuorumSet qsC(2, noKeys, xdr::xvector({ qsACD, qsACD, qsACF })); + + SCPQuorumSet qsD(2, noKeys, xdr::xvector({ qsCDE, qsADE, qsBDE })); + SCPQuorumSet qsE(2, noKeys, xdr::xvector({ qsCDE, qsADE, qsBDE })); + SCPQuorumSet qsF(2, noKeys, xdr::xvector({ qsABF, qsADF, qsBDF })); + + Hash qshA = sha256(xdr::xdr_to_opaque(qsA)); + Hash qshB = sha256(xdr::xdr_to_opaque(qsB)); + Hash qshC = sha256(xdr::xdr_to_opaque(qsC)); + Hash qshD = sha256(xdr::xdr_to_opaque(qsD)); + Hash qshE = sha256(xdr::xdr_to_opaque(qsE)); + Hash qshF = sha256(xdr::xdr_to_opaque(qsF)); + + iq.mPubKeys[pkA]++; + iq.mPubKeys[pkB]++; + iq.mPubKeys[pkC]++; + iq.mPubKeys[pkD]++; + iq.mPubKeys[pkE]++; + iq.mPubKeys[pkF]++; + + iq.mQsetHashes.insert(std::make_pair(pkA, qshA)); + iq.mQsetHashes.insert(std::make_pair(pkB, qshB)); + iq.mQsetHashes.insert(std::make_pair(pkC, qshC)); + iq.mQsetHashes.insert(std::make_pair(pkD, qshD)); + iq.mQsetHashes.insert(std::make_pair(pkE, qshE)); + iq.mQsetHashes.insert(std::make_pair(pkF, qshF)); + + iq.mQsets[qshA] = qsA; + iq.mQsets[qshB] = qsB; + iq.mQsets[qshC] = qsC; + iq.mQsets[qshD] = qsD; + iq.mQsets[qshE] = qsE; + iq.mQsets[qshF] = qsF; + + Config cfg(getTestConfig(0, Config::TESTDB_IN_MEMORY_SQLITE)); + CHECK(!iq.checkQuorumIntersection(cfg)); +} From 8e3ee805a3068a05720fc3595c5115dd2b11a893 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Tue, 12 Jan 2016 13:37:23 -0800 Subject: [PATCH 4/7] Add FetchRecentQsetsWork. --- src/history/HistoryWork.cpp | 80 +++++++++++++++++++++++++++++++++++++ src/history/HistoryWork.h | 21 ++++++++++ 2 files changed, 101 insertions(+) diff --git a/src/history/HistoryWork.cpp b/src/history/HistoryWork.cpp index 1c1634a653..cc1388b6f6 100644 --- a/src/history/HistoryWork.cpp +++ b/src/history/HistoryWork.cpp @@ -1872,4 +1872,84 @@ CatchupRecentWork::onFailureRaise() asio::error_code ec = std::make_error_code(std::errc::timed_out); mEndHandler(ec, HistoryManager::CATCHUP_RECENT, mFirstVerified); } + +/////////////////////////////////////////////////////////////////////////// +// FetchRecentQsetsWork +/////////////////////////////////////////////////////////////////////////// +FetchRecentQsetsWork::FetchRecentQsetsWork(Application& app, WorkParent& parent, + InferredQuorum& inferredQuorum, + handler endHandler) + : Work(app, parent, "fetch-recent-qsets") + , mEndHandler(endHandler) + , mInferredQuorum(inferredQuorum) +{ +} + +void +FetchRecentQsetsWork::onReset() +{ + clearChildren(); + mDownloadSCPMessagesWork.reset(); + mDownloadDir = + make_unique(mApp.getTmpDirManager().tmpDir(getUniqueName())); +} + +void +FetchRecentQsetsWork::onFailureRaise() +{ + asio::error_code ec = std::make_error_code(std::errc::timed_out); + mEndHandler(ec); +} + +Work::State +FetchRecentQsetsWork::onSuccess() +{ + // Phase 1: fetch remote history archive state + if (!mGetHistoryArchiveStateWork) + { + mGetHistoryArchiveStateWork = addWork( + mRemoteState, 0, std::chrono::seconds(0)); + return WORK_PENDING; + } + + // Phase 2: download some SCP messages; for now we just pull the past + // 100 checkpoints = 9 hours of history. A more sophisticated view + // would survey longer time periods at lower resolution. + uint32_t numCheckpoints = 100; + uint32_t step = mApp.getHistoryManager().getCheckpointFrequency(); + uint32_t window = numCheckpoints * step; + uint32_t lastSeq = mRemoteState.currentLedger; + uint32_t firstSeq = lastSeq < window ? (step-1) : (lastSeq-window); + + if (!mDownloadSCPMessagesWork) + { + CLOG(INFO, "History") << "Downloading recent SCP messages: [" + << firstSeq << ", " << lastSeq << "]"; + mDownloadSCPMessagesWork = addWork( + firstSeq, lastSeq, HISTORY_FILE_TYPE_SCP, *mDownloadDir); + return WORK_PENDING; + } + + // Phase 3: extract the qsets. + for (auto i = firstSeq; i <= lastSeq; i += step) + { + CLOG(INFO, "History") << "Scanning for QSets in checkpoint: " << i; + XDRInputFileStream in; + FileTransferInfo fi(*mDownloadDir, HISTORY_FILE_TYPE_SCP, i); + in.open(fi.localPath_nogz()); + SCPHistoryEntry tmp; + while (in && in.readOne(tmp)) + { + mInferredQuorum.noteSCPHistory(tmp); + } + } + + asio::error_code ec; + mEndHandler(ec); + return WORK_SUCCESS; +} + + + + } diff --git a/src/history/HistoryWork.h b/src/history/HistoryWork.h index 83fa292387..e929a5e537 100644 --- a/src/history/HistoryWork.h +++ b/src/history/HistoryWork.h @@ -450,4 +450,25 @@ class RepairMissingBucketsWork : public BucketDownloadWork void onFailureRaise() override; Work::State onSuccess() override; }; + +class FetchRecentQsetsWork : public Work +{ + + typedef std::function handler; + handler mEndHandler; + std::unique_ptr mDownloadDir; + InferredQuorum& mInferredQuorum; + HistoryArchiveState mRemoteState; + std::shared_ptr mGetHistoryArchiveStateWork; + std::shared_ptr mDownloadSCPMessagesWork; + + public: + FetchRecentQsetsWork(Application& app, WorkParent& parent, + InferredQuorum& iq, + handler endHandler); + void onReset() override; + void onFailureRaise() override; + Work::State onSuccess() override; +}; + } From a66e6d1b33899a57b9dfbf0745332480c4c57c27 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Tue, 12 Jan 2016 13:38:02 -0800 Subject: [PATCH 5/7] Teach HistoryManager to infer quorum from recent qsets. --- src/history/HistoryManager.h | 4 ++++ src/history/HistoryManagerImpl.cpp | 16 ++++++++++++++++ src/history/HistoryManagerImpl.h | 2 ++ 3 files changed, 22 insertions(+) diff --git a/src/history/HistoryManager.h b/src/history/HistoryManager.h index 0cfe5aba0f..631d90cf82 100644 --- a/src/history/HistoryManager.h +++ b/src/history/HistoryManager.h @@ -5,6 +5,7 @@ // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 #include "overlay/StellarXDR.h" +#include "history/InferredQuorum.h" #include "history/HistoryArchive.h" #include #include @@ -350,6 +351,9 @@ class HistoryManager // Return the HistoryArchiveState of the LedgerManager's LCL virtual HistoryArchiveState getLastClosedHistoryArchiveState() const = 0; + // Infer a quorum set by reading SCP messages in history archives. + virtual InferredQuorum inferQuorum() = 0; + // Return the name of the HistoryManager's tmpdir (used for storing files in // transit). virtual std::string const& getTmpDir() = 0; diff --git a/src/history/HistoryManagerImpl.cpp b/src/history/HistoryManagerImpl.cpp index 9d55b5d994..d5f3efdc44 100644 --- a/src/history/HistoryManagerImpl.cpp +++ b/src/history/HistoryManagerImpl.cpp @@ -327,6 +327,22 @@ HistoryManagerImpl::getLastClosedHistoryArchiveState() const return HistoryArchiveState(seq, bl); } +InferredQuorum +HistoryManagerImpl::inferQuorum() +{ + InferredQuorum iq; + bool done = false; + auto handler = [&done](asio::error_code const& ec) { done = true; }; + CLOG(INFO, "History") << "Starting FetchRecentQsetsWork"; + mApp.getWorkManager().addWork(iq, handler); + mApp.getWorkManager().advanceChildren(); + while (!done) + { + mApp.getClock().crank(false); + } + return iq; +} + bool HistoryManagerImpl::hasAnyWritableHistoryArchive() { diff --git a/src/history/HistoryManagerImpl.h b/src/history/HistoryManagerImpl.h index 06758d1fa2..b2efdc3ad8 100644 --- a/src/history/HistoryManagerImpl.h +++ b/src/history/HistoryManagerImpl.h @@ -90,6 +90,8 @@ class HistoryManagerImpl : public HistoryManager HistoryArchiveState getLastClosedHistoryArchiveState() const override; + InferredQuorum inferQuorum() override; + std::string const& getTmpDir() override; std::string localFilename(std::string const& basename) override; From b40fd7c14c7d60536e92e7d41e2ddba43756493d Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Tue, 12 Jan 2016 13:38:31 -0800 Subject: [PATCH 6/7] Add main options to infer, graph and check quorums. --- src/main/main.cpp | 87 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/src/main/main.cpp b/src/main/main.cpp index 4ea26cdaba..4208f6256c 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -35,13 +35,16 @@ enum opttag OPT_CMD, OPT_CONF, OPT_CONVERTID, + OPT_CHECKQUORUM, OPT_DUMPXDR, OPT_LOADXDR, OPT_FORCESCP, OPT_FUZZ, OPT_GENFUZZ, OPT_GENSEED, + OPT_GRAPHQUORUM, OPT_HELP, + OPT_INFERQUORUM, OPT_OFFLINEINFO, OPT_LOGLEVEL, OPT_METRIC, @@ -55,13 +58,16 @@ static const struct option stellar_core_options[] = { {"c", required_argument, nullptr, OPT_CMD}, {"conf", required_argument, nullptr, OPT_CONF}, {"convertid", required_argument, nullptr, OPT_CONVERTID}, + {"checkquorum", optional_argument, nullptr, OPT_CHECKQUORUM}, {"dumpxdr", required_argument, nullptr, OPT_DUMPXDR}, {"loadxdr", required_argument, nullptr, OPT_LOADXDR}, {"forcescp", optional_argument, nullptr, OPT_FORCESCP}, {"fuzz", required_argument, nullptr, OPT_FUZZ}, {"genfuzz", required_argument, nullptr, OPT_GENFUZZ}, {"genseed", no_argument, nullptr, OPT_GENSEED}, + {"graphquorum", optional_argument, nullptr, OPT_GRAPHQUORUM}, {"help", no_argument, nullptr, OPT_HELP}, + {"inferquorum", optional_argument, nullptr, OPT_INFERQUORUM}, {"offlineinfo", no_argument, nullptr, OPT_OFFLINEINFO}, {"ll", required_argument, nullptr, OPT_LOGLEVEL}, {"metric", required_argument, nullptr, OPT_METRIC}, @@ -77,30 +83,34 @@ usage(int err = 1) std::ostream& os = err ? std::cerr : std::cout; os << "usage: stellar-core [OPTIONS]\n" "where OPTIONS can be any of:\n" - " --c Command to send to local stellar-core. try " + " --c Send a command to local stellar-core. try " "'--c help' for more information\n" - " --conf FILE To specify a config file ('-' for STDIN, " + " --conf FILE Specify a config file ('-' for STDIN, " "default 'stellar-core.cfg')\n" " --convertid ID Displays ID in all known forms\n" - " --dumpxdr FILE To dump an XDR file, for debugging\n" - " --loadxdr FILE To load an XDR bucket file, for testing\n" + " --dumpxdr FILE Dump an XDR file, for debugging\n" + " --loadxdr FILE Load an XDR bucket file, for testing\n" " --forcescp Next time stellar-core is run, SCP will start " "with the local ledger rather than waiting to hear from the " "network.\n" - " --fuzz FILE To run a single fuzz input and exit\n" + " --fuzz FILE Run a single fuzz input and exit\n" " --genfuzz FILE Generate a random fuzzer input file\n " " --genseed Generate and print a random node seed\n" - " --help To display this string\n" - " --offlineinfo Returns information for an offline instance\n" + " --help Display this string\n" + " --inferquorum Print a quorum set inferred from history\n" + " --checkquorum Check quorum intersection from history\n" + " --graphquorum Print a quorum set graph from history\n" + " --offlineinfo Return information for an offline instance\n" " --ll LEVEL Set the log level. (redundant with --c ll but " "you need this form for the tests.)\n" - " LEVEL can be:\n" + " LEVEL can be: trace, debug, info, error, " + "fatal\n" " --metric METRIC Report metric METRIC on exit\n" " --newdb Creates or restores the DB to the genesis " "ledger\n" " --newhist ARCH Initialize the named history archive ARCH\n" - " --test To run self-tests\n" - " --version To print version information\n"; + " --test Run self-tests\n" + " --version Print version information\n"; exit(err); } @@ -245,6 +255,42 @@ loadXdr(Config const& cfg, std::string const& bucketFile) } } +static void +inferQuorumAndWrite(Config const& cfg) +{ + InferredQuorum iq; + { + VirtualClock clock; + Application::pointer app = Application::create(clock, cfg); + iq = app->getHistoryManager().inferQuorum(); + } + LOG(INFO) << "Inferred quorum"; + std::cout << iq.toString(cfg) << std::endl; +} + +static void +checkQuorumIntersection(Config const& cfg) +{ + VirtualClock clock; + Application::pointer app = Application::create(clock, cfg); + InferredQuorum iq = app->getHistoryManager().inferQuorum(); + iq.checkQuorumIntersection(cfg); +} + +static void +writeQuorumGraph(Config const& cfg) +{ + InferredQuorum iq; + { + VirtualClock clock; + Application::pointer app = Application::create(clock, cfg); + iq = app->getHistoryManager().inferQuorum(); + } + std::string filename = "quorumgraph.dot"; + iq.writeQuorumGraph(cfg, filename); + LOG(INFO) << "Wrote quorum graph to " << filename; +} + static void initializeDatabase(Config& cfg) { @@ -331,6 +377,9 @@ main(int argc, char* const* argv) std::vector rest; optional forceSCP = nullptr; + bool inferQuorum = false; + bool checkQuorum = false; + bool graphQuorum = false; bool newDB = false; bool getOfflineInfo = false; std::string loadXdrBucket = ""; @@ -376,6 +425,15 @@ main(int argc, char* const* argv) std::cout << "Public: " << key.getStrKeyPublic() << std::endl; return 0; } + case OPT_INFERQUORUM: + inferQuorum = true; + break; + case OPT_CHECKQUORUM: + checkQuorum = true; + break; + case OPT_GRAPHQUORUM: + graphQuorum = true; + break; case OPT_OFFLINEINFO: getOfflineInfo = true; break; @@ -440,7 +498,8 @@ main(int argc, char* const* argv) cfg.REPORT_METRICS = metrics; - if (forceSCP || newDB || getOfflineInfo || !loadXdrBucket.empty()) + if (forceSCP || newDB || getOfflineInfo || !loadXdrBucket.empty() + || inferQuorum || graphQuorum || checkQuorum) { setNoListen(cfg); if (newDB) @@ -451,6 +510,12 @@ main(int argc, char* const* argv) showOfflineInfo(cfg); if (!loadXdrBucket.empty()) loadXdr(cfg, loadXdrBucket); + if (inferQuorum) + inferQuorumAndWrite(cfg); + if (checkQuorum) + checkQuorumIntersection(cfg); + if (graphQuorum) + writeQuorumGraph(cfg); return 0; } else if (!newHistories.empty()) From 89763c282a50aa6d1267a85aa49999fac699d515 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Sun, 10 Jan 2016 17:22:01 -0800 Subject: [PATCH 7/7] Add scp to dumpxdr. --- src/main/dumpxdr.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/dumpxdr.cpp b/src/main/dumpxdr.cpp index 6b49666650..22b96c5297 100644 --- a/src/main/dumpxdr.cpp +++ b/src/main/dumpxdr.cpp @@ -22,7 +22,7 @@ dumpstream(XDRInputFileStream& in) void dumpxdr(std::string const& filename) { - std::regex rx(".*(ledger|bucket|transactions|results)-[[:xdigit:]]+\\.xdr"); + std::regex rx(".*(ledger|bucket|transactions|results|scp)-[[:xdigit:]]+\\.xdr"); std::smatch sm; if (std::regex_match(filename, sm, rx)) { @@ -41,11 +41,15 @@ dumpxdr(std::string const& filename) { dumpstream(in); } - else + else if (sm[1] == "results") { - assert(sm[1] == "results"); dumpstream(in); } + else + { + assert(sm[1] == "scp"); + dumpstream(in); + } } else {