parent
ae2987593c
commit
a14c94f8d2
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <mw/common/Macros.h>
|
||||
#include <mw/models/crypto/Hash.h>
|
||||
#include <mw/mmr/LeafIndex.h>
|
||||
#include <set>
|
||||
|
||||
// Forward Declarations
|
||||
class IMMR;
|
||||
class ILeafSet;
|
||||
|
||||
MMR_NAMESPACE
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of contiguous, unpruned leaves,
|
||||
/// and all hashes necessary to prove membership in the PMMR.
|
||||
/// </summary>
|
||||
struct Segment
|
||||
{
|
||||
// The hashes of the requested unspent leaves, in ascending order
|
||||
std::vector<mw::Hash> leaves;
|
||||
|
||||
// The MMR node hashes needed to verify the integrity of the MMR & the provided leaves
|
||||
std::vector<mw::Hash> hashes;
|
||||
|
||||
// The "bagged" hash of the next lower peak (if there is one),
|
||||
// which is necessary to compute the PMMR root
|
||||
boost::optional<mw::Hash> lower_peak;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Builds Segments for a provided MMR and segment.
|
||||
/// </summary>
|
||||
class SegmentFactory
|
||||
{
|
||||
public:
|
||||
static Segment Assemble(
|
||||
const IMMR& mmr,
|
||||
const ILeafSet& leafset,
|
||||
const LeafIndex& first_leaf_idx,
|
||||
const uint16_t num_leaves
|
||||
);
|
||||
|
||||
private:
|
||||
static std::set<Index> CalcHashIndices(
|
||||
const ILeafSet& leafset,
|
||||
const std::vector<Index>& peak_indices,
|
||||
const mmr::LeafIndex& first_leaf_idx,
|
||||
const mmr::LeafIndex& last_leaf_idx
|
||||
);
|
||||
|
||||
static boost::optional<mw::Hash> BagNextLowerPeak(
|
||||
const IMMR& mmr,
|
||||
const std::vector<Index>& peak_indices,
|
||||
const mmr::Index& peak_idx,
|
||||
const uint64_t num_nodes
|
||||
);
|
||||
};
|
||||
|
||||
END_NAMESPACE
|
@ -0,0 +1,147 @@
|
||||
#include <mw/mmr/Segment.h>
|
||||
#include <mw/mmr/LeafSet.h>
|
||||
#include <mw/mmr/MMR.h>
|
||||
#include <mw/mmr/MMRUtil.h>
|
||||
|
||||
using namespace mmr;
|
||||
|
||||
Segment SegmentFactory::Assemble(const IMMR& mmr, const ILeafSet& leafset, const LeafIndex& first_leaf_idx, const uint16_t num_leaves)
|
||||
{
|
||||
if (!leafset.Contains(first_leaf_idx)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Segment segment;
|
||||
segment.leaves.reserve(num_leaves);
|
||||
|
||||
mmr::LeafIndex leaf_idx = first_leaf_idx;
|
||||
mmr::LeafIndex last_leaf_idx = first_leaf_idx;
|
||||
while (segment.leaves.size() < num_leaves && leaf_idx < leafset.GetNextLeafIdx()) {
|
||||
if (leafset.Contains(leaf_idx)) {
|
||||
last_leaf_idx = leaf_idx;
|
||||
segment.leaves.push_back(mmr.GetHash(leaf_idx.GetNodeIndex()));
|
||||
}
|
||||
|
||||
++leaf_idx;
|
||||
}
|
||||
|
||||
const uint64_t num_nodes = leafset.GetNextLeafIdx().GetPosition();
|
||||
std::vector<Index> peak_indices = MMRUtil::CalcPeakIndices(num_nodes);
|
||||
assert(!peak_indices.empty());
|
||||
|
||||
// Populate hashes
|
||||
std::set<Index> hash_indices = CalcHashIndices(leafset, peak_indices, first_leaf_idx, last_leaf_idx);
|
||||
segment.hashes.reserve(hash_indices.size());
|
||||
for (const Index& idx : hash_indices) {
|
||||
segment.hashes.push_back(mmr.GetHash(idx));
|
||||
}
|
||||
|
||||
// Determine the lowest peak that can be calculated using the hashes we've already provided
|
||||
auto peak = *std::find_if(
|
||||
peak_indices.begin(), peak_indices.end(),
|
||||
[&last_leaf_idx](const Index& peak_idx) { return peak_idx > last_leaf_idx.GetNodeIndex(); });
|
||||
|
||||
// Bag the next lower peak (if there is one), so the root can still be calculated
|
||||
segment.lower_peak = BagNextLowerPeak(mmr, peak_indices, peak, num_nodes);
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
std::set<Index> SegmentFactory::CalcHashIndices(
|
||||
const ILeafSet& leafset,
|
||||
const std::vector<Index>& peak_indices,
|
||||
const mmr::LeafIndex& first_leaf_idx,
|
||||
const mmr::LeafIndex& last_leaf_idx)
|
||||
{
|
||||
std::set<Index> proof_indices;
|
||||
|
||||
// 1. Add peaks of mountains to the left of first index
|
||||
boost::optional<Index> prev_peak = boost::make_optional(false, Index());
|
||||
for (const Index& peak_idx : peak_indices) {
|
||||
if (peak_idx < first_leaf_idx.GetNodeIndex()) {
|
||||
proof_indices.insert(peak_idx);
|
||||
prev_peak = peak_idx;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add indices needed to reach left edge of mountain
|
||||
auto on_mountain_left_edge = [prev_peak](const Index& idx) -> bool {
|
||||
const uint64_t adjustment = !!prev_peak ? prev_peak->GetPosition() + 1 : 0;
|
||||
return ((idx.GetPosition() + 2) - adjustment) == (uint64_t(2) << idx.GetHeight());
|
||||
};
|
||||
|
||||
Index idx = first_leaf_idx.GetNodeIndex();
|
||||
while (!on_mountain_left_edge(idx)) {
|
||||
Index sibling_idx = idx.GetSibling();
|
||||
if (sibling_idx < idx) {
|
||||
proof_indices.insert(sibling_idx);
|
||||
idx = Index(idx.GetPosition() + 1, idx.GetHeight() + 1);
|
||||
} else {
|
||||
idx = Index(sibling_idx.GetPosition() + 1, sibling_idx.GetHeight() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Add all pruned parents after first leaf and before last leaf
|
||||
BitSet pruned_parents = MMRUtil::CalcPrunedParents(leafset.ToBitSet());
|
||||
for (uint64_t pos = first_leaf_idx.GetPosition(); pos < last_leaf_idx.GetPosition(); pos++) {
|
||||
if (pruned_parents.test(pos)) {
|
||||
proof_indices.insert(Index::At(pos));
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Add indices needed to reach right edge of mountain containing the last leaf
|
||||
auto peak_iter = std::find_if(
|
||||
peak_indices.begin(), peak_indices.end(),
|
||||
[&last_leaf_idx](const Index& peak_idx) { return peak_idx > last_leaf_idx.GetNodeIndex(); });
|
||||
assert(peak_iter != peak_indices.end());
|
||||
|
||||
Index peak = *peak_iter;
|
||||
auto on_mountain_right_edge = [prev_peak, peak](const Index& idx) -> bool {
|
||||
const uint64_t adjustment = prev_peak
|
||||
.map([](const Index& i) { return i.GetPosition() + 1; })
|
||||
.value_or(0);
|
||||
return (idx.GetPosition() - adjustment) >= ((peak.GetPosition() - adjustment) - peak.GetHeight());
|
||||
};
|
||||
|
||||
idx = last_leaf_idx.GetNodeIndex();
|
||||
while (!on_mountain_right_edge(idx)) {
|
||||
Index sibling_idx = idx.GetSibling();
|
||||
if (sibling_idx > idx) {
|
||||
proof_indices.insert(sibling_idx);
|
||||
idx = Index(sibling_idx.GetPosition() + 1, idx.GetHeight() + 1);
|
||||
} else {
|
||||
idx = Index(idx.GetPosition() + 1, idx.GetHeight() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return proof_indices;
|
||||
}
|
||||
|
||||
boost::optional<mw::Hash> SegmentFactory::BagNextLowerPeak(
|
||||
const IMMR& mmr,
|
||||
const std::vector<Index>& peak_indices,
|
||||
const mmr::Index& peak_idx,
|
||||
const uint64_t num_nodes)
|
||||
{
|
||||
boost::optional<mw::Hash> lower_peak = boost::none;
|
||||
|
||||
if (peak_idx != peak_indices.back()) {
|
||||
// Bag peaks until we reach the next lowest
|
||||
for (auto iter = peak_indices.crbegin(); iter != peak_indices.crend(); iter++) {
|
||||
if (*iter == peak_idx) {
|
||||
break;
|
||||
}
|
||||
|
||||
mw::Hash peakHash = mmr.GetHash(*iter);
|
||||
if (lower_peak) {
|
||||
lower_peak = MMRUtil::CalcParentHash(Index::At(num_nodes), peakHash, *lower_peak);
|
||||
} else {
|
||||
lower_peak = peakHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lower_peak;
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2022 The Litecoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <mw/mmr/MMR.h>
|
||||
#include <mw/mmr/MMRUtil.h>
|
||||
#include <mw/mmr/LeafSet.h>
|
||||
#include <mw/mmr/Segment.h>
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <test_framework/TestMWEB.h>
|
||||
|
||||
namespace std {
|
||||
ostream& operator<<(ostream& os, const mw::Hash& hash)
|
||||
{
|
||||
os << hash.ToHex();
|
||||
return os;
|
||||
}
|
||||
} // namespace std
|
||||
|
||||
using namespace mmr;
|
||||
|
||||
struct MMRWithLeafset {
|
||||
IMMR::Ptr mmr;
|
||||
ILeafSet::Ptr leafset;
|
||||
};
|
||||
|
||||
static mmr::Leaf DeterministicLeaf(const size_t i)
|
||||
{
|
||||
std::vector<uint8_t> serialized{
|
||||
uint8_t(i >> 24),
|
||||
uint8_t(i >> 16),
|
||||
uint8_t(i >> 8),
|
||||
uint8_t(i)};
|
||||
return mmr::Leaf::Create(mmr::LeafIndex::At(i), serialized);
|
||||
}
|
||||
|
||||
static MMRWithLeafset BuildDetermininisticMMR(const size_t num_leaves)
|
||||
{
|
||||
auto mmr = std::make_shared<MemMMR>();
|
||||
auto leafset = LeafSet::Open(GetDataDir(), 0);
|
||||
for (size_t i = 0; i < num_leaves; i++) {
|
||||
mmr->AddLeaf(DeterministicLeaf(i));
|
||||
leafset->Add(mmr::LeafIndex::At(i));
|
||||
}
|
||||
|
||||
return MMRWithLeafset{mmr, leafset};
|
||||
}
|
||||
|
||||
static boost::optional<mw::Hash> CalcBaggedPeak(const IMMR::Ptr& mmr, const mmr::Index& peak_idx)
|
||||
{
|
||||
const uint64_t num_nodes = mmr->GetNextLeafIdx().GetPosition();
|
||||
|
||||
// Find the "peaks"
|
||||
std::vector<mmr::Index> peak_indices = MMRUtil::CalcPeakIndices(num_nodes);
|
||||
|
||||
// Bag 'em
|
||||
boost::optional<mw::Hash> bagged_peak;
|
||||
for (auto iter = peak_indices.crbegin(); iter != peak_indices.crend(); iter++) {
|
||||
mw::Hash peakHash = mmr->GetHash(*iter);
|
||||
if (bagged_peak) {
|
||||
bagged_peak = MMRUtil::CalcParentHash(Index::At(num_nodes), peakHash, *bagged_peak);
|
||||
} else {
|
||||
bagged_peak = peakHash;
|
||||
}
|
||||
|
||||
BOOST_TEST_MESSAGE("peak(" << iter->GetPosition() << "): " << bagged_peak);
|
||||
if (*iter == peak_idx) {
|
||||
return bagged_peak;
|
||||
}
|
||||
}
|
||||
|
||||
return bagged_peak;
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(TestSegment, MWEBTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(AssembleSegment)
|
||||
{
|
||||
auto mmr_with_leafset = BuildDetermininisticMMR(15);
|
||||
auto mmr = mmr_with_leafset.mmr;
|
||||
auto leafset = mmr_with_leafset.leafset;
|
||||
Segment segment = SegmentFactory::Assemble(
|
||||
*mmr,
|
||||
*leafset,
|
||||
mmr::LeafIndex::At(0),
|
||||
4
|
||||
);
|
||||
|
||||
std::vector<mw::Hash> expected_leaves{
|
||||
DeterministicLeaf(0).GetHash(),
|
||||
DeterministicLeaf(1).GetHash(),
|
||||
DeterministicLeaf(2).GetHash(),
|
||||
DeterministicLeaf(3).GetHash()
|
||||
};
|
||||
BOOST_REQUIRE_EQUAL_COLLECTIONS(segment.leaves.begin(), segment.leaves.end(), expected_leaves.begin(), expected_leaves.end());
|
||||
|
||||
std::vector<mw::Hash> expected_hashes{
|
||||
mmr->GetHash(mmr::Index::At(13))
|
||||
};
|
||||
BOOST_REQUIRE_EQUAL_COLLECTIONS(segment.hashes.begin(), segment.hashes.end(), expected_hashes.begin(), expected_hashes.end());
|
||||
|
||||
boost::optional<mw::Hash> expected_lower_peak = CalcBaggedPeak(mmr, mmr::Index::At(21));
|
||||
BOOST_REQUIRE_EQUAL(expected_lower_peak, segment.lower_peak);
|
||||
|
||||
// Verify PMMR root can be fully recomputed
|
||||
mw::Hash n2 = MMRUtil::CalcParentHash(mmr::Index::At(2), segment.leaves[0], segment.leaves[1]);
|
||||
mw::Hash n5 = MMRUtil::CalcParentHash(mmr::Index::At(5), segment.leaves[2], segment.leaves[3]);
|
||||
mw::Hash n6 = MMRUtil::CalcParentHash(mmr::Index::At(6), n2, n5);
|
||||
mw::Hash n14 = MMRUtil::CalcParentHash(mmr::Index::At(14), n6, segment.hashes[0]);
|
||||
mw::Hash root = MMRUtil::CalcParentHash(Index::At(26), n14, *segment.lower_peak);
|
||||
BOOST_REQUIRE_EQUAL(root, mmr->Root());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in new issue