mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
217 lines
21 KiB
HTML
217 lines
21 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
|
|
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
|
|
<meta name="generator" content="Doxygen 1.8.17"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<title>rippled: SHAMap Introduction</title>
|
|
<link href="tabs.css" rel="stylesheet" type="text/css"/>
|
|
<script type="text/javascript" src="jquery.js"></script>
|
|
<script type="text/javascript" src="dynsections.js"></script>
|
|
<link href="search/search.css" rel="stylesheet" type="text/css"/>
|
|
<script type="text/javascript" src="search/searchdata.js"></script>
|
|
<script type="text/javascript" src="search/search.js"></script>
|
|
<link href="doxygen.css" rel="stylesheet" type="text/css" />
|
|
</head>
|
|
<body>
|
|
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
|
|
<div id="titlearea">
|
|
<table cellspacing="0" cellpadding="0">
|
|
<tbody>
|
|
<tr style="height: 56px;">
|
|
<td id="projectalign" style="padding-left: 0.5em;">
|
|
<div id="projectname">rippled
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<!-- end header part -->
|
|
<!-- Generated by Doxygen 1.8.17 -->
|
|
<script type="text/javascript">
|
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
|
var searchBox = new SearchBox("searchBox", "search",false,'Search');
|
|
/* @license-end */
|
|
</script>
|
|
<script type="text/javascript" src="menudata.js"></script>
|
|
<script type="text/javascript" src="menu.js"></script>
|
|
<script type="text/javascript">
|
|
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt GPL-v2 */
|
|
$(function() {
|
|
initMenu('',true,false,'search.php','Search');
|
|
$(document).ready(function() { init_search(); });
|
|
});
|
|
/* @license-end */</script>
|
|
<div id="main-nav"></div>
|
|
<!-- window showing the filter options -->
|
|
<div id="MSearchSelectWindow"
|
|
onmouseover="return searchBox.OnSearchSelectShow()"
|
|
onmouseout="return searchBox.OnSearchSelectHide()"
|
|
onkeydown="return searchBox.OnSearchSelectKey(event)">
|
|
</div>
|
|
|
|
<!-- iframe showing the search results (closed by default) -->
|
|
<div id="MSearchResultsWindow">
|
|
<iframe src="javascript:void(0)" frameborder="0"
|
|
name="MSearchResults" id="MSearchResults">
|
|
</iframe>
|
|
</div>
|
|
|
|
</div><!-- top -->
|
|
<div class="PageDoc"><div class="header">
|
|
<div class="headertitle">
|
|
<div class="title">SHAMap Introduction </div> </div>
|
|
</div><!--header-->
|
|
<div class="contents">
|
|
<div class="textblock"><p>March 2020</p>
|
|
<p>The <code>SHAMap</code> is a Merkle tree (<a href="http://en.wikipedia.org/wiki/Merkle_tree">http://en.wikipedia.org/wiki/Merkle_tree</a>). The <code>SHAMap</code> is also a radix trie of radix 16 (<a href="http://en.wikipedia.org/wiki/Radix_tree">http://en.wikipedia.org/wiki/Radix_tree</a>).</p>
|
|
<p>The Merkle trie data structure is important because subtrees and even the entire tree can be compared with other trees in O(1) time by simply comparing the hashes. This makes it very efficient to determine if two <code>SHAMap</code>s contain the same set of transactions or account state modifications.</p>
|
|
<p>The radix trie property is helpful in that a key (hash) of a transaction or account state can be used to navigate the trie.</p>
|
|
<p>A <code>SHAMap</code> is a trie with two node types:</p>
|
|
<ol type="1">
|
|
<li>SHAMapInnerNode</li>
|
|
<li>SHAMapLeafNode</li>
|
|
</ol>
|
|
<p>Both of these nodes directly inherit from SHAMapTreeNode which holds data common to both of the node types.</p>
|
|
<p>All non-leaf nodes have type SHAMapInnerNode.</p>
|
|
<p>All leaf nodes have type SHAMapLeafNode.</p>
|
|
<p>The root node is always a SHAMapInnerNode.</p>
|
|
<p>A given <code>SHAMap</code> always stores only one of three kinds of data:</p>
|
|
<ul>
|
|
<li>Transactions with metadata</li>
|
|
<li>Transactions without metadata, or</li>
|
|
<li>Account states.</li>
|
|
</ul>
|
|
<p>So all of the leaf nodes of a particular <code>SHAMap</code> will always have a uniform type. The inner nodes carry no data other than the hash of the nodes beneath them.</p>
|
|
<p>All nodes are owned by shared_ptrs resident in either other nodes, or in case of the root node, a shared_ptr in the <code>SHAMap</code> itself. The use of shared_ptrs permits more than one <code>SHAMap</code> at a time to share ownership of a node. This occurs (for example), when a copy of a <code>SHAMap</code> is made.</p>
|
|
<p>Copies are made with the <code>snapShot</code> function as opposed to the <code>SHAMap</code> copy constructor. See the section on <code>SHAMap</code> creation for more details about <code>snapShot</code>.</p>
|
|
<p>Sequence numbers are used to further customize the node ownership strategy. See the section on sequence numbers for details on sequence numbers.</p>
|
|
<p><img src="https://user-images.githubusercontent.com/46455409/77350005-1ef12c80-6cf9-11ea-9c8d-56410f442859.png" alt="node diagram" class="inline"/></p>
|
|
<h1><a class="anchor" id="autotoc_md285"></a>
|
|
Mutability</h1>
|
|
<p>There are two different ways of building and using a <code>SHAMap</code>:</p>
|
|
<ol type="1">
|
|
<li>A mutable <code>SHAMap</code> and</li>
|
|
<li>An immutable <code>SHAMap</code></li>
|
|
</ol>
|
|
<p>The distinction here is not of the classic C++ immutable-means-unchanging sense. An immutable <code>SHAMap</code> contains <em>nodes</em> that are immutable. Also, once a node has been located in an immutable <code>SHAMap</code>, that node is guaranteed to persist in that <code>SHAMap</code> for the lifetime of the <code>SHAMap</code>.</p>
|
|
<p>So, somewhat counter-intuitively, an immutable <code>SHAMap</code> may grow as new nodes are introduced. But an immutable <code>SHAMap</code> will never get smaller (until it entirely evaporates when it is destroyed). Nodes, once introduced to the immutable <code>SHAMap</code>, also never change their location in memory. So nodes in an immutable <code>SHAMap</code> can be handled using raw pointers (if you're careful).</p>
|
|
<p>One consequence of this design is that an immutable <code>SHAMap</code> can never be "trimmed". There is no way to identify unnecessary nodes in an immutable <code>SHAMap</code> that could be removed. Once a node has been brought into the in-memory <code>SHAMap</code>, that node stays in memory for the life of the <code>SHAMap</code>.</p>
|
|
<p>Most <code>SHAMap</code>s are immutable, in the sense that they don't modify or remove their contained nodes.</p>
|
|
<p>An example where a mutable <code>SHAMap</code> is required is when we want to apply transactions to the last closed ledger. To do so we'd make a mutable snapshot of the state trie and then start applying transactions to it. Because the snapshot is mutable, changes to nodes in the snapshot will not affect nodes in other <code>SHAMap</code>s.</p>
|
|
<p>An example using a immutable ledger would be when there's an open ledger and some piece of code wishes to query the state of the ledger. In this case we don't wish to change the state of the <code>SHAMap</code>, so we'd use an immutable snapshot.</p>
|
|
<h1><a class="anchor" id="autotoc_md286"></a>
|
|
Sequence numbers</h1>
|
|
<p>Both <code>SHAMap</code>s and their nodes carry a sequence number. This is simply an unsigned number that indicates ownership or membership, or a non-membership.</p>
|
|
<p><code>SHAMap</code>s sequence numbers normally start out as 1. However when a snap-shot of a <code>SHAMap</code> is made, the copy's sequence number is 1 greater than the original.</p>
|
|
<p>The nodes of a <code>SHAMap</code> have their own copy of a sequence number. If the <code>SHAMap</code> is mutable, meaning it can change, then all of its nodes must have the same sequence number as the <code>SHAMap</code> itself. This enforces an invariant that none of the nodes are shared with other <code>SHAMap</code>s.</p>
|
|
<p>When a <code>SHAMap</code> needs to have a private copy of a node, not shared by any other <code>SHAMap</code>, it first clones it and then sets the new copy to have a sequence number equal to the <code>SHAMap</code> sequence number. The <code>unshareNode</code> is a private utility which automates the task of first checking if the node is already sharable, and if so, cloning it and giving it the proper sequence number. An example case where a private copy is needed is when an inner node needs to have a child pointer altered. Any modification to a node will require a non-shared node.</p>
|
|
<p>When a <code>SHAMap</code> decides that it is safe to share a node of its own, it sets the node's sequence number to 0 (a <code>SHAMap</code> never has a sequence number of 0). This is done for every node in the trie when <code>SHAMap::walkSubTree</code> is executed.</p>
|
|
<p>Note that other objects in rippled also have sequence numbers (e.g. ledgers). The <code>SHAMap</code> and node sequence numbers should not be confused with these other sequence numbers (no relation).</p>
|
|
<h1><a class="anchor" id="autotoc_md287"></a>
|
|
SHAMap Creation</h1>
|
|
<p>A <code>SHAMap</code> is usually not created from vacuum. Once an initial <code>SHAMap</code> is constructed, later <code>SHAMap</code>s are usually created by calling snapShot(bool isMutable) on the original <code>SHAMap</code>. The returned <code>SHAMap</code> has the expected characteristics (mutable or immutable) based on the passed in flag.</p>
|
|
<p>It is cheaper to make an immutable snapshot of a <code>SHAMap</code> than to make a mutable snapshot. If the <code>SHAMap</code> snapshot is mutable then sharable nodes must be copied before they are placed in the mutable map.</p>
|
|
<p>A new <code>SHAMap</code> is created with each new ledger round. Transactions not executed in the previous ledger populate the <code>SHAMap</code> for the new ledger.</p>
|
|
<h1><a class="anchor" id="autotoc_md288"></a>
|
|
Storing SHAMap data in the database</h1>
|
|
<p>When consensus is reached, the ledger is closed. As part of this process, the <code>SHAMap</code> is stored to the database by calling <code>SHAMap::flushDirty</code>.</p>
|
|
<p>Both <code>unshare()</code> and <code>flushDirty</code> walk the <code>SHAMap</code> by calling <code>SHAMap::walkSubTree</code>. As <code>unshare()</code> walks the trie, nodes are not written to the database, and as <code>flushDirty</code> walks the trie nodes are written to the database. <code>walkSubTree</code> visits every node in the trie. This process must ensure that each node is only owned by this trie, and so "unshares" as it walks each node (from the root down). This is done in the <code>preFlushNode</code> function by ensuring that the node has a sequence number equal to that of the <code>SHAMap</code>. If the node doesn't, it is cloned.</p>
|
|
<p>For each inner node encountered (starting with the root node), each of the children are inspected (from 1 to 16). For each child, if it has a non-zero sequence number (unshareable), the child is first copied. Then if the child is an inner node, we recurse down to that node's children. Otherwise we've found a leaf node and that node is written to the database. A count of each leaf node that is visited is kept. The hash of the data in the leaf node is computed at this time, and the child is reassigned back into the parent inner node just in case the COW operation created a new pointer to this leaf node.</p>
|
|
<p>After processing each node, the node is then marked as sharable again by setting its sequence number to 0.</p>
|
|
<p>After all of an inner node's children are processed, then its hash is updated and the inner node is written to the database. Then this inner node is assigned back into it's parent node, again in case the COW operation created a new pointer to it.</p>
|
|
<h1><a class="anchor" id="autotoc_md289"></a>
|
|
Walking a SHAMap</h1>
|
|
<p>The private function <code>SHAMap::walkTowardsKey</code> is a good example of <em>how</em> to walk a <code>SHAMap</code>, and the various functions that call <code>walkTowardsKey</code> are good examples of <em>why</em> one would want to walk a <code>SHAMap</code> (e.g. <code>SHAMap::findKey</code>). <code>walkTowardsKey</code> always starts at the root of the <code>SHAMap</code> and traverses down through the inner nodes, looking for a leaf node along a path in the trie designated by a <code>uint256</code>.</p>
|
|
<p>As one walks the trie, one can <em>optionally</em> keep a stack of nodes that one has passed through. This isn't necessary for walking the trie, but many clients will use the stack after finding the desired node. For example if one is deleting a node from the trie, the stack is handy for repairing invariants in the trie after the deletion.</p>
|
|
<p>To assist in walking the trie, <code>SHAMap::walkTowardsKey</code> uses a <code>SHAMapNodeID</code> that identifies a node by its path from the root and its depth in the trie. The path is just a "list" of numbers, each in the range [0 .. 15], depicting which child was chosen at each node starting from the root. Each choice is represented by 4 bits, and then packed in sequence into a <code>uint256</code> (such that the longest path possible has 256 / 4 = 64 steps). The high 4 bits of the first byte identify which child of the root is chosen, the lower 4 bits of the first byte identify the child of that node, and so on. The <code>SHAMapNodeID</code> identifying the root node has an ID of 0 and a depth of 0. See <code>selectBranch</code> for details of how we use a <code>SHAMapNodeID</code> to select a "branch" (child) by indexing into a path at a given depth.</p>
|
|
<p>While the current node is an inner node, traversing down the trie from the root continues, unless the path indicates a child that does not exist. And in this case, <code>nullptr</code> is returned to indicate no leaf node along the given path exists. Otherwise a leaf node is found and a (non-owning) pointer to it is returned. At each step, if a stack is requested, a <code>pair<shared_ptr<SHAMapTreeNode>, SHAMapNodeID></code> is pushed onto the stack.</p>
|
|
<p>When a child node is found by <code>selectBranch</code>, the traversal to that node consists of two steps:</p>
|
|
<ol type="1">
|
|
<li>Update the <code>shared_ptr</code> to the current node.</li>
|
|
<li>Update the <code>SHAMapNodeID</code>.</li>
|
|
</ol>
|
|
<p>The first step consists of several attempts to find the node in various places:</p>
|
|
<ol type="1">
|
|
<li>In the trie itself.</li>
|
|
<li>In the node cache.</li>
|
|
<li>In the database.</li>
|
|
</ol>
|
|
<p>If the node is not found in the trie, then it is installed into the trie as part of the traversal process.</p>
|
|
<h1><a class="anchor" id="autotoc_md290"></a>
|
|
Late-arriving Nodes</h1>
|
|
<p>As we noted earlier, <code>SHAMap</code>s (even immutable ones) may grow. If a <code>SHAMap</code> is searching for a node and runs into an empty spot in the trie, then the <code>SHAMap</code> looks to see if the node exists but has not yet been made part of the map. This operation is performed in the <code>SHAMap::fetchNodeNT()</code> method. The <em>NT</em> is this case stands for 'No Throw'.</p>
|
|
<p>The <code>fetchNodeNT()</code> method goes through three phases:</p>
|
|
<ol type="1">
|
|
<li><p class="startli">By calling <code>cacheLookup()</code> we attempt to locate the missing node in the TreeNodeCache. The TreeNodeCache is a cache of immutable SHAMapTreeNodes that are shared across all <code>SHAMap</code>s.</p>
|
|
<p class="startli">Any SHAMapLeafNode that is immutable has a sequence number of zero (sharable). When a mutable <code>SHAMap</code> is created then its SHAMapTreeNodes are given non-zero sequence numbers (unsharable). But all nodes in the TreeNodeCache are immutable, so if one is found here, its sequence number will be 0.</p>
|
|
</li>
|
|
<li>If the node is not in the TreeNodeCache, we attempt to locate the node in the historic data stored by the data base. The call to to <code>fetchNodeFromDB(hash)</code> does that work for us.</li>
|
|
<li>Finally if a filter exists, we check if it can supply the node. This is typically the LedgerMaster which tracks the current ledger and ledgers in the process of closing.</li>
|
|
</ol>
|
|
<h1><a class="anchor" id="autotoc_md291"></a>
|
|
Canonicalize</h1>
|
|
<p><code>canonicalize()</code> is called every time a node is introduced into the <code>SHAMap</code>.</p>
|
|
<p>A call to <code>canonicalize()</code> stores the node in the <code>TreeNodeCache</code> if it does not already exist in the <code>TreeNodeCache</code>.</p>
|
|
<p>The calls to <code>canonicalize()</code> make sure that if the resulting node is already in the <code>SHAMap</code>, node <code>TreeNodeCache</code> or database, then we don't create duplicates by favoring the copy already in the <code>TreeNodeCache</code>.</p>
|
|
<p>By using <code>canonicalize()</code> we manage a thread race condition where two different threads might both recognize the lack of a SHAMapLeafNode at the same time (during a fetch). If they both attempt to insert the node into the <code>SHAMap</code>, then <code>canonicalize</code> makes sure that the first node in wins and the slower thread receives back a pointer to the node inserted by the faster thread. Recall that these two <code>SHAMap</code>s will share the same <code>TreeNodeCache</code>.</p>
|
|
<h1><a class="anchor" id="autotoc_md292"></a>
|
|
<tt>TreeNodeCache</tt></h1>
|
|
<p>The <code>TreeNodeCache</code> is a <code><a class="elRef" href="http://en.cppreference.com/w/cpp/container/unordered_map.html" title="STL class.">std::unordered_map</a></code> keyed on the hash of the <code>SHAMap</code> node. The stored type consists of <code>shared_ptr<SHAMapTreeNode></code>, <code>weak_ptr<SHAMapTreeNode></code>, and a time point indicating the most recent access of this node in the cache. The time point is based on <code><a class="elRef" href="http://en.cppreference.com/w/cpp/chrono/steady_clock.html">std::chrono::steady_clock</a></code>.</p>
|
|
<p>The container uses a cryptographically secure hash that is randomly seeded.</p>
|
|
<p>The <code>TreeNodeCache</code> also carries with it various data used for statistics and logging, and a target age for the contained nodes. When the target age for a node is exceeded, and there are no more references to the node, the node is removed from the <code>TreeNodeCache</code>.</p>
|
|
<h1><a class="anchor" id="autotoc_md293"></a>
|
|
<tt>FullBelowCache</tt></h1>
|
|
<p>This cache remembers which trie keys have all of their children resident in a <code>SHAMap</code>. This optimizes the process of acquiring a complete trie. This is used when creating the missing nodes list. Missing nodes are those nodes that a <code>SHAMap</code> refers to but that are not stored in the local database.</p>
|
|
<p>As a depth-first walk of a <code>SHAMap</code> is performed, if an inner node answers true to <code>isFullBelow()</code> then it is known that none of this node's children are missing nodes, and thus that subtree does not need to be walked. These nodes are stored in the FullBelowCache. Subsequent walks check the FullBelowCache first when encountering a node, and ignore that subtree if found.</p>
|
|
<h1><a class="anchor" id="autotoc_md294"></a>
|
|
<tt>SHAMapTreeNode</tt></h1>
|
|
<p>This is an abstract base class for the concrete node types. It holds the following common data:</p>
|
|
<ol type="1">
|
|
<li>A hash</li>
|
|
<li>An identifier used to perform copy-on-write operations</li>
|
|
</ol>
|
|
<h2><a class="anchor" id="autotoc_md295"></a>
|
|
<tt>SHAMapInnerNode</tt></h2>
|
|
<p><code>SHAMapInnerNode</code> publicly inherits directly from <code>SHAMapTreeNode</code>. It holds the following data:</p>
|
|
<ol type="1">
|
|
<li>Up to 16 child nodes, each held with a shared_ptr.</li>
|
|
<li>A hash for each child.</li>
|
|
<li>A bitset to indicate which of the 16 children exist.</li>
|
|
<li>An identifier used to determine whether the map below this node is fully populated</li>
|
|
</ol>
|
|
<h2><a class="anchor" id="autotoc_md296"></a>
|
|
<tt>SHAMapLeafNode</tt></h2>
|
|
<p><code>SHAMapLeafNode</code> is an abstract class which publicly inherits directly from <code>SHAMapTreeNode</code>. It isIt holds the following data:</p>
|
|
<ol type="1">
|
|
<li>A shared_ptr to a const SHAMapItem.</li>
|
|
</ol>
|
|
<h3><a class="anchor" id="autotoc_md297"></a>
|
|
<tt>SHAMapAccountStateLeafNode</tt></h3>
|
|
<p><code>SHAMapAccountStateLeafNode</code> is a class which publicly inherits directly from <code>SHAMapLeafNode</code>. It is used to represent entries (i.e. account objects, escrow objects, trust lines, etc.) in a state map.</p>
|
|
<h3><a class="anchor" id="autotoc_md298"></a>
|
|
<tt>SHAMapTxLeafNode</tt></h3>
|
|
<p><code>SHAMapTxLeafNode</code> is a class which publicly inherits directly from <code>SHAMapLeafNode</code>. It is used to represent transactions in a state map.</p>
|
|
<h3><a class="anchor" id="autotoc_md299"></a>
|
|
<tt>SHAMapTxPlusMetaLeafNode</tt></h3>
|
|
<p><code>SHAMapTxPlusMetaLeafNode</code> is a class which publicly inherits directly from <code>SHAMapLeafNode</code>. It is used to represent transactions along with metadata associated with this transaction in a state map.</p>
|
|
<h1><a class="anchor" id="autotoc_md300"></a>
|
|
SHAMapItem</h1>
|
|
<p>This holds the following data:</p>
|
|
<ol type="1">
|
|
<li>uint256. The hash of the data.</li>
|
|
<li><a class="elRef" href="http://en.cppreference.com/w/cpp/container/vector.html">vector<unsigned char></a>. The data (transactions, account info). </li>
|
|
</ol>
|
|
</div></div><!-- contents -->
|
|
</div><!-- PageDoc -->
|
|
<!-- start footer part -->
|
|
<hr class="footer"/><address class="footer"><small>
|
|
Generated by  <a href="http://www.doxygen.org/index.html">
|
|
<img class="footer" src="doxygen.png" alt="doxygen"/>
|
|
</a> 1.8.17
|
|
</small></address>
|
|
</body>
|
|
</html>
|