xrpld
Loading...
Searching...
No Matches
SHAMapDelta.cpp
1#include <xrpl/basics/IntrusivePointer.h> // IWYU pragma: keep
2#include <xrpl/basics/IntrusivePointer.ipp> // IWYU pragma: keep
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/basics/safe_cast.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/shamap/SHAMap.h>
8#include <xrpl/shamap/SHAMapInnerNode.h>
9#include <xrpl/shamap/SHAMapItem.h>
10#include <xrpl/shamap/SHAMapMissingNode.h>
11#include <xrpl/shamap/SHAMapTreeNode.h>
12
13#include <boost/smart_ptr/intrusive_ptr.hpp>
14
15#include <array>
16#include <mutex>
17#include <sstream>
18#include <stack>
19#include <thread>
20#include <utility>
21#include <vector>
22
23namespace xrpl {
24
25// This code is used to compare another node's transaction tree
26// to our own. It returns a map containing all items that are different
27// between two SHA maps. It is optimized not to descend down tree
28// branches with the same branch hash. A limit can be passed so
29// that we will abort early if a node sends a map to us that
30// makes no sense at all. (And our sync algorithm will avoid
31// synchronizing matching branches too.)
32
33bool
35 SHAMapTreeNode* node,
36 boost::intrusive_ptr<SHAMapItem const> const& otherMapItem,
37 bool isFirstMap,
38 Delta& differences,
39 int& maxCount) const
40{
41 // Walk a branch of a SHAMap that's matched by an empty branch or single
42 // item in the other map
44 nodeStack.push(node);
45
46 bool emptyBranch = !otherMapItem;
47
48 while (!nodeStack.empty())
49 {
50 node = nodeStack.top();
51 nodeStack.pop();
52
53 if (node->isInner())
54 {
55 // This is an inner node, add all non-empty branches
56 auto inner = safeDowncast<SHAMapInnerNode*>(node);
57 for (int i = 0; i < 16; ++i)
58 {
59 if (!inner->isEmptyBranch(i))
60 nodeStack.push({descendThrow(inner, i)});
61 }
62 }
63 else
64 {
65 // This is a leaf node, process its item
66 auto item = safeDowncast<SHAMapLeafNode*>(node)->peekItem();
67
68 if (emptyBranch || (item->key() != otherMapItem->key()))
69 {
70 // unmatched
71 if (isFirstMap)
72 {
73 differences.insert(std::make_pair(item->key(), DeltaRef(item, nullptr)));
74 }
75 else
76 {
77 differences.insert(std::make_pair(item->key(), DeltaRef(nullptr, item)));
78 }
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 {
88 differences.insert(std::make_pair(item->key(), DeltaRef(item, otherMapItem)));
89 }
90 else
91 {
92 differences.insert(std::make_pair(item->key(), DeltaRef(otherMapItem, item)));
93 }
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)
112 { // this is first map, so other item is from second
113 differences.insert(
114 std::make_pair(otherMapItem->key(), DeltaRef(nullptr, otherMapItem)));
115 }
116 else
117 {
118 differences.insert(
119 std::make_pair(otherMapItem->key(), DeltaRef(otherMapItem, nullptr)));
120 }
121
122 if (--maxCount <= 0)
123 return false;
124 }
125
126 return true;
127}
128
129bool
130SHAMap::compare(SHAMap const& otherMap, Delta& differences, int maxCount) const
131{
132 // compare two hash trees, add up to maxCount differences to the difference
133 // table return value: true=complete table of differences given, false=too
134 // many differences throws on corrupt tables or missing nodes CAUTION:
135 // otherMap is not locked and must be immutable
136
137 XRPL_ASSERT(
138 isValid() && otherMap.isValid(), "xrpl::SHAMap::compare : valid state and valid input");
139
140 if (getHash() == otherMap.getHash())
141 return true;
142
144 std::stack<StackEntry, std::vector<StackEntry>> nodeStack; // track nodes we've pushed
145
146 nodeStack.emplace(root_.get(), otherMap.root_.get());
147 while (!nodeStack.empty())
148 {
149 auto [ourNode, otherNode] = nodeStack.top();
150 nodeStack.pop();
151
152 if ((ourNode == nullptr) || (otherNode == nullptr))
153 {
154 // LCOV_EXCL_START
155 UNREACHABLE("xrpl::SHAMap::compare : missing a node");
157 // LCOV_EXCL_STOP
158 }
159
160 if (ourNode->isLeaf() && otherNode->isLeaf())
161 {
162 // two leaves
163 auto ours = safeDowncast<SHAMapLeafNode*>(ourNode);
164 auto other = safeDowncast<SHAMapLeafNode*>(otherNode);
165 if (ours->peekItem()->key() == other->peekItem()->key())
166 {
167 if (ours->peekItem()->slice() != other->peekItem()->slice())
168 {
169 differences.insert(
171 ours->peekItem()->key(),
172 DeltaRef(ours->peekItem(), other->peekItem())));
173 if (--maxCount <= 0)
174 return false;
175 }
176 }
177 else
178 {
179 differences.insert(
180 std::make_pair(ours->peekItem()->key(), DeltaRef(ours->peekItem(), nullptr)));
181 if (--maxCount <= 0)
182 return false;
183
184 differences.insert(
185 std::make_pair(other->peekItem()->key(), DeltaRef(nullptr, other->peekItem())));
186 if (--maxCount <= 0)
187 return false;
188 }
189 }
190 else if (ourNode->isInner() && otherNode->isLeaf())
191 {
192 auto ours = safeDowncast<SHAMapInnerNode*>(ourNode);
193 auto other = safeDowncast<SHAMapLeafNode*>(otherNode);
194 if (!walkBranch(ours, other->peekItem(), true, differences, maxCount))
195 return false;
196 }
197 else if (ourNode->isLeaf() && otherNode->isInner())
198 {
199 auto ours = safeDowncast<SHAMapLeafNode*>(ourNode);
200 auto other = safeDowncast<SHAMapInnerNode*>(otherNode);
201 if (!otherMap.walkBranch(other, ours->peekItem(), false, differences, maxCount))
202 return false;
203 }
204 else if (ourNode->isInner() && otherNode->isInner())
205 {
206 auto ours = safeDowncast<SHAMapInnerNode*>(ourNode);
207 auto other = safeDowncast<SHAMapInnerNode*>(otherNode);
208 for (int i = 0; i < 16; ++i)
209 {
210 if (ours->getChildHash(i) != other->getChildHash(i))
211 {
212 if (other->isEmptyBranch(i))
213 {
214 // We have a branch, the other tree does not
215 SHAMapTreeNode* iNode = descendThrow(ours, i);
216 if (!walkBranch(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(iNode, nullptr, false, differences, maxCount))
224 return false;
225 }
226 else
227 { // The two trees have different non-empty branches
228 nodeStack.emplace(descendThrow(ours, i), otherMap.descendThrow(other, i));
229 }
230 }
231 }
232 }
233 else
234 {
235 // LCOV_EXCL_START
236 UNREACHABLE("xrpl::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) const
246{
247 if (!root_->isInner()) // root_ is only node, and we have it
248 return;
249
250 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
252
254
255 while (!nodeStack.empty())
256 {
257 intr_ptr::SharedPtr<SHAMapInnerNode> const node = std::move(nodeStack.top());
258 nodeStack.pop();
259
260 for (int i = 0; i < 16; ++i)
261 {
262 if (!node->isEmptyBranch(i))
263 {
264 SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
265
266 if (nextNode)
267 {
268 if (nextNode->isInner())
270 }
271 else
272 {
273 missingNodes.emplace_back(type_, node->getChildHash(i));
274 if (--maxMissing <= 0)
275 return;
276 }
277 }
278 }
279 }
280}
281
282bool
283SHAMap::walkMapParallel(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing) const
284{
285 if (!root_->isInner()) // root_ is only node, and we have it
286 return false;
287
288 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
290 {
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(intr_ptr::staticPointerCast<SHAMapInnerNode>(child));
316
317 JLOG(journal_.debug()) << "starting worker " << rootChildIndex;
318 workers.emplace_back(
319 [&m, &missingNodes, &maxMissing, &exceptions, this](
320 std::stack<StackEntry, std::vector<StackEntry>> nodeStack) {
321 try
322 {
323 while (!nodeStack.empty())
324 {
326 std::move(nodeStack.top());
327 XRPL_ASSERT(node, "xrpl::SHAMap::walkMapParallel : non-null node");
328 nodeStack.pop();
329
330 for (int i = 0; i < 16; ++i)
331 {
332 if (node->isEmptyBranch(i))
333 continue;
334 SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
335
336 if (nextNode)
337 {
338 if (nextNode->isInner())
339 {
340 nodeStack.push(
342 }
343 }
344 else
345 {
346 std::scoped_lock const l{m};
347 missingNodes.emplace_back(type_, node->getChildHash(i));
348 if (--maxMissing <= 0)
349 return;
350 }
351 }
352 }
353 }
354 catch (SHAMapMissingNode const& e)
355 {
356 std::scoped_lock const l(m);
357 exceptions.push_back(e);
358 }
359 },
360 std::move(nodeStacks[rootChildIndex]));
361 }
362
363 for (std::thread& worker : workers)
364 worker.join();
365
366 std::scoped_lock const l(m);
367 if (exceptions.empty())
368 return true;
370 ss << "Exception(s) in ledger load: ";
371 for (auto const& e : exceptions)
372 ss << e.what() << ", ";
373 JLOG(journal_.error()) << ss.str();
374 return false;
375}
376
377} // namespace xrpl
virtual bool isInner() const =0
Determines if this is an inner node.
std::map< uint256, DeltaItem > Delta
Definition SHAMap.h:104
std::pair< boost::intrusive_ptr< SHAMapItem const >, boost::intrusive_ptr< SHAMapItem const > > DeltaRef
Definition SHAMap.h:330
bool walkMapParallel(std::vector< SHAMapMissingNode > &missingNodes, int maxMissing) const
void walkMap(std::vector< SHAMapMissingNode > &missingNodes, int maxMissing) const
beast::Journal journal_
Definition SHAMap.h:80
bool compare(SHAMap const &otherMap, Delta &differences, int maxCount) const
bool isValid() const
Definition SHAMap.h:564
SHAMapType const type_
Definition SHAMap.h:90
SHAMap()=delete
SHAMapTreeNodePtr descendNoStore(SHAMapInnerNode &, int branch) const
Definition SHAMap.cpp:335
SHAMapTreeNodePtr root_
Definition SHAMap.h:88
SHAMapHash getHash() const
Definition SHAMap.cpp:836
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition SHAMap.cpp:281
bool walkBranch(SHAMapTreeNode *node, boost::intrusive_ptr< SHAMapItem const > const &otherMapItem, bool isFirstMap, Delta &differences, int &maxCount) const
T * get() const
Get the raw pointer.
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T insert(T... args)
T make_pair(T... args)
SharedPtr< T > staticPointerCast(TT const &v)
SharedIntrusive< T > SharedPtr
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
intr_ptr::SharedPtr< SHAMapTreeNode > SHAMapTreeNodePtr
Dest safeDowncast(Src *s) noexcept
Definition safe_cast.h:78
BaseUInt< 256 > uint256
Definition base_uint.h:562
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
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)