rippled
Loading...
Searching...
No Matches
SHAMapDelta.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/shamap/SHAMap.h>
21
22#include <xrpl/basics/IntrusivePointer.ipp>
23#include <xrpl/basics/contract.h>
24
25#include <array>
26#include <stack>
27#include <vector>
28
29namespace ripple {
30
31// This code is used to compare another node's transaction tree
32// to our own. It returns a map containing all items that are different
33// between two SHA maps. It is optimized not to descend down tree
34// branches with the same branch hash. A limit can be passed so
35// that we will abort early if a node sends a map to us that
36// makes no sense at all. (And our sync algorithm will avoid
37// synchronizing matching branches too.)
38
39bool
41 SHAMapTreeNode* node,
42 boost::intrusive_ptr<SHAMapItem const> const& otherMapItem,
43 bool isFirstMap,
44 Delta& differences,
45 int& maxCount) const
46{
47 // Walk a branch of a SHAMap that's matched by an empty branch or single
48 // item in the other map
50 nodeStack.push(node);
51
52 bool emptyBranch = !otherMapItem;
53
54 while (!nodeStack.empty())
55 {
56 node = nodeStack.top();
57 nodeStack.pop();
58
59 if (node->isInner())
60 {
61 // This is an inner node, add all non-empty branches
62 auto inner = static_cast<SHAMapInnerNode*>(node);
63 for (int i = 0; i < 16; ++i)
64 if (!inner->isEmptyBranch(i))
65 nodeStack.push({descendThrow(inner, i)});
66 }
67 else
68 {
69 // This is a leaf node, process its item
70 auto item = static_cast<SHAMapLeafNode*>(node)->peekItem();
71
72 if (emptyBranch || (item->key() != otherMapItem->key()))
73 {
74 // unmatched
75 if (isFirstMap)
76 differences.insert(
77 std::make_pair(item->key(), DeltaRef(item, nullptr)));
78 else
79 differences.insert(
80 std::make_pair(item->key(), DeltaRef(nullptr, item)));
81
82 if (--maxCount <= 0)
83 return false;
84 }
85 else if (item->slice() != otherMapItem->slice())
86 {
87 // non-matching items with same tag
88 if (isFirstMap)
89 differences.insert(std::make_pair(
90 item->key(), DeltaRef(item, otherMapItem)));
91 else
92 differences.insert(std::make_pair(
93 item->key(), DeltaRef(otherMapItem, item)));
94
95 if (--maxCount <= 0)
96 return false;
97
98 emptyBranch = true;
99 }
100 else
101 {
102 // exact match
103 emptyBranch = true;
104 }
105 }
106 }
107
108 if (!emptyBranch)
109 {
110 // otherMapItem was unmatched, must add
111 if (isFirstMap) // this is first map, so other item is from second
112 differences.insert(std::make_pair(
113 otherMapItem->key(), DeltaRef(nullptr, otherMapItem)));
114 else
115 differences.insert(std::make_pair(
116 otherMapItem->key(), DeltaRef(otherMapItem, nullptr)));
117
118 if (--maxCount <= 0)
119 return false;
120 }
121
122 return true;
123}
124
125bool
126SHAMap::compare(SHAMap const& otherMap, Delta& differences, int maxCount) const
127{
128 // compare two hash trees, add up to maxCount differences to the difference
129 // table return value: true=complete table of differences given, false=too
130 // many differences throws on corrupt tables or missing nodes CAUTION:
131 // otherMap is not locked and must be immutable
132
133 XRPL_ASSERT(
134 isValid() && otherMap.isValid(),
135 "ripple::SHAMap::compare : valid state and valid input");
136
137 if (getHash() == otherMap.getHash())
138 return true;
139
142 nodeStack; // track nodes we've pushed
143
144 nodeStack.push({root_.get(), otherMap.root_.get()});
145 while (!nodeStack.empty())
146 {
147 auto [ourNode, otherNode] = nodeStack.top();
148 nodeStack.pop();
149
150 if (!ourNode || !otherNode)
151 {
152 // LCOV_EXCL_START
153 UNREACHABLE("ripple::SHAMap::compare : missing a node");
154 Throw<SHAMapMissingNode>(type_, uint256());
155 // LCOV_EXCL_STOP
156 }
157
158 if (ourNode->isLeaf() && otherNode->isLeaf())
159 {
160 // two leaves
161 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
162 auto other = static_cast<SHAMapLeafNode*>(otherNode);
163 if (ours->peekItem()->key() == other->peekItem()->key())
164 {
165 if (ours->peekItem()->slice() != other->peekItem()->slice())
166 {
167 differences.insert(std::make_pair(
168 ours->peekItem()->key(),
169 DeltaRef(ours->peekItem(), other->peekItem())));
170 if (--maxCount <= 0)
171 return false;
172 }
173 }
174 else
175 {
176 differences.insert(std::make_pair(
177 ours->peekItem()->key(),
178 DeltaRef(ours->peekItem(), nullptr)));
179 if (--maxCount <= 0)
180 return false;
181
182 differences.insert(std::make_pair(
183 other->peekItem()->key(),
184 DeltaRef(nullptr, other->peekItem())));
185 if (--maxCount <= 0)
186 return false;
187 }
188 }
189 else if (ourNode->isInner() && otherNode->isLeaf())
190 {
191 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
192 auto other = static_cast<SHAMapLeafNode*>(otherNode);
193 if (!walkBranch(
194 ours, other->peekItem(), true, differences, maxCount))
195 return false;
196 }
197 else if (ourNode->isLeaf() && otherNode->isInner())
198 {
199 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
200 auto other = static_cast<SHAMapInnerNode*>(otherNode);
201 if (!otherMap.walkBranch(
202 other, ours->peekItem(), false, differences, maxCount))
203 return false;
204 }
205 else if (ourNode->isInner() && otherNode->isInner())
206 {
207 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
208 auto other = static_cast<SHAMapInnerNode*>(otherNode);
209 for (int i = 0; i < 16; ++i)
210 if (ours->getChildHash(i) != other->getChildHash(i))
211 {
212 if (other->isEmptyBranch(i))
213 {
214 // We have a branch, the other tree does not
215 SHAMapTreeNode* iNode = descendThrow(ours, i);
216 if (!walkBranch(
217 iNode, nullptr, true, differences, maxCount))
218 return false;
219 }
220 else if (ours->isEmptyBranch(i))
221 {
222 // The other tree has a branch, we do not
223 SHAMapTreeNode* iNode = otherMap.descendThrow(other, i);
224 if (!otherMap.walkBranch(
225 iNode, nullptr, false, differences, maxCount))
226 return false;
227 }
228 else // The two trees have different non-empty branches
229 nodeStack.push(
230 {descendThrow(ours, i),
231 otherMap.descendThrow(other, i)});
232 }
233 }
234 else
235 {
236 // LCOV_EXCL_START
237 UNREACHABLE("ripple::SHAMap::compare : invalid node");
238 // LCOV_EXCL_STOP
239 }
240 }
241
242 return true;
243}
244
245void
246SHAMap::walkMap(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing)
247 const
248{
249 if (!root_->isInner()) // root_ is only node, and we have it
250 return;
251
252 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
254
255 nodeStack.push(intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_));
256
257 while (!nodeStack.empty())
258 {
259 intr_ptr::SharedPtr<SHAMapInnerNode> node = std::move(nodeStack.top());
260 nodeStack.pop();
261
262 for (int i = 0; i < 16; ++i)
263 {
264 if (!node->isEmptyBranch(i))
265 {
267 descendNoStore(*node, i);
268
269 if (nextNode)
270 {
271 if (nextNode->isInner())
272 nodeStack.push(
273 intr_ptr::static_pointer_cast<SHAMapInnerNode>(
274 nextNode));
275 }
276 else
277 {
278 missingNodes.emplace_back(type_, node->getChildHash(i));
279 if (--maxMissing <= 0)
280 return;
281 }
282 }
283 }
284 }
285}
286
287bool
288SHAMap::walkMapParallel(
289 std::vector<SHAMapMissingNode>& missingNodes,
290 int maxMissing) const
291{
292 if (!root_->isInner()) // root_ is only node, and we have it
293 return false;
294
295 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
297 {
298 auto const& innerRoot =
299 intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_);
300 for (int i = 0; i < 16; ++i)
301 {
302 if (!innerRoot->isEmptyBranch(i))
303 topChildren[i] = descendNoStore(*innerRoot, i);
304 }
305 }
307 workers.reserve(16);
309 exceptions.reserve(16);
310
312
313 // This mutex is used inside the worker threads to protect `missingNodes`
314 // and `maxMissing` from race conditions
315 std::mutex m;
316
317 for (int rootChildIndex = 0; rootChildIndex < 16; ++rootChildIndex)
318 {
319 auto const& child = topChildren[rootChildIndex];
320 if (!child || !child->isInner())
321 continue;
322
323 nodeStacks[rootChildIndex].push(
324 intr_ptr::static_pointer_cast<SHAMapInnerNode>(child));
325
326 JLOG(journal_.debug()) << "starting worker " << rootChildIndex;
327 workers.push_back(std::thread(
328 [&m, &missingNodes, &maxMissing, &exceptions, this](
329 std::stack<StackEntry, std::vector<StackEntry>> nodeStack) {
330 try
331 {
332 while (!nodeStack.empty())
333 {
334 intr_ptr::SharedPtr<SHAMapInnerNode> node =
335 std::move(nodeStack.top());
336 XRPL_ASSERT(
337 node,
338 "ripple::SHAMap::walkMapParallel : non-null node");
339 nodeStack.pop();
340
341 for (int i = 0; i < 16; ++i)
342 {
343 if (node->isEmptyBranch(i))
344 continue;
345 intr_ptr::SharedPtr<SHAMapTreeNode> nextNode =
346 descendNoStore(*node, i);
347
348 if (nextNode)
349 {
350 if (nextNode->isInner())
351 nodeStack.push(
352 intr_ptr::static_pointer_cast<
353 SHAMapInnerNode>(nextNode));
354 }
355 else
356 {
357 std::lock_guard l{m};
358 missingNodes.emplace_back(
359 type_, node->getChildHash(i));
360 if (--maxMissing <= 0)
361 return;
362 }
363 }
364 }
365 }
366 catch (SHAMapMissingNode const& e)
367 {
368 std::lock_guard l(m);
369 exceptions.push_back(e);
370 }
371 },
372 std::move(nodeStacks[rootChildIndex])));
373 }
374
375 for (std::thread& worker : workers)
376 worker.join();
377
378 std::lock_guard l(m);
379 if (exceptions.empty())
380 return true;
382 ss << "Exception(s) in ledger load: ";
383 for (auto const& e : exceptions)
384 ss << e.what() << ", ";
385 JLOG(journal_.error()) << ss.str();
386 return false;
387}
388
389} // namespace ripple
virtual bool isInner() const =0
Determines if this is an inner node.
A SHAMap is both a radix tree with a fan-out of 16 and a Merkle tree.
Definition SHAMap.h:99
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition SHAMap.cpp:296
intr_ptr::SharedPtr< SHAMapTreeNode > root_
Definition SHAMap.h:110
boost::intrusive_ptr< SHAMapItem const > const & peekItem(uint256 const &id) const
Definition SHAMap.cpp:618
std::pair< boost::intrusive_ptr< SHAMapItem const >, boost::intrusive_ptr< SHAMapItem const > > DeltaRef
Definition SHAMap.h:376
SHAMapHash getHash() const
Definition SHAMap.cpp:891
bool isValid() const
Definition SHAMap.h:631
bool walkBranch(SHAMapTreeNode *node, boost::intrusive_ptr< SHAMapItem const > const &otherMapItem, bool isFirstMap, Delta &differences, int &maxCount) const
A shared intrusive pointer class that supports weak pointers.
T emplace_back(T... args)
T empty(T... args)
T insert(T... args)
T make_pair(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
Stream & join(Stream &s, Iter iter, Iter end, std::string const &delimiter)
Definition join.h:28
T pop(T... args)
T push_back(T... args)
T push(T... args)
T reserve(T... args)
T str(T... args)
T top(T... args)