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 xrpl {
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(std::make_pair(item->key(), DeltaRef(item, nullptr)));
57 else
58 differences.insert(std::make_pair(item->key(), DeltaRef(nullptr, item)));
59
60 if (--maxCount <= 0)
61 return false;
62 }
63 else if (item->slice() != otherMapItem->slice())
64 {
65 // non-matching items with same tag
66 if (isFirstMap)
67 differences.insert(std::make_pair(item->key(), DeltaRef(item, otherMapItem)));
68 else
69 differences.insert(std::make_pair(item->key(), DeltaRef(otherMapItem, item)));
70
71 if (--maxCount <= 0)
72 return false;
73
74 emptyBranch = true;
75 }
76 else
77 {
78 // exact match
79 emptyBranch = true;
80 }
81 }
82 }
83
84 if (!emptyBranch)
85 {
86 // otherMapItem was unmatched, must add
87 if (isFirstMap) // this is first map, so other item is from second
88 differences.insert(std::make_pair(otherMapItem->key(), DeltaRef(nullptr, otherMapItem)));
89 else
90 differences.insert(std::make_pair(otherMapItem->key(), DeltaRef(otherMapItem, nullptr)));
91
92 if (--maxCount <= 0)
93 return false;
94 }
95
96 return true;
97}
98
99bool
100SHAMap::compare(SHAMap const& otherMap, Delta& differences, int maxCount) const
101{
102 // compare two hash trees, add up to maxCount differences to the difference
103 // table return value: true=complete table of differences given, false=too
104 // many differences throws on corrupt tables or missing nodes CAUTION:
105 // otherMap is not locked and must be immutable
106
107 XRPL_ASSERT(isValid() && otherMap.isValid(), "xrpl::SHAMap::compare : valid state and valid input");
108
109 if (getHash() == otherMap.getHash())
110 return true;
111
113 std::stack<StackEntry, std::vector<StackEntry>> nodeStack; // track nodes we've pushed
114
115 nodeStack.push({root_.get(), otherMap.root_.get()});
116 while (!nodeStack.empty())
117 {
118 auto [ourNode, otherNode] = nodeStack.top();
119 nodeStack.pop();
120
121 if (!ourNode || !otherNode)
122 {
123 // LCOV_EXCL_START
124 UNREACHABLE("xrpl::SHAMap::compare : missing a node");
125 Throw<SHAMapMissingNode>(type_, uint256());
126 // LCOV_EXCL_STOP
127 }
128
129 if (ourNode->isLeaf() && otherNode->isLeaf())
130 {
131 // two leaves
132 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
133 auto other = static_cast<SHAMapLeafNode*>(otherNode);
134 if (ours->peekItem()->key() == other->peekItem()->key())
135 {
136 if (ours->peekItem()->slice() != other->peekItem()->slice())
137 {
138 differences.insert(
139 std::make_pair(ours->peekItem()->key(), DeltaRef(ours->peekItem(), other->peekItem())));
140 if (--maxCount <= 0)
141 return false;
142 }
143 }
144 else
145 {
146 differences.insert(std::make_pair(ours->peekItem()->key(), DeltaRef(ours->peekItem(), nullptr)));
147 if (--maxCount <= 0)
148 return false;
149
150 differences.insert(std::make_pair(other->peekItem()->key(), DeltaRef(nullptr, other->peekItem())));
151 if (--maxCount <= 0)
152 return false;
153 }
154 }
155 else if (ourNode->isInner() && otherNode->isLeaf())
156 {
157 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
158 auto other = static_cast<SHAMapLeafNode*>(otherNode);
159 if (!walkBranch(ours, other->peekItem(), true, differences, maxCount))
160 return false;
161 }
162 else if (ourNode->isLeaf() && otherNode->isInner())
163 {
164 auto ours = static_cast<SHAMapLeafNode*>(ourNode);
165 auto other = static_cast<SHAMapInnerNode*>(otherNode);
166 if (!otherMap.walkBranch(other, ours->peekItem(), false, differences, maxCount))
167 return false;
168 }
169 else if (ourNode->isInner() && otherNode->isInner())
170 {
171 auto ours = static_cast<SHAMapInnerNode*>(ourNode);
172 auto other = static_cast<SHAMapInnerNode*>(otherNode);
173 for (int i = 0; i < 16; ++i)
174 if (ours->getChildHash(i) != other->getChildHash(i))
175 {
176 if (other->isEmptyBranch(i))
177 {
178 // We have a branch, the other tree does not
179 SHAMapTreeNode* iNode = descendThrow(ours, i);
180 if (!walkBranch(iNode, nullptr, true, differences, maxCount))
181 return false;
182 }
183 else if (ours->isEmptyBranch(i))
184 {
185 // The other tree has a branch, we do not
186 SHAMapTreeNode* iNode = otherMap.descendThrow(other, i);
187 if (!otherMap.walkBranch(iNode, nullptr, false, differences, maxCount))
188 return false;
189 }
190 else // The two trees have different non-empty branches
191 nodeStack.push({descendThrow(ours, i), otherMap.descendThrow(other, i)});
192 }
193 }
194 else
195 {
196 // LCOV_EXCL_START
197 UNREACHABLE("xrpl::SHAMap::compare : invalid node");
198 // LCOV_EXCL_STOP
199 }
200 }
201
202 return true;
203}
204
205void
206SHAMap::walkMap(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing) const
207{
208 if (!root_->isInner()) // root_ is only node, and we have it
209 return;
210
211 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
213
214 nodeStack.push(intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_));
215
216 while (!nodeStack.empty())
217 {
218 intr_ptr::SharedPtr<SHAMapInnerNode> node = std::move(nodeStack.top());
219 nodeStack.pop();
220
221 for (int i = 0; i < 16; ++i)
222 {
223 if (!node->isEmptyBranch(i))
224 {
225 intr_ptr::SharedPtr<SHAMapTreeNode> nextNode = descendNoStore(*node, i);
226
227 if (nextNode)
228 {
229 if (nextNode->isInner())
230 nodeStack.push(intr_ptr::static_pointer_cast<SHAMapInnerNode>(nextNode));
231 }
232 else
233 {
234 missingNodes.emplace_back(type_, node->getChildHash(i));
235 if (--maxMissing <= 0)
236 return;
237 }
238 }
239 }
240 }
241}
242
243bool
244SHAMap::walkMapParallel(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing) const
245{
246 if (!root_->isInner()) // root_ is only node, and we have it
247 return false;
248
249 using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
251 {
252 auto const& innerRoot = intr_ptr::static_pointer_cast<SHAMapInnerNode>(root_);
253 for (int i = 0; i < 16; ++i)
254 {
255 if (!innerRoot->isEmptyBranch(i))
256 topChildren[i] = descendNoStore(*innerRoot, i);
257 }
258 }
260 workers.reserve(16);
262 exceptions.reserve(16);
263
265
266 // This mutex is used inside the worker threads to protect `missingNodes`
267 // and `maxMissing` from race conditions
268 std::mutex m;
269
270 for (int rootChildIndex = 0; rootChildIndex < 16; ++rootChildIndex)
271 {
272 auto const& child = topChildren[rootChildIndex];
273 if (!child || !child->isInner())
274 continue;
275
276 nodeStacks[rootChildIndex].push(intr_ptr::static_pointer_cast<SHAMapInnerNode>(child));
277
278 JLOG(journal_.debug()) << "starting worker " << rootChildIndex;
279 workers.push_back(std::thread(
280 [&m, &missingNodes, &maxMissing, &exceptions, this](
281 std::stack<StackEntry, std::vector<StackEntry>> nodeStack) {
282 try
283 {
284 while (!nodeStack.empty())
285 {
286 intr_ptr::SharedPtr<SHAMapInnerNode> node = std::move(nodeStack.top());
287 XRPL_ASSERT(node, "xrpl::SHAMap::walkMapParallel : non-null node");
288 nodeStack.pop();
289
290 for (int i = 0; i < 16; ++i)
291 {
292 if (node->isEmptyBranch(i))
293 continue;
294 intr_ptr::SharedPtr<SHAMapTreeNode> nextNode = descendNoStore(*node, i);
295
296 if (nextNode)
297 {
298 if (nextNode->isInner())
299 nodeStack.push(intr_ptr::static_pointer_cast<SHAMapInnerNode>(nextNode));
300 }
301 else
302 {
303 std::lock_guard l{m};
304 missingNodes.emplace_back(type_, node->getChildHash(i));
305 if (--maxMissing <= 0)
306 return;
307 }
308 }
309 }
310 }
311 catch (SHAMapMissingNode const& e)
312 {
313 std::lock_guard l(m);
314 exceptions.push_back(e);
315 }
316 },
317 std::move(nodeStacks[rootChildIndex])));
318 }
319
320 for (std::thread& worker : workers)
321 worker.join();
322
323 std::lock_guard l(m);
324 if (exceptions.empty())
325 return true;
327 ss << "Exception(s) in ledger load: ";
328 for (auto const& e : exceptions)
329 ss << e.what() << ", ";
330 JLOG(journal_.error()) << ss.str();
331 return false;
332}
333
334} // namespace xrpl
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
boost::intrusive_ptr< SHAMapItem const > const & peekItem(uint256 const &id) const
Definition SHAMap.cpp:534
bool isValid() const
Definition SHAMap.h:549
std::pair< boost::intrusive_ptr< SHAMapItem const >, boost::intrusive_ptr< SHAMapItem const > > DeltaRef
Definition SHAMap.h:330
SHAMapHash getHash() const
Definition SHAMap.cpp:781
SHAMapTreeNode * descendThrow(SHAMapInnerNode *, int branch) const
Definition SHAMap.cpp:247
intr_ptr::SharedPtr< SHAMapTreeNode > root_
Definition SHAMap.h:89
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)