Tidy up TxFormat and create TxFormats, TxFlags

This commit is contained in:
Vinnie Falco
2013-06-15 07:18:14 -07:00
parent 17e370918b
commit 8aab3645cb
56 changed files with 1883 additions and 1599 deletions

View File

@@ -52,14 +52,15 @@ TER AccountSetTransactor::doApply ()
// RequireDestTag
//
if ((tfRequireDestTag | tfOptionalDestTag) == (uTxFlags & (tfRequireDestTag | tfOptionalDestTag)))
// VFALCO TODO Make a function bool areBothFlagsSet (uint value, uint flag1, uint flag2)
if ((TxFlag::requireDestTag | tfOptionalDestTag) == (uTxFlags & (TxFlag::requireDestTag | tfOptionalDestTag)))
{
WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Malformed transaction: Contradictory flags set.";
return temINVALID_FLAG;
}
if ((uTxFlags & tfRequireDestTag) && !isSetBit (uFlagsIn, lsfRequireDestTag))
if ((uTxFlags & TxFlag::requireDestTag) && !isSetBit (uFlagsIn, lsfRequireDestTag))
{
WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Set lsfRequireDestTag.";

View File

@@ -1,40 +0,0 @@
#ifndef __HASHPREFIXES__
#define __HASHPREFIXES__
// TXN - Hash of transaction plus signature to give transaction ID
const uint32 sHP_TransactionID = 0x54584E00;
// TND - Hash of transaction plus metadata
const uint32 sHP_TransactionNode = 0x534E4400;
// MLN - Hash of account state
const uint32 sHP_LeafNode = 0x4D4C4E00;
// MIN - Hash of inner node in tree
const uint32 sHP_InnerNode = 0x4D494E00;
// LGR - Hash of ledger master data for signing
const uint32 sHP_Ledger = 0x4C575200;
// STX - Hash of inner transaction to sign
const uint32 sHP_TransactionSign = 0x53545800;
// VAL - Hash of validation for signing
const uint32 sHP_Validation = 0x56414C00;
// PRP - Hash of proposal for signing
const uint32 sHP_Proposal = 0x50525000;
// stx - TESTNET Hash of inner transaction to sign
const uint32 sHP_TestNetTransactionSign = 0x73747800;
// val - TESTNET Hash of validation for signing
const uint32 sHP_TestNetValidation = 0x76616C00;
// prp - TESTNET Hash of proposal for signing
const uint32 sHP_TestNetProposal = 0x70727000;
#endif
// vim:ts=4

View File

@@ -192,7 +192,7 @@ void Ledger::updateHash ()
}
Serializer s (118);
s.add32 (sHP_Ledger);
s.add32 (HashPrefix::ledgerMaster);
addRaw (s);
mHash = s.getSHA512Half ();
mValidHash = true;
@@ -526,7 +526,7 @@ void Ledger::saveAcceptedLedger (Job&, bool fromConsensus)
// Save the ledger header in the hashed object store
Serializer s (128);
s.add32 (sHP_Ledger);
s.add32 (HashPrefix::ledgerMaster);
addRaw (s);
theApp->getHashedObjectStore ().store (hotLEDGER, mLedgerSeq, s.peekData (), mHash);

View File

@@ -2172,7 +2172,7 @@ void NetworkOPs::makeFetchPack (Job&, boost::weak_ptr<Peer> wPeer,
ripple::TMIndexedObject& newObj = *reply.add_objects ();
newObj.set_hash (wantLedger->getHash ().begin (), 256 / 8);
Serializer s (256);
s.add32 (sHP_Ledger);
s.add32 (HashPrefix::ledgerMaster);
wantLedger->addRaw (s);
newObj.set_data (s.getDataPtr (), s.getLength ());
newObj.set_ledgerseq (lSeq);

View File

@@ -2,15 +2,6 @@
// Carries out the RPC.
//
#include "Pathfinder.h"
#include "RPCHandler.h"
#include "RPCSub.h"
#include "Wallet.h"
#include "RippleCalc.h"
#include "RPCErr.h"
#include "NicknameState.h"
#include "Offer.h"
SETUP_LOG (RPCHandler)
static const int rpcCOST_DEFAULT = 10;
@@ -182,7 +173,7 @@ Json::Value RPCHandler::transactionSign (Json::Value jvRequest, bool bSubmit, Sc
Ledger::pointer lSnapshot = mNetOps->getCurrentSnapshot ();
{
bool bValid;
RLCache::pointer cache = boost::make_shared<RLCache> (lSnapshot);
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (lSnapshot);
Pathfinder pf (cache, raSrcAddressID, dstAccountID,
saSendMax.getCurrency (), saSendMax.getIssuer (), saSend, bValid);
@@ -1517,7 +1508,7 @@ Json::Value RPCHandler::doRipplePathFind (Json::Value jvRequest, int& cost, Scop
jvResult["destination_account"] = raDst.humanAccountID ();
Json::Value jvArray (Json::arrayValue);
RLCache::pointer cache = boost::make_shared<RLCache> (lSnapShot);
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (lSnapShot);
for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i)
{

View File

@@ -3,7 +3,6 @@
#include "HTTPRequest.h"
#include "RPCHandler.h"
#include "LoadManager.h"
class RPCServer : public boost::enable_shared_from_this<RPCServer>
{

View File

@@ -1,236 +0,0 @@
#ifndef __RIPPLE_CALC__
#define __RIPPLE_CALC__
#include <boost/unordered_set.hpp>
#include <boost/tuple/tuple.hpp>
#include "LedgerEntrySet.h"
// VFALCO TODO move this to a separate file
class PaymentNode
{
public:
bool operator== (const PaymentNode& pnOther) const;
Json::Value getJson () const;
private:
// VFALCO TODO remove the need for friend declaration
friend class RippleCalc;
friend class PathState;
uint16 uFlags; // --> From path.
uint160 uAccountID; // --> Accounts: Recieving/sending account.
uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send.
// --- For offer's next has currency out.
uint160 uIssuerID; // --> Currency's issuer
STAmount saTransferRate; // Transfer rate for uIssuerID.
// Computed by Reverse.
STAmount saRevRedeem; // <-- Amount to redeem to next.
STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs.
// Issue isn't used by offers.
STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee.
// Computed by forward.
STAmount saFwdRedeem; // <-- Amount node will redeem to next.
STAmount saFwdIssue; // <-- Amount node will issue to next.
// Issue isn't used by offers.
STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee.
// For offers:
STAmount saRateMax;
// Directory
uint256 uDirectTip; // Current directory.
uint256 uDirectEnd; // Next order book.
bool bDirectAdvance; // Need to advance directory.
SLE::pointer sleDirectDir;
STAmount saOfrRate; // For correct ratio.
// Node
bool bEntryAdvance; // Need to advance entry.
unsigned int uEntry;
uint256 uOfferIndex;
SLE::pointer sleOffer;
uint160 uOfrOwnerID;
bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets.
STAmount saOfferFunds;
STAmount saTakerPays;
STAmount saTakerGets;
};
// account id, currency id, issuer id :: node
typedef boost::tuple<uint160, uint160, uint160> aciSource;
typedef boost::unordered_map<aciSource, unsigned int> curIssuerNode; // Map of currency, issuer to node index.
typedef boost::unordered_map<aciSource, unsigned int>::const_iterator curIssuerNodeConstIterator;
extern std::size_t hash_value (const aciSource& asValue);
// Holds a path state under incremental application.
class PathState
{
public:
typedef boost::shared_ptr<PathState> pointer;
typedef const boost::shared_ptr<PathState>& ref;
TER terStatus;
std::vector<PaymentNode> vpnNodes;
// When processing, don't want to complicate directory walking with deletion.
std::vector<uint256> vUnfundedBecame; // Offers that became unfunded or were completely consumed.
// First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be
// used there.
curIssuerNode umForward; // Map of currency, issuer to node index.
// First time working in reverse a funding source was used.
// Source may only be used there if not mentioned by an account.
curIssuerNode umReverse; // Map of currency, issuer to node index.
LedgerEntrySet lesEntries;
int mIndex; // Index/rank amoung siblings.
uint64 uQuality; // 0 = no quality/liquity left.
const STAmount& saInReq; // --> Max amount to spend by sender.
STAmount saInAct; // --> Amount spent by sender so far.
STAmount saInPass; // <-- Amount spent by sender.
const STAmount& saOutReq; // --> Amount to send.
STAmount saOutAct; // --> Amount actually sent so far.
STAmount saOutPass; // <-- Amount actually sent.
bool bConsumed; // If true, use consumes full liquidity. False, may or may not.
PathState* setIndex (const int iIndex)
{
mIndex = iIndex;
return this;
}
int getIndex ()
{
return mIndex;
};
PathState (
const STAmount& saSend,
const STAmount& saSendMax
) : saInReq (saSendMax), saOutReq (saSend)
{
;
}
PathState (const PathState& psSrc, bool bUnused)
: saInReq (psSrc.saInReq), saOutReq (psSrc.saOutReq)
{
;
}
void setExpanded (
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID
);
void setCanonical (
const PathState& psExpanded
);
Json::Value getJson () const;
#if 0
static PathState::pointer createCanonical (
PathState& ref pspExpanded
)
{
PathState::pointer pspNew = boost::make_shared<PathState> (pspExpanded->saOutAct, pspExpanded->saInAct);
pspNew->setCanonical (pspExpanded);
return pspNew;
}
#endif
static bool lessPriority (PathState& lhs, PathState& rhs);
private:
TER pushNode (const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
TER pushImply (const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
};
class RippleCalc
{
public:
// First time working in reverse a funding source was mentioned. Source may only be used there.
curIssuerNode mumSource; // Map of currency, issuer to node index.
// If the transaction fails to meet some constraint, still need to delete unfunded offers.
boost::unordered_set<uint256> musUnfundedFound; // Offers that were found unfunded.
void pathNext (PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNode (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAdvance (const unsigned int uNode, PathState& psCur, const bool bMultiQuality, const bool bReverse);
TER calcNodeDeliverRev (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uOutAccountID,
const STAmount& saOutReq,
STAmount& saOutAct);
TER calcNodeDeliverFwd (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uInAccountID,
const STAmount& saInReq,
STAmount& saInAct,
STAmount& saInFees);
void calcNodeRipple (const uint32 uQualityIn, const uint32 uQualityOut,
const STAmount& saPrvReq, const STAmount& saCurReq,
STAmount& saPrvAct, STAmount& saCurAct,
uint64& uRateMax);
RippleCalc (LedgerEntrySet& lesNodes, const bool bOpenLedger)
: lesActive (lesNodes), mOpenLedger (bOpenLedger)
{
;
}
static TER rippleCalc (
LedgerEntrySet& lesActive,
STAmount& saMaxAmountAct,
STAmount& saDstAmountAct,
std::vector<PathState::pointer>& vpsExpanded,
const STAmount& saDstAmountReq,
const STAmount& saMaxAmountReq,
const uint160& uDstAccountID,
const uint160& uSrcAccountID,
const STPathSet& spsPaths,
const bool bPartialPayment,
const bool bLimitQuality,
const bool bNoRippleDirect,
const bool bStandAlone, // --> True, not to affect accounts.
const bool bOpenLedger = true // --> What kind of errors to return.
);
static void setCanonical (STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded, bool bKeepDefault);
protected:
LedgerEntrySet& lesActive;
bool mOpenLedger;
};
#endif
// vim:ts=4

View File

@@ -35,8 +35,10 @@ public:
virtual AccountItem::pointer makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry) = 0;
// VFALCO TODO Make this const and change derived classes
virtual LedgerEntryType getType () = 0;
// VFALCO TODO Document the int parameter
virtual Json::Value getJson (int) = 0;
SerializedLedgerEntry::pointer getSLE ()

View File

@@ -1,8 +1,7 @@
// VFALCO TODO Wrap this up in something neater. Replace NULL with nullptr
IApplication* theApp = NULL;
class Application;
SETUP_LOG (Application)

View File

@@ -101,9 +101,18 @@ void Config::setup (const std::string& strConf, bool bTestNet, bool bQuiet)
% VALIDATORS_FILE_NAME);
VALIDATORS_URI = boost::str (boost::format ("/%s") % VALIDATORS_BASE);
SIGN_TRANSACTION = TESTNET ? sHP_TestNetTransactionSign : sHP_TransactionSign;
SIGN_VALIDATION = TESTNET ? sHP_TestNetValidation : sHP_Validation;
SIGN_PROPOSAL = TESTNET ? sHP_TestNetProposal : sHP_Proposal;
if (TESTNET)
{
SIGN_TRANSACTION = HashPrefix::txSignTestnet;
SIGN_VALIDATION = HashPrefix::validationTestnet;
SIGN_PROPOSAL = HashPrefix::proposalTestnet;
}
else
{
SIGN_TRANSACTION = HashPrefix::txSign;
SIGN_VALIDATION = HashPrefix::validation;
SIGN_PROPOSAL = HashPrefix::proposal;
}
if (TESTNET)
Base58::setCurrentAlphabet (Base58::getTestnetAlphabet ());

View File

@@ -108,4 +108,3 @@ public:
extern IApplication* theApp;
#endif
// vim:ts=4

View File

@@ -16,11 +16,17 @@ public:
void shutdown ();
void setThreadCount (int c = 0);
// VFALCO TODO Rename these to newLoadEventMeasurement or something similar
// since they create the object.
//
LoadEvent::pointer getLoadEvent (JobType t, const std::string& name)
{
return boost::make_shared<LoadEvent> (boost::ref (mJobLoads[t]), name, true);
}
// VFALCO TODO Why do we need two versions, one which returns a shared
// pointer and the other which returns an autoptr?
//
LoadEvent::autoptr getLoadEventAP (JobType t, const std::string& name)
{
return LoadEvent::autoptr (new LoadEvent (mJobLoads[t], name, true));

View File

@@ -643,7 +643,7 @@ bool LedgerAcquire::takeBase (const std::string& data) // data must not have has
mHaveBase = true;
Serializer s (data.size () + 4);
s.add32 (sHP_Ledger);
s.add32 (HashPrefix::ledgerMaster);
s.addRaw (data);
theApp->getHashedObjectStore ().store (hotLEDGER, mLedger->getLedgerSeq (), s.peekData (), mHash);

View File

@@ -3,14 +3,25 @@
class LoadMonitor;
// VFALCO NOTE What is the difference between a LoadEvent and a LoadMonitor?
// VFALCO TODO Rename LoadEvent to LoadMonitor::Event
//
// This looks like a scoped elapsed time measuring class
//
class LoadEvent
{
public:
typedef boost::shared_ptr<LoadEvent> pointer;
typedef UPTR_T<LoadEvent> autoptr;
// VFALCO NOTE Why are these shared pointers? Wouldn't there be a
// piece of lifetime-managed calling code that can simply own
// the object?
//
// Why both kinds of containers?
//
typedef boost::shared_ptr <LoadEvent> pointer;
typedef UPTR_T <LoadEvent> autoptr;
public:
// VFALCO TODO remove the dependency on LoadMonitor.
// VFALCO TODO remove the dependency on LoadMonitor. Is that possible?
LoadEvent (LoadMonitor& monitor,
const std::string& name,
bool shouldStart);

View File

@@ -11,19 +11,18 @@ LoadManager::LoadManager (int creditRate, int creditLimit, int debitWarn, int de
, mDeadLock (0)
, mCosts (LT_MAX)
{
addLoadCost (LoadCost (LT_InvalidRequest, -10, LC_CPU | LC_Network));
addLoadCost (LoadCost (LT_RequestNoReply, -1, LC_CPU | LC_Disk));
addLoadCost (LoadCost (LT_InvalidSignature, -100, LC_CPU));
addLoadCost (LoadCost (LT_UnwantedData, -5, LC_CPU | LC_Network));
addLoadCost (LoadCost (LT_BadData, -20, LC_CPU));
addCost (Cost (LT_InvalidRequest, -10, LC_CPU | LC_Network));
addCost (Cost (LT_RequestNoReply, -1, LC_CPU | LC_Disk));
addCost (Cost (LT_InvalidSignature, -100, LC_CPU));
addCost (Cost (LT_UnwantedData, -5, LC_CPU | LC_Network));
addCost (Cost (LT_BadData, -20, LC_CPU));
addLoadCost (LoadCost (LT_NewTrusted, -10, 0));
addLoadCost (LoadCost (LT_NewTransaction, -2, 0));
addLoadCost (LoadCost (LT_NeededData, -10, 0));
addLoadCost (LoadCost (LT_RequestData, -5, LC_Disk | LC_Network));
addLoadCost (LoadCost (LT_CheapQuery, -1, LC_CPU));
addCost (Cost (LT_NewTrusted, -10, 0));
addCost (Cost (LT_NewTransaction, -2, 0));
addCost (Cost (LT_NeededData, -10, 0));
addCost (Cost (LT_RequestData, -5, LC_Disk | LC_Network));
addCost (Cost (LT_CheapQuery, -1, LC_CPU));
}
void LoadManager::init ()
@@ -164,7 +163,7 @@ bool LoadManager::shouldCutoff (LoadSource& source) const
bool LoadManager::adjust (LoadSource& source, LoadType t) const
{
// FIXME: Scale by category
LoadCost cost = mCosts[static_cast<int> (t)];
Cost cost = mCosts[static_cast<int> (t)];
return adjust (source, cost.mCost);
}

View File

@@ -26,25 +26,12 @@ enum LoadType
};
// load categories
static const int LC_Disk = 1;
static const int LC_CPU = 2;
static const int LC_Network = 4;
class LoadCost
// VFALCO NOTE These look like bit flags, name them accordingly
enum
{
public:
LoadType mType;
int mCost;
int mCategories;
LoadCost () : mType (), mCost (0), mCategories (0)
{
;
}
LoadCost (LoadType t, int cost, int cat) : mType (t), mCost (cost), mCategories (cat)
{
;
}
LC_Disk = 1,
LC_CPU = 2,
LC_Network = 4
};
// a single endpoint that can impose load
@@ -79,24 +66,30 @@ public:
{
}
// VFALCO TODO Figure out a way to construct the LoadSource object with
// the proper name instead of renaming it later.
//
void rename (std::string const& name)
{
mName = name;
}
std::string const& getName ()
std::string const& getName () const
{
return mName;
}
bool isPrivileged () const
bool isPrivileged () const
{
return (mFlags & lsfPrivileged) != 0;
}
void setPrivileged ()
void setPrivileged ()
{
mFlags |= lsfPrivileged;
}
int getBalance () const
int getBalance () const
{
return mBalance;
}
@@ -105,16 +98,18 @@ public:
{
return mLogged;
}
void clearLogged ()
{
mLogged = false;
}
void setOutbound ()
void setOutbound ()
{
mFlags |= lsfOutbound;
}
bool isOutbound () const
bool isOutbound () const
{
return (mFlags & lsfOutbound) != 0;
}
@@ -132,43 +127,89 @@ private:
class LoadManager
{
public:
LoadManager (int creditRate = 100, int creditLimit = 500, int debitWarn = -500, int debitLimit = -1000);
LoadManager (int creditRate = 100,
int creditLimit = 500,
int debitWarn = -500,
int debitLimit = -1000);
~LoadManager ();
void init ();
int getCreditRate () const;
int getCreditLimit () const;
int getDebitWarn () const;
int getDebitLimit () const;
void setCreditRate (int);
void setCreditLimit (int);
void setDebitWarn (int);
void setDebitLimit (int);
bool shouldWarn (LoadSource&) const;
bool shouldCutoff (LoadSource&) const;
bool adjust (LoadSource&, int credits) const; // return value: false=balance okay, true=warn/cutoff
bool adjust (LoadSource&, LoadType l) const;
void logWarning (const std::string&) const;
void logDisconnect (const std::string&) const;
int getCost (LoadType t)
int getCost (LoadType t) const
{
return mCosts[static_cast<int> (t)].mCost;
return mCosts [static_cast <int> (t)].mCost;
}
void noDeadLock ();
void arm ()
{
mArmed = true;
}
private:
void canonicalize (LoadSource&, int upTime) const;
void addLoadCost (const LoadCost& c)
class Cost
{
mCosts[static_cast<int> (c.mType)] = c;
public:
Cost ()
: mType ()
, mCost (0)
, mCategories (0)
{
}
Cost (LoadType typeOfLoad, int cost, int category)
: mType (typeOfLoad)
, mCost (cost)
, mCategories (category)
{
}
public:
// VFALCO TODO Make these private
LoadType mType;
int mCost;
int mCategories;
};
void canonicalize (LoadSource&, int upTime) const;
void addCost (const Cost& c)
{
mCosts [static_cast <int> (c.mType)] = c;
}
// VFALCO NOTE Where's the thread object? It's not a data member...
//
void threadEntry ();
private:
@@ -182,9 +223,9 @@ private:
int mDeadLock; // Detect server deadlocks
mutable boost::mutex mLock;
mutable boost::mutex mLock; // VFALCO TODO Replace with juce::Mutex and remove the mutable attribute
std::vector<LoadCost> mCosts;
std::vector <Cost> mCosts;
};
#endif

View File

@@ -3,6 +3,8 @@
// Monitors load levels and response times
// VFALCO TODO Rename this. Having both LoadManager and LoadMonitor is confusing.
//
class LoadMonitor
{
public:

View File

@@ -95,7 +95,7 @@ Json::Value PathRequest::doCreate (Ledger::ref lrLedger, const Json::Value& valu
if (mValid)
{
RLCache::pointer cache = boost::make_shared<RLCache> (lrLedger);
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (lrLedger);
doUpdate (cache, true);
}
}
@@ -220,7 +220,7 @@ Json::Value PathRequest::doStatus (const Json::Value&)
return jvStatus;
}
bool PathRequest::doUpdate (RLCache::ref cache, bool fast)
bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
{
boost::recursive_mutex::scoped_lock sl (mLock);
jvStatus = Json::objectValue;
@@ -319,7 +319,7 @@ void PathRequest::updateAll (Ledger::ref ledger, bool newOnly)
if (requests.empty ())
return;
RLCache::pointer cache = boost::make_shared<RLCache> (ledger);
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (ledger);
BOOST_FOREACH (wref wRequest, requests)
{

View File

@@ -1,22 +1,12 @@
#ifndef _PFREQUEST__H
#define _PFREQUEST__H
#include <set>
#include <vector>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include "Pathfinder.h"
#ifndef RIPPLE_PATHREQUEST_H
#define RIPPLE_PATHREQUEST_H
// A pathfinding request submitted by a client
// The request issuer must maintain a strong pointer
class InfoSub;
class STAmount;
class RLCache;
class RippleLineCache;
// Return values from parseJson <0 = invalid, >0 = valid
#define PFR_PJ_INVALID -1
@@ -45,7 +35,7 @@ public:
Json::Value doClose (const Json::Value&);
Json::Value doStatus (const Json::Value&);
bool doUpdate (const boost::shared_ptr<RLCache>&, bool fast); // update jvStatus
bool doUpdate (const boost::shared_ptr<RippleLineCache>&, bool fast); // update jvStatus
static void updateAll (const boost::shared_ptr<Ledger>& ledger, bool newOnly);

View File

@@ -0,0 +1,787 @@
// TODO:
// - Do automatic bridging via XRP.
//
// OPTIMIZE: When calculating path increment, note if increment consumes all liquidity. No need to revisit path in the future if
// all liquidity is used.
//
class RippleCalc; // for logging
std::size_t hash_value (const aciSource& asValue)
{
std::size_t seed = 0;
asValue.get<0> ().hash_combine (seed);
asValue.get<1> ().hash_combine (seed);
asValue.get<2> ().hash_combine (seed);
return seed;
}
// Compare the non-calculated fields.
bool PathState::Node::operator== (const Node& pnOther) const
{
return pnOther.uFlags == uFlags
&& pnOther.uAccountID == uAccountID
&& pnOther.uCurrencyID == uCurrencyID
&& pnOther.uIssuerID == uIssuerID;
}
// This is for debugging not end users. Output names can be changed without warning.
Json::Value PathState::Node::getJson () const
{
Json::Value jvNode (Json::objectValue);
Json::Value jvFlags (Json::arrayValue);
jvNode["type"] = uFlags;
if (isSetBit (uFlags, STPathElement::typeAccount) || !!uAccountID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account");
if (isSetBit (uFlags, STPathElement::typeCurrency) || !!uCurrencyID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency");
if (isSetBit (uFlags, STPathElement::typeIssuer) || !!uIssuerID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer");
jvNode["flags"] = jvFlags;
if (!!uAccountID)
jvNode["account"] = RippleAddress::createHumanAccountID (uAccountID);
if (!!uCurrencyID)
jvNode["currency"] = STAmount::createHumanCurrency (uCurrencyID);
if (!!uIssuerID)
jvNode["issuer"] = RippleAddress::createHumanAccountID (uIssuerID);
if (saRevRedeem)
jvNode["rev_redeem"] = saRevRedeem.getFullText ();
if (saRevIssue)
jvNode["rev_issue"] = saRevIssue.getFullText ();
if (saRevDeliver)
jvNode["rev_deliver"] = saRevDeliver.getFullText ();
if (saFwdRedeem)
jvNode["fwd_redeem"] = saFwdRedeem.getFullText ();
if (saFwdIssue)
jvNode["fwd_issue"] = saFwdIssue.getFullText ();
if (saFwdDeliver)
jvNode["fwd_deliver"] = saFwdDeliver.getFullText ();
return jvNode;
}
//
// PathState implementation
//
// Return true, iff lhs has less priority than rhs.
bool PathState::lessPriority (PathState& lhs, PathState& rhs)
{
// First rank is quality.
if (lhs.uQuality != rhs.uQuality)
return lhs.uQuality > rhs.uQuality; // Bigger is worse.
// Second rank is best quantity.
if (lhs.saOutPass != rhs.saOutPass)
return lhs.saOutPass < rhs.saOutPass; // Smaller is worse.
// Third rank is path index.
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
}
// Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID.
//
// If the unadded next node as specified by arguments would not work as is, then add the necessary nodes so it would work.
//
// Rules:
// - Currencies must be converted via an offer.
// - A node names it's output.
// - A ripple nodes output issuer must be the node's account or the next node's account.
// - Offers can only go directly to another offer if the currency and issuer are an exact match.
// - Real issuers must be specified for non-XRP.
TER PathState::pushImply (
const uint160& uAccountID, // --> Delivering to this account.
const uint160& uCurrencyID, // --> Delivering this currency.
const uint160& uIssuerID) // --> Delivering this issuer.
{
const Node& pnPrv = vpnNodes.back ();
TER terResult = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushImply> "
<< RippleAddress::createHumanAccountID (uAccountID)
<< " " << STAmount::createHumanCurrency (uCurrencyID)
<< " " << RippleAddress::createHumanAccountID (uIssuerID);
if (pnPrv.uCurrencyID != uCurrencyID)
{
// Currency is different, need to convert via an offer.
terResult = pushNode ( // Offer.
!!uCurrencyID
? STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeCurrency,
ACCOUNT_XRP, // Placeholder for offers.
uCurrencyID, // The offer's output is what is now wanted.
uIssuerID);
}
const Node& pnBck = vpnNodes.back ();
// For ripple, non-XRP, ensure the issuer is on at least one side of the transaction.
if (tesSUCCESS == terResult
&& !!uCurrencyID // Not XRP.
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
{
// Need to ripple through uIssuerID's account.
terResult = pushNode (
STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer,
uIssuerID, // Intermediate account is the needed issuer.
uCurrencyID,
uIssuerID);
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushImply< : %s") % transToken (terResult));
return terResult;
}
// Append a node and insert before it any implied nodes.
// Offers may go back to back.
// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
TER PathState::pushNode (
const int iType,
const uint160& uAccountID,
const uint160& uCurrencyID,
const uint160& uIssuerID)
{
Node pnCur;
const bool bFirst = vpnNodes.empty ();
const Node& pnPrv = bFirst ? Node () : vpnNodes.back ();
// true, iff node is a ripple account. false, iff node is an offer node.
const bool bAccount = isSetBit (iType, STPathElement::typeAccount);
// true, iff currency supplied.
// Currency is specified for the output of the current node.
const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency);
// Issuer is specified for the output of the current node.
const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer);
TER terResult = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushNode> "
<< iType
<< ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-")
<< " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-")
<< "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");
pnCur.uFlags = iType;
pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID;
if (iType & ~STPathElement::typeValidBits)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";
terResult = temBAD_PATH;
}
else if (bIssuer && !pnCur.uCurrencyID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP.";
terResult = temBAD_PATH;
}
else if (bIssuer && !uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";
terResult = temBAD_PATH;
}
else if (!bAccount && !bCurrency && !bIssuer)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";
terResult = temBAD_PATH;
}
else if (bAccount)
{
// Account link
pnCur.uAccountID = uAccountID;
pnCur.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? uAccountID
: ACCOUNT_XRP;
pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
if (bFirst)
{
// The first node is always correct as is.
nothing ();
}
else if (!uAccountID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";
terResult = temBAD_PATH;
}
else
{
// Add required intermediate nodes to deliver to current account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";
terResult = pushImply (
pnCur.uAccountID, // Current account.
pnCur.uCurrencyID, // Wanted currency.
!!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
// Note: pnPrv may no longer be the immediately previous node.
}
if (tesSUCCESS == terResult && !vpnNodes.empty ())
{
const Node& pnBck = vpnNodes.back ();
bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount);
if (bBckAccount)
{
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID));
if (!sleRippleState)
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< "." ;
WriteLog (lsTRACE, RippleCalc) << getJson ();
terResult = terNO_LINE;
}
else
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< "." ;
SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
bool bHigh = pnBck.uAccountID > pnCur.uAccountID;
if (!sleBck)
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);
terResult = terNO_ACCOUNT;
}
else if (isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
&& !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";
terResult = terNO_AUTH;
}
if (tesSUCCESS == terResult)
{
STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID);
STAmount saLimit;
if (!saOwed.isPositive ()
&& -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)))
{
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s")
% saOwed
% saLimit);
terResult = tecPATH_DRY;
}
}
}
}
}
if (tesSUCCESS == terResult)
{
vpnNodes.push_back (pnCur);
}
}
else
{
// Offer link
// Offers bridge a change in currency & issuer or just a change in issuer.
pnCur.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? !!pnPrv.uIssuerID
? pnPrv.uIssuerID // Default to previous issuer
: pnPrv.uAccountID // Or previous account if no previous issuer.
: ACCOUNT_XRP;
pnCur.saRateMax = saZero;
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer.";
terResult = temBAD_PATH;
}
else if (!!pnPrv.uAccountID)
{
// Previous is an account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";
// Insert intermediary issuer account if needed.
terResult = pushImply (
ACCOUNT_XRP, // Rippling, but offers don't have an account.
pnPrv.uCurrencyID,
pnPrv.uIssuerID);
}
if (tesSUCCESS == terResult)
{
vpnNodes.push_back (pnCur);
}
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult));
return terResult;
}
// Set to an expanded path.
//
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP
void PathState::setExpanded (
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID
)
{
uQuality = 1; // Mark path as active.
const uint160 uMaxCurrencyID = saInReq.getCurrency ();
const uint160 uMaxIssuerID = saInReq.getIssuer ();
const uint160 uOutCurrencyID = saOutReq.getCurrency ();
const uint160 uOutIssuerID = saOutReq.getIssuer ();
const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0));
lesEntries = lesSource.duplicate ();
terStatus = tesSUCCESS;
// XRP with issuer is malformed.
if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID))
terStatus = temBAD_PATH;
// Push sending node.
// For non-XRP, issuer is always sending account.
// - Trying to expand, not-compact.
// - Every issuer will be traversed through.
if (tesSUCCESS == terStatus)
terStatus = pushNode (
!!uMaxCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uSenderID,
uMaxCurrencyID, // Max specifes the currency.
uSenderIssuerID);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: pushed: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uSenderID)
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uSenderIssuerID));
if (tesSUCCESS == terStatus
&& uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender.
{
// May have an implied account node.
// - If it was XRP, then issuers would have matched.
// Figure out next node properties for implied node.
const uint160 uNxtCurrencyID = spSourcePath.size ()
? spSourcePath.getElement (0).getCurrency () // Use next node.
: uOutCurrencyID; // Use send.
const uint160 uNxtAccountID = spSourcePath.size ()
? spSourcePath.getElement (0).getAccountID ()
: !!uOutCurrencyID
? uOutIssuerID == uReceiverID
? uReceiverID
: uOutIssuerID // Use implied node.
: ACCOUNT_XRP;
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: implied check: uMaxIssuerID=%s uSenderIssuerID=%s uNxtCurrencyID=%s uNxtAccountID=%s")
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% RippleAddress::createHumanAccountID (uSenderIssuerID)
% STAmount::createHumanCurrency (uNxtCurrencyID)
% RippleAddress::createHumanAccountID (uNxtAccountID));
// Can't just use push implied, because it can't compensate for next account.
if (!uNxtCurrencyID // Next is XRP, offer next. Must go through issuer.
|| uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next...
|| uMaxIssuerID != uNxtAccountID) // Next is not implied issuer
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: sender implied: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID));
// Add account implied by SendMax.
terStatus = pushNode (
!!uMaxCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uMaxIssuerID,
uMaxCurrencyID,
uMaxIssuerID);
}
}
BOOST_FOREACH (const STPathElement & speElement, spSourcePath)
{
if (tesSUCCESS == terStatus)
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:"));
terStatus = pushNode (speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ());
}
}
const Node& pnPrv = vpnNodes.back ();
if (tesSUCCESS == terStatus
&& !!uOutCurrencyID // Next is not XRP
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
&& (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|| pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer.
{
// Add implied account.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: receiver implied: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uOutIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID));
terStatus = pushNode (
!!uOutCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uOutIssuerID,
uOutCurrencyID,
uOutIssuerID);
}
if (tesSUCCESS == terStatus)
{
// Create receiver node.
// Last node is always an account.
terStatus = pushNode (
!!uOutCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uReceiverID, // Receive to output
uOutCurrencyID, // Desired currency
uReceiverID);
}
if (tesSUCCESS == terStatus)
{
// Look for first mention of source in nodes and detect loops.
// Note: The output is not allowed to be a source.
const unsigned int uNodes = vpnNodes.size ();
for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode)
{
const Node& pnCur = vpnNodes[uNode];
if (!umForward.insert (std::make_pair (boost::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second)
{
// Failed to insert. Have a loop.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: loop detected: %s")
% getJson ());
terStatus = temBAD_PATH_LOOP;
}
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID)
% getJson ());
}
// Set to a canonical path.
// - Remove extra elements
// - Assumes path is expanded.
//
// We do canonicalization to:
// - Prevent waste in the ledger.
// - Allow longer paths to be specified than would otherwise be allowed.
//
// Optimization theory:
// - Can omit elements that the expansion routine derives.
// - Can pack some elements into other elements.
//
// Rules:
// - SendMax if not specified, defaults currency to send and if not sending XRP defaults issuer to sender.
// - All paths start with the sender account.
// - Currency and issuer is from SendMax.
// - All paths end with the destination account.
//
// Optimization:
// - An XRP output implies an offer node or destination node is next.
// - A change in currency implies an offer node.
// - A change in issuer...
void PathState::setCanonical (
const PathState& psExpanded
)
{
assert (false);
saInAct = psExpanded.saInAct;
saOutAct = psExpanded.saOutAct;
const uint160 uMaxCurrencyID = saInAct.getCurrency ();
const uint160 uMaxIssuerID = saInAct.getIssuer ();
const uint160 uOutCurrencyID = saOutAct.getCurrency ();
const uint160 uOutIssuerID = saOutAct.getIssuer ();
unsigned int uNode = 0;
unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include.
uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID; // FIXME: This can't be right
uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID;
uint160 uCurrencyID = uMaxCurrencyID;
uint160 uIssuerID = uMaxIssuerID;
// Node 0 is a composite of the sending account and saInAct.
++uNode; // skip node 0
// Last node is implied: Always skip last node
--uEnd; // skip last node
// saInAct
// - currency is always the same as vpnNodes[0].
#if 1
if (uNode != uEnd && uMaxIssuerID != uAccountID)
{
// saInAct issuer is not the sender. This forces an implied node.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd);
// skip node 1
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode;
}
#else
if (uNode != uEnd)
{
// Have another node
bool bKeep = false;
if (uMaxIssuerID != uAccountID)
{
}
if (uMaxCurrencyID) // Not sending XRP.
{
// Node 1 must be an account.
if (uMaxIssuerID != uAccountID)
{
// Node 1 is required to specify issuer.
bKeep = true;
}
else
{
// Node 1 must be an account
}
}
else
{
// Node 1 must be an order book.
bKeep = true;
}
if (bKeep)
{
uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID;
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode; // Keep it.
}
}
#endif
if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd);
// The next to last node is saOutAct if an issuer different from receiver is supplied.
// The next to last node can be implied.
--uEnd;
}
const Node& pnEnd = psExpanded.vpnNodes[uEnd];
if (uNode != uEnd
&& !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID)
{
// The current end node is an offer converting to saOutAct's currency and issuer and can be implied.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd);
--uEnd;
}
// Do not include uEnd.
for (; uNode != uEnd; ++uNode)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd);
const Node& pnPrv = psExpanded.vpnNodes[uNode - 1];
const Node& pnCur = psExpanded.vpnNodes[uNode];
const Node& pnNxt = psExpanded.vpnNodes[uNode + 1];
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
bool bSkip = false;
if (bCurAccount)
{
// Currently at an account.
// Output is non-XRP and issuer is account.
if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID)
{
// Account issues itself.
// XXX Not good enough. Previous account must mention it.
bSkip = true;
}
}
else
{
// Currently at an offer.
const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
if (bPrvAccount && bNxtAccount // Offer surrounded by accounts.
&& pnPrv.uCurrencyID != pnNxt.uCurrencyID)
{
// Offer can be implied by currency change.
// XXX What about issuer?
bSkip = true;
}
}
if (!bSkip)
{
// Copy node
Node pnNew;
bool bSetAccount = bCurAccount;
bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID;
// XXX What if we need the next account because we want to skip it?
bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID;
pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0)
| (bSetCurrency ? STPathElement::typeCurrency : 0)
| (bSetIssuer ? STPathElement::typeIssuer : 0);
if (bSetAccount)
pnNew.uAccountID = pnCur.uAccountID;
if (bSetCurrency)
{
pnNew.uCurrencyID = pnCur.uCurrencyID;
uCurrencyID = pnNew.uCurrencyID;
}
if (bSetIssuer)
pnNew.uIssuerID = pnCur.uIssuerID;
// XXX ^^^ What about setting uIssuerID?
if (bSetCurrency && !uCurrencyID)
uIssuerID.zero ();
vpnNodes.push_back (pnNew);
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setCanonical: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID)
% getJson ());
}
// This is for debugging not end users. Output names can be changed without warning.
Json::Value PathState::getJson () const
{
Json::Value jvPathState (Json::objectValue);
Json::Value jvNodes (Json::arrayValue);
BOOST_FOREACH (const Node & pnNode, vpnNodes)
{
jvNodes.append (pnNode.getJson ());
}
jvPathState["status"] = terStatus;
jvPathState["index"] = mIndex;
jvPathState["nodes"] = jvNodes;
if (saInReq)
jvPathState["in_req"] = saInReq.getJson (0);
if (saInAct)
jvPathState["in_act"] = saInAct.getJson (0);
if (saInPass)
jvPathState["in_pass"] = saInPass.getJson (0);
if (saOutReq)
jvPathState["out_req"] = saOutReq.getJson (0);
if (saOutAct)
jvPathState["out_act"] = saOutAct.getJson (0);
if (saOutPass)
jvPathState["out_pass"] = saOutPass.getJson (0);
if (uQuality)
jvPathState["uQuality"] = boost::str (boost::format ("%d") % uQuality);
return jvPathState;
}

View File

@@ -0,0 +1,158 @@
#ifndef RIPPLE_PATHSTATE_H
#define RIPPLE_PATHSTATE_H
// account id, currency id, issuer id :: node
typedef boost::tuple <uint160, uint160, uint160> aciSource;
typedef boost::unordered_map <aciSource, unsigned int> curIssuerNode; // Map of currency, issuer to node index.
typedef boost::unordered_map <aciSource, unsigned int>::const_iterator curIssuerNodeConstIterator;
extern std::size_t hash_value (const aciSource& asValue);
// Holds a path state under incremental application.
class PathState
{
public:
class Node
{
public:
bool operator == (Node const& pnOther) const;
Json::Value getJson () const;
public:
uint16 uFlags; // --> From path.
uint160 uAccountID; // --> Accounts: Recieving/sending account.
uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send.
// --- For offer's next has currency out.
uint160 uIssuerID; // --> Currency's issuer
STAmount saTransferRate; // Transfer rate for uIssuerID.
// Computed by Reverse.
STAmount saRevRedeem; // <-- Amount to redeem to next.
STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs.
// Issue isn't used by offers.
STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee.
// Computed by forward.
STAmount saFwdRedeem; // <-- Amount node will redeem to next.
STAmount saFwdIssue; // <-- Amount node will issue to next.
// Issue isn't used by offers.
STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee.
// For offers:
STAmount saRateMax;
// Directory
uint256 uDirectTip; // Current directory.
uint256 uDirectEnd; // Next order book.
bool bDirectAdvance; // Need to advance directory.
SLE::pointer sleDirectDir;
STAmount saOfrRate; // For correct ratio.
// PaymentNode
bool bEntryAdvance; // Need to advance entry.
unsigned int uEntry;
uint256 uOfferIndex;
SLE::pointer sleOffer;
uint160 uOfrOwnerID;
bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets.
STAmount saOfferFunds;
STAmount saTakerPays;
STAmount saTakerGets;
};
public:
typedef boost::shared_ptr<PathState> pointer;
typedef const boost::shared_ptr<PathState>& ref;
public:
PathState* setIndex (const int iIndex)
{
mIndex = iIndex;
return this;
}
int getIndex ()
{
return mIndex;
};
PathState (
const STAmount& saSend,
const STAmount& saSendMax)
: saInReq (saSendMax)
, saOutReq (saSend)
{
}
PathState (const PathState& psSrc,
bool bUnused)
: saInReq (psSrc.saInReq)
, saOutReq (psSrc.saOutReq)
{
}
void setExpanded (
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID
);
void setCanonical (
const PathState& psExpanded
);
Json::Value getJson () const;
#if 0
static PathState::pointer createCanonical (
PathState& ref pspExpanded
)
{
PathState::pointer pspNew = boost::make_shared<PathState> (pspExpanded->saOutAct, pspExpanded->saInAct);
pspNew->setCanonical (pspExpanded);
return pspNew;
}
#endif
static bool lessPriority (PathState& lhs, PathState& rhs);
public:
TER terStatus;
std::vector<Node> vpnNodes;
// When processing, don't want to complicate directory walking with deletion.
std::vector<uint256> vUnfundedBecame; // Offers that became unfunded or were completely consumed.
// First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be
// used there.
curIssuerNode umForward; // Map of currency, issuer to node index.
// First time working in reverse a funding source was used.
// Source may only be used there if not mentioned by an account.
curIssuerNode umReverse; // Map of currency, issuer to node index.
LedgerEntrySet lesEntries;
int mIndex; // Index/rank amoung siblings.
uint64 uQuality; // 0 = no quality/liquity left.
const STAmount& saInReq; // --> Max amount to spend by sender.
STAmount saInAct; // --> Amount spent by sender so far.
STAmount saInPass; // <-- Amount spent by sender.
const STAmount& saOutReq; // --> Amount to send.
STAmount saOutAct; // --> Amount actually sent so far.
STAmount saOutPass; // <-- Amount actually sent.
bool bConsumed; // If true, use consumes full liquidity. False, may or may not.
private:
TER pushNode (const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
TER pushImply (const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
};
#endif

View File

@@ -135,7 +135,7 @@ static int getEffectiveLength (const STPath& spPath)
return length;
}
Pathfinder::Pathfinder (RLCache::ref cache,
Pathfinder::Pathfinder (RippleLineCache::ref cache,
const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID,
const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount, bool& bValid)
: mSrcAccountID (uSrcAccountID.getAccountID ()),
@@ -792,18 +792,6 @@ boost::unordered_set<uint160> usAccountDestCurrencies (const RippleAddress& raAc
return usCurrencies;
}
AccountItems& RLCache::getRippleLines (const uint160& accountID)
{
boost::mutex::scoped_lock sl (mLock);
boost::unordered_map<uint160, AccountItems::pointer>::iterator it = mRLMap.find (accountID);
if (it == mRLMap.end ())
it = mRLMap.insert (std::make_pair (accountID, boost::make_shared<AccountItems>
(boost::cref (accountID), boost::cref (mLedger), AccountItem::pointer (new RippleState ())))).first;
return *it->second;
}
bool Pathfinder::matchesOrigin (const uint160& currency, const uint160& issuer)
{
return (currency == mSrcCurrencyID) && (issuer == mSrcIssuerID);

View File

@@ -1,11 +1,7 @@
#ifndef __PATHFINDER__
#define __PATHFINDER__
#include <boost/shared_ptr.hpp>
#include "RippleCalc.h"
#include "OrderBookDB.h"
#ifndef RIPPLE_PATHFINDER_H
#define RIPPLE_PATHFINDER_H
// VFALCO TODO Remove this unused stuff?
#if 0
//
// This is a very simple implementation. This can be made way better.
@@ -32,33 +28,10 @@ public:
};
#endif
class RLCache
{
public:
typedef boost::shared_ptr<RLCache> pointer;
typedef const pointer& ref;
RLCache (Ledger::ref l) : mLedger (l)
{
;
}
Ledger::ref getLedger ()
{
return mLedger;
}
AccountItems& getRippleLines (const uint160& accountID);
private:
boost::mutex mLock;
Ledger::pointer mLedger;
boost::unordered_map<uint160, AccountItems::pointer> mRLMap;
};
class Pathfinder
{
public:
Pathfinder (RLCache::ref cache,
Pathfinder (RippleLineCache::ref cache,
const RippleAddress& srcAccountID, const RippleAddress& dstAccountID,
const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount, bool& bValid);
@@ -66,6 +39,19 @@ public:
bool bDefaultPath (const STPath& spPath);
private:
// void addOptions(PathOption::pointer tail);
// returns true if any building paths are now complete?
bool checkComplete (STPathSet& retPathSet);
// void addPathOption(PathOption::pointer pathOption);
bool matchesOrigin (const uint160& currency, const uint160& issuer);
int getPathsOut (const uint160& currency, const uint160& accountID,
bool isAuthRequired, bool isDestCurrency, const uint160& dest);
private:
uint160 mSrcAccountID;
uint160 mDstAccountID;
@@ -77,25 +63,13 @@ private:
Ledger::pointer mLedger;
PathState::pointer mPsDefault;
LoadEvent::pointer m_loadEvent;
RLCache::pointer mRLCache;
RippleLineCache::pointer mRLCache;
boost::unordered_map<uint160, AccountItems::pointer> mRLMap;
boost::unordered_map<std::pair<uint160, uint160>, int> mPOMap;
// std::list<PathOption::pointer> mBuildingPaths;
// std::list<PathOption::pointer> mCompletePaths;
// void addOptions(PathOption::pointer tail);
// returns true if any building paths are now complete?
bool checkComplete (STPathSet& retPathSet);
// void addPathOption(PathOption::pointer pathOption);
bool matchesOrigin (const uint160& currency, const uint160& issuer);
int getPathsOut (const uint160& currency, const uint160& accountID,
bool isAuthRequired, bool isDestCurrency, const uint160& dest);
};
boost::unordered_set<uint160> usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
@@ -105,5 +79,3 @@ boost::unordered_set<uint160> usAccountSourceCurrencies (const RippleAddress& ra
bool includeXRP);
#endif
// vim:ts=4

View File

@@ -7,824 +7,6 @@
SETUP_LOG (RippleCalc)
std::size_t hash_value (const aciSource& asValue)
{
std::size_t seed = 0;
asValue.get<0> ().hash_combine (seed);
asValue.get<1> ().hash_combine (seed);
asValue.get<2> ().hash_combine (seed);
return seed;
}
// Compare the non-calculated fields.
bool PaymentNode::operator== (const PaymentNode& pnOther) const
{
return pnOther.uFlags == uFlags
&& pnOther.uAccountID == uAccountID
&& pnOther.uCurrencyID == uCurrencyID
&& pnOther.uIssuerID == uIssuerID;
}
// This is for debugging not end users. Output names can be changed without warning.
Json::Value PaymentNode::getJson () const
{
Json::Value jvNode (Json::objectValue);
Json::Value jvFlags (Json::arrayValue);
jvNode["type"] = uFlags;
if (isSetBit (uFlags, STPathElement::typeAccount) || !!uAccountID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account");
if (isSetBit (uFlags, STPathElement::typeCurrency) || !!uCurrencyID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency");
if (isSetBit (uFlags, STPathElement::typeIssuer) || !!uIssuerID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer");
jvNode["flags"] = jvFlags;
if (!!uAccountID)
jvNode["account"] = RippleAddress::createHumanAccountID (uAccountID);
if (!!uCurrencyID)
jvNode["currency"] = STAmount::createHumanCurrency (uCurrencyID);
if (!!uIssuerID)
jvNode["issuer"] = RippleAddress::createHumanAccountID (uIssuerID);
if (saRevRedeem)
jvNode["rev_redeem"] = saRevRedeem.getFullText ();
if (saRevIssue)
jvNode["rev_issue"] = saRevIssue.getFullText ();
if (saRevDeliver)
jvNode["rev_deliver"] = saRevDeliver.getFullText ();
if (saFwdRedeem)
jvNode["fwd_redeem"] = saFwdRedeem.getFullText ();
if (saFwdIssue)
jvNode["fwd_issue"] = saFwdIssue.getFullText ();
if (saFwdDeliver)
jvNode["fwd_deliver"] = saFwdDeliver.getFullText ();
return jvNode;
}
//
// PathState implementation
//
// Return true, iff lhs has less priority than rhs.
bool PathState::lessPriority (PathState& lhs, PathState& rhs)
{
// First rank is quality.
if (lhs.uQuality != rhs.uQuality)
return lhs.uQuality > rhs.uQuality; // Bigger is worse.
// Second rank is best quantity.
if (lhs.saOutPass != rhs.saOutPass)
return lhs.saOutPass < rhs.saOutPass; // Smaller is worse.
// Third rank is path index.
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
}
// Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID.
//
// If the unadded next node as specified by arguments would not work as is, then add the necessary nodes so it would work.
//
// Rules:
// - Currencies must be converted via an offer.
// - A node names it's output.
// - A ripple nodes output issuer must be the node's account or the next node's account.
// - Offers can only go directly to another offer if the currency and issuer are an exact match.
// - Real issuers must be specified for non-XRP.
TER PathState::pushImply (
const uint160& uAccountID, // --> Delivering to this account.
const uint160& uCurrencyID, // --> Delivering this currency.
const uint160& uIssuerID) // --> Delivering this issuer.
{
const PaymentNode& pnPrv = vpnNodes.back ();
TER terResult = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushImply> "
<< RippleAddress::createHumanAccountID (uAccountID)
<< " " << STAmount::createHumanCurrency (uCurrencyID)
<< " " << RippleAddress::createHumanAccountID (uIssuerID);
if (pnPrv.uCurrencyID != uCurrencyID)
{
// Currency is different, need to convert via an offer.
terResult = pushNode ( // Offer.
!!uCurrencyID
? STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeCurrency,
ACCOUNT_XRP, // Placeholder for offers.
uCurrencyID, // The offer's output is what is now wanted.
uIssuerID);
}
const PaymentNode& pnBck = vpnNodes.back ();
// For ripple, non-XRP, ensure the issuer is on at least one side of the transaction.
if (tesSUCCESS == terResult
&& !!uCurrencyID // Not XRP.
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
{
// Need to ripple through uIssuerID's account.
terResult = pushNode (
STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer,
uIssuerID, // Intermediate account is the needed issuer.
uCurrencyID,
uIssuerID);
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushImply< : %s") % transToken (terResult));
return terResult;
}
// Append a node and insert before it any implied nodes.
// Offers may go back to back.
// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
TER PathState::pushNode (
const int iType,
const uint160& uAccountID,
const uint160& uCurrencyID,
const uint160& uIssuerID)
{
PaymentNode pnCur;
const bool bFirst = vpnNodes.empty ();
const PaymentNode& pnPrv = bFirst ? PaymentNode () : vpnNodes.back ();
// true, iff node is a ripple account. false, iff node is an offer node.
const bool bAccount = isSetBit (iType, STPathElement::typeAccount);
// true, iff currency supplied.
// Currency is specified for the output of the current node.
const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency);
// Issuer is specified for the output of the current node.
const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer);
TER terResult = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushNode> "
<< iType
<< ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-")
<< " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-")
<< "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");
pnCur.uFlags = iType;
pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID;
if (iType & ~STPathElement::typeValidBits)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";
terResult = temBAD_PATH;
}
else if (bIssuer && !pnCur.uCurrencyID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP.";
terResult = temBAD_PATH;
}
else if (bIssuer && !uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";
terResult = temBAD_PATH;
}
else if (!bAccount && !bCurrency && !bIssuer)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";
terResult = temBAD_PATH;
}
else if (bAccount)
{
// Account link
pnCur.uAccountID = uAccountID;
pnCur.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? uAccountID
: ACCOUNT_XRP;
pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
if (bFirst)
{
// The first node is always correct as is.
nothing ();
}
else if (!uAccountID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";
terResult = temBAD_PATH;
}
else
{
// Add required intermediate nodes to deliver to current account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";
terResult = pushImply (
pnCur.uAccountID, // Current account.
pnCur.uCurrencyID, // Wanted currency.
!!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
// Note: pnPrv may no longer be the immediately previous node.
}
if (tesSUCCESS == terResult && !vpnNodes.empty ())
{
const PaymentNode& pnBck = vpnNodes.back ();
bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount);
if (bBckAccount)
{
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID));
if (!sleRippleState)
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< "." ;
WriteLog (lsTRACE, RippleCalc) << getJson ();
terResult = terNO_LINE;
}
else
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< "." ;
SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
bool bHigh = pnBck.uAccountID > pnCur.uAccountID;
if (!sleBck)
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);
terResult = terNO_ACCOUNT;
}
else if (isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
&& !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";
terResult = terNO_AUTH;
}
if (tesSUCCESS == terResult)
{
STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID);
STAmount saLimit;
if (!saOwed.isPositive ()
&& -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)))
{
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s")
% saOwed
% saLimit);
terResult = tecPATH_DRY;
}
}
}
}
}
if (tesSUCCESS == terResult)
{
vpnNodes.push_back (pnCur);
}
}
else
{
// Offer link
// Offers bridge a change in currency & issuer or just a change in issuer.
pnCur.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? !!pnPrv.uIssuerID
? pnPrv.uIssuerID // Default to previous issuer
: pnPrv.uAccountID // Or previous account if no previous issuer.
: ACCOUNT_XRP;
pnCur.saRateMax = saZero;
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer.";
terResult = temBAD_PATH;
}
else if (!!pnPrv.uAccountID)
{
// Previous is an account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";
// Insert intermediary issuer account if needed.
terResult = pushImply (
ACCOUNT_XRP, // Rippling, but offers don't have an account.
pnPrv.uCurrencyID,
pnPrv.uIssuerID);
}
if (tesSUCCESS == terResult)
{
vpnNodes.push_back (pnCur);
}
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult));
return terResult;
}
// Set to an expanded path.
//
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP
void PathState::setExpanded (
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID
)
{
uQuality = 1; // Mark path as active.
const uint160 uMaxCurrencyID = saInReq.getCurrency ();
const uint160 uMaxIssuerID = saInReq.getIssuer ();
const uint160 uOutCurrencyID = saOutReq.getCurrency ();
const uint160 uOutIssuerID = saOutReq.getIssuer ();
const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0));
lesEntries = lesSource.duplicate ();
terStatus = tesSUCCESS;
// XRP with issuer is malformed.
if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID))
terStatus = temBAD_PATH;
// Push sending node.
// For non-XRP, issuer is always sending account.
// - Trying to expand, not-compact.
// - Every issuer will be traversed through.
if (tesSUCCESS == terStatus)
terStatus = pushNode (
!!uMaxCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uSenderID,
uMaxCurrencyID, // Max specifes the currency.
uSenderIssuerID);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: pushed: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uSenderID)
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uSenderIssuerID));
if (tesSUCCESS == terStatus
&& uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender.
{
// May have an implied account node.
// - If it was XRP, then issuers would have matched.
// Figure out next node properties for implied node.
const uint160 uNxtCurrencyID = spSourcePath.size ()
? spSourcePath.getElement (0).getCurrency () // Use next node.
: uOutCurrencyID; // Use send.
const uint160 uNxtAccountID = spSourcePath.size ()
? spSourcePath.getElement (0).getAccountID ()
: !!uOutCurrencyID
? uOutIssuerID == uReceiverID
? uReceiverID
: uOutIssuerID // Use implied node.
: ACCOUNT_XRP;
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: implied check: uMaxIssuerID=%s uSenderIssuerID=%s uNxtCurrencyID=%s uNxtAccountID=%s")
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% RippleAddress::createHumanAccountID (uSenderIssuerID)
% STAmount::createHumanCurrency (uNxtCurrencyID)
% RippleAddress::createHumanAccountID (uNxtAccountID));
// Can't just use push implied, because it can't compensate for next account.
if (!uNxtCurrencyID // Next is XRP, offer next. Must go through issuer.
|| uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next...
|| uMaxIssuerID != uNxtAccountID) // Next is not implied issuer
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: sender implied: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID));
// Add account implied by SendMax.
terStatus = pushNode (
!!uMaxCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uMaxIssuerID,
uMaxCurrencyID,
uMaxIssuerID);
}
}
BOOST_FOREACH (const STPathElement & speElement, spSourcePath)
{
if (tesSUCCESS == terStatus)
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:"));
terStatus = pushNode (speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ());
}
}
const PaymentNode& pnPrv = vpnNodes.back ();
if (tesSUCCESS == terStatus
&& !!uOutCurrencyID // Next is not XRP
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
&& (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|| pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer.
{
// Add implied account.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: receiver implied: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uOutIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID));
terStatus = pushNode (
!!uOutCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uOutIssuerID,
uOutCurrencyID,
uOutIssuerID);
}
if (tesSUCCESS == terStatus)
{
// Create receiver node.
// Last node is always an account.
terStatus = pushNode (
!!uOutCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uReceiverID, // Receive to output
uOutCurrencyID, // Desired currency
uReceiverID);
}
if (tesSUCCESS == terStatus)
{
// Look for first mention of source in nodes and detect loops.
// Note: The output is not allowed to be a source.
const unsigned int uNodes = vpnNodes.size ();
for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode)
{
const PaymentNode& pnCur = vpnNodes[uNode];
if (!umForward.insert (std::make_pair (boost::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second)
{
// Failed to insert. Have a loop.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: loop detected: %s")
% getJson ());
terStatus = temBAD_PATH_LOOP;
}
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID)
% getJson ());
}
// Set to a canonical path.
// - Remove extra elements
// - Assumes path is expanded.
//
// We do canonicalization to:
// - Prevent waste in the ledger.
// - Allow longer paths to be specified than would otherwise be allowed.
//
// Optimization theory:
// - Can omit elements that the expansion routine derives.
// - Can pack some elements into other elements.
//
// Rules:
// - SendMax if not specified, defaults currency to send and if not sending XRP defaults issuer to sender.
// - All paths start with the sender account.
// - Currency and issuer is from SendMax.
// - All paths end with the destination account.
//
// Optimization:
// - An XRP output implies an offer node or destination node is next.
// - A change in currency implies an offer node.
// - A change in issuer...
void PathState::setCanonical (
const PathState& psExpanded
)
{
assert (false);
saInAct = psExpanded.saInAct;
saOutAct = psExpanded.saOutAct;
const uint160 uMaxCurrencyID = saInAct.getCurrency ();
const uint160 uMaxIssuerID = saInAct.getIssuer ();
const uint160 uOutCurrencyID = saOutAct.getCurrency ();
const uint160 uOutIssuerID = saOutAct.getIssuer ();
unsigned int uNode = 0;
unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include.
uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID; // FIXME: This can't be right
uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID;
uint160 uCurrencyID = uMaxCurrencyID;
uint160 uIssuerID = uMaxIssuerID;
// Node 0 is a composite of the sending account and saInAct.
++uNode; // skip node 0
// Last node is implied: Always skip last node
--uEnd; // skip last node
// saInAct
// - currency is always the same as vpnNodes[0].
#if 1
if (uNode != uEnd && uMaxIssuerID != uAccountID)
{
// saInAct issuer is not the sender. This forces an implied node.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd);
// skip node 1
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode;
}
#else
if (uNode != uEnd)
{
// Have another node
bool bKeep = false;
if (uMaxIssuerID != uAccountID)
{
}
if (uMaxCurrencyID) // Not sending XRP.
{
// Node 1 must be an account.
if (uMaxIssuerID != uAccountID)
{
// Node 1 is required to specify issuer.
bKeep = true;
}
else
{
// Node 1 must be an account
}
}
else
{
// Node 1 must be an order book.
bKeep = true;
}
if (bKeep)
{
uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID;
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode; // Keep it.
}
}
#endif
if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd);
// The next to last node is saOutAct if an issuer different from receiver is supplied.
// The next to last node can be implied.
--uEnd;
}
const PaymentNode& pnEnd = psExpanded.vpnNodes[uEnd];
if (uNode != uEnd
&& !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID)
{
// The current end node is an offer converting to saOutAct's currency and issuer and can be implied.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd);
--uEnd;
}
// Do not include uEnd.
for (; uNode != uEnd; ++uNode)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd);
const PaymentNode& pnPrv = psExpanded.vpnNodes[uNode - 1];
const PaymentNode& pnCur = psExpanded.vpnNodes[uNode];
const PaymentNode& pnNxt = psExpanded.vpnNodes[uNode + 1];
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
bool bSkip = false;
if (bCurAccount)
{
// Currently at an account.
// Output is non-XRP and issuer is account.
if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID)
{
// Account issues itself.
// XXX Not good enough. Previous account must mention it.
bSkip = true;
}
}
else
{
// Currently at an offer.
const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
if (bPrvAccount && bNxtAccount // Offer surrounded by accounts.
&& pnPrv.uCurrencyID != pnNxt.uCurrencyID)
{
// Offer can be implied by currency change.
// XXX What about issuer?
bSkip = true;
}
}
if (!bSkip)
{
// Copy node
PaymentNode pnNew;
bool bSetAccount = bCurAccount;
bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID;
// XXX What if we need the next account because we want to skip it?
bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID;
pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0)
| (bSetCurrency ? STPathElement::typeCurrency : 0)
| (bSetIssuer ? STPathElement::typeIssuer : 0);
if (bSetAccount)
pnNew.uAccountID = pnCur.uAccountID;
if (bSetCurrency)
{
pnNew.uCurrencyID = pnCur.uCurrencyID;
uCurrencyID = pnNew.uCurrencyID;
}
if (bSetIssuer)
pnNew.uIssuerID = pnCur.uIssuerID;
// XXX ^^^ What about setting uIssuerID?
if (bSetCurrency && !uCurrencyID)
uIssuerID.zero ();
vpnNodes.push_back (pnNew);
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setCanonical: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID)
% getJson ());
}
// Build a canonicalized STPathSet from a vector of PathStates.
void RippleCalc::setCanonical (STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded, bool bKeepDefault)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("SET: setCanonical> %d") % vpsExpanded.size());
BOOST_FOREACH (PathState::ref pspExpanded, vpsExpanded)
{
// Obvious defaults have 2 nodes when expanded.
if (bKeepDefault || 2 != pspExpanded->vpnNodes.size ())
{
PathState psCanonical (*pspExpanded, false); // Doesn't copy.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("SET: setCanonical: %d %d %s") % bKeepDirect % pspExpanded->vpnNodes.size() % pspExpanded->getJson());
psCanonical.setCanonical (*pspExpanded); // Convert.
// Non-obvious defaults have 0 nodes when canonicalized.
if (bKeepDefault || psCanonical.vpnNodes.size ())
{
STPath spCanonical;
BOOST_FOREACH (const PaymentNode & pnElem, psCanonical.vpnNodes)
{
STPathElement speElem (pnElem.uFlags, pnElem.uAccountID, pnElem.uCurrencyID, pnElem.uIssuerID);
spCanonical.addElement (speElem);
}
spsDst.addPath (spCanonical);
}
}
}
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("SET: setCanonical< %d") % spsDst.size());
}
// This is for debugging not end users. Output names can be changed without warning.
Json::Value PathState::getJson () const
{
Json::Value jvPathState (Json::objectValue);
Json::Value jvNodes (Json::arrayValue);
BOOST_FOREACH (const PaymentNode & pnNode, vpnNodes)
{
jvNodes.append (pnNode.getJson ());
}
jvPathState["status"] = terStatus;
jvPathState["index"] = mIndex;
jvPathState["nodes"] = jvNodes;
if (saInReq)
jvPathState["in_req"] = saInReq.getJson (0);
if (saInAct)
jvPathState["in_act"] = saInAct.getJson (0);
if (saInPass)
jvPathState["in_pass"] = saInPass.getJson (0);
if (saOutReq)
jvPathState["out_req"] = saOutReq.getJson (0);
if (saOutAct)
jvPathState["out_act"] = saOutAct.getJson (0);
if (saOutPass)
jvPathState["out_pass"] = saOutPass.getJson (0);
if (uQuality)
jvPathState["uQuality"] = boost::str (boost::format ("%d") % uQuality);
return jvPathState;
}
//
// RippleCalc implementation
//
// If needed, advance to next funded offer.
// - Automatically advances to first offer.
// --> bEntryAdvance: true, to advance to next entry. false, recalculate.
@@ -835,8 +17,8 @@ TER RippleCalc::calcNodeAdvance (
const bool bMultiQuality,
const bool bReverse)
{
PaymentNode& pnPrv = psCur.vpnNodes[uNode - 1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
const uint160& uPrvCurrencyID = pnPrv.uCurrencyID;
const uint160& uPrvIssuerID = pnPrv.uIssuerID;
@@ -1144,8 +326,8 @@ TER RippleCalc::calcNodeDeliverRev (
{
TER terResult = tesSUCCESS;
PaymentNode& pnPrv = psCur.vpnNodes[uNode - 1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
const uint160& uCurIssuerID = pnCur.uIssuerID;
const uint160& uPrvAccountID = pnPrv.uAccountID;
@@ -1424,9 +606,9 @@ TER RippleCalc::calcNodeDeliverFwd (
{
TER terResult = tesSUCCESS;
PaymentNode& pnPrv = psCur.vpnNodes[uNode - 1];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode + 1];
PathState::Node& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnNxt = psCur.vpnNodes[uNode + 1];
const uint160& uNxtAccountID = pnNxt.uAccountID;
const uint160& uCurCurrencyID = pnCur.uCurrencyID;
@@ -1695,8 +877,8 @@ TER RippleCalc::calcNodeOfferRev (
{
TER terResult;
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode + 1];
PathState::Node& pnCur = psCur.vpnNodes [uNode];
PathState::Node& pnNxt = psCur.vpnNodes [uNode + 1];
if (!!pnNxt.uAccountID)
{
@@ -1742,7 +924,7 @@ TER RippleCalc::calcNodeOfferFwd (
)
{
TER terResult;
PaymentNode& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnPrv = psCur.vpnNodes [uNode - 1];
if (!!pnPrv.uAccountID)
{
@@ -1899,9 +1081,9 @@ TER RippleCalc::calcNodeAccountRev (const unsigned int uNode, PathState& psCur,
uint64 uRateMax = 0;
PaymentNode& pnPrv = psCur.vpnNodes[uNode ? uNode - 1 : 0];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode + 1];
PathState::Node& pnPrv = psCur.vpnNodes[uNode ? uNode - 1 : 0];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode + 1];
// Current is allowed to redeem to next.
const bool bPrvAccount = !uNode || isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
@@ -2257,14 +1439,14 @@ TER RippleCalc::calcNodeAccountFwd (
PathState& psCur,
const bool bMultiQuality)
{
TER terResult = tesSUCCESS;
const unsigned int uLast = psCur.vpnNodes.size () - 1;
TER terResult = tesSUCCESS;
const unsigned int uLast = psCur.vpnNodes.size () - 1;
uint64 uRateMax = 0;
uint64 uRateMax = 0;
PaymentNode& pnPrv = psCur.vpnNodes[uNode ? uNode - 1 : 0];
PaymentNode& pnCur = psCur.vpnNodes[uNode];
PaymentNode& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode + 1];
PathState::Node& pnPrv = psCur.vpnNodes[uNode ? uNode - 1 : 0];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode + 1];
const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
@@ -2591,7 +1773,7 @@ TER RippleCalc::calcNodeAccountFwd (
TER RippleCalc::calcNodeFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
const PaymentNode& pnCur = psCur.vpnNodes[uNode];
const PathState::Node& pnCur = psCur.vpnNodes[uNode];
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeFwd> uNode=%d") % uNode);
@@ -2621,9 +1803,9 @@ TER RippleCalc::calcNodeFwd (const unsigned int uNode, PathState& psCur, const b
// <-> [0]saWanted.mAmount : --> limit, <-- actual
TER RippleCalc::calcNodeRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
PaymentNode& pnCur = psCur.vpnNodes[uNode];
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
TER terResult;
PathState::Node& pnCur = psCur.vpnNodes[uNode];
bool const bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
TER terResult;
// Do current node reverse.
const uint160& uCurIssuerID = pnCur.uIssuerID;
@@ -2685,7 +1867,7 @@ void RippleCalc::pathNext (PathState::ref psrCur, const bool bMultiQuality, cons
for (unsigned int uIndex = psrCur->vpnNodes.size (); uIndex--;)
{
PaymentNode& pnCur = psrCur->vpnNodes[uIndex];
PathState::Node& pnCur = psrCur->vpnNodes[uIndex];
pnCur.saRevRedeem.zero ();
pnCur.saRevIssue.zero ();
@@ -3081,7 +2263,7 @@ TER RippleCalc::rippleCalc (
// <-- terResult : tesSUCCESS = no error and if !bAllowPartial complelely satisfied wanted.
// <-> usOffersDeleteAlways:
// <-> usOffersDeleteOnSuccess:
TER calcOfferFill (PaymentNode& pnSrc, PaymentNode& pnDst, bool bAllowPartial)
TER calcOfferFill (Node& pnSrc, Node& pnDst, bool bAllowPartial)
{
TER terResult;

View File

@@ -0,0 +1,78 @@
#ifndef RIPPLE_RIPPLECALC_H
#define RIPPLE_RIPPLECALC_H
// VFALCO TODO What's the difference between a RippleCalc versus Pathfinder?
// Or a RippleState versus PathState?
//
class RippleCalc
{
public:
// First time working in reverse a funding source was mentioned. Source may only be used there.
curIssuerNode mumSource; // Map of currency, issuer to node index.
// If the transaction fails to meet some constraint, still need to delete unfunded offers.
boost::unordered_set<uint256> musUnfundedFound; // Offers that were found unfunded.
void pathNext (PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNode (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAdvance (const unsigned int uNode, PathState& psCur, const bool bMultiQuality, const bool bReverse);
TER calcNodeDeliverRev (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uOutAccountID,
const STAmount& saOutReq,
STAmount& saOutAct);
TER calcNodeDeliverFwd (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uInAccountID,
const STAmount& saInReq,
STAmount& saInAct,
STAmount& saInFees);
void calcNodeRipple (const uint32 uQualityIn, const uint32 uQualityOut,
const STAmount& saPrvReq, const STAmount& saCurReq,
STAmount& saPrvAct, STAmount& saCurAct,
uint64& uRateMax);
RippleCalc (LedgerEntrySet& lesNodes, const bool bOpenLedger)
: lesActive (lesNodes), mOpenLedger (bOpenLedger)
{
;
}
static TER rippleCalc (
LedgerEntrySet& lesActive,
STAmount& saMaxAmountAct,
STAmount& saDstAmountAct,
std::vector<PathState::pointer>& vpsExpanded,
const STAmount& saDstAmountReq,
const STAmount& saMaxAmountReq,
const uint160& uDstAccountID,
const uint160& uSrcAccountID,
const STPathSet& spsPaths,
const bool bPartialPayment,
const bool bLimitQuality,
const bool bNoRippleDirect,
const bool bStandAlone, // --> True, not to affect accounts.
const bool bOpenLedger = true // --> What kind of errors to return.
);
static void setCanonical (STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded, bool bKeepDefault);
protected:
LedgerEntrySet& lesActive;
bool mOpenLedger;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,13 @@
AccountItems& RippleLineCache::getRippleLines (const uint160& accountID)
{
boost::mutex::scoped_lock sl (mLock);
boost::unordered_map <uint160, AccountItems::pointer>::iterator it = mRLMap.find (accountID);
if (it == mRLMap.end ())
it = mRLMap.insert (std::make_pair (accountID, boost::make_shared<AccountItems>
(boost::cref (accountID), boost::cref (mLedger), AccountItem::pointer (new RippleState ())))).first;
return *it->second;
}

View File

@@ -0,0 +1,31 @@
#ifndef RIPPLE_RIPPLELINECACHE_H
#define RIPPLE_RIPPLELINECACHE_H
// Used by Pathfinder
class RippleLineCache
{
public:
typedef boost::shared_ptr <RippleLineCache> pointer;
typedef pointer const& ref;
explicit RippleLineCache (Ledger::ref l)
: mLedger (l)
{
}
Ledger::ref getLedger () // VFALCO TODO const?
{
return mLedger;
}
AccountItems& getRippleLines (const uint160& accountID);
private:
boost::mutex mLock;
Ledger::pointer mLedger;
boost::unordered_map <uint160, AccountItems::pointer> mRLMap;
};
#endif

View File

@@ -1,5 +1,5 @@
#ifndef __RIPPLESTATE__
#define __RIPPLESTATE__
#ifndef RIPPLE_RIPPLESTATE_H
#define RIPPLE_RIPPLESTATE_H
//
// A ripple line's state.
@@ -9,7 +9,89 @@
class RippleState : public AccountItem
{
public:
typedef boost::shared_ptr<RippleState> pointer;
typedef boost::shared_ptr <RippleState> pointer;
public:
RippleState () { }
virtual ~RippleState () { }
AccountItem::pointer makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry);
LedgerEntryType getType ()
{
return ltRIPPLE_STATE;
}
void setViewAccount (const uint160& accountID);
const uint160& getAccountID () const
{
return mViewLowest ? mLowID : mHighID;
}
const uint160& getAccountIDPeer () const
{
return !mViewLowest ? mLowID : mHighID;
}
// True, Provided auth to peer.
bool getAuth () const
{
return isSetBit (mFlags, mViewLowest ? lsfLowAuth : lsfHighAuth);
}
bool getAuthPeer () const
{
return isSetBit (mFlags, !mViewLowest ? lsfLowAuth : lsfHighAuth);
}
const STAmount& getBalance () const
{
return mBalance;
}
const STAmount& getLimit () const
{
return mViewLowest ? mLowLimit : mHighLimit;
}
const STAmount& getLimitPeer () const
{
return !mViewLowest ? mLowLimit : mHighLimit;
}
uint32 getQualityIn () const
{
return ((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn));
}
uint32 getQualityOut () const
{
return ((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut));
}
SerializedLedgerEntry::pointer getSLE ()
{
return mLedgerEntry;
}
const SerializedLedgerEntry& peekSLE () const
{
return *mLedgerEntry;
}
SerializedLedgerEntry& peekSLE ()
{
return *mLedgerEntry;
}
Json::Value getJson (int);
Blob getRaw () const;
private:
explicit RippleState (SerializedLedgerEntry::ref ledgerEntry); // For accounts in a ledger
private:
bool mValid;
@@ -29,76 +111,6 @@ private:
uint64 mHighQualityOut;
STAmount mBalance;
RippleState (SerializedLedgerEntry::ref ledgerEntry); // For accounts in a ledger
public:
RippleState () { }
virtual ~RippleState () {}
AccountItem::pointer makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry);
LedgerEntryType getType ()
{
return (ltRIPPLE_STATE);
}
void setViewAccount (const uint160& accountID);
const uint160& getAccountID () const
{
return mViewLowest ? mLowID : mHighID;
}
const uint160& getAccountIDPeer () const
{
return !mViewLowest ? mLowID : mHighID;
}
// True, Provided auth to peer.
bool getAuth () const
{
return isSetBit (mFlags, mViewLowest ? lsfLowAuth : lsfHighAuth);
}
bool getAuthPeer () const
{
return isSetBit (mFlags, !mViewLowest ? lsfLowAuth : lsfHighAuth);
}
const STAmount& getBalance () const
{
return mBalance;
}
const STAmount& getLimit () const
{
return mViewLowest ? mLowLimit : mHighLimit;
}
const STAmount& getLimitPeer () const
{
return !mViewLowest ? mLowLimit : mHighLimit;
}
uint32 getQualityIn () const
{
return ((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn));
}
uint32 getQualityOut () const
{
return ((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut));
}
SerializedLedgerEntry::pointer getSLE ()
{
return mLedgerEntry;
}
const SerializedLedgerEntry& peekSLE () const
{
return *mLedgerEntry;
}
SerializedLedgerEntry& peekSLE ()
{
return *mLedgerEntry;
}
Json::Value getJson (int);
Blob getRaw () const;
};
#endif
// vim:ts=4

View File

@@ -45,7 +45,7 @@ bool ConsensusTransSetSF::haveNode (const SHAMapNode& id, uint256 const& nodeHas
// this is a transaction, and we have it
WriteLog (lsDEBUG, TransactionAcquire) << "Node in our acquiring TX set is TXN we have";
Serializer s;
s.add32 (sHP_TransactionID);
s.add32 (HashPrefix::transactionID);
txn->getSTransaction ()->add (s, true);
assert (s.getSHA512Half () == nodeHash);
nodeData = s.peekData ();

View File

@@ -44,7 +44,7 @@ SHAMapTreeNode::SHAMapTreeNode (const SHAMapNode& id, Blob const& rawNode, uint3
if (type == 0)
{
// transaction
mItem = boost::make_shared<SHAMapItem> (s.getPrefixHash (sHP_TransactionID), s.peekData ());
mItem = boost::make_shared<SHAMapItem> (s.getPrefixHash (HashPrefix::transactionID), s.peekData ());
mType = tnTRANSACTION_NM;
}
else if (type == 1)
@@ -131,12 +131,12 @@ SHAMapTreeNode::SHAMapTreeNode (const SHAMapNode& id, Blob const& rawNode, uint3
prefix |= rawNode[3];
Serializer s (rawNode.begin () + 4, rawNode.end ());
if (prefix == sHP_TransactionID)
if (prefix == HashPrefix::transactionID)
{
mItem = boost::make_shared<SHAMapItem> (Serializer::getSHA512Half (rawNode), s.peekData ());
mType = tnTRANSACTION_NM;
}
else if (prefix == sHP_LeafNode)
else if (prefix == HashPrefix::leafNode)
{
if (s.getLength () < 32)
throw std::runtime_error ("short PLN node");
@@ -154,7 +154,7 @@ SHAMapTreeNode::SHAMapTreeNode (const SHAMapNode& id, Blob const& rawNode, uint3
mItem = boost::make_shared<SHAMapItem> (u, s.peekData ());
mType = tnACCOUNT_STATE;
}
else if (prefix == sHP_InnerNode)
else if (prefix == HashPrefix::innerNode)
{
if (s.getLength () != 512)
throw std::runtime_error ("invalid PIN node");
@@ -169,7 +169,7 @@ SHAMapTreeNode::SHAMapTreeNode (const SHAMapNode& id, Blob const& rawNode, uint3
mType = tnINNER;
}
else if (prefix == sHP_TransactionNode)
else if (prefix == HashPrefix::txNode)
{
// transaction with metadata
if (s.getLength () < 32)
@@ -214,10 +214,10 @@ bool SHAMapTreeNode::updateHash ()
{
if (mIsBranch != 0)
{
nh = Serializer::getPrefixHash (sHP_InnerNode, reinterpret_cast<unsigned char*> (mHashes), sizeof (mHashes));
nh = Serializer::getPrefixHash (HashPrefix::innerNode, reinterpret_cast<unsigned char*> (mHashes), sizeof (mHashes));
#ifdef PARANOID
Serializer s;
s.add32 (sHP_InnerNode);
s.add32 (HashPrefix::innerNode);
for (int i = 0; i < 16; ++i)
s.add256 (mHashes[i]);
@@ -230,12 +230,12 @@ bool SHAMapTreeNode::updateHash ()
}
else if (mType == tnTRANSACTION_NM)
{
nh = Serializer::getPrefixHash (sHP_TransactionID, mItem->peekData ());
nh = Serializer::getPrefixHash (HashPrefix::transactionID, mItem->peekData ());
}
else if (mType == tnACCOUNT_STATE)
{
Serializer s (mItem->peekSerializer ().getDataLength () + (256 + 32) / 8);
s.add32 (sHP_LeafNode);
s.add32 (HashPrefix::leafNode);
s.addRaw (mItem->peekData ());
s.add256 (mItem->getTag ());
nh = s.getSHA512Half ();
@@ -243,7 +243,7 @@ bool SHAMapTreeNode::updateHash ()
else if (mType == tnTRANSACTION_MD)
{
Serializer s (mItem->peekSerializer ().getDataLength () + (256 + 32) / 8);
s.add32 (sHP_TransactionNode);
s.add32 (HashPrefix::txNode);
s.addRaw (mItem->peekData ());
s.add256 (mItem->getTag ());
nh = s.getSHA512Half ();
@@ -275,7 +275,7 @@ void SHAMapTreeNode::addRaw (Serializer& s, SHANodeFormat format)
if (format == snfPREFIX)
{
s.add32 (sHP_InnerNode);
s.add32 (HashPrefix::innerNode);
for (int i = 0; i < 16; ++i)
s.add256 (mHashes[i]);
@@ -307,7 +307,7 @@ void SHAMapTreeNode::addRaw (Serializer& s, SHANodeFormat format)
{
if (format == snfPREFIX)
{
s.add32 (sHP_LeafNode);
s.add32 (HashPrefix::leafNode);
s.addRaw (mItem->peekData ());
s.add256 (mItem->getTag ());
}
@@ -322,7 +322,7 @@ void SHAMapTreeNode::addRaw (Serializer& s, SHANodeFormat format)
{
if (format == snfPREFIX)
{
s.add32 (sHP_TransactionID);
s.add32 (HashPrefix::transactionID);
s.addRaw (mItem->peekData ());
}
else
@@ -335,7 +335,7 @@ void SHAMapTreeNode::addRaw (Serializer& s, SHANodeFormat format)
{
if (format == snfPREFIX)
{
s.add32 (sHP_TransactionNode);
s.add32 (HashPrefix::txNode);
s.addRaw (mItem->peekData ());
s.add256 (mItem->getTag ());
}

View File

@@ -3,7 +3,21 @@
DEFINE_INSTANCE (SerializedLedgerEntry);
// VFALCO TODO rename this to SerializedLedger?
// VFALCO NOTE
//
// This looks like a central class for Ripple. Almost everything that
// does anything of interest deals with SLE objects. Any documentation
// effort should start with a complete description of this object and
// all of its operations.
//
// It is derived from STObject so it inherits a lot of behavior from that.
//
// VFALCO TODO Rename the source file to match the class
//
// VFALCO TODO Can we rename this class to something shorter and more concise?
//
// Can we just call this LedgerEntry?
//
class SerializedLedgerEntry : public STObject, private IS_INSTANCE (SerializedLedgerEntry)
{
public:

View File

@@ -3,10 +3,13 @@ SETUP_LOG (SerializedTransaction)
DECLARE_INSTANCE (SerializedTransaction);
SerializedTransaction::SerializedTransaction (TransactionType type) : STObject (sfTransaction), mType (type),
mSigGood (false), mSigBad (false)
SerializedTransaction::SerializedTransaction (TransactionType type)
: STObject (sfTransaction)
, mType (type)
, mSigGood (false)
, mSigBad (false)
{
mFormat = TransactionFormat::getTxnFormat (type);
mFormat = TxFormats::getInstance ().findByType (type);
if (mFormat == NULL)
{
@@ -15,14 +18,17 @@ SerializedTransaction::SerializedTransaction (TransactionType type) : STObject (
}
set (mFormat->elements);
setFieldU16 (sfTransactionType, mFormat->t_type);
setFieldU16 (sfTransactionType, mFormat->getType ());
}
SerializedTransaction::SerializedTransaction (const STObject& object) : STObject (object),
mSigGood (false), mSigBad (false)
SerializedTransaction::SerializedTransaction (STObject const& object)
: STObject (object)
, mSigGood (false)
, mSigBad (false)
{
mType = static_cast<TransactionType> (getFieldU16 (sfTransactionType));
mFormat = TransactionFormat::getTxnFormat (mType);
mType = static_cast <TransactionType> (getFieldU16 (sfTransactionType));
mFormat = TxFormats::getInstance ().findByType (mType);
if (!mFormat)
{
@@ -41,7 +47,7 @@ SerializedTransaction::SerializedTransaction (SerializerIterator& sit) : STObjec
{
int length = sit.getBytesLeft ();
if ((length < TransactionMinLen) || (length > TransactionMaxLen))
if ((length < Protocol::txMinSizeBytes) || (length > Protocol::txMaxSizeBytes))
{
Log (lsERROR) << "Transaction has invalid length: " << length;
throw std::runtime_error ("Transaction length invalid");
@@ -50,7 +56,7 @@ SerializedTransaction::SerializedTransaction (SerializerIterator& sit) : STObjec
set (sit);
mType = static_cast<TransactionType> (getFieldU16 (sfTransactionType));
mFormat = TransactionFormat::getTxnFormat (mType);
mFormat = TxFormats::getInstance ().findByType (mType);
if (!mFormat)
{
@@ -141,7 +147,7 @@ uint256 SerializedTransaction::getSigningHash () const
uint256 SerializedTransaction::getTransactionID () const
{
// perhaps we should cache this
return getHash (sHP_TransactionID);
return getHash (HashPrefix::transactionID);
}
Blob SerializedTransaction::getSignature () const

View File

@@ -64,7 +64,7 @@ public:
void setSourceAccount (const RippleAddress & naSource);
std::string getTransactionType () const
{
return mFormat->t_name;
return mFormat->getName ();
}
uint32 getSequence () const
@@ -120,7 +120,7 @@ public:
private:
TransactionType mType;
const TransactionFormat* mFormat;
const TxFormat* mFormat;
SerializedTransaction* duplicate () const
{