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