mirror of
https://github.com/Xahau/xahaud.git
synced 2026-06-02 08:16:42 +00:00
Merge branch 'master' of github.com:jedmccaleb/NewCoin
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
"simple-jsonrpc": "~0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.3.17",
|
||||
"buster": "~0.6.2",
|
||||
"grunt-webpack": "~0.4.0"
|
||||
},
|
||||
|
||||
@@ -235,6 +235,12 @@
|
||||
# [database_path]:
|
||||
# Full path of database directory.
|
||||
#
|
||||
# [path_search_size]
|
||||
# When searching for paths, the maximum number of nodes allowed. This can take
|
||||
# exponentially more resource as the size is increasded.
|
||||
#
|
||||
# The default is: 5
|
||||
#
|
||||
# [rpc_startup]:
|
||||
# Specify a list of RPC commands to run at startup.
|
||||
#
|
||||
|
||||
@@ -25,7 +25,7 @@ Application* theApp = NULL;
|
||||
|
||||
DatabaseCon::DatabaseCon(const std::string& strName, const char *initStrings[], int initCount)
|
||||
{
|
||||
boost::filesystem::path pPath = theConfig.DATA_DIR / strName;
|
||||
boost::filesystem::path pPath = theConfig.RUN_STANDALONE ? "" : theConfig.DATA_DIR / strName;
|
||||
|
||||
mDatabase = new SqliteDatabase(pPath.string().c_str());
|
||||
mDatabase->connect();
|
||||
@@ -49,10 +49,6 @@ Application::Application() :
|
||||
{
|
||||
getRand(mNonce256.begin(), mNonce256.size());
|
||||
getRand(reinterpret_cast<unsigned char *>(&mNonceST), sizeof(mNonceST));
|
||||
mJobQueue.setThreadCount();
|
||||
mSweepTimer.expires_from_now(boost::posix_time::seconds(10));
|
||||
mSweepTimer.async_wait(boost::bind(&Application::sweep, this));
|
||||
mLoadMgr.init();
|
||||
}
|
||||
|
||||
extern const char *RpcDBInit[], *TxnDBInit[], *LedgerDBInit[], *WalletDBInit[], *HashNodeDBInit[], *NetNodeDBInit[];
|
||||
@@ -88,6 +84,11 @@ void sigIntHandler(int)
|
||||
|
||||
void Application::setup()
|
||||
{
|
||||
mJobQueue.setThreadCount();
|
||||
mSweepTimer.expires_from_now(boost::posix_time::seconds(10));
|
||||
mSweepTimer.async_wait(boost::bind(&Application::sweep, this));
|
||||
mLoadMgr.init();
|
||||
|
||||
#ifndef WIN32
|
||||
#ifdef SIGINT
|
||||
if (!theConfig.RUN_STANDALONE)
|
||||
|
||||
@@ -512,6 +512,7 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams)
|
||||
// { "nickname_info", &RPCParser::parseNicknameInfo, 1, 1 },
|
||||
{ "owner_info", &RPCParser::parseOwnerInfo, 1, 2 },
|
||||
{ "peers", &RPCParser::parseAsIs, 0, 0 },
|
||||
{ "ping", &RPCParser::parseAsIs, 0, 0 },
|
||||
// { "profile", &RPCParser::parseProfile, 1, 9 },
|
||||
{ "random", &RPCParser::parseAsIs, 0, 0 },
|
||||
{ "ripple_path_find", &RPCParser::parseRipplePathFind, 1, 1 },
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#define SECTION_NETWORK_QUORUM "network_quorum"
|
||||
#define SECTION_NODE_SEED "node_seed"
|
||||
#define SECTION_NODE_SIZE "node_size"
|
||||
#define SECTION_PATH_SEARCH_SIZE "path_search_size"
|
||||
#define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water"
|
||||
#define SECTION_PEER_IP "peer_ip"
|
||||
#define SECTION_PEER_PORT "peer_port"
|
||||
@@ -219,6 +220,7 @@ Config::Config()
|
||||
|
||||
LEDGER_HISTORY = 256;
|
||||
|
||||
PATH_SEARCH_SIZE = DEFAULT_PATH_SEARCH_SIZE;
|
||||
ACCOUNT_PROBE_MAX = 10;
|
||||
|
||||
VALIDATORS_SITE = DEFAULT_VALIDATORS_SITE;
|
||||
@@ -445,6 +447,9 @@ void Config::load()
|
||||
LEDGER_HISTORY = boost::lexical_cast<uint32>(strTemp);
|
||||
}
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_PATH_SEARCH_SIZE, strTemp))
|
||||
PATH_SEARCH_SIZE = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
if (sectionSingleB(secConfig, SECTION_ACCOUNT_PROBE_MAX, strTemp))
|
||||
ACCOUNT_PROBE_MAX = boost::lexical_cast<int>(strTemp);
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ const int SYSTEM_WEBSOCKET_PUBLIC_PORT = 6563; // XXX Going away.
|
||||
// Might connect with fewer for testing.
|
||||
#define DEFAULT_PEER_CONNECT_LOW_WATER 4
|
||||
|
||||
// Grows exponentially worse.
|
||||
#define DEFAULT_PATH_SEARCH_SIZE 5
|
||||
|
||||
enum SizedItemName
|
||||
{
|
||||
siSweepInterval,
|
||||
@@ -142,6 +145,9 @@ public:
|
||||
bool RPC_ALLOW_REMOTE;
|
||||
Json::Value RPC_STARTUP;
|
||||
|
||||
// Path searching
|
||||
int PATH_SEARCH_SIZE;
|
||||
|
||||
// Validation
|
||||
RippleAddress VALIDATION_SEED, VALIDATION_PUB, VALIDATION_PRIV;
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ const char* Job::toString(JobType t)
|
||||
case jtRPC: return "rpc";
|
||||
case jtACCEPTLEDGER: return "acceptLedger";
|
||||
case jtTXN_PROC: return "processTransaction";
|
||||
case jtOB_SETUP: return "orderBookSetup";
|
||||
case jtPATH_FIND: return "pathFind";
|
||||
default: assert(false); return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -101,7 +103,9 @@ void JobQueue::addJob(JobType type, const boost::function<void(Job&)>& jobFunc)
|
||||
assert(type != jtINVALID);
|
||||
|
||||
boost::mutex::scoped_lock sl(mJobLock);
|
||||
assert(mThreadCount != 0); // do not add jobs to a queue with no threads
|
||||
|
||||
if (type != jtCLIENT) // FIXME: Workaround incorrect client shutdown ordering
|
||||
assert(mThreadCount != 0); // do not add jobs to a queue with no threads
|
||||
|
||||
mJobSet.insert(Job(type, ++mLastJob, mJobLoads[type], jobFunc));
|
||||
++mJobCounts[type];
|
||||
|
||||
@@ -37,13 +37,15 @@ enum JobType
|
||||
jtDEATH = 14, // job of death, used internally
|
||||
|
||||
// special types not dispatched by the job pool
|
||||
jtPEER = 17,
|
||||
jtDISK = 18,
|
||||
jtRPC = 19,
|
||||
jtACCEPTLEDGER = 20,
|
||||
jtTXN_PROC = 21,
|
||||
jtPEER = 24,
|
||||
jtDISK = 25,
|
||||
jtRPC = 26,
|
||||
jtACCEPTLEDGER = 27,
|
||||
jtTXN_PROC = 28,
|
||||
jtOB_SETUP = 29,
|
||||
jtPATH_FIND = 30
|
||||
}; // CAUTION: If you add new types, add them to JobType.cpp too
|
||||
#define NUM_JOB_TYPES 24
|
||||
#define NUM_JOB_TYPES 32
|
||||
|
||||
class Job
|
||||
{
|
||||
|
||||
@@ -362,6 +362,23 @@ bool Ledger::getTransaction(const uint256& txID, Transaction::pointer& txn, Tran
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ledger::getTransactionMeta(const uint256& txID, TransactionMetaSet::pointer& meta)
|
||||
{
|
||||
SHAMapTreeNode::TNType type;
|
||||
SHAMapItem::pointer item = mTransactionMap->peekItem(txID, type);
|
||||
if (!item)
|
||||
return false;
|
||||
|
||||
if (type != SHAMapTreeNode::tnTRANSACTION_MD)
|
||||
return false;
|
||||
|
||||
SerializerIterator it(item->peekSerializer());
|
||||
it.getVL(); // skip transaction
|
||||
meta = boost::make_shared<TransactionMetaSet>(txID, mLedgerSeq, it.getVL());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint256 Ledger::getHash()
|
||||
{
|
||||
if (!mValidHash)
|
||||
@@ -1231,7 +1248,7 @@ uint256 Ledger::getBookBase(const uint160& uTakerPaysCurrency, const uint160& uT
|
||||
|
||||
uint256 uBaseIndex = getQualityIndex(s.getSHA512Half()); // Return with quality 0.
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("getBookBase(%s,%s,%s,%s) = %s")
|
||||
cLog(lsTRACE) << boost::str(boost::format("getBookBase(%s,%s,%s,%s) = %s")
|
||||
% STAmount::createHumanCurrency(uTakerPaysCurrency)
|
||||
% RippleAddress::createHumanAccountID(uTakerPaysIssuerID)
|
||||
% STAmount::createHumanCurrency(uTakerGetsCurrency)
|
||||
|
||||
@@ -179,6 +179,7 @@ public:
|
||||
bool hasTransaction(const uint256& TransID) const { return mTransactionMap->hasItem(TransID); }
|
||||
Transaction::pointer getTransaction(const uint256& transID) const;
|
||||
bool getTransaction(const uint256& transID, Transaction::pointer& txn, TransactionMetaSet::pointer& txMeta);
|
||||
bool getTransactionMeta(const uint256& transID, TransactionMetaSet::pointer& txMeta);
|
||||
|
||||
static SerializedTransaction::pointer getSTransaction(SHAMapItem::ref, SHAMapTreeNode::TNType);
|
||||
SerializedTransaction::pointer getSMTransaction(SHAMapItem::ref, SHAMapTreeNode::TNType,
|
||||
|
||||
@@ -1156,7 +1156,7 @@ int LedgerConsensus::applyTransaction(TransactionEngine& engine, SerializedTrans
|
||||
return LCAT_SUCCESS;
|
||||
}
|
||||
|
||||
if (isTefFailure(result) || isTemMalformed(result))
|
||||
if (isTefFailure(result) || isTemMalformed(result) || isTelLocal(result))
|
||||
{ // failure
|
||||
cLog(lsDEBUG) << "Transaction failure: " << transHuman(result);
|
||||
return LCAT_FAIL;
|
||||
|
||||
@@ -1072,25 +1072,31 @@ STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160&
|
||||
SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID));
|
||||
uint64 uReserve = mLedger->getReserve(sleAccount->getFieldU32(sfOwnerCount));
|
||||
|
||||
saAmount = sleAccount->getFieldAmount(sfBalance)-uReserve;
|
||||
STAmount saBalance = sleAccount->getFieldAmount(sfBalance);
|
||||
|
||||
if (saAmount < uReserve)
|
||||
if (saBalance < uReserve)
|
||||
{
|
||||
saAmount.zero();
|
||||
}
|
||||
else
|
||||
{
|
||||
saAmount -= uReserve;
|
||||
saAmount = saBalance-uReserve;
|
||||
}
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s saBalance=%s uReserve=%d")
|
||||
% RippleAddress::createHumanAccountID(uAccountID)
|
||||
% saAmount.getFullText()
|
||||
% saBalance.getFullText()
|
||||
% uReserve);
|
||||
}
|
||||
else
|
||||
{
|
||||
saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID);
|
||||
}
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s")
|
||||
% RippleAddress::createHumanAccountID(uAccountID)
|
||||
% saAmount.getFullText());
|
||||
cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s")
|
||||
% RippleAddress::createHumanAccountID(uAccountID)
|
||||
% saAmount.getFullText());
|
||||
}
|
||||
|
||||
return saAmount;
|
||||
}
|
||||
@@ -1135,7 +1141,7 @@ STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint1
|
||||
|
||||
if (QUALITY_ONE != uTransitRate)
|
||||
{
|
||||
STAmount saTransitRate(CURRENCY_ONE, ACCOUNT_ONE, uTransitRate, -9);
|
||||
STAmount saTransitRate(CURRENCY_ONE, ACCOUNT_ONE, static_cast<uint64>(uTransitRate), -9);
|
||||
|
||||
STAmount saTransferTotal = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer());
|
||||
STAmount saTransferFee = saTransferTotal-saAmount;
|
||||
|
||||
@@ -29,6 +29,8 @@ void LedgerMaster::pushLedger(Ledger::ref newLedger)
|
||||
// all candidate transactions must already be applied
|
||||
cLog(lsINFO) << "PushLedger: " << newLedger->getHash();
|
||||
boost::recursive_mutex::scoped_lock ml(mLock);
|
||||
if (!mPubLedger)
|
||||
mPubLedger = newLedger;
|
||||
if (!!mFinalizedLedger)
|
||||
{
|
||||
mFinalizedLedger->setClosed();
|
||||
|
||||
@@ -93,6 +93,14 @@ public:
|
||||
|
||||
Ledger::pointer closeLedger(bool recoverHeldTransactions);
|
||||
|
||||
uint256 getHashBySeq(uint32 index)
|
||||
{
|
||||
uint256 hash = mLedgerHistory.getLedgerHash(index);
|
||||
if (hash.isNonZero())
|
||||
return hash;
|
||||
return Ledger::getHashByIndex(index);
|
||||
}
|
||||
|
||||
Ledger::pointer getLedgerBySeq(uint32 index)
|
||||
{
|
||||
if (mCurrentLedger && (mCurrentLedger->getLedgerSeq() == index))
|
||||
|
||||
@@ -242,24 +242,26 @@ void LoadFeeTrack::setRemoteFee(uint32 f)
|
||||
mRemoteTxnLoadFee = f;
|
||||
}
|
||||
|
||||
void LoadFeeTrack::raiseLocalFee()
|
||||
bool LoadFeeTrack::raiseLocalFee()
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mLock);
|
||||
uint32 origFee = mLocalTxnLoadFee;
|
||||
|
||||
if (mLocalTxnLoadFee < mLocalTxnLoadFee) // make sure this fee takes effect
|
||||
mLocalTxnLoadFee = mLocalTxnLoadFee;
|
||||
if (mLocalTxnLoadFee < mRemoteTxnLoadFee) // make sure this fee takes effect
|
||||
mLocalTxnLoadFee = mRemoteTxnLoadFee;
|
||||
|
||||
mLocalTxnLoadFee += (mLocalTxnLoadFee / lftFeeIncFraction); // increment by 1/16th
|
||||
|
||||
if (mLocalTxnLoadFee > lftFeeMax)
|
||||
mLocalTxnLoadFee = lftFeeMax;
|
||||
|
||||
tLog(origFee != mLocalTxnLoadFee, lsDEBUG) <<
|
||||
"Local load fee raised from " << origFee << " to " << mLocalTxnLoadFee;
|
||||
if (origFee == mLocalTxnLoadFee)
|
||||
return false;
|
||||
cLog(lsDEBUG) << "Local load fee raised from " << origFee << " to " << mLocalTxnLoadFee;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoadFeeTrack::lowerLocalFee()
|
||||
bool LoadFeeTrack::lowerLocalFee()
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mLock);
|
||||
uint32 origFee = mLocalTxnLoadFee;
|
||||
@@ -269,8 +271,10 @@ void LoadFeeTrack::lowerLocalFee()
|
||||
if (mLocalTxnLoadFee < lftNormalFee)
|
||||
mLocalTxnLoadFee = lftNormalFee;
|
||||
|
||||
tLog(origFee != mLocalTxnLoadFee, lsDEBUG) <<
|
||||
"Local load fee lowered from " << origFee << " to " << mLocalTxnLoadFee;
|
||||
if (origFee == mLocalTxnLoadFee)
|
||||
return false;
|
||||
cLog(lsDEBUG) << "Local load fee lowered from " << origFee << " to " << mLocalTxnLoadFee;
|
||||
return true;
|
||||
}
|
||||
|
||||
Json::Value LoadFeeTrack::getJson(uint64 baseFee, uint32 referenceFeeUnits)
|
||||
@@ -312,10 +316,13 @@ void LoadManager::threadEntry()
|
||||
++mUptime;
|
||||
}
|
||||
|
||||
bool change;
|
||||
if (theApp->getJobQueue().isOverloaded())
|
||||
theApp->getFeeTrack().raiseLocalFee();
|
||||
change = theApp->getFeeTrack().raiseLocalFee();
|
||||
else
|
||||
theApp->getFeeTrack().lowerLocalFee();
|
||||
change = theApp->getFeeTrack().lowerLocalFee();
|
||||
if (change)
|
||||
theApp->getOPs().reportFeeChange();
|
||||
|
||||
t += boost::posix_time::seconds(1);
|
||||
boost::posix_time::time_duration when = t - boost::posix_time::microsec_clock::universal_time();
|
||||
|
||||
@@ -164,8 +164,8 @@ public:
|
||||
Json::Value getJson(uint64 baseFee, uint32 referenceFeeUnits);
|
||||
|
||||
void setRemoteFee(uint32);
|
||||
void raiseLocalFee();
|
||||
void lowerLocalFee();
|
||||
bool raiseLocalFee();
|
||||
bool lowerLocalFee();
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ void InfoSub::onSendEmpty()
|
||||
NetworkOPs::NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster) :
|
||||
mMode(omDISCONNECTED), mNeedNetworkLedger(false), mProposing(false), mValidating(false),
|
||||
mNetTimer(io_service), mLedgerMaster(pLedgerMaster), mCloseTimeOffset(0), mLastCloseProposers(0),
|
||||
mLastCloseConvergeTime(1000 * LEDGER_IDLE_INTERVAL), mLastValidationTime(0)
|
||||
mLastCloseConvergeTime(1000 * LEDGER_IDLE_INTERVAL), mLastValidationTime(0),
|
||||
mLastLoadBase(256), mLastLoadFactor(256)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -118,6 +119,24 @@ bool NetworkOPs::haveLedger(uint32 seq)
|
||||
return mLedgerMaster->haveLedger(seq);
|
||||
}
|
||||
|
||||
uint32 NetworkOPs::getValidatedSeq()
|
||||
{
|
||||
return mLedgerMaster->getValidatedLedger()->getLedgerSeq();
|
||||
}
|
||||
|
||||
bool NetworkOPs::isValidated(uint32 seq, const uint256& hash)
|
||||
{
|
||||
if (!isValidated(seq))
|
||||
return false;
|
||||
|
||||
return mLedgerMaster->getHashBySeq(seq) == hash;
|
||||
}
|
||||
|
||||
bool NetworkOPs::isValidated(uint32 seq)
|
||||
{ // use when ledger was retrieved by seq
|
||||
return haveLedger(seq) && (seq <= mLedgerMaster->getValidatedLedger()->getLedgerSeq());
|
||||
}
|
||||
|
||||
bool NetworkOPs::addWantedHash(const uint256& h)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mWantedHashLock);
|
||||
@@ -1020,12 +1039,12 @@ void NetworkOPs::pubServer()
|
||||
|
||||
jvObj["type"] = "serverStatus";
|
||||
jvObj["server_status"] = strOperatingMode();
|
||||
jvObj["load_base"] = theApp->getFeeTrack().getLoadBase();
|
||||
jvObj["load_factor"] = theApp->getFeeTrack().getLoadFactor();
|
||||
jvObj["load_base"] = (mLastLoadBase = theApp->getFeeTrack().getLoadBase());
|
||||
jvObj["load_factor"] = (mLastLoadFactor = theApp->getFeeTrack().getLoadFactor());
|
||||
|
||||
BOOST_FOREACH(InfoSub* ispListener, mSubServer)
|
||||
{
|
||||
ispListener->send(jvObj);
|
||||
ispListener->send(jvObj, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1046,7 +1065,7 @@ void NetworkOPs::setMode(OperatingMode om)
|
||||
|
||||
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >
|
||||
NetworkOPs::getAccountTxs(const RippleAddress& account, uint32 minLedger, uint32 maxLedger)
|
||||
{
|
||||
{ // can be called with no locks
|
||||
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > ret;
|
||||
|
||||
std::string sql =
|
||||
@@ -1073,7 +1092,7 @@ std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >
|
||||
}else rawMeta.resize(metaSize);
|
||||
|
||||
TransactionMetaSet::pointer meta= boost::make_shared<TransactionMetaSet>(txn->getID(), txn->getLedger(), rawMeta.getData());
|
||||
ret.push_back(std::pair<Transaction::pointer, TransactionMetaSet::pointer>(txn,meta));
|
||||
ret.push_back(std::pair<Transaction::ref, TransactionMetaSet::ref>(txn,meta));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1162,7 +1181,7 @@ Json::Value NetworkOPs::getServerInfo(bool human, bool admin)
|
||||
}
|
||||
else
|
||||
info["load_factor"] =
|
||||
static_cast<double>(theApp->getFeeTrack().getLoadBase()) / theApp->getFeeTrack().getLoadFactor();
|
||||
static_cast<double>(theApp->getFeeTrack().getLoadFactor()) / theApp->getFeeTrack().getLoadBase();
|
||||
|
||||
bool valid = false;
|
||||
Ledger::pointer lpClosed = getValidatedLedger();
|
||||
@@ -1227,7 +1246,7 @@ void NetworkOPs::pubProposedTransaction(Ledger::ref lpCurrent, const SerializedT
|
||||
boost::recursive_mutex::scoped_lock sl(mMonitorLock);
|
||||
BOOST_FOREACH(InfoSub* ispListener, mSubRTTransactions)
|
||||
{
|
||||
ispListener->send(jvObj);
|
||||
ispListener->send(jvObj, true);
|
||||
}
|
||||
}
|
||||
TransactionMetaSet::pointer ret;
|
||||
@@ -1258,7 +1277,7 @@ void NetworkOPs::pubLedger(Ledger::ref lpAccepted)
|
||||
|
||||
BOOST_FOREACH(InfoSub* ispListener, mSubLedger)
|
||||
{
|
||||
ispListener->send(jvObj);
|
||||
ispListener->send(jvObj, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1285,6 +1304,15 @@ void NetworkOPs::pubLedger(Ledger::ref lpAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkOPs::reportFeeChange()
|
||||
{
|
||||
if ((theApp->getFeeTrack().getLoadBase() == mLastLoadBase) &&
|
||||
(theApp->getFeeTrack().getLoadFactor() == mLastLoadFactor))
|
||||
return;
|
||||
|
||||
theApp->getJobQueue().addJob(jtCLIENT, boost::bind(&NetworkOPs::pubServer, this));
|
||||
}
|
||||
|
||||
Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, TER terResult, bool bAccepted, Ledger::ref lpCurrent, const std::string& strType)
|
||||
{
|
||||
Json::Value jvObj(Json::objectValue);
|
||||
@@ -1323,12 +1351,12 @@ void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedT
|
||||
|
||||
BOOST_FOREACH(InfoSub* ispListener, mSubTransactions)
|
||||
{
|
||||
ispListener->send(jvObj);
|
||||
ispListener->send(jvObj, true);
|
||||
}
|
||||
|
||||
BOOST_FOREACH(InfoSub* ispListener, mSubRTTransactions)
|
||||
{
|
||||
ispListener->send(jvObj);
|
||||
ispListener->send(jvObj, true);
|
||||
}
|
||||
}
|
||||
theApp->getOrderBookDB().processTxn(stTxn, terResult, meta, jvObj);
|
||||
@@ -1339,6 +1367,8 @@ void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedT
|
||||
void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, bool bAccepted, TransactionMetaSet::pointer& meta)
|
||||
{
|
||||
boost::unordered_set<InfoSub*> notify;
|
||||
int iProposed = 0;
|
||||
int iAccepted = 0;
|
||||
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mMonitorLock);
|
||||
@@ -1356,6 +1386,7 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr
|
||||
{
|
||||
BOOST_FOREACH(InfoSub* ispListener, simiIt->second)
|
||||
{
|
||||
++iProposed;
|
||||
notify.insert(ispListener);
|
||||
}
|
||||
}
|
||||
@@ -1368,6 +1399,7 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr
|
||||
{
|
||||
BOOST_FOREACH(InfoSub* ispListener, simiIt->second)
|
||||
{
|
||||
++iAccepted;
|
||||
notify.insert(ispListener);
|
||||
}
|
||||
}
|
||||
@@ -1375,6 +1407,7 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr
|
||||
}
|
||||
}
|
||||
}
|
||||
cLog(lsINFO) << boost::str(boost::format("pubAccountTransaction: iProposed=%d iAccepted=%d") % iProposed % iAccepted);
|
||||
|
||||
// FIXME: This can crash. An InfoSub can go away while we hold a regular pointer to it.
|
||||
if (!notify.empty())
|
||||
@@ -1385,7 +1418,7 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr
|
||||
|
||||
BOOST_FOREACH(InfoSub* ispListener, notify)
|
||||
{
|
||||
ispListener->send(jvObj);
|
||||
ispListener->send(jvObj, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1401,6 +1434,8 @@ void NetworkOPs::subAccount(InfoSub* ispListener, const boost::unordered_set<Rip
|
||||
// For the connection, monitor each account.
|
||||
BOOST_FOREACH(const RippleAddress& naAccountID, vnaAccountIDs)
|
||||
{
|
||||
cLog(lsINFO) << boost::str(boost::format("subAccount: account: %d") % naAccountID.humanAccountID());
|
||||
|
||||
ispListener->insertSubAccountInfo(naAccountID, uLedgerIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
|
||||
virtual ~InfoSub();
|
||||
|
||||
virtual void send(const Json::Value& jvObj) = 0;
|
||||
virtual void send(const Json::Value& jvObj, bool broadcast) = 0;
|
||||
|
||||
void onSendEmpty();
|
||||
|
||||
@@ -116,6 +116,9 @@ protected:
|
||||
boost::recursive_mutex mWantedHashLock;
|
||||
boost::unordered_set<uint256> mWantedHashes;
|
||||
|
||||
uint32 mLastLoadBase;
|
||||
uint32 mLastLoadFactor;
|
||||
|
||||
void setMode(OperatingMode);
|
||||
|
||||
Json::Value transJson(const SerializedTransaction& stTxn, TER terResult, bool bAccepted, Ledger::ref lpCurrent, const std::string& strType);
|
||||
@@ -153,6 +156,10 @@ public:
|
||||
// Do we have this inclusive range of ledgers in our database
|
||||
bool haveLedgerRange(uint32 from, uint32 to);
|
||||
bool haveLedger(uint32 seq);
|
||||
uint32 getValidatedSeq();
|
||||
bool isValidated(uint32 seq);
|
||||
bool isValidated(uint32 seq, const uint256& hash);
|
||||
bool isValidated(Ledger::ref l) { return isValidated(l->getLedgerSeq(), l->getHash()); }
|
||||
|
||||
SerializedValidation::ref getLastValidation() { return mLastValidation; }
|
||||
void setLastValidation(SerializedValidation::ref v) { mLastValidation = v; }
|
||||
@@ -257,6 +264,7 @@ public:
|
||||
std::list<LedgerProposal::pointer> >& peekStoredProposals() { return mStoredProposals; }
|
||||
void storeProposal(LedgerProposal::ref proposal, const RippleAddress& peerPublic);
|
||||
uint256 getConsensusLCL();
|
||||
void reportFeeChange();
|
||||
|
||||
bool addWantedHash(const uint256& h);
|
||||
bool isWantedHash(const uint256& h, bool remove);
|
||||
|
||||
@@ -56,11 +56,13 @@ TER OfferCreateTransactor::takeOffers(
|
||||
while (temUNCERTAIN == terResult)
|
||||
{
|
||||
SLE::pointer sleOfferDir;
|
||||
uint64 uTipQuality = 0;
|
||||
uint64 uTipQuality = 0;
|
||||
STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays);
|
||||
|
||||
// Figure out next offer to take, if needed.
|
||||
if (saTakerGot < saTakerGets // Have less than wanted.
|
||||
&& saTakerPaid < saTakerPays) // Didn't spend all funds.
|
||||
if (saTakerFunds // Taker has funds available.
|
||||
&& saTakerGot < saTakerGets // Have less than wanted.
|
||||
&& saTakerPaid < saTakerPays) // Didn't spend all funds allocated.
|
||||
{
|
||||
sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->getNextLedgerIndex(uTipIndex, uBookEnd));
|
||||
if (sleOfferDir)
|
||||
@@ -82,7 +84,15 @@ TER OfferCreateTransactor::takeOffers(
|
||||
}
|
||||
}
|
||||
|
||||
if (!sleOfferDir // No offer directory to take.
|
||||
if (!saTakerFunds) // Taker has no funds.
|
||||
{
|
||||
// Done. Ran out of funds on previous round. As fees aren't calculated directly in this routine, funds are checked here.
|
||||
cLog(lsINFO) << "takeOffers: done: taker unfunded.";
|
||||
|
||||
bUnfunded = true; // Don't create an order.
|
||||
terResult = tesSUCCESS;
|
||||
}
|
||||
else if (!sleOfferDir // No offer directory to take.
|
||||
|| uTakeQuality < uTipQuality // No offers of sufficient quality available.
|
||||
|| (bPassive && uTakeQuality == uTipQuality))
|
||||
{
|
||||
@@ -149,7 +159,6 @@ TER OfferCreateTransactor::takeOffers(
|
||||
cLog(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText();
|
||||
|
||||
STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays);
|
||||
STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays);
|
||||
SLE::pointer sleOfferAccount; // Owner of offer.
|
||||
|
||||
if (!saOfferFunds.isPositive()) // Includes zero.
|
||||
@@ -243,17 +252,13 @@ TER OfferCreateTransactor::takeOffers(
|
||||
|
||||
if (!bUnfunded)
|
||||
{
|
||||
terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker.
|
||||
// Distribute funds. The sends charge appropriate fees which are implied by offer.
|
||||
|
||||
// if (tesSUCCESS == terResult)
|
||||
// terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); // Offer owner pays issuer transfer fee.
|
||||
terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker.
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
terResult = mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner.
|
||||
|
||||
// if (tesSUCCESS == terResult)
|
||||
// terResult = mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); // Taker pays issuer transfer fee.
|
||||
|
||||
// Reduce amount considered paid by taker's rate (not actual cost).
|
||||
STAmount saPay = saTakerPays - saTakerPaid;
|
||||
if (saTakerFunds < saPay)
|
||||
@@ -550,6 +555,9 @@ TER OfferCreateTransactor::doApply()
|
||||
|
||||
tLog(tesSUCCESS != terResult, lsINFO) << boost::str(boost::format("OfferCreate: final terResult=%s") % transToken(terResult));
|
||||
|
||||
if (isTesSuccess(terResult))
|
||||
theApp->getOrderBookDB().invalidate();
|
||||
|
||||
return terResult;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#include "OrderBook.h"
|
||||
#include "Ledger.h"
|
||||
|
||||
OrderBook::pointer OrderBook::newOrderBook(SerializedLedgerEntry::pointer ledgerEntry)
|
||||
OrderBook::pointer OrderBook::newOrderBook(SerializedLedgerEntry::ref ledgerEntry)
|
||||
{
|
||||
if(ledgerEntry->getType() != ltOFFER) return( OrderBook::pointer());
|
||||
|
||||
return( OrderBook::pointer(new OrderBook(ledgerEntry)));
|
||||
}
|
||||
|
||||
OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry)
|
||||
OrderBook::OrderBook(SerializedLedgerEntry::ref ledgerEntry)
|
||||
{
|
||||
const STAmount saTakerGets = ledgerEntry->getFieldAmount(sfTakerGets);
|
||||
const STAmount saTakerPays = ledgerEntry->getFieldAmount(sfTakerPays);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
|
||||
#ifndef ORDERBOOK_H
|
||||
#define ORDERBOOK_H
|
||||
|
||||
|
||||
#include "SerializedLedger.h"
|
||||
#include "NetworkOPs.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
@@ -15,14 +20,14 @@ class OrderBook
|
||||
uint160 mIssuerOut;
|
||||
|
||||
//SerializedLedgerEntry::pointer mLedgerEntry;
|
||||
OrderBook(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger
|
||||
OrderBook(SerializedLedgerEntry::ref ledgerEntry); // For accounts in a ledger
|
||||
public:
|
||||
typedef boost::shared_ptr<OrderBook> pointer;
|
||||
typedef const boost::shared_ptr<OrderBook>& ref;
|
||||
|
||||
// returns NULL if ledgerEntry doesn't point to an order
|
||||
// if ledgerEntry is an Order it creates the OrderBook this order would live in
|
||||
static OrderBook::pointer newOrderBook(SerializedLedgerEntry::pointer ledgerEntry);
|
||||
static OrderBook::pointer newOrderBook(SerializedLedgerEntry::ref ledgerEntry);
|
||||
|
||||
uint256& getBookBase(){ return(mBookBase); }
|
||||
uint160& getCurrencyIn(){ return(mCurrencyIn); }
|
||||
@@ -34,4 +39,6 @@ public:
|
||||
STAmount& getTakePrice(STAmount& takeAmount);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "Application.h"
|
||||
#include "OrderBookDB.h"
|
||||
#include "Log.h"
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
OrderBookDB::OrderBookDB()
|
||||
OrderBookDB::OrderBookDB() : mSeq(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// TODO: this would be way faster if we could just look under the order dirs
|
||||
void OrderBookDB::setup(Ledger::pointer ledger)
|
||||
void OrderBookDB::invalidate()
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
mSeq = 0;
|
||||
}
|
||||
|
||||
// TODO: this would be way faster if we could just look under the order dirs
|
||||
void OrderBookDB::setup(Ledger::ref ledger)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
|
||||
if (ledger->getLedgerSeq() == mSeq)
|
||||
return;
|
||||
mSeq = ledger->getLedgerSeq();
|
||||
|
||||
LoadEvent::autoptr ev = theApp->getJobQueue().getLoadEventAP(jtOB_SETUP);
|
||||
|
||||
mXRPOrders.clear();
|
||||
mIssuerMap.clear();
|
||||
mKnownMap.clear();
|
||||
@@ -61,17 +76,21 @@ void OrderBookDB::setup(Ledger::pointer ledger)
|
||||
// return list of all orderbooks that want IssuerID
|
||||
std::vector<OrderBook::pointer>& OrderBookDB::getBooks(const uint160& issuerID)
|
||||
{
|
||||
return mIssuerMap.find(issuerID) == mIssuerMap.end()
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
std::map< uint160, std::vector<OrderBook::pointer> >::iterator it = mIssuerMap.find(issuerID);
|
||||
return (it == mIssuerMap.end())
|
||||
? mEmptyVector
|
||||
: mIssuerMap[issuerID];
|
||||
: it->second;
|
||||
}
|
||||
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, std::vector<OrderBook::pointer>& bookRet)
|
||||
{
|
||||
if (mIssuerMap.find(issuerID) == mIssuerMap.end())
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
std::map< uint160, std::vector<OrderBook::pointer> >::iterator it = mIssuerMap.find(issuerID);
|
||||
if (it != mIssuerMap.end())
|
||||
{
|
||||
BOOST_FOREACH(OrderBook::ref book, mIssuerMap[issuerID])
|
||||
BOOST_FOREACH(OrderBook::ref book, it->second)
|
||||
{
|
||||
if (book->getCurrencyIn() == currencyID)
|
||||
bookRet.push_back(book);
|
||||
@@ -81,6 +100,7 @@ void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, s
|
||||
|
||||
BookListeners::pointer OrderBookDB::makeBookListeners(uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
BookListeners::pointer ret=getBookListeners(currencyIn, currencyOut, issuerIn, issuerOut);
|
||||
if(!ret)
|
||||
{
|
||||
@@ -92,6 +112,7 @@ BookListeners::pointer OrderBookDB::makeBookListeners(uint160 currencyIn, uint16
|
||||
|
||||
BookListeners::pointer OrderBookDB::getBookListeners(uint160 currencyIn, uint160 currencyOut, uint160 issuerIn, uint160 issuerOut)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > > >::iterator it0=mListeners.find(issuerIn);
|
||||
if(it0 != mListeners.end())
|
||||
{
|
||||
@@ -163,6 +184,7 @@ BookListeners::pointer OrderBookDB::getBookListeners(uint160 currencyIn, uint160
|
||||
// We need to determine which streams a given meta effects
|
||||
void OrderBookDB::processTxn(const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta,Json::Value& jvObj)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock sl(mLock);
|
||||
if(terResult==tesSUCCESS)
|
||||
{
|
||||
// check if this is an offer or an offer cancel or a payment that consumes an offer
|
||||
@@ -226,7 +248,7 @@ void BookListeners::publish(Json::Value& jvObj)
|
||||
|
||||
BOOST_FOREACH(InfoSub* sub,mListeners)
|
||||
{
|
||||
sub->send(jvObj);
|
||||
sub->send(jvObj, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
|
||||
#ifndef ORDERBOOK_DB_H
|
||||
#define ORDERBOOK_DB_H
|
||||
|
||||
#include "Ledger.h"
|
||||
#include "OrderBook.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
@@ -31,9 +35,13 @@ class OrderBookDB
|
||||
|
||||
std::map<uint256, bool > mKnownMap;
|
||||
|
||||
uint32 mSeq;
|
||||
boost::recursive_mutex mLock;
|
||||
|
||||
public:
|
||||
OrderBookDB();
|
||||
void setup(Ledger::pointer ledger);
|
||||
void setup(Ledger::ref ledger);
|
||||
void invalidate();
|
||||
|
||||
// return list of all orderbooks that want XRP
|
||||
std::vector<OrderBook::pointer>& getXRPInBooks(){ return mXRPOrders; }
|
||||
@@ -56,4 +64,6 @@ public:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ParseSection.h"
|
||||
#include "Log.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <iostream>
|
||||
@@ -7,6 +8,8 @@
|
||||
|
||||
#define SECTION_DEFAULT_NAME ""
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
section ParseSection(const std::string& strInput, const bool bTrim)
|
||||
{
|
||||
std::string strData(strInput);
|
||||
@@ -113,6 +116,12 @@ bool sectionSingleB(section& secSource, const std::string& strSection, std::stri
|
||||
{
|
||||
strValue = (*pmtEntries)[0];
|
||||
}
|
||||
else if (pmtEntries)
|
||||
{
|
||||
cLog(lsWARNING) << boost::str(boost::format("Section [%s]: requires 1 line not %d lines.")
|
||||
% strSection
|
||||
% pmtEntries->size());
|
||||
}
|
||||
|
||||
return bSingle;
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ bool Pathfinder::bDefaultPath(const STPath& spPath)
|
||||
// When path is a default (implied). Don't need to add it to return set.
|
||||
bDefault = pspCurrent->vpnNodes == mPsDefault->vpnNodes;
|
||||
|
||||
cLog(lsDEBUG) << "findPaths: expanded path: " << pspCurrent->getJson();
|
||||
cLog(lsDEBUG) << "findPaths: default path: indirect: " << spPath.getJson(0);
|
||||
cLog(lsTRACE) << "findPaths: expanded path: " << pspCurrent->getJson();
|
||||
cLog(lsTRACE) << "findPaths: default path: indirect: " << spPath.getJson(0);
|
||||
|
||||
return bDefault;
|
||||
}
|
||||
@@ -127,17 +127,21 @@ bool Pathfinder::bDefaultPath(const STPath& spPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID, const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount)
|
||||
: mSrcAccountID(uSrcAccountID.getAccountID()),
|
||||
Pathfinder::Pathfinder(Ledger::ref ledger,
|
||||
const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID,
|
||||
const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount)
|
||||
: mSrcAccountID(uSrcAccountID.getAccountID()),
|
||||
mDstAccountID(uDstAccountID.getAccountID()),
|
||||
mDstAmount(saDstAmount),
|
||||
mSrcCurrencyID(uSrcCurrencyID),
|
||||
mSrcIssuerID(uSrcIssuerID)
|
||||
mSrcIssuerID(uSrcIssuerID),
|
||||
mSrcAmount(uSrcCurrencyID, uSrcIssuerID, 1u, 0, true),
|
||||
mLedger(ledger)
|
||||
{
|
||||
mLedger = theApp->getLedgerMaster().getCurrentLedger();
|
||||
mSrcAmount = STAmount(uSrcCurrencyID, uSrcIssuerID, 1, 0, true); // -1/uSrcIssuerID/uSrcIssuerID
|
||||
|
||||
theApp->getOrderBookDB().setup( theApp->getLedgerMaster().getCurrentLedger()); // TODO: have the orderbook update itself rather than rebuild it from scratch each time
|
||||
theApp->getOrderBookDB().setup(mLedger);
|
||||
|
||||
mLoadMonitor = theApp->getJobQueue().getLoadEvent(jtPATH_FIND);
|
||||
|
||||
// Construct the default path for later comparison.
|
||||
|
||||
@@ -155,14 +159,14 @@ Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress&
|
||||
if (tesSUCCESS == psDefault->terStatus)
|
||||
{
|
||||
// The default path works, remember it.
|
||||
cLog(lsDEBUG) << "Pathfinder: default path: " << psDefault->getJson();
|
||||
cLog(lsTRACE) << "Pathfinder: default path: " << psDefault->getJson();
|
||||
|
||||
mPsDefault = psDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The default path doesn't work.
|
||||
cLog(lsDEBUG) << "Pathfinder: default path: NONE: " << transToken(psDefault->terStatus);
|
||||
cLog(lsTRACE) << "Pathfinder: default path: NONE: " << transToken(psDefault->terStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +189,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
{
|
||||
bool bFound = false; // True, iff found a path.
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s")
|
||||
cLog(lsTRACE) << boost::str(boost::format("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s")
|
||||
% RippleAddress::createHumanAccountID(mSrcAccountID)
|
||||
% RippleAddress::createHumanAccountID(mDstAccountID)
|
||||
% mDstAmount.getFullText()
|
||||
@@ -193,49 +197,123 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
% RippleAddress::createHumanAccountID(mSrcIssuerID)
|
||||
);
|
||||
|
||||
if (mLedger)
|
||||
if (!mLedger)
|
||||
{
|
||||
LedgerEntrySet lesActive(mLedger);
|
||||
std::vector<STPath> vspResults;
|
||||
std::queue<STPath> qspExplore; // Path stubs to explore.
|
||||
cLog(lsDEBUG) << "findPaths< no ledger";
|
||||
|
||||
STPath spSeed;
|
||||
bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The end is the cursor, start at the source account.
|
||||
STPathElement speEnd(mSrcAccountID,
|
||||
mSrcCurrencyID,
|
||||
!!mSrcCurrencyID
|
||||
? mSrcAccountID // Non-XRP, start with self as issuer.
|
||||
: ACCOUNT_XRP);
|
||||
LedgerEntrySet lesActive(mLedger);
|
||||
|
||||
SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mSrcAccountID));
|
||||
if (!sleSrc)
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths< no source"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SLE::pointer sleDst = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mDstAccountID));
|
||||
if (!sleDst)
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths< no dest"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<STPath> vspResults;
|
||||
std::queue<STPath> qspExplore; // Path stubs to explore.
|
||||
|
||||
STPath spSeed;
|
||||
bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer.
|
||||
|
||||
// The end is the cursor, start at the source account.
|
||||
STPathElement speEnd(mSrcAccountID,
|
||||
mSrcCurrencyID,
|
||||
!!mSrcCurrencyID
|
||||
? mSrcAccountID // Non-XRP, start with self as issuer.
|
||||
: ACCOUNT_XRP);
|
||||
|
||||
// Build a path of one element: the source.
|
||||
spSeed.addElement(speEnd);
|
||||
|
||||
if (bForcedIssuer)
|
||||
{
|
||||
// Add forced source issuer to seed, via issuer's account.
|
||||
STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
|
||||
|
||||
// Build a path of one element: the source.
|
||||
spSeed.addElement(speEnd);
|
||||
}
|
||||
|
||||
if (bForcedIssuer)
|
||||
// Push the seed path to explore.
|
||||
qspExplore.push(spSeed);
|
||||
|
||||
while (qspExplore.size()) { // Have paths to explore?
|
||||
STPath spPath = qspExplore.front();
|
||||
|
||||
qspExplore.pop(); // Pop the first path from the queue.
|
||||
|
||||
speEnd = spPath.mPath.back(); // Get the last node from the path.
|
||||
|
||||
if (!speEnd.mCurrencyID // Tail output is XRP.
|
||||
&& !mDstAmount.getCurrency()) // Which is dst currency.
|
||||
{
|
||||
// Add forced source issuer to seed, via issuer's account.
|
||||
STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
|
||||
// Done, cursor produces XRP and dest wants XRP.
|
||||
|
||||
spSeed.addElement(speEnd);
|
||||
// Remove implied source.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
|
||||
if (bForcedIssuer)
|
||||
{
|
||||
// Remove implied source issuer.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
}
|
||||
|
||||
if (spPath.size())
|
||||
{
|
||||
// There is an actual path element.
|
||||
cLog(lsTRACE) << "findPaths: adding path: " << spPath.getJson(0);
|
||||
|
||||
vspResults.push_back(spPath); // Potential result.
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsTRACE) << "findPaths: empty path: XRP->XRP";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Push the seed path to explore.
|
||||
qspExplore.push(spSeed);
|
||||
cLog(lsDEBUG) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID);
|
||||
cLog(lsDEBUG) << "findPaths: finish? currency: " << (speEnd.mCurrencyID == mDstAmount.getCurrency());
|
||||
cLog(lsTRACE) << "findPaths: finish? issuer: "
|
||||
<< RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||
<< " / "
|
||||
<< RippleAddress::createHumanAccountID(mDstAmount.getIssuer())
|
||||
<< " / "
|
||||
<< RippleAddress::createHumanAccountID(mDstAccountID);
|
||||
cLog(lsDEBUG) << "findPaths: finish? issuer is desired: " << (speEnd.mIssuerID == mDstAmount.getIssuer());
|
||||
|
||||
while (qspExplore.size()) { // Have paths to explore?
|
||||
STPath spPath = qspExplore.front();
|
||||
// YYY Allows going through self. Is this wanted?
|
||||
if (speEnd.mAccountID == mDstAccountID // Tail is destination account.
|
||||
&& speEnd.mCurrencyID == mDstAmount.getCurrency() // With correct output currency.
|
||||
&& ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer.
|
||||
|| mDstAmount.getIssuer() == mDstAccountID // Any issuer is good.
|
||||
|| mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer.
|
||||
{
|
||||
// Done, found a path to the destination.
|
||||
// Cursor on the dest account with correct currency and issuer.
|
||||
|
||||
qspExplore.pop(); // Pop the first path from the queue.
|
||||
if (bDefaultPath(spPath)) {
|
||||
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
|
||||
|
||||
speEnd = spPath.mPath.back(); // Get the last node from the path.
|
||||
|
||||
if (!speEnd.mCurrencyID // Tail output is XRP.
|
||||
&& !mDstAmount.getCurrency()) // Which is dst currency.
|
||||
bFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Done, cursor produces XRP and dest wants XRP.
|
||||
// Remove implied nodes.
|
||||
|
||||
// Remove implied source.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
|
||||
if (bForcedIssuer)
|
||||
@@ -243,129 +321,81 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
// Remove implied source issuer.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
}
|
||||
spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1);
|
||||
|
||||
if (spPath.size())
|
||||
{
|
||||
// There is an actual path element.
|
||||
cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
|
||||
vspResults.push_back(spPath); // Potential result.
|
||||
|
||||
vspResults.push_back(spPath); // Potential result.
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << "findPaths: empty path: XRP->XRP";
|
||||
}
|
||||
|
||||
continue;
|
||||
cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID);
|
||||
cLog(lsDEBUG) << "findPaths: finish? currency: " << (speEnd.mCurrencyID == mDstAmount.getCurrency());
|
||||
cLog(lsDEBUG) << "findPaths: finish? issuer: "
|
||||
<< RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||
<< " / "
|
||||
<< RippleAddress::createHumanAccountID(mDstAmount.getIssuer())
|
||||
<< " / "
|
||||
<< RippleAddress::createHumanAccountID(mDstAccountID);
|
||||
cLog(lsDEBUG) << "findPaths: finish? issuer is desired: " << (speEnd.mIssuerID == mDstAmount.getIssuer());
|
||||
continue;
|
||||
}
|
||||
|
||||
// YYY Allows going through self. Is this wanted?
|
||||
if (speEnd.mAccountID == mDstAccountID // Tail is destination account.
|
||||
&& speEnd.mCurrencyID == mDstAmount.getCurrency() // With correct output currency.
|
||||
&& ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer.
|
||||
|| mDstAmount.getIssuer() == mDstAccountID // Any issuer is good.
|
||||
|| mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer.
|
||||
bool bContinued = false; // True, if wasn't a dead end.
|
||||
|
||||
cLog(lsTRACE) <<
|
||||
boost::str(boost::format("findPaths: cursor: %s - %s/%s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID));
|
||||
|
||||
if (spPath.mPath.size() >= iMaxSteps)
|
||||
{
|
||||
// Path is at maximum size. Don't want to add more.
|
||||
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: dropping: path would exceed max steps"));
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (!speEnd.mCurrencyID)
|
||||
{
|
||||
// Cursor is for XRP, continue with qualifying books: XRP -> non-XRP
|
||||
BOOST_FOREACH(OrderBook::ref book, theApp->getOrderBookDB().getXRPInBooks())
|
||||
{
|
||||
// Done, found a path to the destination.
|
||||
// Cursor on the dest account with correct currency and issuer.
|
||||
// New end is an order book with the currency and issuer.
|
||||
|
||||
if (bDefaultPath(spPath)) {
|
||||
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
|
||||
|
||||
bFound = true;
|
||||
}
|
||||
else
|
||||
// Don't allow looping through same order books.
|
||||
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{
|
||||
// Remove implied nodes.
|
||||
STPath spNew(spPath);
|
||||
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
spNew.mPath.push_back(speBook); // Add the order book.
|
||||
spNew.mPath.push_back(speAccount); // Add the account and currency
|
||||
|
||||
if (bForcedIssuer)
|
||||
{
|
||||
// Remove implied source issuer.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
}
|
||||
spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1);
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: XRP -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speBook.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speBook.mIssuerID));
|
||||
|
||||
vspResults.push_back(spPath); // Potential result.
|
||||
qspExplore.push(spNew);
|
||||
|
||||
cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
|
||||
bContinued = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bContinued = false; // True, if wasn't a dead end.
|
||||
tLog(!bContinued, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: XRP -> dead end"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last element is for non-XRP, continue by adding ripple lines and order books.
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: cursor: %s - %s/%s")
|
||||
// Create new paths for each outbound account not already in the path.
|
||||
AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState()));
|
||||
SLE::pointer sleEnd = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mAccountID));
|
||||
|
||||
tLog(!sleEnd, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: order book: %s/%s : ")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID));
|
||||
|
||||
if (spPath.mPath.size() == iMaxSteps)
|
||||
if (sleEnd)
|
||||
{
|
||||
// Path is at maximum size. Don't want to add more.
|
||||
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: dropping: path would exceed max steps"));
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (!speEnd.mCurrencyID)
|
||||
{
|
||||
// Cursor is for XRP, continue with qualifying books: XRP -> non-XRP
|
||||
BOOST_FOREACH(OrderBook::ref book, theApp->getOrderBookDB().getXRPInBooks())
|
||||
{
|
||||
// New end is an order book with the currency and issuer.
|
||||
|
||||
// Don't allow looping through same order books.
|
||||
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{
|
||||
STPath spNew(spPath);
|
||||
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
spNew.mPath.push_back(speBook); // Add the order book.
|
||||
spNew.mPath.push_back(speAccount); // Add the account and currency
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: XRP -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speBook.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speBook.mIssuerID));
|
||||
|
||||
qspExplore.push(spNew);
|
||||
|
||||
bContinued = true;
|
||||
}
|
||||
}
|
||||
|
||||
tLog(!bContinued, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: XRP -> dead end"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Last element is for non-XRP, continue by adding ripple lines and order books.
|
||||
|
||||
// Create new paths for each outbound account not already in the path.
|
||||
AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState()));
|
||||
SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mAccountID));
|
||||
|
||||
tLog(sleSrc, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: account without root: %s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID));
|
||||
|
||||
bool bRequireAuth = isSetBit(sleSrc->getFieldU32(sfFlags), lsfRequireAuth);
|
||||
// On a non-XRP account:
|
||||
bool bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth);
|
||||
|
||||
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems())
|
||||
{
|
||||
@@ -388,7 +418,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
|| (bRequireAuth && !rspEntry->getAuth()))) // Not authorized to hold credit.
|
||||
{
|
||||
// Path has no credit left. Ignore it.
|
||||
cLog(lsDEBUG) <<
|
||||
cLog(lsTRACE) <<
|
||||
boost::str(boost::format("findPaths: No credit: %s/%s -> %s/%s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
@@ -406,7 +436,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
|
||||
bContinued = true;
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
cLog(lsTRACE) <<
|
||||
boost::str(boost::format("findPaths: push explore: %s/%s -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
@@ -414,128 +444,131 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
% RippleAddress::createHumanAccountID(uPeerID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Every book that wants the source currency.
|
||||
std::vector<OrderBook::pointer> books;
|
||||
// Every book that wants the source currency.
|
||||
std::vector<OrderBook::pointer> books;
|
||||
|
||||
theApp->getOrderBookDB().getBooks(speEnd.mIssuerID, speEnd.mCurrencyID, books);
|
||||
|
||||
theApp->getOrderBookDB().getBooks(speEnd.mIssuerID, speEnd.mCurrencyID, books);
|
||||
|
||||
BOOST_FOREACH(OrderBook::ref book, books)
|
||||
BOOST_FOREACH(OrderBook::ref book, books)
|
||||
{
|
||||
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{
|
||||
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||
// A book we haven't seen before. Add it.
|
||||
STPath spNew(spPath);
|
||||
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
spNew.mPath.push_back(speBook); // Add the order book.
|
||||
|
||||
if (!book->getCurrencyOut())
|
||||
{
|
||||
// A book we haven't seen before. Add it.
|
||||
STPath spNew(spPath);
|
||||
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
spNew.mPath.push_back(speBook);
|
||||
qspExplore.push(spNew);
|
||||
|
||||
bContinued = true;
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: push book: %s/%s -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||
% STAmount::createHumanCurrency(book->getCurrencyOut())
|
||||
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
|
||||
// For non-XRP out, don't end on the book, add the issuing account.
|
||||
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
spNew.mPath.push_back(speAccount); // Add the account and currency
|
||||
}
|
||||
}
|
||||
|
||||
tLog(!bContinued, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: dropping: non-XRP -> dead end"));
|
||||
qspExplore.push(spNew);
|
||||
|
||||
bContinued = true;
|
||||
|
||||
cLog(lsTRACE) <<
|
||||
boost::str(boost::format("findPaths: push book: %s/%s -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||
% STAmount::createHumanCurrency(book->getCurrencyOut())
|
||||
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
|
||||
}
|
||||
}
|
||||
|
||||
tLog(!bContinued, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: dropping: non-XRP -> dead end"));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int iLimit = std::min(iMaxPaths, (unsigned int) vspResults.size());
|
||||
unsigned int iLimit = std::min(iMaxPaths, (unsigned int) vspResults.size());
|
||||
|
||||
// Only filter, sort, and limit if have non-default paths.
|
||||
if (iLimit)
|
||||
// Only filter, sort, and limit if have non-default paths.
|
||||
if (iLimit)
|
||||
{
|
||||
std::vector< std::pair<uint64, unsigned int> > vMap;
|
||||
|
||||
// Build map of quality to entry.
|
||||
for (int i = vspResults.size(); i--;)
|
||||
{
|
||||
std::vector< std::pair<uint64, unsigned int> > vMap;
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
STPathSet spsPaths;
|
||||
STPath& spCurrent = vspResults[i];
|
||||
|
||||
// Build map of quality to entry.
|
||||
for (int i = vspResults.size(); i--;)
|
||||
spsPaths.addPath(spCurrent); // Just checking the current path.
|
||||
|
||||
TER terResult;
|
||||
|
||||
try {
|
||||
terResult = RippleCalc::rippleCalc(
|
||||
lesActive,
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
mSrcAmount, // --> amount to send max.
|
||||
mDstAmount, // --> amount to deliver.
|
||||
mDstAccountID,
|
||||
mSrcAccountID,
|
||||
spsPaths,
|
||||
true, // --> bPartialPayment: Allow, it might contribute.
|
||||
false, // --> bLimitQuality: Assume normal transaction.
|
||||
true, // --> bNoRippleDirect: Providing the only path.
|
||||
true); // --> bStandAlone: Don't need to delete unfundeds.
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
STPathSet spsPaths;
|
||||
STPath& spCurrent = vspResults[i];
|
||||
cLog(lsINFO) << "findPaths: Caught throw: " << e.what();
|
||||
|
||||
spsPaths.addPath(spCurrent); // Just checking the current path.
|
||||
|
||||
TER terResult;
|
||||
|
||||
try {
|
||||
terResult = RippleCalc::rippleCalc(
|
||||
lesActive,
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
mSrcAmount, // --> amount to send max.
|
||||
mDstAmount, // --> amount to deliver.
|
||||
mDstAccountID,
|
||||
mSrcAccountID,
|
||||
spsPaths,
|
||||
true, // --> bPartialPayment: Allow, it might contribute.
|
||||
false, // --> bLimitQuality: Assume normal transaction.
|
||||
true, // --> bNoRippleDirect: Providing the only path.
|
||||
true); // --> bStandAlone: Don't need to delete unfundeds.
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cLog(lsINFO) << "findPaths: Caught throw: " << e.what();
|
||||
|
||||
terResult = tefEXCEPTION;
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
uint64 uQuality = STAmount::getRate(saDstAmountAct, saMaxAmountAct);
|
||||
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: quality: %d: %s")
|
||||
% uQuality
|
||||
% spCurrent.getJson(0));
|
||||
|
||||
vMap.push_back(std::make_pair(uQuality, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: dropping: %s: %s")
|
||||
% transToken(terResult)
|
||||
% spCurrent.getJson(0));
|
||||
}
|
||||
terResult = tefEXCEPTION;
|
||||
}
|
||||
|
||||
if (vMap.size())
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
iLimit = std::min(iMaxPaths, (unsigned int) vMap.size());
|
||||
uint64 uQuality = STAmount::getRate(saDstAmountAct, saMaxAmountAct);
|
||||
|
||||
bFound = true;
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: quality: %d: %s")
|
||||
% uQuality
|
||||
% spCurrent.getJson(0));
|
||||
|
||||
std::sort(vMap.begin(), vMap.end(), bQualityCmp); // Lower is better and should be first.
|
||||
|
||||
// Output best quality entries.
|
||||
for (int i = 0; i != vMap.size(); ++i)
|
||||
{
|
||||
spsDst.addPath(vspResults[vMap[i].second]);
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: %s") % spsDst.getJson(0));
|
||||
vMap.push_back(std::make_pair(uQuality, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: non-defaults filtered away"));
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: dropping: %s: %s")
|
||||
% transToken(terResult)
|
||||
% spCurrent.getJson(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths: no ledger"));
|
||||
|
||||
if (vMap.size())
|
||||
{
|
||||
iLimit = std::min(iMaxPaths, (unsigned int) vMap.size());
|
||||
|
||||
bFound = true;
|
||||
|
||||
std::sort(vMap.begin(), vMap.end(), bQualityCmp); // Lower is better and should be first.
|
||||
|
||||
// Output best quality entries.
|
||||
for (int i = 0; i != vMap.size(); ++i)
|
||||
{
|
||||
spsDst.addPath(vspResults[vMap[i].second]);
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: %s") % spsDst.getJson(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: non-defaults filtered away"));
|
||||
}
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("findPaths< bFound=%d") % bFound);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#ifndef __PATHFINDER__
|
||||
#define __PATHFINDER__
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "SerializedTypes.h"
|
||||
#include "RippleAddress.h"
|
||||
#include "RippleCalc.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include "OrderBookDB.h"
|
||||
|
||||
#if 0
|
||||
//
|
||||
@@ -41,9 +43,9 @@ class Pathfinder
|
||||
uint160 mSrcIssuerID;
|
||||
STAmount mSrcAmount;
|
||||
|
||||
//OrderBookDB mOrderBook;
|
||||
Ledger::pointer mLedger;
|
||||
PathState::pointer mPsDefault;
|
||||
LoadEvent::pointer mLoadMonitor;
|
||||
|
||||
// std::list<PathOption::pointer> mBuildingPaths;
|
||||
// std::list<PathOption::pointer> mCompletePaths;
|
||||
@@ -56,7 +58,9 @@ class Pathfinder
|
||||
// void addPathOption(PathOption::pointer pathOption);
|
||||
|
||||
public:
|
||||
Pathfinder(const RippleAddress& srcAccountID, const RippleAddress& dstAccountID, const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount);
|
||||
Pathfinder(Ledger::ref ledger,
|
||||
const RippleAddress& srcAccountID, const RippleAddress& dstAccountID,
|
||||
const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount);
|
||||
|
||||
bool findPaths(const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst);
|
||||
|
||||
|
||||
@@ -177,22 +177,28 @@ Json::Value RPCHandler::transactionSign(Json::Value jvRequest, bool bSubmit)
|
||||
return rpcError(rpcINVALID_PARAMS);
|
||||
}
|
||||
|
||||
Pathfinder pf(raSrcAddressID, dstAccountID, saSendMax.getCurrency(), saSendMax.getIssuer(), saSend);
|
||||
|
||||
if (!pf.findPaths(7, 3, spsPaths))
|
||||
Ledger::pointer lSnapshot = boost::make_shared<Ledger>(
|
||||
boost::ref(*theApp->getOPs().getCurrentLedger()), false);
|
||||
{
|
||||
cLog(lsDEBUG) << "transactionSign: build_path: No paths found.";
|
||||
ScopedUnlock su(theApp->getMasterLock());
|
||||
Pathfinder pf(lSnapshot, raSrcAddressID, dstAccountID,
|
||||
saSendMax.getCurrency(), saSendMax.getIssuer(), saSend);
|
||||
|
||||
return rpcError(rpcNO_PATH);
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << "transactionSign: build_path: " << spsPaths.getJson(0);
|
||||
}
|
||||
if (!pf.findPaths(theConfig.PATH_SEARCH_SIZE, 3, spsPaths))
|
||||
{
|
||||
cLog(lsDEBUG) << "transactionSign: build_path: No paths found.";
|
||||
|
||||
if (!spsPaths.isEmpty())
|
||||
{
|
||||
txJSON["Paths"]=spsPaths.getJson(0);
|
||||
return rpcError(rpcNO_PATH);
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << "transactionSign: build_path: " << spsPaths.getJson(0);
|
||||
}
|
||||
|
||||
if (!spsPaths.isEmpty())
|
||||
{
|
||||
txJSON["Paths"]=spsPaths.getJson(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,8 +500,9 @@ Json::Value RPCHandler::authorize(Ledger::ref lrLedger,
|
||||
}
|
||||
|
||||
// --> strIdent: public key, account ID, or regular seed.
|
||||
// --> bStrict: Only allow account id or public key.
|
||||
// <-- bIndex: true if iIndex > 0 and used the index.
|
||||
Json::Value RPCHandler::accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex)
|
||||
Json::Value RPCHandler::accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex, const bool bStrict)
|
||||
{
|
||||
RippleAddress naSeed;
|
||||
|
||||
@@ -504,6 +511,10 @@ Json::Value RPCHandler::accountFromString(Ledger::ref lrLedger, RippleAddress& n
|
||||
// Got the account.
|
||||
bIndex = false;
|
||||
}
|
||||
else if (bStrict)
|
||||
{
|
||||
return rpcError(rpcACT_MALFORMED);
|
||||
}
|
||||
// Must be a seed.
|
||||
else if (!naSeed.setSeedGeneric(strIdent))
|
||||
{
|
||||
@@ -564,6 +575,7 @@ Json::Value RPCHandler::doAcceptLedger(Json::Value jvRequest)
|
||||
// {
|
||||
// ident : <indent>,
|
||||
// account_index : <index> // optional
|
||||
// strict: <bool> // true, only allow public keys and addresses. false, default.
|
||||
// ledger_hash : <ledger>
|
||||
// ledger_index : <ledger_index>
|
||||
// }
|
||||
@@ -581,11 +593,12 @@ Json::Value RPCHandler::doAccountInfo(Json::Value jvRequest)
|
||||
std::string strIdent = jvRequest["ident"].asString();
|
||||
bool bIndex;
|
||||
int iIndex = jvRequest.isMember("account_index") ? jvRequest["account_index"].asUInt() : 0;
|
||||
bool bStrict = jvRequest.isMember("strict") && jvRequest["strict"].asBool();
|
||||
RippleAddress naAccount;
|
||||
|
||||
// Get info on account.
|
||||
|
||||
Json::Value jvAccepted = accountFromString(lpLedger, naAccount, bIndex, strIdent, iIndex);
|
||||
Json::Value jvAccepted = accountFromString(lpLedger, naAccount, bIndex, strIdent, iIndex, bStrict);
|
||||
|
||||
if (!jvAccepted.empty())
|
||||
return jvAccepted;
|
||||
@@ -740,7 +753,7 @@ Json::Value RPCHandler::doNicknameInfo(Json::Value params)
|
||||
// 'ident' : <indent>,
|
||||
// 'account_index' : <index> // optional
|
||||
// }
|
||||
// XXX This would be better if it too the ledger.
|
||||
// XXX This would be better if it took the ledger.
|
||||
Json::Value RPCHandler::doOwnerInfo(Json::Value jvRequest)
|
||||
{
|
||||
if (!jvRequest.isMember("ident"))
|
||||
@@ -755,11 +768,11 @@ Json::Value RPCHandler::doOwnerInfo(Json::Value jvRequest)
|
||||
|
||||
// Get info on account.
|
||||
|
||||
Json::Value jAccepted = accountFromString(mNetOps->getClosedLedger(), raAccount, bIndex, strIdent, iIndex);
|
||||
Json::Value jAccepted = accountFromString(mNetOps->getClosedLedger(), raAccount, bIndex, strIdent, iIndex, false);
|
||||
|
||||
ret["accepted"] = jAccepted.empty() ? mNetOps->getOwnerInfo(mNetOps->getClosedLedger(), raAccount) : jAccepted;
|
||||
|
||||
Json::Value jCurrent = accountFromString(mNetOps->getCurrentLedger(), raAccount, bIndex, strIdent, iIndex);
|
||||
Json::Value jCurrent = accountFromString(mNetOps->getCurrentLedger(), raAccount, bIndex, strIdent, iIndex, false);
|
||||
|
||||
ret["current"] = jCurrent.empty() ? mNetOps->getOwnerInfo(mNetOps->getCurrentLedger(), raAccount) : jCurrent;
|
||||
|
||||
@@ -768,11 +781,16 @@ Json::Value RPCHandler::doOwnerInfo(Json::Value jvRequest)
|
||||
|
||||
Json::Value RPCHandler::doPeers(Json::Value)
|
||||
{
|
||||
Json::Value obj(Json::objectValue);
|
||||
Json::Value jvResult(Json::objectValue);
|
||||
|
||||
obj["peers"]=theApp->getConnectionPool().getPeersJson();
|
||||
jvResult["peers"] = theApp->getConnectionPool().getPeersJson();
|
||||
|
||||
return obj;
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value RPCHandler::doPing(Json::Value)
|
||||
{
|
||||
return Json::Value(Json::objectValue);
|
||||
}
|
||||
|
||||
// profile offers <pass_a> <account_a> <currency_offer_a> <account_b> <currency_offer_b> <count> [submit]
|
||||
@@ -868,8 +886,8 @@ Json::Value RPCHandler::doProfile(Json::Value jvRequest)
|
||||
}
|
||||
|
||||
// {
|
||||
// account: <account>|<nickname>|<account_public_key> [<index>]
|
||||
// index: <number> // optional, defaults to 0.
|
||||
// account: <account>|<nickname>|<account_public_key>
|
||||
// account_index: <number> // optional, defaults to 0.
|
||||
// ledger_hash : <ledger>
|
||||
// ledger_index : <ledger_index>
|
||||
// }
|
||||
@@ -890,7 +908,7 @@ Json::Value RPCHandler::doAccountLines(Json::Value jvRequest)
|
||||
|
||||
RippleAddress raAccount;
|
||||
|
||||
jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex);
|
||||
jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex, false);
|
||||
|
||||
if (!jvResult.empty())
|
||||
return jvResult;
|
||||
@@ -946,8 +964,8 @@ Json::Value RPCHandler::doAccountLines(Json::Value jvRequest)
|
||||
}
|
||||
|
||||
// {
|
||||
// account: <account>|<nickname>|<account_public_key> [<index>]
|
||||
// index: <number> // optional, defaults to 0.
|
||||
// account: <account>|<nickname>|<account_public_key>
|
||||
// account_index: <number> // optional, defaults to 0.
|
||||
// ledger_hash : <ledger>
|
||||
// ledger_index : <ledger_index>
|
||||
// }
|
||||
@@ -968,7 +986,7 @@ Json::Value RPCHandler::doAccountOffers(Json::Value jvRequest)
|
||||
|
||||
RippleAddress raAccount;
|
||||
|
||||
jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex);
|
||||
jvResult = accountFromString(lpLedger, raAccount, bIndex, strIdent, iIndex, false);
|
||||
|
||||
if (!jvResult.empty())
|
||||
return jvResult;
|
||||
@@ -1115,7 +1133,8 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest)
|
||||
}
|
||||
}
|
||||
|
||||
LedgerEntrySet lesSnapshot(lpCurrent);
|
||||
Ledger::pointer lSnapShot = boost::make_shared<Ledger>(boost::ref(*lpCurrent), false);
|
||||
LedgerEntrySet lesSnapshot(lSnapShot);
|
||||
|
||||
ScopedUnlock su(theApp->getMasterLock()); // As long as we have a locked copy of the ledger, we can unlock.
|
||||
|
||||
@@ -1148,9 +1167,9 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest)
|
||||
}
|
||||
|
||||
STPathSet spsComputed;
|
||||
Pathfinder pf(raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount);
|
||||
Pathfinder pf(lSnapShot, raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount);
|
||||
|
||||
if (!pf.findPaths(7, 3, spsComputed))
|
||||
if (!pf.findPaths(theConfig.PATH_SEARCH_SIZE, 3, spsComputed))
|
||||
{
|
||||
cLog(lsDEBUG) << "ripple_path_find: No paths found.";
|
||||
}
|
||||
@@ -1413,14 +1432,29 @@ Json::Value RPCHandler::doTx(Json::Value jvRequest)
|
||||
|
||||
if (Transaction::isHexTxID(strTransaction))
|
||||
{ // transaction by ID
|
||||
Json::Value ret;
|
||||
uint256 txid(strTransaction);
|
||||
|
||||
Transaction::pointer txn = theApp->getMasterTransaction().fetch(txid, true);
|
||||
|
||||
if (!txn) return rpcError(rpcTXN_NOT_FOUND);
|
||||
|
||||
return txn->getJson(0);
|
||||
Json::Value ret = txn->getJson(0);
|
||||
|
||||
if (txn->getLedger() != 0)
|
||||
{
|
||||
Ledger::pointer lgr = theApp->getLedgerMaster().getLedgerBySeq(txn->getLedger());
|
||||
if (lgr)
|
||||
{
|
||||
TransactionMetaSet::pointer set;
|
||||
if (lgr->getTransactionMeta(txid, set))
|
||||
{
|
||||
ret["meta"] = set->getJson(0);
|
||||
ret["validated"] = theApp->getOPs().isValidated(lgr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return rpcError(rpcNOT_IMPL);
|
||||
@@ -1510,18 +1544,18 @@ Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest)
|
||||
if (!raAccount.setAccountID(jvRequest["account"].asString()))
|
||||
return rpcError(rpcACT_MALFORMED);
|
||||
|
||||
if (jvRequest.isMember("ledger"))
|
||||
{
|
||||
minLedger = maxLedger = jvRequest["ledger"].asUInt();
|
||||
}
|
||||
else if (jvRequest.isMember("ledger_min") && jvRequest.isMember("ledger_max"))
|
||||
if (jvRequest.isMember("ledger_min") && jvRequest.isMember("ledger_max"))
|
||||
{
|
||||
minLedger = jvRequest["ledger_min"].asUInt();
|
||||
maxLedger = jvRequest["ledger_max"].asUInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
return rpcError(rpcLGR_IDX_MALFORMED);
|
||||
Ledger::pointer l;
|
||||
Json::Value ret = lookupLedger(jvRequest, l);
|
||||
if (!l)
|
||||
return ret;
|
||||
minLedger = maxLedger = l->getLedgerSeq();
|
||||
}
|
||||
|
||||
if ((maxLedger < minLedger) || (maxLedger == 0))
|
||||
@@ -1533,18 +1567,30 @@ Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest)
|
||||
try
|
||||
{
|
||||
#endif
|
||||
int vl = theApp->getOPs().getValidatedSeq();
|
||||
ScopedUnlock su(theApp->getMasterLock());
|
||||
|
||||
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > txns = mNetOps->getAccountTxs(raAccount, minLedger, maxLedger);
|
||||
Json::Value ret(Json::objectValue);
|
||||
ret["account"] = raAccount.humanAccountID();
|
||||
Json::Value ledgers(Json::arrayValue);
|
||||
|
||||
// uint32 currentLedger = 0;
|
||||
for (std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >::iterator it = txns.begin(), end = txns.end(); it != end; ++it)
|
||||
{
|
||||
Json::Value obj(Json::objectValue);
|
||||
|
||||
if (it->first) obj["tx"] = it->first->getJson(1);
|
||||
if (it->second) obj["meta"] = it->second->getJson(0);
|
||||
if (it->first)
|
||||
obj["tx"] = it->first->getJson(1);
|
||||
if (it->second)
|
||||
{
|
||||
obj["meta"] = it->second->getJson(0);
|
||||
|
||||
uint32 s = it->second->getLgrSeq();
|
||||
if (s > vl)
|
||||
obj["validated"] = false;
|
||||
else if (theApp->getOPs().haveLedger(s))
|
||||
obj["validated"] = true;
|
||||
}
|
||||
|
||||
ret["transactions"].append(obj);
|
||||
}
|
||||
@@ -2066,7 +2112,7 @@ Json::Value RPCHandler::lookupLedger(Json::Value jvRequest, Ledger::pointer& lpL
|
||||
Json::Value jvResult;
|
||||
|
||||
uint256 uLedger = jvRequest.isMember("ledger_hash") ? uint256(jvRequest["ledger_hash"].asString()) : 0;
|
||||
uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0;
|
||||
int32 iLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asInt() : -2;
|
||||
|
||||
if (!!uLedger)
|
||||
{
|
||||
@@ -2076,38 +2122,51 @@ Json::Value RPCHandler::lookupLedger(Json::Value jvRequest, Ledger::pointer& lpL
|
||||
if (!lpLedger)
|
||||
{
|
||||
jvResult["error"] = "ledgerNotFound";
|
||||
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed.
|
||||
iLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed.
|
||||
}
|
||||
else if (!!uLedgerIndex)
|
||||
if (-1 == iLedgerIndex)
|
||||
{
|
||||
lpLedger = mNetOps->getLedgerBySeq(uLedgerIndex);
|
||||
lpLedger = theApp->getLedgerMaster().getClosedLedger();
|
||||
iLedgerIndex = lpLedger->getLedgerSeq();
|
||||
}
|
||||
if (-2 == iLedgerIndex)
|
||||
{
|
||||
// Default to current ledger.
|
||||
lpLedger = mNetOps->getCurrentLedger();
|
||||
iLedgerIndex = lpLedger->getLedgerSeq();
|
||||
}
|
||||
else if (iLedgerIndex <= 0)
|
||||
{
|
||||
jvResult["error"] = "ledgerNotFound";
|
||||
|
||||
return jvResult;
|
||||
}
|
||||
else if (iLedgerIndex)
|
||||
{
|
||||
lpLedger = mNetOps->getLedgerBySeq(iLedgerIndex);
|
||||
|
||||
if (!lpLedger)
|
||||
{
|
||||
jvResult["error"] = "ledgerNotFound"; // ledger_index from future?
|
||||
|
||||
return jvResult;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to current ledger.
|
||||
lpLedger = mNetOps->getCurrentLedger();
|
||||
uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index.
|
||||
}
|
||||
|
||||
if (lpLedger->isClosed())
|
||||
{
|
||||
if (!!uLedger)
|
||||
jvResult["ledger_hash"] = uLedger.ToString();
|
||||
|
||||
jvResult["ledger_index"] = uLedgerIndex;
|
||||
jvResult["ledger_index"] = iLedgerIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
jvResult["ledger_current_index"] = uLedgerIndex;
|
||||
jvResult["ledger_current_index"] = iLedgerIndex;
|
||||
}
|
||||
|
||||
return jvResult;
|
||||
@@ -2401,10 +2460,14 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest)
|
||||
RPCSub *rspSub = mNetOps->findRpcSub(strUrl);
|
||||
if (!rspSub)
|
||||
{
|
||||
cLog(lsINFO) << boost::str(boost::format("doSubscribe: building: %s") % strUrl);
|
||||
|
||||
rspSub = mNetOps->addRpcSub(strUrl, new RPCSub(strUrl, strUsername, strPassword));
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << boost::str(boost::format("doSubscribe: reusing: %s") % strUrl);
|
||||
|
||||
if (jvRequest.isMember("username"))
|
||||
rspSub->setUsername(strUsername);
|
||||
|
||||
@@ -2430,7 +2493,6 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest)
|
||||
if (streamName=="server")
|
||||
{
|
||||
mNetOps->subServer(ispSub, jvResult);
|
||||
|
||||
}
|
||||
else if (streamName=="ledger")
|
||||
{
|
||||
@@ -2483,6 +2545,8 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest)
|
||||
else
|
||||
{
|
||||
mNetOps->subAccount(ispSub, usnaAccoundIds, uLedgerIndex, false);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("doSubscribe: accounts: %d") % usnaAccoundIds.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2702,6 +2766,7 @@ Json::Value RPCHandler::doCommand(const Json::Value& jvRequest, int iRole)
|
||||
// { "nickname_info", &RPCHandler::doNicknameInfo, false, optCurrent },
|
||||
{ "owner_info", &RPCHandler::doOwnerInfo, false, optCurrent },
|
||||
{ "peers", &RPCHandler::doPeers, true, optNone },
|
||||
{ "ping", &RPCHandler::doPing, false, optNone },
|
||||
// { "profile", &RPCHandler::doProfile, false, optCurrent },
|
||||
{ "random", &RPCHandler::doRandom, false, optNone },
|
||||
{ "ripple_path_find", &RPCHandler::doRipplePathFind, false, optCurrent },
|
||||
|
||||
@@ -41,7 +41,7 @@ class RPCHandler
|
||||
const RippleAddress& naVerifyGenerator);
|
||||
Json::Value accounts(Ledger::ref lrLedger, const RippleAddress& naMasterGenerator);
|
||||
|
||||
Json::Value accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex);
|
||||
Json::Value accountFromString(Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex, const bool bStrict);
|
||||
|
||||
Json::Value doAcceptLedger(Json::Value jvRequest);
|
||||
|
||||
@@ -64,6 +64,7 @@ class RPCHandler
|
||||
Json::Value doNicknameInfo(Json::Value params);
|
||||
Json::Value doOwnerInfo(Json::Value params);
|
||||
Json::Value doPeers(Json::Value params);
|
||||
Json::Value doPing(Json::Value params);
|
||||
Json::Value doProfile(Json::Value params);
|
||||
Json::Value doRandom(Json::Value jvRequest);
|
||||
Json::Value doRipplePathFind(Json::Value jvRequest);
|
||||
|
||||
@@ -8,27 +8,27 @@
|
||||
SETUP_LOG();
|
||||
|
||||
RPCSub::RPCSub(const std::string& strUrl, const std::string& strUsername, const std::string& strPassword)
|
||||
: mUrl(strUrl), mSSL(false), mUsername(strUsername), mPassword(strPassword)
|
||||
: mUrl(strUrl), mSSL(false), mUsername(strUsername), mPassword(strPassword), mSending(false)
|
||||
{
|
||||
std::string strScheme;
|
||||
|
||||
if (!parseUrl(strUrl, strScheme, mIp, mPort, mPath))
|
||||
{
|
||||
throw std::runtime_error("Failed to parse url.");
|
||||
throw std::runtime_error("Failed to parse url.");
|
||||
}
|
||||
else if (strScheme == "https")
|
||||
{
|
||||
mSSL = true;
|
||||
mSSL = true;
|
||||
}
|
||||
else if (strScheme != "http")
|
||||
{
|
||||
throw std::runtime_error("Only http and https is supported.");
|
||||
throw std::runtime_error("Only http and https is supported.");
|
||||
}
|
||||
|
||||
mSeq = 1;
|
||||
|
||||
if (mPort < 0)
|
||||
mPort = mSSL ? 443 : 80;
|
||||
mPort = mSSL ? 443 : 80;
|
||||
}
|
||||
|
||||
// XXX Could probably create a bunch of send jobs in a single get of the lock.
|
||||
@@ -39,74 +39,75 @@ void RPCSub::sendThread()
|
||||
|
||||
do
|
||||
{
|
||||
{
|
||||
// Obtain the lock to manipulate the queue and change sending.
|
||||
boost::mutex::scoped_lock sl(mLockInfo);
|
||||
{
|
||||
// Obtain the lock to manipulate the queue and change sending.
|
||||
boost::mutex::scoped_lock sl(mLockInfo);
|
||||
|
||||
if (mDeque.empty())
|
||||
{
|
||||
mSending = false;
|
||||
bSend = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::pair<int, Json::Value> pEvent = mDeque.front();
|
||||
if (mDeque.empty())
|
||||
{
|
||||
mSending = false;
|
||||
bSend = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::pair<int, Json::Value> pEvent = mDeque.front();
|
||||
|
||||
mDeque.pop_front();
|
||||
mDeque.pop_front();
|
||||
|
||||
jvEvent = pEvent.second;
|
||||
jvEvent["seq"] = pEvent.first;
|
||||
jvEvent = pEvent.second;
|
||||
jvEvent["seq"] = pEvent.first;
|
||||
|
||||
bSend = true;
|
||||
}
|
||||
}
|
||||
bSend = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Send outside of the lock.
|
||||
if (bSend)
|
||||
{
|
||||
// XXX Might not need this in a try.
|
||||
try
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("callRPC calling: %s") % mIp);
|
||||
// Send outside of the lock.
|
||||
if (bSend)
|
||||
{
|
||||
// XXX Might not need this in a try.
|
||||
try
|
||||
{
|
||||
cLog(lsINFO) << boost::str(boost::format("callRPC calling: %s") % mIp);
|
||||
|
||||
callRPC(
|
||||
theApp->getIOService(),
|
||||
mIp, mPort,
|
||||
mUsername, mPassword,
|
||||
mPath, "event",
|
||||
jvEvent,
|
||||
mSSL);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cLog(lsDEBUG) << boost::str(boost::format("callRPC exception: %s") % e.what());
|
||||
}
|
||||
}
|
||||
callRPC(
|
||||
theApp->getIOService(),
|
||||
mIp, mPort,
|
||||
mUsername, mPassword,
|
||||
mPath, "event",
|
||||
jvEvent,
|
||||
mSSL);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cLog(lsINFO) << boost::str(boost::format("callRPC exception: %s") % e.what());
|
||||
}
|
||||
}
|
||||
} while (bSend);
|
||||
}
|
||||
|
||||
void RPCSub::send(const Json::Value& jvObj)
|
||||
void RPCSub::send(const Json::Value& jvObj, bool broadcast)
|
||||
{
|
||||
boost::mutex::scoped_lock sl(mLockInfo);
|
||||
|
||||
if (RPC_EVENT_QUEUE_MAX == mDeque.size())
|
||||
{
|
||||
// Drop the previous event.
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("callRPC drop"));
|
||||
mDeque.pop_back();
|
||||
// Drop the previous event.
|
||||
cLog(lsWARNING) << boost::str(boost::format("callRPC drop"));
|
||||
mDeque.pop_back();
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("callRPC push: %s") % jvObj);
|
||||
cLog(broadcast ? lsDEBUG : lsINFO) << boost::str(boost::format("callRPC push: %s") % jvObj);
|
||||
|
||||
mDeque.push_back(std::make_pair(mSeq++, jvObj));
|
||||
|
||||
if (!mSending)
|
||||
{
|
||||
// Start a sending thread.
|
||||
mSending = true;
|
||||
// Start a sending thread.
|
||||
mSending = true;
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("callRPC start"));
|
||||
boost::thread(boost::bind(&RPCSub::sendThread, this)).detach();
|
||||
cLog(lsINFO) << boost::str(boost::format("callRPC start"));
|
||||
boost::thread(boost::bind(&RPCSub::sendThread, this)).detach();
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
virtual ~RPCSub() { ; }
|
||||
|
||||
// Implement overridden functions from base class:
|
||||
void send(const Json::Value& jvObj);
|
||||
void send(const Json::Value& jvObj, bool broadcast);
|
||||
|
||||
void setUsername(const std::string& strUsername)
|
||||
{
|
||||
|
||||
@@ -107,7 +107,7 @@ TER PathState::pushImply(
|
||||
uIssuerID);
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << boost::str(boost::format("pushImply< : %s") % transToken(terResult));
|
||||
cLog(lsINFO) << boost::str(boost::format("pushImply< : %s") % transToken(terResult));
|
||||
|
||||
return terResult;
|
||||
}
|
||||
@@ -280,7 +280,7 @@ TER PathState::pushNode(
|
||||
vpnNodes.push_back(pnCur);
|
||||
}
|
||||
}
|
||||
cLog(lsDEBUG) << boost::str(boost::format("pushNode< : %s") % transToken(terResult));
|
||||
cLog(lsINFO) << boost::str(boost::format("pushNode< : %s") % transToken(terResult));
|
||||
|
||||
return terResult;
|
||||
}
|
||||
@@ -1660,7 +1660,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
? lesActive.rippleOwed(uCurAccountID, uNxtAccountID, uCurrencyID)
|
||||
: STAmount(uCurrencyID, uCurAccountID);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev> uNode=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev> uNode=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s")
|
||||
% uNode
|
||||
% uLast
|
||||
% RippleAddress::createHumanAccountID(uPrvAccountID)
|
||||
@@ -1681,7 +1681,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
STAmount& saPrvIssueAct = pnPrv.saRevIssue;
|
||||
|
||||
// For !bPrvAccount
|
||||
const STAmount saPrvDeliverReq = STAmount::saFromSigned(uCurrencyID, uCurAccountID, -1); // Unlimited.
|
||||
const STAmount saPrvDeliverReq = STAmount(uCurrencyID, uCurAccountID, -1); // Unlimited.
|
||||
STAmount& saPrvDeliverAct = pnPrv.saRevDeliver;
|
||||
|
||||
// For bNxtAccount
|
||||
@@ -1695,14 +1695,14 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
const STAmount& saCurDeliverReq = pnCur.saRevDeliver;
|
||||
STAmount saCurDeliverAct(saCurDeliverReq.getCurrency(), saCurDeliverReq.getIssuer());
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saCurIssueReq=%s saNxtOwed=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saCurIssueReq=%s saNxtOwed=%s")
|
||||
% saPrvRedeemReq.getFullText()
|
||||
% saPrvIssueReq.getFullText()
|
||||
% saCurRedeemReq.getFullText()
|
||||
% saCurIssueReq.getFullText()
|
||||
% saNxtOwed.getFullText());
|
||||
|
||||
cLog(lsINFO) << psCur.getJson();
|
||||
cLog(lsDEBUG) << psCur.getJson();
|
||||
|
||||
assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand.
|
||||
assert(!saCurIssueReq // If not issuing, fine.
|
||||
@@ -1727,7 +1727,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
: psCur.saOutReq-psCur.saOutAct; // Previous is an offer, no limit: redeem own IOUs.
|
||||
STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer());
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s")
|
||||
% saCurWantedReq.getFullText());
|
||||
|
||||
// Calculate redeem
|
||||
@@ -1740,7 +1740,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
|
||||
uRateMax = STAmount::uRateOne;
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1 saPrvRedeemReq=%s (available) saPrvRedeemAct=%s uRateMax=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1 saPrvRedeemReq=%s (available) saPrvRedeemAct=%s uRateMax=%s")
|
||||
% saPrvRedeemReq.getFullText()
|
||||
% saPrvRedeemAct.getFullText()
|
||||
% STAmount::saFromRate(uRateMax).getText());
|
||||
@@ -1761,7 +1761,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
// If we previously redeemed and this has a poorer rate, this won't be included the current increment.
|
||||
calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct, uRateMax);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Issuing: Rate: quality in : 1.0 saPrvIssueAct=%s saCurWantedAct=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: Issuing: Rate: quality in : 1.0 saPrvIssueAct=%s saCurWantedAct=%s")
|
||||
% saPrvIssueAct.getFullText()
|
||||
% saCurWantedAct.getFullText());
|
||||
}
|
||||
@@ -1785,7 +1785,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
// Rate : 1.0 : quality out
|
||||
calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out saPrvRedeemAct=%s saCurRedeemAct=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out saPrvRedeemAct=%s saCurRedeemAct=%s")
|
||||
% saPrvRedeemAct.getFullText()
|
||||
% saCurRedeemAct.getFullText());
|
||||
}
|
||||
@@ -1797,7 +1797,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
// Rate: quality in : quality out
|
||||
calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out: saPrvIssueAct=%s saCurRedeemAct=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out: saPrvIssueAct=%s saCurRedeemAct=%s")
|
||||
% saPrvIssueAct.getFullText()
|
||||
% saCurRedeemAct.getFullText());
|
||||
}
|
||||
@@ -1824,7 +1824,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
// Rate: quality in : 1.0
|
||||
calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0: saPrvIssueAct=%s saCurIssueAct=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0: saPrvIssueAct=%s saCurIssueAct=%s")
|
||||
% saPrvIssueAct.getFullText()
|
||||
% saCurIssueAct.getFullText());
|
||||
}
|
||||
@@ -1835,7 +1835,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
terResult = tecPATH_DRY;
|
||||
}
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s")
|
||||
% saCurRedeemReq.getFullText()
|
||||
% saCurIssueReq.getFullText()
|
||||
% saPrvOwed.getFullText()
|
||||
@@ -1847,7 +1847,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
{
|
||||
// account --> ACCOUNT --> offer
|
||||
// Note: deliver is always issue as ACCOUNT is the issuer for the offer input.
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer"));
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer"));
|
||||
|
||||
saPrvRedeemAct.zero(saCurRedeemReq);
|
||||
saPrvIssueAct.zero(saCurRedeemReq);
|
||||
@@ -1874,7 +1874,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
terResult = tecPATH_DRY;
|
||||
}
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s")
|
||||
% saCurDeliverReq.getFullText()
|
||||
% saCurDeliverAct.getFullText()
|
||||
% saPrvOwed.getFullText());
|
||||
@@ -1891,7 +1891,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
: psCur.saOutReq-psCur.saOutAct; // Previous is an offer, no limit: redeem own IOUs.
|
||||
STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer());
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s")
|
||||
% saCurWantedReq.getFullText());
|
||||
|
||||
// Rate: quality in : 1.0
|
||||
@@ -1907,7 +1907,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
{
|
||||
// offer --> ACCOUNT --> account
|
||||
// Note: offer is always delivering(redeeming) as account is issuer.
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account"));
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account"));
|
||||
|
||||
// deliver -> redeem
|
||||
if (saCurRedeemReq) // Next wants us to redeem.
|
||||
@@ -1924,7 +1924,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax);
|
||||
}
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s")
|
||||
% saCurRedeemReq.getFullText()
|
||||
% saCurRedeemAct.getFullText()
|
||||
% saCurIssueReq.getFullText()
|
||||
@@ -1941,7 +1941,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c
|
||||
{
|
||||
// offer --> ACCOUNT --> offer
|
||||
// deliver/redeem -> deliver/issue.
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> offer"));
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> offer"));
|
||||
|
||||
saPrvDeliverAct.zero(saCurRedeemReq);
|
||||
|
||||
@@ -2025,7 +2025,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
// For !uNode
|
||||
STAmount& saCurSendMaxPass = psCur.saInPass; // Report how much pass sends.
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s")
|
||||
% uNode
|
||||
% uLast
|
||||
% saPrvRedeemReq.getFullText()
|
||||
@@ -2069,7 +2069,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
|
||||
saCurSendMaxPass += saCurIssueAct;
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saInReq=%s saInAct=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s saCurSendMaxPass=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saInReq=%s saInAct=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s saCurSendMaxPass=%s")
|
||||
% psCur.saInReq.getFullText()
|
||||
% psCur.saInAct.getFullText()
|
||||
% saCurRedeemAct.getFullText()
|
||||
@@ -2080,7 +2080,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
else if (uNode == uLast)
|
||||
{
|
||||
// account --> ACCOUNT --> $
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s")
|
||||
% RippleAddress::createHumanAccountID(uPrvAccountID)
|
||||
% RippleAddress::createHumanAccountID(uCurAccountID)
|
||||
% saPrvRedeemReq.getFullText()
|
||||
@@ -2103,7 +2103,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
else
|
||||
{
|
||||
// account --> ACCOUNT --> account
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account"));
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account"));
|
||||
|
||||
saCurRedeemAct.zero(saCurRedeemReq);
|
||||
saCurIssueAct.zero(saCurIssueReq);
|
||||
@@ -2223,7 +2223,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
if (uNode == uLast)
|
||||
{
|
||||
// offer --> ACCOUNT --> $
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $ : %s") % saPrvDeliverReq.getFullText());
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $ : %s") % saPrvDeliverReq.getFullText());
|
||||
|
||||
STAmount& saCurReceive = psCur.saOutPass;
|
||||
|
||||
@@ -2235,7 +2235,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
else
|
||||
{
|
||||
// offer --> ACCOUNT --> account
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account"));
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account"));
|
||||
|
||||
saCurRedeemAct.zero(saCurRedeemReq);
|
||||
saCurIssueAct.zero(saCurIssueReq);
|
||||
@@ -2264,7 +2264,7 @@ TER RippleCalc::calcNodeAccountFwd(
|
||||
{
|
||||
// offer --> ACCOUNT --> offer
|
||||
// deliver/redeem -> deliver/issue.
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer"));
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer"));
|
||||
|
||||
saCurDeliverAct.zero(saCurDeliverReq);
|
||||
|
||||
@@ -2286,7 +2286,7 @@ TER RippleCalc::calcNodeFwd(const unsigned int uNode, PathState& psCur, const bo
|
||||
const PaymentNode& pnCur = psCur.vpnNodes[uNode];
|
||||
const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount);
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeFwd> uNode=%d") % uNode);
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeFwd> uNode=%d") % uNode);
|
||||
|
||||
TER terResult = bCurAccount
|
||||
? calcNodeAccountFwd(uNode, psCur, bMultiQuality)
|
||||
@@ -2297,7 +2297,7 @@ TER RippleCalc::calcNodeFwd(const unsigned int uNode, PathState& psCur, const bo
|
||||
terResult = calcNodeFwd(uNode+1, psCur, bMultiQuality);
|
||||
}
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeFwd< uNode=%d terResult=%d") % uNode % terResult);
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeFwd< uNode=%d terResult=%d") % uNode % terResult);
|
||||
|
||||
return terResult;
|
||||
}
|
||||
@@ -2323,7 +2323,7 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState& psCur, const bo
|
||||
|
||||
saTransferRate = STAmount::saFromRate(lesActive.rippleTransferRate(uCurIssuerID));
|
||||
|
||||
cLog(lsINFO) << boost::str(boost::format("calcNodeRev> uNode=%d uIssuerID=%s saTransferRate=%s")
|
||||
cLog(lsDEBUG) << boost::str(boost::format("calcNodeRev> uNode=%d uIssuerID=%s saTransferRate=%s")
|
||||
% uNode
|
||||
% RippleAddress::createHumanAccountID(uCurIssuerID)
|
||||
% saTransferRate.getFullText());
|
||||
@@ -2368,7 +2368,7 @@ void RippleCalc::pathNext(PathState::ref psrCur, const bool bMultiQuality, const
|
||||
psrCur->vUnfundedBecame.clear();
|
||||
psrCur->umReverse.clear();
|
||||
|
||||
cLog(lsINFO) << "pathNext: Path In: " << psrCur->getJson();
|
||||
cLog(lsDEBUG) << "pathNext: Path In: " << psrCur->getJson();
|
||||
|
||||
assert(psrCur->vpnNodes.size() >= 2);
|
||||
|
||||
@@ -2377,7 +2377,7 @@ void RippleCalc::pathNext(PathState::ref psrCur, const bool bMultiQuality, const
|
||||
|
||||
psrCur->terStatus = calcNodeRev(uLast, *psrCur, bMultiQuality);
|
||||
|
||||
cLog(lsINFO) << "pathNext: Path after reverse: " << psrCur->getJson();
|
||||
cLog(lsDEBUG) << "pathNext: Path after reverse: " << psrCur->getJson();
|
||||
|
||||
if (tesSUCCESS == psrCur->terStatus)
|
||||
{
|
||||
@@ -2400,7 +2400,7 @@ void RippleCalc::pathNext(PathState::ref psrCur, const bool bMultiQuality, const
|
||||
|
||||
psrCur->uQuality = STAmount::getRate(psrCur->saOutPass, psrCur->saInPass); // Calculate relative quality.
|
||||
|
||||
cLog(lsINFO) << "pathNext: Path after forward: " << psrCur->getJson();
|
||||
cLog(lsDEBUG) << "pathNext: Path after forward: " << psrCur->getJson();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ void SHAMap::getMissingNodes(std::vector<SHAMapNode>& nodeIDs, std::vector<uint2
|
||||
{
|
||||
SHAMapTreeNode::pointer ptr =
|
||||
boost::make_shared<SHAMapTreeNode>(childID, nodeData, mSeq - 1, snfPREFIX, childHash);
|
||||
cLog(lsTRACE) << "Got sync node from cache: " << *d;
|
||||
cLog(lsTRACE) << "Got sync node from cache: " << *ptr;
|
||||
mTNByID[*ptr] = ptr;
|
||||
d = ptr.get();
|
||||
}
|
||||
|
||||
@@ -233,6 +233,34 @@ protected:
|
||||
: SerializedType(name), mCurrency(cur), mIssuer(iss), mValue(val), mOffset(off),
|
||||
mIsNative(isNat), mIsNegative(isNeg) { ; }
|
||||
|
||||
void set(int64 v)
|
||||
{
|
||||
if (v < 0)
|
||||
{
|
||||
mIsNegative = true;
|
||||
mValue = static_cast<uint64>(-v);
|
||||
}
|
||||
else
|
||||
{
|
||||
mIsNegative = false;
|
||||
mValue = static_cast<uint64>(v);
|
||||
}
|
||||
}
|
||||
|
||||
void set(int v)
|
||||
{
|
||||
if (v < 0)
|
||||
{
|
||||
mIsNegative = true;
|
||||
mValue = static_cast<uint64>(-v);
|
||||
}
|
||||
else
|
||||
{
|
||||
mIsNegative = false;
|
||||
mValue = static_cast<uint64>(v);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
static const int cMinOffset = -96, cMaxOffset = 80;
|
||||
static const uint64 cMinValue = 1000000000000000ull, cMaxValue = 9999999999999999ull;
|
||||
@@ -249,16 +277,52 @@ public:
|
||||
: SerializedType(n), mValue(v), mOffset(0), mIsNative(true), mIsNegative(isNeg)
|
||||
{ ; }
|
||||
|
||||
STAmount(const uint160& uCurrencyID, const uint160& uIssuerID, uint64 uV = 0, int iOff = 0, bool bNegative = false)
|
||||
STAmount(SField::ref n, int64 v) : SerializedType(n), mOffset(0), mIsNative(true)
|
||||
{ set(v); }
|
||||
|
||||
STAmount(const uint160& uCurrencyID, const uint160& uIssuerID,
|
||||
uint64 uV = 0, int iOff = 0, bool bNegative = false)
|
||||
: mCurrency(uCurrencyID), mIssuer(uIssuerID), mValue(uV), mOffset(iOff), mIsNegative(bNegative)
|
||||
{ canonicalize(); }
|
||||
|
||||
STAmount(const uint160& uCurrencyID, const uint160& uIssuerID,
|
||||
uint32 uV, int iOff = 0, bool bNegative = false)
|
||||
: mCurrency(uCurrencyID), mIssuer(uIssuerID), mValue(uV), mOffset(iOff), mIsNegative(bNegative)
|
||||
{ canonicalize(); }
|
||||
|
||||
// YYY This should probably require issuer too.
|
||||
STAmount(SField::ref n, const uint160& currency, const uint160& issuer,
|
||||
uint64 v = 0, int off = 0, bool isNeg = false) :
|
||||
SerializedType(n), mCurrency(currency), mIssuer(issuer), mValue(v), mOffset(off), mIsNegative(isNeg)
|
||||
{ canonicalize(); }
|
||||
|
||||
STAmount(const uint160& uCurrencyID, const uint160& uIssuerID, int64 v, int iOff = 0)
|
||||
: mCurrency(uCurrencyID), mIssuer(uIssuerID), mOffset(iOff)
|
||||
{
|
||||
set(v);
|
||||
canonicalize();
|
||||
}
|
||||
|
||||
STAmount(SField::ref n, const uint160& currency, const uint160& issuer, int64 v, int off = 0)
|
||||
: SerializedType(n), mCurrency(currency), mIssuer(issuer), mOffset(off)
|
||||
{
|
||||
set(v);
|
||||
canonicalize();
|
||||
}
|
||||
|
||||
STAmount(const uint160& uCurrencyID, const uint160& uIssuerID, int v, int iOff = 0)
|
||||
: mCurrency(uCurrencyID), mIssuer(uIssuerID), mOffset(iOff)
|
||||
{
|
||||
set(v);
|
||||
canonicalize();
|
||||
}
|
||||
|
||||
STAmount(SField::ref n, const uint160& currency, const uint160& issuer, int v, int off = 0)
|
||||
: SerializedType(n), mCurrency(currency), mIssuer(issuer), mOffset(off)
|
||||
{
|
||||
set(v);
|
||||
canonicalize();
|
||||
}
|
||||
|
||||
STAmount(SField::ref, const Json::Value&);
|
||||
|
||||
static STAmount createFromInt64(SField::ref n, int64 v);
|
||||
@@ -271,9 +335,6 @@ public:
|
||||
static STAmount saFromRate(uint64 uRate = 0)
|
||||
{ return STAmount(CURRENCY_ONE, ACCOUNT_ONE, uRate, -9, false); }
|
||||
|
||||
static STAmount saFromSigned(const uint160& uCurrencyID, const uint160& uIssuerID, int64 iV = 0, int iOff = 0)
|
||||
{ return STAmount(uCurrencyID, uIssuerID, iV < 0 ? -iV : iV, iOff, iV < 0); }
|
||||
|
||||
SerializedTypeID getSType() const { return STI_AMOUNT; }
|
||||
std::string getText() const;
|
||||
std::string getRaw() const;
|
||||
|
||||
@@ -56,7 +56,7 @@ protected:
|
||||
mutable boost::recursive_mutex mLock;
|
||||
|
||||
std::string mName; // Used for logging
|
||||
unsigned int mTargetSize; // Desired number of cache entries (0 = ignore)
|
||||
int mTargetSize; // Desired number of cache entries (0 = ignore)
|
||||
int mTargetAge; // Desired maximum cache age
|
||||
int mCacheCount; // Number of items cached
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ std::vector<RippleAddress> TransactionMetaSet::getAffectedAccounts()
|
||||
std::vector<RippleAddress> accounts;
|
||||
accounts.reserve(10);
|
||||
|
||||
// This code should match the behavior of the JS method:
|
||||
// Meta#getAffectedAccounts
|
||||
BOOST_FOREACH(const STObject& it, mNodes)
|
||||
{
|
||||
int index = it.getFieldIndex((it.getFName() == sfCreatedNode) ? sfNewFields : sfFinalFields);
|
||||
|
||||
@@ -19,6 +19,7 @@ class TransactionMetaSet
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<TransactionMetaSet> pointer;
|
||||
typedef const pointer& ref;
|
||||
|
||||
protected:
|
||||
uint256 mTransactionID;
|
||||
|
||||
@@ -206,15 +206,15 @@ TER Transactor::apply()
|
||||
mHasAuthKey = mTxnAccount->isFieldPresent(sfRegularKey);
|
||||
}
|
||||
|
||||
terResult = checkSeq();
|
||||
if (terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
terResult = payFee();
|
||||
if (terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
terResult = checkSig();
|
||||
if (terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
terResult = checkSeq();
|
||||
if (terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
mEngine->entryModify(mTxnAccount);
|
||||
|
||||
return doApply();
|
||||
|
||||
@@ -72,11 +72,11 @@ public:
|
||||
}
|
||||
|
||||
// Implement overridden functions from base class:
|
||||
void send(const Json::Value& jvObj)
|
||||
void send(const Json::Value& jvObj, bool broadcast)
|
||||
{
|
||||
connection_ptr ptr = mConnection.lock();
|
||||
if (ptr)
|
||||
mHandler->send(ptr, jvObj);
|
||||
mHandler->send(ptr, jvObj, broadcast);
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
@@ -61,11 +61,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void send(connection_ptr cpClient, const std::string& strMessage)
|
||||
void send(connection_ptr cpClient, const std::string& strMessage, bool broadcast)
|
||||
{
|
||||
try
|
||||
{
|
||||
cLog(lsDEBUG) << "Ws:: Sending '" << strMessage << "'";
|
||||
cLog(broadcast ? lsTRACE : lsDEBUG) << "Ws:: Sending '" << strMessage << "'";
|
||||
|
||||
cpClient->send(strMessage);
|
||||
}
|
||||
@@ -75,13 +75,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void send(connection_ptr cpClient, const Json::Value& jvObj)
|
||||
void send(connection_ptr cpClient, const Json::Value& jvObj, bool broadcast)
|
||||
{
|
||||
Json::FastWriter jfwWriter;
|
||||
|
||||
// cLog(lsDEBUG) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'";
|
||||
|
||||
send(cpClient, jfwWriter.write(jvObj));
|
||||
send(cpClient, jfwWriter.write(jvObj), broadcast);
|
||||
}
|
||||
|
||||
void pingTimer(connection_ptr cpClient)
|
||||
@@ -177,7 +177,7 @@ public:
|
||||
jvResult["type"] = "error";
|
||||
jvResult["error"] = "wsTextRequired"; // We only accept text messages.
|
||||
|
||||
send(cpClient, jvResult);
|
||||
send(cpClient, jvResult, false);
|
||||
}
|
||||
else if (!jrReader.parse(mpMessage->get_payload(), jvRequest) || jvRequest.isNull() || !jvRequest.isObject())
|
||||
{
|
||||
@@ -187,7 +187,7 @@ public:
|
||||
jvResult["error"] = "jsonInvalid"; // Received invalid json.
|
||||
jvResult["value"] = mpMessage->get_payload();
|
||||
|
||||
send(cpClient, jvResult);
|
||||
send(cpClient, jvResult, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -200,7 +200,7 @@ public:
|
||||
return;
|
||||
conn = it->second;
|
||||
}
|
||||
send(cpClient, conn->invokeCommand(jvRequest));
|
||||
send(cpClient, conn->invokeCommand(jvRequest), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,121 @@
|
||||
|
||||
// var network = require("./network.js");
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Amount = require('./amount.js').Amount;
|
||||
var UInt160 = require('./amount.js').UInt160;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
|
||||
var Account = function (network, account) {
|
||||
this._network = network;
|
||||
this._account = UInt160.json_rewrite(account);
|
||||
var extend = require('extend');
|
||||
|
||||
return this;
|
||||
var Account = function (remote, account) {
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._account = UInt160.from_json(account);
|
||||
this._account_id = this._account.to_json();
|
||||
this._subs = 0;
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = {};
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!this._subs && 'open' === this._remote._online_state) {
|
||||
this._remote.request_subscribe()
|
||||
.accounts(this._account_id)
|
||||
.request();
|
||||
}
|
||||
this._subs += 1;
|
||||
}
|
||||
});
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
this._subs -= 1;
|
||||
|
||||
if (!this._subs && 'open' === this._remote._online_state) {
|
||||
this._remote.request_unsubscribe()
|
||||
.accounts(this._account_id)
|
||||
.request();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._remote.on('connect', function () {
|
||||
if (self._subs) {
|
||||
this._remote.request_subscribe()
|
||||
.accounts(this._account_id)
|
||||
.request();
|
||||
}
|
||||
});
|
||||
|
||||
this.on('transaction', function (msg) {
|
||||
var changed = false;
|
||||
msg.mmeta.each(function (an) {
|
||||
if (an.entryType === 'AccountRoot' &&
|
||||
an.fields.Account === self._account_id) {
|
||||
extend(self._entry, an.fieldsNew, an.fieldsFinal);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
self.emit('entry', self._entry);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Account.prototype = new EventEmitter;
|
||||
|
||||
/**
|
||||
* List of events that require a remote subscription to the account.
|
||||
*/
|
||||
Account.subscribe_events = ['transaction', 'entry'];
|
||||
|
||||
Account.prototype.to_json = function ()
|
||||
{
|
||||
return this._account.to_json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the AccountId is valid.
|
||||
*
|
||||
* Note: This does not tell you whether the account exists in the ledger.
|
||||
*/
|
||||
Account.prototype.is_valid = function ()
|
||||
{
|
||||
return this._account.is_valid();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the current AccountRoot entry.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* "entry" event.
|
||||
*
|
||||
* @param {function (err, entry)} callback Called with the result
|
||||
*/
|
||||
Account.prototype.entry = function (callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
self._remote.request_account_info(this._account_id)
|
||||
.on('success', function (e) {
|
||||
extend(self._entry, e.account_data);
|
||||
self.emit('entry', self._entry);
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
})
|
||||
.on('error', function (e) {
|
||||
callback(e);
|
||||
})
|
||||
.request();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
|
||||
@@ -380,7 +380,12 @@ Amount.prototype.ratio_human = function (denominator) {
|
||||
* @return {Amount} The product. Unit will be the same as the first factor.
|
||||
*/
|
||||
Amount.prototype.product_human = function (factor) {
|
||||
factor = Amount.from_json(factor);
|
||||
if ("number" === typeof factor && parseInt(factor) === factor) {
|
||||
// Special handling of integer arguments
|
||||
factor = Amount.from_json("" + factor + ".0");
|
||||
} else {
|
||||
factor = Amount.from_json(factor);
|
||||
}
|
||||
|
||||
var product = this.multiply(factor);
|
||||
|
||||
@@ -413,6 +418,10 @@ Amount.prototype.is_negative = function () {
|
||||
: false; // NaN is not negative
|
||||
};
|
||||
|
||||
Amount.prototype.is_positive = function () {
|
||||
return !this.is_zero() && !this.is_negative();
|
||||
};
|
||||
|
||||
// Only checks the value. Not the currency and issuer.
|
||||
Amount.prototype.is_valid = function () {
|
||||
return this._value instanceof BigInteger;
|
||||
@@ -436,7 +445,7 @@ Amount.prototype.issuer = function () {
|
||||
Amount.prototype.multiply = function (v) {
|
||||
var result;
|
||||
|
||||
if (this.is_zero()) {
|
||||
if (this.is_zero()) {
|
||||
result = this.clone();
|
||||
}
|
||||
else if (v.is_zero()) {
|
||||
@@ -690,6 +699,7 @@ Amount.prototype.set_currency = function (c) {
|
||||
{
|
||||
c.copyTo(this._currency);
|
||||
}
|
||||
this._is_native = this._currency.is_native();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
108
src/js/meta.js
Normal file
108
src/js/meta.js
Normal file
@@ -0,0 +1,108 @@
|
||||
var extend = require('extend');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
/**
|
||||
* Meta data processing facility.
|
||||
*/
|
||||
var Meta = function (raw_data)
|
||||
{
|
||||
this.nodes = [];
|
||||
|
||||
for (var i = 0, l = raw_data.AffectedNodes.length; i < l; i++) {
|
||||
var an = raw_data.AffectedNodes[i],
|
||||
result = {};
|
||||
|
||||
["CreatedNode", "ModifiedNode", "DeletedNode"].forEach(function (x) {
|
||||
if (an[x]) result.diffType = x;
|
||||
});
|
||||
|
||||
if (!result.diffType) return null;
|
||||
|
||||
an = an[result.diffType];
|
||||
|
||||
result.entryType = an.LedgerEntryType;
|
||||
result.ledgerIndex = an.LedgerIndex;
|
||||
|
||||
result.fields = extend({}, an.PreviousFields, an.NewFields, an.FinalFields);
|
||||
result.fieldsPrev = an.PreviousFields || {};
|
||||
result.fieldsNew = an.NewFields || {};
|
||||
result.fieldsFinal = an.FinalFields || {};
|
||||
|
||||
this.nodes.push(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a function on each affected node.
|
||||
*
|
||||
* The callback is passed two parameters. The first is a node object which looks
|
||||
* like this:
|
||||
*
|
||||
* {
|
||||
* // Type of diff, e.g. CreatedNode, ModifiedNode
|
||||
* diffType: 'CreatedNode'
|
||||
*
|
||||
* // Type of node affected, e.g. RippleState, AccountRoot
|
||||
* entryType: 'RippleState',
|
||||
*
|
||||
* // Index of the ledger this change occurred in
|
||||
* ledgerIndex: '01AB01AB...',
|
||||
*
|
||||
* // Contains all fields with later versions taking precedence
|
||||
* //
|
||||
* // This is a shorthand for doing things like checking which account
|
||||
* // this affected without having to check the diffType.
|
||||
* fields: {...},
|
||||
*
|
||||
* // Old fields (before the change)
|
||||
* fieldsPrev: {...},
|
||||
*
|
||||
* // New fields (that have been added)
|
||||
* fieldsNew: {...},
|
||||
*
|
||||
* // Changed fields
|
||||
* fieldsFinal: {...}
|
||||
* }
|
||||
*
|
||||
* The second parameter to the callback is the index of the node in the metadata
|
||||
* (first entry is index 0).
|
||||
*/
|
||||
Meta.prototype.each = function (fn)
|
||||
{
|
||||
for (var i = 0, l = this.nodes.length; i < l; i++) {
|
||||
fn(this.nodes[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
var amountFieldsAffectingIssuer = [
|
||||
"LowLimit", "HighLimit", "TakerPays", "TakerGets"
|
||||
];
|
||||
Meta.prototype.getAffectedAccounts = function ()
|
||||
{
|
||||
var accounts = [];
|
||||
|
||||
// This code should match the behavior of the C++ method:
|
||||
// TransactionMetaSet::getAffectedAccounts
|
||||
this.each(function (an) {
|
||||
var fields = (an.diffType === "CreatedNode") ? an.fieldsNew : an.fieldsFinal;
|
||||
|
||||
for (var i in fields) {
|
||||
var field = fields[i];
|
||||
|
||||
if ("string" === typeof field && UInt160.is_valid(field)) {
|
||||
accounts.push(field);
|
||||
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) {
|
||||
var amount = Amount.from_json(field);
|
||||
var issuer = amount.issuer();
|
||||
if (issuer.is_valid() && !issuer.is_zero()) {
|
||||
accounts.push(issuer.to_json());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return accounts;
|
||||
};
|
||||
|
||||
exports.Meta = Meta;
|
||||
@@ -20,6 +20,8 @@ var Amount = require('./amount').Amount;
|
||||
var Currency = require('./amount').Currency;
|
||||
var UInt160 = require('./amount').UInt160;
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var Account = require('./account').Account;
|
||||
var Meta = require('./meta').Meta;
|
||||
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
@@ -233,6 +235,7 @@ var Remote = function (opts, trace) {
|
||||
}
|
||||
|
||||
// Cache information for accounts.
|
||||
// DEPRECATED, will be removed
|
||||
this.accounts = {
|
||||
// Consider sequence numbers stable if you know you're not generating bad transactions.
|
||||
// Otherwise, clear it to have it automatically refreshed from the network.
|
||||
@@ -241,6 +244,9 @@ var Remote = function (opts, trace) {
|
||||
|
||||
};
|
||||
|
||||
// Hash map of Account objects by AccountId.
|
||||
this._accounts = {};
|
||||
|
||||
// List of secrets that we know about.
|
||||
this.secrets = {
|
||||
// Secrets can be set by calling set_secret(account, secret).
|
||||
@@ -342,11 +348,13 @@ Remote.prototype._set_state = function (state) {
|
||||
switch (state) {
|
||||
case 'online':
|
||||
this._online_state = 'open';
|
||||
this.emit('connect');
|
||||
this.emit('connected');
|
||||
break;
|
||||
|
||||
case 'offline':
|
||||
this._online_state = 'closed';
|
||||
this.emit('disconnect');
|
||||
this.emit('disconnected');
|
||||
break;
|
||||
}
|
||||
@@ -387,11 +395,13 @@ Remote.prototype.ledger_hash = function () {
|
||||
|
||||
// Stop from open state.
|
||||
Remote.prototype._connect_stop = function () {
|
||||
delete this.ws.onerror;
|
||||
delete this.ws.onclose;
|
||||
if (this.ws) {
|
||||
delete this.ws.onerror;
|
||||
delete this.ws.onclose;
|
||||
|
||||
this.ws.terminate();
|
||||
delete this.ws;
|
||||
this.ws.close();
|
||||
delete this.ws;
|
||||
}
|
||||
|
||||
this._set_state('offline');
|
||||
};
|
||||
@@ -450,6 +460,12 @@ Remote.prototype._connect_start = function () {
|
||||
|
||||
if (this.trace) console.log("remote: connect: %s", url);
|
||||
|
||||
// There should not be an active connection at this point, but if there is
|
||||
// we will shut it down so we don't end up with a duplicate.
|
||||
if (this.ws) {
|
||||
this._connect_stop();
|
||||
}
|
||||
|
||||
var WebSocket = require('ws');
|
||||
var ws = this.ws = new WebSocket(url);
|
||||
|
||||
@@ -507,6 +523,7 @@ Remote.prototype._connect_start = function () {
|
||||
|
||||
// It is possible for messages to be dispatched after the connection is closed.
|
||||
Remote.prototype._connect_message = function (ws, json) {
|
||||
var self = this;
|
||||
var message = JSON.parse(json);
|
||||
var unexpected = false;
|
||||
var request;
|
||||
@@ -556,6 +573,23 @@ Remote.prototype._connect_message = function (ws, json) {
|
||||
|
||||
case 'account':
|
||||
// XXX If not trusted, need proof.
|
||||
if (this.trace) utils.logObject("remote: account: %s", message);
|
||||
|
||||
// Process metadata
|
||||
message.mmeta = new Meta(message.meta);
|
||||
|
||||
// Pass the event on to any related Account objects
|
||||
var affected = message.mmeta.getAffectedAccounts();
|
||||
for (var i = 0, l = affected.length; i < l; i++) {
|
||||
var account = self._accounts[affected[i]];
|
||||
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (account && account._subs) {
|
||||
account.emit('transaction', message);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('account', message);
|
||||
break;
|
||||
@@ -930,7 +964,7 @@ Remote.prototype._server_subscribe = function () {
|
||||
self._load_factor = message.load_factor || 1.0;
|
||||
self._fee_ref = message.fee_ref;
|
||||
self._fee_base = message.fee_base;
|
||||
self._reserve_base = message.reverse_base;
|
||||
self._reserve_base = message.reserve_base;
|
||||
self._reserve_inc = message.reserve_inc;
|
||||
self._server_status = message.server_status;
|
||||
|
||||
@@ -994,6 +1028,14 @@ Remote.prototype.request_owner_count = function (account, current) {
|
||||
});
|
||||
};
|
||||
|
||||
Remote.prototype.account = function (accountId) {
|
||||
var account = new Account(this, accountId);
|
||||
|
||||
if (!account.is_valid()) return account;
|
||||
|
||||
return this._accounts[account.to_json()] = account;
|
||||
};
|
||||
|
||||
// Return the next account sequence if possible.
|
||||
// <-- undefined or Sequence
|
||||
Remote.prototype.account_seq = function (account, advance) {
|
||||
|
||||
@@ -401,8 +401,8 @@ Transaction.prototype.destination_tag = function (tag) {
|
||||
Transaction._path_rewrite = function (path) {
|
||||
var path_new = [];
|
||||
|
||||
for (var index in path) {
|
||||
var node = path[index];
|
||||
for (var i = 0, l = path.length; i < l; i++) {
|
||||
var node = path[i];
|
||||
var node_new = {};
|
||||
|
||||
if ('account' in node)
|
||||
@@ -421,7 +421,7 @@ Transaction._path_rewrite = function (path) {
|
||||
}
|
||||
|
||||
Transaction.prototype.path_add = function (path) {
|
||||
this.tx_json.Paths = this.tx_json.Paths || []
|
||||
this.tx_json.Paths = this.tx_json.Paths || [];
|
||||
this.tx_json.Paths.push(Transaction._path_rewrite(path));
|
||||
|
||||
return this;
|
||||
@@ -430,8 +430,8 @@ Transaction.prototype.path_add = function (path) {
|
||||
// --> paths: undefined or array of path
|
||||
// A path is an array of objects containing some combination of: account, currency, issuer
|
||||
Transaction.prototype.paths = function (paths) {
|
||||
for (var index in paths) {
|
||||
this.path_add(paths[index]);
|
||||
for (var i = 0, l = paths.length; i < l; i++) {
|
||||
this.path_add(paths[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
@@ -20,8 +20,8 @@ var UInt = function () {
|
||||
this._value = NaN;
|
||||
};
|
||||
|
||||
UInt.json_rewrite = function (j) {
|
||||
return this.from_json(j).to_json();
|
||||
UInt.json_rewrite = function (j, opts) {
|
||||
return this.from_json(j).to_json(opts);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
@@ -92,6 +92,10 @@ UInt.prototype.is_valid = function () {
|
||||
return this._value instanceof BigInteger;
|
||||
};
|
||||
|
||||
UInt.prototype.is_zero = function () {
|
||||
return this._value.equals(BigInteger.ZERO);
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt.prototype.parse_generic = function (j) {
|
||||
// Canonicalize and validate
|
||||
|
||||
@@ -59,8 +59,8 @@ UInt160.prototype.to_json = function (opts) {
|
||||
|
||||
var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes());
|
||||
|
||||
if (config.gateways && output in config.gateways && !opts.no_gateway)
|
||||
output = config.gateways[output];
|
||||
if (opts.gateways && output in opts.gateways)
|
||||
output = opts.gateways[output];
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
@@ -1354,6 +1354,93 @@ buster.testCase("Offer tests 3", {
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, standalone: true }),
|
||||
'tearDown' : testutils.build_teardown(),
|
||||
|
||||
"offer fee consumes funds" :
|
||||
function (done) {
|
||||
var self = this;
|
||||
var final_create;
|
||||
|
||||
async.waterfall([
|
||||
function (callback) {
|
||||
// Provide micro amounts to compensate for fees to make results round nice.
|
||||
self.what = "Create accounts.";
|
||||
|
||||
testutils.create_accounts(self.remote, "root", "350.000020", ["alice", "bob", "mtgox"], callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set limits.";
|
||||
|
||||
testutils.credit_limits(self.remote,
|
||||
{
|
||||
"alice" : "1000/USD/mtgox",
|
||||
"bob" : "1000/USD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Distribute funds.";
|
||||
|
||||
testutils.payments(self.remote,
|
||||
{
|
||||
"mtgox" : [ "500/USD/bob" ],
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Create offer bob.";
|
||||
|
||||
self.remote.transaction()
|
||||
.offer_create("bob", "200.0", "200/USD/mtgox")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: offer_create: %s", json.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
// Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
|
||||
// Ask for more than available to prove reserve works.
|
||||
self.what = "Create offer alice.";
|
||||
|
||||
self.remote.transaction()
|
||||
.offer_create("alice", "200/USD/mtgox", "200.0")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: offer_create: %s", json.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
// function (callback) {
|
||||
// self.what = "Display ledger";
|
||||
//
|
||||
// self.remote.request_ledger('current', true)
|
||||
// .on('success', function (m) {
|
||||
// console.log("Ledger: %s", JSON.stringify(m, undefined, 2));
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
function (callback) {
|
||||
self.what = "Verify balances.";
|
||||
|
||||
testutils.verify_balances(self.remote,
|
||||
{
|
||||
"alice" : [ "100/USD/mtgox", "250.0" ],
|
||||
"bob" : "400/USD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
], function (error) {
|
||||
// console.log("result: error=%s", error);
|
||||
buster.refute(error);
|
||||
|
||||
done();
|
||||
});
|
||||
},
|
||||
"offer create then cross offer" :
|
||||
function (done) {
|
||||
var self = this;
|
||||
|
||||
@@ -906,7 +906,6 @@ buster.testCase("Via offers", {
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, no_server: true }),
|
||||
'tearDown' : testutils.build_teardown(),
|
||||
|
||||
// XXX Triggers bad path expansion.
|
||||
"Via gateway" :
|
||||
// carol holds mtgoxAUD, sells mtgoxAUD for XRP
|
||||
// bob will hold mtgoxAUD
|
||||
@@ -1138,4 +1137,53 @@ buster.testCase("Via offers", {
|
||||
},
|
||||
});
|
||||
|
||||
buster.testCase("Indirect paths", {
|
||||
// 'setUp' : testutils.build_setup(),
|
||||
'setUp' : testutils.build_setup({ verbose: true }),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, no_server: true }),
|
||||
'tearDown' : testutils.build_teardown(),
|
||||
|
||||
"//path find" :
|
||||
function (done) {
|
||||
var self = this;
|
||||
|
||||
async.waterfall([
|
||||
function (callback) {
|
||||
self.what = "Create accounts.";
|
||||
|
||||
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol"], callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set credit limits.";
|
||||
|
||||
testutils.credit_limits(self.remote,
|
||||
{
|
||||
"bob" : "1000/USD/alice",
|
||||
"carol" : "2000/USD/bob",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Find path from alice to carol";
|
||||
|
||||
self.remote.request_ripple_path_find("alice", "carol", "5/USD/carol",
|
||||
[ { 'currency' : "USD" } ])
|
||||
.on('success', function (m) {
|
||||
console.log("proposed: %s", JSON.stringify(m));
|
||||
|
||||
// 1 alternative.
|
||||
buster.assert.equals(1, m.alternatives.length)
|
||||
// Path is empty.
|
||||
buster.assert.equals(0, m.alternatives[0].paths_canonical.length)
|
||||
|
||||
callback();
|
||||
})
|
||||
.request();
|
||||
},
|
||||
], function (error) {
|
||||
buster.refute(error, self.what);
|
||||
done();
|
||||
});
|
||||
},
|
||||
});
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
Reference in New Issue
Block a user