Merge branch 'master' of github.com:jedmccaleb/NewCoin

This commit is contained in:
Arthur Britto
2013-02-17 23:00:04 -08:00
48 changed files with 1237 additions and 498 deletions

View File

@@ -19,6 +19,7 @@
"simple-jsonrpc": "~0.0.1"
},
"devDependencies": {
"grunt": "~0.3.17",
"buster": "~0.6.2",
"grunt-webpack": "~0.4.0"
},

View File

@@ -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.
#

View File

@@ -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)

View File

@@ -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 },

View File

@@ -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);

View File

@@ -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;

View File

@@ -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];

View File

@@ -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
{

View File

@@ -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)

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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))

View File

@@ -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();

View File

@@ -164,8 +164,8 @@ public:
Json::Value getJson(uint64 baseFee, uint32 referenceFeeUnits);
void setRemoteFee(uint32);
void raiseLocalFee();
void lowerLocalFee();
bool raiseLocalFee();
bool lowerLocalFee();
};

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 },

View File

@@ -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);

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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
{

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -19,6 +19,7 @@ class TransactionMetaSet
{
public:
typedef boost::shared_ptr<TransactionMetaSet> pointer;
typedef const pointer& ref;
protected:
uint256 mTransactionID;

View File

@@ -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();

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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
View 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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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