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