Files
rippled/modules/ripple_app/ledger/ripple_LedgerEntrySet.cpp
2013-08-14 18:10:22 -07:00

1646 lines
55 KiB
C++

//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (LedgerEntrySet)
// #define META_DEBUG
// VFALCO TODO Replace this macro with a documented language constant
// NOTE Is this part of the protocol?
//
#define DIR_NODE_MAX 32
void LedgerEntrySet::init (Ledger::ref ledger, uint256 const& transactionID,
uint32 ledgerID, TransactionEngineParams params)
{
mEntries.clear ();
mLedger = ledger;
mSet.init (transactionID, ledgerID);
mParams = params;
mSeq = 0;
}
void LedgerEntrySet::clear ()
{
mEntries.clear ();
mSet.clear ();
}
LedgerEntrySet LedgerEntrySet::duplicate () const
{
return LedgerEntrySet (mLedger, mEntries, mSet, mSeq + 1);
}
void LedgerEntrySet::setTo (const LedgerEntrySet& e)
{
mLedger = e.mLedger;
mEntries = e.mEntries;
mSet = e.mSet;
mParams = e.mParams;
mSeq = e.mSeq;
}
void LedgerEntrySet::swapWith (LedgerEntrySet& e)
{
std::swap (mLedger, e.mLedger);
mEntries.swap (e.mEntries);
mSet.swap (e.mSet);
std::swap (mParams, e.mParams);
std::swap (mSeq, e.mSeq);
}
// Find an entry in the set. If it has the wrong sequence number, copy it and update the sequence number.
// This is basically: copy-on-read.
SLE::pointer LedgerEntrySet::getEntry (uint256 const& index, LedgerEntryAction& action)
{
std::map<uint256, LedgerEntrySetEntry>::iterator it = mEntries.find (index);
if (it == mEntries.end ())
{
action = taaNONE;
return SLE::pointer ();
}
if (it->second.mSeq != mSeq)
{
assert (it->second.mSeq < mSeq);
it->second.mEntry = boost::make_shared<SerializedLedgerEntry> (*it->second.mEntry);
it->second.mSeq = mSeq;
}
action = it->second.mAction;
return it->second.mEntry;
}
SLE::pointer LedgerEntrySet::entryCreate (LedgerEntryType letType, uint256 const& index)
{
assert (index.isNonZero ());
SLE::pointer sleNew = boost::make_shared<SLE> (letType, index);
entryCreate (sleNew);
return sleNew;
}
SLE::pointer LedgerEntrySet::entryCache (LedgerEntryType letType, uint256 const& index)
{
assert (mLedger);
SLE::pointer sleEntry;
if (index.isNonZero ())
{
LedgerEntryAction action;
sleEntry = getEntry (index, action);
if (!sleEntry)
{
assert (action != taaDELETE);
sleEntry = mImmutable ? mLedger->getSLEi (index) : mLedger->getSLE (index);
if (sleEntry)
entryCache (sleEntry);
}
else if (action == taaDELETE)
sleEntry.reset ();
}
return sleEntry;
}
LedgerEntryAction LedgerEntrySet::hasEntry (uint256 const& index) const
{
std::map<uint256, LedgerEntrySetEntry>::const_iterator it = mEntries.find (index);
if (it == mEntries.end ())
return taaNONE;
return it->second.mAction;
}
void LedgerEntrySet::entryCache (SLE::ref sle)
{
assert (mLedger);
assert (sle->isMutable () || mImmutable); // Don't put an immutable SLE in a mutable LES
std::map<uint256, LedgerEntrySetEntry>::iterator it = mEntries.find (sle->getIndex ());
if (it == mEntries.end ())
{
mEntries.insert (std::make_pair (sle->getIndex (), LedgerEntrySetEntry (sle, taaCACHED, mSeq)));
return;
}
switch (it->second.mAction)
{
case taaCACHED:
assert (sle == it->second.mEntry);
it->second.mSeq = mSeq;
it->second.mEntry = sle;
return;
default:
throw std::runtime_error ("Cache after modify/delete/create");
}
}
void LedgerEntrySet::entryCreate (SLE::ref sle)
{
assert (mLedger && !mImmutable);
assert (sle->isMutable ());
std::map<uint256, LedgerEntrySetEntry>::iterator it = mEntries.find (sle->getIndex ());
if (it == mEntries.end ())
{
mEntries.insert (std::make_pair (sle->getIndex (), LedgerEntrySetEntry (sle, taaCREATE, mSeq)));
return;
}
switch (it->second.mAction)
{
case taaDELETE:
WriteLog (lsDEBUG, LedgerEntrySet) << "Create after Delete = Modify";
it->second.mEntry = sle;
it->second.mAction = taaMODIFY;
it->second.mSeq = mSeq;
break;
case taaMODIFY:
throw std::runtime_error ("Create after modify");
case taaCREATE:
throw std::runtime_error ("Create after create"); // This could be made to work
case taaCACHED:
throw std::runtime_error ("Create after cache");
default:
throw std::runtime_error ("Unknown taa");
}
assert (it->second.mSeq == mSeq);
}
void LedgerEntrySet::entryModify (SLE::ref sle)
{
assert (sle->isMutable () && !mImmutable);
assert (mLedger);
std::map<uint256, LedgerEntrySetEntry>::iterator it = mEntries.find (sle->getIndex ());
if (it == mEntries.end ())
{
mEntries.insert (std::make_pair (sle->getIndex (), LedgerEntrySetEntry (sle, taaMODIFY, mSeq)));
return;
}
assert (it->second.mSeq == mSeq);
assert (it->second.mEntry == sle);
switch (it->second.mAction)
{
case taaCACHED:
it->second.mAction = taaMODIFY;
fallthru ();
case taaCREATE:
case taaMODIFY:
it->second.mSeq = mSeq;
it->second.mEntry = sle;
break;
case taaDELETE:
throw std::runtime_error ("Modify after delete");
default:
throw std::runtime_error ("Unknown taa");
}
}
void LedgerEntrySet::entryDelete (SLE::ref sle)
{
assert (sle->isMutable () && !mImmutable);
assert (mLedger);
std::map<uint256, LedgerEntrySetEntry>::iterator it = mEntries.find (sle->getIndex ());
if (it == mEntries.end ())
{
assert (false); // deleting an entry not cached?
mEntries.insert (std::make_pair (sle->getIndex (), LedgerEntrySetEntry (sle, taaDELETE, mSeq)));
return;
}
assert (it->second.mSeq == mSeq);
assert (it->second.mEntry == sle);
switch (it->second.mAction)
{
case taaCACHED:
case taaMODIFY:
it->second.mSeq = mSeq;
it->second.mEntry = sle;
it->second.mAction = taaDELETE;
break;
case taaCREATE:
mEntries.erase (it);
break;
case taaDELETE:
break;
default:
throw std::runtime_error ("Unknown taa");
}
}
bool LedgerEntrySet::hasChanges ()
{
typedef std::map<uint256, LedgerEntrySetEntry>::value_type u256_LES_pair;
BOOST_FOREACH (u256_LES_pair & it, mEntries)
if (it.second.mAction != taaCACHED)
return true;
return false;
}
bool LedgerEntrySet::intersect (const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight)
{
return true; // XXX Needs implementation
}
Json::Value LedgerEntrySet::getJson (int) const
{
Json::Value ret (Json::objectValue);
Json::Value nodes (Json::arrayValue);
for (std::map<uint256, LedgerEntrySetEntry>::const_iterator it = mEntries.begin (),
end = mEntries.end (); it != end; ++it)
{
Json::Value entry (Json::objectValue);
entry["node"] = it->first.GetHex ();
switch (it->second.mEntry->getType ())
{
case ltINVALID:
entry["type"] = "invalid";
break;
case ltACCOUNT_ROOT:
entry["type"] = "acccount_root";
break;
case ltDIR_NODE:
entry["type"] = "dir_node";
break;
case ltGENERATOR_MAP:
entry["type"] = "generator_map";
break;
case ltRIPPLE_STATE:
entry["type"] = "ripple_state";
break;
case ltNICKNAME:
entry["type"] = "nickname";
break;
case ltOFFER:
entry["type"] = "offer";
break;
default:
assert (false);
}
switch (it->second.mAction)
{
case taaCACHED:
entry["action"] = "cache";
break;
case taaMODIFY:
entry["action"] = "modify";
break;
case taaDELETE:
entry["action"] = "delete";
break;
case taaCREATE:
entry["action"] = "create";
break;
default:
assert (false);
}
nodes.append (entry);
}
ret["nodes" ] = nodes;
ret["metaData"] = mSet.getJson (0);
return ret;
}
SLE::pointer LedgerEntrySet::getForMod (uint256 const& node, Ledger::ref ledger,
boost::unordered_map<uint256, SLE::pointer>& newMods)
{
std::map<uint256, LedgerEntrySetEntry>::iterator it = mEntries.find (node);
if (it != mEntries.end ())
{
if (it->second.mAction == taaDELETE)
{
WriteLog (lsFATAL, LedgerEntrySet) << "Trying to thread to deleted node";
return SLE::pointer ();
}
if (it->second.mAction == taaCACHED)
it->second.mAction = taaMODIFY;
if (it->second.mSeq != mSeq)
{
it->second.mEntry = boost::make_shared<SerializedLedgerEntry> (*it->second.mEntry);
it->second.mSeq = mSeq;
}
return it->second.mEntry;
}
boost::unordered_map<uint256, SLE::pointer>::iterator me = newMods.find (node);
if (me != newMods.end ())
{
assert (me->second);
return me->second;
}
SLE::pointer ret = ledger->getSLE (node);
if (ret)
newMods.insert (std::make_pair (node, ret));
return ret;
}
bool LedgerEntrySet::threadTx (const RippleAddress& threadTo, Ledger::ref ledger,
boost::unordered_map<uint256, SLE::pointer>& newMods)
{
#ifdef META_DEBUG
WriteLog (lsTRACE, LedgerEntrySet) << "Thread to " << threadTo.getAccountID ();
#endif
SLE::pointer sle = getForMod (Ledger::getAccountRootIndex (threadTo.getAccountID ()), ledger, newMods);
if (!sle)
{
WriteLog (lsFATAL, LedgerEntrySet) << "Threading to non-existent account: " << threadTo.humanAccountID ();
assert (false);
return false;
}
return threadTx (sle, ledger, newMods);
}
bool LedgerEntrySet::threadTx (SLE::ref threadTo, Ledger::ref ledger,
boost::unordered_map<uint256, SLE::pointer>& newMods)
{
// node = the node that was modified/deleted/created
// threadTo = the node that needs to know
uint256 prevTxID;
uint32 prevLgrID;
if (!threadTo->thread (mSet.getTxID (), mSet.getLgrSeq (), prevTxID, prevLgrID))
return false;
if (prevTxID.isZero () ||
TransactionMetaSet::thread (mSet.getAffectedNode (threadTo, sfModifiedNode), prevTxID, prevLgrID))
return true;
assert (false);
return false;
}
bool LedgerEntrySet::threadOwners (SLE::ref node, Ledger::ref ledger,
boost::unordered_map<uint256, SLE::pointer>& newMods)
{
// thread new or modified node to owner or owners
if (node->hasOneOwner ()) // thread to owner's account
{
#ifdef META_DEBUG
WriteLog (lsTRACE, LedgerEntrySet) << "Thread to single owner";
#endif
return threadTx (node->getOwner (), ledger, newMods);
}
else if (node->hasTwoOwners ()) // thread to owner's accounts
{
#ifdef META_DEBUG
WriteLog (lsTRACE, LedgerEntrySet) << "Thread to two owners";
#endif
return
threadTx (node->getFirstOwner (), ledger, newMods) &&
threadTx (node->getSecondOwner (), ledger, newMods);
}
else
return false;
}
void LedgerEntrySet::calcRawMeta (Serializer& s, TER result, uint32 index)
{
// calculate the raw meta data and return it. This must be called before the set is committed
// Entries modified only as a result of building the transaction metadata
boost::unordered_map<uint256, SLE::pointer> newMod;
typedef std::map<uint256, LedgerEntrySetEntry>::value_type u256_LES_pair;
BOOST_FOREACH (u256_LES_pair & it, mEntries)
{
SField::ptr type = &sfGeneric;
switch (it.second.mAction)
{
case taaMODIFY:
#ifdef META_DEBUG
WriteLog (lsTRACE, LedgerEntrySet) << "Modified Node " << it.first;
#endif
type = &sfModifiedNode;
break;
case taaDELETE:
#ifdef META_DEBUG
WriteLog (lsTRACE, LedgerEntrySet) << "Deleted Node " << it.first;
#endif
type = &sfDeletedNode;
break;
case taaCREATE:
#ifdef META_DEBUG
WriteLog (lsTRACE, LedgerEntrySet) << "Created Node " << it.first;
#endif
type = &sfCreatedNode;
break;
default: // ignore these
break;
}
if (type == &sfGeneric)
continue;
SLE::pointer origNode = mLedger->getSLEi (it.first);
SLE::pointer curNode = it.second.mEntry;
if ((type == &sfModifiedNode) && (*curNode == *origNode))
continue;
uint16 nodeType = curNode ? curNode->getFieldU16 (sfLedgerEntryType) : origNode->getFieldU16 (sfLedgerEntryType);
mSet.setAffectedNode (it.first, *type, nodeType);
if (type == &sfDeletedNode)
{
assert (origNode && curNode);
threadOwners (origNode, mLedger, newMod); // thread transaction to owners
STObject prevs (sfPreviousFields);
BOOST_FOREACH (const SerializedType & obj, *origNode)
{
// go through the original node for modified fields saved on modification
if (obj.getFName ().shouldMeta (SField::sMD_ChangeOrig) && !curNode->hasMatchingEntry (obj))
prevs.addObject (obj);
}
if (!prevs.empty ())
mSet.getAffectedNode (it.first).addObject (prevs);
STObject finals (sfFinalFields);
BOOST_FOREACH (const SerializedType & obj, *curNode)
{
// go through the final node for final fields
if (obj.getFName ().shouldMeta (SField::sMD_Always | SField::sMD_DeleteFinal))
finals.addObject (obj);
}
if (!finals.empty ())
mSet.getAffectedNode (it.first).addObject (finals);
}
else if (type == &sfModifiedNode)
{
assert (curNode && origNode);
if (curNode->isThreadedType ()) // thread transaction to node it modified
threadTx (curNode, mLedger, newMod);
STObject prevs (sfPreviousFields);
BOOST_FOREACH (const SerializedType & obj, *origNode)
{
// search the original node for values saved on modify
if (obj.getFName ().shouldMeta (SField::sMD_ChangeOrig) && !curNode->hasMatchingEntry (obj))
prevs.addObject (obj);
}
if (!prevs.empty ())
mSet.getAffectedNode (it.first).addObject (prevs);
STObject finals (sfFinalFields);
BOOST_FOREACH (const SerializedType & obj, *curNode)
{
// search the final node for values saved always
if (obj.getFName ().shouldMeta (SField::sMD_Always | SField::sMD_ChangeNew))
finals.addObject (obj);
}
if (!finals.empty ())
mSet.getAffectedNode (it.first).addObject (finals);
}
else if (type == &sfCreatedNode) // if created, thread to owner(s)
{
assert (curNode && !origNode);
threadOwners (curNode, mLedger, newMod);
if (curNode->isThreadedType ()) // always thread to self
threadTx (curNode, mLedger, newMod);
STObject news (sfNewFields);
BOOST_FOREACH (const SerializedType & obj, *curNode)
{
// save non-default values
if (!obj.isDefault () && obj.getFName ().shouldMeta (SField::sMD_Create | SField::sMD_Always))
news.addObject (obj);
}
if (!news.empty ())
mSet.getAffectedNode (it.first).addObject (news);
}
else assert (false);
}
// add any new modified nodes to the modification set
typedef std::map<uint256, SLE::pointer>::value_type u256_sle_pair;
BOOST_FOREACH (u256_sle_pair & it, newMod)
entryModify (it.second);
mSet.addRaw (s, result, index);
WriteLog (lsTRACE, LedgerEntrySet) << "Metadata:" << mSet.getJson (0);
}
TER LedgerEntrySet::dirCount (uint256 const& uRootIndex, uint32& uCount)
{
uint64 uNodeDir = 0;
uCount = 0;
do
{
SLE::pointer sleNode = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeDir));
if (sleNode)
{
uCount += sleNode->getFieldV256 (sfIndexes).peekValue ().size ();
uNodeDir = sleNode->getFieldU64 (sfIndexNext); // Get next node.
}
else if (uNodeDir)
{
WriteLog (lsWARNING, LedgerEntrySet) << "dirCount: no such node";
assert (false);
return tefBAD_LEDGER;
}
}
while (uNodeDir);
return tesSUCCESS;
}
// <-- uNodeDir: For deletion, present to make dirDelete efficient.
// --> uRootIndex: The index of the base of the directory. Nodes are based off of this.
// --> uLedgerIndex: Value to add to directory.
// Only append. This allow for things that watch append only structure to just monitor from the last node on ward.
// Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random.
TER LedgerEntrySet::dirAdd (
uint64& uNodeDir,
uint256 const& uRootIndex,
uint256 const& uLedgerIndex,
FUNCTION_TYPE<void (SLE::ref)> fDescriber)
{
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("dirAdd: uRootIndex=%s uLedgerIndex=%s")
% uRootIndex.ToString ()
% uLedgerIndex.ToString ());
SLE::pointer sleNode;
STVector256 svIndexes;
SLE::pointer sleRoot = entryCache (ltDIR_NODE, uRootIndex);
if (!sleRoot)
{
// No root, make it.
sleRoot = entryCreate (ltDIR_NODE, uRootIndex);
sleRoot->setFieldH256 (sfRootIndex, uRootIndex);
fDescriber (sleRoot);
sleNode = sleRoot;
uNodeDir = 0;
}
else
{
uNodeDir = sleRoot->getFieldU64 (sfIndexPrevious); // Get index to last directory node.
if (uNodeDir)
{
// Try adding to last node.
sleNode = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeDir));
assert (sleNode);
}
else
{
// Try adding to root. Didn't have a previous set to the last node.
sleNode = sleRoot;
}
svIndexes = sleNode->getFieldV256 (sfIndexes);
if (DIR_NODE_MAX != svIndexes.peekValue ().size ())
{
// Add to current node.
entryModify (sleNode);
}
// Add to new node.
else if (!++uNodeDir)
{
return tecDIR_FULL;
}
else
{
// Have old last point to new node
sleNode->setFieldU64 (sfIndexNext, uNodeDir);
entryModify (sleNode);
// Have root point to new node.
sleRoot->setFieldU64 (sfIndexPrevious, uNodeDir);
entryModify (sleRoot);
// Create the new node.
sleNode = entryCreate (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeDir));
sleNode->setFieldH256 (sfRootIndex, uRootIndex);
if (uNodeDir != 1)
sleNode->setFieldU64 (sfIndexPrevious, uNodeDir - 1);
fDescriber (sleNode);
svIndexes = STVector256 ();
}
}
svIndexes.peekValue ().push_back (uLedgerIndex); // Append entry.
sleNode->setFieldV256 (sfIndexes, svIndexes); // Save entry.
WriteLog (lsTRACE, LedgerEntrySet) << "dirAdd: creating: root: " << uRootIndex.ToString ();
WriteLog (lsTRACE, LedgerEntrySet) << "dirAdd: appending: Entry: " << uLedgerIndex.ToString ();
WriteLog (lsTRACE, LedgerEntrySet) << "dirAdd: appending: Node: " << strHex (uNodeDir);
// WriteLog (lsINFO, LedgerEntrySet) << "dirAdd: appending: PREV: " << svIndexes.peekValue()[0].ToString();
return tesSUCCESS;
}
// Ledger must be in a state for this to work.
TER LedgerEntrySet::dirDelete (
const bool bKeepRoot, // --> True, if we never completely clean up, after we overflow the root node.
const uint64& uNodeDir, // --> Node containing entry.
uint256 const& uRootIndex, // --> The index of the base of the directory. Nodes are based off of this.
uint256 const& uLedgerIndex, // --> Value to remove from directory.
const bool bStable, // --> True, not to change relative order of entries.
const bool bSoft) // --> True, uNodeDir is not hard and fast (pass uNodeDir=0).
{
uint64 uNodeCur = uNodeDir;
SLE::pointer sleNode = entryCache (ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex (uRootIndex, uNodeCur) : uRootIndex);
if (!sleNode)
{
WriteLog (lsWARNING, LedgerEntrySet)
<< boost::str (boost::format ("dirDelete: no such node: uRootIndex=%s uNodeDir=%s uLedgerIndex=%s")
% uRootIndex.ToString ()
% strHex (uNodeDir)
% uLedgerIndex.ToString ());
if (!bSoft)
{
assert (false);
return tefBAD_LEDGER;
}
else if (uNodeDir < 20)
{
// Go the extra mile. Even if node doesn't exist, try the next node.
return dirDelete (bKeepRoot, uNodeDir + 1, uRootIndex, uLedgerIndex, bStable, true);
}
else
{
return tefBAD_LEDGER;
}
}
STVector256 svIndexes = sleNode->getFieldV256 (sfIndexes);
std::vector<uint256>& vuiIndexes = svIndexes.peekValue ();
std::vector<uint256>::iterator it;
it = std::find (vuiIndexes.begin (), vuiIndexes.end (), uLedgerIndex);
if (vuiIndexes.end () == it)
{
if (!bSoft)
{
assert (false);
WriteLog (lsWARNING, LedgerEntrySet) << "dirDelete: no such entry";
return tefBAD_LEDGER;
}
else if (uNodeDir < 20)
{
// Go the extra mile. Even if entry not in node, try the next node.
return dirDelete (bKeepRoot, uNodeDir + 1, uRootIndex, uLedgerIndex, bStable, true);
}
else
{
return tefBAD_LEDGER;
}
}
// Remove the element.
if (vuiIndexes.size () > 1)
{
if (bStable)
{
vuiIndexes.erase (it);
}
else
{
*it = vuiIndexes[vuiIndexes.size () - 1];
vuiIndexes.resize (vuiIndexes.size () - 1);
}
}
else
{
vuiIndexes.clear ();
}
sleNode->setFieldV256 (sfIndexes, svIndexes);
entryModify (sleNode);
if (vuiIndexes.empty ())
{
// May be able to delete nodes.
uint64 uNodePrevious = sleNode->getFieldU64 (sfIndexPrevious);
uint64 uNodeNext = sleNode->getFieldU64 (sfIndexNext);
if (!uNodeCur)
{
// Just emptied root node.
if (!uNodePrevious)
{
// Never overflowed the root node. Delete it.
entryDelete (sleNode);
}
// Root overflowed.
else if (bKeepRoot)
{
// If root overflowed and not allowed to delete overflowed root node.
nothing ();
}
else if (uNodePrevious != uNodeNext)
{
// Have more than 2 nodes. Can't delete root node.
nothing ();
}
else
{
// Have only a root node and a last node.
SLE::pointer sleLast = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeNext));
assert (sleLast);
if (sleLast->getFieldV256 (sfIndexes).peekValue ().empty ())
{
// Both nodes are empty.
entryDelete (sleNode); // Delete root.
entryDelete (sleLast); // Delete last.
}
else
{
// Have an entry, can't delete root node.
nothing ();
}
}
}
// Just emptied a non-root node.
else if (uNodeNext)
{
// Not root and not last node. Can delete node.
SLE::pointer slePrevious = entryCache (ltDIR_NODE, uNodePrevious ? Ledger::getDirNodeIndex (uRootIndex, uNodePrevious) : uRootIndex);
assert (slePrevious);
SLE::pointer sleNext = entryCache (ltDIR_NODE, uNodeNext ? Ledger::getDirNodeIndex (uRootIndex, uNodeNext) : uRootIndex);
assert (slePrevious);
assert (sleNext);
if (!slePrevious)
{
WriteLog (lsWARNING, LedgerEntrySet) << "dirDelete: previous node is missing";
return tefBAD_LEDGER;
}
if (!sleNext)
{
WriteLog (lsWARNING, LedgerEntrySet) << "dirDelete: next node is missing";
return tefBAD_LEDGER;
}
// Fix previous to point to its new next.
slePrevious->setFieldU64 (sfIndexNext, uNodeNext);
entryModify (slePrevious);
// Fix next to point to its new previous.
sleNext->setFieldU64 (sfIndexPrevious, uNodePrevious);
entryModify (sleNext);
entryDelete(sleNode);
}
// Last node.
else if (bKeepRoot || uNodePrevious)
{
// Not allowed to delete last node as root was overflowed.
// Or, have pervious entries preventing complete delete.
nothing ();
}
else
{
// Last and only node besides the root.
SLE::pointer sleRoot = entryCache (ltDIR_NODE, uRootIndex);
assert (sleRoot);
if (sleRoot->getFieldV256 (sfIndexes).peekValue ().empty ())
{
// Both nodes are empty.
entryDelete (sleRoot); // Delete root.
entryDelete (sleNode); // Delete last.
}
else
{
// Root has an entry, can't delete.
nothing ();
}
}
}
return tesSUCCESS;
}
// Return the first entry and advance uDirEntry.
// <-- true, if had a next entry.
bool LedgerEntrySet::dirFirst (
uint256 const& uRootIndex, // --> Root of directory.
SLE::pointer& sleNode, // <-- current node
unsigned int& uDirEntry, // <-- next entry
uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero.
{
sleNode = entryCache (ltDIR_NODE, uRootIndex);
uDirEntry = 0;
assert (sleNode); // Never probe for directories.
return LedgerEntrySet::dirNext (uRootIndex, sleNode, uDirEntry, uEntryIndex);
}
// Return the current entry and advance uDirEntry.
// <-- true, if had a next entry.
bool LedgerEntrySet::dirNext (
uint256 const& uRootIndex, // --> Root of directory
SLE::pointer& sleNode, // <-> current node
unsigned int& uDirEntry, // <-> next entry
uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero.
{
STVector256 svIndexes = sleNode->getFieldV256 (sfIndexes);
std::vector<uint256>& vuiIndexes = svIndexes.peekValue ();
assert (uDirEntry <= vuiIndexes.size ());
if (uDirEntry >= vuiIndexes.size ())
{
uint64 uNodeNext = sleNode->getFieldU64 (sfIndexNext);
if (!uNodeNext)
{
uEntryIndex.zero ();
return false;
}
else
{
SLE::pointer sleNext = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeNext));
uDirEntry = 0;
if (!sleNext)
{ // This should never happen
WriteLog (lsFATAL, LedgerEntrySet) << "Corrupt directory: index:" << uRootIndex << " next:" << uNodeNext;
return false;
}
sleNode = sleNext;
return dirNext (uRootIndex, sleNode, uDirEntry, uEntryIndex);
}
}
uEntryIndex = vuiIndexes[uDirEntry++];
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("dirNext: uDirEntry=%d uEntryIndex=%s") % uDirEntry % uEntryIndex);
return true;
}
uint256 LedgerEntrySet::getNextLedgerIndex (uint256 const& uHash)
{
// find next node in ledger that isn't deleted by LES
uint256 ledgerNext = uHash;
std::map<uint256, LedgerEntrySetEntry>::const_iterator it;
do
{
ledgerNext = mLedger->getNextLedgerIndex (ledgerNext);
it = mEntries.find (ledgerNext);
}
while ((it != mEntries.end ()) && (it->second.mAction == taaDELETE));
// find next node in LES that isn't deleted
for (it = mEntries.upper_bound (uHash); it != mEntries.end (); ++it)
{
// node found in LES, node found in ledger, return earliest
if (it->second.mAction != taaDELETE)
return (ledgerNext.isNonZero () && (ledgerNext < it->first)) ? ledgerNext : it->first;
}
// nothing next in LES, return next ledger node
return ledgerNext;
}
uint256 LedgerEntrySet::getNextLedgerIndex (uint256 const& uHash, uint256 const& uEnd)
{
uint256 next = getNextLedgerIndex (uHash);
if (next > uEnd)
return uint256 ();
return next;
}
// If there is a count, adjust the owner count by iAmount. Otherwise, compute the owner count and store it.
void LedgerEntrySet::ownerCountAdjust (const uint160& uOwnerID, int iAmount, SLE::ref sleAccountRoot)
{
SLE::pointer sleHold = sleAccountRoot
? SLE::pointer ()
: entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uOwnerID));
SLE::ref sleRoot = sleAccountRoot
? sleAccountRoot
: sleHold;
const uint32 uOwnerCount = sleRoot->getFieldU32 (sfOwnerCount);
const uint32 uNew = iAmount + int (uOwnerCount) > 0
? uOwnerCount + iAmount
: 0;
if (uOwnerCount != uNew)
{
sleRoot->setFieldU32 (sfOwnerCount, uOwnerCount + iAmount);
entryModify (sleRoot);
}
}
TER LedgerEntrySet::offerDelete (SLE::ref sleOffer, uint256 const& uOfferIndex, const uint160& uOwnerID)
{
bool bOwnerNode = sleOffer->isFieldPresent (sfOwnerNode); // Detect legacy dirs.
uint64 uOwnerNode = sleOffer->getFieldU64 (sfOwnerNode);
TER terResult = dirDelete (false, uOwnerNode, Ledger::getOwnerDirIndex (uOwnerID), uOfferIndex, false, !bOwnerNode);
if (tesSUCCESS == terResult)
{
ownerCountAdjust (uOwnerID, -1);
uint256 uDirectory = sleOffer->getFieldH256 (sfBookDirectory);
uint64 uBookNode = sleOffer->getFieldU64 (sfBookNode);
// Offer delete is always hard. Always have hints.
terResult = dirDelete (false, uBookNode, uDirectory, uOfferIndex, true, true);
}
entryDelete (sleOffer);
return terResult;
}
TER LedgerEntrySet::offerDelete (uint256 const& uOfferIndex)
{
SLE::pointer sleOffer = entryCache (ltOFFER, uOfferIndex);
if (!sleOffer)
return tesSUCCESS;
const uint160 uOwnerID = sleOffer->getFieldAccount160 (sfAccount);
return offerDelete (sleOffer, uOfferIndex, uOwnerID);
}
// Returns amount owed by uToAccountID to uFromAccountID.
// <-- $owed/uCurrencyID/uToAccountID: positive: uFromAccountID holds IOUs., negative: uFromAccountID owes IOUs.
STAmount LedgerEntrySet::rippleOwed (const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID)
{
STAmount saBalance;
SLE::pointer sleRippleState = entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (uToAccountID, uFromAccountID, uCurrencyID));
if (sleRippleState)
{
saBalance = sleRippleState->getFieldAmount (sfBalance);
if (uToAccountID < uFromAccountID)
saBalance.negate ();
saBalance.setIssuer (uToAccountID);
}
else
{
saBalance.zero (uCurrencyID, uToAccountID);
WriteLog (lsDEBUG, LedgerEntrySet) << "rippleOwed: No credit line between "
<< RippleAddress::createHumanAccountID (uFromAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (uToAccountID)
<< " for "
<< STAmount::createHumanCurrency (uCurrencyID)
<< "." ;
#if 0
// We could cut off coming here if we test for no line sooner.
assert (false);
#endif
}
return saBalance;
}
// Maximum amount of IOUs uToAccountID will hold from uFromAccountID.
// <-- $amount/uCurrencyID/uToAccountID.
STAmount LedgerEntrySet::rippleLimit (const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID)
{
STAmount saLimit;
SLE::pointer sleRippleState = entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (uToAccountID, uFromAccountID, uCurrencyID));
if (sleRippleState)
{
saLimit = sleRippleState->getFieldAmount (uToAccountID < uFromAccountID ? sfLowLimit : sfHighLimit);
saLimit.setIssuer (uToAccountID);
}
else
{
saLimit.zero (uCurrencyID, uToAccountID);
#if 0
// We could cut off coming here if we test for no line sooner.
assert (false);
#endif
}
return saLimit;
}
uint32 LedgerEntrySet::rippleTransferRate (const uint160& uIssuerID)
{
SLE::pointer sleAccount = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uIssuerID));
uint32 uQuality = sleAccount && sleAccount->isFieldPresent (sfTransferRate)
? sleAccount->getFieldU32 (sfTransferRate)
: QUALITY_ONE;
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f")
% RippleAddress::createHumanAccountID (uIssuerID)
% !!sleAccount
% (uQuality / 1000000000.0));
return uQuality;
}
uint32 LedgerEntrySet::rippleTransferRate (const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID)
{
return uSenderID == uIssuerID || uReceiverID == uIssuerID
? QUALITY_ONE
: rippleTransferRate (uIssuerID);
}
// XXX Might not need this, might store in nodes on calc reverse.
uint32 LedgerEntrySet::rippleQualityIn (const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, SField::ref sfLow, SField::ref sfHigh)
{
uint32 uQuality = QUALITY_ONE;
SLE::pointer sleRippleState;
if (uToAccountID == uFromAccountID)
{
nothing ();
}
else
{
sleRippleState = entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (uToAccountID, uFromAccountID, uCurrencyID));
if (sleRippleState)
{
SField::ref sfField = uToAccountID < uFromAccountID ? sfLow : sfHigh;
uQuality = sleRippleState->isFieldPresent (sfField)
? sleRippleState->getFieldU32 (sfField)
: QUALITY_ONE;
if (!uQuality)
uQuality = 1; // Avoid divide by zero.
}
else
{
// XXX Ideally, catch no before this. So we can assert to be stricter.
uQuality = QUALITY_ONE;
}
}
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("rippleQuality: %s uToAccountID=%s uFromAccountID=%s uCurrencyID=%s bLine=%d uQuality=%f")
% (sfLow == sfLowQualityIn ? "in" : "out")
% RippleAddress::createHumanAccountID (uToAccountID)
% RippleAddress::createHumanAccountID (uFromAccountID)
% STAmount::createHumanCurrency (uCurrencyID)
% !!sleRippleState
% (uQuality / 1000000000.0));
// assert(uToAccountID == uFromAccountID || !!sleRippleState);
return uQuality;
}
// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative.
// <-- IOU's uAccountID has of uIssuerID.
STAmount LedgerEntrySet::rippleHolds (const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID)
{
STAmount saBalance;
SLE::pointer sleRippleState = entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (uAccountID, uIssuerID, uCurrencyID));
if (!sleRippleState)
{
saBalance.zero (uCurrencyID, uIssuerID);
}
else if (uAccountID > uIssuerID)
{
saBalance = sleRippleState->getFieldAmount (sfBalance);
saBalance.negate (); // Put balance in uAccountID terms.
saBalance.setIssuer (uIssuerID);
}
else
{
saBalance = sleRippleState->getFieldAmount (sfBalance);
saBalance.setIssuer (uIssuerID);
}
return saBalance;
}
// Returns the amount an account can spend without going into debt.
//
// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative.
STAmount LedgerEntrySet::accountHolds (const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID)
{
STAmount saAmount;
if (!uCurrencyID)
{
SLE::pointer sleAccount = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uAccountID));
uint64 uReserve = mLedger->getReserve (sleAccount->getFieldU32 (sfOwnerCount));
STAmount saBalance = sleAccount->getFieldAmount (sfBalance);
if (saBalance < uReserve)
{
saAmount.zero ();
}
else
{
saAmount = saBalance - uReserve;
}
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("accountHolds: uAccountID=%s saAmount=%s saBalance=%s uReserve=%d")
% RippleAddress::createHumanAccountID (uAccountID)
% saAmount.getFullText ()
% saBalance.getFullText ()
% uReserve);
}
else
{
saAmount = rippleHolds (uAccountID, uCurrencyID, uIssuerID);
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("accountHolds: uAccountID=%s saAmount=%s")
% RippleAddress::createHumanAccountID (uAccountID)
% saAmount.getFullText ());
}
return saAmount;
}
// Returns the funds available for uAccountID for a currency/issuer.
// Use when you need a default for rippling uAccountID's currency.
// XXX Should take into account quality?
// --> saDefault/currency/issuer
// <-- saFunds: Funds available. May be negative.
// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault.
STAmount LedgerEntrySet::accountFunds (const uint160& uAccountID, const STAmount& saDefault)
{
STAmount saFunds;
if (!saDefault.isNative () && saDefault.getIssuer () == uAccountID)
{
saFunds = saDefault;
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("accountFunds: uAccountID=%s saDefault=%s SELF-FUNDED")
% RippleAddress::createHumanAccountID (uAccountID)
% saDefault.getFullText ());
}
else
{
saFunds = accountHolds (uAccountID, saDefault.getCurrency (), saDefault.getIssuer ());
WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("accountFunds: uAccountID=%s saDefault=%s saFunds=%s")
% RippleAddress::createHumanAccountID (uAccountID)
% saDefault.getFullText ()
% saFunds.getFullText ());
}
return saFunds;
}
// Calculate transit fee.
STAmount LedgerEntrySet::rippleTransferFee (const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount)
{
if (uSenderID != uIssuerID && uReceiverID != uIssuerID)
{
uint32 uTransitRate = rippleTransferRate (uIssuerID);
if (QUALITY_ONE != uTransitRate)
{
STAmount saTransitRate (CURRENCY_ONE, ACCOUNT_ONE, static_cast<uint64> (uTransitRate), -9);
STAmount saTransferTotal = STAmount::multiply (saAmount, saTransitRate, saAmount.getCurrency (), saAmount.getIssuer ());
STAmount saTransferFee = saTransferTotal - saAmount;
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("rippleTransferFee: saTransferFee=%s")
% saTransferFee.getFullText ());
return saTransferFee;
}
}
return STAmount (saAmount.getCurrency (), saAmount.getIssuer ());
}
TER LedgerEntrySet::trustCreate (
const bool bSrcHigh,
const uint160& uSrcAccountID,
const uint160& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
const bool bAuth, // --> authorize account.
const STAmount& saBalance, // --> balance of account being set. Issuer should be ACCOUNT_ONE
const STAmount& saLimit, // --> limit for account being set. Issuer should be the account being set.
const uint32 uQualityIn,
const uint32 uQualityOut)
{
const uint160& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID;
const uint160& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID;
SLE::pointer sleRippleState = entryCreate (ltRIPPLE_STATE, uIndex);
uint64 uLowNode;
uint64 uHighNode;
TER terResult = dirAdd (
uLowNode,
Ledger::getOwnerDirIndex (uLowAccountID),
sleRippleState->getIndex (),
BIND_TYPE (&Ledger::ownerDirDescriber, P_1, uLowAccountID));
if (tesSUCCESS == terResult)
{
terResult = dirAdd (
uHighNode,
Ledger::getOwnerDirIndex (uHighAccountID),
sleRippleState->getIndex (),
BIND_TYPE (&Ledger::ownerDirDescriber, P_1, uHighAccountID));
}
if (tesSUCCESS == terResult)
{
const bool bSetDst = saLimit.getIssuer () == uDstAccountID;
const bool bSetHigh = bSrcHigh ^ bSetDst;
sleRippleState->setFieldU64 (sfLowNode, uLowNode); // Remember deletion hints.
sleRippleState->setFieldU64 (sfHighNode, uHighNode);
sleRippleState->setFieldAmount (!bSetHigh ? sfLowLimit : sfHighLimit, saLimit);
sleRippleState->setFieldAmount ( bSetHigh ? sfLowLimit : sfHighLimit, STAmount (saBalance.getCurrency (), bSetDst ? uSrcAccountID : uDstAccountID));
if (uQualityIn)
sleRippleState->setFieldU32 (!bSetHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn);
if (uQualityOut)
sleRippleState->setFieldU32 (!bSetHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut);
uint32 uFlags = !bSetHigh ? lsfLowReserve : lsfHighReserve;
if (bAuth)
{
uFlags |= (!bSetHigh ? lsfLowAuth : lsfHighAuth);
}
sleRippleState->setFieldU32 (sfFlags, uFlags);
ownerCountAdjust (!bSetDst ? uSrcAccountID : uDstAccountID, 1, sleAccount);
sleRippleState->setFieldAmount (sfBalance, bSetHigh ? -saBalance : saBalance); // ONLY: Create ripple balance.
}
return terResult;
}
TER LedgerEntrySet::trustDelete (SLE::ref sleRippleState, const uint160& uLowAccountID, const uint160& uHighAccountID)
{
bool bLowNode = sleRippleState->isFieldPresent (sfLowNode); // Detect legacy dirs.
bool bHighNode = sleRippleState->isFieldPresent (sfHighNode);
uint64 uLowNode = sleRippleState->getFieldU64 (sfLowNode);
uint64 uHighNode = sleRippleState->getFieldU64 (sfHighNode);
TER terResult;
WriteLog (lsTRACE, LedgerEntrySet) << "trustDelete: Deleting ripple line: low";
terResult = dirDelete (false, uLowNode, Ledger::getOwnerDirIndex (uLowAccountID), sleRippleState->getIndex (), false, !bLowNode);
if (tesSUCCESS == terResult)
{
WriteLog (lsTRACE, LedgerEntrySet) << "trustDelete: Deleting ripple line: high";
terResult = dirDelete (false, uHighNode, Ledger::getOwnerDirIndex (uHighAccountID), sleRippleState->getIndex (), false, !bHighNode);
}
WriteLog (lsTRACE, LedgerEntrySet) << "trustDelete: Deleting ripple line: state";
entryDelete (sleRippleState);
return terResult;
}
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line of needed.
// --> bCheckIssuer : normally require issuer to be involved.
TER LedgerEntrySet::rippleCredit (const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer)
{
uint160 uIssuerID = saAmount.getIssuer ();
uint160 uCurrencyID = saAmount.getCurrency ();
// Make sure issuer is involved.
assert (!bCheckIssuer || uSenderID == uIssuerID || uReceiverID == uIssuerID);
// Disallow sending to self.
assert (uSenderID != uReceiverID);
bool bSenderHigh = uSenderID > uReceiverID;
uint256 uIndex = Ledger::getRippleStateIndex (uSenderID, uReceiverID, saAmount.getCurrency ());
SLE::pointer sleRippleState = entryCache (ltRIPPLE_STATE, uIndex);
TER terResult;
assert (!!uSenderID && uSenderID != ACCOUNT_ONE);
assert (!!uReceiverID && uReceiverID != ACCOUNT_ONE);
if (!sleRippleState)
{
STAmount saReceiverLimit = STAmount (uCurrencyID, uReceiverID);
STAmount saBalance = saAmount;
saBalance.setIssuer (ACCOUNT_ONE);
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("rippleCredit: create line: %s --> %s : %s")
% RippleAddress::createHumanAccountID (uSenderID)
% RippleAddress::createHumanAccountID (uReceiverID)
% saAmount.getFullText ());
terResult = trustCreate (
bSenderHigh,
uSenderID,
uReceiverID,
uIndex,
entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uReceiverID)),
false,
saBalance,
saReceiverLimit);
}
else
{
STAmount saBalance = sleRippleState->getFieldAmount (sfBalance);
if (bSenderHigh)
saBalance.negate (); // Put balance in sender terms.
STAmount saBefore = saBalance;
saBalance -= saAmount;
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("rippleCredit: %s --> %s : before=%s amount=%s after=%s")
% RippleAddress::createHumanAccountID (uSenderID)
% RippleAddress::createHumanAccountID (uReceiverID)
% saBefore.getFullText ()
% saAmount.getFullText ()
% saBalance.getFullText ());
bool bDelete = false;
uint32 uFlags;
// YYY Could skip this if rippling in reverse.
if (saBefore.isPositive () // Sender balance was positive.
&& !saBalance.isPositive () // Sender is zero or negative.
&& isSetBit ((uFlags = sleRippleState->getFieldU32 (sfFlags)), !bSenderHigh ? lsfLowReserve : lsfHighReserve) // Sender reserve is set.
&& !sleRippleState->getFieldAmount (!bSenderHigh ? sfLowLimit : sfHighLimit) // Sender trust limit is 0.
&& !sleRippleState->getFieldU32 (!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) // Sender quality in is 0.
&& !sleRippleState->getFieldU32 (!bSenderHigh ? sfLowQualityOut : sfHighQualityOut)) // Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
SLE::pointer sleSender = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uSenderID));
ownerCountAdjust (uSenderID, -1, sleSender);
sleRippleState->setFieldU32 (sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve)); // Clear reserve flag.
bDelete = !saBalance // Balance is zero.
&& !isSetBit (uFlags, bSenderHigh ? lsfLowReserve : lsfHighReserve); // Receiver reserve is clear.
}
if (bSenderHigh)
saBalance.negate ();
// Want to reflect balance to zero even if we are deleting line.
sleRippleState->setFieldAmount (sfBalance, saBalance); // ONLY: Adjust ripple balance.
if (bDelete)
{
terResult = trustDelete (sleRippleState,
bSenderHigh ? uReceiverID : uSenderID,
!bSenderHigh ? uReceiverID : uSenderID);
}
else
{
entryModify (sleRippleState);
terResult = tesSUCCESS;
}
}
return terResult;
}
// Send regardless of limits.
// --> saAmount: Amount/currency/issuer to deliver to reciever.
// <-- saActual: Amount actually cost. Sender pay's fees.
TER LedgerEntrySet::rippleSend (const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, STAmount& saActual)
{
const uint160 uIssuerID = saAmount.getIssuer ();
TER terResult;
assert (!!uSenderID && !!uReceiverID);
assert (uSenderID != uReceiverID);
if (uSenderID == uIssuerID || uReceiverID == uIssuerID || uIssuerID == ACCOUNT_ONE)
{
// Direct send: redeeming IOUs and/or sending own IOUs.
terResult = rippleCredit (uSenderID, uReceiverID, saAmount, false);
saActual = saAmount;
terResult = tesSUCCESS;
}
else
{
// Sending 3rd party IOUs: transit.
STAmount saTransitFee = rippleTransferFee (uSenderID, uReceiverID, uIssuerID, saAmount);
saActual = !saTransitFee ? saAmount : saAmount + saTransitFee;
saActual.setIssuer (uIssuerID); // XXX Make sure this done in + above.
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("rippleSend> %s -- > %s : deliver=%s fee=%s cost=%s")
% RippleAddress::createHumanAccountID (uSenderID)
% RippleAddress::createHumanAccountID (uReceiverID)
% saAmount.getFullText ()
% saTransitFee.getFullText ()
% saActual.getFullText ());
terResult = rippleCredit (uIssuerID, uReceiverID, saAmount);
if (tesSUCCESS == terResult)
terResult = rippleCredit (uSenderID, uIssuerID, saActual);
}
return terResult;
}
TER LedgerEntrySet::accountSend (const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount)
{
TER terResult = tesSUCCESS;
assert (!saAmount.isNegative ());
if (!saAmount || (uSenderID == uReceiverID))
{
nothing ();
}
else if (saAmount.isNative ())
{
// XRP send which does not check reserve and can do pure adjustment.
SLE::pointer sleSender = !!uSenderID
? entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uSenderID))
: SLE::pointer ();
SLE::pointer sleReceiver = !!uReceiverID
? entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uReceiverID))
: SLE::pointer ();
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("accountSend> %s (%s) -> %s (%s) : %s")
% RippleAddress::createHumanAccountID (uSenderID)
% (sleSender ? (sleSender->getFieldAmount (sfBalance)).getFullText () : "-")
% RippleAddress::createHumanAccountID (uReceiverID)
% (sleReceiver ? (sleReceiver->getFieldAmount (sfBalance)).getFullText () : "-")
% saAmount.getFullText ());
if (sleSender)
{
if (sleSender->getFieldAmount (sfBalance) < saAmount)
{
terResult = isSetBit (mParams, tapOPEN_LEDGER) ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
else
{
sleSender->setFieldAmount (sfBalance, sleSender->getFieldAmount (sfBalance) - saAmount); // Decrement XRP balance.
entryModify (sleSender);
}
}
if (tesSUCCESS == terResult && sleReceiver)
{
sleReceiver->setFieldAmount (sfBalance, sleReceiver->getFieldAmount (sfBalance) + saAmount); // Increment XRP balance.
entryModify (sleReceiver);
}
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("accountSend< %s (%s) -> %s (%s) : %s")
% RippleAddress::createHumanAccountID (uSenderID)
% (sleSender ? (sleSender->getFieldAmount (sfBalance)).getFullText () : "-")
% RippleAddress::createHumanAccountID (uReceiverID)
% (sleReceiver ? (sleReceiver->getFieldAmount (sfBalance)).getFullText () : "-")
% saAmount.getFullText ());
}
else
{
STAmount saActual;
WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("accountSend: %s -> %s : %s")
% RippleAddress::createHumanAccountID (uSenderID)
% RippleAddress::createHumanAccountID (uReceiverID)
% saAmount.getFullText ());
terResult = rippleSend (uSenderID, uReceiverID, saAmount, saActual);
}
return terResult;
}
// vim:ts=4