Files
rippled/src/ripple_app/shamap/SHAMap.cpp
Miguel Portilla 98612a7cd6 Cleanup nodestore backend classes:
* Add README.md
* Add missing std::move calls
* Refactor visitAll in backend
2014-05-05 13:50:00 -07:00

1392 lines
38 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include "../../beast/beast/unit_test/suite.h"
namespace ripple {
SETUP_LOG (SHAMap)
void SHAMap::DefaultMissingNodeHandler::operator() (std::uint32_t refNUm)
{
getApp().getOPs ().missingNodeInLedger (refNUm);
};
//------------------------------------------------------------------------------
SHAMap::SHAMap (SHAMapType t, FullBelowCache& fullBelowCache, std::uint32_t seq,
MissingNodeHandler missing_node_handler)
: m_fullBelowCache (fullBelowCache)
, mSeq (seq)
, mLedgerSeq (0)
, mState (smsModifying)
, mType (t)
, mTXMap (false)
, m_missing_node_handler (missing_node_handler)
{
assert (mSeq != 0);
if (t == smtSTATE)
mTNByID.rehash (STATE_MAP_BUCKETS);
root = boost::make_shared<SHAMapTreeNode> (mSeq, SHAMapNode (0, uint256 ()));
root->makeInner ();
mTNByID.replace(*root, root);
}
SHAMap::SHAMap (SHAMapType t, uint256 const& hash, FullBelowCache& fullBelowCache,
MissingNodeHandler missing_node_handler)
: m_fullBelowCache (fullBelowCache)
, mSeq (1)
, mLedgerSeq (0)
, mState (smsSynching)
, mType (t)
, mTXMap (false)
, m_missing_node_handler (missing_node_handler)
{
if (t == smtSTATE)
mTNByID.rehash (STATE_MAP_BUCKETS);
root = boost::make_shared<SHAMapTreeNode> (mSeq, SHAMapNode (0, uint256 ()));
root->makeInner ();
mTNByID.replace(*root, root);
}
TaggedCache <uint256, SHAMapTreeNode>
SHAMap::treeNodeCache ("TreeNodeCache", 65536, 60,
get_seconds_clock (),
LogPartition::getJournal <TaggedCacheLog> ());
SHAMap::~SHAMap ()
{
mState = smsInvalid;
logTimedDestroy <SHAMap> (mTNByID,
beast::String ("mTNByID with ") +
beast::String::fromNumber (mTNByID.size ()) + " items");
if (mDirtyNodes)
{
logTimedDestroy <SHAMap> (mDirtyNodes,
beast::String ("mDirtyNodes with ") +
beast::String::fromNumber (mDirtyNodes->size ()) + " items");
}
if (root)
{
logTimedDestroy <SHAMap> (root,
beast::String ("root node"));
}
}
void SHAMapNode::setMHash () const
{
using namespace std;
std::size_t h = HashMaps::getInstance ().getNonce <std::size_t> ()
+ (mDepth * HashMaps::goldenRatio);
const unsigned int* ptr = reinterpret_cast <const unsigned int*> (mNodeID.begin ());
for (int i = (mDepth + 7) / 8; i != 0; --i)
h = (h * HashMaps::goldenRatio) ^ *ptr++;
mHash = h;
}
std::size_t hash_value (const SHAMapNode& mn)
{
return mn.getMHash ();
}
SHAMap::pointer SHAMap::snapShot (bool isMutable)
{
SHAMap::pointer ret = boost::make_shared<SHAMap> (mType,
std::ref (m_fullBelowCache));
SHAMap& newMap = *ret;
// Return a new SHAMap that is a snapshot of this one
// Initially most nodes are shared and CoW is forced where needed
{
ScopedReadLockType sl (mLock);
newMap.mSeq = mSeq;
newMap.mTNByID = mTNByID;
newMap.root = root;
if (!isMutable)
newMap.mState = smsImmutable;
// If the existing map has any nodes it might modify, unshare ours now
if (mState != smsImmutable)
{
BOOST_FOREACH(NodeMap::value_type& nodeIt, mTNByID.peekMap())
{
if (nodeIt.second->getSeq() == mSeq)
{ // We might modify this node, so duplicate it in the snapShot
SHAMapTreeNode::pointer newNode = boost::make_shared<SHAMapTreeNode> (*nodeIt.second, mSeq);
newMap.mTNByID.replace (*newNode, newNode);
if (newNode->isRoot ())
newMap.root = newNode;
}
}
}
else if (isMutable) // Need to unshare on changes to the snapshot
++newMap.mSeq;
}
return ret;
}
std::stack<SHAMapTreeNode::pointer> SHAMap::getStack (uint256 const& id, bool include_nonmatching_leaf)
{
// Walk the tree as far as possible to the specified identifier
// produce a stack of nodes along the way, with the terminal node at the top
std::stack<SHAMapTreeNode::pointer> stack;
SHAMapTreeNode::pointer node = root;
while (!node->isLeaf ())
{
stack.push (node);
int branch = node->selectBranch (id);
assert (branch >= 0);
if (node->isEmptyBranch (branch))
return stack;
try
{
node = getNode (node->getChildNodeID (branch), node->getChildHash (branch), false);
}
catch (SHAMapMissingNode& mn)
{
mn.setTargetNode (id);
throw;
}
}
if (include_nonmatching_leaf || (node->peekItem ()->getTag () == id))
stack.push (node);
return stack;
}
void SHAMap::dirtyUp (std::stack<SHAMapTreeNode::pointer>& stack, uint256 const& target, uint256 prevHash)
{
// walk the tree up from through the inner nodes to the root
// update linking hashes and add nodes to dirty list
assert ((mState != smsSynching) && (mState != smsImmutable));
while (!stack.empty ())
{
SHAMapTreeNode::pointer node = stack.top ();
stack.pop ();
assert (node->isInnerNode ());
int branch = node->selectBranch (target);
assert (branch >= 0);
returnNode (node, true);
if (!node->setChildHash (branch, prevHash))
{
WriteLog (lsFATAL, SHAMap) << "dirtyUp terminates early";
assert (false);
return;
}
#ifdef ST_DEBUG
WriteLog (lsTRACE, SHAMap) << "dirtyUp sets branch " << branch << " to " << prevHash;
#endif
prevHash = node->getNodeHash ();
assert (prevHash.isNonZero ());
}
}
SHAMapTreeNode::pointer SHAMap::checkCacheNode (const SHAMapNode& iNode)
{
SHAMapTreeNode::pointer ret = mTNByID.retrieve(iNode);
if (ret && (ret->getSeq()!= 0))
ret->touch (mSeq);
return ret;
}
SHAMapTreeNode::pointer SHAMap::walkTo (uint256 const& id, bool modify)
{
// walk down to the terminal node for this ID
SHAMapTreeNode::pointer inNode = root;
while (!inNode->isLeaf ())
{
int branch = inNode->selectBranch (id);
if (inNode->isEmptyBranch (branch))
return inNode;
try
{
inNode = getNode (inNode->getChildNodeID (branch), inNode->getChildHash (branch), false);
}
catch (SHAMapMissingNode& mn)
{
mn.setTargetNode (id);
throw;
}
}
if (inNode->getTag () != id)
return SHAMapTreeNode::pointer ();
if (modify)
returnNode (inNode, true);
return inNode;
}
SHAMapTreeNode* SHAMap::walkToPointer (uint256 const& id)
{
SHAMapTreeNode* inNode = root.get ();
while (!inNode->isLeaf ())
{
int branch = inNode->selectBranch (id);
if (inNode->isEmptyBranch (branch))
return nullptr;
inNode = getNodePointer (inNode->getChildNodeID (branch), inNode->getChildHash (branch));
assert (inNode);
}
return (inNode->getTag () == id) ? inNode : nullptr;
}
SHAMapTreeNode::pointer SHAMap::getNode (const SHAMapNode& id, uint256 const& hash, bool modify)
{
// retrieve a node whose node hash is known
SHAMapTreeNode::pointer node = checkCacheNode (id);
if (node)
{
#if BEAST_DEBUG
if (node->getNodeHash () != hash)
{
WriteLog (lsFATAL, SHAMap) << "Attempt to get node, hash not in tree";
WriteLog (lsFATAL, SHAMap) << "ID: " << id;
WriteLog (lsFATAL, SHAMap) << "TgtHash " << hash;
WriteLog (lsFATAL, SHAMap) << "NodHash " << node->getNodeHash ();
throw std::runtime_error ("invalid node");
}
#endif
returnNode (node, modify);
return node;
}
return fetchNodeExternal (id, hash);
}
SHAMapTreeNode* SHAMap::getNodePointer (const SHAMapNode& id, uint256 const& hash)
{
// fast, but you do not hold a reference
SHAMapTreeNode* ret = getNodePointerNT (id, hash);
if (!ret)
throw (SHAMapMissingNode (mType, id, hash));
return ret;
}
SHAMapTreeNode* SHAMap::getNodePointerNT (const SHAMapNode& id, uint256 const& hash)
{
SHAMapTreeNode::pointer ret = mTNByID.retrieve (id);
if (!ret)
ret = fetchNodeExternalNT (id, hash);
return ret ? ret.get() : nullptr;
}
SHAMapTreeNode* SHAMap::getNodePointer (const SHAMapNode& id, uint256 const& hash, SHAMapSyncFilter* filter)
{
SHAMapTreeNode* ret = getNodePointerNT (id, hash, filter);
if (!ret)
throw (SHAMapMissingNode (mType, id, hash));
return ret;
}
SHAMapTreeNode* SHAMap::getNodePointerNT (const SHAMapNode& id, uint256 const& hash, SHAMapSyncFilter* filter)
{
SHAMapTreeNode* node = getNodePointerNT (id, hash);
if (!node && filter)
{ // Our regular node store didn't have the node. See if the filter does
Blob nodeData;
if (filter->haveNode (id, hash, nodeData))
{
SHAMapTreeNode::pointer node = boost::make_shared<SHAMapTreeNode> (
boost::cref (id), boost::cref (nodeData), 0, snfPREFIX, boost::cref (hash), true);
canonicalize (hash, node);
// Canonicalize the node with mTNByID to make sure all threads gets the same node
// If the node is new, tell the filter
if (mTNByID.canonicalize (id, &node))
filter->gotNode (true, id, hash, nodeData, node->getType ());
return node.get ();
}
}
return node;
}
void SHAMap::returnNode (SHAMapTreeNode::pointer& node, bool modify)
{
// make sure the node is suitable for the intended operation (copy on write)
assert (node->isValid ());
assert (node->getSeq () <= mSeq);
if (node && modify && (node->getSeq () != mSeq))
{
// have a CoW
assert (node->getSeq () < mSeq);
assert (mState != smsImmutable);
node = boost::make_shared<SHAMapTreeNode> (*node, mSeq); // here's to the new node, same as the old node
assert (node->isValid ());
mTNByID.replace (*node, node);
if (node->isRoot ())
root = node;
if (mDirtyNodes)
(*mDirtyNodes)[*node] = node;
}
}
void SHAMap::trackNewNode (SHAMapTreeNode::pointer& node)
{
assert (node->getSeq() == mSeq);
if (mDirtyNodes)
(*mDirtyNodes)[*node] = node;
}
SHAMapTreeNode* SHAMap::firstBelow (SHAMapTreeNode* node)
{
// Return the first item below this node
do
{
// Walk down the tree
if (node->hasItem ())
return node;
bool foundNode = false;
for (int i = 0; i < 16; ++i)
if (!node->isEmptyBranch (i))
{
node = getNodePointer (node->getChildNodeID (i), node->getChildHash (i));
foundNode = true;
break;
}
if (!foundNode)
return nullptr;
}
while (true);
}
SHAMapTreeNode* SHAMap::lastBelow (SHAMapTreeNode* node)
{
do
{
// Walk down the tree
if (node->hasItem ())
return node;
bool foundNode = false;
for (int i = 15; i >= 0; ++i)
if (!node->isEmptyBranch (i))
{
node = getNodePointer (node->getChildNodeID (i), node->getChildHash (i));
foundNode = true;
break;
}
if (!foundNode)
return nullptr;
}
while (true);
}
SHAMapItem::pointer SHAMap::onlyBelow (SHAMapTreeNode* node)
{
// If there is only one item below this node, return it
while (!node->isLeaf ())
{
SHAMapTreeNode* nextNode = nullptr;
for (int i = 0; i < 16; ++i)
if (!node->isEmptyBranch (i))
{
if (nextNode)
return SHAMapItem::pointer (); // two leaves below
nextNode = getNodePointer (node->getChildNodeID (i), node->getChildHash (i));
}
if (!nextNode)
{
WriteLog (lsFATAL, SHAMap) << *node;
assert (false);
return SHAMapItem::pointer ();
}
node = nextNode;
}
assert (node->hasItem ());
return node->peekItem ();
}
void SHAMap::eraseChildren (SHAMapTreeNode::pointer node)
{
// this node has only one item below it, erase its children
bool erase = false;
while (node->isInner ())
{
for (int i = 0; i < 16; ++i)
if (!node->isEmptyBranch (i))
{
SHAMapTreeNode::pointer nextNode = getNode (node->getChildNodeID (i), node->getChildHash (i), false);
if (erase)
{
returnNode (node, true);
if (mTNByID.erase (*node))
assert (false);
}
erase = true;
node = nextNode;
break;
}
}
returnNode (node, true);
if (mTNByID.erase (*node) == 0)
assert (false);
return;
}
static const SHAMapItem::pointer no_item;
SHAMapItem::pointer SHAMap::peekFirstItem ()
{
ScopedReadLockType sl (mLock);
SHAMapTreeNode* node = firstBelow (root.get ());
if (!node)
return no_item;
return node->peekItem ();
}
SHAMapItem::pointer SHAMap::peekFirstItem (SHAMapTreeNode::TNType& type)
{
ScopedReadLockType sl (mLock);
SHAMapTreeNode* node = firstBelow (root.get ());
if (!node)
return no_item;
type = node->getType ();
return node->peekItem ();
}
SHAMapItem::pointer SHAMap::peekLastItem ()
{
ScopedReadLockType sl (mLock);
SHAMapTreeNode* node = lastBelow (root.get ());
if (!node)
return no_item;
return node->peekItem ();
}
SHAMapItem::pointer SHAMap::peekNextItem (uint256 const& id)
{
SHAMapTreeNode::TNType type;
return peekNextItem (id, type);
}
SHAMapItem::pointer SHAMap::peekNextItem (uint256 const& id, SHAMapTreeNode::TNType& type)
{
// Get a pointer to the next item in the tree after a given item - item need not be in tree
ScopedReadLockType sl (mLock);
std::stack<SHAMapTreeNode::pointer> stack = getStack (id, true);
while (!stack.empty ())
{
SHAMapTreeNode::pointer node = stack.top ();
stack.pop ();
if (node->isLeaf ())
{
if (node->peekItem ()->getTag () > id)
{
type = node->getType ();
return node->peekItem ();
}
}
else
for (int i = node->selectBranch (id) + 1; i < 16; ++i)
if (!node->isEmptyBranch (i))
{
SHAMapTreeNode* firstNode = getNodePointer (node->getChildNodeID (i), node->getChildHash (i));
assert (firstNode);
firstNode = firstBelow (firstNode);
if (!firstNode || firstNode->isInner ())
throw (std::runtime_error ("missing/corrupt node"));
type = firstNode->getType ();
return firstNode->peekItem ();
}
}
// must be last item
return no_item;
}
// Get a pointer to the previous item in the tree after a given item - item need not be in tree
SHAMapItem::pointer SHAMap::peekPrevItem (uint256 const& id)
{
ScopedReadLockType sl (mLock);
std::stack<SHAMapTreeNode::pointer> stack = getStack (id, true);
while (!stack.empty ())
{
SHAMapTreeNode::pointer node = stack.top ();
stack.pop ();
if (node->isLeaf ())
{
if (node->peekItem ()->getTag () < id)
return node->peekItem ();
}
else
{
for (int i = node->selectBranch (id) - 1; i >= 0; --i)
{
if (!node->isEmptyBranch (i))
{
node = getNode (node->getChildNodeID (i), node->getChildHash (i), false);
SHAMapTreeNode* item = firstBelow (node.get ());
if (!item)
throw (std::runtime_error ("missing node"));
return item->peekItem ();
}
}
}
}
// must be last item
return no_item;
}
SHAMapItem::pointer SHAMap::peekItem (uint256 const& id)
{
ScopedReadLockType sl (mLock);
SHAMapTreeNode* leaf = walkToPointer (id);
if (!leaf)
return no_item;
return leaf->peekItem ();
}
SHAMapItem::pointer SHAMap::peekItem (uint256 const& id, SHAMapTreeNode::TNType& type)
{
ScopedReadLockType sl (mLock);
SHAMapTreeNode* leaf = walkToPointer (id);
if (!leaf)
return no_item;
type = leaf->getType ();
return leaf->peekItem ();
}
SHAMapItem::pointer SHAMap::peekItem (uint256 const& id, uint256& hash)
{
ScopedReadLockType sl (mLock);
SHAMapTreeNode* leaf = walkToPointer (id);
if (!leaf)
return no_item;
hash = leaf->getNodeHash ();
return leaf->peekItem ();
}
bool SHAMap::hasItem (uint256 const& id)
{
// does the tree have an item with this ID
ScopedReadLockType sl (mLock);
SHAMapTreeNode* leaf = walkToPointer (id);
return (leaf != nullptr);
}
bool SHAMap::delItem (uint256 const& id)
{
// delete the item with this ID
ScopedWriteLockType sl (mLock);
assert (mState != smsImmutable);
std::stack<SHAMapTreeNode::pointer> stack = getStack (id, true);
if (stack.empty ())
throw (std::runtime_error ("missing node"));
SHAMapTreeNode::pointer leaf = stack.top ();
stack.pop ();
if (!leaf || !leaf->hasItem () || (leaf->peekItem ()->getTag () != id))
return false;
SHAMapTreeNode::TNType type = leaf->getType ();
returnNode (leaf, true);
if (mTNByID.erase (*leaf) == 0)
assert (false);
uint256 prevHash;
while (!stack.empty ())
{
SHAMapTreeNode::pointer node = stack.top ();
stack.pop ();
returnNode (node, true);
assert (node->isInner ());
if (!node->setChildHash (node->selectBranch (id), prevHash))
{
assert (false);
return true;
}
if (!node->isRoot ())
{
// we may have made this a node with 1 or 0 children
int bc = node->getBranchCount ();
if (bc == 0)
{
prevHash = uint256 ();
if (!mTNByID.erase (*node))
assert (false);
}
else if (bc == 1)
{
// pull up on the thread
SHAMapItem::pointer item = onlyBelow (node.get ());
if (item)
{
returnNode (node, true);
eraseChildren (node);
node->setItem (item, type);
}
prevHash = node->getNodeHash ();
assert (prevHash.isNonZero ());
}
else
{
prevHash = node->getNodeHash ();
assert (prevHash.isNonZero ());
}
}
else assert (stack.empty ());
}
return true;
}
bool SHAMap::addGiveItem (SHAMapItem::ref item, bool isTransaction, bool hasMeta)
{
// add the specified item, does not update
uint256 tag = item->getTag ();
SHAMapTreeNode::TNType type = !isTransaction ? SHAMapTreeNode::tnACCOUNT_STATE :
(hasMeta ? SHAMapTreeNode::tnTRANSACTION_MD : SHAMapTreeNode::tnTRANSACTION_NM);
ScopedWriteLockType sl (mLock);
assert (mState != smsImmutable);
std::stack<SHAMapTreeNode::pointer> stack = getStack (tag, true);
if (stack.empty ())
throw (std::runtime_error ("missing node"));
SHAMapTreeNode::pointer node = stack.top ();
stack.pop ();
if (node->isLeaf () && (node->peekItem ()->getTag () == tag))
return false;
uint256 prevHash;
returnNode (node, true);
if (node->isInner ())
{
// easy case, we end on an inner node
int branch = node->selectBranch (tag);
assert (node->isEmptyBranch (branch));
SHAMapTreeNode::pointer newNode =
boost::make_shared<SHAMapTreeNode> (node->getChildNodeID (branch), item, type, mSeq);
if (!mTNByID.peekMap().emplace (SHAMapNode (*newNode), newNode).second)
{
WriteLog (lsFATAL, SHAMap) << "Node: " << *node;
WriteLog (lsFATAL, SHAMap) << "NewNode: " << *newNode;
dump ();
assert (false);
throw (std::runtime_error ("invalid inner node"));
}
trackNewNode (newNode);
node->setChildHash (branch, newNode->getNodeHash ());
}
else
{
// this is a leaf node that has to be made an inner node holding two items
SHAMapItem::pointer otherItem = node->peekItem ();
assert (otherItem && (tag != otherItem->getTag ()));
node->makeInner ();
int b1, b2;
while ((b1 = node->selectBranch (tag)) == (b2 = node->selectBranch (otherItem->getTag ())))
{
// we need a new inner node, since both go on same branch at this level
SHAMapTreeNode::pointer newNode =
boost::make_shared<SHAMapTreeNode> (mSeq, node->getChildNodeID (b1));
newNode->makeInner ();
if (!mTNByID.peekMap().emplace (SHAMapNode (*newNode), newNode).second)
assert (false);
stack.push (node);
node = newNode;
trackNewNode (node);
}
// we can add the two leaf nodes here
assert (node->isInner ());
SHAMapTreeNode::pointer newNode =
boost::make_shared<SHAMapTreeNode> (node->getChildNodeID (b1), item, type, mSeq);
assert (newNode->isValid () && newNode->isLeaf ());
if (!mTNByID.peekMap().emplace (SHAMapNode (*newNode), newNode).second)
assert (false);
node->setChildHash (b1, newNode->getNodeHash ()); // OPTIMIZEME hash op not needed
trackNewNode (newNode);
newNode = boost::make_shared<SHAMapTreeNode> (node->getChildNodeID (b2), otherItem, type, mSeq);
assert (newNode->isValid () && newNode->isLeaf ());
if (!mTNByID.peekMap().emplace (SHAMapNode (*newNode), newNode).second)
assert (false);
node->setChildHash (b2, newNode->getNodeHash ());
trackNewNode (newNode);
}
dirtyUp (stack, tag, node->getNodeHash ());
return true;
}
bool SHAMap::addItem (const SHAMapItem& i, bool isTransaction, bool hasMetaData)
{
return addGiveItem (boost::make_shared<SHAMapItem> (i), isTransaction, hasMetaData);
}
bool SHAMap::updateGiveItem (SHAMapItem::ref item, bool isTransaction, bool hasMeta)
{
// can't change the tag but can change the hash
uint256 tag = item->getTag ();
ScopedWriteLockType sl (mLock);
assert (mState != smsImmutable);
std::stack<SHAMapTreeNode::pointer> stack = getStack (tag, true);
if (stack.empty ())
throw (std::runtime_error ("missing node"));
SHAMapTreeNode::pointer node = stack.top ();
stack.pop ();
if (!node->isLeaf () || (node->peekItem ()->getTag () != tag))
{
assert (false);
return false;
}
returnNode (node, true);
if (!node->setItem (item, !isTransaction ? SHAMapTreeNode::tnACCOUNT_STATE :
(hasMeta ? SHAMapTreeNode::tnTRANSACTION_MD : SHAMapTreeNode::tnTRANSACTION_NM)))
{
WriteLog (lsWARNING, SHAMap) << "SHAMap setItem, no change";
return true;
}
dirtyUp (stack, tag, node->getNodeHash ());
return true;
}
void SHAMapItem::dump ()
{
WriteLog (lsINFO, SHAMap) << "SHAMapItem(" << mTag << ") " << mData.size () << "bytes";
}
SHAMapTreeNode::pointer SHAMap::fetchNodeExternal (const SHAMapNode& id, uint256 const& hash)
{
SHAMapTreeNode::pointer ret = fetchNodeExternalNT (id, hash);
if (!ret)
throw (SHAMapMissingNode (mType, id, hash));
return ret;
}
// Non-blocking version
SHAMapTreeNode* SHAMap::getNodeAsync (
const SHAMapNode& id,
uint256 const& hash,
SHAMapSyncFilter *filter,
bool& pending)
{
pending = false;
// If the node is in mTNByID, return it
SHAMapTreeNode::pointer ptr = mTNByID.retrieve (id);
if (ptr)
return ptr.get ();
// Try the tree node cache
ptr = getCache (hash, id);
if (!ptr)
{
// Try the filter
if (filter)
{
Blob nodeData;
if (filter->haveNode (id, hash, nodeData))
{
ptr = boost::make_shared <SHAMapTreeNode> (
boost::cref (id), boost::cref (nodeData), 0, snfPREFIX, boost::cref (hash), true);
filter->gotNode (true, id, hash, nodeData, ptr->getType ());
}
}
if (!ptr)
{
if (mTXMap)
{
// We don't store proposed transaction nodes in the node store
return nullptr;
}
NodeObject::pointer obj;
if (!getApp().getNodeStore().asyncFetch (hash, obj))
{ // We would have to block
pending = true;
assert (!obj);
return nullptr;
}
if (!obj)
return nullptr;
ptr = boost::make_shared <SHAMapTreeNode> (id, obj->getData(), 0, snfPREFIX, hash, true);
if (id != *ptr)
{
assert (false);
return nullptr;
}
}
// Put it in the tree node cache
canonicalize (hash, ptr);
}
if (id.isRoot ())
{
// It is legal to replace the root
mTNByID.replace (id, ptr);
root = ptr;
}
else
mTNByID.canonicalize (id, &ptr);
return ptr.get ();
}
/** Look at the cache and back end (things external to this SHAMap) to
find a tree node. Only a read lock is required because mTNByID has its
own, internal synchronization. Every thread calling this function must
get a shared pointer to the same underlying node.
This function does not throw.
*/
SHAMapTreeNode::pointer SHAMap::fetchNodeExternalNT (const SHAMapNode& id, uint256 const& hash)
{
SHAMapTreeNode::pointer ret;
if (!getApp().running ())
return ret;
// Check the cache of shared, immutable tree nodes
ret = getCache (hash, id);
if (ret)
{ // The node was found in the TreeNodeCache
assert (ret->getSeq() == 0);
assert (id == *ret);
}
else
{ // Check the back end
NodeObject::pointer obj (getApp ().getNodeStore ().fetch (hash));
if (!obj)
{
if (mLedgerSeq != 0)
{
m_missing_node_handler (mLedgerSeq);
mLedgerSeq = 0;
}
return ret;
}
try
{
// We make this node immutable (seq == 0) so that it can be shared
// CoW is needed if it is modified
ret = boost::make_shared<SHAMapTreeNode> (id, obj->getData (), 0, snfPREFIX, hash, true);
if (id != *ret)
{
WriteLog (lsFATAL, SHAMap) << "id:" << id << ", got:" << *ret;
assert (false);
return SHAMapTreeNode::pointer ();
}
if (ret->getNodeHash () != hash)
{
WriteLog (lsFATAL, SHAMap) << "Hashes don't match";
assert (false);
return SHAMapTreeNode::pointer ();
}
// Share this immutable tree node in the TreeNodeCache
canonicalize (hash, ret);
}
catch (...)
{
WriteLog (lsWARNING, SHAMap) << "fetchNodeExternal gets an invalid node: " << hash;
return SHAMapTreeNode::pointer ();
}
}
if (id.isRoot ()) // it is legal to replace an existing root
{
mTNByID.replace(id, ret);
root = ret;
}
else // Make sure other threads get pointers to the same underlying object
mTNByID.canonicalize (id, &ret);
return ret;
}
bool SHAMap::fetchRoot (uint256 const& hash, SHAMapSyncFilter* filter)
{
if (hash == root->getNodeHash ())
return true;
if (ShouldLog (lsTRACE, SHAMap))
{
if (mType == smtTRANSACTION)
WriteLog (lsTRACE, SHAMap) << "Fetch root TXN node " << hash;
else if (mType == smtSTATE)
WriteLog (lsTRACE, SHAMap) << "Fetch root STATE node " << hash;
else
WriteLog (lsTRACE, SHAMap) << "Fetch root SHAMap node " << hash;
}
SHAMapTreeNode::pointer newRoot = fetchNodeExternalNT(SHAMapNode(), hash);
if (newRoot)
{
root = newRoot;
}
else
{
Blob nodeData;
if (!filter || !filter->haveNode (SHAMapNode (), hash, nodeData))
return false;
root = boost::make_shared<SHAMapTreeNode> (SHAMapNode (), nodeData,
mSeq - 1, snfPREFIX, hash, true);
mTNByID.replace(*root, root);
filter->gotNode (true, SHAMapNode (), hash, nodeData, root->getType ());
}
assert (root->getNodeHash () == hash);
return true;
}
int SHAMap::armDirty ()
{
// begin saving dirty nodes
mDirtyNodes = boost::make_shared< ripple::unordered_map<SHAMapNode, SHAMapTreeNode::pointer, SHAMapNode_hash> > ();
return ++mSeq;
}
int SHAMap::flushDirty (NodeMap& map, int maxNodes, NodeObjectType t, std::uint32_t seq)
{
int flushed = 0;
Serializer s;
for (NodeMap::iterator it = map.begin (); it != map.end (); it = map.erase (it))
{
// tLog(t == hotTRANSACTION_NODE, lsDEBUG) << "TX node write " << it->first;
// tLog(t == hotACCOUNT_NODE, lsDEBUG) << "STATE node write " << it->first;
s.erase ();
it->second->addRaw (s, snfPREFIX);
#ifdef BEAST_DEBUG
if (s.getSHA512Half () != it->second->getNodeHash ())
{
WriteLog (lsFATAL, SHAMap) << * (it->second);
WriteLog (lsFATAL, SHAMap) << beast::lexicalCast <std::string> (s.getDataLength ());
WriteLog (lsFATAL, SHAMap) << s.getSHA512Half () << " != " << it->second->getNodeHash ();
assert (false);
}
#endif
getApp().getNodeStore ().store (t, seq, std::move (s.modData ()), it->second->getNodeHash ());
if (flushed++ >= maxNodes)
return flushed;
}
return flushed;
}
boost::shared_ptr<SHAMap::NodeMap> SHAMap::disarmDirty ()
{
// stop saving dirty nodes
ScopedWriteLockType sl (mLock);
boost::shared_ptr<NodeMap> ret;
ret.swap (mDirtyNodes);
return ret;
}
SHAMapTreeNode::pointer SHAMap::getNode (const SHAMapNode& nodeID)
{
SHAMapTreeNode::pointer node = checkCacheNode (nodeID);
if (node)
return node;
node = root;
while (nodeID != *node)
{
int branch = node->selectBranch (nodeID.getNodeID ());
assert (branch >= 0);
if ((branch < 0) || node->isEmptyBranch (branch))
return SHAMapTreeNode::pointer ();
node = getNode (node->getChildNodeID (branch), node->getChildHash (branch), false);
assert (node);
}
return node;
}
// This function returns NULL if no node with that ID exists in the map
// It throws if the map is incomplete
SHAMapTreeNode* SHAMap::getNodePointer (const SHAMapNode& nodeID)
{
SHAMapTreeNode::pointer nodeptr = mTNByID.retrieve (nodeID);
if (nodeptr)
{
SHAMapTreeNode* ret = nodeptr.get ();
ret->touch(mSeq);
return ret;
}
SHAMapTreeNode* node = root.get();
while (nodeID != *node)
{
if (node->isLeaf ())
return nullptr;
int branch = node->selectBranch (nodeID.getNodeID ());
assert (branch >= 0);
if ((branch < 0) || node->isEmptyBranch (branch))
return nullptr;
node = getNodePointer (node->getChildNodeID (branch), node->getChildHash (branch));
assert (node);
}
return node;
}
bool SHAMap::getPath (uint256 const& index, std::vector< Blob >& nodes, SHANodeFormat format)
{
// Return the path of nodes to the specified index in the specified format
// Return value: true = node present, false = node not present
ScopedReadLockType sl (mLock);
SHAMapTreeNode* inNode = root.get ();
while (!inNode->isLeaf ())
{
Serializer s;
inNode->addRaw (s, format);
nodes.push_back (s.peekData ());
int branch = inNode->selectBranch (index);
if (inNode->isEmptyBranch (branch)) // paths leads to empty branch
return false;
inNode = getNodePointer (inNode->getChildNodeID (branch), inNode->getChildHash (branch));
assert (inNode);
}
if (inNode->getTag () != index) // path leads to different leaf
return false;
// path lead to the requested leaf
Serializer s;
inNode->addRaw (s, format);
nodes.push_back (s.peekData ());
return true;
}
void SHAMap::dropCache ()
{
ScopedWriteLockType sl (mLock);
assert (mState == smsImmutable);
mTNByID.clear ();
if (root)
mTNByID.canonicalize(*root, &root);
}
void SHAMap::dropBelow (SHAMapTreeNode* d)
{
if (d->isInner ())
for (int i = 0 ; i < 16; ++i)
if (!d->isEmptyBranch (i))
mTNByID.erase (d->getChildNodeID (i));
}
void SHAMap::dump (bool hash)
{
WriteLog (lsINFO, SHAMap) << " MAP Contains";
ScopedWriteLockType sl (mLock);
for (ripple::unordered_map<SHAMapNode, SHAMapTreeNode::pointer, SHAMapNode_hash>::iterator it = mTNByID.peekMap().begin ();
it != mTNByID.peekMap().end (); ++it)
{
WriteLog (lsINFO, SHAMap) << it->second->getString ();
CondLog (hash, lsINFO, SHAMap) << it->second->getNodeHash ();
}
}
SHAMapTreeNode::pointer SHAMap::getCache (uint256 const& hash, SHAMapNode const& id)
{
SHAMapTreeNode::pointer ret = treeNodeCache.fetch (hash);
assert (!ret || !ret->getSeq());
if (ret && (*ret != id))
{
// We have the data, but with a different node ID
WriteLog (lsTRACE, SHAMap) << "ID mismatch: " << id << " != " << *ret;
ret = boost::make_shared <SHAMapTreeNode> (*ret, 0);
ret->set(id);
// Future fetches are likely to use the "new" ID
treeNodeCache.canonicalize (hash, ret, true);
assert (*ret == id);
assert (ret->getNodeHash() == hash);
}
return ret;
}
void SHAMap::canonicalize (uint256 const& hash, SHAMapTreeNode::pointer& node)
{
assert (node->getSeq() == 0);
treeNodeCache.canonicalize (hash, node);
}
//------------------------------------------------------------------------------
class SHAMap_test : public beast::unit_test::suite
{
public:
// VFALCO TODO Rename this to createFilledVector and pass an unsigned char, tidy up
//
static Blob IntToVUC (int v)
{
Blob vuc;
for (int i = 0; i < 32; ++i)
vuc.push_back (static_cast<unsigned char> (v));
return vuc;
}
void run ()
{
testcase ("add/traverse");
FullBelowCache fullBelowCache ("test.full_below",
get_seconds_clock ());
// h3 and h4 differ only in the leaf, same terminal node (level 19)
uint256 h1, h2, h3, h4, h5;
h1.SetHex ("092891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
h2.SetHex ("436ccbac3347baa1f1e53baeef1f43334da88f1f6d70d963b833afd6dfa289fe");
h3.SetHex ("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
h4.SetHex ("b92891fe4ef6cee585fdc6fda2e09eb4d386363158ec3321b8123e5a772c6ca8");
h5.SetHex ("a92891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
SHAMap sMap (smtFREE, fullBelowCache);
SHAMapItem i1 (h1, IntToVUC (1)), i2 (h2, IntToVUC (2)), i3 (h3, IntToVUC (3)), i4 (h4, IntToVUC (4)), i5 (h5, IntToVUC (5));
unexpected (!sMap.addItem (i2, true, false), "no add");
unexpected (!sMap.addItem (i1, true, false), "no add");
SHAMapItem::pointer i;
i = sMap.peekFirstItem ();
unexpected (!i || (*i != i1), "bad traverse");
i = sMap.peekNextItem (i->getTag ());
unexpected (!i || (*i != i2), "bad traverse");
i = sMap.peekNextItem (i->getTag ());
unexpected (!!i, "bad traverse");
sMap.addItem (i4, true, false);
sMap.delItem (i2.getTag ());
sMap.addItem (i3, true, false);
i = sMap.peekFirstItem ();
unexpected (!i || (*i != i1), "bad traverse");
i = sMap.peekNextItem (i->getTag ());
unexpected (!i || (*i != i3), "bad traverse");
i = sMap.peekNextItem (i->getTag ());
unexpected (!i || (*i != i4), "bad traverse");
i = sMap.peekNextItem (i->getTag ());
unexpected (!!i, "bad traverse");
testcase ("snapshot");
uint256 mapHash = sMap.getHash ();
SHAMap::pointer map2 = sMap.snapShot (false);
unexpected (sMap.getHash () != mapHash, "bad snapshot");
unexpected (map2->getHash () != mapHash, "bad snapshot");
unexpected (!sMap.delItem (sMap.peekFirstItem ()->getTag ()), "bad mod");
unexpected (sMap.getHash () == mapHash, "bad snapshot");
unexpected (map2->getHash () != mapHash, "bad snapshot");
}
};
BEAST_DEFINE_TESTSUITE(SHAMap,ripple_app,ripple);
} // ripple