diff --git a/.gitignore b/.gitignore index 2530f7eb82..92eb7bb785 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ *.o obj/* bin/newcoind - newcoind +# Ignore locally installed node_modules +node_modules + +# Ignore tmp directory. +tmp diff --git a/Debug/newcoin.exe.embed.manifest.res b/Debug/newcoin.exe.embed.manifest.res deleted file mode 100644 index 9c8df0e3c8..0000000000 Binary files a/Debug/newcoin.exe.embed.manifest.res and /dev/null differ diff --git a/Debug/newcoin_manifest.rc b/Debug/newcoin_manifest.rc deleted file mode 100644 index 20da22ef1c..0000000000 Binary files a/Debug/newcoin_manifest.rc and /dev/null differ diff --git a/src/AccountState.cpp b/src/AccountState.cpp index 774e4247db..85ad825fed 100644 --- a/src/AccountState.cpp +++ b/src/AccountState.cpp @@ -9,6 +9,7 @@ #include "Ledger.h" #include "Serializer.h" +#include "Log.h" AccountState::AccountState(const NewcoinAddress& naAccountID) : mAccountID(naAccountID), mValid(false) { @@ -59,11 +60,8 @@ void AccountState::addJson(Json::Value& val) void AccountState::dump() { Json::Value j(Json::objectValue); - addJson(j); - - Json::StyledStreamWriter ssw; - ssw.write(std::cerr, j); + Log(lsINFO) << j; } // vim:ts=4 diff --git a/src/Application.h b/src/Application.h index 79ffb8cf34..6511d0f566 100644 --- a/src/Application.h +++ b/src/Application.h @@ -78,6 +78,7 @@ public: NetworkOPs& getOPs() { return mNetOps; } boost::asio::io_service& getIOService() { return mIOService; } + boost::asio::io_service& getAuxService() { return mAuxService; } LedgerMaster& getMasterLedger() { return mMasterLedger; } LedgerAcquireMaster& getMasterLedgerAcquire() { return mMasterLedgerAcquire; } diff --git a/src/DBInit.cpp b/src/DBInit.cpp index e968ddf3c0..c0f7086b78 100644 --- a/src/DBInit.cpp +++ b/src/DBInit.cpp @@ -14,6 +14,7 @@ const char *TxnDBInit[] = { LedgerSeq BIGINT UNSIGNED, \ Status CHARACTER(1), \ RawTxn BLOB \ + TxnMeta BLOB \ );", "CREATE TABLE PubKeys ( \ ID CHARACTER(35) PRIMARY KEY, \ diff --git a/src/Ledger.cpp b/src/Ledger.cpp index 9b36e003b8..2348ef63ec 100644 --- a/src/Ledger.cpp +++ b/src/Ledger.cpp @@ -154,7 +154,7 @@ void Ledger::setAccepted(uint32 closeTime, int closeResolution, bool correctClos void Ledger::setAccepted() { // used when we acquired the ledger - assert(mClosed && (mCloseTime != 0) && (mCloseResolution != 0)); + // FIXME assert(mClosed && (mCloseTime != 0) && (mCloseResolution != 0)); mCloseTime -= mCloseTime % mCloseResolution; updateHash(); mAccepted = true; @@ -211,35 +211,51 @@ RippleState::pointer Ledger::accessRippleState(const uint256& uNode) return boost::make_shared(sle); } -bool Ledger::addTransaction(const Transaction::pointer& trans) +bool Ledger::addTransaction(const uint256& txID, const Serializer& txn) { // low-level - just add to table - assert(!mAccepted); - assert(trans->getID().isNonZero()); - Serializer s; - trans->getSTransaction()->add(s); - SHAMapItem::pointer item = boost::make_shared(trans->getID(), s.peekData()); - if (!mTransactionMap->addGiveItem(item, true, false)) // FIXME: TX metadata + SHAMapItem::pointer item = boost::make_shared(txID, txn.peekData()); + if (!mTransactionMap->addGiveItem(item, true, false)) return false; return true; } -bool Ledger::addTransaction(const uint256& txID, const Serializer& txn) +bool Ledger::addTransaction(const uint256& txID, const Serializer& txn, const Serializer& md) { // low-level - just add to table - SHAMapItem::pointer item = boost::make_shared(txID, txn.peekData()); - if (!mTransactionMap->addGiveItem(item, true, false)) // FIXME: TX metadata + Serializer s(txn.getDataLength() + md.getDataLength() + 64); + s.addVL(txn.peekData()); + s.addVL(md.peekData()); + SHAMapItem::pointer item = boost::make_shared(txID, s.peekData()); + if (!mTransactionMap->addGiveItem(item, true, true)) return false; return true; } Transaction::pointer Ledger::getTransaction(const uint256& transID) const { - SHAMapItem::pointer item = mTransactionMap->peekItem(transID); + SHAMapTreeNode::TNType type; + SHAMapItem::pointer item = mTransactionMap->peekItem(transID, type); if (!item) return Transaction::pointer(); Transaction::pointer txn = theApp->getMasterTransaction().fetch(transID, false); - if (txn) return txn; + if (txn) + return txn; + + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + txn = Transaction::sharedTransaction(item->getData(), true); + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { + std::vector txnData; + int txnLength; + if (!item->peekSerializer().getVL(txnData, 0, txnLength)) + return Transaction::pointer(); + txn = Transaction::sharedTransaction(txnData, false); + } + else + { + assert(false); + return Transaction::pointer(); + } - txn = Transaction::sharedTransaction(item->getData(), true); if (txn->getStatus() == NEW) txn->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq); @@ -247,6 +263,39 @@ Transaction::pointer Ledger::getTransaction(const uint256& transID) const return txn; } +bool Ledger::getTransaction(const uint256& txID, Transaction::pointer& txn, TransactionMetaSet::pointer& meta) +{ + SHAMapTreeNode::TNType type; + SHAMapItem::pointer item = mTransactionMap->peekItem(txID, type); + if (!item) + return false; + + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + { // in tree with no metadata + txn = theApp->getMasterTransaction().fetch(txID, false); + meta = TransactionMetaSet::pointer(); + if (!txn) + txn = Transaction::sharedTransaction(item->getData(), true); + } + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { // in tree with metadata + SerializerIterator it(item->getData()); + txn = theApp->getMasterTransaction().fetch(txID, false); + if (!txn) + txn = Transaction::sharedTransaction(it.getVL(), true); + else + it.getVL(); // skip transaction + meta = boost::make_shared(mLedgerSeq, it.getVL()); + } + else + return false; + + if (txn->getStatus() == NEW) + txn->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq); + theApp->getMasterTransaction().canonicalize(txn, false); + return true; +} + bool Ledger::unitTest() { return true; @@ -440,14 +489,38 @@ void Ledger::addJson(Json::Value& ret, int options) if (mTransactionMap && (full || ((options & LEDGER_JSON_DUMP_TXNS) != 0))) { Json::Value txns(Json::arrayValue); - for (SHAMapItem::pointer item = mTransactionMap->peekFirstItem(); !!item; - item = mTransactionMap->peekNextItem(item->getTag())) + SHAMapTreeNode::TNType type; + for (SHAMapItem::pointer item = mTransactionMap->peekFirstItem(type); !!item; + item = mTransactionMap->peekNextItem(item->getTag(), type)) { if (full) { - SerializerIterator sit(item->peekSerializer()); - SerializedTransaction txn(sit); - txns.append(txn.getJson(0)); + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + { + SerializerIterator sit(item->peekSerializer()); + SerializedTransaction txn(sit); + txns.append(txn.getJson(0)); + } + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { + SerializerIterator sit(item->peekSerializer()); + Serializer sTxn(sit.getVL()); + Serializer sMeta(sit.getVL()); + + SerializerIterator tsit(sTxn); + SerializedTransaction txn(tsit); + + TransactionMetaSet meta(mLedgerSeq, sit.getVL()); + Json::Value txJson = txn.getJson(0); + txJson["metaData"] = meta.getJson(0); + txns.append(txJson); + } + else + { + Json::Value error = Json::objectValue; + error[item->getTag().GetHex()] = type; + txns.append(error); + } } else txns.append(item->getTag().GetHex()); } diff --git a/src/Ledger.h b/src/Ledger.h index c671a57bf2..d059f01581 100644 --- a/src/Ledger.h +++ b/src/Ledger.h @@ -11,6 +11,7 @@ #include "../json/value.h" #include "Transaction.h" +#include "TransactionMeta.h" #include "AccountState.h" #include "RippleState.h" #include "NicknameState.h" @@ -56,7 +57,7 @@ public: TR_PASTASEQ = 6, // account is past this transaction TR_PREASEQ = 7, // account is missing transactions before this TR_BADLSEQ = 8, // ledger too early - TR_TOOSMALL = 9, // amount is less than Tx fee + TR_TOOSMALL = 9, // amount is less than Tx fee }; // ledger close flags @@ -82,8 +83,6 @@ private: protected: - bool addTransaction(const Transaction::pointer&); - bool addTransaction(const uint256& id, const Serializer& txn); static Ledger::pointer getSQL(const std::string& sqlStatement); @@ -152,8 +151,11 @@ public: bool isAcquiringAS(void); // Transaction Functions + bool addTransaction(const uint256& id, const Serializer& txn); + bool addTransaction(const uint256& id, const Serializer& txn, const Serializer& metaData); 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); // high-level functions AccountState::pointer getAccountState(const NewcoinAddress& acctID); diff --git a/src/LedgerAcquire.cpp b/src/LedgerAcquire.cpp index 353b7d8872..bbfd67063b 100644 --- a/src/LedgerAcquire.cpp +++ b/src/LedgerAcquire.cpp @@ -84,9 +84,8 @@ void PeerSet::TimerEntry(boost::weak_ptr wptr, const boost::system::err if (result == boost::asio::error::operation_aborted) return; boost::shared_ptr ptr = wptr.lock(); - if (!ptr) - return; - ptr->invokeOnTimer(); + if (ptr) + ptr->invokeOnTimer(); } LedgerAcquire::LedgerAcquire(const uint256& hash) : PeerSet(hash, LEDGER_ACQUIRE_TIMEOUT), @@ -109,7 +108,7 @@ void LedgerAcquire::onTimer() boost::weak_ptr LedgerAcquire::pmDowncast() { - return boost::shared_polymorphic_downcast(shared_from_this()); + return boost::shared_polymorphic_downcast(shared_from_this()); } void LedgerAcquire::done() diff --git a/src/LedgerAcquire.h b/src/LedgerAcquire.h index 6781eddf88..b589618299 100644 --- a/src/LedgerAcquire.h +++ b/src/LedgerAcquire.h @@ -78,6 +78,7 @@ protected: public: LedgerAcquire(const uint256& hash); + virtual ~LedgerAcquire() { ; } bool isBase() const { return mHaveBase; } bool isAcctStComplete() const { return mHaveState; } diff --git a/src/LedgerConsensus.cpp b/src/LedgerConsensus.cpp index ce064fe47c..aff9f7bdaf 100644 --- a/src/LedgerConsensus.cpp +++ b/src/LedgerConsensus.cpp @@ -25,24 +25,26 @@ typedef std::pair u256_lct_pair; TransactionAcquire::TransactionAcquire(const uint256& hash) : PeerSet(hash, TX_ACQUIRE_TIMEOUT), mHaveRoot(false) { - mMap = boost::make_shared(); - mMap->setSynching(); + mMap = boost::make_shared(hash); } void TransactionAcquire::done() { if (mFailed) { - Log(lsWARNING) << "Failed to acqiure TXs " << mHash; + Log(lsWARNING) << "Failed to acquire TXs " << mHash; theApp->getOPs().mapComplete(mHash, SHAMap::pointer()); } else + { + mMap->setImmutable(); theApp->getOPs().mapComplete(mHash, mMap); + } } boost::weak_ptr TransactionAcquire::pmDowncast() { - return boost::shared_polymorphic_downcast(shared_from_this()); + return boost::shared_polymorphic_downcast(shared_from_this()); } void TransactionAcquire::trigger(Peer::ref peer, bool timer) @@ -109,7 +111,8 @@ bool TransactionAcquire::takeNodes(const std::list& nodeIDs, } if (!mMap->addRootNode(getHash(), *nodeDatait, snfWIRE)) return false; - else mHaveRoot = true; + else + mHaveRoot = true; } else if (!mMap->addKnownNode(*nodeIDit, *nodeDatait, &sf)) return false; @@ -174,37 +177,41 @@ void LCTransaction::unVote(const uint160& peer) } } -bool LCTransaction::updatePosition(int percentTime, bool proposing) +bool LCTransaction::updateVote(int percentTime, bool proposing) { - if (mOurPosition && (mNays == 0)) + if (mOurVote && (mNays == 0)) return false; - if (!mOurPosition && (mYays == 0)) + if (!mOurVote && (mYays == 0)) return false; bool newPosition; + int weight; if (proposing) // give ourselves full weight { // This is basically the percentage of nodes voting 'yes' (including us) - int weight = (mYays * 100 + (mOurPosition ? 100 : 0)) / (mNays + mYays + 1); + weight = (mYays * 100 + (mOurVote ? 100 : 0)) / (mNays + mYays + 1); // To prevent avalanche stalls, we increase the needed weight slightly over time if (percentTime < AV_MID_CONSENSUS_TIME) newPosition = weight > AV_INIT_CONSENSUS_PCT; else if (percentTime < AV_LATE_CONSENSUS_TIME) newPosition = weight > AV_MID_CONSENSUS_PCT; else newPosition = weight > AV_LATE_CONSENSUS_PCT; } - else // don't let us outweight a proposing node, just recognize consensus + else // don't let us outweigh a proposing node, just recognize consensus + { + weight = -1; newPosition = mYays > mNays; + } - if (newPosition == mOurPosition) + if (newPosition == mOurVote) { #ifdef LC_DEBUG - Log(lsTRACE) << "No change (" << (mOurPosition ? "YES" : "NO") << ") : weight " + Log(lsTRACE) << "No change (" << (mOurVote ? "YES" : "NO") << ") : weight " << weight << ", percent " << percentTime; #endif return false; } - mOurPosition = newPosition; - Log(lsTRACE) << "We now vote " << (mOurPosition ? "YES" : "NO") << " on " << mTransactionID; + mOurVote = newPosition; + Log(lsTRACE) << "We now vote " << (mOurVote ? "YES" : "NO") << " on " << mTransactionID; return true; } @@ -333,6 +340,7 @@ void LedgerConsensus::handleLCL(const uint256& lclHash) mPeerPositions.clear(); mDisputes.clear(); mDeadNodes.clear(); + playbackProposals(); return; } @@ -357,9 +365,9 @@ void LedgerConsensus::takeInitialPosition(Ledger& initialLedger) { uint256 set = it.second->getCurrentHash(); if (found.insert(set).second) - { - boost::unordered_map::iterator iit = mComplete.find(set); - if (iit != mComplete.end()) + { // OPTIMIZEME: Don't process the same set more than once + boost::unordered_map::iterator iit = mAcquired.find(set); + if (iit != mAcquired.end()) createDisputes(initialSet, iit->second); } } @@ -374,7 +382,7 @@ void LedgerConsensus::takeInitialPosition(Ledger& initialLedger) propose(); } -void LedgerConsensus::createDisputes(const SHAMap::pointer& m1, const SHAMap::pointer& m2) +void LedgerConsensus::createDisputes(SHAMap::ref m1, SHAMap::ref m2) { SHAMap::SHAMapDiff differences; m1->compare(m2, differences, 16384); @@ -394,33 +402,36 @@ void LedgerConsensus::createDisputes(const SHAMap::pointer& m1, const SHAMap::po } } -void LedgerConsensus::mapComplete(const uint256& hash, const SHAMap::pointer& map, bool acquired) +void LedgerConsensus::mapComplete(const uint256& hash, SHAMap::ref map, bool acquired) { if (acquired) Log(lsINFO) << "We have acquired TXS " << hash; - mAcquiring.erase(hash); if (!map) { // this is an invalid/corrupt map - mComplete[hash] = map; + mAcquired[hash] = map; + mAcquiring.erase(hash); Log(lsWARNING) << "A trusted node directed us to acquire an invalid TXN map"; return; } + assert(hash == map->getHash()); - if (mComplete.find(hash) != mComplete.end()) + if (mAcquired.find(hash) != mAcquired.end()) return; // we already have this map - if (mOurPosition && (map->getHash() != mOurPosition->getCurrentHash())) + if (mOurPosition && (!mOurPosition->isBowOut()) && (hash != mOurPosition->getCurrentHash())) { // this could create disputed transactions - boost::unordered_map::iterator it2 = mComplete.find(mOurPosition->getCurrentHash()); - if (it2 != mComplete.end()) + boost::unordered_map::iterator it2 = mAcquired.find(mOurPosition->getCurrentHash()); + if (it2 != mAcquired.end()) { assert((it2->first == mOurPosition->getCurrentHash()) && it2->second); createDisputes(it2->second, map); } - else assert(false); // We don't have our own position?! + else + assert(false); // We don't have our own position?! } - mComplete[map->getHash()] = map; + mAcquired[hash] = map; + mAcquiring.erase(hash); // Adjust tracking for each peer that takes this position std::vector peers; @@ -446,7 +457,7 @@ void LedgerConsensus::sendHaveTxSet(const uint256& hash, bool direct) theApp->getConnectionPool().relayMessage(NULL, packet); } -void LedgerConsensus::adjustCount(const SHAMap::pointer& map, const std::vector& peers) +void LedgerConsensus::adjustCount(SHAMap::ref map, const std::vector& peers) { // Adjust the counts on all disputed transactions based on the set of peers taking this position BOOST_FOREACH(u256_lct_pair& it, mDisputes) { @@ -594,14 +605,15 @@ void LedgerConsensus::updateOurPositions() BOOST_FOREACH(u256_lct_pair& it, mDisputes) { - if (it.second->updatePosition(mClosePercent, mProposing)) + if (it.second->updateVote(mClosePercent, mProposing)) { if (!changes) { - ourPosition = mComplete[mOurPosition->getCurrentHash()]->snapShot(true); + ourPosition = mAcquired[mOurPosition->getCurrentHash()]->snapShot(true); + assert(ourPosition); changes = true; } - if (it.second->getOurPosition()) // now a yes + if (it.second->getOurVote()) // now a yes { ourPosition->addItem(SHAMapItem(it.first, it.second->peekTransaction()), true, false); // addedTx.push_back(it.first); @@ -662,41 +674,53 @@ void LedgerConsensus::updateOurPositions() ((closeTime != (roundCloseTime(mOurPosition->getCloseTime()))) || (mOurPosition->isStale(ourCutoff)))) { // close time changed or our position is stale - ourPosition = mComplete[mOurPosition->getCurrentHash()]->snapShot(true); + ourPosition = mAcquired[mOurPosition->getCurrentHash()]->snapShot(true); + assert(ourPosition); changes = true; } if (changes) { uint256 newHash = ourPosition->getHash(); - mOurPosition->changePosition(newHash, closeTime); - if (mProposing) - propose(); - mapComplete(newHash, ourPosition, false); Log(lsINFO) << "Position change: CTime " << closeTime << ", tx " << newHash; + if (mOurPosition->changePosition(newHash, closeTime)) + { + if (mProposing) + propose(); + mapComplete(newHash, ourPosition, false); + } } } bool LedgerConsensus::haveConsensus() -{ +{ // FIXME: Should check for a supermajority on each disputed transaction + // counting unacquired TX sets as disagreeing int agree = 0, disagree = 0; uint256 ourPosition = mOurPosition->getCurrentHash(); BOOST_FOREACH(u160_prop_pair& it, mPeerPositions) { - if (it.second->getCurrentHash() == ourPosition) - ++agree; - else - ++disagree; + if (!it.second->isBowOut()) + { + if (it.second->getCurrentHash() == ourPosition) + ++agree; + else + ++disagree; + } } - int currentValidations = theApp->getValidations().getCurrentValidationCount(mPreviousLedger->getCloseTimeNC()); + int currentValidations = theApp->getValidations().getNodesAfter(mPrevLedgerHash); + +#ifdef LC_DEBUG + Log(lsINFO) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree; +#endif + return ContinuousLedgerTiming::haveConsensus(mPreviousProposers, agree + disagree, agree, currentValidations, mPreviousMSeconds, mCurrentMSeconds); } SHAMap::pointer LedgerConsensus::getTransactionTree(const uint256& hash, bool doAcquire) { - boost::unordered_map::iterator it = mComplete.find(hash); - if (it == mComplete.end()) + boost::unordered_map::iterator it = mAcquired.find(hash); + if (it == mAcquired.end()) { // we have not completed acquiring this ledger if (mState == lcsPRE_CLOSE) @@ -780,10 +804,11 @@ void LedgerConsensus::addDisputedTransaction(const uint256& txID, const std::vec bool ourPosition = false; if (mOurPosition) { - boost::unordered_map::iterator mit = mComplete.find(mOurPosition->getCurrentHash()); - if (mit != mComplete.end()) + boost::unordered_map::iterator mit = mAcquired.find(mOurPosition->getCurrentHash()); + if (mit != mAcquired.end()) ourPosition = mit->second->hasItem(txID); - else assert(false); // We don't have our own position? + else + assert(false); // We don't have our own position? } LCTransaction::pointer txn = boost::make_shared(txID, tx, ourPosition); @@ -792,8 +817,8 @@ void LedgerConsensus::addDisputedTransaction(const uint256& txID, const std::vec BOOST_FOREACH(u160_prop_pair& pit, mPeerPositions) { boost::unordered_map::const_iterator cit = - mComplete.find(pit.second->getCurrentHash()); - if (cit != mComplete.end() && cit->second) + mAcquired.find(pit.second->getCurrentHash()); + if (cit != mAcquired.end() && cit->second) txn->setVote(pit.first, cit->second->hasItem(txID)); } } @@ -867,13 +892,15 @@ bool LedgerConsensus::peerGaveNodes(Peer::ref peer, const uint256& setHash, const std::list& nodeIDs, const std::list< std::vector >& nodeData) { boost::unordered_map::iterator acq = mAcquiring.find(setHash); - if (acq == mAcquiring.end()) return false; - return acq->second->takeNodes(nodeIDs, nodeData, peer); + if (acq == mAcquiring.end()) + return false; + TransactionAcquire::pointer set = acq->second; // We must keep the set around during the function + return set->takeNodes(nodeIDs, nodeData, peer); } void LedgerConsensus::beginAccept() { - SHAMap::pointer consensusSet = mComplete[mOurPosition->getCurrentHash()]; + SHAMap::pointer consensusSet = mAcquired[mOurPosition->getCurrentHash()]; if (!consensusSet) { Log(lsFATAL) << "We don't have a consensus set"; @@ -882,8 +909,7 @@ void LedgerConsensus::beginAccept() } theApp->getOPs().newLCL(mPeerPositions.size(), mCurrentMSeconds, mNewLedgerHash); - boost::thread thread(boost::bind(&LedgerConsensus::Saccept, shared_from_this(), consensusSet)); - thread.detach(); + theApp->getIOService().post(boost::bind(&LedgerConsensus::Saccept, shared_from_this(), consensusSet)); } void LedgerConsensus::Saccept(boost::shared_ptr This, SHAMap::pointer txSet) @@ -954,7 +980,7 @@ void LedgerConsensus::applyTransaction(TransactionEngine& engine, const Serializ #endif } -void LedgerConsensus::applyTransactions(const SHAMap::pointer& set, Ledger::ref applyLedger, +void LedgerConsensus::applyTransactions(SHAMap::ref set, Ledger::ref applyLedger, Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr) { TransactionEngineParams parms = openLgr ? tapOPEN_LEDGER : tapNONE; @@ -1016,7 +1042,7 @@ uint32 LedgerConsensus::roundCloseTime(uint32 closeTime) return closeTime - (closeTime % mCloseResolution); } -void LedgerConsensus::accept(const SHAMap::pointer& set) +void LedgerConsensus::accept(SHAMap::ref set) { assert(set->getHash() == mOurPosition->getCurrentHash()); @@ -1070,7 +1096,7 @@ void LedgerConsensus::accept(const SHAMap::pointer& set) TransactionEngine engine(newOL); BOOST_FOREACH(u256_lct_pair& it, mDisputes) { - if (!it.second->getOurPosition()) + if (!it.second->getOurVote()) { // we voted NO try { diff --git a/src/LedgerConsensus.h b/src/LedgerConsensus.h index 17aa02691d..3833c300b3 100644 --- a/src/LedgerConsensus.h +++ b/src/LedgerConsensus.h @@ -37,11 +37,11 @@ protected: public: TransactionAcquire(const uint256& hash); + virtual ~TransactionAcquire() { ; } SHAMap::pointer getMap() { return mMap; } - bool takeNodes(const std::list& IDs, const std::list< std::vector >& data, - Peer::ref); + bool takeNodes(const std::list& IDs, const std::list< std::vector >& data, Peer::ref); }; class LCTransaction @@ -49,24 +49,24 @@ class LCTransaction protected: uint256 mTransactionID; int mYays, mNays; - bool mOurPosition; + bool mOurVote; Serializer transaction; boost::unordered_map mVotes; public: typedef boost::shared_ptr pointer; - LCTransaction(const uint256 &txID, const std::vector& tx, bool ourPosition) : - mTransactionID(txID), mYays(0), mNays(0), mOurPosition(ourPosition), transaction(tx) { ; } + LCTransaction(const uint256 &txID, const std::vector& tx, bool ourVote) : + mTransactionID(txID), mYays(0), mNays(0), mOurVote(ourVote), transaction(tx) { ; } const uint256& getTransactionID() const { return mTransactionID; } - bool getOurPosition() const { return mOurPosition; } + bool getOurVote() const { return mOurVote; } Serializer& peekTransaction() { return transaction; } void setVote(const uint160& peer, bool votesYes); void unVote(const uint160& peer); - bool updatePosition(int percentTime, bool proposing); + bool updateVote(int percentTime, bool proposing); }; enum LCState @@ -100,7 +100,7 @@ protected: boost::unordered_map mPeerPositions; // Transaction Sets, indexed by hash of transaction tree - boost::unordered_map mComplete; + boost::unordered_map mAcquired; boost::unordered_map mAcquiring; // Peer sets @@ -120,21 +120,21 @@ protected: // final accept logic static void Saccept(boost::shared_ptr This, SHAMap::pointer txSet); - void accept(const SHAMap::pointer& txSet); + void accept(SHAMap::ref txSet); void weHave(const uint256& id, Peer::ref avoidPeer); void startAcquiring(const TransactionAcquire::pointer&); SHAMap::pointer find(const uint256& hash); - void createDisputes(const SHAMap::pointer&, const SHAMap::pointer&); + void createDisputes(SHAMap::ref, SHAMap::ref); void addDisputedTransaction(const uint256&, const std::vector& transaction); - void adjustCount(const SHAMap::pointer& map, const std::vector& peers); + void adjustCount(SHAMap::ref map, const std::vector& peers); void propose(); void addPosition(LedgerProposal&, bool ours); void removePosition(LedgerProposal&, bool ours); void sendHaveTxSet(const uint256& set, bool direct); - void applyTransactions(const SHAMap::pointer& transactionSet, Ledger::ref targetLedger, + void applyTransactions(SHAMap::ref transactionSet, Ledger::ref targetLedger, Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr); void applyTransaction(TransactionEngine& engine, const SerializedTransaction::pointer& txn, Ledger::ref targetLedger, CanonicalTXSet& failedTransactions, bool openLgr); @@ -162,7 +162,7 @@ public: SHAMap::pointer getTransactionTree(const uint256& hash, bool doAcquire); TransactionAcquire::pointer getAcquiring(const uint256& hash); - void mapComplete(const uint256& hash, const SHAMap::pointer& map, bool acquired); + void mapComplete(const uint256& hash, SHAMap::ref map, bool acquired); void checkLCL(); void handleLCL(const uint256& lclHash); diff --git a/src/LedgerEntrySet.cpp b/src/LedgerEntrySet.cpp index 5b5b703e66..4dbef407b1 100644 --- a/src/LedgerEntrySet.cpp +++ b/src/LedgerEntrySet.cpp @@ -1,7 +1,13 @@ + #include "LedgerEntrySet.h" #include +#include "Log.h" + +// Small for testing, should likely be 32 or 64. +#define DIR_NODE_MAX 2 + void LedgerEntrySet::init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID) { mEntries.clear(); @@ -251,6 +257,8 @@ Json::Value LedgerEntrySet::getJson(int) const } ret["nodes" ] = nodes; + ret["metaData"] = mSet.getJson(0); + return ret; } @@ -286,9 +294,13 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger, bool LedgerEntrySet::threadTx(TransactionMetaNode& metaNode, const NewcoinAddress& threadTo, Ledger::ref ledger, boost::unordered_map& newMods) { + Log(lsTRACE) << "Thread to " << threadTo.getAccountID(); SLE::pointer sle = getForMod(Ledger::getAccountRootIndex(threadTo.getAccountID()), ledger, newMods); if (!sle) + { + assert(false); return false; + } return threadTx(metaNode, sle, ledger, newMods); } @@ -319,7 +331,7 @@ bool LedgerEntrySet::threadOwners(TransactionMetaNode& metaNode, SLE::ref node, return false; } -void LedgerEntrySet::calcRawMeta(Serializer& s, Ledger::ref origLedger) +void LedgerEntrySet::calcRawMeta(Serializer& s) { // calculate the raw meta data and return it. This must be called before the set is committed // Entries modified only as a result of building the transaction metadata @@ -344,21 +356,25 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, Ledger::ref origLedger) nType = TMNCreatedNode; break; - default: - // ignore these + default: // ignore these break; } if (nType == TMNEndOfMetadata) continue; - SLE::pointer origNode = origLedger->getSLE(it->first); + SLE::pointer origNode = mLedger->getSLE(it->first); + + if (origNode && (origNode->getType() == ltDIR_NODE)) // No metadata for dir nodes + continue; + SLE::pointer curNode = it->second.mEntry; TransactionMetaNode &metaNode = mSet.getAffectedNode(it->first, nType); if (nType == TMNDeletedNode) { - threadOwners(metaNode, origNode, origLedger, newMod); + assert(origNode); + threadOwners(metaNode, origNode, mLedger, newMod); if (origNode->getIFieldPresent(sfAmount)) { // node has an amount, covers ripple state nodes @@ -390,16 +406,20 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, Ledger::ref origLedger) } if (nType == TMNCreatedNode) // if created, thread to owner(s) - threadOwners(metaNode, curNode, origLedger, newMod); + { + assert(!origNode); + threadOwners(metaNode, curNode, mLedger, newMod); + } if ((nType == TMNCreatedNode) || (nType == TMNModifiedNode)) { if (curNode->isThreadedType()) // always thread to self - threadTx(metaNode, curNode, origLedger, newMod); + threadTx(metaNode, curNode, mLedger, newMod); } if (nType == TMNModifiedNode) { + assert(origNode); if (origNode->getIFieldPresent(sfAmount)) { // node has an amount, covers account root nodes and ripple nodes STAmount amount = origNode->getIValueFieldAmount(sfAmount); @@ -423,9 +443,697 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, Ledger::ref origLedger) // add any new modified nodes to the modification set for (boost::unordered_map::iterator it = newMod.begin(), end = newMod.end(); it != end; ++it) - entryCache(it->second); + entryModify(it->second); + +#ifdef DEBUG + Log(lsINFO) << "Metadata:" << mSet.getJson(0); +#endif mSet.addRaw(s); } +// <-- uNodeDir: For deletion, present to make dirDelete efficient. +// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. +// --> uLedgerIndex: Value to add to directory. +// We only append. This allow for things that watch append only structure to just monitor from the last node on ward. +// Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. +TER LedgerEntrySet::dirAdd( + uint64& uNodeDir, + const uint256& uRootIndex, + const uint256& uLedgerIndex) +{ + SLE::pointer sleNode; + STVector256 svIndexes; + SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); + + if (!sleRoot) + { + // No root, make it. + sleRoot = entryCreate(ltDIR_NODE, uRootIndex); + + sleNode = sleRoot; + uNodeDir = 0; + } + else + { + uNodeDir = sleRoot->getIFieldU64(sfIndexPrevious); // Get index to last directory node. + + if (uNodeDir) + { + // Try adding to last node. + sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); + + assert(sleNode); + } + else + { + // Try adding to root. Didn't have a previous set to the last node. + sleNode = sleRoot; + } + + svIndexes = sleNode->getIFieldV256(sfIndexes); + + if (DIR_NODE_MAX != svIndexes.peekValue().size()) + { + // Add to current node. + entryModify(sleNode); + } + // Add to new node. + else if (!++uNodeDir) + { + return terDIR_FULL; + } + else + { + // Have old last point to new node, if it was not root. + if (uNodeDir == 1) + { + // Previous node is root node. + + sleRoot->setIFieldU64(sfIndexNext, uNodeDir); + } + else + { + // Previous node is not root node. + + SLE::pointer slePrevious = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir-1)); + + slePrevious->setIFieldU64(sfIndexNext, uNodeDir); + entryModify(slePrevious); + + sleNode->setIFieldU64(sfIndexPrevious, uNodeDir-1); + } + + // Have root point to new node. + sleRoot->setIFieldU64(sfIndexPrevious, uNodeDir); + entryModify(sleRoot); + + // Create the new node. + sleNode = entryCreate(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); + svIndexes = STVector256(); + } + } + + svIndexes.peekValue().push_back(uLedgerIndex); // Append entry. + sleNode->setIFieldV256(sfIndexes, svIndexes); // Save entry. + + Log(lsINFO) << "dirAdd: creating: root: " << uRootIndex.ToString(); + Log(lsINFO) << "dirAdd: appending: Entry: " << uLedgerIndex.ToString(); + Log(lsINFO) << "dirAdd: appending: Node: " << strHex(uNodeDir); + // Log(lsINFO) << "dirAdd: appending: PREV: " << svIndexes.peekValue()[0].ToString(); + + return tesSUCCESS; +} + +// Ledger must be in a state for this to work. +TER LedgerEntrySet::dirDelete( + const bool bKeepRoot, // --> True, if we never completely clean up, after we overflow the root node. + const uint64& uNodeDir, // --> Node containing entry. + const uint256& uRootIndex, // --> The index of the base of the directory. Nodes are based off of this. + const uint256& uLedgerIndex, // --> Value to add to directory. + const bool bStable) // --> True, not to change relative order of entries. +{ + uint64 uNodeCur = uNodeDir; + SLE::pointer sleNode = entryCache(ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex(uRootIndex, uNodeCur) : uRootIndex); + + assert(sleNode); + + if (!sleNode) + { + Log(lsWARNING) << "dirDelete: no such node"; + + return tefBAD_LEDGER; + } + + STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); + std::vector& vuiIndexes = svIndexes.peekValue(); + std::vector::iterator it; + + it = std::find(vuiIndexes.begin(), vuiIndexes.end(), uLedgerIndex); + + assert(vuiIndexes.end() != it); + if (vuiIndexes.end() == it) + { + assert(false); + + Log(lsWARNING) << "dirDelete: no such entry"; + + return tefBAD_LEDGER; + } + + // Remove the element. + if (vuiIndexes.size() > 1) + { + if (bStable) + { + vuiIndexes.erase(it); + } + else + { + *it = vuiIndexes[vuiIndexes.size()-1]; + vuiIndexes.resize(vuiIndexes.size()-1); + } + } + else + { + vuiIndexes.clear(); + } + + sleNode->setIFieldV256(sfIndexes, svIndexes); + entryModify(sleNode); + + if (vuiIndexes.empty()) + { + // May be able to delete nodes. + uint64 uNodePrevious = sleNode->getIFieldU64(sfIndexPrevious); + uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); + + if (!uNodeCur) + { + // Just emptied root node. + + if (!uNodePrevious) + { + // Never overflowed the root node. Delete it. + entryDelete(sleNode); + } + // Root overflowed. + else if (bKeepRoot) + { + // If root overflowed and not allowed to delete overflowed root node. + + nothing(); + } + else if (uNodePrevious != uNodeNext) + { + // Have more than 2 nodes. Can't delete root node. + + nothing(); + } + else + { + // Have only a root node and a last node. + SLE::pointer sleLast = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); + + assert(sleLast); + + if (sleLast->getIFieldV256(sfIndexes).peekValue().empty()) + { + // Both nodes are empty. + + entryDelete(sleNode); // Delete root. + entryDelete(sleLast); // Delete last. + } + else + { + // Have an entry, can't delete root node. + + nothing(); + } + } + } + // Just emptied a non-root node. + else if (uNodeNext) + { + // Not root and not last node. Can delete node. + + SLE::pointer slePrevious = entryCache(ltDIR_NODE, uNodePrevious ? Ledger::getDirNodeIndex(uRootIndex, uNodePrevious) : uRootIndex); + + assert(slePrevious); + + SLE::pointer sleNext = entryCache(ltDIR_NODE, uNodeNext ? Ledger::getDirNodeIndex(uRootIndex, uNodeNext) : uRootIndex); + + assert(slePrevious); + assert(sleNext); + + if (!slePrevious) + { + Log(lsWARNING) << "dirDelete: previous node is missing"; + + return tefBAD_LEDGER; + } + + if (!sleNext) + { + Log(lsWARNING) << "dirDelete: next node is missing"; + + return tefBAD_LEDGER; + } + + // Fix previous to point to its new next. + slePrevious->setIFieldU64(sfIndexNext, uNodeNext); + entryModify(slePrevious); + + // Fix next to point to its new previous. + sleNext->setIFieldU64(sfIndexPrevious, uNodePrevious); + entryModify(sleNext); + } + // Last node. + else if (bKeepRoot || uNodePrevious) + { + // Not allowed to delete last node as root was overflowed. + // Or, have pervious entries preventing complete delete. + + nothing(); + } + else + { + // Last and only node besides the root. + SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); + + assert(sleRoot); + + if (sleRoot->getIFieldV256(sfIndexes).peekValue().empty()) + { + // Both nodes are empty. + + entryDelete(sleRoot); // Delete root. + entryDelete(sleNode); // Delete last. + } + else + { + // Root has an entry, can't delete. + + nothing(); + } + } + } + + return tesSUCCESS; +} + +// Return the first entry and advance uDirEntry. +// <-- true, if had a next entry. +bool LedgerEntrySet::dirFirst( + const uint256& uRootIndex, // --> Root of directory. + SLE::pointer& sleNode, // <-- current node + unsigned int& uDirEntry, // <-- next entry + uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. +{ + sleNode = entryCache(ltDIR_NODE, uRootIndex); + uDirEntry = 0; + + assert(sleNode); // We never probe for directories. + + return LedgerEntrySet::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); +} + +// Return the current entry and advance uDirEntry. +// <-- true, if had a next entry. +bool LedgerEntrySet::dirNext( + const uint256& uRootIndex, // --> Root of directory + SLE::pointer& sleNode, // <-> current node + unsigned int& uDirEntry, // <-> next entry + uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. +{ + STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); + std::vector& vuiIndexes = svIndexes.peekValue(); + + if (uDirEntry == vuiIndexes.size()) + { + uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); + + if (!uNodeNext) + { + uEntryIndex.zero(); + + return false; + } + else + { + sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); + uDirEntry = 0; + + return dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); + } + } + + uEntryIndex = vuiIndexes[uDirEntry++]; +Log(lsINFO) << boost::str(boost::format("dirNext: uDirEntry=%d uEntryIndex=%s") % uDirEntry % uEntryIndex); + + return true; +} + +TER LedgerEntrySet::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) +{ + uint64 uOwnerNode = sleOffer->getIFieldU64(sfOwnerNode); + TER terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); + + if (tesSUCCESS == terResult) + { + uint256 uDirectory = sleOffer->getIFieldH256(sfBookDirectory); + uint64 uBookNode = sleOffer->getIFieldU64(sfBookNode); + + terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); + } + + entryDelete(sleOffer); + + return terResult; +} + +TER LedgerEntrySet::offerDelete(const uint256& uOfferIndex) +{ + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + const uint160 uOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + + return offerDelete(sleOffer, uOfferIndex, uOwnerID); +} + +// Returns amount owed by uToAccountID to uFromAccountID. +// <-- $owed/uCurrencyID/uToAccountID: positive: uFromAccountID holds IOUs., negative: uFromAccountID owes IOUs. +STAmount LedgerEntrySet::rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + if (uToAccountID < uFromAccountID) + saBalance.negate(); + saBalance.setIssuer(uToAccountID); + } + else + { + Log(lsINFO) << "rippleOwed: No credit line between " + << NewcoinAddress::createHumanAccountID(uFromAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(uToAccountID) + << " for " + << STAmount::createHumanCurrency(uCurrencyID) + << "." ; + + assert(false); + } + + return saBalance; +} + +// Maximum amount of IOUs uToAccountID will hold from uFromAccountID. +// <-- $amount/uCurrencyID/uToAccountID. +STAmount LedgerEntrySet::rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saLimit; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + assert(sleRippleState); + if (sleRippleState) + { + saLimit = sleRippleState->getIValueFieldAmount(uToAccountID < uFromAccountID ? sfLowLimit : sfHighLimit); + saLimit.setIssuer(uToAccountID); + } + + return saLimit; + +} + +uint32 LedgerEntrySet::rippleTransferRate(const uint160& uIssuerID) +{ + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); + + uint32 uQuality = sleAccount && sleAccount->getIFieldPresent(sfTransferRate) + ? sleAccount->getIFieldU32(sfTransferRate) + : QUALITY_ONE; + + Log(lsINFO) << boost::str(boost::format("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") + % NewcoinAddress::createHumanAccountID(uIssuerID) + % !!sleAccount + % (uQuality/1000000000.0)); + + assert(sleAccount); + + return uQuality; +} + +// XXX Might not need this, might store in nodes on calc reverse. +uint32 LedgerEntrySet::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow, const SOE_Field sfHigh) +{ + uint32 uQuality = QUALITY_ONE; + SLE::pointer sleRippleState; + + if (uToAccountID == uFromAccountID) + { + nothing(); + } + else + { + sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + SOE_Field sfField = uToAccountID < uFromAccountID ? sfLow: sfHigh; + + uQuality = sleRippleState->getIFieldPresent(sfField) + ? sleRippleState->getIFieldU32(sfField) + : QUALITY_ONE; + + if (!uQuality) + uQuality = 1; // Avoid divide by zero. + } + } + + Log(lsINFO) << boost::str(boost::format("rippleQuality: %s uToAccountID=%s uFromAccountID=%s uCurrencyID=%s bLine=%d uQuality=%f") + % (sfLow == sfLowQualityIn ? "in" : "out") + % NewcoinAddress::createHumanAccountID(uToAccountID) + % NewcoinAddress::createHumanAccountID(uFromAccountID) + % STAmount::createHumanCurrency(uCurrencyID) + % !!sleRippleState + % (uQuality/1000000000.0)); + + assert(uToAccountID == uFromAccountID || !!sleRippleState); + + return uQuality; +} + +// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative. +// <-- IOU's uAccountID has of uIssuerID +STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); + + if (sleRippleState) + { + saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + + if (uAccountID > uIssuerID) + saBalance.negate(); // Put balance in uAccountID terms. + } + + return saBalance; +} + +// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. +STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + STAmount saAmount; + + if (!uCurrencyID) + { + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); + + saAmount = sleAccount->getIValueFieldAmount(sfBalance); + + Log(lsINFO) << "accountHolds: stamps: " << saAmount.getText(); + } + else + { + saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); + + Log(lsINFO) << "accountHolds: " + << saAmount.getFullText() + << " : " + << STAmount::createHumanCurrency(uCurrencyID) + << "/" + << NewcoinAddress::createHumanAccountID(uIssuerID); + } + + return saAmount; +} + +// Returns the funds available for uAccountID for a currency/issuer. +// Use when you need a default for rippling uAccountID's currency. +// --> saDefault/currency/issuer +// <-- saFunds: Funds available. May be negative. +// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault. +STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault) +{ + STAmount saFunds; + + Log(lsINFO) << "accountFunds: uAccountID=" + << NewcoinAddress::createHumanAccountID(uAccountID); + Log(lsINFO) << "accountFunds: saDefault.isNative()=" << saDefault.isNative(); + Log(lsINFO) << "accountFunds: saDefault.getIssuer()=" + << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); + + if (!saDefault.isNative() && saDefault.getIssuer() == uAccountID) + { + saFunds = saDefault; + + Log(lsINFO) << "accountFunds: offer funds: ripple self-funded: " << saFunds.getText(); + } + else + { + saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); + + Log(lsINFO) << "accountFunds: offer funds: uAccountID =" + << NewcoinAddress::createHumanAccountID(uAccountID) + << " : " + << saFunds.getText() + << "/" + << saDefault.getHumanCurrency() + << "/" + << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); + } + + return saFunds; +} + +// Calculate transit fee. +STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) +{ + STAmount saTransitFee; + + if (uSenderID != uIssuerID && uReceiverID != uIssuerID) + { + uint32 uTransitRate = rippleTransferRate(uIssuerID); + + if (QUALITY_ONE != uTransitRate) + { + STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9); + + saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer()); + } + } + + return saTransitFee; +} + +// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. +void LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) +{ + uint160 uIssuerID = saAmount.getIssuer(); + + assert(!bCheckIssuer || uSenderID == uIssuerID || uReceiverID == uIssuerID); + + bool bFlipped = uSenderID > uReceiverID; + uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); + + if (!sleRippleState) + { + Log(lsINFO) << "rippleCredit: Creating ripple line: " << uIndex.ToString(); + + STAmount saBalance = saAmount; + + sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); + + if (!bFlipped) + saBalance.negate(); + + sleRippleState->setIFieldAmount(sfBalance, saBalance); + sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, uSenderID); + sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uReceiverID); + } + else + { + STAmount saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + + if (!bFlipped) + saBalance.negate(); // Put balance in low terms. + + saBalance += saAmount; + + if (!bFlipped) + saBalance.negate(); + + sleRippleState->setIFieldAmount(sfBalance, saBalance); + + entryModify(sleRippleState); + } +} + +// Send regardless of limits. +// --> saAmount: Amount/currency/issuer for receiver to get. +// <-- saActual: Amount actually sent. Sender pay's fees. +STAmount LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +{ + STAmount saActual; + const uint160 uIssuerID = saAmount.getIssuer(); + + assert(!!uSenderID && !!uReceiverID); + + if (uSenderID == uIssuerID || uReceiverID == uIssuerID) + { + // Direct send: redeeming IOUs and/or sending own IOUs. + rippleCredit(uSenderID, uReceiverID, saAmount); + + saActual = saAmount; + } + else + { + // Sending 3rd party IOUs: transit. + + STAmount saTransitFee = rippleTransferFee(uSenderID, uReceiverID, uIssuerID, saAmount); + + saActual = !saTransitFee ? saAmount : saAmount+saTransitFee; + + saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. + + rippleCredit(uIssuerID, uReceiverID, saAmount); + rippleCredit(uSenderID, uIssuerID, saActual); + } + + return saActual; +} + +void LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +{ + assert(!saAmount.isNegative()); + + if (!saAmount) + { + nothing(); + } + else if (saAmount.isNative()) + { + SLE::pointer sleSender = !!uSenderID + ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) + : SLE::pointer(); + SLE::pointer sleReceiver = !!uReceiverID + ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)) + : SLE::pointer(); + + Log(lsINFO) << boost::str(boost::format("accountSend> %s (%s) -> %s (%s) : %s") + % NewcoinAddress::createHumanAccountID(uSenderID) + % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % NewcoinAddress::createHumanAccountID(uReceiverID) + % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % saAmount.getFullText()); + + if (sleSender) + { + sleSender->setIFieldAmount(sfBalance, sleSender->getIValueFieldAmount(sfBalance) - saAmount); + entryModify(sleSender); + } + + if (sleReceiver) + { + sleReceiver->setIFieldAmount(sfBalance, sleReceiver->getIValueFieldAmount(sfBalance) + saAmount); + entryModify(sleReceiver); + } + + Log(lsINFO) << boost::str(boost::format("accountSend< %s (%s) -> %s (%s) : %s") + % NewcoinAddress::createHumanAccountID(uSenderID) + % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % NewcoinAddress::createHumanAccountID(uReceiverID) + % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % saAmount.getFullText()); + } + else + { + rippleSend(uSenderID, uReceiverID, saAmount); + } +} // vim:ts=4 diff --git a/src/LedgerEntrySet.h b/src/LedgerEntrySet.h index c840057c27..92d96294e9 100644 --- a/src/LedgerEntrySet.h +++ b/src/LedgerEntrySet.h @@ -6,6 +6,7 @@ #include "SerializedLedger.h" #include "TransactionMeta.h" #include "Ledger.h" +#include "TransactionErr.h" enum LedgerEntryAction { @@ -16,7 +17,6 @@ enum LedgerEntryAction taaCREATE, // Newly created. }; - class LedgerEntrySetEntry { public: @@ -67,6 +67,9 @@ public: void init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID); void clear(); + Ledger::pointer& getLedger() { return mLedger; } + const Ledger::pointer& getLedgerRef() const { return mLedger; } + // basic entry functions SLE::pointer getEntry(const uint256& index, LedgerEntryAction&); LedgerEntryAction hasEntry(const uint256& index) const; @@ -79,8 +82,45 @@ public: SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); + // Directory functions. + TER dirAdd( + uint64& uNodeDir, // Node of entry. + const uint256& uRootIndex, + const uint256& uLedgerIndex); + + TER dirDelete( + const bool bKeepRoot, + const uint64& uNodeDir, // Node item is mentioned in. + const uint256& uRootIndex, + const uint256& uLedgerIndex, // Item being deleted + const bool bStable); + + bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); + bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); + + // Offer functions. + TER offerDelete(const uint256& uOfferIndex); + TER offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); + + // Balance functions. + uint32 rippleTransferRate(const uint160& uIssuerID); + STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); + STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); + uint32 rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow=sfLowQualityIn, const SOE_Field sfHigh=sfHighQualityIn); + uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) + { return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); } + + STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); + void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); + STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + + STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); + Json::Value getJson(int) const; - void calcRawMeta(Serializer&, Ledger::ref originalLedger); + void calcRawMeta(Serializer&); // iterator functions bool isEmpty() const { return mEntries.empty(); } diff --git a/src/LedgerMaster.cpp b/src/LedgerMaster.cpp index a3cc5a1475..abdbf12e99 100644 --- a/src/LedgerMaster.cpp +++ b/src/LedgerMaster.cpp @@ -80,8 +80,7 @@ Ledger::pointer LedgerMaster::closeLedger() return closingLedger; } -TER LedgerMaster::doTransaction(const SerializedTransaction& txn, uint32 targetLedger, - TransactionEngineParams params) +TER LedgerMaster::doTransaction(const SerializedTransaction& txn, TransactionEngineParams params) { TER result = mEngine.applyTransaction(txn, params); theApp->getOPs().pubTransaction(mEngine.getLedger(), txn, result); diff --git a/src/LedgerMaster.h b/src/LedgerMaster.h index 8db6f34490..37db69c5c2 100644 --- a/src/LedgerMaster.h +++ b/src/LedgerMaster.h @@ -45,8 +45,7 @@ public: void runStandAlone() { mFinalizedLedger = mCurrentLedger; } - TER doTransaction(const SerializedTransaction& txn, uint32 targetLedger, - TransactionEngineParams params); + TER doTransaction(const SerializedTransaction& txn, TransactionEngineParams params); void pushLedger(Ledger::ref newLedger); void pushLedger(Ledger::ref newLCL, Ledger::ref newOL); diff --git a/src/LedgerProposal.cpp b/src/LedgerProposal.cpp index 5840c10372..a334a612f7 100644 --- a/src/LedgerProposal.cpp +++ b/src/LedgerProposal.cpp @@ -54,17 +54,20 @@ bool LedgerProposal::checkSign(const std::string& signature, const uint256& sign return mPublicKey.verifyNodePublic(signingHash, signature); } -void LedgerProposal::changePosition(const uint256& newPosition, uint32 closeTime) +bool LedgerProposal::changePosition(const uint256& newPosition, uint32 closeTime) { + if (mProposeSeq == seqLeave) + return false; + mCurrentHash = newPosition; mCloseTime = closeTime; mTime = boost::posix_time::second_clock::universal_time(); ++mProposeSeq; + return true; } void LedgerProposal::bowOut() { - mCurrentHash = uint256(); mTime = boost::posix_time::second_clock::universal_time(); mProposeSeq = seqLeave; } diff --git a/src/LedgerProposal.h b/src/LedgerProposal.h index adaa557719..fb7df34309 100644 --- a/src/LedgerProposal.h +++ b/src/LedgerProposal.h @@ -59,11 +59,12 @@ public: void setSignature(const std::string& signature) { mSignature = signature; } bool hasSignature() { return !mSignature.empty(); } bool isPrevLedger(const uint256& pl) { return mPreviousLedger == pl; } + bool isBowOut() { return mProposeSeq == seqLeave; } const boost::posix_time::ptime getCreateTime() { return mTime; } bool isStale(boost::posix_time::ptime cutoff) { return mTime <= cutoff; } - void changePosition(const uint256& newPosition, uint32 newCloseTime); + bool changePosition(const uint256& newPosition, uint32 newCloseTime); void bowOut(); Json::Value getJson() const; }; diff --git a/src/LedgerTiming.h b/src/LedgerTiming.h index 80e89b30f4..551c9bfce6 100644 --- a/src/LedgerTiming.h +++ b/src/LedgerTiming.h @@ -28,6 +28,10 @@ // How often we check state or change positions (in milliseconds) # define LEDGER_GRANULARITY 1000 +// The percentage of active trusted validators that must be able to +// keep up with the network or we consider the network overloaded +# define LEDGER_NET_RATIO 70 + // How long we consider a proposal fresh # define PROPOSE_FRESHNESS 20 diff --git a/src/Log.h b/src/Log.h index 42c8bb00c9..7153012226 100644 --- a/src/Log.h +++ b/src/Log.h @@ -6,6 +6,9 @@ #include #include +// Ensure that we don't get value.h without writer.h +#include "../json/json.h" + enum LogSeverity { lsTRACE = 0, diff --git a/src/NetworkOPs.cpp b/src/NetworkOPs.cpp index 754559db83..0433892263 100644 --- a/src/NetworkOPs.cpp +++ b/src/NetworkOPs.cpp @@ -93,7 +93,7 @@ Transaction::pointer NetworkOPs::submitTransaction(const Transaction::pointer& t return tpTransNew; } -Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, uint32 tgtLedger, Peer* source) +Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, Peer* source) { Transaction::pointer dbtx = theApp->getMasterTransaction().fetch(trans->getID(), true); if (dbtx) return dbtx; @@ -105,7 +105,7 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, return trans; } - TER r = mLedgerMaster->doTransaction(*trans->getSTransaction(), tgtLedger, tapOPEN_LEDGER); + TER r = mLedgerMaster->doTransaction(*trans->getSTransaction(), tapOPEN_LEDGER); if (r == tefFAILURE) throw Fault(IO_ERROR); if (r == terPRE_SEQ) @@ -140,7 +140,6 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, tx.set_rawtransaction(&s.getData().front(), s.getLength()); tx.set_status(newcoin::tsCURRENT); tx.set_receivetimestamp(getNetworkTimeNC()); - tx.set_ledgerindexpossible(trans->getLedger()); PackedMessage::pointer packet = boost::make_shared(tx, newcoin::mtTRANSACTION); theApp->getConnectionPool().relayMessage(source, packet); @@ -157,7 +156,6 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, tx.set_rawtransaction(&s.getData().front(), s.getLength()); tx.set_status(newcoin::tsCURRENT); tx.set_receivetimestamp(getNetworkTimeNC()); - tx.set_ledgerindexpossible(tgtLedger); PackedMessage::pointer packet = boost::make_shared(tx, newcoin::mtTRANSACTION); theApp->getConnectionPool().relayMessage(source, packet); } @@ -213,7 +211,11 @@ SLE::pointer NetworkOPs::getGenerator(const uint256& uLedger, const uint160& uGe { LedgerStateParms qry = lepNONE; - return mLedgerMaster->getLedgerByHash(uLedger)->getGenerator(qry, uGeneratorID); + Ledger::pointer ledger = mLedgerMaster->getLedgerByHash(uLedger); + if (!ledger) + return SLE::pointer(); + else + return ledger->getGenerator(qry, uGeneratorID); } // @@ -714,7 +716,7 @@ bool NetworkOPs::hasTXSet(const boost::shared_ptr& peer, const uint256& se return mConsensus->peerHasSet(peer, set, status); } -void NetworkOPs::mapComplete(const uint256& hash, const SHAMap::pointer& map) +void NetworkOPs::mapComplete(const uint256& hash, SHAMap::ref map) { if (mConsensus) mConsensus->mapComplete(hash, map, true); diff --git a/src/NetworkOPs.h b/src/NetworkOPs.h index 401d33baee..ed6e7f0471 100644 --- a/src/NetworkOPs.h +++ b/src/NetworkOPs.h @@ -116,8 +116,7 @@ public: // Transaction::pointer submitTransaction(const Transaction::pointer& tpTrans); - Transaction::pointer processTransaction(Transaction::pointer transaction, uint32 targetLedger = 0, - Peer* source = NULL); + Transaction::pointer processTransaction(Transaction::pointer transaction, Peer* source = NULL); Transaction::pointer findTransactionByID(const uint256& transactionID); int findTransactionsBySource(const uint256& uLedger, std::list&, const NewcoinAddress& sourceAccount, uint32 minSeq, uint32 maxSeq); @@ -172,7 +171,7 @@ public: bool recvValidation(const SerializedValidation::pointer& val); SHAMap::pointer getTXMap(const uint256& hash); bool hasTXSet(const boost::shared_ptr& peer, const uint256& set, newcoin::TxSetStatus status); - void mapComplete(const uint256& hash, const SHAMap::pointer& map); + void mapComplete(const uint256& hash, SHAMap::ref map); // network state machine void checkState(const boost::system::error_code& result); diff --git a/src/Operation.h b/src/Operation.h index 09f023f6a2..b2946f2945 100644 --- a/src/Operation.h +++ b/src/Operation.h @@ -12,6 +12,8 @@ public: virtual bool work(Interpreter* interpreter)=0; virtual int getFee(); + + virtual ~Operation() { ; } }; // this is just an Int in the code diff --git a/src/Peer.cpp b/src/Peer.cpp index aec3ce31e2..ea27e06c81 100644 --- a/src/Peer.cpp +++ b/src/Peer.cpp @@ -702,13 +702,7 @@ void Peer::recvTransaction(newcoin::TMTransaction& packet) } #endif - uint32 targetLedger = 0; - if (packet.has_ledgerindexfinal()) - targetLedger = packet.ledgerindexfinal(); - else if (packet.has_ledgerindexpossible()) - targetLedger = packet.ledgerindexpossible(); - - tx = theApp->getOPs().processTransaction(tx, targetLedger, this); + tx = theApp->getOPs().processTransaction(tx, this); if(tx->getStatus() != INCLUDED) { // transaction wasn't accepted into ledger diff --git a/src/RippleCalc.cpp b/src/RippleCalc.cpp new file mode 100644 index 0000000000..7b88303bc9 --- /dev/null +++ b/src/RippleCalc.cpp @@ -0,0 +1,2400 @@ + +#include +#include +#include + +#include "RippleCalc.h" +#include "Log.h" + +#include "../json/writer.h" + +std::size_t hash_value(const aciSource& asValue) +{ + std::size_t seed = 0; + + asValue.get<0>().hash_combine(seed); + asValue.get<1>().hash_combine(seed); + asValue.get<2>().hash_combine(seed); + + return seed; +} + +// If needed, advance to next funded offer. +// - Automatically advances to first offer. +// - Set bEntryAdvance to advance to next entry. +// <-- uOfferIndex : 0=end of list. +TER RippleCalc::calcNodeAdvance( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const bool bReverse) +{ + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const uint160& uPrvCurrencyID = pnPrv.uCurrencyID; + const uint160& uPrvIssuerID = pnPrv.uIssuerID; + const uint160& uCurCurrencyID = pnCur.uCurrencyID; + const uint160& uCurIssuerID = pnCur.uIssuerID; + + uint256& uDirectTip = pnCur.uDirectTip; + uint256 uDirectEnd = pnCur.uDirectEnd; + bool& bDirectAdvance = pnCur.bDirectAdvance; + SLE::pointer& sleDirectDir = pnCur.sleDirectDir; + STAmount& saOfrRate = pnCur.saOfrRate; + + bool& bEntryAdvance = pnCur.bEntryAdvance; + unsigned int& uEntry = pnCur.uEntry; + uint256& uOfferIndex = pnCur.uOfferIndex; + SLE::pointer& sleOffer = pnCur.sleOffer; + uint160& uOfrOwnerID = pnCur.uOfrOwnerID; + STAmount& saOfferFunds = pnCur.saOfferFunds; + STAmount& saTakerPays = pnCur.saTakerPays; + STAmount& saTakerGets = pnCur.saTakerGets; + bool& bFundsDirty = pnCur.bFundsDirty; + + TER terResult = tesSUCCESS; + + do + { + bool bDirectDirDirty = false; + + if (!uDirectEnd) + { + // Need to initialize current node. + + uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); + uDirectEnd = Ledger::getQualityNext(uDirectTip); + sleDirectDir = lesActive.entryCache(ltDIR_NODE, uDirectTip); + bDirectAdvance = !sleDirectDir; + bDirectDirDirty = true; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Initialize node: uDirectTip=%s uDirectEnd=%s bDirectAdvance=%d") % uDirectTip % uDirectEnd % bDirectAdvance); + } + + if (bDirectAdvance) + { + // Get next quality. + uDirectTip = lesActive.getLedger()->getNextLedgerIndex(uDirectTip, uDirectEnd); + bDirectDirDirty = true; + bDirectAdvance = false; + + if (!!uDirectTip) + { + // Have another quality directory. + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Quality advance: uDirectTip=%s") % uDirectTip); + + sleDirectDir = lesActive.entryCache(ltDIR_NODE, uDirectTip); + } + else if (bReverse) + { + Log(lsINFO) << "calcNodeAdvance: No more offers."; + + uOfferIndex = 0; + break; + } + else + { + // No more offers. Should be done rather than fall off end of book. + Log(lsINFO) << "calcNodeAdvance: Unreachable: Fell off end of order book."; + assert(false); + + terResult = tefEXCEPTION; + } + } + + if (bDirectDirDirty) + { + saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio + uEntry = 0; + bEntryAdvance = true; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); + } + + if (!bEntryAdvance) + { + if (bFundsDirty) + { + saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); + saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); + + saOfferFunds = lesActive.accountFunds(uOfrOwnerID, saTakerGets); // Funds left. + bFundsDirty = false; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); + } + else + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: as is")); + nothing(); + } + } + else if (!lesActive.dirNext(uDirectTip, sleDirectDir, uEntry, uOfferIndex)) + { + // Failed to find an entry in directory. + + uOfferIndex = 0; + + // Do another cur directory iff bMultiQuality + if (bMultiQuality) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: next quality")); + bDirectAdvance = true; + } + else if (!bReverse) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: unreachable: ran out of offers")); + assert(false); // Can't run out of offers in forward direction. + terResult = tefEXCEPTION; + } + } + else + { + // Got a new offer. + sleOffer = lesActive.entryCache(ltOFFER, uOfferIndex); + uOfrOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + + const aciSource asLine = boost::make_tuple(uOfrOwnerID, uCurCurrencyID, uCurIssuerID); + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfrOwnerID=%s") % NewcoinAddress::createHumanAccountID(uOfrOwnerID)); + + if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= lesActive.getLedger()->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcNodeAdvance: expired offer"; + + assert(musUnfundedFound.find(uOfferIndex) != musUnfundedFound.end()); // Verify reverse found it too. + bEntryAdvance = true; + continue; + } + + // Allowed to access source from this node? + // XXX This can get called multiple times for same source in a row, caching result would be nice. + curIssuerNodeConstIterator itForward = pspCur->umForward.find(asLine); + const bool bFoundForward = itForward != pspCur->umForward.end(); + + if (bFoundForward && itForward->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (forward)"; + + bEntryAdvance = true; + continue; + } + + curIssuerNodeConstIterator itPast = mumSource.find(asLine); + bool bFoundPast = itPast != mumSource.end(); + + if (bFoundPast && itPast->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (past)"; + + bEntryAdvance = true; + continue; + } + + curIssuerNodeConstIterator itReverse = pspCur->umReverse.find(asLine); + bool bFoundReverse = itReverse != pspCur->umReverse.end(); + + if (bFoundReverse && itReverse->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (reverse)"; + + bEntryAdvance = true; + continue; + } + + saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); + saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); + + saOfferFunds = lesActive.accountFunds(uOfrOwnerID, saTakerGets); // Funds left. + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded. + Log(lsINFO) << "calcNodeAdvance: unfunded offer"; + + if (bReverse && !bFoundReverse && !bFoundPast) + { + // Never mentioned before: found unfunded. + musUnfundedFound.insert(uOfferIndex); // Mark offer for always deletion. + } + + // YYY Could verify offer is correct place for unfundeds. + bEntryAdvance = true; + continue; + } + + if (bReverse // Need to remember reverse mention. + && !bFoundPast // Not mentioned in previous passes. + && !bFoundReverse) // Not mentioned for pass. + { + // Consider source mentioned by current path state. + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: remember=%s/%s/%s") + % NewcoinAddress::createHumanAccountID(uOfrOwnerID) + % STAmount::createHumanCurrency(uCurCurrencyID) + % NewcoinAddress::createHumanAccountID(uCurIssuerID)); + + pspCur->umReverse.insert(std::make_pair(asLine, uIndex)); + } + + bFundsDirty = false; + bEntryAdvance = false; + } + } + while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance)); + + if (tesSUCCESS == terResult) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfferIndex=%s") % uOfferIndex); + } + else + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: terResult=%s") % transToken(terResult)); + } + + return terResult; +} + +// Between offer nodes, the fee charged may vary. Therefore, process one inbound offer at a time. +// Propagate the inbound offer's requirements to the previous node. The previous node adjusts the amount output and the +// amount spent on fees. +// Continue process till request is satisified while we the rate does not increase past the initial rate. +TER RippleCalc::calcNodeDeliverRev( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uOutAccountID, // --> Output owner's account. + const STAmount& saOutReq, // --> Funds wanted. + STAmount& saOutAct) // <-- Funds delivered. +{ + TER terResult = tesSUCCESS; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvAccountID = pnPrv.uAccountID; + const STAmount& saTransferRate = pnCur.saTransferRate; + + STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be adjusted. + + saOutAct = 0; + + while (saOutAct != saOutReq) // Did not deliver limit. + { + bool& bEntryAdvance = pnCur.bEntryAdvance; + STAmount& saOfrRate = pnCur.saOfrRate; + uint256& uOfferIndex = pnCur.uOfferIndex; + SLE::pointer& sleOffer = pnCur.sleOffer; + const uint160& uOfrOwnerID = pnCur.uOfrOwnerID; + bool& bFundsDirty = pnCur.bFundsDirty; + STAmount& saOfferFunds = pnCur.saOfferFunds; + STAmount& saTakerPays = pnCur.saTakerPays; + STAmount& saTakerGets = pnCur.saTakerGets; + STAmount& saRateMax = pnCur.saRateMax; + + terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, true); // If needed, advance to next funded offer. + + if (tesSUCCESS != terResult || !uOfferIndex) + { + // Error or out of offers. + break; + } + + const STAmount saOutFeeRate = uOfrOwnerID == uCurIssuerID || uOutAccountID == uCurIssuerID // Issuer receiving or sending. + ? saOne // No fee. + : saTransferRate; // Transfer rate of issuer. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: uOfrOwnerID=%s uOutAccountID=%s uCurIssuerID=%s saTransferRate=%s saOutFeeRate=%s") + % NewcoinAddress::createHumanAccountID(uOfrOwnerID) + % NewcoinAddress::createHumanAccountID(uOutAccountID) + % NewcoinAddress::createHumanAccountID(uCurIssuerID) + % saTransferRate.getFullText() + % saOutFeeRate.getFullText()); + + if (!saRateMax) + { + // Set initial rate. + saRateMax = saOutFeeRate; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Set initial rate: saRateMax=%s saOutFeeRate=%s") + % saRateMax + % saOutFeeRate); + } + else if (saRateMax < saOutFeeRate) + { + // Offer exceeds initial rate. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Offer exceeds initial rate: saRateMax=%s saOutFeeRate=%s") + % saRateMax + % saOutFeeRate); + + nothing(); + break; + } + else if (saOutFeeRate < saRateMax) + { + // Reducing rate. + + saRateMax = saOutFeeRate; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Reducing rate: saRateMax=%s") + % saRateMax); + } + + STAmount saOutPass = std::min(std::min(saOfferFunds, saTakerGets), saOutReq-saOutAct); // Offer maximum out - assuming no out fees. + STAmount saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); // Offer out with fees. + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saOutReq=%s saOutAct=%s saTakerGets=%s saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") + % saOutReq + % saOutAct + % saTakerGets + % saOutPass + % saOutPlusFees + % saOfferFunds); + + if (saOutPlusFees > saOfferFunds) + { + // Offer owner can not cover all fees, compute saOutPass based on saOfferFunds. + + saOutPlusFees = saOfferFunds; + saOutPass = STAmount::divide(saOutPlusFees, saOutFeeRate); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Total exceeds fees: saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") + % saOutPass + % saOutPlusFees + % saOfferFunds); + } + + // Compute portion of input needed to cover output. + + STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays); + STAmount saInPassAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saInPassReq=%s saOfrRate=%s saOutPass=%s saOutPlusFees=%s") + % saInPassReq + % saOfrRate + % saOutPass + % saOutPlusFees); + + // Find out input amount actually available at current rate. + if (!!uPrvAccountID) + { + // account --> OFFER --> ? + // Previous is the issuer and receiver is an offer, so no fee or quality. + // Previous is the issuer and has unlimited funds. + // Offer owner is obtaining IOUs via an offer, so credit line limits are ignored. + // As limits are ignored, don't need to adjust previous account's balance. + + saInPassAct = saInPassReq; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: account --> OFFER --> ? : saInPassAct=%s") + % saPrvDlvReq); + } + else + { + // offer --> OFFER --> ? + + terResult = calcNodeDeliverRev( + uIndex-1, + pspCur, + bMultiQuality, + uOfrOwnerID, + saInPassReq, + saInPassAct); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer --> OFFER --> ? : saInPassAct=%s") + % saInPassAct); + } + + if (tesSUCCESS != terResult) + break; + + if (saInPassAct != saInPassReq) + { + // Adjust output to conform to limited input. + saOutPass = STAmount::divide(saInPassAct, saOfrRate, saTakerGets); + saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: adjusted: saOutPass=%s saOutPlusFees=%s") + % saOutPass + % saOutPlusFees); + } + + // Funds were spent. + bFundsDirty = true; + + // Deduct output, don't actually need to send. + lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); + + // Adjust offer + sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPass); + sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); + + lesActive.entryModify(sleOffer); + + if (saOutPass == saTakerGets) + { + // Offer became unfunded. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer became unfunded.")); + + bEntryAdvance = true; + } + + saOutAct += saOutPass; + saPrvDlvReq += saInPassAct; + } + + if (!saOutAct) + terResult = tepPATH_DRY; + + return terResult; +} + +// Deliver maximum amount of funds from previous node. +// Goal: Make progress consuming the offer. +TER RippleCalc::calcNodeDeliverFwd( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uInAccountID, // --> Input owner's account. + const STAmount& saInFunds, // --> Funds available for delivery and fees. + const STAmount& saInReq, // --> Limit to deliver. + STAmount& saInAct, // <-- Amount delivered. + STAmount& saInFees) // <-- Fees charged. +{ + TER terResult = tesSUCCESS; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; + + const uint160& uNxtAccountID = pnNxt.uAccountID; + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvIssuerID = pnPrv.uIssuerID; + const STAmount& saTransferRate = pnPrv.saTransferRate; + + saInAct = 0; + saInFees = 0; + + while (tesSUCCESS == terResult + && saInAct != saInReq // Did not deliver limit. + && saInAct + saInFees != saInFunds) // Did not deliver all funds. + { + terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. + + if (tesSUCCESS == terResult) + { + bool& bEntryAdvance = pnCur.bEntryAdvance; + STAmount& saOfrRate = pnCur.saOfrRate; + uint256& uOfferIndex = pnCur.uOfferIndex; + SLE::pointer& sleOffer = pnCur.sleOffer; + const uint160& uOfrOwnerID = pnCur.uOfrOwnerID; + bool& bFundsDirty = pnCur.bFundsDirty; + STAmount& saOfferFunds = pnCur.saOfferFunds; + STAmount& saTakerPays = pnCur.saTakerPays; + STAmount& saTakerGets = pnCur.saTakerGets; + + + const STAmount saInFeeRate = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. + ? saOne // No fee. + : saTransferRate; // Transfer rate of issuer. + + // + // First calculate assuming no output fees. + // XXX Make sure derived in does not exceed actual saTakerPays due to rounding. + + STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees. + STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout. + STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees. + STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds. + STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. + STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. + + STAmount saInPassFees; + STAmount saOutPassAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") + % saOutFunded + % saInFunded + % saInTotal + % saInSum + % saInPassAct + % saOutPassMax); + + if (!!uNxtAccountID) + { + // ? --> OFFER --> account + // Input fees: vary based upon the consumed offer's owner. + // Output fees: none as the destination account is the issuer. + + // XXX This doesn't claim input. + // XXX Assumes input is in limbo. XXX Check. + + // Debit offer owner. + lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax); + + saOutPassAct = saOutPassMax; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") + % saOutPassAct); + } + else + { + // ? --> OFFER --> offer + STAmount saOutPassFees; + + terResult = RippleCalc::calcNodeDeliverFwd( + uIndex+1, + pspCur, + bMultiQuality, + uOfrOwnerID, + saOutPassMax, + saOutPassMax, + saOutPassAct, // <-- Amount delivered. + saOutPassFees); // <-- Fees charged. + + if (tesSUCCESS != terResult) + break; + + // Offer maximum in limited by next payout. + saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); + saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") + % saTakerGets.getFullText() + % saTakerPays.getFullText() + % saInPassAct.getFullText() + % saOutPassAct.getFullText()); + + // Funds were spent. + bFundsDirty = true; + + // Credit issuer transfer fees. + lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassFees); + + // Credit offer owner from offer. + lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassAct); + + // Adjust offer + sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); + sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); + + lesActive.entryModify(sleOffer); + + if (saOutPassAct == saTakerGets) + { + // Offer became unfunded. + pspCur->vUnfundedBecame.push_back(uOfferIndex); + bEntryAdvance = true; + } + + saInAct += saInPassAct; + saInFees += saInPassFees; + } + } + + return terResult; +} + +// Called to drive from the last offer node in a chain. +TER RippleCalc::calcNodeOfferRev( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality) +{ + TER terResult; + + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; + + if (!!pnNxt.uAccountID) + { + // Next is an account node, resolve current offer node's deliver. + STAmount saDeliverAct; + + terResult = calcNodeDeliverRev( + uIndex, + pspCur, + bMultiQuality, + + pnNxt.uAccountID, + pnCur.saRevDeliver, + saDeliverAct); + } + else + { + // Next is an offer. Deliver has already been resolved. + terResult = tesSUCCESS; + } + + return terResult; +} + +// Called to drive the from the first offer node in a chain. +// - Offer input is limbo. +// - Current offers consumed. +// - Current offer owners debited. +// - Transfer fees credited to issuer. +// - Payout to issuer or limbo. +// - Deliver is set without transfer fees. +TER RippleCalc::calcNodeOfferFwd( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality + ) +{ + TER terResult; + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + + if (!!pnPrv.uAccountID) + { + // Previous is an account node, resolve its deliver. + STAmount saInAct; + STAmount saInFees; + + terResult = calcNodeDeliverFwd( + uIndex, + pspCur, + bMultiQuality, + pnPrv.uAccountID, + pnPrv.saFwdDeliver, + pnPrv.saFwdDeliver, + saInAct, + saInFees); + + assert(tesSUCCESS != terResult || pnPrv.saFwdDeliver == saInAct+saInFees); + } + else + { + // Previous is an offer. Deliver has already been resolved. + terResult = tesSUCCESS; + } + + return terResult; + +} + +// Cur is the driver and will be filled exactly. +// uQualityIn -> uQualityOut +// saPrvReq -> saCurReq +// sqPrvAct -> saCurAct +// This is a minimizing routine: moving in reverse it propagates the send limit to the sender, moving forward it propagates the +// actual send toward the receiver. +// This routine works backwards as it calculates previous wants based on previous credit limits and current wants. +// This routine works forwards as it calculates current deliver based on previous delivery limits and current wants. +// XXX Deal with uQualityIn or uQualityOut = 0 +void RippleCalc::calcNodeRipple( + const uint32 uQualityIn, + const uint32 uQualityOut, + const STAmount& saPrvReq, // --> in limit including fees, <0 = unlimited + const STAmount& saCurReq, // --> out limit (driver) + STAmount& saPrvAct, // <-> in limit including achieved + STAmount& saCurAct, // <-> out limit achieved. + uint64& uRateMax) +{ + Log(lsINFO) << boost::str(boost::format("calcNodeRipple> uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") + % uQualityIn + % uQualityOut + % saPrvReq.getFullText() + % saCurReq.getFullText() + % saPrvAct.getFullText() + % saCurAct.getFullText()); + + assert(saPrvReq.getCurrency() == saCurReq.getCurrency()); + + const bool bPrvUnlimited = saPrvReq.isNegative(); + const STAmount saPrv = bPrvUnlimited ? STAmount(saPrvReq) : saPrvReq-saPrvAct; + const STAmount saCur = saCurReq-saCurAct; + +#if 0 + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCur=%s") + % bPrvUnlimited + % saPrv.getFullText() + % saCur.getFullText()); +#endif + + if (uQualityIn >= uQualityOut) + { + // No fee. + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: No fees")); + + if (!uRateMax || STAmount::uRateOne <= uRateMax) + { + STAmount saTransfer = bPrvUnlimited ? saCur : std::min(saPrv, saCur); + + saPrvAct += saTransfer; + saCurAct += saTransfer; + + if (!uRateMax) + uRateMax = STAmount::uRateOne; + } + } + else + { + // Fee. + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: Fee")); + + uint64 uRate = STAmount::getRate(STAmount(uQualityIn), STAmount(uQualityOut)); + + if (!uRateMax || uRate <= uRateMax) + { + const uint160 uCurrencyID = saCur.getCurrency(); + const uint160 uCurIssuerID = saCur.getIssuer(); + const uint160 uPrvIssuerID = saPrv.getIssuer(); + + STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, uCurrencyID, uCurIssuerID), uQualityIn, uCurrencyID, uCurIssuerID); + + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCurIn=%s") % bPrvUnlimited % saPrv.getFullText() % saCurIn.getFullText()); + if (bPrvUnlimited || saCurIn <= saPrv) + { + // All of cur. Some amount of prv. + saCurAct += saCur; + saPrvAct += saCurIn; + Log(lsINFO) << boost::str(boost::format("calcNodeRipple:3c: saCurReq=%s saPrvAct=%s") % saCurReq.getFullText() % saPrvAct.getFullText()); + } + else + { + // A part of cur. All of prv. (cur as driver) + STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityIn, uCurrencyID, uCurIssuerID), uQualityOut, uCurrencyID, uCurIssuerID); + Log(lsINFO) << boost::str(boost::format("calcNodeRipple:4: saCurReq=%s") % saCurReq.getFullText()); + + saCurAct += saCurOut; + saPrvAct = saPrvReq; + + if (!uRateMax) + uRateMax = uRate; + } + } + } + + Log(lsINFO) << boost::str(boost::format("calcNodeRipple< uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") + % uQualityIn + % uQualityOut + % saPrvReq.getFullText() + % saCurReq.getFullText() + % saPrvAct.getFullText() + % saCurAct.getFullText()); +} + +// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur... +// <-- tesSUCCESS or tepPATH_DRY +TER RippleCalc::calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + TER terResult = tesSUCCESS; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + uint64 uRateMax = 0; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; + + // Current is allowed to redeem to next. + const bool bPrvAccount = !uIndex || isSetBit(pnPrv.uFlags, STPathElement::typeAccount); + const bool bNxtAccount = uIndex == uLast || isSetBit(pnNxt.uFlags, STPathElement::typeAccount); + + const uint160& uCurAccountID = pnCur.uAccountID; + const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; + const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. + + const uint160& uCurrencyID = pnCur.uCurrencyID; + + const uint32 uQualityIn = uIndex ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; + const uint32 uQualityOut = uIndex != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; + + // For bPrvAccount + const STAmount saPrvOwed = bPrvAccount && uIndex // Previous account is owed. + ? lesActive.rippleOwed(uCurAccountID, uPrvAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + const STAmount saPrvLimit = bPrvAccount && uIndex // Previous account may owe. + ? lesActive.rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + const STAmount saNxtOwed = bNxtAccount && uIndex != uLast // Next account is owed. + ? lesActive.rippleOwed(uCurAccountID, uNxtAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev> uIndex=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s") + % uIndex + % uLast + % NewcoinAddress::createHumanAccountID(uPrvAccountID) + % NewcoinAddress::createHumanAccountID(uCurAccountID) + % NewcoinAddress::createHumanAccountID(uNxtAccountID) + % STAmount::createHumanCurrency(uCurrencyID) + % uQualityIn + % uQualityOut + % saPrvOwed.getFullText() + % saPrvLimit.getFullText()); + + // Previous can redeem the owed IOUs it holds. + const STAmount saPrvRedeemReq = saPrvOwed.isPositive() ? saPrvOwed : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = pnPrv.saRevRedeem; + + // Previous can issue up to limit minus whatever portion of limit already used (not including redeemable amount). + const STAmount saPrvIssueReq = saPrvOwed.isNegative() ? saPrvLimit+saPrvOwed : saPrvLimit; + STAmount& saPrvIssueAct = pnPrv.saRevIssue; + + // For !bPrvAccount + const STAmount saPrvDeliverReq = STAmount::saFromSigned(uCurrencyID, uCurAccountID, -1); // Unlimited. + STAmount& saPrvDeliverAct = pnPrv.saRevDeliver; + + // For bNxtAccount + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount saCurRedeemAct(saCurRedeemReq.getCurrency(), saCurRedeemReq.getIssuer()); + + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount saCurIssueAct(saCurIssueReq.getCurrency(), saCurIssueReq.getIssuer()); // Track progress. + + // For !bNxtAccount + const STAmount& saCurDeliverReq = pnCur.saRevDeliver; + STAmount saCurDeliverAct(saCurDeliverReq.getCurrency(), saCurDeliverReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saNxtOwed=%s") + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText() + % saCurRedeemReq.getFullText() + % saNxtOwed.getFullText()); + + Log(lsINFO) << pspCur->getJson(); + + assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand. + assert(!saCurIssueReq || !saNxtOwed.isPositive() || saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. + + if (bPrvAccount && bNxtAccount) + { + if (!uIndex) + { + // ^ --> ACCOUNT --> account|offer + // Nothing to do, there is no previous to adjust. + nothing(); + } + else if (uIndex == uLast) + { + // account --> ACCOUNT --> $ + // Overall deliverable. + const STAmount& saCurWantedReq = bPrvAccount + ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. + : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. + STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s") + % saCurWantedReq.getFullText()); + + // Calculate redeem + if (saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Redeem at 1:1 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1")); + + saCurWantedAct = std::min(saPrvRedeemReq, saCurWantedReq); + saPrvRedeemAct = saCurWantedAct; + + uRateMax = STAmount::uRateOne; + } + + // Calculate issuing. + if (saCurWantedReq != saCurWantedAct // Need more. + && saPrvIssueReq) // Will accept IOUs from prevous. + { + // Rate: quality in : 1.0 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); + + // 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); + } + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tepPATH_DRY; + } + } + else + { + // ^|account --> ACCOUNT --> account + + // redeem (part 1) -> redeem + if (saCurRedeemReq // Next wants IOUs redeemed. + && saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Rate : 1.0 : quality out + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out")); + + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); + } + + // issue (part 1) -> redeem + if (saCurRedeemReq != saCurRedeemAct // Next wants more IOUs redeemed. + && saPrvRedeemAct == saPrvRedeemReq) // Previous has no IOUs to redeem remaining. + { + // Rate: quality in : quality out + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out")); + + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); + } + + // redeem (part 2) -> issue. + if (saCurIssueReq // Next wants IOUs issued. + && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. + && saPrvRedeemAct != saPrvRedeemReq) // Did not complete redeeming previous IOUs. + { + // Rate : 1.0 : transfer_rate + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : transfer_rate")); + + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); + } + + // issue (part 2) -> issue + if (saCurIssueReq != saCurIssueAct // Need wants more IOUs issued. + && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. + && saPrvRedeemReq == saPrvRedeemAct) // Previously redeemed all owed IOUs. + { + // Rate: quality in : 1.0 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); + + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); + } + + if (!saCurRedeemAct && !saCurIssueAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s") + % saCurRedeemReq.getFullText() + % saCurIssueReq.getFullText() + % saPrvOwed.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueAct.getFullText()); + } + } + else if (bPrvAccount && !bNxtAccount) + { + // account --> ACCOUNT --> offer + // Note: deliver is always issue as ACCOUNT is the issuer for the offer input. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer")); + + // redeem -> deliver/issue. + if (saPrvOwed.isPositive() // Previous has IOUs to redeem. + && saCurDeliverReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> deliver/issue + if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed. + && saCurDeliverReq != saCurDeliverAct) // Still need some issued. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); + } + + if (!saCurDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s") + % saCurDeliverReq.getFullText() + % saCurDeliverAct.getFullText() + % saPrvOwed.getFullText()); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + const STAmount& saCurWantedReq = bPrvAccount + ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. + : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. + STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s") + % saCurWantedReq.getFullText()); + + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tepPATH_DRY; + } + } + else + { + // offer --> ACCOUNT --> account + // Note: offer is always delivering(redeeming) as account is issuer. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account")); + + // deliver -> redeem + if (saCurRedeemReq) // Next wants us to redeem. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); + } + + // deliver -> issue. + if (saCurRedeemReq == saCurRedeemAct // Can only issue if previously redeemed all. + && saCurIssueReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s") + % saCurRedeemReq.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueReq.getFullText() + % saPrvDeliverAct.getFullText()); + + if (!saPrvDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + } + } + else + { + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> offer")); + + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); + + if (!saCurDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + } + + return terResult; +} + +// Perfrom balance adjustments between previous and current node. +// - The previous node: specifies what to push through to current. +// - All of previous output is consumed. +// Then, compute output for next node. +// - Current node: specify what to push through to next. +// - Output to next node is computed as input minus quality or transfer fee. +TER RippleCalc::calcNodeAccountFwd( + const unsigned int uIndex, // 0 <= uIndex <= uLast + const PathState::pointer& pspCur, + const bool bMultiQuality) +{ + TER terResult = tesSUCCESS; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + uint64 uRateMax = 0; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; + + const bool bPrvAccount = isSetBit(pnPrv.uFlags, STPathElement::typeAccount); + const bool bNxtAccount = isSetBit(pnNxt.uFlags, STPathElement::typeAccount); + + const uint160& uCurAccountID = pnCur.uAccountID; + const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; + const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. + + const uint160& uCurrencyID = pnCur.uCurrencyID; + + uint32 uQualityIn = uIndex ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; + uint32 uQualityOut = uIndex == uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; + + // For bNxtAccount + const STAmount& saPrvRedeemReq = pnPrv.saFwdRedeem; + STAmount saPrvRedeemAct(saPrvRedeemReq.getCurrency(), saPrvRedeemReq.getIssuer()); + + const STAmount& saPrvIssueReq = pnPrv.saFwdIssue; + STAmount saPrvIssueAct(saPrvIssueReq.getCurrency(), saPrvIssueReq.getIssuer()); + + // For !bPrvAccount + const STAmount& saPrvDeliverReq = pnPrv.saRevDeliver; + STAmount saPrvDeliverAct(saPrvDeliverReq.getCurrency(), saPrvDeliverReq.getIssuer()); + + // For bNxtAccount + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount& saCurRedeemAct = pnCur.saFwdRedeem; + + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount& saCurIssueAct = pnCur.saFwdIssue; + + // For !bNxtAccount + const STAmount& saCurDeliverReq = pnCur.saRevDeliver; + STAmount& saCurDeliverAct = pnCur.saFwdDeliver; + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uIndex=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s") + % uIndex + % uLast + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText() + % saPrvDeliverReq.getFullText() + % saCurRedeemReq.getFullText() + % saCurIssueReq.getFullText() + % saCurDeliverReq.getFullText()); + + // Ripple through account. + + if (bPrvAccount && bNxtAccount) + { + if (!uIndex) + { + // ^ --> ACCOUNT --> account + + // First node, calculate amount to send. + // XXX Use stamp/ripple balance + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount& saCurRedeemAct = pnCur.saFwdRedeem; + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount& saCurIssueAct = pnCur.saFwdIssue; + + const STAmount& saCurSendMaxReq = pspCur->saInReq; // Negative for no limit, doing a calculation. + STAmount& saCurSendMaxAct = pspCur->saInAct; // Report to user how much this sends. + + if (saCurRedeemReq) + { + // Redeem requested. + saCurRedeemAct = saCurRedeemReq.isNegative() + ? saCurRedeemReq + : std::min(saCurRedeemReq, saCurSendMaxReq); + } + else + { + saCurRedeemAct = STAmount(saCurRedeemReq); + } + saCurSendMaxAct = saCurRedeemAct; + + if (saCurIssueReq && (saCurSendMaxReq.isNegative() || saCurSendMaxReq != saCurRedeemAct)) + { + // Issue requested and not over budget. + saCurIssueAct = saCurSendMaxReq.isNegative() + ? saCurIssueReq + : std::min(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); + } + else + { + saCurIssueAct = STAmount(saCurIssueReq); + } + saCurSendMaxAct += saCurIssueAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saCurSendMaxReq=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s") + % saCurSendMaxReq.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueReq.getFullText() + % saCurIssueAct.getFullText()); + } + else if (uIndex == uLast) + { + // account --> ACCOUNT --> $ + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s") + % NewcoinAddress::createHumanAccountID(uPrvAccountID) + % NewcoinAddress::createHumanAccountID(uCurAccountID) + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText()); + + // Last node. Accept all funds. Calculate amount actually to credit. + + STAmount& saCurReceive = pspCur->saOutAct; + + STAmount saIssueCrd = uQualityIn >= QUALITY_ONE + ? saPrvIssueReq // No fee. + : STAmount::multiply(saPrvIssueReq, uQualityIn, uCurrencyID, saPrvIssueReq.getIssuer()); // Fee. + + // Amount to credit. + saCurReceive = saPrvRedeemReq+saIssueCrd; + + // Actually receive. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); + } + else + { + // account --> ACCOUNT --> account + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account")); + + // Previous redeem part 1: redeem -> redeem + if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); + } + + // Previous issue part 1: issue -> redeem + if (saPrvIssueReq != saPrvIssueAct // Previous wants to issue. + && saCurRedeemReq != saCurRedeemAct) // Current has more to redeem to next. + { + // Rate: quality in : quality out + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); + } + + // Previous redeem part 2: redeem -> issue. + // wants to redeem and current would and can issue. + // If redeeming cur to next is done, this implies can issue. + if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem. + && saCurRedeemReq == saCurRedeemAct // Current has no more to redeem to next. + && saCurIssueReq) + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); + } + + // Previous issue part 2 : issue -> issue + if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); + } + + // Adjust prv --> cur balance : take all inbound + // XXX Currency must be in amount. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + } + else if (bPrvAccount && !bNxtAccount) + { + // account --> ACCOUNT --> offer + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); + + // redeem -> issue. + // wants to redeem and current would and can issue. + // If redeeming cur to next is done, this implies can issue. + if (saPrvRedeemReq) // Previous wants to redeem. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> issue + if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. + && saPrvIssueReq) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); + } + + // Adjust prv --> cur balance : take all inbound + // XXX Currency must be in amount. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $")); + + STAmount& saCurReceive = pspCur->saOutAct; + + // Amount to credit. + saCurReceive = saPrvDeliverAct; + + // No income balance adjustments necessary. The paying side inside the offer paid to this account. + } + else + { + // offer --> ACCOUNT --> account + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account")); + + // deliver -> redeem + if (saPrvDeliverReq) // Previous wants to deliver. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); + } + + // deliver -> issue + // Wants to redeem and current would and can issue. + if (saPrvDeliverReq != saPrvDeliverAct // Previous still wants to deliver. + && saCurRedeemReq == saCurRedeemAct // Current has more to redeem to next. + && saCurIssueReq) // Current wants issue. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); + } + + // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. + } + } + else + { + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer")); + + if (saPrvDeliverReq // Previous wants to deliver + && saCurIssueReq) // Current wants issue. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); + } + + // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. + } + + return terResult; +} + +// Return true, iff lhs has less priority than rhs. +bool PathState::lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs) +{ + if (lhs->uQuality != rhs->uQuality) + return lhs->uQuality > rhs->uQuality; // Bigger is worse. + + // Best quanity is second rank. + if (lhs->saOutAct != rhs->saOutAct) + return lhs->saOutAct < rhs->saOutAct; // Smaller is worse. + + // Path index is third rank. + return lhs->mIndex > rhs->mIndex; // Bigger is worse. +} + +// Make sure the path delivers to uAccountID: uCurrencyID from uIssuerID. +// +// Rules: +// - Currencies must be converted via an offer. +// - A node names it's output. +// - A ripple nodes output issuer must be the node's account or the next node's account. +// - Offers can only go directly to another offer if the currency and issuer are an exact match. +TER PathState::pushImply( + const uint160& uAccountID, // --> Delivering to this account. + const uint160& uCurrencyID, // --> Delivering this currency. + const uint160& uIssuerID) // --> Delivering this issuer. +{ + const PaymentNode& pnPrv = vpnNodes.back(); + TER terResult = tesSUCCESS; + + Log(lsINFO) << "pushImply> " + << NewcoinAddress::createHumanAccountID(uAccountID) + << " " << STAmount::createHumanCurrency(uCurrencyID) + << " " << NewcoinAddress::createHumanAccountID(uIssuerID); + + if (pnPrv.uCurrencyID != uCurrencyID) + { + // Currency is different, need to convert via an offer. + + terResult = pushNode( + STPathElement::typeCurrency // Offer. + | STPathElement::typeIssuer, + ACCOUNT_ONE, // Placeholder for offers. + uCurrencyID, // The offer's output is what is now wanted. + uIssuerID); + + } + + // For ripple, non-stamps, ensure the issuer is on at least one side of the transaction. + if (tesSUCCESS == terResult + && !!uCurrencyID // Not stamps. + && (pnPrv.uAccountID != uIssuerID // Previous is not issuing own IOUs. + && uAccountID != uIssuerID)) // Current is not receiving own IOUs. + { + // Need to ripple through uIssuerID's account. + + terResult = pushNode( + STPathElement::typeAccount, + uIssuerID, // Intermediate account is the needed issuer. + uCurrencyID, + uIssuerID); + } + + Log(lsINFO) << "pushImply< " << terResult; + + return terResult; +} + +// Append a node and insert before it any implied nodes. +// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE +TER PathState::pushNode( + const int iType, + const uint160& uAccountID, + const uint160& uCurrencyID, + const uint160& uIssuerID) +{ + Log(lsINFO) << "pushNode> " + << NewcoinAddress::createHumanAccountID(uAccountID) + << " " << STAmount::createHumanCurrency(uCurrencyID) + << "/" << NewcoinAddress::createHumanAccountID(uIssuerID); + PaymentNode pnCur; + const bool bFirst = vpnNodes.empty(); + const PaymentNode& pnPrv = bFirst ? PaymentNode() : vpnNodes.back(); + // true, iff node is a ripple account. false, iff node is an offer node. + const bool bAccount = isSetBit(iType, STPathElement::typeAccount); + // true, iff currency supplied. + // Currency is specified for the output of the current node. + const bool bCurrency = isSetBit(iType, STPathElement::typeCurrency); + // Issuer is specified for the output of the current node. + const bool bIssuer = isSetBit(iType, STPathElement::typeIssuer); + TER terResult = tesSUCCESS; + + pnCur.uFlags = iType; + + if (iType & ~STPathElement::typeValidBits) + { + Log(lsINFO) << "pushNode: bad bits."; + + terResult = temBAD_PATH; + } + else if (bAccount) + { + // Account link + + pnCur.uAccountID = uAccountID; + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; + pnCur.saRevRedeem = STAmount(uCurrencyID, uAccountID); + pnCur.saRevIssue = STAmount(uCurrencyID, uAccountID); + + if (!bFirst) + { + // Add required intermediate nodes to deliver to current account. + terResult = pushImply( + pnCur.uAccountID, // Current account. + pnCur.uCurrencyID, // Wanted currency. + !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XNS); // Account as issuer. + } + + if (tesSUCCESS == terResult && !vpnNodes.empty()) + { + const PaymentNode& pnBck = vpnNodes.back(); + bool bBckAccount = isSetBit(pnBck.uFlags, STPathElement::typeAccount); + + if (bBckAccount) + { + SLE::pointer sleRippleState = mLedger->getSLE(Ledger::getRippleStateIndex(pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); + + if (!sleRippleState) + { + Log(lsINFO) << "pushNode: No credit line between " + << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) + << " for " + << STAmount::createHumanCurrency(pnPrv.uCurrencyID) + << "." ; + + Log(lsINFO) << getJson(); + + terResult = terNO_LINE; + } + else + { + Log(lsINFO) << "pushNode: Credit line found between " + << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) + << " for " + << STAmount::createHumanCurrency(pnPrv.uCurrencyID) + << "." ; + } + } + } + + if (tesSUCCESS == terResult) + vpnNodes.push_back(pnCur); + } + else + { + // Offer link + // Offers bridge a change in currency & issuer or just a change in issuer. + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : pnCur.uAccountID; + pnCur.saRateMax = saZero; + + if (!!pnPrv.uAccountID) + { + // Previous is an account. + + // Insert intermediary issuer account if needed. + terResult = pushImply( + !!pnPrv.uCurrencyID + ? ACCOUNT_ONE // Rippling, but offer's don't have an account. + : ACCOUNT_XNS, + pnPrv.uCurrencyID, + pnPrv.uIssuerID); + } + + if (tesSUCCESS == terResult) + { + vpnNodes.push_back(pnCur); + } + } + Log(lsINFO) << "pushNode< " << terResult; + + return terResult; +} + +PathState::PathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ) + : mLedger(lesSource.getLedgerRef()), mIndex(iIndex), uQuality(0) +{ + const uint160 uInCurrencyID = saSendMax.getCurrency(); + const uint160 uOutCurrencyID = saSend.getCurrency(); + const uint160 uInIssuerID = !!uInCurrencyID ? saSendMax.getIssuer() : ACCOUNT_XNS; + const uint160 uOutIssuerID = !!uOutCurrencyID ? saSend.getIssuer() : ACCOUNT_XNS; + + lesEntries = lesSource.duplicate(); + + saInReq = saSendMax; + saOutReq = saSend; + + // Push sending node. + terStatus = pushNode( + STPathElement::typeAccount + | STPathElement::typeCurrency + | STPathElement::typeIssuer, + uSenderID, + uInCurrencyID, + uInIssuerID); + + BOOST_FOREACH(const STPathElement& speElement, spSourcePath) + { + if (tesSUCCESS == terStatus) + terStatus = pushNode(speElement.getNodeType(), speElement.getAccountID(), speElement.getCurrency(), speElement.getIssuerID()); + } + + if (tesSUCCESS == terStatus) + { + // Create receiver node. + + terStatus = pushImply(uReceiverID, uOutCurrencyID, uOutIssuerID); + if (tesSUCCESS == terStatus) + { + terStatus = pushNode( + STPathElement::typeAccount // Last node is always an account. + | STPathElement::typeCurrency + | STPathElement::typeIssuer, + uReceiverID, // Receive to output + uOutCurrencyID, // Desired currency + uOutIssuerID); + } + } + + if (tesSUCCESS == terStatus) + { + // Look for first mention of source in nodes and detect loops. + // Note: The output is not allowed to be a source. + + const unsigned int uNodes = vpnNodes.size(); + + for (unsigned int uIndex = 0; tesSUCCESS == terStatus && uIndex != uNodes; ++uIndex) + { + const PaymentNode& pnCur = vpnNodes[uIndex]; + + if (!!pnCur.uAccountID) + { + // Source is a ripple line + nothing(); + } + else if (!umForward.insert(std::make_pair(boost::make_tuple(pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uIndex)).second) + { + // Failed to insert. Have a loop. + Log(lsINFO) << boost::str(boost::format("PathState: loop detected: %s") + % getJson()); + + terStatus = temBAD_PATH_LOOP; + } + } + } + + Log(lsINFO) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s") + % STAmount::createHumanCurrency(uInCurrencyID) + % NewcoinAddress::createHumanAccountID(uInIssuerID) + % STAmount::createHumanCurrency(uOutCurrencyID) + % NewcoinAddress::createHumanAccountID(uOutIssuerID) + % getJson()); +} + +Json::Value PathState::getJson() const +{ + Json::Value jvPathState(Json::objectValue); + Json::Value jvNodes(Json::arrayValue); + + BOOST_FOREACH(const PaymentNode& pnNode, vpnNodes) + { + Json::Value jvNode(Json::objectValue); + + Json::Value jvFlags(Json::arrayValue); + + if (pnNode.uFlags & STPathElement::typeAccount) + jvFlags.append("account"); + + jvNode["flags"] = jvFlags; + + if (pnNode.uFlags & STPathElement::typeAccount) + jvNode["account"] = NewcoinAddress::createHumanAccountID(pnNode.uAccountID); + + if (!!pnNode.uCurrencyID) + jvNode["currency"] = STAmount::createHumanCurrency(pnNode.uCurrencyID); + + if (!!pnNode.uIssuerID) + jvNode["issuer"] = NewcoinAddress::createHumanAccountID(pnNode.uIssuerID); + + // if (pnNode.saRevRedeem) + jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText(); + + // if (pnNode.saRevIssue) + jvNode["rev_issue"] = pnNode.saRevIssue.getFullText(); + + // if (pnNode.saRevDeliver) + jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText(); + + // if (pnNode.saFwdRedeem) + jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText(); + + // if (pnNode.saFwdIssue) + jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText(); + + // if (pnNode.saFwdDeliver) + jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText(); + + jvNodes.append(jvNode); + } + + jvPathState["status"] = terStatus; + jvPathState["index"] = mIndex; + jvPathState["nodes"] = jvNodes; + + if (saInReq) + jvPathState["in_req"] = saInReq.getJson(0); + + if (saInAct) + jvPathState["in_act"] = saInAct.getJson(0); + + if (saOutReq) + jvPathState["out_req"] = saOutReq.getJson(0); + + if (saOutAct) + jvPathState["out_act"] = saOutAct.getJson(0); + + if (uQuality) + jvPathState["uQuality"] = Json::Value::UInt(uQuality); + + return jvPathState; +} + +TER RippleCalc::calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + const PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); + + Log(lsINFO) << boost::str(boost::format("calcNodeFwd> uIndex=%d") % uIndex); + + TER terResult = bCurAccount + ? calcNodeAccountFwd(uIndex, pspCur, bMultiQuality) + : calcNodeOfferFwd(uIndex, pspCur, bMultiQuality); + + if (tesSUCCESS == terResult && uIndex + 1 != pspCur->vpnNodes.size()) + { + terResult = calcNodeFwd(uIndex+1, pspCur, bMultiQuality); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeFwd< uIndex=%d terResult=%d") % uIndex % terResult); + + return terResult; +} + +// Calculate a node and its previous nodes. +// From the destination work in reverse towards the source calculating how much must be asked for. +// Then work forward, figuring out how much can actually be delivered. +// <-- terResult: tesSUCCESS or tepPATH_DRY +// <-> pnNodes: +// --> [end]saWanted.mAmount +// --> [all]saWanted.mCurrency +// --> [all]saAccount +// <-> [0]saWanted.mAmount : --> limit, <-- actual +TER RippleCalc::calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); + TER terResult; + + // Do current node reverse. + const uint160& uCurIssuerID = pnCur.uIssuerID; + STAmount& saTransferRate = pnCur.saTransferRate; + + saTransferRate = STAmount::saFromRate(lesActive.rippleTransferRate(uCurIssuerID)); + + Log(lsINFO) << boost::str(boost::format("calcNodeRev> uIndex=%d uIssuerID=%s saTransferRate=%s") + % uIndex + % NewcoinAddress::createHumanAccountID(uCurIssuerID) + % saTransferRate.getFullText()); + + terResult = bCurAccount + ? calcNodeAccountRev(uIndex, pspCur, bMultiQuality) + : calcNodeOfferRev(uIndex, pspCur, bMultiQuality); + + // Do previous. + if (tesSUCCESS != terResult) + { + // Error, don't continue. + nothing(); + } + else if (uIndex) + { + // Continue in reverse. + + terResult = calcNodeRev(uIndex-1, pspCur, bMultiQuality); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeRev< uIndex=%d terResult=%s/%d") % uIndex % transToken(terResult) % terResult); + + return terResult; +} + +// Calculate the next increment of a path. +// The increment is what can satisfy a portion or all of the requested output at the best quality. +// <-- pspCur->uQuality +void RippleCalc::pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) +{ + // The next state is what is available in preference order. + // This is calculated when referenced accounts changed. + const bool bMultiQuality = iPaths == 1; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + Log(lsINFO) << "Path In: " << pspCur->getJson(); + + assert(pspCur->vpnNodes.size() >= 2); + + pspCur->vUnfundedBecame.clear(); + pspCur->umReverse.clear(); + + lesCurrent = lesCheckpoint; // Restore from checkpoint. + lesCurrent.bumpSeq(); // Begin ledger varance. + + pspCur->terStatus = calcNodeRev(uLast, pspCur, bMultiQuality); + + Log(lsINFO) << "Path after reverse: " << pspCur->getJson(); + + if (tesSUCCESS == pspCur->terStatus) + { + // Do forward. + lesCurrent = lesCheckpoint; // Restore from checkpoint. + lesCurrent.bumpSeq(); // Begin ledger varance. + + pspCur->terStatus = calcNodeFwd(0, pspCur, bMultiQuality); + + pspCur->uQuality = tesSUCCESS == pspCur->terStatus + ? STAmount::getRate(pspCur->saOutAct, pspCur->saInAct) // Calculate relative quality. + : 0; // Mark path as inactive. + + Log(lsINFO) << "Path after forward: " << pspCur->getJson(); + } +} + +// XXX Stand alone calculation not implemented, does not calculate required input. +TER RippleCalc::rippleCalc( + LedgerEntrySet& lesActive, // <-> --> = Fee applied to src balance. + STAmount& saMaxAmountAct, // <-- The computed input amount. + STAmount& saDstAmountAct, // <-- The computed output amount. + const STAmount& saMaxAmountReq, // --> -1 = no limit. + const STAmount& saDstAmountReq, + const uint160& uDstAccountID, + const uint160& uSrcAccountID, + const STPathSet& spsPaths, + const bool bPartialPayment, + const bool bLimitQuality, + const bool bNoRippleDirect + ) +{ + RippleCalc rc(lesActive); + + TER terResult = temUNCERTAIN; + + // YYY Might do basic checks on src and dst validity as per doPayment. + + if (bNoRippleDirect && spsPaths.isEmpty()) + { + Log(lsINFO) << "doPayment: Invalid transaction: No paths and direct ripple not allowed."; + + return temRIPPLE_EMPTY; + } + + // Incrementally search paths. + std::vector vpsPaths; + + if (!bNoRippleDirect) + { + // Direct path. + // XXX Might also make a stamp bridge by default. + Log(lsINFO) << "doPayment: Build direct:"; + + PathState::pointer pspDirect = PathState::createPathState( + vpsPaths.size(), + lesActive, + STPath(), + uDstAccountID, + uSrcAccountID, + saDstAmountReq, + saMaxAmountReq); + + if (pspDirect) + { + // Return if malformed. + if (pspDirect->terStatus >= temMALFORMED && pspDirect->terStatus < tefFAILURE) + return pspDirect->terStatus; + + if (tesSUCCESS == pspDirect->terStatus) + { + // Had a success. + terResult = tesSUCCESS; + + vpsPaths.push_back(pspDirect); + } + } + } + + Log(lsINFO) << "doPayment: Paths in set: " << spsPaths.getPathCount(); + + BOOST_FOREACH(const STPath& spPath, spsPaths) + { + Log(lsINFO) << "doPayment: Build path:"; + + PathState::pointer pspExpanded = PathState::createPathState( + vpsPaths.size(), + lesActive, + spPath, + uDstAccountID, + uSrcAccountID, + saDstAmountReq, + saMaxAmountReq); + + if (pspExpanded) + { + // Return if malformed. + if (pspExpanded->terStatus >= temMALFORMED && pspExpanded->terStatus < tefFAILURE) + return pspExpanded->terStatus; + + if (tesSUCCESS == pspExpanded->terStatus) + { + // Had a success. + terResult = tesSUCCESS; + } + + vpsPaths.push_back(pspExpanded); + } + } + + if (vpsPaths.empty()) + { + return tefEXCEPTION; + } + else if (tesSUCCESS != terResult) + { + // No path successes. + + return vpsPaths[0]->terStatus; + } + else + { + terResult = temUNCERTAIN; + } + + STAmount saPaid; + STAmount saWanted; + const LedgerEntrySet lesBase = lesActive; // Checkpoint with just fees paid. + const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmountReq, saMaxAmountReq) : 0; + // When processing, don't want to complicate directory walking with deletion. + std::vector vuUnfundedBecame; // Offers that became unfunded. + + while (temUNCERTAIN == terResult) + { + PathState::pointer pspBest; + const LedgerEntrySet lesCheckpoint = lesActive; + + // Find the best path. + BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths) + { + rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment. + + if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality. + || !pspBest // Best is not yet set. + || (pspCur->uQuality && PathState::lessPriority(pspBest, pspCur))) // Current is better than set. + { + lesActive.swapWith(pspCur->lesEntries); // For the path, save ledger state. + pspBest = pspCur; + } + } + + if (pspBest) + { + // Apply best path. + + // Record best pass' offers that became unfunded for deletion on success. + vuUnfundedBecame.insert(vuUnfundedBecame.end(), pspBest->vUnfundedBecame.begin(), pspBest->vUnfundedBecame.end()); + + // Record best pass' LedgerEntrySet to build off of and potentially return. + lesActive.swapWith(pspBest->lesEntries); + + // Figure out if done. + if (temUNCERTAIN == terResult && saPaid == saWanted) + { + terResult = tesSUCCESS; + } + else + { + // Prepare for next pass. + + // Merge best pass' umReverse. + rc.mumSource.insert(pspBest->umReverse.begin(), pspBest->umReverse.end()); + } + } + // Not done and ran out of paths. + else if (!bPartialPayment) + { + // Partial payment not allowed. + terResult = tepPATH_PARTIAL; + lesActive = lesBase; // Revert to just fees charged. + } + // Partial payment ok. + else if (!saPaid) + { + // No payment at all. + terResult = tepPATH_DRY; + lesActive = lesBase; // Revert to just fees charged. + } + else + { + terResult = tesSUCCESS; + } + } + + if (tesSUCCESS == terResult) + { + // Delete became unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, vuUnfundedBecame) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + } + + // Delete found unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, rc.musUnfundedFound) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + + return terResult; +} + +#if 0 +// XXX Need to adjust for fees. +// Find offers to satisfy pnDst. +// - Does not adjust any balances as there is at least a forward pass to come. +// --> pnDst.saWanted: currency and amount wanted +// --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use. +// --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, limit to use. +// <-- pnDst.saReceive +// <-- pnDst.saIOUForgive +// <-- pnDst.saIOUAccept +// <-- terResult : tesSUCCESS = no error and if !bAllowPartial complelely satisfied wanted. +// <-> usOffersDeleteAlways: +// <-> usOffersDeleteOnSuccess: +TER calcOfferFill(PaymentNode& pnSrc, PaymentNode& pnDst, bool bAllowPartial) +{ + TER terResult; + + if (pnDst.saWanted.isNative()) + { + // Transfer stamps. + + STAmount saSrcFunds = pnSrc.saAccount->accountHolds(pnSrc.saAccount, uint160(0), uint160(0)); + + if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted)) + { + pnSrc.saSend = min(saSrcFunds, pnDst.saWanted); + pnDst.saReceive = pnSrc.saSend; + } + else + { + terResult = terINSUF_PATH; + } + } + else + { + // Ripple funds. + + // Redeem to limit. + terResult = calcOfferFill( + accountHolds(pnSrc.saAccount, pnDst.saWanted.getCurrency(), pnDst.saWanted.getIssuer()), + pnSrc.saIOURedeem, + pnDst.saIOUForgive, + bAllowPartial); + + if (tesSUCCESS == terResult) + { + // Issue to wanted. + terResult = calcOfferFill( + pnDst.saWanted, // As much as wanted is available, limited by credit limit. + pnSrc.saIOUIssue, + pnDst.saIOUAccept, + bAllowPartial); + } + + if (tesSUCCESS == terResult && !bAllowPartial) + { + STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept; + + if (saTotal != saWanted) + terResult = terINSUF_PATH; + } + } + + return terResult; +} +#endif + +#if 0 +// Get the next offer limited by funding. +// - Stop when becomes unfunded. +void TransactionEngine::calcOfferBridgeNext( + const uint256& uBookRoot, // --> Which order book to look in. + const uint256& uBookEnd, // --> Limit of how far to look. + uint256& uBookDirIndex, // <-> Current directory. <-- 0 = no offer available. + uint64& uBookDirNode, // <-> Which node. 0 = first. + unsigned int& uBookDirEntry, // <-> Entry in node. 0 = first. + STAmount& saOfferIn, // <-- How much to pay in, fee inclusive, to get saOfferOut out. + STAmount& saOfferOut // <-- How much offer pays out. + ) +{ + saOfferIn = 0; // XXX currency & issuer + saOfferOut = 0; // XXX currency & issuer + + bool bDone = false; + + while (!bDone) + { + uint256 uOfferIndex; + + // Get uOfferIndex. + mNodes.dirNext(uBookRoot, uBookEnd, uBookDirIndex, uBookDirNode, uBookDirEntry, uOfferIndex); + + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); + STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); + + if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcOfferFirst: encountered expired offer"; + } + else + { + STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); + // Outbound fees are paid by offer owner. + // XXX Calculate outbound fee rate. + + if (saOfferPays.isNative()) + { + // No additional fees for stamps. + + nothing(); + } + else if (saOfferPays.getIssuer() == uOfferOwnerID) + { + // Offerer is issue own IOUs. + // No fees at this exact point, XXX receiving node may charge a fee. + // XXX Make sure has a credit line with receiver, limit by credit line. + + nothing(); + // XXX Broken - could be issuing or redeeming or both. + } + else + { + // Offer must be redeeming IOUs. + + // No additional + // XXX Broken + } + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded. + Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; + } + else if (saOfferFunds >= saOfferPays) + { + // Offer fully funded. + + // Account transfering funds in to offer always pays inbound fees. + + saOfferIn = saOfferGets; // XXX Add in fees? + + saOfferOut = saOfferPays; + + bDone = true; + } + else + { + // Offer partially funded. + + // saOfferIn/saOfferFunds = saOfferGets/saOfferPays + // XXX Round such that all saOffer funds are exhausted. + saOfferIn = (saOfferFunds*saOfferGets)/saOfferPays; // XXX Add in fees? + saOfferOut = saOfferFunds; + + bDone = true; + } + } + + if (!bDone) + { + // musUnfundedFound.insert(uOfferIndex); + } + } + while (bNext); +} +#endif + +#if 0 +// If either currency is not stamps, then also calculates vs stamp bridge. +// --> saWanted: Limit of how much is wanted out. +// <-- saPay: How much to pay into the offer. +// <-- saGot: How much to the offer pays out. Never more than saWanted. +// Given two value's enforce a minimum: +// - reverse: prv is maximum to pay in (including fee) - cur is what is wanted: generally, minimizing prv +// - forward: prv is actual amount to pay in (including fee) - cur is what is wanted: generally, minimizing cur +// Value in is may be rippled or credited from limbo. Value out is put in limbo. +// If next is an offer, the amount needed is in cur reedem. +// XXX What about account mentioned multiple times via offers? +void TransactionEngine::calcNodeOffer( + bool bForward, + bool bMultiQuality, // True, if this is the only active path: we can do multiple qualities in this pass. + const uint160& uPrvAccountID, // If 0, then funds from previous offer's limbo + const uint160& uPrvCurrencyID, + const uint160& uPrvIssuerID, + const uint160& uCurCurrencyID, + const uint160& uCurIssuerID, + + const STAmount& uPrvRedeemReq, // --> In limit. + STAmount& uPrvRedeemAct, // <-> In limit achived. + const STAmount& uCurRedeemReq, // --> Out limit. Driver when uCurIssuerID == uNxtIssuerID (offer would redeem to next) + STAmount& uCurRedeemAct, // <-> Out limit achived. + + const STAmount& uCurIssueReq, // --> In limit. + STAmount& uCurIssueAct, // <-> In limit achived. + const STAmount& uCurIssueReq, // --> Out limit. Driver when uCurIssueReq != uNxtIssuerID (offer would effectively issue or transfer to next) + STAmount& uCurIssueAct, // <-> Out limit achived. + + STAmount& saPay, + STAmount& saGot + ) const +{ + TER terResult = temUNKNOWN; + + // Direct: not bridging via XNS + bool bDirectNext = true; // True, if need to load. + uint256 uDirectQuality; + uint256 uDirectTip = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); + uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); + + // Bridging: bridging via XNS + bool bBridge = true; // True, if bridging active. False, missing an offer. + uint256 uBridgeQuality; + STAmount saBridgeIn; // Amount available. + STAmount saBridgeOut; + + bool bInNext = true; // True, if need to load. + STAmount saInIn; // Amount available. Consumed in loop. Limited by offer funding. + STAmount saInOut; + uint256 uInTip; // Current entry. + uint256 uInEnd; + unsigned int uInEntry; + + bool bOutNext = true; + STAmount saOutIn; + STAmount saOutOut; + uint256 uOutTip; + uint256 uOutEnd; + unsigned int uOutEntry; + + saPay.zero(); + saPay.setCurrency(uPrvCurrencyID); + saPay.setIssuer(uPrvIssuerID); + + saNeed = saWanted; + + if (!uCurCurrencyID && !uPrvCurrencyID) + { + // Bridging: Neither currency is XNS. + uInTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, CURRENCY_XNS, ACCOUNT_XNS); + uInEnd = Ledger::getQualityNext(uInTip); + uOutTip = Ledger::getBookBase(CURRENCY_XNS, ACCOUNT_XNS, uCurCurrencyID, uCurIssuerID); + uOutEnd = Ledger::getQualityNext(uInTip); + } + + // Find our head offer. + + bool bRedeeming = false; + bool bIssuing = false; + + // The price varies as we change between issuing and transfering, so unless bMultiQuality, we must stick with a mode once it + // is determined. + + if (bBridge && (bInNext || bOutNext)) + { + // Bridging and need to calculate next bridge rate. + // A bridge can consist of multiple offers. As offer's are consumed, the effective rate changes. + + if (bInNext) + { +// sleInDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uInIndex, uInEnd)); + // Get the next funded offer. + offerBridgeNext(uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding. + bInNext = false; + } + + if (bOutNext) + { +// sleOutDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uOutIndex, uOutEnd)); + offerNext(uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut); + bOutNext = false; + } + + if (!uInIndex || !uOutIndex) + { + bBridge = false; // No more offers to bridge. + } + else + { + // Have bridge in and out entries. + // Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost. + + saBridgeOut.zero(); + + if (saInOut < saOutIn) + { + // Limit by in. + + // XXX Need to include fees in saBridgeIn. + saBridgeIn = saInIn; // All of in + // Limit bridge out: saInOut/saBridgeOut = saOutIn/saOutOut + // Round such that we would take all of in offer, otherwise would have leftovers. + saBridgeOut = (saInOut * saOutOut) / saOutIn; + } + else if (saInOut > saOutIn) + { + // Limit by out, if at all. + + // XXX Need to include fees in saBridgeIn. + // Limit bridge in:saInIn/saInOuts = aBridgeIn/saOutIn + // Round such that would take all of out offer. + saBridgeIn = (saInIn * saOutIn) / saInOuts; + saBridgeOut = saOutOut; // All of out. + } + else + { + // Entries match, + + // XXX Need to include fees in saBridgeIn. + saBridgeIn = saInIn; // All of in + saBridgeOut = saOutOut; // All of out. + } + + uBridgeQuality = STAmount::getRate(saBridgeIn, saBridgeOut); // Inclusive of fees. + } + } + + if (bBridge) + { + bUseBridge = !uDirectTip || (uBridgeQuality < uDirectQuality) + } + else if (!!uDirectTip) + { + bUseBridge = false + } + else + { + // No more offers. Declare success, even if none returned. + saGot = saWanted-saNeed; + terResult = tesSUCCESS; + } + + if (tesSUCCESS != terResult) + { + STAmount& saAvailIn = bUseBridge ? saBridgeIn : saDirectIn; + STAmount& saAvailOut = bUseBridge ? saBridgeOut : saDirectOut; + + if (saAvailOut > saNeed) + { + // Consume part of offer. Done. + + saNeed = 0; + saPay += (saNeed*saAvailIn)/saAvailOut; // Round up, prefer to pay more. + } + else + { + // Consume entire offer. + + saNeed -= saAvailOut; + saPay += saAvailIn; + + if (bUseBridge) + { + // Consume bridge out. + if (saOutOut == saAvailOut) + { + // Consume all. + saOutOut = 0; + saOutIn = 0; + bOutNext = true; + } + else + { + // Consume portion of bridge out, must be consuming all of bridge in. + // saOutIn/saOutOut = saSpent/saAvailOut + // Round? + saOutIn -= (saOutIn*saAvailOut)/saOutOut; + saOutOut -= saAvailOut; + } + + // Consume bridge in. + if (saOutIn == saAvailIn) + { + // Consume all. + saInOut = 0; + saInIn = 0; + bInNext = true; + } + else + { + // Consume portion of bridge in, must be consuming all of bridge out. + // saInIn/saInOut = saAvailIn/saPay + // Round? + saInOut -= (saInOut*saAvailIn)/saInIn; + saInIn -= saAvailIn; + } + } + else + { + bDirectNext = true; + } + } + } +} +#endif + +// vim:ts=4 diff --git a/src/RippleCalc.h b/src/RippleCalc.h new file mode 100644 index 0000000000..673393b5d5 --- /dev/null +++ b/src/RippleCalc.h @@ -0,0 +1,190 @@ +#ifndef __RIPPLE_CALC__ +#define __RIPPLE_CALC__ + +#include + +#include "LedgerEntrySet.h" + +class PaymentNode { +protected: + friend class RippleCalc; + friend class PathState; + + uint16 uFlags; // --> From path. + + uint160 uAccountID; // --> Accounts: Recieving/sending account. + uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send. + // --- For offer's next has currency out. + uint160 uIssuerID; // --> Currency's issuer + + STAmount saTransferRate; // Transfer rate for uIssuerID. + + // Computed by Reverse. + STAmount saRevRedeem; // <-- Amount to redeem to next. + STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. + // Issue isn't used by offers. + STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee. + + // Computed by forward. + STAmount saFwdRedeem; // <-- Amount node will redeem to next. + STAmount saFwdIssue; // <-- Amount node will issue to next. + // Issue isn't used by offers. + STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee. + + // For offers: + + STAmount saRateMax; // XXX Should rate be sticky for forward too? + + // Directory + uint256 uDirectTip; // Current directory. + uint256 uDirectEnd; // Next order book. + bool bDirectAdvance; // Need to advance directory. + SLE::pointer sleDirectDir; + STAmount saOfrRate; // For correct ratio. + + // Node + bool bEntryAdvance; // Need to advance entry. + unsigned int uEntry; + uint256 uOfferIndex; + SLE::pointer sleOffer; + uint160 uOfrOwnerID; + bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets. + STAmount saOfferFunds; + STAmount saTakerPays; + STAmount saTakerGets; +}; + +// account id, currency id, issuer id :: node +typedef boost::tuple aciSource; +typedef boost::unordered_map curIssuerNode; // Map of currency, issuer to node index. +typedef boost::unordered_map::const_iterator curIssuerNodeConstIterator; + +extern std::size_t hash_value(const aciSource& asValue); + +// Holds a path state under incremental application. +class PathState +{ +protected: + const Ledger::pointer& mLedger; + + TER pushNode(const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + TER pushImply(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + +public: + typedef boost::shared_ptr pointer; + + TER terStatus; + std::vector vpnNodes; + + // When processing, don't want to complicate directory walking with deletion. + std::vector vUnfundedBecame; // Offers that became unfunded or were completely consumed. + + // First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be + // used there. + curIssuerNode umForward; // Map of currency, issuer to node index. + + // First time working in reverse a funding source was used. + // Source may only be used there if not mentioned by an account. + curIssuerNode umReverse; // Map of currency, issuer to node index. + + LedgerEntrySet lesEntries; + + int mIndex; + uint64 uQuality; // 0 = none. + STAmount saInReq; // Max amount to spend by sender + STAmount saInAct; // Amount spent by sender (calc output) + STAmount saOutReq; // Amount to send (calc input) + STAmount saOutAct; // Amount actually sent (calc output). + + PathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ); + + Json::Value getJson() const; + + static PathState::pointer createPathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ) + { + return boost::make_shared(iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax); + } + + static bool lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs); +}; + +class RippleCalc +{ +protected: + LedgerEntrySet& lesActive; + +public: + // First time working in reverse a funding source was mentioned. Source may only be used there. + curIssuerNode mumSource; // Map of currency, issuer to node index. + + // If the transaction fails to meet some constraint, still need to delete unfunded offers. + boost::unordered_set musUnfundedFound; // Offers that were found unfunded. + + PathState::pointer pathCreate(const STPath& spPath); + void pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent); + TER calcNode(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeOfferRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeOfferFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAccountFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAdvance(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality, const bool bReverse); + TER calcNodeDeliverRev( + const unsigned int uIndex, + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uOutAccountID, + const STAmount& saOutReq, + STAmount& saOutAct); + + TER calcNodeDeliverFwd( + const unsigned int uIndex, + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uInAccountID, + const STAmount& saInFunds, + const STAmount& saInReq, + STAmount& saInAct, + STAmount& saInFees); + + void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, + const STAmount& saPrvReq, const STAmount& saCurReq, + STAmount& saPrvAct, STAmount& saCurAct, + uint64& uRateMax); + + RippleCalc(LedgerEntrySet& lesNodes) : lesActive(lesNodes) { ; } + + static TER rippleCalc( + LedgerEntrySet& lesActive, + STAmount& saMaxAmountAct, + STAmount& saDstAmountAct, + const STAmount& saDstAmountReq, + const STAmount& saMaxAmountReq, + const uint160& uDstAccountID, + const uint160& uSrcAccountID, + const STPathSet& spsPaths, + const bool bPartialPayment, + const bool bLimitQuality, + const bool bNoRippleDirect + ); +}; + +#endif +// vim:ts=4 diff --git a/src/SHAMap.cpp b/src/SHAMap.cpp index 8ae69c405e..652326f177 100644 --- a/src/SHAMap.cpp +++ b/src/SHAMap.cpp @@ -39,14 +39,14 @@ std::size_t hash_value(const uint160& u) } -SHAMap::SHAMap(uint32 seq) : mSeq(seq), mState(Modifying) +SHAMap::SHAMap(uint32 seq) : mSeq(seq), mState(smsModifying) { root = boost::make_shared(mSeq, SHAMapNode(0, uint256())); root->makeInner(); mTNByID[*root] = root; } -SHAMap::SHAMap(const uint256& hash) : mSeq(0), mState(Synching) +SHAMap::SHAMap(const uint256& hash) : mSeq(0), mState(smsSynching) { // FIXME: Need to acquire root node root = boost::make_shared(mSeq, SHAMapNode(0, uint256())); root->makeInner(); @@ -62,7 +62,7 @@ SHAMap::pointer SHAMap::snapShot(bool isMutable) newMap.mTNByID = mTNByID; newMap.root = root; if (!isMutable) - newMap.mState = Immutable; + newMap.mState = smsImmutable; return ret; } @@ -105,7 +105,7 @@ void SHAMap::dirtyUp(std::stack& stack, const uint256& { // walk the tree up from through the inner nodes to the root // update linking hashes and add nodes to dirty list - assert((mState != Synching) && (mState != Immutable)); + assert((mState != smsSynching) && (mState != smsImmutable)); while (!stack.empty()) { @@ -238,7 +238,7 @@ SHAMapItem::SHAMapItem(const uint256& tag, const Serializer& data) : mTag(tag), mData(data.peekData()) { ; } -SHAMapItem::pointer SHAMap::firstBelow(SHAMapTreeNode* node) +SHAMapTreeNode* SHAMap::firstBelow(SHAMapTreeNode* node) { // Return the first item below this node #ifdef ST_DEBUG @@ -246,7 +246,7 @@ SHAMapItem::pointer SHAMap::firstBelow(SHAMapTreeNode* node) #endif do { // Walk down the tree - if (node->hasItem()) return node->peekItem(); + if (node->hasItem()) return node; bool foundNode = false; for (int i = 0; i < 16; ++i) @@ -261,11 +261,12 @@ SHAMapItem::pointer SHAMap::firstBelow(SHAMapTreeNode* node) foundNode = true; break; } - if (!foundNode) return SHAMapItem::pointer(); + if (!foundNode) + return NULL; } while (true); } -SHAMapItem::pointer SHAMap::lastBelow(SHAMapTreeNode* node) +SHAMapTreeNode* SHAMap::lastBelow(SHAMapTreeNode* node) { #ifdef DEBUG std::cerr << "lastBelow(" << *node << ")" << std::endl; @@ -273,7 +274,8 @@ SHAMapItem::pointer SHAMap::lastBelow(SHAMapTreeNode* node) do { // Walk down the tree - if (node->hasItem()) return node->peekItem(); + if (node->hasItem()) + return node; bool foundNode = false; for (int i = 15; i >= 0; ++i) @@ -283,7 +285,8 @@ SHAMapItem::pointer SHAMap::lastBelow(SHAMapTreeNode* node) foundNode = true; break; } - if (!foundNode) return SHAMapItem::pointer(); + if (!foundNode) + return NULL; } while (true); } @@ -345,16 +348,39 @@ void SHAMap::eraseChildren(SHAMapTreeNode::pointer node) SHAMapItem::pointer SHAMap::peekFirstItem() { boost::recursive_mutex::scoped_lock sl(mLock); - return firstBelow(root.get()); + SHAMapTreeNode *node = firstBelow(root.get()); + if (!node) + return SHAMapItem::pointer(); + return node->peekItem(); +} + +SHAMapItem::pointer SHAMap::peekFirstItem(SHAMapTreeNode::TNType& type) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + SHAMapTreeNode *node = firstBelow(root.get()); + if (!node) + return SHAMapItem::pointer(); + type = node->getType(); + return node->peekItem(); } SHAMapItem::pointer SHAMap::peekLastItem() { boost::recursive_mutex::scoped_lock sl(mLock); - return lastBelow(root.get()); + SHAMapTreeNode *node = lastBelow(root.get()); + if (!node) + return SHAMapItem::pointer(); + return node->peekItem(); } SHAMapItem::pointer SHAMap::peekNextItem(const uint256& id) +{ + SHAMapTreeNode::TNType type; + return peekNextItem(id, type); +} + + +SHAMapItem::pointer SHAMap::peekNextItem(const uint256& id, SHAMapTreeNode::TNType& type) { // Get a pointer to the next item in the tree after a given item - item must be in tree boost::recursive_mutex::scoped_lock sl(mLock); @@ -367,17 +393,24 @@ SHAMapItem::pointer SHAMap::peekNextItem(const uint256& id) if (node->isLeaf()) { if (node->peekItem()->getTag() > id) - return node->peekItem(); - } - else for (int i = node->selectBranch(id) + 1; i < 16; ++i) - if (!node->isEmptyBranch(i)) { - node = getNode(node->getChildNodeID(i), node->getChildHash(i), false); - SHAMapItem::pointer item = firstBelow(node.get()); - if (!item) - throw std::runtime_error("missing node"); - return item; + type = node->getType(); + return node->peekItem(); } + } + else + for (int i = node->selectBranch(id) + 1; i < 16; ++i) + if (!node->isEmptyBranch(i)) + { + SHAMapTreeNode *firstNode = getNodePointer(node->getChildNodeID(i), node->getChildHash(i)); + if (!firstNode) + throw std::runtime_error("missing node"); + firstNode = firstBelow(firstNode); + if (!firstNode) + throw std::runtime_error("missing node"); + type = firstNode->getType(); + return firstNode->peekItem(); + } } // must be last item return SHAMapItem::pointer(); @@ -402,10 +435,10 @@ SHAMapItem::pointer SHAMap::peekPrevItem(const uint256& id) if (!node->isEmptyBranch(i)) { node = getNode(node->getChildNodeID(i), node->getChildHash(i), false); - SHAMapItem::pointer item = firstBelow(node.get()); + SHAMapTreeNode* item = firstBelow(node.get()); if (!item) throw std::runtime_error("missing node"); - return item; + return item->peekItem(); } } // must be last item @@ -421,6 +454,16 @@ SHAMapItem::pointer SHAMap::peekItem(const uint256& id) return leaf->peekItem(); } +SHAMapItem::pointer SHAMap::peekItem(const uint256& id, SHAMapTreeNode::TNType& type) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + SHAMapTreeNode* leaf = walkToPointer(id); + if (!leaf) + return SHAMapItem::pointer(); + type = leaf->getType(); + return leaf->peekItem(); +} + bool SHAMap::hasItem(const uint256& id) { // does the tree have an item with this ID boost::recursive_mutex::scoped_lock sl(mLock); @@ -432,7 +475,7 @@ bool SHAMap::hasItem(const uint256& id) bool SHAMap::delItem(const uint256& id) { // delete the item with this ID boost::recursive_mutex::scoped_lock sl(mLock); - assert(mState != Immutable); + assert(mState != smsImmutable); std::stack stack = getStack(id, true, false); if (stack.empty()) @@ -509,7 +552,7 @@ bool SHAMap::addGiveItem(const SHAMapItem::pointer& item, bool isTransaction, bo (hasMeta ? SHAMapTreeNode::tnTRANSACTION_MD : SHAMapTreeNode::tnTRANSACTION_NM); boost::recursive_mutex::scoped_lock sl(mLock); - assert(mState != Immutable); + assert(mState != smsImmutable); std::stack stack = getStack(tag, true, false); if (stack.empty()) @@ -601,7 +644,7 @@ bool SHAMap::updateGiveItem(const SHAMapItem::pointer& item, bool isTransaction, uint256 tag = item->getTag(); boost::recursive_mutex::scoped_lock sl(mLock); - assert(mState != Immutable); + assert(mState != smsImmutable); std::stack stack = getStack(tag, true, false); if (stack.empty()) throw std::runtime_error("missing node"); diff --git a/src/SHAMap.h b/src/SHAMap.h index b84dcb7272..e7f5046d5c 100644 --- a/src/SHAMap.h +++ b/src/SHAMap.h @@ -24,7 +24,8 @@ class SHAMap; class SHAMapNode { // Identifies a node in a SHA256 hash public: - typedef boost::shared_ptr pointer; + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; private: static uint256 smMasks[65]; // AND with hash to get node id @@ -216,11 +217,11 @@ public: enum SHAMapState { - Modifying = 0, // Objects can be added and removed (like an open ledger) - Immutable = 1, // Map cannot be changed (like a closed ledger) - Synching = 2, // Map's hash is locked in, valid nodes can be added (like a peer's closing ledger) - Floating = 3, // Map is free to change hash (like a synching open ledger) - Invalid = 4, // Map is known not to be valid (usually synching a corrupt ledger) + smsModifying = 0, // Objects can be added and removed (like an open ledger) + smsImmutable = 1, // Map cannot be changed (like a closed ledger) + smsSynching = 2, // Map's hash is locked in, valid nodes can be added (like a peer's closing ledger) + smsFloating = 3, // Map is free to change hash (like a synching open ledger) + smsInvalid = 4, // Map is known not to be valid (usually synching a corrupt ledger) }; class SHAMapSyncFilter @@ -228,9 +229,11 @@ class SHAMapSyncFilter public: SHAMapSyncFilter() { ; } virtual ~SHAMapSyncFilter() { ; } + virtual void gotNode(const SHAMapNode& id, const uint256& nodeHash, const std::vector& nodeData, bool isLeaf) { ; } + virtual bool haveNode(const SHAMapNode& id, const uint256& nodeHash, std::vector& nodeData) { return false; } }; @@ -247,6 +250,7 @@ public: { ; } virtual ~SHAMapMissingNode() throw() { ; } + const SHAMapNode& getNodeID() const { return mNodeID; } const uint256& getNodeHash() const { return mNodeHash; } }; @@ -255,6 +259,8 @@ class SHAMap { public: typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; + typedef std::map > SHAMapDiff; private: @@ -280,9 +286,9 @@ protected: SHAMapTreeNode::pointer getNode(const SHAMapNode& id); SHAMapTreeNode::pointer getNode(const SHAMapNode& id, const uint256& hash, bool modify); SHAMapTreeNode* getNodePointer(const SHAMapNode& id, const uint256& hash); + SHAMapTreeNode* firstBelow(SHAMapTreeNode*); + SHAMapTreeNode* lastBelow(SHAMapTreeNode*); - SHAMapItem::pointer firstBelow(SHAMapTreeNode*); - SHAMapItem::pointer lastBelow(SHAMapTreeNode*); SHAMapItem::pointer onlyBelow(SHAMapTreeNode*); void eraseChildren(SHAMapTreeNode::pointer); @@ -295,6 +301,8 @@ public: SHAMap(uint32 seq = 0); SHAMap(const uint256& hash); + ~SHAMap() { mState = smsInvalid; } + // Returns a new map that's a snapshot of this one. Force CoW SHAMap::pointer snapShot(bool isMutable); @@ -318,11 +326,14 @@ public: // save a copy if you only need a temporary SHAMapItem::pointer peekItem(const uint256& id); + SHAMapItem::pointer peekItem(const uint256& id, SHAMapTreeNode::TNType& type); // traverse functions SHAMapItem::pointer peekFirstItem(); + SHAMapItem::pointer peekFirstItem(SHAMapTreeNode::TNType& type); SHAMapItem::pointer peekLastItem(); SHAMapItem::pointer peekNextItem(const uint256&); + SHAMapItem::pointer peekNextItem(const uint256&, SHAMapTreeNode::TNType& type); SHAMapItem::pointer peekPrevItem(const uint256&); // comparison/sync functions @@ -337,17 +348,17 @@ public: SHAMapSyncFilter* filter); // status functions - void setImmutable(void) { assert(mState != Invalid); mState = Immutable; } - void clearImmutable(void) { mState = Modifying; } - bool isSynching(void) const { return (mState == Floating) || (mState == Synching); } - void setSynching(void) { mState = Synching; } - void setFloating(void) { mState = Floating; } - void clearSynching(void) { mState = Modifying; } - bool isValid(void) { return mState != Invalid; } + void setImmutable() { assert(mState != smsInvalid); mState = smsImmutable; } + void clearImmutable() { mState = smsModifying; } + bool isSynching() const { return (mState == smsFloating) || (mState == smsSynching); } + void setSynching() { mState = smsSynching; } + void setFloating() { mState = smsFloating; } + void clearSynching() { mState = smsModifying; } + bool isValid() { return mState != smsInvalid; } // caution: otherMap must be accessed only by this function // return value: true=successfully completed, false=too different - bool compare(const SHAMap::pointer& otherMap, SHAMapDiff& differences, int maxCount); + bool compare(SHAMap::ref otherMap, SHAMapDiff& differences, int maxCount); void armDirty(); int flushDirty(int maxNodes, HashedObjectType t, uint32 seq); diff --git a/src/SHAMapDiff.cpp b/src/SHAMapDiff.cpp index 4bef1f73d1..3f5adb3e0d 100644 --- a/src/SHAMapDiff.cpp +++ b/src/SHAMapDiff.cpp @@ -91,17 +91,22 @@ bool SHAMap::walkBranch(SHAMapTreeNode* node, SHAMapItem::pointer otherMapItem, return true; } -bool SHAMap::compare(const SHAMap::pointer& otherMap, SHAMapDiff& differences, int maxCount) +bool SHAMap::compare(SHAMap::ref otherMap, SHAMapDiff& differences, int maxCount) { // compare two hash trees, add up to maxCount differences to the difference table // return value: true=complete table of differences given, false=too many differences // throws on corrupt tables or missing nodes + // CAUTION: otherMap is not locked and must be immutable + + assert(isValid() && otherMap && otherMap->isValid()); std::stack nodeStack; // track nodes we've pushed - ScopedLock sl(Lock()); - if (getHash() == otherMap->getHash()) return true; - nodeStack.push(SHAMapDiffNode(SHAMapNode(), getHash(), otherMap->getHash())); + boost::recursive_mutex::scoped_lock sl(mLock); + if (getHash() == otherMap->getHash()) + return true; + + nodeStack.push(SHAMapDiffNode(SHAMapNode(), getHash(), otherMap->getHash())); while (!nodeStack.empty()) { SHAMapDiffNode dNode(nodeStack.top()); @@ -109,6 +114,11 @@ bool SHAMap::compare(const SHAMap::pointer& otherMap, SHAMapDiff& differences, i SHAMapTreeNode* ourNode = getNodePointer(dNode.mNodeID, dNode.mOurHash); SHAMapTreeNode* otherNode = otherMap->getNodePointer(dNode.mNodeID, dNode.mOtherHash); + if (!ourNode || !otherNode) + { + assert(false); + throw SHAMapMissingNode(dNode.mNodeID, uint256()); + } if (ourNode->isLeaf() && otherNode->isLeaf()) { // two leaves @@ -118,17 +128,20 @@ bool SHAMap::compare(const SHAMap::pointer& otherMap, SHAMapDiff& differences, i { differences.insert(std::make_pair(ourNode->getTag(), std::make_pair(ourNode->getItem(), otherNode->getItem()))); - if (--maxCount <= 0) return false; + if (--maxCount <= 0) + return false; } } else { differences.insert(std::make_pair(ourNode->getTag(), std::make_pair(ourNode->getItem(), SHAMapItem::pointer()))); - if (--maxCount <= 0) return false; + if (--maxCount <= 0) + return false; differences.insert(std::make_pair(otherNode->getTag(), std::make_pair(SHAMapItem::pointer(), otherNode->getItem()))); - if (--maxCount <= 0) return false; + if (--maxCount <= 0) + return false; } } else if (ourNode->isInner() && otherNode->isLeaf()) @@ -164,7 +177,8 @@ bool SHAMap::compare(const SHAMap::pointer& otherMap, SHAMapDiff& differences, i ourNode->getChildHash(i), otherNode->getChildHash(i))); } } - else assert(false); + else + assert(false); } return true; diff --git a/src/SHAMapNodes.cpp b/src/SHAMapNodes.cpp index 65514ad95f..746176aba3 100644 --- a/src/SHAMapNodes.cpp +++ b/src/SHAMapNodes.cpp @@ -296,8 +296,9 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector(txID, s.peekData()); mType = tnTRANSACTION_MD; } diff --git a/src/ScriptData.h b/src/ScriptData.h index af9ce45c0e..66fe2ed3e4 100644 --- a/src/ScriptData.h +++ b/src/ScriptData.h @@ -9,6 +9,8 @@ class Data public: typedef boost::shared_ptr pointer; + virtual ~Data(){ ; } + virtual bool isInt32(){ return(false); } virtual bool isFloat(){ return(false); } virtual bool isUint160(){ return(false); } diff --git a/src/SerializedLedger.cpp b/src/SerializedLedger.cpp index 5796952368..41c4bc7fb8 100644 --- a/src/SerializedLedger.cpp +++ b/src/SerializedLedger.cpp @@ -102,7 +102,7 @@ bool SerializedLedgerEntry::thread(const uint256& txID, uint32 ledgerSeq, uint25 if (oldPrevTxID == txID) return false; prevTxID = oldPrevTxID; - prevLedgerID = getIFieldU32(sfLastTxnID); + prevLedgerID = getIFieldU32(sfLastTxnSeq); assert(prevTxID != txID); setIFieldH256(sfLastTxnID, txID); setIFieldU32(sfLastTxnSeq, ledgerSeq); diff --git a/src/SerializedObject.cpp b/src/SerializedObject.cpp index 7600d803b7..af4210f3d1 100644 --- a/src/SerializedObject.cpp +++ b/src/SerializedObject.cpp @@ -6,6 +6,8 @@ #include "../json/writer.h" +#include "Log.h" + std::auto_ptr STObject::makeDefaultObject(SerializedTypeID id, const char *name) { switch(id) @@ -713,8 +715,7 @@ void STObject::unitTest() copy.setValueFieldU32(sfTest3, 1); if (object1.getSerializer() == copy.getSerializer()) throw std::runtime_error("STObject error"); #ifdef DEBUG - Json::StyledStreamWriter ssw; - ssw.write(std::cerr, copy.getJson(0)); + Log(lsDEBUG) << copy.getJson(0); #endif for (int i = 0; i < 1000; i++) diff --git a/src/SerializedObject.h b/src/SerializedObject.h index c8245d6d9d..08377304c1 100644 --- a/src/SerializedObject.h +++ b/src/SerializedObject.h @@ -106,7 +106,6 @@ enum SOE_Field sfTakerGets, sfTakerPays, sfTarget, - sfTargetLedger, sfTransferRate, sfVersion, sfWalletLocator, diff --git a/src/SerializedTransaction.h b/src/SerializedTransaction.h index 59f1650ca1..ee4ffb3ea4 100644 --- a/src/SerializedTransaction.h +++ b/src/SerializedTransaction.h @@ -28,7 +28,7 @@ protected: TransactionType mType; STVariableLength mSignature; STObject mMiddleTxn, mInnerTxn; - TransactionFormat* mFormat; + const TransactionFormat* mFormat; SerializedTransaction* duplicate() const { return new SerializedTransaction(*this); } diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index 4684345496..3bd54dc34a 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -9,6 +9,9 @@ #include "NewcoinAddress.h" #include "utils.h" +STAmount saZero(CURRENCY_ONE, ACCOUNT_ONE, 0); +STAmount saOne(CURRENCY_ONE, ACCOUNT_ONE, 1); + std::string SerializedType::getFullText() const { std::string ret; diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index 17e79af3c1..3a6417986e 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -395,6 +395,9 @@ public: Json::Value getJson(int) const; }; +extern STAmount saZero; +extern STAmount saOne; + class STHash128 : public SerializedType { protected: diff --git a/src/Transaction.h b/src/Transaction.h index 1ae8c0aa43..abe612853f 100644 --- a/src/Transaction.h +++ b/src/Transaction.h @@ -281,7 +281,6 @@ public: STAmount getAmount() const { return mTransaction->getITFieldU64(sfAmount); } STAmount getFee() const { return mTransaction->getTransactionFee(); } uint32 getFromAccountSeq() const { return mTransaction->getSequence(); } - uint32 getSourceLedger() const { return mTransaction->getITFieldU32(sfTargetLedger); } uint32 getIdent() const { return mTransaction->getITFieldU32(sfSourceTag); } std::vector getSignature() const { return mTransaction->getSignature(); } uint32 getLedger() const { return mInLedger; } diff --git a/src/TransactionAction.cpp b/src/TransactionAction.cpp new file mode 100644 index 0000000000..f60ae0af57 --- /dev/null +++ b/src/TransactionAction.cpp @@ -0,0 +1,1171 @@ +// +// XXX Make sure all fields are recognized in transactions. +// + +#include +#include +#include +#include + +#include "TransactionEngine.h" + +#include "../json/writer.h" + +#include "Config.h" +#include "Contract.h" +#include "Interpreter.h" +#include "Log.h" +#include "RippleCalc.h" +#include "TransactionFormats.h" +#include "utils.h" + +#define RIPPLE_PATHS_MAX 3 + +// Set the authorized public key for an account. May also set the generator map. +TER TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) +{ + // + // Verify that submitter knows the private key for the generator. + // Otherwise, people could deny access to generators. + // + + std::vector vucCipher = txn.getITFieldVL(sfGenerator); + std::vector vucPubKey = txn.getITFieldVL(sfPubKey); + std::vector vucSignature = txn.getITFieldVL(sfSignature); + NewcoinAddress naAccountPublic = NewcoinAddress::createAccountPublic(vucPubKey); + + if (!naAccountPublic.accountPublicVerify(Serializer::getSHA512Half(vucCipher), vucSignature)) + { + Log(lsWARNING) << "createGenerator: bad signature unauthorized generator claim"; + + return tefBAD_GEN_AUTH; + } + + // Create generator. + uint160 hGeneratorID = naAccountPublic.getAccountID(); + + SLE::pointer sleGen = entryCache(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); + if (!sleGen) + { + // Create the generator. + Log(lsTRACE) << "createGenerator: creating generator"; + + sleGen = entryCreate(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); + + sleGen->setIFieldVL(sfGenerator, vucCipher); + } + else if (bMustSetGenerator) + { + // Doing a claim. Must set generator. + // Generator is already in use. Regular passphrases limited to one wallet. + Log(lsWARNING) << "createGenerator: generator already in use"; + + return tefGEN_IN_USE; + } + + // Set the public key needed to use the account. + uint160 uAuthKeyID = bMustSetGenerator + ? hGeneratorID // Claim + : txn.getITFieldAccount(sfAuthorizedKey); // PasswordSet + + mTxnAccount->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); + + return tesSUCCESS; +} + +TER TransactionEngine::doAccountSet(const SerializedTransaction& txn) +{ + Log(lsINFO) << "doAccountSet>"; + + // + // EmailHash + // + + if (txn.getITFieldPresent(sfEmailHash)) + { + uint128 uHash = txn.getITFieldH128(sfEmailHash); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset email hash"; + + mTxnAccount->makeIFieldAbsent(sfEmailHash); + } + else + { + Log(lsINFO) << "doAccountSet: set email hash"; + + mTxnAccount->setIFieldH128(sfEmailHash, uHash); + } + } + + // + // WalletLocator + // + + if (txn.getITFieldPresent(sfWalletLocator)) + { + uint256 uHash = txn.getITFieldH256(sfWalletLocator); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset wallet locator"; + + mTxnAccount->makeIFieldAbsent(sfEmailHash); + } + else + { + Log(lsINFO) << "doAccountSet: set wallet locator"; + + mTxnAccount->setIFieldH256(sfWalletLocator, uHash); + } + } + + // + // MessageKey + // + + if (!txn.getITFieldPresent(sfMessageKey)) + { + nothing(); + } + else + { + Log(lsINFO) << "doAccountSet: set message key"; + + mTxnAccount->setIFieldVL(sfMessageKey, txn.getITFieldVL(sfMessageKey)); + } + + // + // Domain + // + + if (txn.getITFieldPresent(sfDomain)) + { + std::vector vucDomain = txn.getITFieldVL(sfDomain); + + if (vucDomain.empty()) + { + Log(lsINFO) << "doAccountSet: unset domain"; + + mTxnAccount->makeIFieldAbsent(sfDomain); + } + else + { + Log(lsINFO) << "doAccountSet: set domain"; + + mTxnAccount->setIFieldVL(sfDomain, vucDomain); + } + } + + // + // TransferRate + // + + if (txn.getITFieldPresent(sfTransferRate)) + { + uint32 uRate = txn.getITFieldU32(sfTransferRate); + + if (!uRate) + { + Log(lsINFO) << "doAccountSet: unset transfer rate"; + + mTxnAccount->makeIFieldAbsent(sfTransferRate); + } + else + { + Log(lsINFO) << "doAccountSet: set transfer rate"; + + mTxnAccount->setIFieldU32(sfTransferRate, uRate); + } + } + + // + // PublishHash && PublishSize + // + + bool bPublishHash = txn.getITFieldPresent(sfPublishHash); + bool bPublishSize = txn.getITFieldPresent(sfPublishSize); + + if (bPublishHash ^ bPublishSize) + { + Log(lsINFO) << "doAccountSet: bad publish"; + + return temBAD_PUBLISH; + } + else if (bPublishHash && bPublishSize) + { + uint256 uHash = txn.getITFieldH256(sfPublishHash); + uint32 uSize = txn.getITFieldU32(sfPublishSize); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset publish"; + + mTxnAccount->makeIFieldAbsent(sfPublishHash); + mTxnAccount->makeIFieldAbsent(sfPublishSize); + } + else + { + Log(lsINFO) << "doAccountSet: set publish"; + + mTxnAccount->setIFieldH256(sfPublishHash, uHash); + mTxnAccount->setIFieldU32(sfPublishSize, uSize); + } + } + + Log(lsINFO) << "doAccountSet<"; + + return tesSUCCESS; +} + +TER TransactionEngine::doClaim(const SerializedTransaction& txn) +{ + Log(lsINFO) << "doClaim>"; + + TER terResult = setAuthorized(txn, true); + + Log(lsINFO) << "doClaim<"; + + return terResult; +} + +TER TransactionEngine::doCreditSet(const SerializedTransaction& txn) +{ + TER terResult = tesSUCCESS; + Log(lsINFO) << "doCreditSet>"; + + // Check if destination makes sense. + uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); + + if (!uDstAccountID) + { + Log(lsINFO) << "doCreditSet: Invalid transaction: Destination account not specifed."; + + return temDST_NEEDED; + } + else if (mTxnAccountID == uDstAccountID) + { + Log(lsINFO) << "doCreditSet: Invalid transaction: Can not extend credit to self."; + + return temDST_IS_SRC; + } + + SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if (!sleDst) + { + Log(lsINFO) << "doCreditSet: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. + const bool bLimitAmount = txn.getITFieldPresent(sfLimitAmount); + const STAmount saLimitAmount = bLimitAmount ? txn.getITFieldAmount(sfLimitAmount) : STAmount(); + const bool bQualityIn = txn.getITFieldPresent(sfQualityIn); + const uint32 uQualityIn = bQualityIn ? txn.getITFieldU32(sfQualityIn) : 0; + const bool bQualityOut = txn.getITFieldPresent(sfQualityOut); + const uint32 uQualityOut = bQualityIn ? txn.getITFieldU32(sfQualityOut) : 0; + const uint160 uCurrencyID = saLimitAmount.getCurrency(); + bool bDelIndex = false; + + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); + if (sleRippleState) + { + // A line exists in one or more directions. +#if 0 + if (!saLimitAmount) + { + // Zeroing line. + uint160 uLowID = sleRippleState->getIValueFieldAccount(sfLowID).getAccountID(); + uint160 uHighID = sleRippleState->getIValueFieldAccount(sfHighID).getAccountID(); + bool bLow = uLowID == uSrcAccountID; + bool bHigh = uLowID == uDstAccountID; + bool bBalanceZero = !sleRippleState->getIValueFieldAmount(sfBalance); + STAmount saDstLimit = sleRippleState->getIValueFieldAmount(bSendLow ? sfLowLimit : sfHighLimit); + bool bDstLimitZero = !saDstLimit; + + assert(bLow || bHigh); + + if (bBalanceZero && bDstLimitZero) + { + // Zero balance and eliminating last limit. + + bDelIndex = true; + terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex(), false); + } + } +#endif + + if (!bDelIndex) + { + if (bLimitAmount) + sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit: sfLowLimit , saLimitAmount); + + if (!bQualityIn) + { + nothing(); + } + else if (uQualityIn) + { + sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + } + else + { + sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); + } + + if (!bQualityOut) + { + nothing(); + } + else if (uQualityOut) + { + sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); + } + else + { + sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); + } + + entryModify(sleRippleState); + } + + Log(lsINFO) << "doCreditSet: Modifying ripple line: bDelIndex=" << bDelIndex; + } + // Line does not exist. + else if (!saLimitAmount) + { + Log(lsINFO) << "doCreditSet: Redundant: Setting non-existant ripple line to 0."; + + return terNO_LINE_NO_ZERO; + } + else + { + // Create a new ripple line. + sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); + + Log(lsINFO) << "doCreditSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); + + sleRippleState->setIFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. + sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAmount); + sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, ACCOUNT_ONE)); + sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID); + sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID); + if (uQualityIn) + sleRippleState->setIFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); + if (uQualityOut) + sleRippleState->setIFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut); + + uint64 uSrcRef; // Ignored, dirs never delete. + + terResult = mNodes.dirAdd(uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex()); + + if (tesSUCCESS == terResult) + terResult = mNodes.dirAdd(uSrcRef, Ledger::getOwnerDirIndex(uDstAccountID), sleRippleState->getIndex()); + } + + Log(lsINFO) << "doCreditSet<"; + + return terResult; +} + +TER TransactionEngine::doNicknameSet(const SerializedTransaction& txn) +{ + std::cerr << "doNicknameSet>" << std::endl; + + const uint256 uNickname = txn.getITFieldH256(sfNickname); + const bool bMinOffer = txn.getITFieldPresent(sfMinimumOffer); + const STAmount saMinOffer = bMinOffer ? txn.getITFieldAmount(sfAmount) : STAmount(); + + SLE::pointer sleNickname = entryCache(ltNICKNAME, uNickname); + + if (sleNickname) + { + // Edit old entry. + sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); + + if (bMinOffer && saMinOffer) + { + sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); + } + else + { + sleNickname->makeIFieldAbsent(sfMinimumOffer); + } + + entryModify(sleNickname); + } + else + { + // Make a new entry. + // XXX Need to include authorization limiting for first year. + + sleNickname = entryCreate(ltNICKNAME, Ledger::getNicknameIndex(uNickname)); + + std::cerr << "doNicknameSet: Creating nickname node: " << sleNickname->getIndex().ToString() << std::endl; + + sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); + + if (bMinOffer && saMinOffer) + sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); + } + + std::cerr << "doNicknameSet<" << std::endl; + + return tesSUCCESS; +} + +TER TransactionEngine::doPasswordFund(const SerializedTransaction& txn) +{ + std::cerr << "doPasswordFund>" << std::endl; + + const uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); + SLE::pointer sleDst = mTxnAccountID == uDstAccountID + ? mTxnAccount + : entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if (!sleDst) + { + // Destination account does not exist. + std::cerr << "doPasswordFund: Delay transaction: Destination account does not exist." << std::endl; + + return terSET_MISSING_DST; + } + + if (sleDst->getFlags() & lsfPasswordSpent) + { + sleDst->clearFlag(lsfPasswordSpent); + + std::cerr << "doPasswordFund: Clearing spent." << sleDst->getFlags() << std::endl; + + if (mTxnAccountID != uDstAccountID) { + std::cerr << "doPasswordFund: Destination modified." << std::endl; + + entryModify(sleDst); + } + } + + std::cerr << "doPasswordFund<" << std::endl; + + return tesSUCCESS; +} + +TER TransactionEngine::doPasswordSet(const SerializedTransaction& txn) +{ + std::cerr << "doPasswordSet>" << std::endl; + + if (mTxnAccount->getFlags() & lsfPasswordSpent) + { + std::cerr << "doPasswordSet: Delay transaction: Funds already spent." << std::endl; + + return terFUNDS_SPENT; + } + + mTxnAccount->setFlag(lsfPasswordSpent); + + TER terResult = setAuthorized(txn, false); + + std::cerr << "doPasswordSet<" << std::endl; + + return terResult; +} + + +// XXX Need to audit for things like setting accountID not having memory. +TER TransactionEngine::doPayment(const SerializedTransaction& txn, const TransactionEngineParams params) +{ + // Ripple if source or destination is non-native or if there are paths. + const uint32 uTxFlags = txn.getFlags(); + const bool bCreate = isSetBit(uTxFlags, tfCreateAccount); + const bool bPartialPayment = isSetBit(uTxFlags, tfPartialPayment); + const bool bLimitQuality = isSetBit(uTxFlags, tfLimitQuality); + const bool bNoRippleDirect = isSetBit(uTxFlags, tfNoRippleDirect); + const bool bPaths = txn.getITFieldPresent(sfPaths); + const bool bMax = txn.getITFieldPresent(sfSendMax); + const uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); + const STAmount saDstAmount = txn.getITFieldAmount(sfAmount); + const STAmount saMaxAmount = bMax ? txn.getITFieldAmount(sfSendMax) : saDstAmount; + const uint160 uSrcCurrency = saMaxAmount.getCurrency(); + const uint160 uDstCurrency = saDstAmount.getCurrency(); + + Log(lsINFO) << boost::str(boost::format("doPayment> saMaxAmount=%s saDstAmount=%s") + % saMaxAmount.getFullText() + % saDstAmount.getFullText()); + + if (!uDstAccountID) + { + Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specifed."; + + return temDST_NEEDED; + } + else if (!saDstAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad amount: " << saDstAmount.getHumanCurrency() << " " << saDstAmount.getText(); + + return temBAD_AMOUNT; + } + else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + { + Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redunant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") + % mTxnAccountID.ToString() + % uDstAccountID.ToString() + % uSrcCurrency.ToString() + % uDstCurrency.ToString()); + + return temREDUNDANT; + } + else if (bMax + && ((saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency()) + || (saDstAmount.isNative() && saMaxAmount.isNative()))) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad SendMax."; + + return temINVALID; + } + + SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if (!sleDst) + { + // Destination account does not exist. + if (bCreate && !saDstAmount.isNative()) + { + // This restriction could be relaxed. + Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XNS."; + + return temCREATEXNS; + } + else if (!bCreate) + { + Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + // Create the account. + sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->setIFieldAccount(sfAccount, uDstAccountID); + sleDst->setIFieldU32(sfSequence, 1); + } + else + { + entryModify(sleDst); + } + + TER terResult; + // XXX Should bMax be sufficient to imply ripple? + const bool bRipple = bPaths || bMax || !saDstAmount.isNative(); + + if (bRipple) + { + // Ripple payment + + STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + + terResult = isSetBit(params, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX + ? telBAD_PATH_COUNT + : RippleCalc::rippleCalc( + mNodes, + saMaxAmountAct, + saDstAmountAct, + saMaxAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + bPartialPayment, + bLimitQuality, + bNoRippleDirect); + } + else + { + // Direct XNS payment. + + STAmount saSrcXNSBalance = mTxnAccount->getIValueFieldAmount(sfBalance); + + if (saSrcXNSBalance < saDstAmount) + { + // Transaction might succeed, if applied in a different order. + Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; + + terResult = terUNFUNDED; + } + else + { + mTxnAccount->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); + sleDst->setIFieldAmount(sfBalance, sleDst->getIValueFieldAmount(sfBalance) + saDstAmount); + + terResult = tesSUCCESS; + } + } + + std::string strToken; + std::string strHuman; + + if (transResultInfo(terResult, strToken, strHuman)) + { + Log(lsINFO) << boost::str(boost::format("doPayment: %s: %s") % strToken % strHuman); + } + else + { + assert(false); + } + + return terResult; +} + +TER TransactionEngine::doWalletAdd(const SerializedTransaction& txn) +{ + std::cerr << "WalletAdd>" << std::endl; + + const std::vector vucPubKey = txn.getITFieldVL(sfPubKey); + const std::vector vucSignature = txn.getITFieldVL(sfSignature); + const uint160 uAuthKeyID = txn.getITFieldAccount(sfAuthorizedKey); + const NewcoinAddress naMasterPubKey = NewcoinAddress::createAccountPublic(vucPubKey); + const uint160 uDstAccountID = naMasterPubKey.getAccountID(); + + if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature)) + { + std::cerr << "WalletAdd: unauthorized: bad signature " << std::endl; + + return tefBAD_ADD_AUTH; + } + + SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + if (sleDst) + { + std::cerr << "WalletAdd: account already created" << std::endl; + + return tefCREATED; + } + + STAmount saAmount = txn.getITFieldAmount(sfAmount); + STAmount saSrcBalance = mTxnAccount->getIValueFieldAmount(sfBalance); + + if (saSrcBalance < saAmount) + { + std::cerr + << boost::str(boost::format("WalletAdd: Delay transaction: insufficent balance: balance=%s amount=%s") + % saSrcBalance.getText() + % saAmount.getText()) + << std::endl; + + return terUNFUNDED; + } + + // Deduct initial balance from source account. + mTxnAccount->setIFieldAmount(sfBalance, saSrcBalance-saAmount); + + // Create the account. + sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->setIFieldAccount(sfAccount, uDstAccountID); + sleDst->setIFieldU32(sfSequence, 1); + sleDst->setIFieldAmount(sfBalance, saAmount); + sleDst->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); + + std::cerr << "WalletAdd<" << std::endl; + + return tesSUCCESS; +} + +TER TransactionEngine::doInvoice(const SerializedTransaction& txn) +{ + return temUNKNOWN; +} + +// Take as much as possible. Adjusts account balances. Charges fees on top to taker. +// --> uBookBase: The order book to take against. +// --> saTakerPays: What the taker offers (w/ issuer) +// --> saTakerGets: What the taker wanted (w/ issuer) +// <-- saTakerPaid: What taker paid not including fees. To reduce an offer. +// <-- saTakerGot: What taker got not including fees. To reduce an offer. +// <-- terResult: tesSUCCESS or terNO_ACCOUNT +// XXX: Fees should be paid by the source of the currency. +TER TransactionEngine::takeOffers( + bool bPassive, + const uint256& uBookBase, + const uint160& uTakerAccountID, + const SLE::pointer& sleTakerAccount, + const STAmount& saTakerPays, + const STAmount& saTakerGets, + STAmount& saTakerPaid, + STAmount& saTakerGot) +{ + assert(saTakerPays && saTakerGets); + + Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); + + uint256 uTipIndex = uBookBase; + const uint256 uBookEnd = Ledger::getQualityNext(uBookBase); + const uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays); + const uint160 uTakerPaysAccountID = saTakerPays.getIssuer(); + const uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); + TER terResult = temUNCERTAIN; + + boost::unordered_set usOfferUnfundedFound; // Offers found unfunded. + boost::unordered_set usOfferUnfundedBecame; // Offers that became unfunded. + boost::unordered_set usAccountTouched; // Accounts touched. + + saTakerPaid = 0; + saTakerGot = 0; + + while (temUNCERTAIN == terResult) + { + SLE::pointer sleOfferDir; + uint64 uTipQuality; + + // Figure out next offer to take, if needed. + if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) + { + // Taker has needs. + + sleOfferDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uTipIndex, uBookEnd)); + if (sleOfferDir) + { + Log(lsINFO) << "takeOffers: possible counter offer found"; + + uTipIndex = sleOfferDir->getIndex(); + uTipQuality = Ledger::getQuality(uTipIndex); + } + else + { + Log(lsINFO) << "takeOffers: counter offer book is empty: " + << uTipIndex.ToString() + << " ... " + << uBookEnd.ToString(); + } + } + + if (!sleOfferDir // No offer directory to take. + || uTakeQuality < uTipQuality // No offer's of sufficient quality available. + || (bPassive && uTakeQuality == uTipQuality)) + { + // Done. + Log(lsINFO) << "takeOffers: done"; + + terResult = tesSUCCESS; + } + else + { + // Have an offer directory to consider. + Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); + + SLE::pointer sleBookNode; + unsigned int uBookEntry; + uint256 uOfferIndex; + + mNodes.dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); + + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); + + const uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); + STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); + + if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) + { + // Offer is expired. Expired offers are considered unfunded. Delete it. + Log(lsINFO) << "takeOffers: encountered expired offer"; + + usOfferUnfundedFound.insert(uOfferIndex); + } + else if (uOfferOwnerID == uTakerAccountID) + { + // Would take own offer. Consider old offer expired. Delete it. + Log(lsINFO) << "takeOffers: encountered taker's own old offer"; + + usOfferUnfundedFound.insert(uOfferIndex); + } + else + { + // Get offer funds available. + + Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); + + STAmount saOfferFunds = mNodes.accountFunds(uOfferOwnerID, saOfferPays); + STAmount saTakerFunds = mNodes.accountFunds(uTakerAccountID, saTakerPays); + SLE::pointer sleOfferAccount; // Owner of offer. + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded, possibly due to previous balance action. + Log(lsINFO) << "takeOffers: offer unfunded: delete"; + + boost::unordered_set::iterator account = usAccountTouched.find(uOfferOwnerID); + if (account != usAccountTouched.end()) + { + // Previously touched account. + usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. + } + else + { + // Never touched source account. + usOfferUnfundedFound.insert(uOfferIndex); // Delete found unfunded offer when possible. + } + } + else + { + STAmount saPay = saTakerPays - saTakerPaid; + if (saTakerFunds < saPay) + saPay = saTakerFunds; + STAmount saSubTakerPaid; + STAmount saSubTakerGot; + + Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText(); + + bool bOfferDelete = STAmount::applyOffer( + saOfferFunds, + saPay, // Driver XXX need to account for fees. + saOfferPays, + saOfferGets, + saTakerPays, + saTakerGets, + saSubTakerPaid, + saSubTakerGot); + + Log(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText(); + Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); + + // Adjust offer + + // Offer owner will pay less. Subtract what taker just got. + sleOffer->setIFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); + + // Offer owner will get less. Subtract what owner just paid. + sleOffer->setIFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); + + entryModify(sleOffer); + + if (bOfferDelete) + { + // Offer now fully claimed or now unfunded. + Log(lsINFO) << "takeOffers: offer claimed: delete"; + + usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. + + // Offer owner's account is no longer pristine. + usAccountTouched.insert(uOfferOwnerID); + } + else + { + Log(lsINFO) << "takeOffers: offer partial claim."; + } + + // Offer owner pays taker. + saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? + + mNodes.accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); + + saTakerGot += saSubTakerGot; + + // Taker pays offer owner. + saSubTakerPaid.setIssuer(uTakerPaysAccountID); + + mNodes.accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); + + saTakerPaid += saSubTakerPaid; + } + } + } + } + + // On storing meta data, delete offers that were found unfunded to prevent encountering them in future. + if (tesSUCCESS == terResult) + { + BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedFound) + { + terResult = mNodes.offerDelete(uOfferIndex); + if (tesSUCCESS != terResult) + break; + } + } + + if (tesSUCCESS == terResult) + { + // On success, delete offers that became unfunded. + BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) + { + terResult = mNodes.offerDelete(uOfferIndex); + if (tesSUCCESS != terResult) + break; + } + } + + return terResult; +} + +TER TransactionEngine::doOfferCreate(const SerializedTransaction& txn) +{ +Log(lsWARNING) << "doOfferCreate> " << txn.getJson(0); + const uint32 txFlags = txn.getFlags(); + const bool bPassive = isSetBit(txFlags, tfPassive); + STAmount saTakerPays = txn.getITFieldAmount(sfTakerPays); + STAmount saTakerGets = txn.getITFieldAmount(sfTakerGets); +Log(lsWARNING) << "doOfferCreate: saTakerPays=" << saTakerPays.getFullText(); +Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); + const uint160 uPaysIssuerID = saTakerPays.getIssuer(); + const uint160 uGetsIssuerID = saTakerGets.getIssuer(); + const uint32 uExpiration = txn.getITFieldU32(sfExpiration); + const bool bHaveExpiration = txn.getITFieldPresent(sfExpiration); + const uint32 uSequence = txn.getSequence(); + + const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); + SLE::pointer sleOffer = entryCreate(ltOFFER, uLedgerIndex); + + Log(lsINFO) << "doOfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence; + + const uint160 uPaysCurrency = saTakerPays.getCurrency(); + const uint160 uGetsCurrency = saTakerGets.getCurrency(); + const uint64 uRate = STAmount::getRate(saTakerGets, saTakerPays); + + TER terResult = tesSUCCESS; + uint256 uDirectory; // Delete hints. + uint64 uOwnerNode; + uint64 uBookNode; + + if (bHaveExpiration && !uExpiration) + { + Log(lsWARNING) << "doOfferCreate: Malformed offer: bad expiration"; + + terResult = temBAD_EXPIRATION; + } + else if (bHaveExpiration && mLedger->getParentCloseTimeNC() >= uExpiration) + { + Log(lsWARNING) << "doOfferCreate: Expired transaction: offer expired"; + + // XXX CHARGE FEE ONLY. + terResult = tesSUCCESS; + } + else if (saTakerPays.isNative() && saTakerGets.isNative()) + { + Log(lsWARNING) << "doOfferCreate: Malformed offer: XNS for XNS"; + + terResult = temBAD_OFFER; + } + else if (!saTakerPays || !saTakerGets) + { + Log(lsWARNING) << "doOfferCreate: Malformed offer: bad amount"; + + terResult = temBAD_OFFER; + } + else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) + { + Log(lsWARNING) << "doOfferCreate: Malformed offer: redundant offer"; + + terResult = temREDUNDANT; + } + else if (saTakerPays.isNative() != !uPaysIssuerID || saTakerGets.isNative() != !uGetsIssuerID) + { + Log(lsWARNING) << "doOfferCreate: Malformed offer: bad issuer"; + + terResult = temBAD_ISSUER; + } + else if (!mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) + { + Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded."; + + terResult = terUNFUNDED; + } + + if (tesSUCCESS == terResult && !saTakerPays.isNative()) + { + SLE::pointer sleTakerPays = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID)); + + if (!sleTakerPays) + { + Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existant issuer: " << NewcoinAddress::createHumanAccountID(uPaysIssuerID); + + terResult = terNO_ACCOUNT; + } + } + + if (tesSUCCESS == terResult) + { + STAmount saOfferPaid; + STAmount saOfferGot; + const uint256 uTakeBookBase = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); + + Log(lsINFO) << boost::str(boost::format("doOfferCreate: take against book: %s : %s/%s -> %s/%s") + % uTakeBookBase.ToString() + % saTakerGets.getHumanCurrency() + % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()) + % saTakerPays.getHumanCurrency() + % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer())); + + // Take using the parameters of the offer. + terResult = takeOffers( + bPassive, + uTakeBookBase, + mTxnAccountID, + mTxnAccount, + saTakerGets, + saTakerPays, + saOfferPaid, // How much was spent. + saOfferGot // How much was got. + ); + + Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult; + Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); + + if (tesSUCCESS == terResult) + { + saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got. + saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid. + } + } + + Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()); + Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: takeOffers: funds=" << mNodes.accountFunds(mTxnAccountID, saTakerGets).getFullText(); + + // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); + // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); + + if (tesSUCCESS == terResult + && saTakerPays // Still wanting something. + && saTakerGets // Still offering something. + && mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. + { + // We need to place the remainder of the offer into its order book. + + // Add offer to owner's directory. + terResult = mNodes.dirAdd(uOwnerNode, Ledger::getOwnerDirIndex(mTxnAccountID), uLedgerIndex); + + if (tesSUCCESS == terResult) + { + uint256 uBookBase = Ledger::getBookBase(uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID); + + Log(lsINFO) << boost::str(boost::format("doOfferCreate: adding to book: %s : %s/%s -> %s/%s") + % uBookBase.ToString() + % saTakerPays.getHumanCurrency() + % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer()) + % saTakerGets.getHumanCurrency() + % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer())); + + uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. + + // Add offer to order book. + terResult = mNodes.dirAdd(uBookNode, uDirectory, uLedgerIndex); + } + + if (tesSUCCESS == terResult) + { + // Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); + // Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); + // Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); + // Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); + // Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); + // Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); + + sleOffer->setIFieldAccount(sfAccount, mTxnAccountID); + sleOffer->setIFieldU32(sfSequence, uSequence); + sleOffer->setIFieldH256(sfBookDirectory, uDirectory); + sleOffer->setIFieldAmount(sfTakerPays, saTakerPays); + sleOffer->setIFieldAmount(sfTakerGets, saTakerGets); + sleOffer->setIFieldU64(sfOwnerNode, uOwnerNode); + sleOffer->setIFieldU64(sfBookNode, uBookNode); + + if (uExpiration) + sleOffer->setIFieldU32(sfExpiration, uExpiration); + + if (bPassive) + sleOffer->setFlag(lsfPassive); + } + } + + return terResult; +} + +TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) +{ + TER terResult; + const uint32 uSequence = txn.getITFieldU32(sfOfferSequence); + const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + if (sleOffer) + { + Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; + + terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + } + else + { + Log(lsWARNING) << "doOfferCancel: offer not found: " + << NewcoinAddress::createHumanAccountID(mTxnAccountID) + << " : " << uSequence + << " : " << uOfferIndex.ToString(); + + terResult = terOFFER_NOT_FOUND; + } + + return terResult; +} + +TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) +{ + Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); + + const uint32 expiration = txn.getITFieldU32(sfExpiration); +// const uint32 bondAmount = txn.getITFieldU32(sfBondAmount); +// const uint32 stampEscrow = txn.getITFieldU32(sfStampEscrow); + STAmount rippleEscrow = txn.getITFieldAmount(sfRippleEscrow); + std::vector createCode = txn.getITFieldVL(sfCreateCode); + std::vector fundCode = txn.getITFieldVL(sfFundCode); + std::vector removeCode = txn.getITFieldVL(sfRemoveCode); + std::vector expireCode = txn.getITFieldVL(sfExpireCode); + + // make sure + // expiration hasn't passed + // bond amount is enough + // they have the stamps for the bond + + // place contract in ledger + // run create code + + + if (mLedger->getParentCloseTimeNC() >= expiration) + { + Log(lsWARNING) << "doContractAdd: Expired transaction: offer expired"; + return(tefALREADY); + } + //TODO: check bond + //if( txn.getSourceAccount() ) + + Contract contract; + Script::Interpreter interpreter; + TER terResult=interpreter.interpret(&contract,txn,createCode); + if(tesSUCCESS != terResult) + { + + } + + return(terResult); +} + +TER TransactionEngine::doContractRemove(const SerializedTransaction& txn) +{ + // TODO: + return(tesSUCCESS); +} + +// vim:ts=4 diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 4c57195192..3dc45c7c15 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -1,873 +1,21 @@ // -// XXX Should make sure all fields and are recognized on a transactions. +// XXX Make sure all fields are recognized in transactions. // +#include + #include "TransactionEngine.h" -#include -#include -#include -#include - #include "../json/writer.h" #include "Config.h" #include "Log.h" #include "TransactionFormats.h" #include "utils.h" -#include "Interpreter.h" -#include "Contract.h" - -// Small for testing, should likely be 32 or 64. -#define DIR_NODE_MAX 2 -#define RIPPLE_PATHS_MAX 3 - -static STAmount saZero(CURRENCY_ONE, ACCOUNT_ONE, 0); -static STAmount saOne(CURRENCY_ONE, ACCOUNT_ONE, 1); - -std::size_t hash_value(const aciSource& asValue) -{ - std::size_t seed = 0; - - asValue.get<0>().hash_combine(seed); - asValue.get<1>().hash_combine(seed); - asValue.get<2>().hash_combine(seed); - - return seed; -} - -bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) -{ - static struct { - TER terCode; - const char* cpToken; - const char* cpHuman; - } transResultInfoA[] = { - { tefALREADY, "tefALREADY", "The exact transaction was already in this ledger" }, - { tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." }, - { tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." }, - { tefBAD_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed." }, - { tefBAD_GEN_AUTH, "tefBAD_GEN_AUTH", "Not authorized to claim generator." }, - { tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." }, - { tefCLAIMED, "tefCLAIMED", "Can not claim a previously claimed account." }, - { tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." }, - { tefCREATED, "tefCREATED", "Can't add an already created account." }, - { tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." }, - { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past" }, - - { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: too many paths." }, - { telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." }, - - { temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." }, - { temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, - { temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed." }, - { temBAD_ISSUER, "temBAD_ISSUER", "Malformed." }, - { temBAD_OFFER, "temBAD_OFFER", "Malformed." }, - { temBAD_PATH, "temBAD_PATH", "Malformed." }, - { temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed." }, - { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: bad publish." }, - { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, - { temCREATEXNS, "temCREATEXNS", "Can not specify non XNS for Create." }, - { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, - { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, - { temINSUF_FEE_P, "temINSUF_FEE_P", "Fee not allowed." }, - { temINVALID, "temINVALID", "The transaction is ill-formed" }, - { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, - { temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." }, - { temUNCERTAIN, "temUNCERTAIN", "In process of determining result. Never returned." }, - { temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet." }, - - { tepPATH_DRY, "tepPATH_DRY", "Path could not send partial amount." }, - { tepPATH_PARTIAL, "tepPATH_PARTIAL", "Path could not send full amount." }, - - { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, - { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, - { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." }, - { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, - { terNO_DST, "terNO_DST", "The destination does not exist" }, - { terNO_LINE, "terNO_LINE", "No such line." }, - { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, - { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, - { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, - { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, - { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, - - { tesSUCCESS, "tesSUCCESS", "The transaction was applied" }, - }; - - int iIndex = NUMBER(transResultInfoA); - - while (iIndex-- && transResultInfoA[iIndex].terCode != terCode) - ; - - if (iIndex >= 0) - { - strToken = transResultInfoA[iIndex].cpToken; - strHuman = transResultInfoA[iIndex].cpHuman; - } - - return iIndex >= 0; -} - -static std::string transToken(TER terCode) -{ - std::string strToken; - std::string strHuman; - - return transResultInfo(terCode, strToken, strHuman) ? strToken : "-"; -} - -#if 0 -static std::string transHuman(TER terCode) -{ - std::string strToken; - std::string strHuman; - - return transResultInfo(terCode, strToken, strHuman) ? strHuman : "-"; -} -#endif - -// Returns amount owed by uToAccountID to uFromAccountID. -// <-- $owed/uCurrencyID/uToAccountID: positive: uFromAccountID holds IOUs., negative: uFromAccountID owes IOUs. -STAmount TransactionEngine::rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) -{ - STAmount saBalance; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - if (sleRippleState) - { - saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - if (uToAccountID < uFromAccountID) - saBalance.negate(); - saBalance.setIssuer(uToAccountID); - } - else - { - Log(lsINFO) << "rippleOwed: No credit line between " - << NewcoinAddress::createHumanAccountID(uFromAccountID) - << " and " - << NewcoinAddress::createHumanAccountID(uToAccountID) - << " for " - << STAmount::createHumanCurrency(uCurrencyID) - << "." ; - - assert(false); - } - - return saBalance; - -} - -// Maximum amount of IOUs uToAccountID will hold from uFromAccountID. -// <-- $amount/uCurrencyID/uToAccountID. -STAmount TransactionEngine::rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) -{ - STAmount saLimit; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - assert(sleRippleState); - if (sleRippleState) - { - saLimit = sleRippleState->getIValueFieldAmount(uToAccountID < uFromAccountID ? sfLowLimit : sfHighLimit); - saLimit.setIssuer(uToAccountID); - } - - return saLimit; - -} - -uint32 TransactionEngine::rippleTransferRate(const uint160& uIssuerID) -{ - SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); - - uint32 uQuality = sleAccount && sleAccount->getIFieldPresent(sfTransferRate) - ? sleAccount->getIFieldU32(sfTransferRate) - : QUALITY_ONE; - - Log(lsINFO) << boost::str(boost::format("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") - % NewcoinAddress::createHumanAccountID(uIssuerID) - % !!sleAccount - % (uQuality/1000000000.0)); - - assert(sleAccount); - - return uQuality; -} - -// XXX Might not need this, might store in nodes on calc reverse. -uint32 TransactionEngine::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow, const SOE_Field sfHigh) -{ - uint32 uQuality = QUALITY_ONE; - SLE::pointer sleRippleState; - - if (uToAccountID == uFromAccountID) - { - nothing(); - } - else - { - sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - if (sleRippleState) - { - SOE_Field sfField = uToAccountID < uFromAccountID ? sfLow: sfHigh; - - uQuality = sleRippleState->getIFieldPresent(sfField) - ? sleRippleState->getIFieldU32(sfField) - : QUALITY_ONE; - - if (!uQuality) - uQuality = 1; // Avoid divide by zero. - } - } - - Log(lsINFO) << boost::str(boost::format("rippleQuality: %s uToAccountID=%s uFromAccountID=%s uCurrencyID=%s bLine=%d uQuality=%f") - % (sfLow == sfLowQualityIn ? "in" : "out") - % NewcoinAddress::createHumanAccountID(uToAccountID) - % NewcoinAddress::createHumanAccountID(uFromAccountID) - % STAmount::createHumanCurrency(uCurrencyID) - % !!sleRippleState - % (uQuality/1000000000.0)); - - assert(uToAccountID == uFromAccountID || !!sleRippleState); - - return uQuality; -} - -// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative. -// <-- IOU's uAccountID has of uIssuerID -STAmount TransactionEngine::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) -{ - STAmount saBalance; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); - - if (sleRippleState) - { - saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - - if (uAccountID > uIssuerID) - saBalance.negate(); // Put balance in uAccountID terms. - } - - return saBalance; -} - -// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. -STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) -{ - STAmount saAmount; - - if (!uCurrencyID) - { - SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); - - saAmount = sleAccount->getIValueFieldAmount(sfBalance); - - Log(lsINFO) << "accountHolds: stamps: " << saAmount.getText(); - } - else - { - saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); - - Log(lsINFO) << "accountHolds: " - << saAmount.getFullText() - << " : " - << STAmount::createHumanCurrency(uCurrencyID) - << "/" - << NewcoinAddress::createHumanAccountID(uIssuerID); - } - - return saAmount; -} - -// Returns the funds available for uAccountID for a currency/issuer. -// Use when you need a default for rippling uAccountID's currency. -// --> saDefault/currency/issuer -// <-- saFunds: Funds available. May be negative. -// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault. -STAmount TransactionEngine::accountFunds(const uint160& uAccountID, const STAmount& saDefault) -{ - STAmount saFunds; - - Log(lsINFO) << "accountFunds: uAccountID=" - << NewcoinAddress::createHumanAccountID(uAccountID); - Log(lsINFO) << "accountFunds: saDefault.isNative()=" << saDefault.isNative(); - Log(lsINFO) << "accountFunds: saDefault.getIssuer()=" - << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); - - if (!saDefault.isNative() && saDefault.getIssuer() == uAccountID) - { - saFunds = saDefault; - - Log(lsINFO) << "accountFunds: offer funds: ripple self-funded: " << saFunds.getText(); - } - else - { - saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); - - Log(lsINFO) << "accountFunds: offer funds: uAccountID =" - << NewcoinAddress::createHumanAccountID(uAccountID) - << " : " - << saFunds.getText() - << "/" - << saDefault.getHumanCurrency() - << "/" - << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); - } - - return saFunds; -} - -// Calculate transit fee. -STAmount TransactionEngine::rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) -{ - STAmount saTransitFee; - - if (uSenderID != uIssuerID && uReceiverID != uIssuerID) - { - uint32 uTransitRate = rippleTransferRate(uIssuerID); - - if (QUALITY_ONE != uTransitRate) - { - STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9); - - saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer()); - } - } - - return saTransitFee; -} - -// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. -void TransactionEngine::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) -{ - uint160 uIssuerID = saAmount.getIssuer(); - - assert(!bCheckIssuer || uSenderID == uIssuerID || uReceiverID == uIssuerID); - - bool bFlipped = uSenderID > uReceiverID; - uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); - - if (!sleRippleState) - { - Log(lsINFO) << "rippleCredit: Creating ripple line: " << uIndex.ToString(); - - STAmount saBalance = saAmount; - - sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); - - if (!bFlipped) - saBalance.negate(); - - sleRippleState->setIFieldAmount(sfBalance, saBalance); - sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, uSenderID); - sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uReceiverID); - } - else - { - STAmount saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - - if (!bFlipped) - saBalance.negate(); // Put balance in low terms. - - saBalance += saAmount; - - if (!bFlipped) - saBalance.negate(); - - sleRippleState->setIFieldAmount(sfBalance, saBalance); - - entryModify(sleRippleState); - } -} - -// Send regardless of limits. -// --> saAmount: Amount/currency/issuer for receiver to get. -// <-- saActual: Amount actually sent. Sender pay's fees. -STAmount TransactionEngine::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) -{ - STAmount saActual; - const uint160 uIssuerID = saAmount.getIssuer(); - - assert(!!uSenderID && !!uReceiverID); - - if (uSenderID == uIssuerID || uReceiverID == uIssuerID) - { - // Direct send: redeeming IOUs and/or sending own IOUs. - rippleCredit(uSenderID, uReceiverID, saAmount); - - saActual = saAmount; - } - else - { - // Sending 3rd party IOUs: transit. - - STAmount saTransitFee = rippleTransferFee(uSenderID, uReceiverID, uIssuerID, saAmount); - - saActual = !saTransitFee ? saAmount : saAmount+saTransitFee; - - saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. - - rippleCredit(uIssuerID, uReceiverID, saAmount); - rippleCredit(uSenderID, uIssuerID, saActual); - } - - return saActual; -} - -void TransactionEngine::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) -{ - assert(!saAmount.isNegative()); - - if (!saAmount) - { - nothing(); - } - else if (saAmount.isNative()) - { - SLE::pointer sleSender = !!uSenderID - ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) - : SLE::pointer(); - SLE::pointer sleReceiver = !!uReceiverID - ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)) - : SLE::pointer(); - - Log(lsINFO) << boost::str(boost::format("accountSend> %s (%s) -> %s (%s) : %s") - % NewcoinAddress::createHumanAccountID(uSenderID) - % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % NewcoinAddress::createHumanAccountID(uReceiverID) - % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % saAmount.getFullText()); - - if (sleSender) - { - sleSender->setIFieldAmount(sfBalance, sleSender->getIValueFieldAmount(sfBalance) - saAmount); - entryModify(sleSender); - } - - if (sleReceiver) - { - sleReceiver->setIFieldAmount(sfBalance, sleReceiver->getIValueFieldAmount(sfBalance) + saAmount); - entryModify(sleReceiver); - } - - Log(lsINFO) << boost::str(boost::format("accountSend< %s (%s) -> %s (%s) : %s") - % NewcoinAddress::createHumanAccountID(uSenderID) - % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % NewcoinAddress::createHumanAccountID(uReceiverID) - % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % saAmount.getFullText()); - } - else - { - rippleSend(uSenderID, uReceiverID, saAmount); - } -} - -TER TransactionEngine::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) -{ - uint64 uOwnerNode = sleOffer->getIFieldU64(sfOwnerNode); - TER terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); - - if (tesSUCCESS == terResult) - { - uint256 uDirectory = sleOffer->getIFieldH256(sfBookDirectory); - uint64 uBookNode = sleOffer->getIFieldU64(sfBookNode); - - terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); - } - - entryDelete(sleOffer); - - return terResult; -} - -TER TransactionEngine::offerDelete(const uint256& uOfferIndex) -{ - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - const uint160 uOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - - return offerDelete(sleOffer, uOfferIndex, uOwnerID); -} - -// <-- uNodeDir: For deletion, present to make dirDelete efficient. -// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. -// --> uLedgerIndex: Value to add to directory. -// We only append. This allow for things that watch append only structure to just monitor from the last node on ward. -// Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. -TER TransactionEngine::dirAdd( - uint64& uNodeDir, - const uint256& uRootIndex, - const uint256& uLedgerIndex) -{ - SLE::pointer sleNode; - STVector256 svIndexes; - SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); - - if (!sleRoot) - { - // No root, make it. - sleRoot = entryCreate(ltDIR_NODE, uRootIndex); - - sleNode = sleRoot; - uNodeDir = 0; - } - else - { - uNodeDir = sleRoot->getIFieldU64(sfIndexPrevious); // Get index to last directory node. - - if (uNodeDir) - { - // Try adding to last node. - sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); - - assert(sleNode); - } - else - { - // Try adding to root. Didn't have a previous set to the last node. - sleNode = sleRoot; - } - - svIndexes = sleNode->getIFieldV256(sfIndexes); - - if (DIR_NODE_MAX != svIndexes.peekValue().size()) - { - // Add to current node. - entryModify(sleNode); - } - // Add to new node. - else if (!++uNodeDir) - { - return terDIR_FULL; - } - else - { - // Have old last point to new node, if it was not root. - if (uNodeDir == 1) - { - // Previous node is root node. - - sleRoot->setIFieldU64(sfIndexNext, uNodeDir); - } - else - { - // Previous node is not root node. - - SLE::pointer slePrevious = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir-1)); - - slePrevious->setIFieldU64(sfIndexNext, uNodeDir); - entryModify(slePrevious); - - sleNode->setIFieldU64(sfIndexPrevious, uNodeDir-1); - } - - // Have root point to new node. - sleRoot->setIFieldU64(sfIndexPrevious, uNodeDir); - entryModify(sleRoot); - - // Create the new node. - sleNode = entryCreate(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); - svIndexes = STVector256(); - } - } - - svIndexes.peekValue().push_back(uLedgerIndex); // Append entry. - sleNode->setIFieldV256(sfIndexes, svIndexes); // Save entry. - - Log(lsINFO) << "dirAdd: creating: root: " << uRootIndex.ToString(); - Log(lsINFO) << "dirAdd: appending: Entry: " << uLedgerIndex.ToString(); - Log(lsINFO) << "dirAdd: appending: Node: " << strHex(uNodeDir); - // Log(lsINFO) << "dirAdd: appending: PREV: " << svIndexes.peekValue()[0].ToString(); - - return tesSUCCESS; -} - -// Ledger must be in a state for this to work. -TER TransactionEngine::dirDelete( - const bool bKeepRoot, // --> True, if we never completely clean up, after we overflow the root node. - const uint64& uNodeDir, // --> Node containing entry. - const uint256& uRootIndex, // --> The index of the base of the directory. Nodes are based off of this. - const uint256& uLedgerIndex, // --> Value to add to directory. - const bool bStable) // --> True, not to change relative order of entries. -{ - uint64 uNodeCur = uNodeDir; - SLE::pointer sleNode = entryCache(ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex(uRootIndex, uNodeCur) : uRootIndex); - - assert(sleNode); - - if (!sleNode) - { - Log(lsWARNING) << "dirDelete: no such node"; - - return tefBAD_LEDGER; - } - - STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); - std::vector& vuiIndexes = svIndexes.peekValue(); - std::vector::iterator it; - - it = std::find(vuiIndexes.begin(), vuiIndexes.end(), uLedgerIndex); - - assert(vuiIndexes.end() != it); - if (vuiIndexes.end() == it) - { - assert(false); - - Log(lsWARNING) << "dirDelete: no such entry"; - - return tefBAD_LEDGER; - } - - // Remove the element. - if (vuiIndexes.size() > 1) - { - if (bStable) - { - vuiIndexes.erase(it); - } - else - { - *it = vuiIndexes[vuiIndexes.size()-1]; - vuiIndexes.resize(vuiIndexes.size()-1); - } - } - else - { - vuiIndexes.clear(); - } - - sleNode->setIFieldV256(sfIndexes, svIndexes); - entryModify(sleNode); - - if (vuiIndexes.empty()) - { - // May be able to delete nodes. - uint64 uNodePrevious = sleNode->getIFieldU64(sfIndexPrevious); - uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); - - if (!uNodeCur) - { - // Just emptied root node. - - if (!uNodePrevious) - { - // Never overflowed the root node. Delete it. - entryDelete(sleNode); - } - // Root overflowed. - else if (bKeepRoot) - { - // If root overflowed and not allowed to delete overflowed root node. - - nothing(); - } - else if (uNodePrevious != uNodeNext) - { - // Have more than 2 nodes. Can't delete root node. - - nothing(); - } - else - { - // Have only a root node and a last node. - SLE::pointer sleLast = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); - - assert(sleLast); - - if (sleLast->getIFieldV256(sfIndexes).peekValue().empty()) - { - // Both nodes are empty. - - entryDelete(sleNode); // Delete root. - entryDelete(sleLast); // Delete last. - } - else - { - // Have an entry, can't delete root node. - - nothing(); - } - } - } - // Just emptied a non-root node. - else if (uNodeNext) - { - // Not root and not last node. Can delete node. - - SLE::pointer slePrevious = entryCache(ltDIR_NODE, uNodePrevious ? Ledger::getDirNodeIndex(uRootIndex, uNodePrevious) : uRootIndex); - - assert(slePrevious); - - SLE::pointer sleNext = entryCache(ltDIR_NODE, uNodeNext ? Ledger::getDirNodeIndex(uRootIndex, uNodeNext) : uRootIndex); - - assert(slePrevious); - assert(sleNext); - - if (!slePrevious) - { - Log(lsWARNING) << "dirDelete: previous node is missing"; - - return tefBAD_LEDGER; - } - - if (!sleNext) - { - Log(lsWARNING) << "dirDelete: next node is missing"; - - return tefBAD_LEDGER; - } - - // Fix previous to point to its new next. - slePrevious->setIFieldU64(sfIndexNext, uNodeNext); - entryModify(slePrevious); - - // Fix next to point to its new previous. - sleNext->setIFieldU64(sfIndexPrevious, uNodePrevious); - entryModify(sleNext); - } - // Last node. - else if (bKeepRoot || uNodePrevious) - { - // Not allowed to delete last node as root was overflowed. - // Or, have pervious entries preventing complete delete. - - nothing(); - } - else - { - // Last and only node besides the root. - SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); - - assert(sleRoot); - - if (sleRoot->getIFieldV256(sfIndexes).peekValue().empty()) - { - // Both nodes are empty. - - entryDelete(sleRoot); // Delete root. - entryDelete(sleNode); // Delete last. - } - else - { - // Root has an entry, can't delete. - - nothing(); - } - } - } - - return tesSUCCESS; -} - -// Return the first entry and advance uDirEntry. -// <-- true, if had a next entry. -bool TransactionEngine::dirFirst( - const uint256& uRootIndex, // --> Root of directory. - SLE::pointer& sleNode, // <-- current node - unsigned int& uDirEntry, // <-- next entry - uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. -{ - sleNode = entryCache(ltDIR_NODE, uRootIndex); - uDirEntry = 0; - - assert(sleNode); // We never probe for directories. - - return TransactionEngine::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); -} - -// Return the current entry and advance uDirEntry. -// <-- true, if had a next entry. -bool TransactionEngine::dirNext( - const uint256& uRootIndex, // --> Root of directory - SLE::pointer& sleNode, // <-> current node - unsigned int& uDirEntry, // <-> next entry - uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. -{ - STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); - std::vector& vuiIndexes = svIndexes.peekValue(); - - if (uDirEntry == vuiIndexes.size()) - { - uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); - - if (!uNodeNext) - { - uEntryIndex.zero(); - - return false; - } - else - { - sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); - uDirEntry = 0; - - return dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); - } - } - - uEntryIndex = vuiIndexes[uDirEntry++]; -Log(lsINFO) << boost::str(boost::format("dirNext: uDirEntry=%d uEntryIndex=%s") % uDirEntry % uEntryIndex); - - return true; -} - -// Set the authorized public key for an account. May also set the generator map. -TER TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) -{ - // - // Verify that submitter knows the private key for the generator. - // Otherwise, people could deny access to generators. - // - - std::vector vucCipher = txn.getITFieldVL(sfGenerator); - std::vector vucPubKey = txn.getITFieldVL(sfPubKey); - std::vector vucSignature = txn.getITFieldVL(sfSignature); - NewcoinAddress naAccountPublic = NewcoinAddress::createAccountPublic(vucPubKey); - - if (!naAccountPublic.accountPublicVerify(Serializer::getSHA512Half(vucCipher), vucSignature)) - { - Log(lsWARNING) << "createGenerator: bad signature unauthorized generator claim"; - - return tefBAD_GEN_AUTH; - } - - // Create generator. - uint160 hGeneratorID = naAccountPublic.getAccountID(); - - SLE::pointer sleGen = entryCache(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); - if (!sleGen) - { - // Create the generator. - Log(lsTRACE) << "createGenerator: creating generator"; - - sleGen = entryCreate(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); - - sleGen->setIFieldVL(sfGenerator, vucCipher); - } - else if (bMustSetGenerator) - { - // Doing a claim. Must set generator. - // Generator is already in use. Regular passphrases limited to one wallet. - Log(lsWARNING) << "createGenerator: generator already in use"; - - return tefGEN_IN_USE; - } - - // Set the public key needed to use the account. - uint160 uAuthKeyID = bMustSetGenerator - ? hGeneratorID // Claim - : txn.getITFieldAccount(sfAuthorizedKey); // PasswordSet - - mTxnAccount->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); - - return tesSUCCESS; -} void TransactionEngine::txnWrite() { - // Write back the account states and add the transaction to the ledger + // Write back the account states for (boost::unordered_map::iterator it = mNodes.begin(), end = mNodes.end(); it != end; ++it) { @@ -1274,7 +422,7 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa break; case ttPAYMENT: - terResult = doPayment(txn); + terResult = doPayment(txn, params); break; case ttWALLET_ADD: @@ -1282,10 +430,10 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa break; case ttCONTRACT: - terResult= doContractAdd(txn); + terResult = doContractAdd(txn); break; case ttCONTRACT_REMOVE: - terResult=doContractRemove(txn); + terResult = doContractRemove(txn); break; default: @@ -1310,22 +458,31 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa if (tesSUCCESS == terResult || isTepPartial(terResult)) { // Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially). + Serializer m; + mNodes.calcRawMeta(m); + txnWrite(); Serializer s; - txn.add(s); - if (!mLedger->addTransaction(txID, s)) - assert(false); + if (isSetBit(params, tapOPEN_LEDGER)) + { + if (!mLedger->addTransaction(txID, s)) + assert(false); + } + else + { + if (!mLedger->addTransaction(txID, s, m)) + assert(false); - // Charge whatever fee they specified. - mLedger->destroyCoins(saPaid.getNValue()); + // Charge whatever fee they specified. + mLedger->destroyCoins(saPaid.getNValue()); + } } mTxnAccount = SLE::pointer(); mNodes.clear(); - musUnfundedFound.clear(); if (!isSetBit(params, tapOPEN_LEDGER) && (isTemMalformed(terResult) || isTefFailure(terResult))) @@ -1336,3443 +493,4 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa return terResult; } - - -TER TransactionEngine::doAccountSet(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doAccountSet>"; - - // - // EmailHash - // - - if (txn.getITFieldPresent(sfEmailHash)) - { - uint128 uHash = txn.getITFieldH128(sfEmailHash); - - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset email hash"; - - mTxnAccount->makeIFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set email hash"; - - mTxnAccount->setIFieldH128(sfEmailHash, uHash); - } - } - - // - // WalletLocator - // - - if (txn.getITFieldPresent(sfWalletLocator)) - { - uint256 uHash = txn.getITFieldH256(sfWalletLocator); - - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset wallet locator"; - - mTxnAccount->makeIFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set wallet locator"; - - mTxnAccount->setIFieldH256(sfWalletLocator, uHash); - } - } - - // - // MessageKey - // - - if (!txn.getITFieldPresent(sfMessageKey)) - { - nothing(); - } - else - { - Log(lsINFO) << "doAccountSet: set message key"; - - mTxnAccount->setIFieldVL(sfMessageKey, txn.getITFieldVL(sfMessageKey)); - } - - // - // Domain - // - - if (txn.getITFieldPresent(sfDomain)) - { - std::vector vucDomain = txn.getITFieldVL(sfDomain); - - if (vucDomain.empty()) - { - Log(lsINFO) << "doAccountSet: unset domain"; - - mTxnAccount->makeIFieldAbsent(sfDomain); - } - else - { - Log(lsINFO) << "doAccountSet: set domain"; - - mTxnAccount->setIFieldVL(sfDomain, vucDomain); - } - } - - // - // TransferRate - // - - if (txn.getITFieldPresent(sfTransferRate)) - { - uint32 uRate = txn.getITFieldU32(sfTransferRate); - - if (!uRate) - { - Log(lsINFO) << "doAccountSet: unset transfer rate"; - - mTxnAccount->makeIFieldAbsent(sfTransferRate); - } - else - { - Log(lsINFO) << "doAccountSet: set transfer rate"; - - mTxnAccount->setIFieldU32(sfTransferRate, uRate); - } - } - - // - // PublishHash && PublishSize - // - - bool bPublishHash = txn.getITFieldPresent(sfPublishHash); - bool bPublishSize = txn.getITFieldPresent(sfPublishSize); - - if (bPublishHash ^ bPublishSize) - { - Log(lsINFO) << "doAccountSet: bad publish"; - - return temBAD_PUBLISH; - } - else if (bPublishHash && bPublishSize) - { - uint256 uHash = txn.getITFieldH256(sfPublishHash); - uint32 uSize = txn.getITFieldU32(sfPublishSize); - - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset publish"; - - mTxnAccount->makeIFieldAbsent(sfPublishHash); - mTxnAccount->makeIFieldAbsent(sfPublishSize); - } - else - { - Log(lsINFO) << "doAccountSet: set publish"; - - mTxnAccount->setIFieldH256(sfPublishHash, uHash); - mTxnAccount->setIFieldU32(sfPublishSize, uSize); - } - } - - Log(lsINFO) << "doAccountSet<"; - - return tesSUCCESS; -} - -TER TransactionEngine::doClaim(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doClaim>"; - - TER terResult = setAuthorized(txn, true); - - Log(lsINFO) << "doClaim<"; - - return terResult; -} - -TER TransactionEngine::doCreditSet(const SerializedTransaction& txn) -{ - TER terResult = tesSUCCESS; - Log(lsINFO) << "doCreditSet>"; - - // Check if destination makes sense. - uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - - if (!uDstAccountID) - { - Log(lsINFO) << "doCreditSet: Invalid transaction: Destination account not specifed."; - - return temDST_NEEDED; - } - else if (mTxnAccountID == uDstAccountID) - { - Log(lsINFO) << "doCreditSet: Invalid transaction: Can not extend credit to self."; - - return temDST_IS_SRC; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - Log(lsINFO) << "doCreditSet: Delay transaction: Destination account does not exist."; - - return terNO_DST; - } - - const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. - const bool bLimitAmount = txn.getITFieldPresent(sfLimitAmount); - const STAmount saLimitAmount = bLimitAmount ? txn.getITFieldAmount(sfLimitAmount) : STAmount(); - const bool bQualityIn = txn.getITFieldPresent(sfQualityIn); - const uint32 uQualityIn = bQualityIn ? txn.getITFieldU32(sfQualityIn) : 0; - const bool bQualityOut = txn.getITFieldPresent(sfQualityOut); - const uint32 uQualityOut = bQualityIn ? txn.getITFieldU32(sfQualityOut) : 0; - const uint160 uCurrencyID = saLimitAmount.getCurrency(); - bool bDelIndex = false; - - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); - if (sleRippleState) - { - // A line exists in one or more directions. -#if 0 - if (!saLimitAmount) - { - // Zeroing line. - uint160 uLowID = sleRippleState->getIValueFieldAccount(sfLowID).getAccountID(); - uint160 uHighID = sleRippleState->getIValueFieldAccount(sfHighID).getAccountID(); - bool bLow = uLowID == uSrcAccountID; - bool bHigh = uLowID == uDstAccountID; - bool bBalanceZero = !sleRippleState->getIValueFieldAmount(sfBalance); - STAmount saDstLimit = sleRippleState->getIValueFieldAmount(bSendLow ? sfLowLimit : sfHighLimit); - bool bDstLimitZero = !saDstLimit; - - assert(bLow || bHigh); - - if (bBalanceZero && bDstLimitZero) - { - // Zero balance and eliminating last limit. - - bDelIndex = true; - terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex(), false); - } - } -#endif - - if (!bDelIndex) - { - if (bLimitAmount) - sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit: sfLowLimit , saLimitAmount); - - if (!bQualityIn) - { - nothing(); - } - else if (uQualityIn) - { - sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - } - else - { - sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); - } - - if (!bQualityOut) - { - nothing(); - } - else if (uQualityOut) - { - sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - } - else - { - sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); - } - - entryModify(sleRippleState); - } - - Log(lsINFO) << "doCreditSet: Modifying ripple line: bDelIndex=" << bDelIndex; - } - // Line does not exist. - else if (!saLimitAmount) - { - Log(lsINFO) << "doCreditSet: Redundant: Setting non-existant ripple line to 0."; - - return terNO_LINE_NO_ZERO; - } - else - { - // Create a new ripple line. - sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); - - Log(lsINFO) << "doCreditSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); - - sleRippleState->setIFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. - sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAmount); - sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, ACCOUNT_ONE)); - sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID); - sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID); - if (uQualityIn) - sleRippleState->setIFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); - if (uQualityOut) - sleRippleState->setIFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut); - - uint64 uSrcRef; // Ignored, dirs never delete. - - terResult = dirAdd(uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex()); - - if (tesSUCCESS == terResult) - terResult = dirAdd(uSrcRef, Ledger::getOwnerDirIndex(uDstAccountID), sleRippleState->getIndex()); - } - - Log(lsINFO) << "doCreditSet<"; - - return terResult; -} - -TER TransactionEngine::doNicknameSet(const SerializedTransaction& txn) -{ - std::cerr << "doNicknameSet>" << std::endl; - - const uint256 uNickname = txn.getITFieldH256(sfNickname); - const bool bMinOffer = txn.getITFieldPresent(sfMinimumOffer); - const STAmount saMinOffer = bMinOffer ? txn.getITFieldAmount(sfAmount) : STAmount(); - - SLE::pointer sleNickname = entryCache(ltNICKNAME, uNickname); - - if (sleNickname) - { - // Edit old entry. - sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); - - if (bMinOffer && saMinOffer) - { - sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); - } - else - { - sleNickname->makeIFieldAbsent(sfMinimumOffer); - } - - entryModify(sleNickname); - } - else - { - // Make a new entry. - // XXX Need to include authorization limiting for first year. - - sleNickname = entryCreate(ltNICKNAME, Ledger::getNicknameIndex(uNickname)); - - std::cerr << "doNicknameSet: Creating nickname node: " << sleNickname->getIndex().ToString() << std::endl; - - sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); - - if (bMinOffer && saMinOffer) - sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); - } - - std::cerr << "doNicknameSet<" << std::endl; - - return tesSUCCESS; -} - -TER TransactionEngine::doPasswordFund(const SerializedTransaction& txn) -{ - std::cerr << "doPasswordFund>" << std::endl; - - const uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - SLE::pointer sleDst = mTxnAccountID == uDstAccountID - ? mTxnAccount - : entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - // Destination account does not exist. - std::cerr << "doPasswordFund: Delay transaction: Destination account does not exist." << std::endl; - - return terSET_MISSING_DST; - } - - if (sleDst->getFlags() & lsfPasswordSpent) - { - sleDst->clearFlag(lsfPasswordSpent); - - std::cerr << "doPasswordFund: Clearing spent." << sleDst->getFlags() << std::endl; - - if (mTxnAccountID != uDstAccountID) { - std::cerr << "doPasswordFund: Destination modified." << std::endl; - - entryModify(sleDst); - } - } - - std::cerr << "doPasswordFund<" << std::endl; - - return tesSUCCESS; -} - -TER TransactionEngine::doPasswordSet(const SerializedTransaction& txn) -{ - std::cerr << "doPasswordSet>" << std::endl; - - if (mTxnAccount->getFlags() & lsfPasswordSpent) - { - std::cerr << "doPasswordSet: Delay transaction: Funds already spent." << std::endl; - - return terFUNDS_SPENT; - } - - mTxnAccount->setFlag(lsfPasswordSpent); - - TER terResult = setAuthorized(txn, false); - - std::cerr << "doPasswordSet<" << std::endl; - - return terResult; -} - -// If needed, advance to next funded offer. -// - Automatically advances to first offer. -// - Set bEntryAdvance to advance to next entry. -// <-- uOfferIndex : 0=end of list. -TER TransactionEngine::calcNodeAdvance( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality, - const bool bReverse) -{ - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - - const uint160& uPrvCurrencyID = pnPrv.uCurrencyID; - const uint160& uPrvIssuerID = pnPrv.uIssuerID; - const uint160& uCurCurrencyID = pnCur.uCurrencyID; - const uint160& uCurIssuerID = pnCur.uIssuerID; - - uint256& uDirectTip = pnCur.uDirectTip; - uint256 uDirectEnd = pnCur.uDirectEnd; - bool& bDirectAdvance = pnCur.bDirectAdvance; - SLE::pointer& sleDirectDir = pnCur.sleDirectDir; - STAmount& saOfrRate = pnCur.saOfrRate; - - bool& bEntryAdvance = pnCur.bEntryAdvance; - unsigned int& uEntry = pnCur.uEntry; - uint256& uOfferIndex = pnCur.uOfferIndex; - SLE::pointer& sleOffer = pnCur.sleOffer; - uint160& uOfrOwnerID = pnCur.uOfrOwnerID; - STAmount& saOfferFunds = pnCur.saOfferFunds; - STAmount& saTakerPays = pnCur.saTakerPays; - STAmount& saTakerGets = pnCur.saTakerGets; - bool& bFundsDirty = pnCur.bFundsDirty; - - TER terResult = tesSUCCESS; - - do - { - bool bDirectDirDirty = false; - - if (!uDirectEnd) - { - // Need to initialize current node. - - uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); - uDirectEnd = Ledger::getQualityNext(uDirectTip); - sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); - bDirectAdvance = !sleDirectDir; - bDirectDirDirty = true; - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Initialize node: uDirectTip=%s uDirectEnd=%s bDirectAdvance=%d") % uDirectTip % uDirectEnd % bDirectAdvance); - } - - if (bDirectAdvance) - { - // Get next quality. - uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); - bDirectDirDirty = true; - bDirectAdvance = false; - - if (!!uDirectTip) - { - // Have another quality directory. - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Quality advance: uDirectTip=%s") % uDirectTip); - - sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); - } - else if (bReverse) - { - Log(lsINFO) << "calcNodeAdvance: No more offers."; - - uOfferIndex = 0; - break; - } - else - { - // No more offers. Should be done rather than fall off end of book. - Log(lsINFO) << "calcNodeAdvance: Unreachable: Fell off end of order book."; - assert(false); - - terResult = tefEXCEPTION; - } - } - - if (bDirectDirDirty) - { - saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio - uEntry = 0; - bEntryAdvance = true; - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); - } - - if (!bEntryAdvance) - { - if (bFundsDirty) - { - saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); - saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); - - saOfferFunds = accountFunds(uOfrOwnerID, saTakerGets); // Funds left. - bFundsDirty = false; - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); - } - else - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: as is")); - nothing(); - } - } - else if (!dirNext(uDirectTip, sleDirectDir, uEntry, uOfferIndex)) - { - // Failed to find an entry in directory. - - uOfferIndex = 0; - - // Do another cur directory iff bMultiQuality - if (bMultiQuality) - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: next quality")); - bDirectAdvance = true; - } - else if (!bReverse) - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: unreachable: ran out of offers")); - assert(false); // Can't run out of offers in forward direction. - terResult = tefEXCEPTION; - } - } - else - { - // Got a new offer. - sleOffer = entryCache(ltOFFER, uOfferIndex); - uOfrOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - - const aciSource asLine = boost::make_tuple(uOfrOwnerID, uCurCurrencyID, uCurIssuerID); - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfrOwnerID=%s") % NewcoinAddress::createHumanAccountID(uOfrOwnerID)); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. - Log(lsINFO) << "calcNodeAdvance: expired offer"; - - assert(musUnfundedFound.find(uOfferIndex) != musUnfundedFound.end()); // Verify reverse found it too. - bEntryAdvance = true; - continue; - } - - // Allowed to access source from this node? - // XXX This can get called multiple times for same source in a row, caching result would be nice. - curIssuerNodeConstIterator itForward = pspCur->umForward.find(asLine); - const bool bFoundForward = itForward != pspCur->umForward.end(); - - if (bFoundForward && itForward->second != uIndex) - { - // Temporarily unfunded. Another node uses this source, ignore in this offer. - Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (forward)"; - - bEntryAdvance = true; - continue; - } - - curIssuerNodeConstIterator itPast = mumSource.find(asLine); - bool bFoundPast = itPast != mumSource.end(); - - if (bFoundPast && itPast->second != uIndex) - { - // Temporarily unfunded. Another node uses this source, ignore in this offer. - Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (past)"; - - bEntryAdvance = true; - continue; - } - - curIssuerNodeConstIterator itReverse = pspCur->umReverse.find(asLine); - bool bFoundReverse = itReverse != pspCur->umReverse.end(); - - if (bFoundReverse && itReverse->second != uIndex) - { - // Temporarily unfunded. Another node uses this source, ignore in this offer. - Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (reverse)"; - - bEntryAdvance = true; - continue; - } - - saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); - saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); - - saOfferFunds = accountFunds(uOfrOwnerID, saTakerGets); // Funds left. - - if (!saOfferFunds.isPositive()) - { - // Offer is unfunded. - Log(lsINFO) << "calcNodeAdvance: unfunded offer"; - - if (bReverse && !bFoundReverse && !bFoundPast) - { - // Never mentioned before: found unfunded. - musUnfundedFound.insert(uOfferIndex); // Mark offer for always deletion. - } - - // YYY Could verify offer is correct place for unfundeds. - bEntryAdvance = true; - continue; - } - - if (bReverse // Need to remember reverse mention. - && !bFoundPast // Not mentioned in previous passes. - && !bFoundReverse) // Not mentioned for pass. - { - // Consider source mentioned by current path state. - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: remember=%s/%s/%s") - % NewcoinAddress::createHumanAccountID(uOfrOwnerID) - % STAmount::createHumanCurrency(uCurCurrencyID) - % NewcoinAddress::createHumanAccountID(uCurIssuerID)); - - pspCur->umReverse.insert(std::make_pair(asLine, uIndex)); - } - - bFundsDirty = false; - bEntryAdvance = false; - } - } - while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance)); - - if (tesSUCCESS == terResult) - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfferIndex=%s") % uOfferIndex); - } - else - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: terResult=%s") % transToken(terResult)); - } - - return terResult; -} - -// Between offer nodes, the fee charged may vary. Therefore, process one inbound offer at a time. -// Propagate the inbound offer's requirements to the previous node. The previous node adjusts the amount output and the -// amount spent on fees. -// Continue process till request is satisified while we the rate does not increase past the initial rate. -TER TransactionEngine::calcNodeDeliverRev( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uOutAccountID, // --> Output owner's account. - const STAmount& saOutReq, // --> Funds wanted. - STAmount& saOutAct) // <-- Funds delivered. -{ - TER terResult = tesSUCCESS; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - - const uint160& uCurIssuerID = pnCur.uIssuerID; - const uint160& uPrvAccountID = pnPrv.uAccountID; - const STAmount& saTransferRate = pnCur.saTransferRate; - - STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be adjusted. - - saOutAct = 0; - - while (saOutAct != saOutReq) // Did not deliver limit. - { - bool& bEntryAdvance = pnCur.bEntryAdvance; - STAmount& saOfrRate = pnCur.saOfrRate; - uint256& uOfferIndex = pnCur.uOfferIndex; - SLE::pointer& sleOffer = pnCur.sleOffer; - const uint160& uOfrOwnerID = pnCur.uOfrOwnerID; - bool& bFundsDirty = pnCur.bFundsDirty; - STAmount& saOfferFunds = pnCur.saOfferFunds; - STAmount& saTakerPays = pnCur.saTakerPays; - STAmount& saTakerGets = pnCur.saTakerGets; - STAmount& saRateMax = pnCur.saRateMax; - - terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, true); // If needed, advance to next funded offer. - - if (tesSUCCESS != terResult || !uOfferIndex) - { - // Error or out of offers. - break; - } - - const STAmount saOutFeeRate = uOfrOwnerID == uCurIssuerID || uOutAccountID == uCurIssuerID // Issuer receiving or sending. - ? saOne // No fee. - : saTransferRate; // Transfer rate of issuer. - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: uOfrOwnerID=%s uOutAccountID=%s uCurIssuerID=%s saTransferRate=%s saOutFeeRate=%s") - % NewcoinAddress::createHumanAccountID(uOfrOwnerID) - % NewcoinAddress::createHumanAccountID(uOutAccountID) - % NewcoinAddress::createHumanAccountID(uCurIssuerID) - % saTransferRate.getFullText() - % saOutFeeRate.getFullText()); - - if (!saRateMax) - { - // Set initial rate. - saRateMax = saOutFeeRate; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Set initial rate: saRateMax=%s saOutFeeRate=%s") - % saRateMax - % saOutFeeRate); - } - else if (saRateMax < saOutFeeRate) - { - // Offer exceeds initial rate. - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Offer exceeds initial rate: saRateMax=%s saOutFeeRate=%s") - % saRateMax - % saOutFeeRate); - - nothing(); - break; - } - else if (saOutFeeRate < saRateMax) - { - // Reducing rate. - - saRateMax = saOutFeeRate; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Reducing rate: saRateMax=%s") - % saRateMax); - } - - STAmount saOutPass = std::min(std::min(saOfferFunds, saTakerGets), saOutReq-saOutAct); // Offer maximum out - assuming no out fees. - STAmount saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); // Offer out with fees. - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saOutReq=%s saOutAct=%s saTakerGets=%s saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") - % saOutReq - % saOutAct - % saTakerGets - % saOutPass - % saOutPlusFees - % saOfferFunds); - - if (saOutPlusFees > saOfferFunds) - { - // Offer owner can not cover all fees, compute saOutPass based on saOfferFunds. - - saOutPlusFees = saOfferFunds; - saOutPass = STAmount::divide(saOutPlusFees, saOutFeeRate); - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Total exceeds fees: saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") - % saOutPass - % saOutPlusFees - % saOfferFunds); - } - - // Compute portion of input needed to cover output. - - STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays); - STAmount saInPassAct; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saInPassReq=%s saOfrRate=%s saOutPass=%s saOutPlusFees=%s") - % saInPassReq - % saOfrRate - % saOutPass - % saOutPlusFees); - - // Find out input amount actually available at current rate. - if (!!uPrvAccountID) - { - // account --> OFFER --> ? - // Previous is the issuer and receiver is an offer, so no fee or quality. - // Previous is the issuer and has unlimited funds. - // Offer owner is obtaining IOUs via an offer, so credit line limits are ignored. - // As limits are ignored, don't need to adjust previous account's balance. - - saInPassAct = saInPassReq; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: account --> OFFER --> ? : saInPassAct=%s") - % saPrvDlvReq); - } - else - { - // offer --> OFFER --> ? - - terResult = calcNodeDeliverRev( - uIndex-1, - pspCur, - bMultiQuality, - uOfrOwnerID, - saInPassReq, - saInPassAct); - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer --> OFFER --> ? : saInPassAct=%s") - % saInPassAct); - } - - if (tesSUCCESS != terResult) - break; - - if (saInPassAct != saInPassReq) - { - // Adjust output to conform to limited input. - saOutPass = STAmount::divide(saInPassAct, saOfrRate, saTakerGets); - saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: adjusted: saOutPass=%s saOutPlusFees=%s") - % saOutPass - % saOutPlusFees); - } - - // Funds were spent. - bFundsDirty = true; - - // Deduct output, don't actually need to send. - accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); - - // Adjust offer - sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPass); - sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); - - entryModify(sleOffer); - - if (saOutPass == saTakerGets) - { - // Offer became unfunded. - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer became unfunded.")); - - bEntryAdvance = true; - } - - saOutAct += saOutPass; - saPrvDlvReq += saInPassAct; - } - - if (!saOutAct) - terResult = tepPATH_DRY; - - return terResult; -} - -// Deliver maximum amount of funds from previous node. -// Goal: Make progress consuming the offer. -TER TransactionEngine::calcNodeDeliverFwd( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uInAccountID, // --> Input owner's account. - const STAmount& saInFunds, // --> Funds available for delivery and fees. - const STAmount& saInReq, // --> Limit to deliver. - STAmount& saInAct, // <-- Amount delivered. - STAmount& saInFees) // <-- Fees charged. -{ - TER terResult = tesSUCCESS; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; - - const uint160& uNxtAccountID = pnNxt.uAccountID; - const uint160& uCurIssuerID = pnCur.uIssuerID; - const uint160& uPrvIssuerID = pnPrv.uIssuerID; - const STAmount& saTransferRate = pnPrv.saTransferRate; - - saInAct = 0; - saInFees = 0; - - while (tesSUCCESS == terResult - && saInAct != saInReq // Did not deliver limit. - && saInAct + saInFees != saInFunds) // Did not deliver all funds. - { - terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. - - if (tesSUCCESS == terResult) - { - bool& bEntryAdvance = pnCur.bEntryAdvance; - STAmount& saOfrRate = pnCur.saOfrRate; - uint256& uOfferIndex = pnCur.uOfferIndex; - SLE::pointer& sleOffer = pnCur.sleOffer; - const uint160& uOfrOwnerID = pnCur.uOfrOwnerID; - bool& bFundsDirty = pnCur.bFundsDirty; - STAmount& saOfferFunds = pnCur.saOfferFunds; - STAmount& saTakerPays = pnCur.saTakerPays; - STAmount& saTakerGets = pnCur.saTakerGets; - - - const STAmount saInFeeRate = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. - ? saOne // No fee. - : saTransferRate; // Transfer rate of issuer. - - // - // First calculate assuming no output fees. - // XXX Make sure derived in does not exceed actual saTakerPays due to rounding. - - STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees. - STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout. - STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees. - STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds. - STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. - STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. - - STAmount saInPassFees; - STAmount saOutPassAct; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") - % saOutFunded - % saInFunded - % saInTotal - % saInSum - % saInPassAct - % saOutPassMax); - - if (!!uNxtAccountID) - { - // ? --> OFFER --> account - // Input fees: vary based upon the consumed offer's owner. - // Output fees: none as the destination account is the issuer. - - // XXX This doesn't claim input. - // XXX Assumes input is in limbo. XXX Check. - - // Debit offer owner. - accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax); - - saOutPassAct = saOutPassMax; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") - % saOutPassAct); - } - else - { - // ? --> OFFER --> offer - STAmount saOutPassFees; - - terResult = TransactionEngine::calcNodeDeliverFwd( - uIndex+1, - pspCur, - bMultiQuality, - uOfrOwnerID, - saOutPassMax, - saOutPassMax, - saOutPassAct, // <-- Amount delivered. - saOutPassFees); // <-- Fees charged. - - if (tesSUCCESS != terResult) - break; - - // Offer maximum in limited by next payout. - saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); - saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; - } - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") - % saTakerGets.getFullText() - % saTakerPays.getFullText() - % saInPassAct.getFullText() - % saOutPassAct.getFullText()); - - // Funds were spent. - bFundsDirty = true; - - // Credit issuer transfer fees. - accountSend(uInAccountID, uOfrOwnerID, saInPassFees); - - // Credit offer owner from offer. - accountSend(uInAccountID, uOfrOwnerID, saInPassAct); - - // Adjust offer - sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); - sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); - - entryModify(sleOffer); - - if (saOutPassAct == saTakerGets) - { - // Offer became unfunded. - pspCur->vUnfundedBecame.push_back(uOfferIndex); - bEntryAdvance = true; - } - - saInAct += saInPassAct; - saInFees += saInPassFees; - } - } - - return terResult; -} - -// Called to drive from the last offer node in a chain. -TER TransactionEngine::calcNodeOfferRev( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality) -{ - TER terResult; - - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; - - if (!!pnNxt.uAccountID) - { - // Next is an account node, resolve current offer node's deliver. - STAmount saDeliverAct; - - terResult = calcNodeDeliverRev( - uIndex, - pspCur, - bMultiQuality, - - pnNxt.uAccountID, - pnCur.saRevDeliver, - saDeliverAct); - } - else - { - // Next is an offer. Deliver has already been resolved. - terResult = tesSUCCESS; - } - - return terResult; -} - -// Called to drive the from the first offer node in a chain. -// - Offer input is limbo. -// - Current offers consumed. -// - Current offer owners debited. -// - Transfer fees credited to issuer. -// - Payout to issuer or limbo. -// - Deliver is set without transfer fees. -TER TransactionEngine::calcNodeOfferFwd( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality - ) -{ - TER terResult; - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - - if (!!pnPrv.uAccountID) - { - // Previous is an account node, resolve its deliver. - STAmount saInAct; - STAmount saInFees; - - terResult = calcNodeDeliverFwd( - uIndex, - pspCur, - bMultiQuality, - pnPrv.uAccountID, - pnPrv.saFwdDeliver, - pnPrv.saFwdDeliver, - saInAct, - saInFees); - - assert(tesSUCCESS != terResult || pnPrv.saFwdDeliver == saInAct+saInFees); - } - else - { - // Previous is an offer. Deliver has already been resolved. - terResult = tesSUCCESS; - } - - return terResult; - -} - -// Cur is the driver and will be filled exactly. -// uQualityIn -> uQualityOut -// saPrvReq -> saCurReq -// sqPrvAct -> saCurAct -// This is a minimizing routine: moving in reverse it propagates the send limit to the sender, moving forward it propagates the -// actual send toward the receiver. -// This routine works backwards as it calculates previous wants based on previous credit limits and current wants. -// This routine works forwards as it calculates current deliver based on previous delivery limits and current wants. -// XXX Deal with uQualityIn or uQualityOut = 0 -void TransactionEngine::calcNodeRipple( - const uint32 uQualityIn, - const uint32 uQualityOut, - const STAmount& saPrvReq, // --> in limit including fees, <0 = unlimited - const STAmount& saCurReq, // --> out limit (driver) - STAmount& saPrvAct, // <-> in limit including achieved - STAmount& saCurAct, // <-> out limit achieved. - uint64& uRateMax) -{ - Log(lsINFO) << boost::str(boost::format("calcNodeRipple> uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") - % uQualityIn - % uQualityOut - % saPrvReq.getFullText() - % saCurReq.getFullText() - % saPrvAct.getFullText() - % saCurAct.getFullText()); - - assert(saPrvReq.getCurrency() == saCurReq.getCurrency()); - - const bool bPrvUnlimited = saPrvReq.isNegative(); - const STAmount saPrv = bPrvUnlimited ? STAmount(saPrvReq) : saPrvReq-saPrvAct; - const STAmount saCur = saCurReq-saCurAct; - -#if 0 - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCur=%s") - % bPrvUnlimited - % saPrv.getFullText() - % saCur.getFullText()); -#endif - - if (uQualityIn >= uQualityOut) - { - // No fee. - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: No fees")); - - if (!uRateMax || STAmount::uRateOne <= uRateMax) - { - STAmount saTransfer = bPrvUnlimited ? saCur : std::min(saPrv, saCur); - - saPrvAct += saTransfer; - saCurAct += saTransfer; - - if (!uRateMax) - uRateMax = STAmount::uRateOne; - } - } - else - { - // Fee. - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: Fee")); - - uint64 uRate = STAmount::getRate(STAmount(uQualityIn), STAmount(uQualityOut)); - - if (!uRateMax || uRate <= uRateMax) - { - const uint160 uCurrencyID = saCur.getCurrency(); - const uint160 uCurIssuerID = saCur.getIssuer(); - const uint160 uPrvIssuerID = saPrv.getIssuer(); - - STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, uCurrencyID, uCurIssuerID), uQualityIn, uCurrencyID, uCurIssuerID); - - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCurIn=%s") % bPrvUnlimited % saPrv.getFullText() % saCurIn.getFullText()); - if (bPrvUnlimited || saCurIn <= saPrv) - { - // All of cur. Some amount of prv. - saCurAct += saCur; - saPrvAct += saCurIn; - Log(lsINFO) << boost::str(boost::format("calcNodeRipple:3c: saCurReq=%s saPrvAct=%s") % saCurReq.getFullText() % saPrvAct.getFullText()); - } - else - { - // A part of cur. All of prv. (cur as driver) - STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityIn, uCurrencyID, uCurIssuerID), uQualityOut, uCurrencyID, uCurIssuerID); - Log(lsINFO) << boost::str(boost::format("calcNodeRipple:4: saCurReq=%s") % saCurReq.getFullText()); - - saCurAct += saCurOut; - saPrvAct = saPrvReq; - - if (!uRateMax) - uRateMax = uRate; - } - } - } - - Log(lsINFO) << boost::str(boost::format("calcNodeRipple< uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") - % uQualityIn - % uQualityOut - % saPrvReq.getFullText() - % saCurReq.getFullText() - % saPrvAct.getFullText() - % saCurAct.getFullText()); -} - -// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur... -// <-- tesSUCCESS or tepPATH_DRY -TER TransactionEngine::calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) -{ - TER terResult = tesSUCCESS; - const unsigned int uLast = pspCur->vpnNodes.size() - 1; - - uint64 uRateMax = 0; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; - - // Current is allowed to redeem to next. - const bool bPrvAccount = !uIndex || isSetBit(pnPrv.uFlags, STPathElement::typeAccount); - const bool bNxtAccount = uIndex == uLast || isSetBit(pnNxt.uFlags, STPathElement::typeAccount); - - const uint160& uCurAccountID = pnCur.uAccountID; - const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; - const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. - - const uint160& uCurrencyID = pnCur.uCurrencyID; - - const uint32 uQualityIn = uIndex ? rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; - const uint32 uQualityOut = uIndex != uLast ? rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; - - // For bPrvAccount - const STAmount saPrvOwed = bPrvAccount && uIndex // Previous account is owed. - ? rippleOwed(uCurAccountID, uPrvAccountID, uCurrencyID) - : STAmount(uCurrencyID, uCurAccountID); - - const STAmount saPrvLimit = bPrvAccount && uIndex // Previous account may owe. - ? rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID) - : STAmount(uCurrencyID, uCurAccountID); - - const STAmount saNxtOwed = bNxtAccount && uIndex != uLast // Next account is owed. - ? rippleOwed(uCurAccountID, uNxtAccountID, uCurrencyID) - : STAmount(uCurrencyID, uCurAccountID); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev> uIndex=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s") - % uIndex - % uLast - % NewcoinAddress::createHumanAccountID(uPrvAccountID) - % NewcoinAddress::createHumanAccountID(uCurAccountID) - % NewcoinAddress::createHumanAccountID(uNxtAccountID) - % STAmount::createHumanCurrency(uCurrencyID) - % uQualityIn - % uQualityOut - % saPrvOwed.getFullText() - % saPrvLimit.getFullText()); - - // Previous can redeem the owed IOUs it holds. - const STAmount saPrvRedeemReq = saPrvOwed.isPositive() ? saPrvOwed : STAmount(uCurrencyID, 0); - STAmount& saPrvRedeemAct = pnPrv.saRevRedeem; - - // Previous can issue up to limit minus whatever portion of limit already used (not including redeemable amount). - const STAmount saPrvIssueReq = saPrvOwed.isNegative() ? saPrvLimit+saPrvOwed : saPrvLimit; - STAmount& saPrvIssueAct = pnPrv.saRevIssue; - - // For !bPrvAccount - const STAmount saPrvDeliverReq = STAmount::saFromSigned(uCurrencyID, uCurAccountID, -1); // Unlimited. - STAmount& saPrvDeliverAct = pnPrv.saRevDeliver; - - // For bNxtAccount - const STAmount& saCurRedeemReq = pnCur.saRevRedeem; - STAmount saCurRedeemAct(saCurRedeemReq.getCurrency(), saCurRedeemReq.getIssuer()); - - const STAmount& saCurIssueReq = pnCur.saRevIssue; - STAmount saCurIssueAct(saCurIssueReq.getCurrency(), saCurIssueReq.getIssuer()); // Track progress. - - // For !bNxtAccount - const STAmount& saCurDeliverReq = pnCur.saRevDeliver; - STAmount saCurDeliverAct(saCurDeliverReq.getCurrency(), saCurDeliverReq.getIssuer()); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saNxtOwed=%s") - % saPrvRedeemReq.getFullText() - % saPrvIssueReq.getFullText() - % saCurRedeemReq.getFullText() - % saNxtOwed.getFullText()); - - Log(lsINFO) << pspCur->getJson(); - - assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand. - assert(!saCurIssueReq || !saNxtOwed.isPositive() || saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. - - if (bPrvAccount && bNxtAccount) - { - if (!uIndex) - { - // ^ --> ACCOUNT --> account|offer - // Nothing to do, there is no previous to adjust. - nothing(); - } - else if (uIndex == uLast) - { - // account --> ACCOUNT --> $ - // Overall deliverable. - const STAmount& saCurWantedReq = bPrvAccount - ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. - : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. - STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s") - % saCurWantedReq.getFullText()); - - // Calculate redeem - if (saPrvRedeemReq) // Previous has IOUs to redeem. - { - // Redeem at 1:1 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1")); - - saCurWantedAct = std::min(saPrvRedeemReq, saCurWantedReq); - saPrvRedeemAct = saCurWantedAct; - - uRateMax = STAmount::uRateOne; - } - - // Calculate issuing. - if (saCurWantedReq != saCurWantedAct // Need more. - && saPrvIssueReq) // Will accept IOUs from prevous. - { - // Rate: quality in : 1.0 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); - - // 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); - } - - if (!saCurWantedAct) - { - // Must have processed something. - terResult = tepPATH_DRY; - } - } - else - { - // ^|account --> ACCOUNT --> account - - // redeem (part 1) -> redeem - if (saCurRedeemReq // Next wants IOUs redeemed. - && saPrvRedeemReq) // Previous has IOUs to redeem. - { - // Rate : 1.0 : quality out - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out")); - - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); - } - - // issue (part 1) -> redeem - if (saCurRedeemReq != saCurRedeemAct // Next wants more IOUs redeemed. - && saPrvRedeemAct == saPrvRedeemReq) // Previous has no IOUs to redeem remaining. - { - // Rate: quality in : quality out - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out")); - - calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); - } - - // redeem (part 2) -> issue. - if (saCurIssueReq // Next wants IOUs issued. - && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. - && saPrvRedeemAct != saPrvRedeemReq) // Did not complete redeeming previous IOUs. - { - // Rate : 1.0 : transfer_rate - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : transfer_rate")); - - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); - } - - // issue (part 2) -> issue - if (saCurIssueReq != saCurIssueAct // Need wants more IOUs issued. - && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. - && saPrvRedeemReq == saPrvRedeemAct) // Previously redeemed all owed IOUs. - { - // Rate: quality in : 1.0 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); - - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); - } - - if (!saCurRedeemAct && !saCurIssueAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s") - % saCurRedeemReq.getFullText() - % saCurIssueReq.getFullText() - % saPrvOwed.getFullText() - % saCurRedeemAct.getFullText() - % saCurIssueAct.getFullText()); - } - } - else if (bPrvAccount && !bNxtAccount) - { - // account --> ACCOUNT --> offer - // Note: deliver is always issue as ACCOUNT is the issuer for the offer input. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer")); - - // redeem -> deliver/issue. - if (saPrvOwed.isPositive() // Previous has IOUs to redeem. - && saCurDeliverReq) // Need some issued. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); - } - - // issue -> deliver/issue - if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed. - && saCurDeliverReq != saCurDeliverAct) // Still need some issued. - { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); - } - - if (!saCurDeliverAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s") - % saCurDeliverReq.getFullText() - % saCurDeliverAct.getFullText() - % saPrvOwed.getFullText()); - } - else if (!bPrvAccount && bNxtAccount) - { - if (uIndex == uLast) - { - // offer --> ACCOUNT --> $ - const STAmount& saCurWantedReq = bPrvAccount - ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. - : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. - STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s") - % saCurWantedReq.getFullText()); - - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); - - if (!saCurWantedAct) - { - // Must have processed something. - terResult = tepPATH_DRY; - } - } - else - { - // offer --> ACCOUNT --> account - // Note: offer is always delivering(redeeming) as account is issuer. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account")); - - // deliver -> redeem - if (saCurRedeemReq) // Next wants us to redeem. - { - // Rate : 1.0 : quality out - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); - } - - // deliver -> issue. - if (saCurRedeemReq == saCurRedeemAct // Can only issue if previously redeemed all. - && saCurIssueReq) // Need some issued. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); - } - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s") - % saCurRedeemReq.getFullText() - % saCurRedeemAct.getFullText() - % saCurIssueReq.getFullText() - % saPrvDeliverAct.getFullText()); - - if (!saPrvDeliverAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - } - } - else - { - // offer --> ACCOUNT --> offer - // deliver/redeem -> deliver/issue. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> offer")); - - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); - - if (!saCurDeliverAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - } - - return terResult; -} - -// Perfrom balance adjustments between previous and current node. -// - The previous node: specifies what to push through to current. -// - All of previous output is consumed. -// Then, compute output for next node. -// - Current node: specify what to push through to next. -// - Output to next node is computed as input minus quality or transfer fee. -TER TransactionEngine::calcNodeAccountFwd( - const unsigned int uIndex, // 0 <= uIndex <= uLast - const PathState::pointer& pspCur, - const bool bMultiQuality) -{ - TER terResult = tesSUCCESS; - const unsigned int uLast = pspCur->vpnNodes.size() - 1; - - uint64 uRateMax = 0; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; - - const bool bPrvAccount = isSetBit(pnPrv.uFlags, STPathElement::typeAccount); - const bool bNxtAccount = isSetBit(pnNxt.uFlags, STPathElement::typeAccount); - - const uint160& uCurAccountID = pnCur.uAccountID; - const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; - const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. - - const uint160& uCurrencyID = pnCur.uCurrencyID; - - uint32 uQualityIn = uIndex ? rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; - uint32 uQualityOut = uIndex == uLast ? rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; - - // For bNxtAccount - const STAmount& saPrvRedeemReq = pnPrv.saFwdRedeem; - STAmount saPrvRedeemAct(saPrvRedeemReq.getCurrency(), saPrvRedeemReq.getIssuer()); - - const STAmount& saPrvIssueReq = pnPrv.saFwdIssue; - STAmount saPrvIssueAct(saPrvIssueReq.getCurrency(), saPrvIssueReq.getIssuer()); - - // For !bPrvAccount - const STAmount& saPrvDeliverReq = pnPrv.saRevDeliver; - STAmount saPrvDeliverAct(saPrvDeliverReq.getCurrency(), saPrvDeliverReq.getIssuer()); - - // For bNxtAccount - const STAmount& saCurRedeemReq = pnCur.saRevRedeem; - STAmount& saCurRedeemAct = pnCur.saFwdRedeem; - - const STAmount& saCurIssueReq = pnCur.saRevIssue; - STAmount& saCurIssueAct = pnCur.saFwdIssue; - - // For !bNxtAccount - const STAmount& saCurDeliverReq = pnCur.saRevDeliver; - STAmount& saCurDeliverAct = pnCur.saFwdDeliver; - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uIndex=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s") - % uIndex - % uLast - % saPrvRedeemReq.getFullText() - % saPrvIssueReq.getFullText() - % saPrvDeliverReq.getFullText() - % saCurRedeemReq.getFullText() - % saCurIssueReq.getFullText() - % saCurDeliverReq.getFullText()); - - // Ripple through account. - - if (bPrvAccount && bNxtAccount) - { - if (!uIndex) - { - // ^ --> ACCOUNT --> account - - // First node, calculate amount to send. - // XXX Use stamp/ripple balance - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - - const STAmount& saCurRedeemReq = pnCur.saRevRedeem; - STAmount& saCurRedeemAct = pnCur.saFwdRedeem; - const STAmount& saCurIssueReq = pnCur.saRevIssue; - STAmount& saCurIssueAct = pnCur.saFwdIssue; - - const STAmount& saCurSendMaxReq = pspCur->saInReq; // Negative for no limit, doing a calculation. - STAmount& saCurSendMaxAct = pspCur->saInAct; // Report to user how much this sends. - - if (saCurRedeemReq) - { - // Redeem requested. - saCurRedeemAct = saCurRedeemReq.isNegative() - ? saCurRedeemReq - : std::min(saCurRedeemReq, saCurSendMaxReq); - } - else - { - saCurRedeemAct = STAmount(saCurRedeemReq); - } - saCurSendMaxAct = saCurRedeemAct; - - if (saCurIssueReq && (saCurSendMaxReq.isNegative() || saCurSendMaxReq != saCurRedeemAct)) - { - // Issue requested and not over budget. - saCurIssueAct = saCurSendMaxReq.isNegative() - ? saCurIssueReq - : std::min(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); - } - else - { - saCurIssueAct = STAmount(saCurIssueReq); - } - saCurSendMaxAct += saCurIssueAct; - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saCurSendMaxReq=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s") - % saCurSendMaxReq.getFullText() - % saCurRedeemAct.getFullText() - % saCurIssueReq.getFullText() - % saCurIssueAct.getFullText()); - } - else if (uIndex == uLast) - { - // account --> ACCOUNT --> $ - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s") - % NewcoinAddress::createHumanAccountID(uPrvAccountID) - % NewcoinAddress::createHumanAccountID(uCurAccountID) - % saPrvRedeemReq.getFullText() - % saPrvIssueReq.getFullText()); - - // Last node. Accept all funds. Calculate amount actually to credit. - - STAmount& saCurReceive = pspCur->saOutAct; - - STAmount saIssueCrd = uQualityIn >= QUALITY_ONE - ? saPrvIssueReq // No fee. - : STAmount::multiply(saPrvIssueReq, uQualityIn, uCurrencyID, saPrvIssueReq.getIssuer()); // Fee. - - // Amount to credit. - saCurReceive = saPrvRedeemReq+saIssueCrd; - - // Actually receive. - rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); - } - else - { - // account --> ACCOUNT --> account - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account")); - - // Previous redeem part 1: redeem -> redeem - if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. - { - // Rate : 1.0 : quality out - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); - } - - // Previous issue part 1: issue -> redeem - if (saPrvIssueReq != saPrvIssueAct // Previous wants to issue. - && saCurRedeemReq != saCurRedeemAct) // Current has more to redeem to next. - { - // Rate: quality in : quality out - calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); - } - - // Previous redeem part 2: redeem -> issue. - // wants to redeem and current would and can issue. - // If redeeming cur to next is done, this implies can issue. - if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem. - && saCurRedeemReq == saCurRedeemAct // Current has no more to redeem to next. - && saCurIssueReq) - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); - } - - // Previous issue part 2 : issue -> issue - if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. - { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); - } - - // Adjust prv --> cur balance : take all inbound - // XXX Currency must be in amount. - rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); - } - } - else if (bPrvAccount && !bNxtAccount) - { - // account --> ACCOUNT --> offer - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); - - // redeem -> issue. - // wants to redeem and current would and can issue. - // If redeeming cur to next is done, this implies can issue. - if (saPrvRedeemReq) // Previous wants to redeem. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); - } - - // issue -> issue - if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. - && saPrvIssueReq) // Previous wants to issue. To next must be ok. - { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); - } - - // Adjust prv --> cur balance : take all inbound - // XXX Currency must be in amount. - rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); - } - else if (!bPrvAccount && bNxtAccount) - { - if (uIndex == uLast) - { - // offer --> ACCOUNT --> $ - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $")); - - STAmount& saCurReceive = pspCur->saOutAct; - - // Amount to credit. - saCurReceive = saPrvDeliverAct; - - // No income balance adjustments necessary. The paying side inside the offer paid to this account. - } - else - { - // offer --> ACCOUNT --> account - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account")); - - // deliver -> redeem - if (saPrvDeliverReq) // Previous wants to deliver. - { - // Rate : 1.0 : quality out - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); - } - - // deliver -> issue - // Wants to redeem and current would and can issue. - if (saPrvDeliverReq != saPrvDeliverAct // Previous still wants to deliver. - && saCurRedeemReq == saCurRedeemAct // Current has more to redeem to next. - && saCurIssueReq) // Current wants issue. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); - } - - // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. - } - } - else - { - // offer --> ACCOUNT --> offer - // deliver/redeem -> deliver/issue. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer")); - - if (saPrvDeliverReq // Previous wants to deliver - && saCurIssueReq) // Current wants issue. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); - } - - // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. - } - - return terResult; -} - -// Return true, iff lhs has less priority than rhs. -bool PathState::lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs) -{ - if (lhs->uQuality != rhs->uQuality) - return lhs->uQuality > rhs->uQuality; // Bigger is worse. - - // Best quanity is second rank. - if (lhs->saOutAct != rhs->saOutAct) - return lhs->saOutAct < rhs->saOutAct; // Smaller is worse. - - // Path index is third rank. - return lhs->mIndex > rhs->mIndex; // Bigger is worse. -} - -// Make sure the path delivers to uAccountID: uCurrencyID from uIssuerID. -// -// Rules: -// - Currencies must be converted via an offer. -// - A node names it's output. -// - A ripple nodes output issuer must be the node's account or the next node's account. -// - Offers can only go directly to another offer if the currency and issuer are an exact match. -TER PathState::pushImply( - const uint160& uAccountID, // --> Delivering to this account. - const uint160& uCurrencyID, // --> Delivering this currency. - const uint160& uIssuerID) // --> Delivering this issuer. -{ - const PaymentNode& pnPrv = vpnNodes.back(); - TER terResult = tesSUCCESS; - - Log(lsINFO) << "pushImply> " - << NewcoinAddress::createHumanAccountID(uAccountID) - << " " << STAmount::createHumanCurrency(uCurrencyID) - << " " << NewcoinAddress::createHumanAccountID(uIssuerID); - - if (pnPrv.uCurrencyID != uCurrencyID) - { - // Currency is different, need to convert via an offer. - - terResult = pushNode( - STPathElement::typeCurrency // Offer. - | STPathElement::typeIssuer, - ACCOUNT_ONE, // Placeholder for offers. - uCurrencyID, // The offer's output is what is now wanted. - uIssuerID); - - } - - // For ripple, non-stamps, ensure the issuer is on at least one side of the transaction. - if (tesSUCCESS == terResult - && !!uCurrencyID // Not stamps. - && (pnPrv.uAccountID != uIssuerID // Previous is not issuing own IOUs. - && uAccountID != uIssuerID)) // Current is not receiving own IOUs. - { - // Need to ripple through uIssuerID's account. - - terResult = pushNode( - STPathElement::typeAccount, - uIssuerID, // Intermediate account is the needed issuer. - uCurrencyID, - uIssuerID); - } - - Log(lsINFO) << "pushImply< " << terResult; - - return terResult; -} - -// Append a node and insert before it any implied nodes. -// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE -TER PathState::pushNode( - const int iType, - const uint160& uAccountID, - const uint160& uCurrencyID, - const uint160& uIssuerID) -{ - Log(lsINFO) << "pushNode> " - << NewcoinAddress::createHumanAccountID(uAccountID) - << " " << STAmount::createHumanCurrency(uCurrencyID) - << "/" << NewcoinAddress::createHumanAccountID(uIssuerID); - PaymentNode pnCur; - const bool bFirst = vpnNodes.empty(); - const PaymentNode& pnPrv = bFirst ? PaymentNode() : vpnNodes.back(); - // true, iff node is a ripple account. false, iff node is an offer node. - const bool bAccount = isSetBit(iType, STPathElement::typeAccount); - // true, iff currency supplied. - // Currency is specified for the output of the current node. - const bool bCurrency = isSetBit(iType, STPathElement::typeCurrency); - // Issuer is specified for the output of the current node. - const bool bIssuer = isSetBit(iType, STPathElement::typeIssuer); - TER terResult = tesSUCCESS; - - pnCur.uFlags = iType; - - if (iType & ~STPathElement::typeValidBits) - { - Log(lsINFO) << "pushNode: bad bits."; - - terResult = temBAD_PATH; - } - else if (bAccount) - { - // Account link - - pnCur.uAccountID = uAccountID; - pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; - pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; - pnCur.saRevRedeem = STAmount(uCurrencyID, uAccountID); - pnCur.saRevIssue = STAmount(uCurrencyID, uAccountID); - - if (!bFirst) - { - // Add required intermediate nodes to deliver to current account. - terResult = pushImply( - pnCur.uAccountID, // Current account. - pnCur.uCurrencyID, // Wanted currency. - !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XNS); // Account as issuer. - } - - if (tesSUCCESS == terResult && !vpnNodes.empty()) - { - const PaymentNode& pnBck = vpnNodes.back(); - bool bBckAccount = isSetBit(pnBck.uFlags, STPathElement::typeAccount); - - if (bBckAccount) - { - SLE::pointer sleRippleState = mLedger->getSLE(Ledger::getRippleStateIndex(pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); - - if (!sleRippleState) - { - Log(lsINFO) << "pushNode: No credit line between " - << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) - << " and " - << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) - << " for " - << STAmount::createHumanCurrency(pnPrv.uCurrencyID) - << "." ; - - Log(lsINFO) << getJson(); - - terResult = terNO_LINE; - } - else - { - Log(lsINFO) << "pushNode: Credit line found between " - << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) - << " and " - << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) - << " for " - << STAmount::createHumanCurrency(pnPrv.uCurrencyID) - << "." ; - } - } - } - - if (tesSUCCESS == terResult) - vpnNodes.push_back(pnCur); - } - else - { - // Offer link - // Offers bridge a change in currency & issuer or just a change in issuer. - pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; - pnCur.uIssuerID = bIssuer ? uIssuerID : pnCur.uAccountID; - pnCur.saRateMax = saZero; - - if (!!pnPrv.uAccountID) - { - // Previous is an account. - - // Insert intermediary issuer account if needed. - terResult = pushImply( - !!pnPrv.uCurrencyID - ? ACCOUNT_ONE // Rippling, but offer's don't have an account. - : ACCOUNT_XNS, - pnPrv.uCurrencyID, - pnPrv.uIssuerID); - } - - if (tesSUCCESS == terResult) - { - vpnNodes.push_back(pnCur); - } - } - Log(lsINFO) << "pushNode< " << terResult; - - return terResult; -} - -PathState::PathState( - Ledger::ref lpLedger, - const int iIndex, - const LedgerEntrySet& lesSource, - const STPath& spSourcePath, - const uint160& uReceiverID, - const uint160& uSenderID, - const STAmount& saSend, - const STAmount& saSendMax - ) - : mLedger(lpLedger), mIndex(iIndex), uQuality(0) -{ - const uint160 uInCurrencyID = saSendMax.getCurrency(); - const uint160 uOutCurrencyID = saSend.getCurrency(); - const uint160 uInIssuerID = !!uInCurrencyID ? saSendMax.getIssuer() : ACCOUNT_XNS; - const uint160 uOutIssuerID = !!uOutCurrencyID ? saSend.getIssuer() : ACCOUNT_XNS; - - lesEntries = lesSource.duplicate(); - - saInReq = saSendMax; - saOutReq = saSend; - - // Push sending node. - terStatus = pushNode( - STPathElement::typeAccount - | STPathElement::typeCurrency - | STPathElement::typeIssuer, - uSenderID, - uInCurrencyID, - uInIssuerID); - - BOOST_FOREACH(const STPathElement& speElement, spSourcePath) - { - if (tesSUCCESS == terStatus) - terStatus = pushNode(speElement.getNodeType(), speElement.getAccountID(), speElement.getCurrency(), speElement.getIssuerID()); - } - - if (tesSUCCESS == terStatus) - { - // Create receiver node. - - terStatus = pushImply(uReceiverID, uOutCurrencyID, uOutIssuerID); - if (tesSUCCESS == terStatus) - { - terStatus = pushNode( - STPathElement::typeAccount // Last node is always an account. - | STPathElement::typeCurrency - | STPathElement::typeIssuer, - uReceiverID, // Receive to output - uOutCurrencyID, // Desired currency - uOutIssuerID); - } - } - - if (tesSUCCESS == terStatus) - { - // Look for first mention of source in nodes and detect loops. - // Note: The output is not allowed to be a source. - - const unsigned int uNodes = vpnNodes.size(); - - for (unsigned int uIndex = 0; tesSUCCESS == terStatus && uIndex != uNodes; ++uIndex) - { - const PaymentNode& pnCur = vpnNodes[uIndex]; - - if (!!pnCur.uAccountID) - { - // Source is a ripple line - nothing(); - } - else if (!umForward.insert(std::make_pair(boost::make_tuple(pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uIndex)).second) - { - // Failed to insert. Have a loop. - Log(lsINFO) << boost::str(boost::format("PathState: loop detected: %s") - % getJson()); - - terStatus = temBAD_PATH_LOOP; - } - } - } - - Log(lsINFO) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s") - % STAmount::createHumanCurrency(uInCurrencyID) - % NewcoinAddress::createHumanAccountID(uInIssuerID) - % STAmount::createHumanCurrency(uOutCurrencyID) - % NewcoinAddress::createHumanAccountID(uOutIssuerID) - % getJson()); -} - -Json::Value PathState::getJson() const -{ - Json::Value jvPathState(Json::objectValue); - Json::Value jvNodes(Json::arrayValue); - - BOOST_FOREACH(const PaymentNode& pnNode, vpnNodes) - { - Json::Value jvNode(Json::objectValue); - - Json::Value jvFlags(Json::arrayValue); - - if (pnNode.uFlags & STPathElement::typeAccount) - jvFlags.append("account"); - - jvNode["flags"] = jvFlags; - - if (pnNode.uFlags & STPathElement::typeAccount) - jvNode["account"] = NewcoinAddress::createHumanAccountID(pnNode.uAccountID); - - if (!!pnNode.uCurrencyID) - jvNode["currency"] = STAmount::createHumanCurrency(pnNode.uCurrencyID); - - if (!!pnNode.uIssuerID) - jvNode["issuer"] = NewcoinAddress::createHumanAccountID(pnNode.uIssuerID); - - // if (pnNode.saRevRedeem) - jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText(); - - // if (pnNode.saRevIssue) - jvNode["rev_issue"] = pnNode.saRevIssue.getFullText(); - - // if (pnNode.saRevDeliver) - jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText(); - - // if (pnNode.saFwdRedeem) - jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText(); - - // if (pnNode.saFwdIssue) - jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText(); - - // if (pnNode.saFwdDeliver) - jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText(); - - jvNodes.append(jvNode); - } - - jvPathState["status"] = terStatus; - jvPathState["index"] = mIndex; - jvPathState["nodes"] = jvNodes; - - if (saInReq) - jvPathState["in_req"] = saInReq.getJson(0); - - if (saInAct) - jvPathState["in_act"] = saInAct.getJson(0); - - if (saOutReq) - jvPathState["out_req"] = saOutReq.getJson(0); - - if (saOutAct) - jvPathState["out_act"] = saOutAct.getJson(0); - - if (uQuality) - jvPathState["uQuality"] = Json::Value::UInt(uQuality); - - return jvPathState; -} - -TER TransactionEngine::calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) -{ - const PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); - - Log(lsINFO) << boost::str(boost::format("calcNodeFwd> uIndex=%d") % uIndex); - - TER terResult = bCurAccount - ? calcNodeAccountFwd(uIndex, pspCur, bMultiQuality) - : calcNodeOfferFwd(uIndex, pspCur, bMultiQuality); - - if (tesSUCCESS == terResult && uIndex + 1 != pspCur->vpnNodes.size()) - { - terResult = calcNodeFwd(uIndex+1, pspCur, bMultiQuality); - } - - Log(lsINFO) << boost::str(boost::format("calcNodeFwd< uIndex=%d terResult=%d") % uIndex % terResult); - - return terResult; -} - -// Calculate a node and its previous nodes. -// From the destination work in reverse towards the source calculating how much must be asked for. -// Then work forward, figuring out how much can actually be delivered. -// <-- terResult: tesSUCCESS or tepPATH_DRY -// <-> pnNodes: -// --> [end]saWanted.mAmount -// --> [all]saWanted.mCurrency -// --> [all]saAccount -// <-> [0]saWanted.mAmount : --> limit, <-- actual -TER TransactionEngine::calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) -{ - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); - TER terResult; - - // Do current node reverse. - const uint160& uCurIssuerID = pnCur.uIssuerID; - STAmount& saTransferRate = pnCur.saTransferRate; - - saTransferRate = STAmount::saFromRate(rippleTransferRate(uCurIssuerID)); - - Log(lsINFO) << boost::str(boost::format("calcNodeRev> uIndex=%d uIssuerID=%s saTransferRate=%s") - % uIndex - % NewcoinAddress::createHumanAccountID(uCurIssuerID) - % saTransferRate.getFullText()); - - terResult = bCurAccount - ? calcNodeAccountRev(uIndex, pspCur, bMultiQuality) - : calcNodeOfferRev(uIndex, pspCur, bMultiQuality); - - // Do previous. - if (tesSUCCESS != terResult) - { - // Error, don't continue. - nothing(); - } - else if (uIndex) - { - // Continue in reverse. - - terResult = calcNodeRev(uIndex-1, pspCur, bMultiQuality); - } - - Log(lsINFO) << boost::str(boost::format("calcNodeRev< uIndex=%d terResult=%s/%d") % uIndex % transToken(terResult) % terResult); - - return terResult; -} - -// Calculate the next increment of a path. -// The increment is what can satisfy a portion or all of the requested output at the best quality. -// <-- pspCur->uQuality -void TransactionEngine::pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint) -{ - // The next state is what is available in preference order. - // This is calculated when referenced accounts changed. - const bool bMultiQuality = iPaths == 1; - const unsigned int uLast = pspCur->vpnNodes.size() - 1; - - Log(lsINFO) << "Path In: " << pspCur->getJson(); - - assert(pspCur->vpnNodes.size() >= 2); - - pspCur->vUnfundedBecame.clear(); - pspCur->umReverse.clear(); - - mNodes = lesCheckpoint; // Restore from checkpoint. - mNodes.bumpSeq(); // Begin ledger varance. - - pspCur->terStatus = calcNodeRev(uLast, pspCur, bMultiQuality); - - Log(lsINFO) << "Path after reverse: " << pspCur->getJson(); - - if (tesSUCCESS == pspCur->terStatus) - { - // Do forward. - mNodes = lesCheckpoint; // Restore from checkpoint. - mNodes.bumpSeq(); // Begin ledger varance. - - pspCur->terStatus = calcNodeFwd(0, pspCur, bMultiQuality); - - pspCur->uQuality = tesSUCCESS == pspCur->terStatus - ? STAmount::getRate(pspCur->saOutAct, pspCur->saInAct) // Calculate relative quality. - : 0; // Mark path as inactive. - - Log(lsINFO) << "Path after forward: " << pspCur->getJson(); - } -} - -// XXX Need to audit for things like setting accountID not having memory. -TER TransactionEngine::doPayment(const SerializedTransaction& txn) -{ - // Ripple if source or destination is non-native or if there are paths. - const uint32 uTxFlags = txn.getFlags(); - const bool bCreate = isSetBit(uTxFlags, tfCreateAccount); - const bool bPartialPayment = isSetBit(uTxFlags, tfPartialPayment); - const bool bLimitQuality = isSetBit(uTxFlags, tfLimitQuality); - const bool bNoRippleDirect = isSetBit(uTxFlags, tfNoRippleDirect); - const bool bPaths = txn.getITFieldPresent(sfPaths); - const bool bMax = txn.getITFieldPresent(sfSendMax); - const uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - const STAmount saDstAmount = txn.getITFieldAmount(sfAmount); - const STAmount saMaxAmount = bMax ? txn.getITFieldAmount(sfSendMax) : saDstAmount; - const uint160 uSrcCurrency = saMaxAmount.getCurrency(); - const uint160 uDstCurrency = saDstAmount.getCurrency(); - - Log(lsINFO) << boost::str(boost::format("doPayment> saMaxAmount=%s saDstAmount=%s") - % saMaxAmount.getFullText() - % saDstAmount.getFullText()); - - if (!uDstAccountID) - { - Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specifed."; - - return temDST_NEEDED; - } - else if (!saDstAmount.isPositive()) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad amount: " << saDstAmount.getHumanCurrency() << " " << saDstAmount.getText(); - - return temBAD_AMOUNT; - } - else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) - { - Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redunant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") - % mTxnAccountID.ToString() - % uDstAccountID.ToString() - % uSrcCurrency.ToString() - % uDstCurrency.ToString()); - - return temREDUNDANT; - } - else if (bMax - && ((saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency()) - || (saDstAmount.isNative() && saMaxAmount.isNative()))) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad SendMax."; - - return temINVALID; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - // Destination account does not exist. - if (bCreate && !saDstAmount.isNative()) - { - // This restriction could be relaxed. - Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XNS."; - - return temCREATEXNS; - } - else if (!bCreate) - { - Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; - - return terNO_DST; - } - - // Create the account. - sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - sleDst->setIFieldAccount(sfAccount, uDstAccountID); - sleDst->setIFieldU32(sfSequence, 1); - } - else - { - entryModify(sleDst); - } - - // XXX Should bMax be sufficient to imply ripple? - const bool bRipple = bPaths || bMax || !saDstAmount.isNative(); - - if (!bRipple) - { - // Direct XNS payment. - STAmount saSrcXNSBalance = mTxnAccount->getIValueFieldAmount(sfBalance); - - if (saSrcXNSBalance < saDstAmount) - { - // Transaction might succeed, if applied in a different order. - Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; - - return terUNFUNDED; - } - - mTxnAccount->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); - sleDst->setIFieldAmount(sfBalance, sleDst->getIValueFieldAmount(sfBalance) + saDstAmount); - - return tesSUCCESS; - } - - // - // Ripple payment - // - - STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); - - if (bNoRippleDirect && spsPaths.isEmpty()) - { - Log(lsINFO) << "doPayment: Invalid transaction: No paths and direct ripple not allowed."; - - return temRIPPLE_EMPTY; - } - - // XXX Skip check if final processing. - if (spsPaths.getPathCount() > RIPPLE_PATHS_MAX) - { - return telBAD_PATH_COUNT; - } - - // Incrementally search paths. - std::vector vpsPaths; - - TER terResult = temUNCERTAIN; - - if (!bNoRippleDirect) - { - // Direct path. - // XXX Might also make a stamp bridge by default. - Log(lsINFO) << "doPayment: Build direct:"; - - PathState::pointer pspDirect = PathState::createPathState( - mLedger, - vpsPaths.size(), - mNodes, - STPath(), - uDstAccountID, - mTxnAccountID, - saDstAmount, - saMaxAmount); - - if (pspDirect) - { - // Return if malformed. - if (pspDirect->terStatus >= temMALFORMED && pspDirect->terStatus < tefFAILURE) - return pspDirect->terStatus; - - if (tesSUCCESS == pspDirect->terStatus) - { - // Had a success. - terResult = tesSUCCESS; - - vpsPaths.push_back(pspDirect); - } - } - } - - Log(lsINFO) << "doPayment: Paths in set: " << spsPaths.getPathCount(); - - BOOST_FOREACH(const STPath& spPath, spsPaths) - { - Log(lsINFO) << "doPayment: Build path:"; - - PathState::pointer pspExpanded = PathState::createPathState( - mLedger, - vpsPaths.size(), - mNodes, - spPath, - uDstAccountID, - mTxnAccountID, - saDstAmount, - saMaxAmount); - - if (pspExpanded) - { - // Return if malformed. - if (pspExpanded->terStatus >= temMALFORMED && pspExpanded->terStatus < tefFAILURE) - return pspExpanded->terStatus; - - if (tesSUCCESS == pspExpanded->terStatus) - { - // Had a success. - terResult = tesSUCCESS; - } - - vpsPaths.push_back(pspExpanded); - } - } - - if (vpsPaths.empty()) - { - return tefEXCEPTION; - } - else if (tesSUCCESS != terResult) - { - // No path successes. - - return vpsPaths[0]->terStatus; - } - else - { - terResult = temUNCERTAIN; - } - - STAmount saPaid; - STAmount saWanted; - const LedgerEntrySet lesBase = mNodes; // Checkpoint with just fees paid. - const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmount, saMaxAmount) : 0; - - while (temUNCERTAIN == terResult) - { - PathState::pointer pspBest; - const LedgerEntrySet lesCheckpoint = mNodes; - - // Find the best path. - BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths) - { - pathNext(pspCur, vpsPaths.size(), lesCheckpoint); // Compute increment. - - if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality. - || !pspBest // Best is not yet set. - || (pspCur->uQuality && PathState::lessPriority(pspBest, pspCur))) // Current is better than set. - { - mNodes.swapWith(pspCur->lesEntries); // For the path, save ledger state. - pspBest = pspCur; - } - } - - if (pspBest) - { - // Apply best path. - - // Record best pass' offers that became unfunded for deletion on success. - mvUnfundedBecame.insert(mvUnfundedBecame.end(), pspBest->vUnfundedBecame.begin(), pspBest->vUnfundedBecame.end()); - - // Record best pass' LedgerEntrySet to build off of and potentially return. - mNodes.swapWith(pspBest->lesEntries); - - // Figure out if done. - if (temUNCERTAIN == terResult && saPaid == saWanted) - { - terResult = tesSUCCESS; - } - else - { - // Prepare for next pass. - - // Merge best pass' umReverse. - mumSource.insert(pspBest->umReverse.begin(), pspBest->umReverse.end()); - } - } - // Not done and ran out of paths. - else if (!bPartialPayment) - { - // Partial payment not allowed. - terResult = tepPATH_PARTIAL; - mNodes = lesBase; // Revert to just fees charged. - } - // Partial payment ok. - else if (!saPaid) - { - // No payment at all. - terResult = tepPATH_DRY; - mNodes = lesBase; // Revert to just fees charged. - } - else - { - terResult = tesSUCCESS; - } - } - - if (tesSUCCESS == terResult) - { - // Delete became unfunded offers. - BOOST_FOREACH(const uint256& uOfferIndex, mvUnfundedBecame) - { - if (tesSUCCESS == terResult) - terResult = offerDelete(uOfferIndex); - } - } - - // Delete found unfunded offers. - BOOST_FOREACH(const uint256& uOfferIndex, musUnfundedFound) - { - if (tesSUCCESS == terResult) - terResult = offerDelete(uOfferIndex); - } - - std::string strToken; - std::string strHuman; - - if (transResultInfo(terResult, strToken, strHuman)) - { - Log(lsINFO) << boost::str(boost::format("doPayment: %s: %s") % strToken % strHuman); - } - else - { - assert(false); - } - - return terResult; -} - -TER TransactionEngine::doWalletAdd(const SerializedTransaction& txn) -{ - std::cerr << "WalletAdd>" << std::endl; - - const std::vector vucPubKey = txn.getITFieldVL(sfPubKey); - const std::vector vucSignature = txn.getITFieldVL(sfSignature); - const uint160 uAuthKeyID = txn.getITFieldAccount(sfAuthorizedKey); - const NewcoinAddress naMasterPubKey = NewcoinAddress::createAccountPublic(vucPubKey); - const uint160 uDstAccountID = naMasterPubKey.getAccountID(); - - if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature)) - { - std::cerr << "WalletAdd: unauthorized: bad signature " << std::endl; - - return tefBAD_ADD_AUTH; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - if (sleDst) - { - std::cerr << "WalletAdd: account already created" << std::endl; - - return tefCREATED; - } - - STAmount saAmount = txn.getITFieldAmount(sfAmount); - STAmount saSrcBalance = mTxnAccount->getIValueFieldAmount(sfBalance); - - if (saSrcBalance < saAmount) - { - std::cerr - << boost::str(boost::format("WalletAdd: Delay transaction: insufficent balance: balance=%s amount=%s") - % saSrcBalance.getText() - % saAmount.getText()) - << std::endl; - - return terUNFUNDED; - } - - // Deduct initial balance from source account. - mTxnAccount->setIFieldAmount(sfBalance, saSrcBalance-saAmount); - - // Create the account. - sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - sleDst->setIFieldAccount(sfAccount, uDstAccountID); - sleDst->setIFieldU32(sfSequence, 1); - sleDst->setIFieldAmount(sfBalance, saAmount); - sleDst->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); - - std::cerr << "WalletAdd<" << std::endl; - - return tesSUCCESS; -} - -TER TransactionEngine::doInvoice(const SerializedTransaction& txn) -{ - return temUNKNOWN; -} - -// Take as much as possible. Adjusts account balances. Charges fees on top to taker. -// --> uBookBase: The order book to take against. -// --> saTakerPays: What the taker offers (w/ issuer) -// --> saTakerGets: What the taker wanted (w/ issuer) -// <-- saTakerPaid: What taker paid not including fees. To reduce an offer. -// <-- saTakerGot: What taker got not including fees. To reduce an offer. -// <-- terResult: tesSUCCESS or terNO_ACCOUNT -// XXX: Fees should be paid by the source of the currency. -TER TransactionEngine::takeOffers( - bool bPassive, - const uint256& uBookBase, - const uint160& uTakerAccountID, - const SLE::pointer& sleTakerAccount, - const STAmount& saTakerPays, - const STAmount& saTakerGets, - STAmount& saTakerPaid, - STAmount& saTakerGot) -{ - assert(saTakerPays && saTakerGets); - - Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); - - uint256 uTipIndex = uBookBase; - const uint256 uBookEnd = Ledger::getQualityNext(uBookBase); - const uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays); - const uint160 uTakerPaysAccountID = saTakerPays.getIssuer(); - const uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); - TER terResult = temUNCERTAIN; - - boost::unordered_set usOfferUnfundedFound; // Offers found unfunded. - boost::unordered_set usOfferUnfundedBecame; // Offers that became unfunded. - boost::unordered_set usAccountTouched; // Accounts touched. - - saTakerPaid = 0; - saTakerGot = 0; - - while (temUNCERTAIN == terResult) - { - SLE::pointer sleOfferDir; - uint64 uTipQuality; - - // Figure out next offer to take, if needed. - if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) - { - // Taker has needs. - - sleOfferDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uTipIndex, uBookEnd)); - if (sleOfferDir) - { - Log(lsINFO) << "takeOffers: possible counter offer found"; - - uTipIndex = sleOfferDir->getIndex(); - uTipQuality = Ledger::getQuality(uTipIndex); - } - else - { - Log(lsINFO) << "takeOffers: counter offer book is empty: " - << uTipIndex.ToString() - << " ... " - << uBookEnd.ToString(); - } - } - - if (!sleOfferDir // No offer directory to take. - || uTakeQuality < uTipQuality // No offer's of sufficient quality available. - || (bPassive && uTakeQuality == uTipQuality)) - { - // Done. - Log(lsINFO) << "takeOffers: done"; - - terResult = tesSUCCESS; - } - else - { - // Have an offer directory to consider. - Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); - - SLE::pointer sleBookNode; - unsigned int uBookEntry; - uint256 uOfferIndex; - - dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); - - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); - - const uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. Expired offers are considered unfunded. Delete it. - Log(lsINFO) << "takeOffers: encountered expired offer"; - - usOfferUnfundedFound.insert(uOfferIndex); - } - else if (uOfferOwnerID == uTakerAccountID) - { - // Would take own offer. Consider old offer expired. Delete it. - Log(lsINFO) << "takeOffers: encountered taker's own old offer"; - - usOfferUnfundedFound.insert(uOfferIndex); - } - else - { - // Get offer funds available. - - Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); - - STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); - STAmount saTakerFunds = accountFunds(uTakerAccountID, saTakerPays); - SLE::pointer sleOfferAccount; // Owner of offer. - - if (!saOfferFunds.isPositive()) - { - // Offer is unfunded, possibly due to previous balance action. - Log(lsINFO) << "takeOffers: offer unfunded: delete"; - - boost::unordered_set::iterator account = usAccountTouched.find(uOfferOwnerID); - if (account != usAccountTouched.end()) - { - // Previously touched account. - usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. - } - else - { - // Never touched source account. - usOfferUnfundedFound.insert(uOfferIndex); // Delete found unfunded offer when possible. - } - } - else - { - STAmount saPay = saTakerPays - saTakerPaid; - if (saTakerFunds < saPay) - saPay = saTakerFunds; - STAmount saSubTakerPaid; - STAmount saSubTakerGot; - - Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText(); - - bool bOfferDelete = STAmount::applyOffer( - saOfferFunds, - saPay, // Driver XXX need to account for fees. - saOfferPays, - saOfferGets, - saTakerPays, - saTakerGets, - saSubTakerPaid, - saSubTakerGot); - - Log(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); - - // Adjust offer - - // Offer owner will pay less. Subtract what taker just got. - sleOffer->setIFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); - - // Offer owner will get less. Subtract what owner just paid. - sleOffer->setIFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); - - entryModify(sleOffer); - - if (bOfferDelete) - { - // Offer now fully claimed or now unfunded. - Log(lsINFO) << "takeOffers: offer claimed: delete"; - - usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. - - // Offer owner's account is no longer pristine. - usAccountTouched.insert(uOfferOwnerID); - } - else - { - Log(lsINFO) << "takeOffers: offer partial claim."; - } - - // Offer owner pays taker. - saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? - - accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); - - saTakerGot += saSubTakerGot; - - // Taker pays offer owner. - saSubTakerPaid.setIssuer(uTakerPaysAccountID); - - accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); - - saTakerPaid += saSubTakerPaid; - } - } - } - } - - // On storing meta data, delete offers that were found unfunded to prevent encountering them in future. - if (tesSUCCESS == terResult) - { - BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedFound) - { - terResult = offerDelete(uOfferIndex); - if (tesSUCCESS != terResult) - break; - } - } - - if (tesSUCCESS == terResult) - { - // On success, delete offers that became unfunded. - BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) - { - terResult = offerDelete(uOfferIndex); - if (tesSUCCESS != terResult) - break; - } - } - - return terResult; -} - -TER TransactionEngine::doOfferCreate(const SerializedTransaction& txn) -{ -Log(lsWARNING) << "doOfferCreate> " << txn.getJson(0); - const uint32 txFlags = txn.getFlags(); - const bool bPassive = isSetBit(txFlags, tfPassive); - STAmount saTakerPays = txn.getITFieldAmount(sfTakerPays); - STAmount saTakerGets = txn.getITFieldAmount(sfTakerGets); -Log(lsWARNING) << "doOfferCreate: saTakerPays=" << saTakerPays.getFullText(); -Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); - const uint160 uPaysIssuerID = saTakerPays.getIssuer(); - const uint160 uGetsIssuerID = saTakerGets.getIssuer(); - const uint32 uExpiration = txn.getITFieldU32(sfExpiration); - const bool bHaveExpiration = txn.getITFieldPresent(sfExpiration); - const uint32 uSequence = txn.getSequence(); - - const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); - SLE::pointer sleOffer = entryCreate(ltOFFER, uLedgerIndex); - - Log(lsINFO) << "doOfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence; - - const uint160 uPaysCurrency = saTakerPays.getCurrency(); - const uint160 uGetsCurrency = saTakerGets.getCurrency(); - const uint64 uRate = STAmount::getRate(saTakerGets, saTakerPays); - - TER terResult = tesSUCCESS; - uint256 uDirectory; // Delete hints. - uint64 uOwnerNode; - uint64 uBookNode; - - if (bHaveExpiration && !uExpiration) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad expiration"; - - terResult = temBAD_EXPIRATION; - } - else if (bHaveExpiration && mLedger->getParentCloseTimeNC() >= uExpiration) - { - Log(lsWARNING) << "doOfferCreate: Expired transaction: offer expired"; - - // XXX CHARGE FEE ONLY. - terResult = tesSUCCESS; - } - else if (saTakerPays.isNative() && saTakerGets.isNative()) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: XNS for XNS"; - - terResult = temBAD_OFFER; - } - else if (!saTakerPays || !saTakerGets) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad amount"; - - terResult = temBAD_OFFER; - } - else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: redundant offer"; - - terResult = temREDUNDANT; - } - else if (saTakerPays.isNative() != !uPaysIssuerID || saTakerGets.isNative() != !uGetsIssuerID) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad issuer"; - - terResult = temBAD_ISSUER; - } - else if (!accountFunds(mTxnAccountID, saTakerGets).isPositive()) - { - Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded."; - - terResult = terUNFUNDED; - } - - if (tesSUCCESS == terResult && !saTakerPays.isNative()) - { - SLE::pointer sleTakerPays = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID)); - - if (!sleTakerPays) - { - Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existant issuer: " << NewcoinAddress::createHumanAccountID(uPaysIssuerID); - - terResult = terNO_ACCOUNT; - } - } - - if (tesSUCCESS == terResult) - { - STAmount saOfferPaid; - STAmount saOfferGot; - const uint256 uTakeBookBase = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); - - Log(lsINFO) << boost::str(boost::format("doOfferCreate: take against book: %s : %s/%s -> %s/%s") - % uTakeBookBase.ToString() - % saTakerGets.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()) - % saTakerPays.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer())); - - // Take using the parameters of the offer. - terResult = takeOffers( - bPassive, - uTakeBookBase, - mTxnAccountID, - mTxnAccount, - saTakerGets, - saTakerPays, - saOfferPaid, // How much was spent. - saOfferGot // How much was got. - ); - - Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult; - Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); - - if (tesSUCCESS == terResult) - { - saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got. - saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid. - } - } - - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()); - Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: funds=" << accountFunds(mTxnAccountID, saTakerGets).getFullText(); - - // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); - - if (tesSUCCESS == terResult - && saTakerPays // Still wanting something. - && saTakerGets // Still offering something. - && accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. - { - // We need to place the remainder of the offer into its order book. - - // Add offer to owner's directory. - terResult = dirAdd(uOwnerNode, Ledger::getOwnerDirIndex(mTxnAccountID), uLedgerIndex); - - if (tesSUCCESS == terResult) - { - uint256 uBookBase = Ledger::getBookBase(uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID); - - Log(lsINFO) << boost::str(boost::format("doOfferCreate: adding to book: %s : %s/%s -> %s/%s") - % uBookBase.ToString() - % saTakerPays.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer()) - % saTakerGets.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer())); - - uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. - - // Add offer to order book. - terResult = dirAdd(uBookNode, uDirectory, uLedgerIndex); - } - - if (tesSUCCESS == terResult) - { - // Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); - // Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); - // Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); - // Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); - // Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); - - sleOffer->setIFieldAccount(sfAccount, mTxnAccountID); - sleOffer->setIFieldU32(sfSequence, uSequence); - sleOffer->setIFieldH256(sfBookDirectory, uDirectory); - sleOffer->setIFieldAmount(sfTakerPays, saTakerPays); - sleOffer->setIFieldAmount(sfTakerGets, saTakerGets); - sleOffer->setIFieldU64(sfOwnerNode, uOwnerNode); - sleOffer->setIFieldU64(sfBookNode, uBookNode); - - if (uExpiration) - sleOffer->setIFieldU32(sfExpiration, uExpiration); - - if (bPassive) - sleOffer->setFlag(lsfPassive); - } - } - - return terResult; -} - -TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) -{ - TER terResult; - const uint32 uSequence = txn.getITFieldU32(sfOfferSequence); - const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - if (sleOffer) - { - Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; - - terResult = offerDelete(sleOffer, uOfferIndex, mTxnAccountID); - } - else - { - Log(lsWARNING) << "doOfferCancel: offer not found: " - << NewcoinAddress::createHumanAccountID(mTxnAccountID) - << " : " << uSequence - << " : " << uOfferIndex.ToString(); - - terResult = terOFFER_NOT_FOUND; - } - - return terResult; -} - -#if 0 -// XXX Need to adjust for fees. -// Find offers to satisfy pnDst. -// - Does not adjust any balances as there is at least a forward pass to come. -// --> pnDst.saWanted: currency and amount wanted -// --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use. -// --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, limit to use. -// <-- pnDst.saReceive -// <-- pnDst.saIOUForgive -// <-- pnDst.saIOUAccept -// <-- terResult : tesSUCCESS = no error and if !bAllowPartial complelely satisfied wanted. -// <-> usOffersDeleteAlways: -// <-> usOffersDeleteOnSuccess: -TER calcOfferFill(PaymentNode& pnSrc, PaymentNode& pnDst, bool bAllowPartial) -{ - TER terResult; - - if (pnDst.saWanted.isNative()) - { - // Transfer stamps. - - STAmount saSrcFunds = pnSrc.saAccount->accountHolds(pnSrc.saAccount, uint160(0), uint160(0)); - - if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted)) - { - pnSrc.saSend = min(saSrcFunds, pnDst.saWanted); - pnDst.saReceive = pnSrc.saSend; - } - else - { - terResult = terINSUF_PATH; - } - } - else - { - // Ripple funds. - - // Redeem to limit. - terResult = calcOfferFill( - accountHolds(pnSrc.saAccount, pnDst.saWanted.getCurrency(), pnDst.saWanted.getIssuer()), - pnSrc.saIOURedeem, - pnDst.saIOUForgive, - bAllowPartial); - - if (tesSUCCESS == terResult) - { - // Issue to wanted. - terResult = calcOfferFill( - pnDst.saWanted, // As much as wanted is available, limited by credit limit. - pnSrc.saIOUIssue, - pnDst.saIOUAccept, - bAllowPartial); - } - - if (tesSUCCESS == terResult && !bAllowPartial) - { - STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept; - - if (saTotal != saWanted) - terResult = terINSUF_PATH; - } - } - - return terResult; -} -#endif - -#if 0 -// Get the next offer limited by funding. -// - Stop when becomes unfunded. -void TransactionEngine::calcOfferBridgeNext( - const uint256& uBookRoot, // --> Which order book to look in. - const uint256& uBookEnd, // --> Limit of how far to look. - uint256& uBookDirIndex, // <-> Current directory. <-- 0 = no offer available. - uint64& uBookDirNode, // <-> Which node. 0 = first. - unsigned int& uBookDirEntry, // <-> Entry in node. 0 = first. - STAmount& saOfferIn, // <-- How much to pay in, fee inclusive, to get saOfferOut out. - STAmount& saOfferOut // <-- How much offer pays out. - ) -{ - saOfferIn = 0; // XXX currency & issuer - saOfferOut = 0; // XXX currency & issuer - - bool bDone = false; - - while (!bDone) - { - uint256 uOfferIndex; - - // Get uOfferIndex. - dirNext(uBookRoot, uBookEnd, uBookDirIndex, uBookDirNode, uBookDirEntry, uOfferIndex); - - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. - Log(lsINFO) << "calcOfferFirst: encountered expired offer"; - } - else - { - STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); - // Outbound fees are paid by offer owner. - // XXX Calculate outbound fee rate. - - if (saOfferPays.isNative()) - { - // No additional fees for stamps. - - nothing(); - } - else if (saOfferPays.getIssuer() == uOfferOwnerID) - { - // Offerer is issue own IOUs. - // No fees at this exact point, XXX receiving node may charge a fee. - // XXX Make sure has a credit line with receiver, limit by credit line. - - nothing(); - // XXX Broken - could be issuing or redeeming or both. - } - else - { - // Offer must be redeeming IOUs. - - // No additional - // XXX Broken - } - - if (!saOfferFunds.isPositive()) - { - // Offer is unfunded. - Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; - } - else if (saOfferFunds >= saOfferPays) - { - // Offer fully funded. - - // Account transfering funds in to offer always pays inbound fees. - - saOfferIn = saOfferGets; // XXX Add in fees? - - saOfferOut = saOfferPays; - - bDone = true; - } - else - { - // Offer partially funded. - - // saOfferIn/saOfferFunds = saOfferGets/saOfferPays - // XXX Round such that all saOffer funds are exhausted. - saOfferIn = (saOfferFunds*saOfferGets)/saOfferPays; // XXX Add in fees? - saOfferOut = saOfferFunds; - - bDone = true; - } - } - - if (!bDone) - { - // musUnfundedFound.insert(uOfferIndex); - } - } - while (bNext); -} -#endif - -#if 0 -// If either currency is not stamps, then also calculates vs stamp bridge. -// --> saWanted: Limit of how much is wanted out. -// <-- saPay: How much to pay into the offer. -// <-- saGot: How much to the offer pays out. Never more than saWanted. -// Given two value's enforce a minimum: -// - reverse: prv is maximum to pay in (including fee) - cur is what is wanted: generally, minimizing prv -// - forward: prv is actual amount to pay in (including fee) - cur is what is wanted: generally, minimizing cur -// Value in is may be rippled or credited from limbo. Value out is put in limbo. -// If next is an offer, the amount needed is in cur reedem. -// XXX What about account mentioned multiple times via offers? -void TransactionEngine::calcNodeOffer( - bool bForward, - bool bMultiQuality, // True, if this is the only active path: we can do multiple qualities in this pass. - const uint160& uPrvAccountID, // If 0, then funds from previous offer's limbo - const uint160& uPrvCurrencyID, - const uint160& uPrvIssuerID, - const uint160& uCurCurrencyID, - const uint160& uCurIssuerID, - - const STAmount& uPrvRedeemReq, // --> In limit. - STAmount& uPrvRedeemAct, // <-> In limit achived. - const STAmount& uCurRedeemReq, // --> Out limit. Driver when uCurIssuerID == uNxtIssuerID (offer would redeem to next) - STAmount& uCurRedeemAct, // <-> Out limit achived. - - const STAmount& uCurIssueReq, // --> In limit. - STAmount& uCurIssueAct, // <-> In limit achived. - const STAmount& uCurIssueReq, // --> Out limit. Driver when uCurIssueReq != uNxtIssuerID (offer would effectively issue or transfer to next) - STAmount& uCurIssueAct, // <-> Out limit achived. - - STAmount& saPay, - STAmount& saGot - ) const -{ - TER terResult = temUNKNOWN; - - // Direct: not bridging via XNS - bool bDirectNext = true; // True, if need to load. - uint256 uDirectQuality; - uint256 uDirectTip = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); - uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); - - // Bridging: bridging via XNS - bool bBridge = true; // True, if bridging active. False, missing an offer. - uint256 uBridgeQuality; - STAmount saBridgeIn; // Amount available. - STAmount saBridgeOut; - - bool bInNext = true; // True, if need to load. - STAmount saInIn; // Amount available. Consumed in loop. Limited by offer funding. - STAmount saInOut; - uint256 uInTip; // Current entry. - uint256 uInEnd; - unsigned int uInEntry; - - bool bOutNext = true; - STAmount saOutIn; - STAmount saOutOut; - uint256 uOutTip; - uint256 uOutEnd; - unsigned int uOutEntry; - - saPay.zero(); - saPay.setCurrency(uPrvCurrencyID); - saPay.setIssuer(uPrvIssuerID); - - saNeed = saWanted; - - if (!uCurCurrencyID && !uPrvCurrencyID) - { - // Bridging: Neither currency is XNS. - uInTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, CURRENCY_XNS, ACCOUNT_XNS); - uInEnd = Ledger::getQualityNext(uInTip); - uOutTip = Ledger::getBookBase(CURRENCY_XNS, ACCOUNT_XNS, uCurCurrencyID, uCurIssuerID); - uOutEnd = Ledger::getQualityNext(uInTip); - } - - // Find our head offer. - - bool bRedeeming = false; - bool bIssuing = false; - - // The price varies as we change between issuing and transfering, so unless bMultiQuality, we must stick with a mode once it - // is determined. - - if (bBridge && (bInNext || bOutNext)) - { - // Bridging and need to calculate next bridge rate. - // A bridge can consist of multiple offers. As offer's are consumed, the effective rate changes. - - if (bInNext) - { -// sleInDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uInIndex, uInEnd)); - // Get the next funded offer. - offerBridgeNext(uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding. - bInNext = false; - } - - if (bOutNext) - { -// sleOutDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uOutIndex, uOutEnd)); - offerNext(uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut); - bOutNext = false; - } - - if (!uInIndex || !uOutIndex) - { - bBridge = false; // No more offers to bridge. - } - else - { - // Have bridge in and out entries. - // Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost. - - saBridgeOut.zero(); - - if (saInOut < saOutIn) - { - // Limit by in. - - // XXX Need to include fees in saBridgeIn. - saBridgeIn = saInIn; // All of in - // Limit bridge out: saInOut/saBridgeOut = saOutIn/saOutOut - // Round such that we would take all of in offer, otherwise would have leftovers. - saBridgeOut = (saInOut * saOutOut) / saOutIn; - } - else if (saInOut > saOutIn) - { - // Limit by out, if at all. - - // XXX Need to include fees in saBridgeIn. - // Limit bridge in:saInIn/saInOuts = aBridgeIn/saOutIn - // Round such that would take all of out offer. - saBridgeIn = (saInIn * saOutIn) / saInOuts; - saBridgeOut = saOutOut; // All of out. - } - else - { - // Entries match, - - // XXX Need to include fees in saBridgeIn. - saBridgeIn = saInIn; // All of in - saBridgeOut = saOutOut; // All of out. - } - - uBridgeQuality = STAmount::getRate(saBridgeIn, saBridgeOut); // Inclusive of fees. - } - } - - if (bBridge) - { - bUseBridge = !uDirectTip || (uBridgeQuality < uDirectQuality) - } - else if (!!uDirectTip) - { - bUseBridge = false - } - else - { - // No more offers. Declare success, even if none returned. - saGot = saWanted-saNeed; - terResult = tesSUCCESS; - } - - if (tesSUCCESS != terResult) - { - STAmount& saAvailIn = bUseBridge ? saBridgeIn : saDirectIn; - STAmount& saAvailOut = bUseBridge ? saBridgeOut : saDirectOut; - - if (saAvailOut > saNeed) - { - // Consume part of offer. Done. - - saNeed = 0; - saPay += (saNeed*saAvailIn)/saAvailOut; // Round up, prefer to pay more. - } - else - { - // Consume entire offer. - - saNeed -= saAvailOut; - saPay += saAvailIn; - - if (bUseBridge) - { - // Consume bridge out. - if (saOutOut == saAvailOut) - { - // Consume all. - saOutOut = 0; - saOutIn = 0; - bOutNext = true; - } - else - { - // Consume portion of bridge out, must be consuming all of bridge in. - // saOutIn/saOutOut = saSpent/saAvailOut - // Round? - saOutIn -= (saOutIn*saAvailOut)/saOutOut; - saOutOut -= saAvailOut; - } - - // Consume bridge in. - if (saOutIn == saAvailIn) - { - // Consume all. - saInOut = 0; - saInIn = 0; - bInNext = true; - } - else - { - // Consume portion of bridge in, must be consuming all of bridge out. - // saInIn/saInOut = saAvailIn/saPay - // Round? - saInOut -= (saInOut*saAvailIn)/saInIn; - saInIn -= saAvailIn; - } - } - else - { - bDirectNext = true; - } - } - } -} -#endif - - -TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) -{ - Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); - - const uint32 expiration = txn.getITFieldU32(sfExpiration); - const uint32 bondAmount = txn.getITFieldU32(sfBondAmount); - const uint32 stampEscrow = txn.getITFieldU32(sfStampEscrow); - STAmount rippleEscrow = txn.getITFieldAmount(sfRippleEscrow); - std::vector createCode = txn.getITFieldVL(sfCreateCode); - std::vector fundCode = txn.getITFieldVL(sfFundCode); - std::vector removeCode = txn.getITFieldVL(sfRemoveCode); - std::vector expireCode = txn.getITFieldVL(sfExpireCode); - - // make sure - // expiration hasn't passed - // bond amount is enough - // they have the stamps for the bond - - // place contract in ledger - // run create code - - - if (mLedger->getParentCloseTimeNC() >= expiration) - { - Log(lsWARNING) << "doContractAdd: Expired transaction: offer expired"; - return(tefALREADY); - } - //TODO: check bond - //if( txn.getSourceAccount() ) - - Contract contract; - Script::Interpreter interpreter; - TER terResult=interpreter.interpret(&contract,txn,createCode); - if(tesSUCCESS != terResult) - { - - } - - return(terResult); -} - -TER TransactionEngine::doContractRemove(const SerializedTransaction& txn) -{ - // TODO: - return(tesSUCCESS); -} - // vim:ts=4 diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index b164ea2b01..70d464b7ae 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -1,8 +1,6 @@ #ifndef __TRANSACTIONENGINE__ #define __TRANSACTIONENGINE__ -#include -#include #include #include @@ -10,120 +8,11 @@ #include "SerializedTransaction.h" #include "SerializedLedger.h" #include "LedgerEntrySet.h" +#include "TransactionErr.h" // A TransactionEngine applies serialized transactions to a ledger // It can also, verify signatures, verify fees, and give rejection reasons -enum TER // aka TransactionEngineResult -{ - // Note: Range is stable. Exact numbers are currently unstable. Use tokens. - - // -399 .. -300: L Local error (transaction fee inadequate, exceeds local limit) - // Only valid during non-consensus processing. - // Implications: - // - Not forwarded - // - No fee check - telLOCAL_ERROR = -399, - telBAD_PATH_COUNT, - telINSUF_FEE_P, - - // -299 .. -200: M Malformed (bad signature) - // Causes: - // - Transaction corrupt. - // Implications: - // - Not applied - // - Not forwarded - // - Reject - // - Can not succeed in any imagined ledger. - temMALFORMED = -299, - temBAD_AMOUNT, - temBAD_AUTH_MASTER, - temBAD_EXPIRATION, - temBAD_ISSUER, - temBAD_OFFER, - temBAD_PATH, - temBAD_PATH_LOOP, - temBAD_PUBLISH, - temBAD_SET_ID, - temCREATEXNS, - temDST_IS_SRC, - temDST_NEEDED, - temINSUF_FEE_P, - temINVALID, - temREDUNDANT, - temRIPPLE_EMPTY, - temUNCERTAIN, - temUNKNOWN, - - // -199 .. -100: F Failure (sequence number previously used) - // Causes: - // - Transaction cannot succeed because of ledger state. - // - Unexpected ledger state. - // - C++ exception. - // Implications: - // - Not applied - // - Not forwarded - // - Could succeed in an imagined ledger. - tefFAILURE = -199, - tefALREADY, - tefBAD_ADD_AUTH, - tefBAD_AUTH, - tefBAD_CLAIM_ID, - tefBAD_GEN_AUTH, - tefBAD_LEDGER, - tefCLAIMED, - tefCREATED, - tefEXCEPTION, - tefGEN_IN_USE, - tefPAST_SEQ, - - // -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent) - // Causes: - // - Priror application of another, possibly non-existant, transaction could allow this transaction to succeed. - // Implications: - // - Not applied - // - Not forwarded - // - Might succeed later - // - Hold - terRETRY = -99, - terDIR_FULL, - terFUNDS_SPENT, - terINSUF_FEE_B, - terNO_ACCOUNT, - terNO_DST, - terNO_LINE, - terNO_LINE_NO_ZERO, - terOFFER_NOT_FOUND, // XXX If we check sequence first this could be hard failure. - terPRE_SEQ, - terSET_MISSING_DST, - terUNFUNDED, - - // 0: S Success (success) - // Causes: - // - Success. - // Implications: - // - Applied - // - Forwarded - tesSUCCESS = 0, - - // 100 .. P Partial success (SR) (ripple transaction with no good paths, pay to non-existent account) - // Causes: - // - Success, but does not achieve optimal result. - // Implications: - // - Applied - // - Forwarded - // Only allowed as a return code of appliedTransaction when !tapRetry. Otherwise, treated as terRETRY. - tepPARTIAL = 100, - tepPATH_DRY, - tepPATH_PARTIAL, -}; - -#define isTemMalformed(x) ((x) >= temMALFORMED && (x) < tefFAILURE) -#define isTefFailure(x) ((x) >= tefFAILURE && (x) < terRETRY) -#define isTepPartial(x) ((x) >= tepPATH_PARTIAL) - -bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman); - enum TransactionEngineParams { tapNONE = 0x00, @@ -138,127 +27,6 @@ enum TransactionEngineParams // Transaction can be retried, soft failures allowed }; -class PaymentNode { -protected: - friend class TransactionEngine; - friend class PathState; - - uint16 uFlags; // --> From path. - - uint160 uAccountID; // --> Accounts: Recieving/sending account. - uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send. - // --- For offer's next has currency out. - uint160 uIssuerID; // --> Currency's issuer - - STAmount saTransferRate; // Transfer rate for uIssuerID. - - // Computed by Reverse. - STAmount saRevRedeem; // <-- Amount to redeem to next. - STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. - // Issue isn't used by offers. - STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee. - - // Computed by forward. - STAmount saFwdRedeem; // <-- Amount node will redeem to next. - STAmount saFwdIssue; // <-- Amount node will issue to next. - // Issue isn't used by offers. - STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee. - - // For offers: - - STAmount saRateMax; // XXX Should rate be sticky for forward too? - - // Directory - uint256 uDirectTip; // Current directory. - uint256 uDirectEnd; // Next order book. - bool bDirectAdvance; // Need to advance directory. - SLE::pointer sleDirectDir; - STAmount saOfrRate; // For correct ratio. - - // Node - bool bEntryAdvance; // Need to advance entry. - unsigned int uEntry; - uint256 uOfferIndex; - SLE::pointer sleOffer; - uint160 uOfrOwnerID; - bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets. - STAmount saOfferFunds; - STAmount saTakerPays; - STAmount saTakerGets; -}; - -// account id, currency id, issuer id :: node -typedef boost::tuple aciSource; -typedef boost::unordered_map curIssuerNode; // Map of currency, issuer to node index. -typedef boost::unordered_map::const_iterator curIssuerNodeConstIterator; - -extern std::size_t hash_value(const aciSource& asValue); - -// Holds a path state under incremental application. -class PathState -{ -protected: - Ledger::pointer mLedger; - - TER pushNode(const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - TER pushImply(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - -public: - typedef boost::shared_ptr pointer; - - TER terStatus; - std::vector vpnNodes; - - // When processing, don't want to complicate directory walking with deletion. - std::vector vUnfundedBecame; // Offers that became unfunded or were completely consumed. - - // First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be - // used there. - curIssuerNode umForward; // Map of currency, issuer to node index. - - // First time working in reverse a funding source was used. - // Source may only be used there if not mentioned by an account. - curIssuerNode umReverse; // Map of currency, issuer to node index. - - LedgerEntrySet lesEntries; - - int mIndex; - uint64 uQuality; // 0 = none. - STAmount saInReq; // Max amount to spend by sender - STAmount saInAct; // Amount spent by sender (calc output) - STAmount saOutReq; // Amount to send (calc input) - STAmount saOutAct; // Amount actually sent (calc output). - - PathState( - Ledger::ref lpLedger, - const int iIndex, - const LedgerEntrySet& lesSource, - const STPath& spSourcePath, - const uint160& uReceiverID, - const uint160& uSenderID, - const STAmount& saSend, - const STAmount& saSendMax - ); - - Json::Value getJson() const; - - static PathState::pointer createPathState( - Ledger::ref lpLedger, - const int iIndex, - const LedgerEntrySet& lesSource, - const STPath& spSourcePath, - const uint160& uReceiverID, - const uint160& uSenderID, - const STAmount& saSend, - const STAmount& saSendMax - ) - { - return boost::make_shared(lpLedger, iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax); - } - - static bool lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs); -}; - // One instance per ledger. // Only one transaction applied at a time. class TransactionEngine @@ -266,21 +34,6 @@ class TransactionEngine private: LedgerEntrySet mNodes; - TER dirAdd( - uint64& uNodeDir, // Node of entry. - const uint256& uRootIndex, - const uint256& uLedgerIndex); - - TER dirDelete( - const bool bKeepRoot, - const uint64& uNodeDir, // Node item is mentioned in. - const uint256& uRootIndex, - const uint256& uLedgerIndex, // Item being deleted - const bool bStable); - - bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); - bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); - TER setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator); TER takeOffers( @@ -299,72 +52,11 @@ protected: uint160 mTxnAccountID; SLE::pointer mTxnAccount; - // First time working in reverse a funding source was mentioned. Source may only be used there. - curIssuerNode mumSource; // Map of currency, issuer to node index. - - // When processing, don't want to complicate directory walking with deletion. - std::vector mvUnfundedBecame; // Offers that became unfunded. - - // If the transaction fails to meet some constraint, still need to delete unfunded offers. - boost::unordered_set musUnfundedFound; // Offers that were found unfunded. - SLE::pointer entryCreate(LedgerEntryType type, const uint256& index) { return mNodes.entryCreate(type, index); } SLE::pointer entryCache(LedgerEntryType type, const uint256& index) { return mNodes.entryCache(type, index); } void entryDelete(SLE::ref sleEntry) { mNodes.entryDelete(sleEntry); } void entryModify(SLE::ref sleEntry) { mNodes.entryModify(sleEntry); } - TER offerDelete(const uint256& uOfferIndex); - TER offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); - - uint32 rippleTransferRate(const uint160& uIssuerID); - STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); - STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); - uint32 rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow=sfLowQualityIn, const SOE_Field sfHigh=sfHighQualityIn); - uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) - { return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); } - - STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); - void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); - STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - - STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); - - PathState::pointer pathCreate(const STPath& spPath); - void pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint); - TER calcNode(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeOfferRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeOfferFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeAccountFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeAdvance(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality, const bool bReverse); - TER calcNodeDeliverRev( - const unsigned int uIndex, - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uOutAccountID, - const STAmount& saOutReq, - STAmount& saOutAct); - - TER calcNodeDeliverFwd( - const unsigned int uIndex, - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uInAccountID, - const STAmount& saInFunds, - const STAmount& saInReq, - STAmount& saInAct, - STAmount& saInFees); - - void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, - const STAmount& saPrvReq, const STAmount& saCurReq, - STAmount& saPrvAct, STAmount& saCurAct, - uint64& uRateMax); - void txnWrite(); TER doAccountSet(const SerializedTransaction& txn); @@ -376,7 +68,7 @@ protected: TER doNicknameSet(const SerializedTransaction& txn); TER doPasswordFund(const SerializedTransaction& txn); TER doPasswordSet(const SerializedTransaction& txn); - TER doPayment(const SerializedTransaction& txn); + TER doPayment(const SerializedTransaction& txn, const TransactionEngineParams params); TER doWalletAdd(const SerializedTransaction& txn); TER doContractAdd(const SerializedTransaction& txn); TER doContractRemove(const SerializedTransaction& txn); diff --git a/src/TransactionErr.cpp b/src/TransactionErr.cpp new file mode 100644 index 0000000000..878d7cd1cd --- /dev/null +++ b/src/TransactionErr.cpp @@ -0,0 +1,91 @@ +#include "TransactionErr.h" +#include "utils.h" + +bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) +{ + static struct { + TER terCode; + const char* cpToken; + const char* cpHuman; + } transResultInfoA[] = { + { tefALREADY, "tefALREADY", "The exact transaction was already in this ledger" }, + { tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." }, + { tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." }, + { tefBAD_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed." }, + { tefBAD_GEN_AUTH, "tefBAD_GEN_AUTH", "Not authorized to claim generator." }, + { tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." }, + { tefCLAIMED, "tefCLAIMED", "Can not claim a previously claimed account." }, + { tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." }, + { tefCREATED, "tefCREATED", "Can't add an already created account." }, + { tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." }, + { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past" }, + + { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: too many paths." }, + { telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." }, + + { temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." }, + { temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, + { temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed." }, + { temBAD_ISSUER, "temBAD_ISSUER", "Malformed." }, + { temBAD_OFFER, "temBAD_OFFER", "Malformed." }, + { temBAD_PATH, "temBAD_PATH", "Malformed." }, + { temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed." }, + { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: bad publish." }, + { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, + { temCREATEXNS, "temCREATEXNS", "Can not specify non XNS for Create." }, + { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, + { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, + { temINSUF_FEE_P, "temINSUF_FEE_P", "Fee not allowed." }, + { temINVALID, "temINVALID", "The transaction is ill-formed" }, + { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, + { temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." }, + { temUNCERTAIN, "temUNCERTAIN", "In process of determining result. Never returned." }, + { temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet." }, + + { tepPATH_DRY, "tepPATH_DRY", "Path could not send partial amount." }, + { tepPATH_PARTIAL, "tepPATH_PARTIAL", "Path could not send full amount." }, + + { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, + { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, + { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." }, + { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, + { terNO_DST, "terNO_DST", "The destination does not exist" }, + { terNO_LINE, "terNO_LINE", "No such line." }, + { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, + { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, + { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, + { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, + { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, + + { tesSUCCESS, "tesSUCCESS", "The transaction was applied" }, + }; + + int iIndex = NUMBER(transResultInfoA); + + while (iIndex-- && transResultInfoA[iIndex].terCode != terCode) + ; + + if (iIndex >= 0) + { + strToken = transResultInfoA[iIndex].cpToken; + strHuman = transResultInfoA[iIndex].cpHuman; + } + + return iIndex >= 0; +} + +std::string transToken(TER terCode) +{ + std::string strToken; + std::string strHuman; + + return transResultInfo(terCode, strToken, strHuman) ? strToken : "-"; +} + +std::string transHuman(TER terCode) +{ + std::string strToken; + std::string strHuman; + + return transResultInfo(terCode, strToken, strHuman) ? strHuman : "-"; +} diff --git a/src/TransactionErr.h b/src/TransactionErr.h new file mode 100644 index 0000000000..4f0d5a7994 --- /dev/null +++ b/src/TransactionErr.h @@ -0,0 +1,118 @@ +#ifndef _TRANSACTION_ERR_ +#define _TRANSACTION_ERR_ + +#include + +enum TER // aka TransactionEngineResult +{ + // Note: Range is stable. Exact numbers are currently unstable. Use tokens. + + // -399 .. -300: L Local error (transaction fee inadequate, exceeds local limit) + // Only valid during non-consensus processing. + // Implications: + // - Not forwarded + // - No fee check + telLOCAL_ERROR = -399, + telBAD_PATH_COUNT, + telINSUF_FEE_P, + + // -299 .. -200: M Malformed (bad signature) + // Causes: + // - Transaction corrupt. + // Implications: + // - Not applied + // - Not forwarded + // - Reject + // - Can not succeed in any imagined ledger. + temMALFORMED = -299, + temBAD_AMOUNT, + temBAD_AUTH_MASTER, + temBAD_EXPIRATION, + temBAD_ISSUER, + temBAD_OFFER, + temBAD_PATH, + temBAD_PATH_LOOP, + temBAD_PUBLISH, + temBAD_SET_ID, + temCREATEXNS, + temDST_IS_SRC, + temDST_NEEDED, + temINSUF_FEE_P, + temINVALID, + temREDUNDANT, + temRIPPLE_EMPTY, + temUNCERTAIN, + temUNKNOWN, + + // -199 .. -100: F Failure (sequence number previously used) + // Causes: + // - Transaction cannot succeed because of ledger state. + // - Unexpected ledger state. + // - C++ exception. + // Implications: + // - Not applied + // - Not forwarded + // - Could succeed in an imagined ledger. + tefFAILURE = -199, + tefALREADY, + tefBAD_ADD_AUTH, + tefBAD_AUTH, + tefBAD_CLAIM_ID, + tefBAD_GEN_AUTH, + tefBAD_LEDGER, + tefCLAIMED, + tefCREATED, + tefEXCEPTION, + tefGEN_IN_USE, + tefPAST_SEQ, + + // -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent) + // Causes: + // - Priror application of another, possibly non-existant, transaction could allow this transaction to succeed. + // Implications: + // - Not applied + // - Not forwarded + // - Might succeed later + // - Hold + terRETRY = -99, + terDIR_FULL, + terFUNDS_SPENT, + terINSUF_FEE_B, + terNO_ACCOUNT, + terNO_DST, + terNO_LINE, + terNO_LINE_NO_ZERO, + terOFFER_NOT_FOUND, // XXX If we check sequence first this could be hard failure. + terPRE_SEQ, + terSET_MISSING_DST, + terUNFUNDED, + + // 0: S Success (success) + // Causes: + // - Success. + // Implications: + // - Applied + // - Forwarded + tesSUCCESS = 0, + + // 100 .. P Partial success (SR) (ripple transaction with no good paths, pay to non-existent account) + // Causes: + // - Success, but does not achieve optimal result. + // Implications: + // - Applied + // - Forwarded + // Only allowed as a return code of appliedTransaction when !tapRetry. Otherwise, treated as terRETRY. + tepPARTIAL = 100, + tepPATH_DRY, + tepPATH_PARTIAL, +}; + +#define isTemMalformed(x) ((x) >= temMALFORMED && (x) < tefFAILURE) +#define isTefFailure(x) ((x) >= tefFAILURE && (x) < terRETRY) +#define isTepPartial(x) ((x) >= tepPATH_PARTIAL) + +bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman); +std::string transToken(TER terCode); +std::string transHuman(TER terCode); + +#endif diff --git a/src/TransactionMeta.cpp b/src/TransactionMeta.cpp index 8a14cbbf14..d43e55e343 100644 --- a/src/TransactionMeta.cpp +++ b/src/TransactionMeta.cpp @@ -160,11 +160,6 @@ TransactionMetaNode::TransactionMetaNode(int type, const uint256& node, Serializ void TransactionMetaNode::addRaw(Serializer& s) { - if (mEntries.empty()) - { // ack, an empty node - assert(false); - return; - } s.add8(mType); s.add256(mNode); mEntries.sort(); diff --git a/src/TransactionMeta.h b/src/TransactionMeta.h index 729a724808..10423aeda9 100644 --- a/src/TransactionMeta.h +++ b/src/TransactionMeta.h @@ -46,6 +46,7 @@ protected: public: TransactionMetaNodeEntry(int type) : mType(type) { ; } + virtual ~TransactionMetaNodeEntry() { ; } int getType() const { return mType; } virtual Json::Value getJson(int) const = 0; @@ -167,6 +168,9 @@ public: class TransactionMetaSet { +public: + typedef boost::shared_ptr pointer; + protected: uint256 mTransactionID; uint32 mLedger; diff --git a/src/ValidationCollection.cpp b/src/ValidationCollection.cpp index b11b38b633..e4d1117549 100644 --- a/src/ValidationCollection.cpp +++ b/src/ValidationCollection.cpp @@ -116,19 +116,40 @@ int ValidationCollection::getTrustedValidationCount(const uint256& ledger) return trusted; } -int ValidationCollection::getCurrentValidationCount(uint32 afterTime) -{ +int ValidationCollection::getNodesAfter(const uint256& ledger) +{ // Number of trusted nodes that have moved past this ledger int count = 0; boost::mutex::scoped_lock sl(mValidationLock); for (boost::unordered_map::iterator it = mCurrentValidations.begin(), end = mCurrentValidations.end(); it != end; ++it) { - if (it->second->isTrusted() && (it->second->getSignTime() > afterTime)) + if (it->second->isTrusted() && it->second->isPreviousHash(ledger)) ++count; } return count; } +int ValidationCollection::getLoadRatio(bool overLoaded) +{ // how many trusted nodes are able to keep up, higher is better + int goodNodes = overLoaded ? 1 : 0; + int badNodes = overLoaded ? 0 : 1; + { + boost::mutex::scoped_lock sl(mValidationLock); + for (boost::unordered_map::iterator it = mCurrentValidations.begin(), + end = mCurrentValidations.end(); it != end; ++it) + { + if (it->second->isTrusted()) + { + if (it->second->isFull()) + ++goodNodes; + else + ++badNodes; + } + } + } + return (goodNodes * 100) / (goodNodes + badNodes); +} + boost::unordered_map ValidationCollection::getCurrentValidations(uint256 currentLedger) { uint32 cutoff = theApp->getOPs().getNetworkTimeNC() - LEDGER_VAL_INTERVAL; diff --git a/src/ValidationCollection.h b/src/ValidationCollection.h index 1972b7b63e..95cd4592e0 100644 --- a/src/ValidationCollection.h +++ b/src/ValidationCollection.h @@ -34,7 +34,9 @@ public: void getValidationCount(const uint256& ledger, bool currentOnly, int& trusted, int& untrusted); int getTrustedValidationCount(const uint256& ledger); - int getCurrentValidationCount(uint32 afterTime); + + int getNodesAfter(const uint256& ledger); + int getLoadRatio(bool overLoaded); boost::unordered_map getCurrentValidations(uint256 currentLedger = uint256()); diff --git a/src/Version.h b/src/Version.h index df42303f00..d7cd3a02aa 100644 --- a/src/Version.h +++ b/src/Version.h @@ -5,7 +5,7 @@ // #define SERVER_VERSION_MAJOR 0 -#define SERVER_VERSION_MINOR 4 +#define SERVER_VERSION_MINOR 5 #define SERVER_VERSION_SUB "-a" #define SERVER_NAME "NewCoin" @@ -16,11 +16,11 @@ // Version we prefer to speak: #define PROTO_VERSION_MAJOR 0 -#define PROTO_VERSION_MINOR 6 +#define PROTO_VERSION_MINOR 7 // Version we will speak to: #define MIN_PROTO_MAJOR 0 -#define MIN_PROTO_MINOR 6 +#define MIN_PROTO_MINOR 7 #define MAKE_VERSION_INT(maj,min) ((maj << 16) | min) #define GET_VERSION_MAJOR(ver) (ver >> 16) diff --git a/src/bignum.h b/src/bignum.h index 78bce87609..a9a08eda2d 100644 --- a/src/bignum.h +++ b/src/bignum.h @@ -27,7 +27,7 @@ private: protected: BN_CTX* pctx; - BN_CTX* operator=(BN_CTX* pnew) { return pctx = pnew; } + CAutoBN_CTX& operator=(BN_CTX* pnew) { pctx = pnew; return *this; } public: CAutoBN_CTX() diff --git a/src/newcoin.proto b/src/newcoin.proto index 2426f16ece..cd33de9f0f 100644 --- a/src/newcoin.proto +++ b/src/newcoin.proto @@ -69,9 +69,6 @@ message TMTransaction { required bytes rawTransaction = 1; required TransactionStatus status = 2; optional uint64 receiveTimestamp = 3; - optional uint32 ledgerIndexPossible = 4; // the node may not know - optional uint32 ledgerIndexFinal = 5; - optional bytes conflictingTransaction = 6; } diff --git a/test/buster.js b/test/buster.js new file mode 100644 index 0000000000..5dd49ec7cc --- /dev/null +++ b/test/buster.js @@ -0,0 +1,9 @@ +var config = module.exports; + +config["Newcoin tests"] = { + rootPath: "../", + environment: "node", + tests: [ + "test/*-test.js" + ] +} diff --git a/test/server.js b/test/server.js new file mode 100644 index 0000000000..404df4060c --- /dev/null +++ b/test/server.js @@ -0,0 +1,60 @@ +// Manage test servers + +// Provide servers +// +// Servers are created in tmp/server/$server +// + +console.log("server.js>"); + +var config = require("./config.js"); +var utils = require("./utils.js"); + +var fs = require("fs"); +var path = require("path"); +var util = require("util"); +// var child = require("child"); + +var serverPath = function(name) { + return "tmp/server/" + name; +}; + +// Return a server's newcoind.cfg as string. +var serverConfig = function(name) { + var cfg = config.servers[name]; + + return Object.keys(cfg).map(function (o) { + return util.format("[%s]\n%s\n", o, cfg[o]); + }).join(""); +}; + +var writeConfig = function(name, done) { + fs.writeFile(path.join(serverPath(name), "newcoind.cfg"), serverConfig(name), 'utf8', done); +}; + +var makeBase = function(name, done) { + var path = serverPath(name); + + console.log("start> %s: %s", name, path); + + // Reset the server directory, build it if needed. + utils.resetPath(path, parseInt('0777', 8), function (e) { + if (e) { + throw e; + } + else { + writeConfig(name, done); + } + }); + + console.log("start< %s", name); +}; + +var start = function(name, done) { + makeBase(name, done); +}; + +exports.start = start; + +console.log("server.js<"); +// vim:ts=4 diff --git a/test/standalone-test.js b/test/standalone-test.js new file mode 100644 index 0000000000..81221d7d19 --- /dev/null +++ b/test/standalone-test.js @@ -0,0 +1,18 @@ +// console.log("standalone-test.js>"); + +var fs = require("fs"); +var buster = require("buster"); + +var server = require("./server.js"); + +buster.testCase("Check standalone server startup", { + "Start": function (done) { + server.start("alpha", function(e) { + buster.refute(e); + done(); + }); + } +}); + +// console.log("standalone-test.js<"); +// vim:ts=4 diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000000..c840431213 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,130 @@ + +var fs = require("fs"); +var path = require("path"); + +var filterErr = function(code, done) { + return function (e) { + done(e.code !== code ? e : undefined); + }; +}; + +var throwErr = function(done) { + return function (e) { + if (e) + throw e; + + done(); + }; +}; + +// apply function to elements of array. Return first true value to done or undefined. +var mapOr = function(func, array, done) { + if (array.length) { + func(array[array.length-1], function (v) { + if (v) { + done(v); + } + else { + array.length -= 1; + mapOr(func, array, done); + } + }); + } + else { + done(); + } +}; + +// Make a directory and sub-directories. +var mkPath = function(dirPath, mode, done) { + fs.mkdir(dirPath, mode, function (e) { + if (e && e.code === "EEXIST") { + // Already exists, done. + done(); + } + else if (e.code === "ENOENT") { + // Missing sub dir. + + mkPath(path.dirname(dirPath), mode, function(e) { + if (e) { + throw e; + } + else { + mkPath(dirPath, mode, done); + } + }); + } + else { + throw e; + } + }); +}; + +// Empty a directory. +var emptyPath = function(dirPath, done) { + fs.readdir(dirPath, function (err, files) { + if (err) { + done(err); + } + else { + mapOr(rmPath, files.map(function(f) { return path.join(dirPath, f); }), done); + } + }); +}; + +// Remove path recursively. +var rmPath = function(dirPath, done) { + console.log("rmPath: %s", dirPath); + + fs.lstat(dirPath, function (err, stats) { + if (err && err.code == "ENOENT") { + done(); + } + if (err) { + done(err); + } + else if (stats.isDirectory()) { + emptyPath(dirPath, function (e) { + if (e) { + done(e); + } + else { +// console.log("rmdir: %s", dirPath); done(); + fs.rmdir(dirPath, done); + } + }); + } + else { +// console.log("unlink: %s", dirPath); done(); + fs.unlink(dirPath, done); + } + }); +}; + +// Create directory if needed and empty if needed. +var resetPath = function(dirPath, mode, done) { + mkPath(dirPath, mode, function (e) { + if (e) { + done(e); + } + else { + emptyPath(dirPath, done); + } + }); +}; + +var trace = function(comment, func) { + return function() { + console.log("%s: %s", trace, arguments.toString); + func(arguments); + }; +}; + +exports.emptyPath = emptyPath; +exports.mapOr = mapOr; +exports.mkPath = mkPath; +exports.resetPath = resetPath; +exports.rmPath = rmPath; +exports.trace = trace; + +// vim:ts=4 diff --git a/tests/client1/config.xml b/tests/client1/config.xml deleted file mode 100644 index dfe441b032..0000000000 --- a/tests/client1/config.xml +++ /dev/null @@ -1,5 +0,0 @@ - - 4000 - 5001 - 30 - diff --git a/tests/client1/nodes.xml b/tests/client1/nodes.xml deleted file mode 100644 index 9754b37c4f..0000000000 --- a/tests/client1/nodes.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tests/client1/unl.xml b/tests/client1/unl.xml deleted file mode 100644 index 553486b098..0000000000 --- a/tests/client1/unl.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/client1/wallet.xml b/tests/client1/wallet.xml deleted file mode 100644 index 1c364ebb03..0000000000 --- a/tests/client1/wallet.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -
- - -
-
\ No newline at end of file diff --git a/tests/client2/config.xml b/tests/client2/config.xml deleted file mode 100644 index d01e35c99f..0000000000 --- a/tests/client2/config.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 5000 - 5002 - 30 - diff --git a/tests/client2/nodes.xml b/tests/client2/nodes.xml deleted file mode 100644 index 9754b37c4f..0000000000 --- a/tests/client2/nodes.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tests/client2/unl.xml b/tests/client2/unl.xml deleted file mode 100644 index 553486b098..0000000000 --- a/tests/client2/unl.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/client2/wallet.xml b/tests/client2/wallet.xml deleted file mode 100644 index 1c364ebb03..0000000000 --- a/tests/client2/wallet.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -
- - -
-
\ No newline at end of file diff --git a/tests/setup.bat b/tests/setup.bat deleted file mode 100644 index 61304847bb..0000000000 --- a/tests/setup.bat +++ /dev/null @@ -1,6 +0,0 @@ -REM copy C:\code\newcoin\Release\newcoin.exe C:\code\newcoin\tests\client1 -REM copy C:\code\newcoin\Release\newcoin.exe C:\code\newcoin\tests\client2 - -copy d:\code\newcoin\Debug\newcoin.exe d:\code\newcoin\tests\client1 -copy d:\code\newcoin\Debug\newcoin.exe d:\code\newcoin\tests\client2 -