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 <xrpl/basics/IntrusivePointer.ipp>
21#include <xrpl/basics/contract.h>
22#include <xrpl/shamap/SHAMap.h>
23
24#include <array>
25#include <stack>
26#include <vector>
27
28namespace ripple {
29
30// This code is used to compare another node's transaction tree
31// to our own. It returns a map containing all items that are different
32// between two SHA maps. It is optimized not to descend down tree
33// branches with the same branch hash. A limit can be passed so
34// that we will abort early if a node sends a map to us that
35// makes no sense at all. (And our sync algorithm will avoid
36// synchronizing matching branches too.)
37
38bool
40 SHAMapTreeNode* node,
41 boost::intrusive_ptr<SHAMapItem const> const& otherMapItem,
42 bool isFirstMap,
43 Delta& differences,
44 int& maxCount) const
45{
46 // Walk a branch of a SHAMap that's matched by an empty branch or single
47 // item in the other map
49 nodeStack.push(node);
50
51 bool emptyBranch = !otherMapItem;
52
53 while (!nodeStack.empty())
54 {
55 node = nodeStack.top();
56 nodeStack.pop();
57
58 if (node->isInner())
59 {
60 // This is an inner node, add all non-empty branches
61 auto inner = static_cast<SHAMapInnerNode*>(node);
62 for (int i = 0; i < 16; ++i)
63 if (!inner->isEmptyBranch(i))
64 nodeStack.push({descendThrow(inner, i)});
65 }
66 else
67 {
68 // This is a leaf node, process its item
69 auto item = static_cast<SHAMapLeafNode*>(node)->peekItem();
70
71 if (emptyBranch || (item->key() != otherMapItem->key()))
72 {
73 // unmatched
74 if (isFirstMap)
75 differences.insert(
76 std::make_pair(item->key(), DeltaRef(item, nullptr)));
77 else
78 differences.insert(
79 std::make_pair(item->key(), DeltaRef(nullptr, item)));
80
81 if (--maxCount <= 0)
82 return false;
83 }
84 else if (item->slice() != otherMapItem->slice())
85 {
86 // non-matching items with same tag
87 if (isFirstMap)
88 differences.insert(std::make_pair(
89 item->key(), DeltaRef(item, otherMapItem)));
90 else
91 differences.insert(std::make_pair(
92 item->key(), DeltaRef(otherMapItem, item)));
93
94 if (--maxCount <= 0)
95 return false;
96
97 emptyBranch = true;
98 }
99 else
100 {
101 // exact match
102 emptyBranch = true;
103 }
104 }
105 }
106
107 if (!emptyBranch)
108 {
109 // otherMapItem was unmatched, must add
110 if (isFirstMap) // this is first map, so other item is from second
111 differences.insert(std::make_pair(
112 otherMapItem->key(), DeltaRef(nullptr, otherMapItem)));
113 else
114 differences.insert(std::make_pair(
115 otherMapItem->key(), DeltaRef(otherMapItem, nullptr)));
116
117 if (--maxCount <= 0)
118 return false;
119 }
120
121 return true;
122}
123
124bool
125SHAMap::compare(SHAMap const& otherMap, Delta& differences, int maxCount) const
126{
127 // compare two hash trees, add up to maxCount differences to the difference
128 // table return value: true=complete table of differences given, false=too
129 // many differences throws on corrupt tables or missing nodes CAUTION:
130 // otherMap is not locked and must be immutable
131
132 XRPL_ASSERT(
133 isValid() && otherMap.isValid(),
134 "ripple::SHAMap::compare : valid state and valid input");
135
136 if (getHash() == otherMap.getHash())
137 return true;
138
141 nodeStack; // track nodes we've pushed
142
143 nodeStack.push({root_.get(), otherMap.root_.get()});
144 while (!nodeStack.empty())
145 {
146 auto [ourNode, otherNode] = nodeStack.top();
147 nodeStack.pop();
148
149 if (!ourNode || !otherNode)
150 {
151 // LCOV_EXCL_START
152 UNREACHABLE("ripple::SHAMap::compare : missing a node");
153 Throw<SHAMapMissingNode>(type_, uint256());
154 // LCOV_EXCL_STOP
155 }
156
157 if (ourNode->isLeaf() && otherNode->isLeaf())
158 {
159 // two leaves
160 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
161 auto other = static_cast<SHAMapLeafNode*>(otherNode);
162 if (ours->peekItem()->key() == other->peekItem()->key())
163 {
164 if (ours->peekItem()->slice() != other->peekItem()->slice())
165 {
166 differences.insert(std::make_pair(
167 ours->peekItem()->key(),
168 DeltaRef(ours->peekItem(), other->peekItem())));
169 if (--maxCount <= 0)
170 return false;
171 }
172 }
173 else
174 {
175 differences.insert(std::make_pair(
176 ours->peekItem()->key(),
177 DeltaRef(ours->peekItem(), nullptr)));
178 if (--maxCount <= 0)
179 return false;
180
181 differences.insert(std::make_pair(
182 other->peekItem()->key(),
183 DeltaRef(nullptr, other->peekItem())));
184 if (--maxCount <= 0)
185 return false;
186 }
187 }
188 else if (ourNode->isInner() && otherNode->isLeaf())
189 {
190 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
191 auto other = static_cast<SHAMapLeafNode*>(otherNode);
192 if (!walkBranch(
193 ours, other->peekItem(), true, differences, maxCount))
194 return false;
195 }
196 else if (ourNode->isLeaf() && otherNode->isInner())
197 {
198 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
199 auto other = static_cast<SHAMapInnerNode*>(otherNode);
200 if (!otherMap.walkBranch(
201 other, ours->peekItem(), false, differences, maxCount))
202 return false;
203 }
204 else if (ourNode->isInner() && otherNode->isInner())
205 {
206 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
207 auto other = static_cast<SHAMapInnerNode*>(otherNode);
208 for (int i = 0; i < 16; ++i)
209 if (ours->getChildHash(i) != other->getChildHash(i))
210 {
211 if (other->isEmptyBranch(i))
212 {
213 // We have a branch, the other tree does not
214 SHAMapTreeNode* iNode = descendThrow(ours, i);
215 if (!walkBranch(
216 iNode, nullptr, true, differences, maxCount))
217 return false;
218 }
219 else if (ours->isEmptyBranch(i))
220 {
221 // The other tree has a branch, we do not
222 SHAMapTreeNode* iNode = otherMap.descendThrow(other, i);
223 if (!otherMap.walkBranch(
224 iNode, nullptr, false, differences, maxCount))
225 return false;
226 }
227 else // The two trees have different non-empty branches
228 nodeStack.push(
229 {descendThrow(ours, i),
230 otherMap.descendThrow(other, i)});
231 }
232 }
233 else
234 {
235 // LCOV_EXCL_START
236 UNREACHABLE("ripple::SHAMap::compare : invalid node");
237 // LCOV_EXCL_STOP
238 }
239 }
240
241 return true;
242}
243
244void
245SHAMap::walkMap(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing)
246 const
247{
248 if (!root_->isInner()) // root_ is only node, and we have it
249 return;
250
251 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
253
254 nodeStack.push(intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_));
255
256 while (!nodeStack.empty())
257 {
258 intr_ptr::SharedPtr<SHAMapInnerNode> node = std::move(nodeStack.top());
259 nodeStack.pop();
260
261 for (int i = 0; i < 16; ++i)
262 {
263 if (!node->isEmptyBranch(i))
264 {
266 descendNoStore(*node, i);
267
268 if (nextNode)
269 {
270 if (nextNode->isInner())
271 nodeStack.push(
272 intr_ptr::static_pointer_cast<SHAMapInnerNode>(
273 nextNode));
274 }
275 else
276 {
277 missingNodes.emplace_back(type_, node->getChildHash(i));
278 if (--maxMissing <= 0)
279 return;
280 }
281 }
282 }
283 }
284}
285
286bool
287SHAMap::walkMapParallel(
288 std::vector<SHAMapMissingNode>& missingNodes,
289 int maxMissing) const
290{
291 if (!root_->isInner()) // root_ is only node, and we have it
292 return false;
293
294 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
296 {
297 auto const& innerRoot =
298 intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_);
299 for (int i = 0; i < 16; ++i)
300 {
301 if (!innerRoot->isEmptyBranch(i))
302 topChildren[i] = descendNoStore(*innerRoot, i);
303 }
304 }
306 workers.reserve(16);
308 exceptions.reserve(16);
309
311
312 // This mutex is used inside the worker threads to protect `missingNodes`
313 // and `maxMissing` from race conditions
314 std::mutex m;
315
316 for (int rootChildIndex = 0; rootChildIndex < 16; ++rootChildIndex)
317 {
318 auto const& child = topChildren[rootChildIndex];
319 if (!child || !child->isInner())
320 continue;
321
322 nodeStacks[rootChildIndex].push(
323 intr_ptr::static_pointer_cast<SHAMapInnerNode>(child));
324
325 JLOG(journal_.debug()) << "starting worker " << rootChildIndex;
326 workers.push_back(std::thread(
327 [&m, &missingNodes, &maxMissing, &exceptions, this](
328 std::stack<StackEntry, std::vector<StackEntry>> nodeStack) {
329 try
330 {
331 while (!nodeStack.empty())
332 {
333 intr_ptr::SharedPtr<SHAMapInnerNode> node =
334 std::move(nodeStack.top());
335 XRPL_ASSERT(
336 node,
337 "ripple::SHAMap::walkMapParallel : non-null node");
338 nodeStack.pop();
339
340 for (int i = 0; i < 16; ++i)
341 {
342 if (node->isEmptyBranch(i))
343 continue;
344 intr_ptr::SharedPtr<SHAMapTreeNode> nextNode =
345 descendNoStore(*node, i);
346
347 if (nextNode)
348 {
349 if (nextNode->isInner())
350 nodeStack.push(
351 intr_ptr::static_pointer_cast<
352 SHAMapInnerNode>(nextNode));
353 }
354 else
355 {
356 std::lock_guard l{m};
357 missingNodes.emplace_back(
358 type_, node->getChildHash(i));
359 if (--maxMissing <= 0)
360 return;
361 }
362 }
363 }
364 }
365 catch (SHAMapMissingNode const& e)
366 {
367 std::lock_guard l(m);
368 exceptions.push_back(e);
369 }
370 },
371 std::move(nodeStacks[rootChildIndex])));
372 }
373
374 for (std::thread& worker : workers)
375 worker.join();
376
377 std::lock_guard l(m);
378 if (exceptions.empty())
379 return true;
381 ss << "Exception(s) in ledger load: ";
382 for (auto const& e : exceptions)
383 ss << e.what() << ", ";
384 JLOG(journal_.error()) << ss.str();
385 return false;
386}
387
388} // 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:97
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition SHAMap.cpp:295
intr_ptr::SharedPtr< SHAMapTreeNode > root_
Definition SHAMap.h:108
boost::intrusive_ptr< SHAMapItem const > const & peekItem(uint256 const &id) const
Definition SHAMap.cpp:617
std::pair< boost::intrusive_ptr< SHAMapItem const >, boost::intrusive_ptr< SHAMapItem const > > DeltaRef
Definition SHAMap.h:374
SHAMapHash getHash() const
Definition SHAMap.cpp:890
bool isValid() const
Definition SHAMap.h:629
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)