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/contract.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 UNREACHABLE("ripple::SHAMap::compare : missing a node");
152 Throw<SHAMapMissingNode>(type_, uint256());
153 }
154
155 if (ourNode->isLeaf() && otherNode->isLeaf())
156 {
157 // two leaves
158 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
159 auto other = static_cast<SHAMapLeafNode*>(otherNode);
160 if (ours->peekItem()->key() == other->peekItem()->key())
161 {
162 if (ours->peekItem()->slice() != other->peekItem()->slice())
163 {
164 differences.insert(std::make_pair(
165 ours->peekItem()->key(),
166 DeltaRef(ours->peekItem(), other->peekItem())));
167 if (--maxCount <= 0)
168 return false;
169 }
170 }
171 else
172 {
173 differences.insert(std::make_pair(
174 ours->peekItem()->key(),
175 DeltaRef(ours->peekItem(), nullptr)));
176 if (--maxCount <= 0)
177 return false;
178
179 differences.insert(std::make_pair(
180 other->peekItem()->key(),
181 DeltaRef(nullptr, other->peekItem())));
182 if (--maxCount <= 0)
183 return false;
184 }
185 }
186 else if (ourNode->isInner() && otherNode->isLeaf())
187 {
188 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
189 auto other = static_cast<SHAMapLeafNode*>(otherNode);
190 if (!walkBranch(
191 ours, other->peekItem(), true, differences, maxCount))
192 return false;
193 }
194 else if (ourNode->isLeaf() && otherNode->isInner())
195 {
196 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
197 auto other = static_cast<SHAMapInnerNode*>(otherNode);
198 if (!otherMap.walkBranch(
199 other, ours->peekItem(), false, differences, maxCount))
200 return false;
201 }
202 else if (ourNode->isInner() && otherNode->isInner())
203 {
204 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
205 auto other = static_cast<SHAMapInnerNode*>(otherNode);
206 for (int i = 0; i < 16; ++i)
207 if (ours->getChildHash(i) != other->getChildHash(i))
208 {
209 if (other->isEmptyBranch(i))
210 {
211 // We have a branch, the other tree does not
212 SHAMapTreeNode* iNode = descendThrow(ours, i);
213 if (!walkBranch(
214 iNode, nullptr, true, differences, maxCount))
215 return false;
216 }
217 else if (ours->isEmptyBranch(i))
218 {
219 // The other tree has a branch, we do not
220 SHAMapTreeNode* iNode = otherMap.descendThrow(other, i);
221 if (!otherMap.walkBranch(
222 iNode, nullptr, false, differences, maxCount))
223 return false;
224 }
225 else // The two trees have different non-empty branches
226 nodeStack.push(
227 {descendThrow(ours, i),
228 otherMap.descendThrow(other, i)});
229 }
230 }
231 else
232 UNREACHABLE("ripple::SHAMap::compare : invalid node");
233 }
234
235 return true;
236}
237
238void
239SHAMap::walkMap(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing)
240 const
241{
242 if (!root_->isInner()) // root_ is only node, and we have it
243 return;
244
245 using StackEntry = std::shared_ptr<SHAMapInnerNode>;
247
248 nodeStack.push(std::static_pointer_cast<SHAMapInnerNode>(root_));
249
250 while (!nodeStack.empty())
251 {
252 std::shared_ptr<SHAMapInnerNode> node = std::move(nodeStack.top());
253 nodeStack.pop();
254
255 for (int i = 0; i < 16; ++i)
256 {
257 if (!node->isEmptyBranch(i))
258 {
260 descendNoStore(node, i);
261
262 if (nextNode)
263 {
264 if (nextNode->isInner())
265 nodeStack.push(
266 std::static_pointer_cast<SHAMapInnerNode>(
267 nextNode));
268 }
269 else
270 {
271 missingNodes.emplace_back(type_, node->getChildHash(i));
272 if (--maxMissing <= 0)
273 return;
274 }
275 }
276 }
277 }
278}
279
280bool
281SHAMap::walkMapParallel(
282 std::vector<SHAMapMissingNode>& missingNodes,
283 int maxMissing) const
284{
285 if (!root_->isInner()) // root_ is only node, and we have it
286 return false;
287
288 using StackEntry = std::shared_ptr<SHAMapInnerNode>;
290 {
291 auto const& innerRoot =
292 std::static_pointer_cast<SHAMapInnerNode>(root_);
293 for (int i = 0; i < 16; ++i)
294 {
295 if (!innerRoot->isEmptyBranch(i))
296 topChildren[i] = descendNoStore(innerRoot, i);
297 }
298 }
300 workers.reserve(16);
302 exceptions.reserve(16);
303
305
306 // This mutex is used inside the worker threads to protect `missingNodes`
307 // and `maxMissing` from race conditions
308 std::mutex m;
309
310 for (int rootChildIndex = 0; rootChildIndex < 16; ++rootChildIndex)
311 {
312 auto const& child = topChildren[rootChildIndex];
313 if (!child || !child->isInner())
314 continue;
315
316 nodeStacks[rootChildIndex].push(
317 std::static_pointer_cast<SHAMapInnerNode>(child));
318
319 JLOG(journal_.debug()) << "starting worker " << rootChildIndex;
320 workers.push_back(std::thread(
321 [&m, &missingNodes, &maxMissing, &exceptions, this](
322 std::stack<StackEntry, std::vector<StackEntry>> nodeStack) {
323 try
324 {
325 while (!nodeStack.empty())
326 {
327 std::shared_ptr<SHAMapInnerNode> node =
328 std::move(nodeStack.top());
329 XRPL_ASSERT(
330 node,
331 "ripple::SHAMap::walkMapParallel : non-null node");
332 nodeStack.pop();
333
334 for (int i = 0; i < 16; ++i)
335 {
336 if (node->isEmptyBranch(i))
337 continue;
338 std::shared_ptr<SHAMapTreeNode> nextNode =
339 descendNoStore(node, i);
340
341 if (nextNode)
342 {
343 if (nextNode->isInner())
344 nodeStack.push(std::static_pointer_cast<
345 SHAMapInnerNode>(nextNode));
346 }
347 else
348 {
349 std::lock_guard l{m};
350 missingNodes.emplace_back(
351 type_, node->getChildHash(i));
352 if (--maxMissing <= 0)
353 return;
354 }
355 }
356 }
357 }
358 catch (SHAMapMissingNode const& e)
359 {
360 std::lock_guard l(m);
361 exceptions.push_back(e);
362 }
363 },
364 std::move(nodeStacks[rootChildIndex])));
365 }
366
367 for (std::thread& worker : workers)
368 worker.join();
369
370 std::lock_guard l(m);
371 if (exceptions.empty())
372 return true;
374 ss << "Exception(s) in ledger load: ";
375 for (auto const& e : exceptions)
376 ss << e.what() << ", ";
377 JLOG(journal_.error()) << ss.str();
378 return false;
379}
380
381} // 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:95
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition: SHAMap.cpp:294
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:372
std::shared_ptr< SHAMapTreeNode > root_
Definition: SHAMap.h:106
SHAMapHash getHash() const
Definition: SHAMap.cpp:889
bool isValid() const
Definition: SHAMap.h:626
bool walkBranch(SHAMapTreeNode *node, boost::intrusive_ptr< SHAMapItem const > const &otherMapItem, bool isFirstMap, Delta &differences, int &maxCount) const
Definition: SHAMapDelta.cpp:39
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:26
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)