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