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 (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++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 (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++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 (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++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 (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
293 {
294 if (!innerRoot->isEmptyBranch(i))
295 topChildren[i] = descendNoStore(*innerRoot, i);
296 }
297 }
302
304 nodeStacks;
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 (auto rootChildIndex = 0u; rootChildIndex < SHAMapInnerNode::kBranchFactor;
311 ++rootChildIndex)
312 {
313 auto const& child = topChildren[rootChildIndex];
314 if (!child || !child->isInner())
315 continue;
316
317 nodeStacks[rootChildIndex].push(intr_ptr::staticPointerCast<SHAMapInnerNode>(child));
318
319 JLOG(journal_.debug()) << "starting worker " << rootChildIndex;
320 workers.emplace_back(
321 [&m, &missingNodes, &maxMissing, &exceptions, this](
322 std::stack<StackEntry, std::vector<StackEntry>> nodeStack) {
323 try
324 {
325 while (!nodeStack.empty())
326 {
328 std::move(nodeStack.top());
329 XRPL_ASSERT(node, "xrpl::SHAMap::walkMapParallel : non-null node");
330 nodeStack.pop();
331
332 for (auto i = 0u; i < SHAMapInnerNode::kBranchFactor; ++i)
333 {
334 if (node->isEmptyBranch(i))
335 continue;
336 SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
337
338 if (nextNode)
339 {
340 if (nextNode->isInner())
341 {
342 nodeStack.push(
344 }
345 }
346 else
347 {
348 std::scoped_lock const l{m};
349 missingNodes.emplace_back(type_, node->getChildHash(i));
350 if (--maxMissing <= 0)
351 return;
352 }
353 }
354 }
355 }
356 catch (SHAMapMissingNode const& e)
357 {
358 std::scoped_lock const l(m);
359 exceptions.push_back(e);
360 }
361 },
362 std::move(nodeStacks[rootChildIndex]));
363 }
364
365 for (std::thread& worker : workers)
366 worker.join();
367
368 std::scoped_lock const l(m);
369 if (exceptions.empty())
370 return true;
372 ss << "Exception(s) in ledger load: ";
373 for (auto const& e : exceptions)
374 ss << e.what() << ", ";
375 JLOG(journal_.error()) << ss.str();
376 return false;
377}
378
379} // namespace xrpl
static constexpr unsigned int kBranchFactor
Each inner node has 16 children (the 'radix tree' part of the map).
virtual bool isInner() const =0
Determines if this is an inner node.
SHAMapTreeNodePtr descendNoStore(SHAMapInnerNode &, unsigned int branch) const
std::pair< boost::intrusive_ptr< SHAMapItem const >, boost::intrusive_ptr< SHAMapItem const > > DeltaRef
Definition SHAMap.h:524
bool walkMapParallel(std::vector< SHAMapMissingNode > &missingNodes, int maxMissing) const
void walkMap(std::vector< SHAMapMissingNode > &missingNodes, int maxMissing) const
beast::Journal journal_
Definition SHAMap.h:116
std::map< UInt256, DeltaItem > Delta
Definition SHAMap.h:148
bool compare(SHAMap const &otherMap, Delta &differences, int maxCount) const
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, unsigned int branch) const
bool isValid() const
Definition SHAMap.h:779
SHAMapType const type_
Definition SHAMap.h:130
SHAMap()=delete
SHAMapTreeNodePtr root_
Definition SHAMap.h:128
SHAMapHash getHash() const
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
BaseUInt< 256 > UInt256
Definition base_uint.h:580
Dest safeDowncast(Src *s) noexcept
Definition safe_cast.h:84
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
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)