diff --git a/newcoin.vcxproj b/newcoin.vcxproj index 32ee35c9e..1891a5d7a 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -95,6 +95,7 @@ + @@ -127,17 +128,21 @@ + + + + @@ -145,7 +150,6 @@ - @@ -163,16 +167,18 @@ - + + + @@ -192,6 +198,7 @@ + @@ -224,17 +231,21 @@ + + + + @@ -267,6 +278,8 @@ + + @@ -274,6 +287,7 @@ + diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index 0380497b0..064de313c 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -198,9 +198,6 @@ Source Files - - Source Files - Source Files @@ -252,9 +249,6 @@ Source Files - - Source Files - Source Files @@ -324,6 +318,30 @@ Source Files\websocketpp + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -602,6 +620,30 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + diff --git a/src/cpp/ripple/AccountSetTransactor.cpp b/src/cpp/ripple/AccountSetTransactor.cpp new file mode 100644 index 000000000..32ded2c6f --- /dev/null +++ b/src/cpp/ripple/AccountSetTransactor.cpp @@ -0,0 +1,119 @@ +#include "AccountSetTransactor.h" + +TER AccountSetTransactor::doApply() +{ + Log(lsINFO) << "doAccountSet>"; + + // + // EmailHash + // + + if (mTxn.isFieldPresent(sfEmailHash)) + { + uint128 uHash = mTxn.getFieldH128(sfEmailHash); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset email hash"; + + mTxnAccount->makeFieldAbsent(sfEmailHash); + } + else + { + Log(lsINFO) << "doAccountSet: set email hash"; + + mTxnAccount->setFieldH128(sfEmailHash, uHash); + } + } + + // + // WalletLocator + // + + if (mTxn.isFieldPresent(sfWalletLocator)) + { + uint256 uHash = mTxn.getFieldH256(sfWalletLocator); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset wallet locator"; + + mTxnAccount->makeFieldAbsent(sfEmailHash); + } + else + { + Log(lsINFO) << "doAccountSet: set wallet locator"; + + mTxnAccount->setFieldH256(sfWalletLocator, uHash); + } + } + + // + // MessageKey + // + + if (!mTxn.isFieldPresent(sfMessageKey)) + { + nothing(); + } + else + { + Log(lsINFO) << "doAccountSet: set message key"; + + mTxnAccount->setFieldVL(sfMessageKey, mTxn.getFieldVL(sfMessageKey)); + } + + // + // Domain + // + + if (mTxn.isFieldPresent(sfDomain)) + { + std::vector vucDomain = mTxn.getFieldVL(sfDomain); + + if (vucDomain.empty()) + { + Log(lsINFO) << "doAccountSet: unset domain"; + + mTxnAccount->makeFieldAbsent(sfDomain); + } + else + { + Log(lsINFO) << "doAccountSet: set domain"; + + mTxnAccount->setFieldVL(sfDomain, vucDomain); + } + } + + // + // TransferRate + // + + if (mTxn.isFieldPresent(sfTransferRate)) + { + uint32 uRate = mTxn.getFieldU32(sfTransferRate); + + if (!uRate || uRate == QUALITY_ONE) + { + Log(lsINFO) << "doAccountSet: unset transfer rate"; + + mTxnAccount->makeFieldAbsent(sfTransferRate); + } + else if (uRate > QUALITY_ONE) + { + Log(lsINFO) << "doAccountSet: set transfer rate"; + + mTxnAccount->setFieldU32(sfTransferRate, uRate); + } + else + { + Log(lsINFO) << "doAccountSet: bad transfer rate"; + + return temBAD_TRANSFER_RATE; + } + } + + Log(lsINFO) << "doAccountSet<"; + + return tesSUCCESS; +} \ No newline at end of file diff --git a/src/cpp/ripple/AccountSetTransactor.h b/src/cpp/ripple/AccountSetTransactor.h new file mode 100644 index 000000000..214a32d27 --- /dev/null +++ b/src/cpp/ripple/AccountSetTransactor.h @@ -0,0 +1,9 @@ +#include "Transactor.h" + +class AccountSetTransactor : public Transactor +{ +public: + AccountSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/JobQueue.h b/src/cpp/ripple/JobQueue.h index 26a72dca2..bdd23bc69 100644 --- a/src/cpp/ripple/JobQueue.h +++ b/src/cpp/ripple/JobQueue.h @@ -17,13 +17,15 @@ enum JobType { // must be in priority order, low to high jtINVALID, - jtVALIDATION_ut, - jtTRANSACTION, - jtPROPOSAL_ut, - jtVALIDATION_t, - jtTRANSACTION_l, - jtPROPOSAL_t, - jtADMIN, + jtVALIDATION_ut, // A validation from an untrusted source + jtCLIENTOP_ut, // A client operation from a non-local/untrusted source + jtTRANSACTION, // A transaction received from the network + jtPROPOSAL_ut, // A proposal from an untrusted source + jtCLIENTOP_t, // A client operation from a trusted source + jtVALIDATION_t, // A validation from a trusted source + jtTRANSACTION_l, // A local transaction + jtPROPOSAL_t, // A proposal from a trusted source + jtADMIN, // An administrative operation jtDEATH, // job of death, used internally }; diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index fae8b107b..64bf9ef4a 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -44,6 +44,7 @@ DEFINE_INSTANCE(Ledger); class Ledger : public boost::enable_shared_from_this, public IS_INSTANCE(Ledger) { // The basic Ledger structure, can be opened, closed, or synching friend class TransactionEngine; + friend class Transactor; public: typedef boost::shared_ptr pointer; typedef const boost::shared_ptr& ref; diff --git a/src/cpp/ripple/LedgerConsensus.cpp b/src/cpp/ripple/LedgerConsensus.cpp index e5a877b3b..8daade3bd 100644 --- a/src/cpp/ripple/LedgerConsensus.cpp +++ b/src/cpp/ripple/LedgerConsensus.cpp @@ -267,7 +267,7 @@ bool LCTransaction::updateVote(int percentTime, bool proposing) LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previousLedger, uint32 closeTime) : mState(lcsPRE_CLOSE), mCloseTime(closeTime), mPrevLedgerHash(prevLCLHash), mPreviousLedger(previousLedger), - mValPublic(theConfig.VALIDATION_PUB), mValPrivate(theConfig.VALIDATION_PRIV), + mValPublic(theConfig.VALIDATION_PUB), mValPrivate(theConfig.VALIDATION_PRIV), mConsensusFail(false), mCurrentMSeconds(0), mClosePercent(0), mHaveCloseTimeConsensus(false), mConsensusStartTime(boost::posix_time::microsec_clock::universal_time()) { @@ -305,6 +305,36 @@ LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previou } } +void LedgerConsensus::checkOurValidation() +{ // This only covers some cases - Fix for the case where we can't ever acquire the consensus ledger + if (!mHaveCorrectLCL || !mValPublic.isValid() || !mValPrivate.isValid()) + return; + + SerializedValidation::pointer lastVal = theApp->getOPs().getLastValidation(); + if (lastVal) + { + if (lastVal->getFieldU32(sfLedgerSequence) == mPreviousLedger->getLedgerSeq()) + return; + if (lastVal->getLedgerHash() == mPrevLedgerHash) + return; + } + + uint256 signingHash; + SerializedValidation::pointer v = boost::make_shared + (mPreviousLedger->getHash(), theApp->getOPs().getValidationTimeNC(), mValPublic, false); + v->setTrusted(); + v->sign(signingHash, mValPrivate); + theApp->isNew(signingHash); + theApp->getValidations().addValidation(v); + std::vector validation = v->getSigned(); + ripple::TMValidation val; + val.set_validation(&validation[0], validation.size()); + theApp->getConnectionPool().relayMessage(NULL, + boost::make_shared(val, ripple::mtVALIDATION)); + theApp->getOPs().setLastValidation(v); + cLog(lsWARNING) << "Sending partial validation"; +} + void LedgerConsensus::checkLCL() { uint256 netLgr = mPrevLedgerHash; @@ -575,12 +605,13 @@ void LedgerConsensus::statePreClose() void LedgerConsensus::closeLedger() { - mState = lcsESTABLISH; - mConsensusStartTime = boost::posix_time::microsec_clock::universal_time(); - mCloseTime = theApp->getOPs().getCloseTimeNC(); - theApp->getOPs().setLastCloseTime(mCloseTime); - statusChange(ripple::neCLOSING_LEDGER, *mPreviousLedger); - takeInitialPosition(*theApp->getMasterLedger().closeLedger(true)); + checkOurValidation(); + mState = lcsESTABLISH; + mConsensusStartTime = boost::posix_time::microsec_clock::universal_time(); + mCloseTime = theApp->getOPs().getCloseTimeNC(); + theApp->getOPs().setLastCloseTime(mCloseTime); + statusChange(ripple::neCLOSING_LEDGER, *mPreviousLedger); + takeInitialPosition(*theApp->getMasterLedger().closeLedger(true)); } void LedgerConsensus::stateEstablish() @@ -769,7 +800,7 @@ bool LedgerConsensus::haveConsensus(bool forReal) cLog(lsDEBUG) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree; return ContinuousLedgerTiming::haveConsensus(mPreviousProposers, agree + disagree, agree, currentValidations, - mPreviousMSeconds, mCurrentMSeconds, forReal); + mPreviousMSeconds, mCurrentMSeconds, forReal, mConsensusFail); } SHAMap::pointer LedgerConsensus::getTransactionTree(const uint256& hash, bool doAcquire) @@ -1193,15 +1224,17 @@ void LedgerConsensus::accept(SHAMap::ref set) } statusChange(ripple::neACCEPTED_LEDGER, *newLCL); - if (mValidating) + if (mValidating && !mConsensusFail) { uint256 signingHash; SerializedValidation::pointer v = boost::make_shared - (newLCLHash, theApp->getOPs().getValidationTimeNC(), mValPublic, mValPrivate, - mProposing, boost::ref(signingHash)); + (newLCLHash, theApp->getOPs().getValidationTimeNC(), mValPublic, mProposing); + v->setFieldU32(sfLedgerSequence, newLCL->getLedgerSeq()); + v->sign(signingHash, mValPrivate); v->setTrusted(); theApp->isNew(signingHash); // suppress it if we receive it theApp->getValidations().addValidation(v); + theApp->getOPs().setLastValidation(v); std::vector validation = v->getSigned(); ripple::TMValidation val; val.set_validation(&validation[0], validation.size()); @@ -1238,7 +1271,7 @@ void LedgerConsensus::accept(SHAMap::ref set) cLog(lsINFO) << "Applying transactions from current ledger"; applyTransactions(theApp->getMasterLedger().getCurrentLedger()->peekTransactionMap(), newOL, newLCL, failedTransactions, true); - theApp->getMasterLedger().pushLedger(newLCL, newOL, true); + theApp->getMasterLedger().pushLedger(newLCL, newOL, !mConsensusFail); mNewLedgerHash = newLCL->getHash(); mState = lcsACCEPTED; sl.unlock(); diff --git a/src/cpp/ripple/LedgerConsensus.h b/src/cpp/ripple/LedgerConsensus.h index 064d5800c..3b66fa9f4 100644 --- a/src/cpp/ripple/LedgerConsensus.h +++ b/src/cpp/ripple/LedgerConsensus.h @@ -91,7 +91,7 @@ protected: LedgerAcquire::pointer mAcquiringLedger; LedgerProposal::pointer mOurPosition; RippleAddress mValPublic, mValPrivate; - bool mProposing, mValidating, mHaveCorrectLCL; + bool mProposing, mValidating, mHaveCorrectLCL, mConsensusFail; int mCurrentMSeconds, mClosePercent, mCloseResolution; bool mHaveCloseTimeConsensus; @@ -148,6 +148,7 @@ protected: void playbackProposals(); int getThreshold(); void closeLedger(); + void checkOurValidation(); void beginAccept(bool synchronous); void endConsensus(); diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 50fea3774..1aeffb14b 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -395,13 +395,14 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, TER result) SLE::pointer origNode = mLedger->getSLE(it.first); SLE::pointer curNode = it.second.mEntry; - uint16 nodeType = curNode ? curNode->getFieldU16(sfLedgerEntry) : origNode->getFieldU16(sfLedgerEntry); + uint16 nodeType = curNode ? curNode->getFieldU16(sfLedgerEntryType) : origNode->getFieldU16(sfLedgerEntryType); mSet.setAffectedNode(it.first, *type, nodeType); if (type == &sfDeletedNode) { assert(origNode); + assert(curNode); threadOwners(origNode, mLedger, newMod); STObject finals(sfFinalFields); diff --git a/src/cpp/ripple/LedgerTiming.cpp b/src/cpp/ripple/LedgerTiming.cpp index 6119123a4..dd92c953c 100644 --- a/src/cpp/ripple/LedgerTiming.cpp +++ b/src/cpp/ripple/LedgerTiming.cpp @@ -63,7 +63,8 @@ bool ContinuousLedgerTiming::haveConsensus( int currentFinished, // proposers who have validated a ledger after this one int previousAgreeTime, // how long it took to agree on the last ledger int currentAgreeTime, // how long we've been trying to agree - bool forReal) // deciding whether to stop consensus process + bool forReal, // deciding whether to stop consensus process + bool& failed) // we can't reach a consensus { cLog(lsTRACE) << boost::str(boost::format("CLC::haveConsensus: prop=%d/%d agree=%d validated=%d time=%d/%d%s") % currentProposers % previousProposers % currentAgree % currentFinished % currentAgreeTime % previousAgreeTime % @@ -85,13 +86,15 @@ bool ContinuousLedgerTiming::haveConsensus( if (((currentAgree * 100 + 100) / (currentProposers + 1)) > 80) { tLog(forReal, lsINFO) << "normal consensus"; + failed = false; return true; } - // If 50% of the nodes on your UNL have moved on, you should declare consensus - if (((currentFinished * 100) / (currentProposers + 1)) > 50) + // If 80% of the nodes on your UNL have moved on, you should declare consensus + if (((currentFinished * 100) / (currentProposers + 1)) > 80) { - tLog(forReal, lsWARNING) << "We see no consensus, but 50% of nodes have moved on"; + tLog(forReal, lsWARNING) << "We see no consensus, but 80% of nodes have moved on"; + failed = true; return true; } diff --git a/src/cpp/ripple/LedgerTiming.h b/src/cpp/ripple/LedgerTiming.h index 6cf535c40..821eb6073 100644 --- a/src/cpp/ripple/LedgerTiming.h +++ b/src/cpp/ripple/LedgerTiming.h @@ -66,7 +66,7 @@ public: int previousProposers, int currentProposers, int currentAgree, int currentClosed, int previousAgreeTime, int currentAgreeTime, - bool forReal); + bool forReal, bool& failed); static int getNextLedgerTimeResolution(int previousResolution, bool previousAgree, int ledgerSeq); }; diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index b3006f7a5..89a5d77e7 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1006,8 +1006,8 @@ void NetworkOPs::pubProposedTransaction(Ledger::ref lpCurrent, const SerializedT ispListener->send(jvObj); } } - - pubAccountTransaction(lpCurrent,stTxn,terResult,false,TransactionMetaSet::pointer()); + TransactionMetaSet::pointer ret; + pubAccountTransaction(lpCurrent,stTxn,terResult,false,ret); } void NetworkOPs::pubLedger(Ledger::ref lpAccepted) @@ -1168,11 +1168,10 @@ std::map NetworkOPs::getAffectedAccounts(const SerializedTra const STAccount* sa = dynamic_cast(&it); if (sa) { - bool found = false; RippleAddress na = sa->getValueNCA(); accounts[na]=true; }else - { + { if( it.getFName() == sfLimitAmount ) { const STAmount* amount = dynamic_cast(&it); diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 6d06250cf..778846a22 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -85,6 +85,8 @@ protected: uint256 mLastCloseHash; uint32 mLastCloseTime; uint32 mLastValidationTime; + SerializedValidation::pointer mLastValidation; + // XXX Split into more locks. boost::interprocess::interprocess_upgradable_mutex mMonitorLock; @@ -131,8 +133,10 @@ public: Ledger::pointer getLedgerByHash(const uint256& hash) { return mLedgerMaster->getLedgerByHash(hash); } Ledger::pointer getLedgerBySeq(const uint32 seq) { return mLedgerMaster->getLedgerBySeq(seq); } - uint256 getClosedLedgerHash() - { return mLedgerMaster->getClosedLedger()->getHash(); } + uint256 getClosedLedgerHash() { return mLedgerMaster->getClosedLedger()->getHash(); } + + SerializedValidation::ref getLastValidation() { return mLastValidation; } + void setLastValidation(SerializedValidation::ref v) { mLastValidation = v; } SLE::pointer getSLE(Ledger::pointer lpLedger, const uint256& uHash) { return lpLedger->getSLE(uHash); } diff --git a/src/cpp/ripple/OfferCancelTransactor.cpp b/src/cpp/ripple/OfferCancelTransactor.cpp new file mode 100644 index 000000000..2110c037e --- /dev/null +++ b/src/cpp/ripple/OfferCancelTransactor.cpp @@ -0,0 +1,41 @@ +#include "OfferCancelTransactor.h" +#include "Log.h" + +TER OfferCancelTransactor::doApply() +{ + TER terResult; + const uint32 uOfferSequence = mTxn.getFieldU32(sfOfferSequence); + const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence); + + Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + + if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence) + { + Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + + terResult = temBAD_SEQUENCE; + } + else + { + const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uOfferSequence); + SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex); + + if (sleOffer) + { + Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence; + + terResult = mEngine->getNodes().offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + } + else + { + Log(lsWARNING) << "doOfferCancel: offer not found: " + << RippleAddress::createHumanAccountID(mTxnAccountID) + << " : " << uOfferSequence + << " : " << uOfferIndex.ToString(); + + terResult = tesSUCCESS; + } + } + + return terResult; +} \ No newline at end of file diff --git a/src/cpp/ripple/OfferCancelTransactor.h b/src/cpp/ripple/OfferCancelTransactor.h new file mode 100644 index 000000000..8ddd6a5c1 --- /dev/null +++ b/src/cpp/ripple/OfferCancelTransactor.h @@ -0,0 +1,9 @@ +#include "Transactor.h" + +class OfferCancelTransactor : public Transactor +{ +public: + OfferCancelTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/OfferCreateTransactor.cpp b/src/cpp/ripple/OfferCreateTransactor.cpp new file mode 100644 index 000000000..9f940b6e3 --- /dev/null +++ b/src/cpp/ripple/OfferCreateTransactor.cpp @@ -0,0 +1,440 @@ +#include "OfferCreateTransactor.h" +#include + +// 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 OfferCreateTransactor::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 = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer()); + saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer()); + + while (temUNCERTAIN == terResult) + { + SLE::pointer sleOfferDir; + uint64 uTipQuality; + + // Figure out next offer to take, if needed. + if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) + { + // Taker, still, needs to get and pay. + + sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->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 offers 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; + + mEngine->getNodes().dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); + + SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex); + + Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); + + const uint160 uOfferOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID(); + STAmount saOfferPays = sleOffer->getFieldAmount(sfTakerGets); + STAmount saOfferGets = sleOffer->getFieldAmount(sfTakerPays); + + if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(sfExpiration) <= mEngine->getLedger()->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 = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays); + STAmount saTakerFunds = mEngine->getNodes().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; + STAmount saTakerIssuerFee; + STAmount saOfferIssuerFee; + + 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( + mEngine->getNodes().rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID), + mEngine->getNodes().rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID), + saOfferFunds, + saPay, // Driver XXX need to account for fees. + saOfferPays, + saOfferGets, + saTakerPays, + saTakerGets, + saSubTakerPaid, + saSubTakerGot, + saTakerIssuerFee, + saOfferIssuerFee); + + 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->setFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); + + // Offer owner will get less. Subtract what owner just paid. + sleOffer->setFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); + + mEngine->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? + assert(!!saSubTakerGot.getIssuer()); + + mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); + mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); + + saTakerGot += saSubTakerGot; + + // Taker pays offer owner. + // saSubTakerPaid.setIssuer(uTakerPaysAccountID); + assert(!!saSubTakerPaid.getIssuer()); + + mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); + mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); + + 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 = mEngine->getNodes().offerDelete(uOfferIndex); + if (tesSUCCESS != terResult) + break; + } + } + + if (tesSUCCESS == terResult) + { + // On success, delete offers that became unfunded. + BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) + { + terResult = mEngine->getNodes().offerDelete(uOfferIndex); + if (tesSUCCESS != terResult) + break; + } + } + + return terResult; +} + +TER OfferCreateTransactor::doApply() +{ + Log(lsWARNING) << "doOfferCreate> " << mTxn.getJson(0); + const uint32 txFlags = mTxn.getFlags(); + const bool bPassive = isSetBit(txFlags, tfPassive); + STAmount saTakerPays = mTxn.getFieldAmount(sfTakerPays); + STAmount saTakerGets = mTxn.getFieldAmount(sfTakerGets); + + Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGets=%s") + % saTakerPays.getFullText() + % saTakerGets.getFullText()); + + const uint160 uPaysIssuerID = saTakerPays.getIssuer(); + const uint160 uGetsIssuerID = saTakerGets.getIssuer(); + const uint32 uExpiration = mTxn.getFieldU32(sfExpiration); + const bool bHaveExpiration = mTxn.isFieldPresent(sfExpiration); + const uint32 uSequence = mTxn.getSequence(); + + const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); + SLE::pointer sleOffer = mEngine->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 && mEngine->getLedger()->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: XRP for XRP"; + + terResult = temBAD_OFFER; + } + else if (!saTakerPays.isPositive() || !saTakerGets.isPositive()) + { + 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 (!mEngine->getNodes().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 = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID)); + + if (!sleTakerPays) + { + Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::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 for %s -> %s") + % uTakeBookBase.ToString() + % saTakerGets.getFullText() + % saTakerPays.getFullText()); + + // Take using the parameters of the offer. +#if 1 + Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); + terResult = takeOffers( + bPassive, + uTakeBookBase, + mTxnAccountID, + mTxnAccount, + saTakerGets, + saTakerPays, + saOfferPaid, // How much was spent. + saOfferGot // How much was got. + ); +#else + terResult = tesSUCCESS; +#endif + 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: AFTER 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: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).getFullText(); + + // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); + // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); + + if (tesSUCCESS == terResult + && saTakerPays // Still wanting something. + && saTakerGets // Still offering something. + && mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. + { + // We need to place the remainder of the offer into its order book. + Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") + % saTakerPays.getFullText() + % saTakerGets.getFullText()); + + // Add offer to owner's directory. + terResult = mEngine->getNodes().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() + % RippleAddress::createHumanAccountID(saTakerPays.getIssuer()) + % saTakerGets.getHumanCurrency() + % RippleAddress::createHumanAccountID(saTakerGets.getIssuer())); + + uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. + + // Add offer to order book. + terResult = mEngine->getNodes().dirAdd(uBookNode, uDirectory, uLedgerIndex); + } + + if (tesSUCCESS == terResult) + { + Log(lsWARNING) << "doOfferCreate: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); + Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << RippleAddress::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->setFieldAccount(sfAccount, mTxnAccountID); + sleOffer->setFieldU32(sfSequence, uSequence); + sleOffer->setFieldH256(sfBookDirectory, uDirectory); + sleOffer->setFieldAmount(sfTakerPays, saTakerPays); + sleOffer->setFieldAmount(sfTakerGets, saTakerGets); + sleOffer->setFieldU64(sfOwnerNode, uOwnerNode); + sleOffer->setFieldU64(sfBookNode, uBookNode); + + if (uExpiration) + sleOffer->setFieldU32(sfExpiration, uExpiration); + + if (bPassive) + sleOffer->setFlag(lsfPassive); + } + } + + Log(lsINFO) << "doOfferCreate: final sleOffer=" << sleOffer->getJson(0); + + return terResult; +} diff --git a/src/cpp/ripple/OfferCreateTransactor.h b/src/cpp/ripple/OfferCreateTransactor.h new file mode 100644 index 000000000..02db25ca6 --- /dev/null +++ b/src/cpp/ripple/OfferCreateTransactor.h @@ -0,0 +1,22 @@ +#include "Transactor.h" + + +class OfferCreateTransactor : public Transactor +{ + TER takeOffers( + bool bPassive, + const uint256& uBookBase, + const uint160& uTakerAccountID, + const SLE::pointer& sleTakerAccount, + const STAmount& saTakerPays, + const STAmount& saTakerGets, + STAmount& saTakerPaid, + STAmount& saTakerGot); + +public: + OfferCreateTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; + + diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp new file mode 100644 index 000000000..7577341cb --- /dev/null +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -0,0 +1,168 @@ +#include "PaymentTransactor.h" +#include "Config.h" +#include "RippleCalc.h" + +#define RIPPLE_PATHS_MAX 3 + +// TODO: only have the higher fee if the account doesn't in fact exist +void PaymentTransactor::calculateFee() +{ + if (mTxn.getFlags() & tfCreateAccount) + { + mFeeDue = theConfig.FEE_ACCOUNT_CREATE; + }else Transactor::calculateFee(); +} + +TER PaymentTransactor::doApply() +{ + // Ripple if source or destination is non-native or if there are paths. + const uint32 uTxFlags = mTxn.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 = mTxn.isFieldPresent(sfPaths); + const bool bMax = mTxn.isFieldPresent(sfSendMax); + const uint160 uDstAccountID = mTxn.getFieldAccount160(sfDestination); + const STAmount saDstAmount = mTxn.getFieldAmount(sfAmount); + const STAmount saMaxAmount = bMax + ? mTxn.getFieldAmount(sfSendMax) + : saDstAmount.isNative() + ? saDstAmount + : STAmount(saDstAmount.getCurrency(), mTxnAccountID, saDstAmount.getMantissa(), saDstAmount.getExponent(), saDstAmount.isNegative()); + 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 specified."; + + return temDST_NEEDED; + } + else if (bMax && !saMaxAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad max amount: " << saMaxAmount.getFullText(); + + return temBAD_AMOUNT; + } + else if (!saDstAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad dst amount: " << saDstAmount.getFullText(); + + return temBAD_AMOUNT; + } + else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + { + Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redundant 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 = mEngine->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 XRP."; + + return temCREATEXRP; + } + else if (!bCreate) + { + Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + // Create the account. + sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->setFieldAccount(sfAccount, uDstAccountID); + sleDst->setFieldU32(sfSequence, 1); + } + else + { + mEngine->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 = mTxn.getFieldPathSet(sfPaths); + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + + terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX + ? telBAD_PATH_COUNT + : RippleCalc::rippleCalc( + mEngine->getNodes(), + saMaxAmountAct, + saDstAmountAct, + saMaxAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + bPartialPayment, + bLimitQuality, + bNoRippleDirect); + } + else + { + // Direct XRP payment. + + STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance); + + if (saSrcXRPBalance < saDstAmount) + { + // Transaction might succeed, if applied in a different order. + Log(lsINFO) << "doPayment: Delay transaction: Insufficient funds."; + + terResult = terUNFUNDED; + } + else + { + mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount); + sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(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; +} \ No newline at end of file diff --git a/src/cpp/ripple/PaymentTransactor.h b/src/cpp/ripple/PaymentTransactor.h new file mode 100644 index 000000000..2c93ea713 --- /dev/null +++ b/src/cpp/ripple/PaymentTransactor.h @@ -0,0 +1,11 @@ + +#include "Transactor.h" + +class PaymentTransactor : public Transactor +{ + void calculateFee(); +public: + PaymentTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index d46da78cc..6579bb903 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -1312,13 +1312,15 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param cLog(lsTRACE) << "RPC:" << command; cLog(lsTRACE) << "RPC params:" << params; + mRole = role; + static struct { const char* pCommand; doFuncPtr dfpFunc; int iMinParams; int iMaxParams; - bool mAdminRequired; - bool mEvented; + bool bAdminRequired; + bool bEvented; unsigned int iOptions; } commandsA[] = { // Request-response methods @@ -1335,6 +1337,7 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param { "ledger_closed", &RPCHandler::doLedgerClosed, 0, 0, false, false, optClosed }, { "ledger_current", &RPCHandler::doLedgerCurrent, 0, 0, false, false, optCurrent }, { "ledger_entry", &RPCHandler::doLedgerEntry, -1, -1, false, false, optCurrent }, + { "ledger_header", &RPCHandler::doLedgerHeader, -1, -1, false, false, optCurrent }, { "log_level", &RPCHandler::doLogLevel, 0, 2, true }, { "logrotate", &RPCHandler::doLogRotate, 0, 0, true }, { "nickname_info", &RPCHandler::doNicknameInfo, 1, 1, false, false, optCurrent }, @@ -1380,11 +1383,11 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param { return rpcError(rpcUNKNOWN_COMMAND); } - else if (commandsA[i].mAdminRequired && role != ADMIN) + else if (commandsA[i].bAdminRequired && mRole != ADMIN) { return rpcError(rpcNO_PERMISSION); } - else if (commandsA[i].mEvented && mInfoSub == NULL) + else if (commandsA[i].bEvented && mInfoSub == NULL) { return rpcError(rpcNO_EVENTS); } @@ -1705,15 +1708,13 @@ Json::Value RPCHandler::doTransactionEntry(const Json::Value& jvRequest) return jvResult; } -Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest) +Json::Value RPCHandler::lookupLedger(const Json::Value& jvRequest, Ledger::pointer& lpLedger) { Json::Value jvResult; uint256 uLedger = jvRequest.isMember("ledger_hash") ? uint256(jvRequest["ledger_hash"].asString()) : 0; uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; - Ledger::pointer lpLedger; - if (!!uLedger) { // Ledger directly specified. @@ -1756,6 +1757,17 @@ Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest) jvResult["ledger_current_index"] = uLedgerIndex; } + return jvResult; +} + +Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest) +{ + Ledger::pointer lpLedger; + Json::Value jvResult = lookupLedger(jvRequest, lpLedger); + + if (!lpLedger) + return jvResult; + uint256 uNodeIndex; bool bNodeBinary = false; @@ -1951,6 +1963,25 @@ Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest) return jvResult; } +Json::Value RPCHandler::doLedgerHeader(const Json::Value& jvRequest) +{ + Ledger::pointer lpLedger; + Json::Value jvResult = lookupLedger(jvRequest, lpLedger); + + if (!lpLedger) + return jvResult; + + Serializer s; + + lpLedger->addRaw(s); + + jvResult["ledger_data"] = strHex(s.peekData()); + + if (mRole == ADMIN) + lpLedger->addJson(jvResult, 0); + + return jvRequest; +} boost::unordered_set RPCHandler::parseAccountIds(const Json::Value& jvArray) { diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index 7be97a6d5..0b742c8dd 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -8,6 +8,7 @@ class RPCHandler { NetworkOPs* mNetOps; InfoSub* mInfoSub; + int mRole; typedef Json::Value (RPCHandler::*doFuncPtr)(const Json::Value ¶ms); enum { @@ -22,6 +23,8 @@ class RPCHandler int getParamCount(const Json::Value& params); bool extractString(std::string& param, const Json::Value& params, int index); + Json::Value lookupLedger(const Json::Value& jvRequest, Ledger::pointer& lpLedger); + Json::Value getMasterGenerator(const uint256& uLedger, const RippleAddress& naRegularSeed, RippleAddress& naMasterGenerator); Json::Value authorize(const uint256& uLedger, const RippleAddress& naRegularSeed, const RippleAddress& naSrcAccountID, RippleAddress& naAccountPublic, RippleAddress& naAccountPrivate, @@ -86,6 +89,7 @@ class RPCHandler Json::Value doLedgerClosed(const Json::Value& params); Json::Value doLedgerCurrent(const Json::Value& params); Json::Value doLedgerEntry(const Json::Value& params); + Json::Value doLedgerHeader(const Json::Value& params); Json::Value doTransactionEntry(const Json::Value& params); Json::Value doSubscribe(const Json::Value& params); diff --git a/src/cpp/ripple/RegularKeySetTransactor.cpp b/src/cpp/ripple/RegularKeySetTransactor.cpp new file mode 100644 index 000000000..ba9e2ee82 --- /dev/null +++ b/src/cpp/ripple/RegularKeySetTransactor.cpp @@ -0,0 +1,51 @@ +#include "RegularKeySetTransactor.h" +#include "Log.h" + + +SETUP_LOG(); + +// TODO: +TER RegularKeySetTransactor::checkSig() +{ + // Transaction's signing public key must be for the source account. + // To prove the master private key made this transaction. + if (mSigningPubKey.getAccountID() != mTxnAccountID) + { + // Signing Pub Key must be for Source Account ID. + cLog(lsWARNING) << "sourceAccountID: " << mSigningPubKey.humanAccountID(); + cLog(lsWARNING) << "txn accountID: " << mTxn.getSourceAccount().humanAccountID(); + + return temBAD_SET_ID; + } + return tesSUCCESS; +} + +// TODO: this should be default fee if flag isn't set +void RegularKeySetTransactor::calculateFee() +{ + mFeeDue = 0; +} + + +// TODO: change to take a fee if there is one there +TER RegularKeySetTransactor::doApply() +{ + std::cerr << "doRegularKeySet>" << std::endl; + + if (mTxnAccount->getFlags() & lsfPasswordSpent) + { + std::cerr << "doRegularKeySet: Delay transaction: Funds already spent." << std::endl; + + return terFUNDS_SPENT; + } + + mTxnAccount->setFlag(lsfPasswordSpent); + + uint160 uAuthKeyID=mTxn.getFieldAccount160(sfAuthorizedKey); + mTxnAccount->setFieldAccount(sfAuthorizedKey, uAuthKeyID); + + + std::cerr << "doRegularKeySet<" << std::endl; + + return tesSUCCESS; +} diff --git a/src/cpp/ripple/RegularKeySetTransactor.h b/src/cpp/ripple/RegularKeySetTransactor.h new file mode 100644 index 000000000..a6df0b356 --- /dev/null +++ b/src/cpp/ripple/RegularKeySetTransactor.h @@ -0,0 +1,11 @@ +#include "Transactor.h" + +class RegularKeySetTransactor : public Transactor +{ + void calculateFee(); +public: + RegularKeySetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + TER checkFee(); + TER checkSig(); + TER doApply(); +}; diff --git a/src/cpp/ripple/RippleAddress.cpp b/src/cpp/ripple/RippleAddress.cpp index a194a16bc..ecb152857 100644 --- a/src/cpp/ripple/RippleAddress.cpp +++ b/src/cpp/ripple/RippleAddress.cpp @@ -115,7 +115,7 @@ uint160 RippleAddress::getNodeID() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getNodeID"); case VER_NODE_PUBLIC: // Note, we are encoding the left. @@ -129,7 +129,7 @@ const std::vector& RippleAddress::getNodePublic() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getNodePublic"); case VER_NODE_PUBLIC: return vchData; @@ -143,7 +143,7 @@ std::string RippleAddress::humanNodePublic() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanNodePublic"); case VER_NODE_PUBLIC: return ToString(); @@ -209,7 +209,7 @@ const std::vector& RippleAddress::getNodePrivateData() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getNodePrivateData"); case VER_NODE_PRIVATE: return vchData; @@ -223,7 +223,7 @@ uint256 RippleAddress::getNodePrivate() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source = getNodePrivate"); case VER_NODE_PRIVATE: return uint256(vchData); @@ -237,7 +237,7 @@ std::string RippleAddress::humanNodePrivate() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanNodePrivate"); case VER_NODE_PRIVATE: return ToString(); @@ -279,7 +279,7 @@ uint160 RippleAddress::getAccountID() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getAccountID"); case VER_ACCOUNT_ID: return uint160(vchData); @@ -297,7 +297,7 @@ std::string RippleAddress::humanAccountID() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanAccountID"); case VER_ACCOUNT_ID: return ToString(); @@ -353,7 +353,7 @@ const std::vector& RippleAddress::getAccountPublic() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getAccountPublic"); case VER_ACCOUNT_ID: throw std::runtime_error("public not available from account id"); @@ -371,7 +371,7 @@ std::string RippleAddress::humanAccountPublic() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanAccountPublic"); case VER_ACCOUNT_ID: throw std::runtime_error("public not available from account id"); @@ -446,7 +446,7 @@ uint256 RippleAddress::getAccountPrivate() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getAccountPrivate"); case VER_ACCOUNT_PRIVATE: return uint256(vchData); @@ -460,7 +460,7 @@ std::string RippleAddress::humanAccountPrivate() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanAccountPrivate"); case VER_ACCOUNT_PRIVATE: return ToString(); @@ -606,7 +606,7 @@ BIGNUM* RippleAddress::getGeneratorBN() const { // returns the public generator switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getGeneratorBN"); case VER_FAMILY_GENERATOR: // Do nothing. @@ -625,7 +625,7 @@ const std::vector& RippleAddress::getGenerator() const { // returns the public generator switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getGenerator"); case VER_FAMILY_GENERATOR: // Do nothing. @@ -640,7 +640,7 @@ std::string RippleAddress::humanGenerator() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanGenerator"); case VER_FAMILY_GENERATOR: return ToString(); @@ -678,7 +678,7 @@ uint128 RippleAddress::getSeed() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - getSeed"); case VER_FAMILY_SEED: return uint128(vchData); @@ -692,7 +692,7 @@ std::string RippleAddress::humanSeed1751() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanSeed1751"); case VER_FAMILY_SEED: { @@ -719,7 +719,7 @@ std::string RippleAddress::humanSeed() const { switch (nVersion) { case VER_NONE: - throw std::runtime_error("unset source"); + throw std::runtime_error("unset source - humanSeed"); case VER_FAMILY_SEED: return ToString(); diff --git a/src/cpp/ripple/SerializedValidation.cpp b/src/cpp/ripple/SerializedValidation.cpp index 75d8249ab..c344e1002 100644 --- a/src/cpp/ripple/SerializedValidation.cpp +++ b/src/cpp/ripple/SerializedValidation.cpp @@ -39,26 +39,32 @@ SerializedValidation::SerializedValidation(SerializerIterator& sit, bool checkSi } SerializedValidation::SerializedValidation(const uint256& ledgerHash, uint32 signTime, - const RippleAddress& naPub, const RippleAddress& naPriv, bool isFull, uint256& signingHash) + const RippleAddress& raPub, bool isFull) : STObject(sValidationFormat, sfValidation), mTrusted(false) -{ +{ // Does not sign setFieldH256(sfLedgerHash, ledgerHash); setFieldU32(sfSigningTime, signTime); - setFieldVL(sfSigningPubKey, naPub.getNodePublic()); - mNodeID = naPub.getNodeID(); + setFieldVL(sfSigningPubKey, raPub.getNodePublic()); + mNodeID = raPub.getNodeID(); assert(mNodeID.isNonZero()); if (!isFull) setFlag(sFullFlag); +} +void SerializedValidation::sign(const RippleAddress& raPriv) +{ + uint256 signingHash; + sign(signingHash, raPriv); +} + +void SerializedValidation::sign(uint256& signingHash, const RippleAddress& raPriv) +{ signingHash = getSigningHash(); std::vector signature; - naPriv.signNodePrivate(signingHash, signature); + raPriv.signNodePrivate(signingHash, signature); setFieldVL(sfSignature, signature); - // XXX Check if this can fail. - // if (!RippleAddress::createNodePrivate(naSeed).signNodePrivate(getSigningHash(), mSignature.peekValue())) - // throw std::runtime_error("Unable to sign validation"); } uint256 SerializedValidation::getSigningHash() const @@ -90,8 +96,8 @@ bool SerializedValidation::isValid(const uint256& signingHash) const { try { - RippleAddress naPublicKey = RippleAddress::createNodePublic(getFieldVL(sfSigningPubKey)); - return naPublicKey.isValid() && naPublicKey.verifyNodePublic(signingHash, getFieldVL(sfSignature)); + RippleAddress raPublicKey = RippleAddress::createNodePublic(getFieldVL(sfSigningPubKey)); + return raPublicKey.isValid() && raPublicKey.verifyNodePublic(signingHash, getFieldVL(sfSignature)); } catch (...) { diff --git a/src/cpp/ripple/SerializedValidation.h b/src/cpp/ripple/SerializedValidation.h index 57b3b42a8..72e1fc50e 100644 --- a/src/cpp/ripple/SerializedValidation.h +++ b/src/cpp/ripple/SerializedValidation.h @@ -24,13 +24,14 @@ public: // These throw if the object is not valid SerializedValidation(SerializerIterator& sit, bool checkSignature = true); - SerializedValidation(const uint256& ledgerHash, uint32 signTime, const RippleAddress& naPub, - const RippleAddress& naPriv, bool isFull, uint256& signingHash); + + // Does not sign the validation + SerializedValidation(const uint256& ledgerHash, uint32 signTime, const RippleAddress& raPub, bool isFull); uint256 getLedgerHash() const; uint32 getSignTime() const; uint32 getFlags() const; - RippleAddress getSignerPublic() const; + RippleAddress getSignerPublic() const; uint160 getNodeID() const { return mNodeID; } bool isValid() const; bool isFull() const; @@ -41,6 +42,8 @@ public: void setTrusted() { mTrusted = true; } std::vector getSigned() const; std::vector getSignature() const; + void sign(uint256& signingHash, const RippleAddress& raPrivate); + void sign(const RippleAddress& raPrivate); // The validation this replaced const uint256& getPreviousHash() { return mPreviousHash; } diff --git a/src/cpp/ripple/Transaction.h b/src/cpp/ripple/Transaction.h index 0f208d2b5..f56fbbc96 100644 --- a/src/cpp/ripple/Transaction.h +++ b/src/cpp/ripple/Transaction.h @@ -2,8 +2,7 @@ #define __TRANSACTION__ // -// Notes: this code contains legacy constructored sharedXYZ and setXYZ. The intent is for these functions to go away. Transactions -// should now be constructed in JSON with. Use STObject::parseJson to obtain a binary version. +// Transactions should be constructed in JSON with. Use STObject::parseJson to obtain a binary version. // #include diff --git a/src/cpp/ripple/TransactionAction.cpp b/src/cpp/ripple/TransactionAction.cpp index 05bdc79d5..4342987e6 100644 --- a/src/cpp/ripple/TransactionAction.cpp +++ b/src/cpp/ripple/TransactionAction.cpp @@ -21,1120 +21,19 @@ #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. - // - /* JED: taking out generator stuff until we have a better idea of how people will use this - std::vector vucCipher = txn.getFieldVL(sfGenerator); - std::vector vucPubKey = txn.getFieldVL(sfPublicKey); - std::vector vucSignature = txn.getFieldVL(sfSignature); - RippleAddress naAccountPublic = RippleAddress::createAccountPublic(vucPubKey); - // FIXME: This should be moved to the transaction's signature check and cached - 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->setFieldVL(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.getFieldAccount160(sfAuthorizedKey); // PasswordSet - */ - uint160 uAuthKeyID=txn.getFieldAccount160(sfRegularKey); - mTxnAccount->setFieldAccount(sfRegularKey, uAuthKeyID); - return tesSUCCESS; -} -TER TransactionEngine::doAccountSet(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doAccountSet>"; - // - // EmailHash - // - if (txn.isFieldPresent(sfEmailHash)) - { - uint128 uHash = txn.getFieldH128(sfEmailHash); - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset email hash"; - - mTxnAccount->makeFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set email hash"; - - mTxnAccount->setFieldH128(sfEmailHash, uHash); - } - } - - // - // WalletLocator - // - - if (txn.isFieldPresent(sfWalletLocator)) - { - uint256 uHash = txn.getFieldH256(sfWalletLocator); - - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset wallet locator"; - - mTxnAccount->makeFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set wallet locator"; - - mTxnAccount->setFieldH256(sfWalletLocator, uHash); - } - } - - // - // MessageKey - // - - if (!txn.isFieldPresent(sfMessageKey)) - { - nothing(); - } - else - { - Log(lsINFO) << "doAccountSet: set message key"; - - mTxnAccount->setFieldVL(sfMessageKey, txn.getFieldVL(sfMessageKey)); - } - - // - // Domain - // - - if (txn.isFieldPresent(sfDomain)) - { - std::vector vucDomain = txn.getFieldVL(sfDomain); - - if (vucDomain.empty()) - { - Log(lsINFO) << "doAccountSet: unset domain"; - - mTxnAccount->makeFieldAbsent(sfDomain); - } - else - { - Log(lsINFO) << "doAccountSet: set domain"; - - mTxnAccount->setFieldVL(sfDomain, vucDomain); - } - } - - // - // TransferRate - // - - if (txn.isFieldPresent(sfTransferRate)) - { - uint32 uRate = txn.getFieldU32(sfTransferRate); - - if (!uRate || uRate == QUALITY_ONE) - { - Log(lsINFO) << "doAccountSet: unset transfer rate"; - - mTxnAccount->makeFieldAbsent(sfTransferRate); - } - else if (uRate > QUALITY_ONE) - { - Log(lsINFO) << "doAccountSet: set transfer rate"; - - mTxnAccount->setFieldU32(sfTransferRate, uRate); - } - else - { - Log(lsINFO) << "doAccountSet: bad transfer rate"; - - return temBAD_TRANSFER_RATE; - } - } - - Log(lsINFO) << "doAccountSet<"; - - return tesSUCCESS; -} - - -TER TransactionEngine::doTrustSet(const SerializedTransaction& txn) -{ - TER terResult = tesSUCCESS; - Log(lsINFO) << "doTrustSet>"; - - const STAmount saLimitAmount = txn.getFieldAmount(sfLimitAmount); - const bool bQualityIn = txn.isFieldPresent(sfQualityIn); - const uint32 uQualityIn = bQualityIn ? txn.getFieldU32(sfQualityIn) : 0; - const bool bQualityOut = txn.isFieldPresent(sfQualityOut); - const uint32 uQualityOut = bQualityIn ? txn.getFieldU32(sfQualityOut) : 0; - const uint160 uCurrencyID = saLimitAmount.getCurrency(); - uint160 uDstAccountID = saLimitAmount.getIssuer(); - const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. - bool bDelIndex = false; - - // Check if destination makes sense. - - if (saLimitAmount.isNegative()) - { - Log(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit."; - - return temBAD_AMOUNT; - } - else if (!uDstAccountID) - { - Log(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; - - return temDST_NEEDED; - } - else if (mTxnAccountID == uDstAccountID) - { - Log(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; - - return temDST_IS_SRC; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - Log(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist."; - - return terNO_DST; - } - - STAmount saLimitAllow = saLimitAmount; - saLimitAllow.setIssuer(mTxnAccountID); - - 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->getFieldAmount(sfLowLimit).getIssuer(); - uint160 uHighID = sleRippleState->getFieldAmount(sfHighLimit).getIssuer(); - bool bLow = uLowID == uSrcAccountID; - bool bHigh = uLowID == uDstAccountID; - bool bBalanceZero = !sleRippleState->getFieldAmount(sfBalance); - STAmount saDstLimit = sleRippleState->getFieldAmount(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) - { - sleRippleState->setFieldAmount(bFlipped ? sfHighLimit: sfLowLimit, saLimitAllow); - - if (!bQualityIn) - { - nothing(); - } - else if (uQualityIn) - { - sleRippleState->setFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - } - else - { - sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); - } - - if (!bQualityOut) - { - nothing(); - } - else if (uQualityOut) - { - sleRippleState->setFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - } - else - { - sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); - } - - entryModify(sleRippleState); - } - - Log(lsINFO) << "doTrustSet: Modifying ripple line: bDelIndex=" << bDelIndex; - } - // Line does not exist. - else if (!saLimitAmount) - { - Log(lsINFO) << "doTrustSet: Redundant: Setting non-existent 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) << "doTrustSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); - - sleRippleState->setFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. - sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAllow); - sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uDstAccountID)); - - if (uQualityIn) - sleRippleState->setFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); - if (uQualityOut) - sleRippleState->setFieldU32(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) << "doTrustSet<"; - - return terResult; -} - - -/* -TER TransactionEngine::doPasswordFund(const SerializedTransaction& txn) -{ - std::cerr << "doPasswordFund>" << std::endl; - - const uint160 uDstAccountID = txn.getFieldAccount160(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; -} -*/ - -// TODO: change to take a fee if there is one there -TER TransactionEngine::doRegularKeySet(const SerializedTransaction& txn) -{ - std::cerr << "doRegularKeySet>" << std::endl; - - if (mTxnAccount->getFlags() & lsfPasswordSpent) - { - std::cerr << "doRegularKeySet: Delay transaction: Funds already spent." << std::endl; - - return terFUNDS_SPENT; - } - - mTxnAccount->setFlag(lsfPasswordSpent); - - TER terResult = setAuthorized(txn, false); - - std::cerr << "doRegularKeySet<" << 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.isFieldPresent(sfPaths); - const bool bMax = txn.isFieldPresent(sfSendMax); - const uint160 uDstAccountID = txn.getFieldAccount160(sfDestination); - const STAmount saDstAmount = txn.getFieldAmount(sfAmount); - const STAmount saMaxAmount = bMax - ? txn.getFieldAmount(sfSendMax) - : saDstAmount.isNative() - ? saDstAmount - : STAmount(saDstAmount.getCurrency(), mTxnAccountID, saDstAmount.getMantissa(), saDstAmount.getExponent(), saDstAmount.isNegative()); - 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 specified."; - - return temDST_NEEDED; - } - else if (bMax && !saMaxAmount.isPositive()) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad max amount: " << saMaxAmount.getFullText(); - - return temBAD_AMOUNT; - } - else if (!saDstAmount.isPositive()) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad dst amount: " << saDstAmount.getFullText(); - - return temBAD_AMOUNT; - } - else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) - { - Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redundant 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 XRP."; - - return temCREATEXRP; - } - 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->setFieldAccount(sfAccount, uDstAccountID); - sleDst->setFieldU32(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.getFieldPathSet(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 XRP payment. - - STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance); - - if (saSrcXRPBalance < saDstAmount) - { - // Transaction might succeed, if applied in a different order. - Log(lsINFO) << "doPayment: Delay transaction: Insufficient funds."; - - terResult = terUNFUNDED; - } - else - { - mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount); - sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(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.getFieldVL(sfPublicKey); - const std::vector vucSignature = txn.getFieldVL(sfSignature); - const uint160 uAuthKeyID = txn.getFieldAccount160(sfRegularKey); - const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey); - const uint160 uDstAccountID = naMasterPubKey.getAccountID(); - - // FIXME: This should be moved to the transaction's signature check logic and cached - 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.getFieldAmount(sfAmount); - STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); - - if (saSrcBalance < saAmount) - { - std::cerr - << boost::str(boost::format("WalletAdd: Delay transaction: insufficient balance: balance=%s amount=%s") - % saSrcBalance.getText() - % saAmount.getText()) - << std::endl; - - return terUNFUNDED; - } - - // Deduct initial balance from source account. - mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saAmount); - - // Create the account. - sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - sleDst->setFieldAccount(sfAccount, uDstAccountID); - sleDst->setFieldU32(sfSequence, 1); - sleDst->setFieldAmount(sfBalance, saAmount); - sleDst->setFieldAccount(sfRegularKey, uAuthKeyID); - - std::cerr << "WalletAdd<" << std::endl; - - return tesSUCCESS; -} - - -// 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 = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer()); - saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer()); - - while (temUNCERTAIN == terResult) - { - SLE::pointer sleOfferDir; - uint64 uTipQuality; - - // Figure out next offer to take, if needed. - if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) - { - // Taker, still, needs to get and pay. - - 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 offers 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->getFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getFieldAmount(sfTakerPays); - - if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(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; - STAmount saTakerIssuerFee; - STAmount saOfferIssuerFee; - - 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( - mNodes.rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID), - mNodes.rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID), - saOfferFunds, - saPay, // Driver XXX need to account for fees. - saOfferPays, - saOfferGets, - saTakerPays, - saTakerGets, - saSubTakerPaid, - saSubTakerGot, - saTakerIssuerFee, - saOfferIssuerFee); - - 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->setFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); - - // Offer owner will get less. Subtract what owner just paid. - sleOffer->setFieldAmount(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? - assert(!!saSubTakerGot.getIssuer()); - - mNodes.accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); - mNodes.accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); - - saTakerGot += saSubTakerGot; - - // Taker pays offer owner. - // saSubTakerPaid.setIssuer(uTakerPaysAccountID); - assert(!!saSubTakerPaid.getIssuer()); - - mNodes.accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); - mNodes.accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); - - 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.getFieldAmount(sfTakerPays); - STAmount saTakerGets = txn.getFieldAmount(sfTakerGets); - -Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGets=%s") - % saTakerPays.getFullText() - % saTakerGets.getFullText()); - - const uint160 uPaysIssuerID = saTakerPays.getIssuer(); - const uint160 uGetsIssuerID = saTakerGets.getIssuer(); - const uint32 uExpiration = txn.getFieldU32(sfExpiration); - const bool bHaveExpiration = txn.isFieldPresent(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: XRP for XRP"; - - terResult = temBAD_OFFER; - } - else if (!saTakerPays.isPositive() || !saTakerGets.isPositive()) - { - 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: " << RippleAddress::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 for %s -> %s") - % uTakeBookBase.ToString() - % saTakerGets.getFullText() - % saTakerPays.getFullText()); - - // Take using the parameters of the offer. -#if 1 - Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); - terResult = takeOffers( - bPassive, - uTakeBookBase, - mTxnAccountID, - mTxnAccount, - saTakerGets, - saTakerPays, - saOfferPaid, // How much was spent. - saOfferGot // How much was got. - ); -#else - terResult = tesSUCCESS; -#endif - 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: AFTER 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: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mNodes.accountFunds(mTxnAccountID, saTakerGets).getFullText(); - - // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::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. - Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") - % saTakerPays.getFullText() - % saTakerGets.getFullText()); - - // 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() - % RippleAddress::createHumanAccountID(saTakerPays.getIssuer()) - % saTakerGets.getHumanCurrency() - % RippleAddress::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: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); - Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << RippleAddress::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->setFieldAccount(sfAccount, mTxnAccountID); - sleOffer->setFieldU32(sfSequence, uSequence); - sleOffer->setFieldH256(sfBookDirectory, uDirectory); - sleOffer->setFieldAmount(sfTakerPays, saTakerPays); - sleOffer->setFieldAmount(sfTakerGets, saTakerGets); - sleOffer->setFieldU64(sfOwnerNode, uOwnerNode); - sleOffer->setFieldU64(sfBookNode, uBookNode); - - if (uExpiration) - sleOffer->setFieldU32(sfExpiration, uExpiration); - - if (bPassive) - sleOffer->setFlag(lsfPassive); - } - } - - Log(lsINFO) << "doOfferCreate: final sleOffer=" << sleOffer->getJson(0); - - return terResult; -} - -TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) -{ - TER terResult; - const uint32 uOfferSequence = txn.getFieldU32(sfOfferSequence); - const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence); - - Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; - - if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence) - { - Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; - - terResult = temBAD_SEQUENCE; - } - else - { - const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uOfferSequence); - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - if (sleOffer) - { - Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence; - - terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); - } - else - { - Log(lsWARNING) << "doOfferCancel: offer not found: " - << RippleAddress::createHumanAccountID(mTxnAccountID) - << " : " << uOfferSequence - << " : " << uOfferIndex.ToString(); - - terResult = tesSUCCESS; - } - } - - return terResult; -} - -TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) -{ - Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); - - const uint32 expiration = txn.getFieldU32(sfExpiration); -// const uint32 bondAmount = txn.getFieldU32(sfBondAmount); -// const uint32 stampEscrow = txn.getFieldU32(sfStampEscrow); - STAmount rippleEscrow = txn.getFieldAmount(sfRippleEscrow); - std::vector createCode = txn.getFieldVL(sfCreateCode); - std::vector fundCode = txn.getFieldVL(sfFundCode); - std::vector removeCode = txn.getFieldVL(sfRemoveCode); - std::vector expireCode = txn.getFieldVL(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/cpp/ripple/TransactionEngine.cpp b/src/cpp/ripple/TransactionEngine.cpp index cd77712c5..faffb090d 100644 --- a/src/cpp/ripple/TransactionEngine.cpp +++ b/src/cpp/ripple/TransactionEngine.cpp @@ -4,8 +4,10 @@ #include #include +#include #include "TransactionEngine.h" +#include "Transactor.h" #include "../json/writer.h" @@ -88,394 +90,74 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa } #endif - TER terResult = tesSUCCESS; - uint256 txID = txn.getTransactionID(); - if (!txID) + Transactor::pointer transactor=Transactor::makeTransactor(txn,params,this); + if(transactor) { - cLog(lsWARNING) << "applyTransaction: invalid transaction id"; - - terResult = temINVALID; - } - - // - // Verify transaction is signed properly. - // - - // Extract signing key - // Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed - // without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is - // associated with the account. - // XXX This could be a lot cleaner to prevent unnecessary copying. - RippleAddress naSigningPubKey; - - if (tesSUCCESS == terResult) - naSigningPubKey = RippleAddress::createAccountPublic(txn.getSigningPubKey()); - - // Consistency: really signed. - if ((tesSUCCESS == terResult) && !isSetBit(params, tapNO_CHECK_SIGN) && !txn.checkSign(naSigningPubKey)) - { - cLog(lsWARNING) << "applyTransaction: Invalid transaction: bad signature"; - - terResult = temINVALID; - } - - STAmount saCost = theConfig.FEE_DEFAULT; - - // Customize behavior based on transaction type. - if (tesSUCCESS == terResult) - { - switch (txn.getTxnType()) + uint256 txID = txn.getTransactionID(); + if (!txID) { - case ttCLAIM: - case ttREGULAR_KEY_SET: - saCost = 0; - break; + cLog(lsWARNING) << "applyTransaction: invalid transaction id"; - case ttPAYMENT: - if (txn.getFlags() & tfCreateAccount) - { - saCost = theConfig.FEE_ACCOUNT_CREATE; - } - break; - - case ttNICKNAME_SET: - { - SLE::pointer sleNickname = entryCache(ltNICKNAME, txn.getFieldH256(sfNickname)); - - if (!sleNickname) - saCost = theConfig.FEE_NICKNAME_CREATE; - } - break; - - case ttACCOUNT_SET: - case ttTRUST_SET: - case ttOFFER_CREATE: - case ttOFFER_CANCEL: - case ttPASSWORD_FUND: - case ttWALLET_ADD: - nothing(); - break; - - case ttINVALID: - cLog(lsWARNING) << "applyTransaction: Invalid transaction: ttINVALID transaction type"; - terResult = temINVALID; - break; - - default: - cLog(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type"; - terResult = temUNKNOWN; - break; + return temINVALID; } - } - STAmount saPaid = txn.getTransactionFee(); + TER terResult= transactor->apply(); + std::string strToken; + std::string strHuman; - if (tesSUCCESS == terResult) - { - if (saCost) + transResultInfo(terResult, strToken, strHuman); + + cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; + + if (isTepPartial(terResult) && isSetBit(params, tapRETRY)) { - // Only check fee is sufficient when the ledger is open. - if (isSetBit(params, tapOPEN_LEDGER) && saPaid < saCost) + // Partial result and allowed to retry, reclassify as a retry. + terResult = terRETRY; + } + + if ((tesSUCCESS == terResult) || isTepPartial(terResult)) + { + // Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially). + Serializer m; + mNodes.calcRawMeta(m, terResult); + + txnWrite(); + + Serializer s; + txn.add(s); + + if (isSetBit(params, tapOPEN_LEDGER)) { - cLog(lsINFO) << "applyTransaction: insufficient fee"; - - terResult = telINSUF_FEE_P; + if (!mLedger->addTransaction(txID, s)) + assert(false); } - } - else - { - if (saPaid) - { - // Transaction is malformed. - cLog(lsWARNING) << "applyTransaction: fee not allowed"; - - terResult = temINSUF_FEE_P; - } - } - } - - // Get source account ID. - mTxnAccountID = txn.getSourceAccount().getAccountID(); - if (tesSUCCESS == terResult && !mTxnAccountID) - { - cLog(lsWARNING) << "applyTransaction: bad source id"; - - terResult = temINVALID; - } - - if (tesSUCCESS != terResult) - return terResult; - - boost::recursive_mutex::scoped_lock sl(mLedger->mLock); - - mTxnAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mTxnAccountID)); - - // Find source account - // If are only forwarding, due to resource limitations, we might verifying only some transactions, this would be probablistic. - - STAmount saSrcBalance; - uint32 t_seq = txn.getSequence(); - bool bHaveAuthKey = false; - - if (!mTxnAccount) - { - cLog(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") % - txn.getSourceAccount().humanAccountID()); - - terResult = terNO_ACCOUNT; - } - else - { - saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); - bHaveAuthKey = mTxnAccount->isFieldPresent(sfRegularKey); - } - - // Check if account claimed. - if (tesSUCCESS == terResult) - { - switch (txn.getTxnType()) - { - case ttCLAIM: - if (bHaveAuthKey) - { - cLog(lsWARNING) << "applyTransaction: Account already claimed."; - - terResult = tefCLAIMED; - } - break; - - default: - nothing(); - break; - } - } - - // Consistency: Check signature - if (tesSUCCESS == terResult) - { - switch (txn.getTxnType()) - { - case ttCLAIM: - // Transaction's signing public key must be for the source account. - // To prove the master private key made this transaction. - if (naSigningPubKey.getAccountID() != mTxnAccountID) - { - // Signing Pub Key must be for Source Account ID. - cLog(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID(); - cLog(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID(); - - terResult = tefBAD_CLAIM_ID; - } - break; - - case ttREGULAR_KEY_SET: - // Transaction's signing public key must be for the source account. - // To prove the master private key made this transaction. - if (naSigningPubKey.getAccountID() != mTxnAccountID) - { - // Signing Pub Key must be for Source Account ID. - cLog(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID(); - cLog(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID(); - - terResult = temBAD_SET_ID; - } - break; - - default: - // Verify the transaction's signing public key is the key authorized for signing. - if (bHaveAuthKey && naSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfRegularKey).getAccountID()) - { - // Authorized to continue. - nothing(); - } - else if (naSigningPubKey.getAccountID() == mTxnAccountID) - { - // Authorized to continue. - nothing(); - } - else if (bHaveAuthKey) - { - cLog(lsINFO) << "applyTransaction: Delay: Not authorized to use account."; - - terResult = tefBAD_AUTH; - } - else - { - cLog(lsINFO) << "applyTransaction: Invalid: Not authorized to use account."; - - terResult = temBAD_AUTH_MASTER; - } - break; - } - } - - // Deduct the fee, so it's not available during the transaction. - // Will only write the account back, if the transaction succeeds. - if (tesSUCCESS != terResult || !saCost) - { - nothing(); - } - else if (saSrcBalance < saPaid) - { - cLog(lsINFO) - << boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s") - % saSrcBalance.getText() - % saPaid.getText()); - - terResult = terINSUF_FEE_B; - } - else - { - mTxnAccount->setFieldAmount(sfBalance, saSrcBalance - saPaid); - } - - // Validate sequence - if (tesSUCCESS != terResult) - { - nothing(); - } - else if (saCost) - { - uint32 a_seq = mTxnAccount->getFieldU32(sfSequence); - - cLog(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq; - - if (t_seq != a_seq) - { - if (a_seq < t_seq) - { - cLog(lsINFO) << "applyTransaction: future sequence number"; - - terResult = terPRE_SEQ; - } - else if (mLedger->hasTransaction(txID)) - terResult = tefALREADY; else { - cLog(lsWARNING) << "applyTransaction: past sequence number"; + if (!mLedger->addTransaction(txID, s, m)) + assert(false); - terResult = tefPAST_SEQ; + STAmount saPaid = txn.getTransactionFee(); + // Charge whatever fee they specified. + mLedger->destroyCoins(saPaid.getNValue()); } } - else + + mTxnAccount.reset(); + mNodes.clear(); + + if (!isSetBit(params, tapOPEN_LEDGER) + && (isTemMalformed(terResult) || isTefFailure(terResult))) { - mTxnAccount->setFieldU32(sfSequence, t_seq + 1); + // XXX Malformed or failed transaction in closed ledger must bow out. } - } - else + + return terResult; + }else { - cLog(lsINFO) << "applyTransaction: Zero cost transaction"; - - if (t_seq) - { - cLog(lsINFO) << "applyTransaction: bad sequence for pre-paid transaction"; - - terResult = tefPAST_SEQ; - } + cLog(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type"; + return temUNKNOWN; } - - if (tesSUCCESS == terResult) - { - entryModify(mTxnAccount); - - switch (txn.getTxnType()) - { - case ttACCOUNT_SET: - terResult = doAccountSet(txn); - break; - - - case ttTRUST_SET: - terResult = doTrustSet(txn); - break; - - case ttINVALID: - cLog(lsINFO) << "applyTransaction: invalid type"; - terResult = temINVALID; - break; - - case ttOFFER_CREATE: - terResult = doOfferCreate(txn); - break; - - case ttOFFER_CANCEL: - terResult = doOfferCancel(txn); - break; - - case ttREGULAR_KEY_SET: - terResult = doRegularKeySet(txn); - break; - - case ttPAYMENT: - terResult = doPayment(txn, params); - break; - - case ttWALLET_ADD: - terResult = doWalletAdd(txn); - break; - - case ttCONTRACT: - terResult = doContractAdd(txn); - break; - case ttCONTRACT_REMOVE: - terResult = doContractRemove(txn); - break; - - default: - terResult = temUNKNOWN; - break; - } - } - - std::string strToken; - std::string strHuman; - - transResultInfo(terResult, strToken, strHuman); - - cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; - - if (isTepPartial(terResult) && isSetBit(params, tapRETRY)) - { - // Partial result and allowed to retry, reclassify as a retry. - terResult = terRETRY; - } - - if ((tesSUCCESS == terResult) || isTepPartial(terResult)) - { - // Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially). - Serializer m; - mNodes.calcRawMeta(m, terResult); - - txnWrite(); - - Serializer s; - txn.add(s); - - 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()); - } - } - - mTxnAccount.reset(); - mNodes.clear(); - - if (!isSetBit(params, tapOPEN_LEDGER) - && (isTemMalformed(terResult) || isTefFailure(terResult))) - { - // XXX Malformed or failed transaction in closed ledger must bow out. - } - - return terResult; } // vim:ts=4 + diff --git a/src/cpp/ripple/TransactionEngine.h b/src/cpp/ripple/TransactionEngine.h index 1ecfadd43..67d970679 100644 --- a/src/cpp/ripple/TransactionEngine.h +++ b/src/cpp/ripple/TransactionEngine.h @@ -11,6 +11,10 @@ #include "TransactionErr.h" #include "InstanceCounter.h" +#include +#include +#include + DEFINE_INSTANCE(TransactionEngine); // A TransactionEngine applies serialized transactions to a ledger @@ -38,6 +42,7 @@ private: LedgerEntrySet mNodes; TER setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator); + TER checkSig(const SerializedTransaction& txn); TER takeOffers( bool bPassive, @@ -55,30 +60,26 @@ protected: uint160 mTxnAccountID; SLE::pointer mTxnAccount; + + + void txnWrite(); + + +public: + typedef boost::shared_ptr pointer; + + TransactionEngine() { ; } + TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); } + + LedgerEntrySet& getNodes() { return mNodes; } + Ledger::pointer getLedger() { return mLedger; } + void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; } + 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); } - void txnWrite(); - - TER doAccountSet(const SerializedTransaction& txn); - TER doTrustSet(const SerializedTransaction& txn); - TER doOfferCreate(const SerializedTransaction& txn); - TER doOfferCancel(const SerializedTransaction& txn); - TER doRegularKeySet(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); - -public: - TransactionEngine() { ; } - TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); } - - Ledger::pointer getLedger() { return mLedger; } - void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; } - TER applyTransaction(const SerializedTransaction&, TransactionEngineParams); }; diff --git a/src/cpp/ripple/Transactor.cpp b/src/cpp/ripple/Transactor.cpp new file mode 100644 index 000000000..4878ea406 --- /dev/null +++ b/src/cpp/ripple/Transactor.cpp @@ -0,0 +1,220 @@ +#include "Transactor.h" +#include "Log.h" +#include "Config.h" +#include "PaymentTransactor.h" +#include "RegularKeySetTransactor.h" +#include "AccountSetTransactor.h" +#include "WalletAddTransactor.h" +#include "OfferCancelTransactor.h" +#include "OfferCreateTransactor.h" +#include "TrustSetTransactor.h" + +SETUP_LOG(); + +Transactor::pointer Transactor::makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) +{ + switch(txn.getTxnType()) + { + case ttPAYMENT: + return( Transactor::pointer(new PaymentTransactor(txn,params,engine)) ); + case ttACCOUNT_SET: + return( Transactor::pointer(new AccountSetTransactor(txn,params,engine)) ); + case ttREGULAR_KEY_SET: + return( Transactor::pointer(new RegularKeySetTransactor(txn,params,engine)) ); + case ttTRUST_SET: + return( Transactor::pointer(new TrustSetTransactor(txn,params,engine)) ); + case ttOFFER_CREATE: + return( Transactor::pointer(new OfferCreateTransactor(txn,params,engine)) ); + case ttOFFER_CANCEL: + return( Transactor::pointer(new OfferCancelTransactor(txn,params,engine)) ); + case ttWALLET_ADD: + return( Transactor::pointer(new WalletAddTransactor(txn,params,engine)) ); + default: + return(Transactor::pointer()); + } +} + + +Transactor::Transactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : mTxn(txn), mParams(params), mEngine(engine) +{ + mHasAuthKey=false; +} + + + + +void Transactor::calculateFee() +{ + mFeeDue = theConfig.FEE_DEFAULT; +} + +TER Transactor::payFee() +{ + STAmount saPaid = mTxn.getTransactionFee(); + + // Only check fee is sufficient when the ledger is open. + if (isSetBit(mParams, tapOPEN_LEDGER) && saPaid < mFeeDue) + { + cLog(lsINFO) << "applyTransaction: insufficient fee"; + + return telINSUF_FEE_P; + } + + if( !saPaid ) return tesSUCCESS; + + // Deduct the fee, so it's not available during the transaction. + // Will only write the account back, if the transaction succeeds. + if (mSourceBalance < saPaid) + { + cLog(lsINFO) + << boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s") + % mSourceBalance.getText() + % saPaid.getText()); + + return terINSUF_FEE_B; + } + + mSourceBalance -= saPaid; + mTxnAccount->setFieldAmount(sfBalance, mSourceBalance); + + return tesSUCCESS; + +} + + +TER Transactor::checkSig() +{ + // Consistency: Check signature + // Verify the transaction's signing public key is the key authorized for signing. + if (mHasAuthKey && mSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfAuthorizedKey).getAccountID()) + { + // Authorized to continue. + nothing(); + } + else if (mSigningPubKey.getAccountID() == mTxnAccountID) + { + // Authorized to continue. + nothing(); + } + else if (mHasAuthKey) + { + cLog(lsINFO) << "applyTransaction: Delay: Not authorized to use account."; + + return tefBAD_AUTH; + } + else + { + cLog(lsINFO) << "applyTransaction: Invalid: Not authorized to use account."; + + return temBAD_AUTH_MASTER; + } + + return tesSUCCESS; +} + +TER Transactor::checkSeq() +{ + uint32 t_seq = mTxn.getSequence(); + uint32 a_seq = mTxnAccount->getFieldU32(sfSequence); + + cLog(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq; + + if (t_seq != a_seq) + { + if (a_seq < t_seq) + { + cLog(lsINFO) << "applyTransaction: future sequence number"; + + return terPRE_SEQ; + } + else + { + uint256 txID = mTxn.getTransactionID(); + if (mEngine->getLedger()->hasTransaction(txID)) + return tefALREADY; + } + + cLog(lsWARNING) << "applyTransaction: past sequence number"; + + return tefPAST_SEQ; + + }else + { + mTxnAccount->setFieldU32(sfSequence, t_seq + 1); + } + + return tesSUCCESS; +} + +// check stuff before you bother to lock the ledger +TER Transactor::preCheck() +{ + + mTxnAccountID = mTxn.getSourceAccount().getAccountID(); + if (!mTxnAccountID) + { + cLog(lsWARNING) << "applyTransaction: bad source id"; + + return temINVALID; + } + + // Extract signing key + // Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed + // without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is + // associated with the account. + // XXX This could be a lot cleaner to prevent unnecessary copying. + mSigningPubKey = RippleAddress::createAccountPublic(mTxn.getSigningPubKey()); + + // Consistency: really signed. + if ( !isSetBit(mParams, tapNO_CHECK_SIGN) && !mTxn.checkSign(mSigningPubKey)) + { + cLog(lsWARNING) << "applyTransaction: Invalid transaction: bad signature"; + + return temINVALID; + } + + return tesSUCCESS; +} + +TER Transactor::apply() +{ + TER terResult = tesSUCCESS; + terResult=preCheck(); + if(terResult != tesSUCCESS) return(terResult); + + calculateFee(); + + boost::recursive_mutex::scoped_lock sl(mEngine->getLedger()->mLock); + + mTxnAccount = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mTxnAccountID)); + + // Find source account + // If are only forwarding, due to resource limitations, we might verifying only some transactions, this would be probabilistic. + + if (!mTxnAccount) + { + cLog(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") % + mTxn.getSourceAccount().humanAccountID()); + + return terNO_ACCOUNT; + } + else + { + mSourceBalance = mTxnAccount->getFieldAmount(sfBalance); + mHasAuthKey = mTxnAccount->isFieldPresent(sfAuthorizedKey); + } + + terResult=payFee(); + if(terResult != tesSUCCESS) return(terResult); + + terResult=checkSig(); + if(terResult != tesSUCCESS) return(terResult); + + terResult=checkSeq(); + if(terResult != tesSUCCESS) return(terResult); + + mEngine->entryModify(mTxnAccount); + + return doApply(); + +} \ No newline at end of file diff --git a/src/cpp/ripple/Transactor.h b/src/cpp/ripple/Transactor.h new file mode 100644 index 000000000..88a1a6764 --- /dev/null +++ b/src/cpp/ripple/Transactor.h @@ -0,0 +1,41 @@ +#ifndef __TRANSACTOR__ +#define __TRANSACTOR__ + +#include "SerializedTransaction.h" +#include "TransactionErr.h" +#include "TransactionEngine.h" +#include + +class Transactor +{ +protected: + const SerializedTransaction& mTxn; + TransactionEngine* mEngine; + TransactionEngineParams mParams; + + uint160 mTxnAccountID; + STAmount mFeeDue; + STAmount mSourceBalance; + SLE::pointer mTxnAccount; + bool mHasAuthKey; + RippleAddress mSigningPubKey; + + + TER preCheck(); + TER checkSeq(); + TER payFee(); + virtual void calculateFee(); + virtual TER checkSig(); + virtual TER doApply()=0; + + Transactor(const SerializedTransaction& txn, TransactionEngineParams params, TransactionEngine* engine); + +public: + typedef boost::shared_ptr pointer; + + static Transactor::pointer makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine); + + TER apply(); +}; + +#endif \ No newline at end of file diff --git a/src/cpp/ripple/TrustSetTransactor.cpp b/src/cpp/ripple/TrustSetTransactor.cpp new file mode 100644 index 000000000..9eb5cbc06 --- /dev/null +++ b/src/cpp/ripple/TrustSetTransactor.cpp @@ -0,0 +1,147 @@ +#include "TrustSetTransactor.h" + +TER TrustSetTransactor::doApply() +{ + TER terResult = tesSUCCESS; + Log(lsINFO) << "doTrustSet>"; + + const STAmount saLimitAmount = mTxn.getFieldAmount(sfLimitAmount); + const bool bQualityIn = mTxn.isFieldPresent(sfQualityIn); + const uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32(sfQualityIn) : 0; + const bool bQualityOut = mTxn.isFieldPresent(sfQualityOut); + const uint32 uQualityOut = bQualityIn ? mTxn.getFieldU32(sfQualityOut) : 0; + const uint160 uCurrencyID = saLimitAmount.getCurrency(); + uint160 uDstAccountID = saLimitAmount.getIssuer(); + const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. + bool bDelIndex = false; + + // Check if destination makes sense. + + if (saLimitAmount.isNegative()) + { + Log(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit."; + + return temBAD_AMOUNT; + } + else if (!uDstAccountID) + { + Log(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; + + return temDST_NEEDED; + } + else if (mTxnAccountID == uDstAccountID) + { + Log(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; + + return temDST_IS_SRC; + } + + SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if (!sleDst) + { + Log(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + STAmount saLimitAllow = saLimitAmount; + saLimitAllow.setIssuer(mTxnAccountID); + + SLE::pointer sleRippleState = mEngine->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->getFieldAmount(sfLowLimit).getIssuer(); + uint160 uHighID = sleRippleState->getFieldAmount(sfHighLimit).getIssuer(); + bool bLow = uLowID == uSrcAccountID; + bool bHigh = uLowID == uDstAccountID; + bool bBalanceZero = !sleRippleState->getFieldAmount(sfBalance); + STAmount saDstLimit = sleRippleState->getFieldAmount(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) + { + sleRippleState->setFieldAmount(bFlipped ? sfHighLimit: sfLowLimit, saLimitAllow); + + if (!bQualityIn) + { + nothing(); + } + else if (uQualityIn) + { + sleRippleState->setFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + } + else + { + sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); + } + + if (!bQualityOut) + { + nothing(); + } + else if (uQualityOut) + { + sleRippleState->setFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); + } + else + { + sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); + } + + mEngine->entryModify(sleRippleState); + } + + Log(lsINFO) << "doTrustSet: Modifying ripple line: bDelIndex=" << bDelIndex; + } + // Line does not exist. + else if (!saLimitAmount) + { + Log(lsINFO) << "doTrustSet: Redundant: Setting non-existent ripple line to 0."; + + return terNO_LINE_NO_ZERO; + } + else + { + // Create a new ripple line. + sleRippleState = mEngine->entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); + + Log(lsINFO) << "doTrustSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); + + sleRippleState->setFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. + sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAllow); + sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uDstAccountID)); + + if (uQualityIn) + sleRippleState->setFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); + if (uQualityOut) + sleRippleState->setFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut); + + uint64 uSrcRef; // Ignored, dirs never delete. + + terResult = mEngine->getNodes().dirAdd(uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex()); + + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().dirAdd(uSrcRef, Ledger::getOwnerDirIndex(uDstAccountID), sleRippleState->getIndex()); + } + + Log(lsINFO) << "doTrustSet<"; + + return terResult; +} \ No newline at end of file diff --git a/src/cpp/ripple/TrustSetTransactor.h b/src/cpp/ripple/TrustSetTransactor.h new file mode 100644 index 000000000..69b09aa01 --- /dev/null +++ b/src/cpp/ripple/TrustSetTransactor.h @@ -0,0 +1,9 @@ +#include "Transactor.h" + +class TrustSetTransactor : public Transactor +{ +public: + TrustSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index f4c7b941b..eded830c6 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -97,12 +97,12 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren uint32 now = theApp->getOPs().getNetworkTimeNC(); if (set) { - for (ValidationSet::iterator vit = set->begin(), end = set->end(); vit != end; ++vit) + BOOST_FOREACH(u160_val_pair& it, *set) { - bool isTrusted = vit->second->isTrusted(); + bool isTrusted = it.second->isTrusted(); if (isTrusted && currentOnly) { - uint32 closeTime = vit->second->getSignTime(); + uint32 closeTime = it.second->getSignTime(); if ((now < (closeTime - LEDGER_EARLY_INTERVAL)) || (now > (closeTime + LEDGER_VAL_INTERVAL))) isTrusted = false; else @@ -119,6 +119,28 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren cLog(lsTRACE) << "VC: " << ledger << "t:" << trusted << " u:" << untrusted; } +void ValidationCollection::getValidationTypes(const uint256& ledger, int& full, int& partial) +{ + full = partial = 0; + boost::mutex::scoped_lock sl(mValidationLock); + VSpointer set = findSet(ledger); + if (set) + { + BOOST_FOREACH(u160_val_pair& it, *set) + { + if (it.second->isTrusted()) + { + if (it.second->isFull()) + ++full; + else + ++partial; + } + } + } + cLog(lsTRACE) << "VC: " << ledger << "f:" << full << " p:" << partial; +} + + int ValidationCollection::getTrustedValidationCount(const uint256& ledger) { int trusted = 0; @@ -126,9 +148,9 @@ int ValidationCollection::getTrustedValidationCount(const uint256& ledger) VSpointer set = findSet(ledger); if (set) { - for (ValidationSet::iterator vit = set->begin(), end = set->end(); vit != end; ++vit) + BOOST_FOREACH(u160_val_pair& it, *set) { - if (vit->second->isTrusted()) + if (it.second->isTrusted()) ++trusted; } } diff --git a/src/cpp/ripple/ValidationCollection.h b/src/cpp/ripple/ValidationCollection.h index e4a22ec4f..51f60a5a9 100644 --- a/src/cpp/ripple/ValidationCollection.h +++ b/src/cpp/ripple/ValidationCollection.h @@ -38,6 +38,7 @@ public: bool addValidation(const SerializedValidation::pointer&); ValidationSet getValidations(const uint256& ledger); void getValidationCount(const uint256& ledger, bool currentOnly, int& trusted, int& untrusted); + void getValidationTypes(const uint256& ledger, int& full, int& partial); int getTrustedValidationCount(const uint256& ledger); diff --git a/src/cpp/ripple/WalletAddTransactor.cpp b/src/cpp/ripple/WalletAddTransactor.cpp new file mode 100644 index 000000000..88dd8f026 --- /dev/null +++ b/src/cpp/ripple/WalletAddTransactor.cpp @@ -0,0 +1,58 @@ +#include "WalletAddTransactor.h" + +TER WalletAddTransactor::doApply() +{ + std::cerr << "WalletAdd>" << std::endl; + + const std::vector vucPubKey = mTxn.getFieldVL(sfPublicKey); + const std::vector vucSignature = mTxn.getFieldVL(sfSignature); + const uint160 uAuthKeyID = mTxn.getFieldAccount160(sfAuthorizedKey); + const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey); + const uint160 uDstAccountID = naMasterPubKey.getAccountID(); + + // FIXME: This should be moved to the transaction's signature check logic and cached + 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 = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + if (sleDst) + { + std::cerr << "WalletAdd: account already created" << std::endl; + + return tefCREATED; + } + + STAmount saAmount = mTxn.getFieldAmount(sfAmount); + STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); + + if (saSrcBalance < saAmount) + { + std::cerr + << boost::str(boost::format("WalletAdd: Delay transaction: insufficient balance: balance=%s amount=%s") + % saSrcBalance.getText() + % saAmount.getText()) + << std::endl; + + return terUNFUNDED; + } + + // Deduct initial balance from source account. + mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saAmount); + + // Create the account. + sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->setFieldAccount(sfAccount, uDstAccountID); + sleDst->setFieldU32(sfSequence, 1); + sleDst->setFieldAmount(sfBalance, saAmount); + sleDst->setFieldAccount(sfAuthorizedKey, uAuthKeyID); + + std::cerr << "WalletAdd<" << std::endl; + + return tesSUCCESS; +} \ No newline at end of file diff --git a/src/cpp/ripple/WalletAddTransactor.h b/src/cpp/ripple/WalletAddTransactor.h new file mode 100644 index 000000000..8bed5f0fe --- /dev/null +++ b/src/cpp/ripple/WalletAddTransactor.h @@ -0,0 +1,10 @@ +#include "Transactor.h" + + +class WalletAddTransactor : public Transactor +{ +public: + WalletAddTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/js/remote.js b/src/js/remote.js index 40e931f9d..5ae60c078 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -21,7 +21,7 @@ var EventEmitter = require('events').EventEmitter; var Amount = require('./amount.js').Amount; var UInt160 = require('./amount.js').UInt160; -// Request events emmitted: +// Request events emitted: // 'success' : Request successful. // 'error' : Request failed. // 'remoteError' @@ -166,11 +166,11 @@ Request.prototype.accounts = function (accounts) { // Remote - access to a remote Ripple server via websocket. // // Events: -// 'connectted' +// 'connected' // 'disconnected' // 'state': -// - 'online' : connectted and subscribed -// - 'offline' : not subscribed or not connectted. +// - 'online' : connected and subscribed +// - 'offline' : not subscribed or not connected. // 'ledger_closed': A good indicate of ready to serve. // 'subscribed' : This indicates stand-alone is available. // @@ -268,7 +268,7 @@ Remote.fees = { 'offer' : Amount.from_json("10"), }; -// Set the emited state: 'online' or 'offline' +// Set the emitted state: 'online' or 'offline' Remote.prototype._set_state = function (state) { if (this.trace) console.log("remote: set_state: %s", state); @@ -530,21 +530,16 @@ Remote.prototype.request = function (request) { }; Remote.prototype.request_server_info = function () { - var request = new Request(this, 'rpc'); - - request.message.command = 'server_info'; - - return request; + return new Request(this, 'server_info'); }; Remote.prototype.request_ledger = function (params) { // XXX Does this require the server to be trusted? //assert(this.trusted); - var request = new Request(this, 'rpc'); + var request = new Request(this, 'ledger'); - request.message.command = 'ledger'; - request.message.params = params; + request.message.params = params; return request; }; @@ -553,21 +548,19 @@ Remote.prototype.request_ledger = function (params) { Remote.prototype.request_ledger_hash = function () { assert(this.trusted); // If not trusted, need to check proof. - var request = new Request(this, 'rpc'); - - request.message.command = 'ledger_closed'; + return new Request(this, 'ledger_closed'); +}; - return request; +// .ledger() +// .ledger_index() +Remote.prototype.request_ledger_header = function () { + return new Request(this, 'ledger_header'); }; // Get the current proposed ledger entry. May be closed (and revised) at any time (even before returning). // Only for unit testing. Remote.prototype.request_ledger_current = function () { - var request = new Request(this, 'rpc'); - - request.message.command = 'ledger_current'; - - return request; + return new Request(this, 'ledger_current'); }; // --> type : the type of ledger entry. @@ -578,9 +571,7 @@ Remote.prototype.request_ledger_entry = function (type) { assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. var self = this; - var request = new Request(this, 'rpc'); - - request.message.command = 'ledger_entry'; + var request = new Request(this, 'ledger_entry'); if (type) this.type = type; @@ -662,11 +653,7 @@ Remote.prototype.request_unsubscribe = function (streams) { Remote.prototype.request_transaction_entry = function (hash) { assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. - var request = new Request(this, 'rpc'); - - request.message.command = 'transaction_entry'; - - return request + return (new Request(this, 'transaction_entry')) .tx_hash(hash); }; @@ -674,9 +661,8 @@ Remote.prototype.request_ripple_lines_get = function (accountID) { // XXX Does this require the server to be trusted? //assert(this.trusted); - var request = new Request(this, 'rpc'); + var request = new Request(this, 'ripple_lines_get'); - request.message.command = 'ripple_lines_get'; // XXX Convert API call to JSON request.message.params = [accountID]; @@ -687,9 +673,8 @@ Remote.prototype.request_wallet_accounts = function (key) { // XXX Does this require the server to be trusted? //assert(this.trusted); - var request = new Request(this, 'rpc'); + var request = new Request(this, 'wallet_accounts'); - request.message.command = 'wallet_accounts'; // XXX Convert API call to JSON request.message.params = [key]; @@ -700,9 +685,8 @@ Remote.prototype.request_account_tx = function (accountID, minLedger, maxLedger) // XXX Does this require the server to be trusted? //assert(this.trusted); - var request = new Request(this, 'rpc'); + var request = new Request(this, 'account_tx'); - request.message.command = 'account_tx'; // XXX Convert API call to JSON request.message.params = [accountID, minLedger, maxLedger]; @@ -751,9 +735,7 @@ Remote.prototype.submit = function (transaction) { .request(); } else { - var submit_request = new Request(this, 'rpc'); - - submit_request.message.command = 'submit_json'; + var submit_request = new Request(this, 'submit_json'); submit_request.tx_json(transaction.tx_json); submit_request.secret(transaction.secret); @@ -808,9 +790,7 @@ Remote.prototype._server_subscribe = function () { Remote.prototype.ledger_accept = function () { if (this.stand_alone || undefined === this.stand_alone) { - var request = new Request(this, 'rpc'); - - request.message.command = 'ledger_accept'; + var request = new Request(this, 'ledger_accept'); request .request(); @@ -957,43 +937,32 @@ Remote.prototype.request_ripple_balance = function (account, issuer, currency, c } Remote.prototype.request_unl_list = function () { - var request = new Request(this, 'rpc'); - - request.message.command = 'unl_list'; - - return request; + return new Request(this, 'unl_list'); }; Remote.prototype.request_unl_add = function (addr, note) { - var request = new Request(this, 'rpc'); + var request = new Request(this, 'unl_add'); - request.message.command = 'unl_add'; request.message.params = [addr, note]; return request; }; Remote.prototype.request_unl_delete = function (publicKey) { - var request = new Request(this, 'rpc'); + var request = new Request(this, 'unl_delete'); - request.message.command = 'unl_delete'; request.message.params = [publicKey]; return request; }; Remote.prototype.request_peers = function () { - var request = new Request(this, 'rpc'); - - request.message.command = 'peers'; - - return request; + return new Request(this, 'peers'); }; Remote.prototype.request_connect = function (ip, port) { - var request = new Request(this, 'rpc'); + var request = new Request(this, 'connect'); - request.message.command = 'connect'; request.message.params = [ip, port]; return request; @@ -1421,7 +1390,7 @@ Transaction.prototype.payment = function (src, dst, deliver_amount) { Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) { this.secret = this._account_secret(src); - this.tx_json.TransactionType = 'CreditSet'; + this.tx_json.TransactionType = 'TrustSet'; this.tx_json.Account = UInt160.json_rewrite(src); // Allow limit of 0 through. diff --git a/test/config.js b/test/config.js index 42ce8bea4..6a693172d 100644 --- a/test/config.js +++ b/test/config.js @@ -14,12 +14,13 @@ exports.servers = { // A local test server. "alpha" : { 'trusted' : true, + 'no_server' : true, // "peer_ip" : "0.0.0.0", // "peer_port" : 51235, 'rpc_ip' : "0.0.0.0", 'rpc_port' : 5005, 'websocket_ip' : "127.0.0.1", - 'websocket_port' : 6005, + 'websocket_port' : 5006, // 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", // 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta" } diff --git a/test/send-test.js b/test/send-test.js index 77811d301..9d732818b 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -15,11 +15,26 @@ var serverDelay = 1500; buster.testRunner.timeout = 5000; +buster.testCase("Simple", { + 'setUp' : testutils.build_setup({no_server: true}), + 'tearDown' : testutils.build_teardown(), + + "simple." : + function (done) { buster.assert(1); done(); + + this.remote.transaction() + .payment('root', 'alice', "10000") + .on('success', function (r) { + done(); + }).submit(); + } + }); + buster.testCase("Sending", { 'setUp' : testutils.build_setup(), 'tearDown' : testutils.build_teardown(), - "send XRP to non-existant account without create." : + "=> send XRP to non-existent account without create." : function (done) { var self = this; var ledgers = 20; @@ -77,7 +92,7 @@ buster.testCase("Sending", { }, // Also test transaction becomes lost after terNO_DST. - "credit_limit to non-existant account = terNO_DST" : + "credit_limit to non-existent account = terNO_DST" : function (done) { this.remote.transaction() .ripple_line_set("root", "100/USD/alice") @@ -102,7 +117,7 @@ buster.testCase("Sending", { testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "mtgox"], callback); }, function (callback) { - self.what = "Check a non-existant credit limit."; + self.what = "Check a non-existent credit limit."; self.remote.request_ripple_balance("alice", "mtgox", "USD", 'CURRENT') .on('ripple_state', function (m) { @@ -245,7 +260,7 @@ buster.testCase("Sending future", { function (done) { var self = this; - // self.remote.set_trace(); + self.remote.set_trace(); async.waterfall([ function (callback) { @@ -276,7 +291,7 @@ buster.testCase("Sending future", { buster.assert(m.result !== 'tesSUCCESS'); }) .submit(); - }, + },/* function (callback) { self.what = "Verify balance."; @@ -412,7 +427,7 @@ buster.testCase("Sending future", { callback(); }) .request(); - }, + },*/ // function (callback) { // // Make sure all is good after canonical ordering. // self.what = "Close the ledger and check balance."; diff --git a/test/testutils.js b/test/testutils.js index c087b3d0a..1fc424619 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -106,7 +106,10 @@ var build_setup = function (opts, host) { * @param host {String} Identifier for the host configuration to be used. */ var build_teardown = function (host) { + return function (done) { + + host = host || config.server_default; var data = this.store[host]; @@ -114,16 +117,22 @@ var build_teardown = function (host) { async.series([ function disconnectWebsocketStep(callback) { + data.remote .on('disconnected', callback) .connect(false); }, function stopServerStep(callback) { - if (opts.no_server) return callback(); + + if (opts.no_server) + { + + return callback(); + } data.server.on('stopped', callback).stop(); } - ], done); + ], done); }; }; diff --git a/test/websocket-test.js b/test/websocket-test.js index 8c0d2940c..6730866bb 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -2,6 +2,7 @@ var buster = require("buster"); var Server = require("./server.js").Server; var Remote = require("../src/js/remote.js").Remote; +var config = require("./config.js"); require("../src/js/remote.js").config = require("./config.js"); @@ -9,10 +10,10 @@ buster.testRunner.timeout = 5000; buster.testCase("WebSocket connection", { 'setUp' : - function (done) { server = Server.from_config("alpha").on('started', done).start(); }, + function (done) { if (config.servers.alpha.no_server) done(); else server = Server.from_config("alpha").on('started', done).start(); }, 'tearDown' : - function (done) { server.on('stopped', done).stop(); }, + function (done) { if (config.servers.alpha.no_server) done(); else server.on('stopped', done).stop(); }, "websocket connect and disconnect" : function (done) {