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