From aababa680f0caccf97037c2c081adb7c03da0b62 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Tue, 24 Jul 2012 15:02:09 -0700 Subject: [PATCH] Add ripple quality in and out. --- src/LedgerFormats.cpp | 10 +- src/RPCServer.cpp | 19 +- src/SerializedObject.h | 4 + src/SerializedTypes.h | 14 + src/Transaction.cpp | 29 +- src/Transaction.h | 12 +- src/TransactionEngine.cpp | 635 ++++++++++++++++++++++++++++++++----- src/TransactionEngine.h | 23 +- src/TransactionFormats.cpp | 9 +- src/main.cpp | 2 +- 10 files changed, 642 insertions(+), 115 deletions(-) diff --git a/src/LedgerFormats.cpp b/src/LedgerFormats.cpp index 20f49943f..da46d732d 100644 --- a/src/LedgerFormats.cpp +++ b/src/LedgerFormats.cpp @@ -64,8 +64,10 @@ LedgerEntryFormat LedgerFormats[]= { S_FIELD(LowLimit), STI_AMOUNT, SOE_REQUIRED, 0 }, { S_FIELD(HighID), STI_ACCOUNT, SOE_REQUIRED, 0 }, { S_FIELD(HighLimit), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(QualityIn), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(QualityOut), STI_UINT32, SOE_IFFLAG, 2 }, + { S_FIELD(LowQualityIn), STI_UINT32, SOE_IFFLAG, 1 }, + { S_FIELD(LowQualityOut), STI_UINT32, SOE_IFFLAG, 2 }, + { S_FIELD(HighQualityIn), STI_UINT32, SOE_IFFLAG, 4 }, + { S_FIELD(HighQualityOut), STI_UINT32, SOE_IFFLAG, 8 }, { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } }, @@ -75,9 +77,9 @@ LedgerEntryFormat LedgerFormats[]= LedgerEntryFormat* getLgrFormat(LedgerEntryType t) { LedgerEntryFormat* f = LedgerFormats; - while(f->t_name != NULL) + while (f->t_name != NULL) { - if(f->t_type == t) return f; + if (f->t_type == t) return f; ++f; } return NULL; diff --git a/src/RPCServer.cpp b/src/RPCServer.cpp index 1b87d9acf..43be2f8af 100644 --- a/src/RPCServer.cpp +++ b/src/RPCServer.cpp @@ -1125,15 +1125,19 @@ Json::Value RPCServer::doPeers(const Json::Value& params) return obj; } -// ripple_line_set [] [] +// ripple_line_set [] [] [] Json::Value RPCServer::doRippleLineSet(const Json::Value& params) { NewcoinAddress naSeed; NewcoinAddress naSrcAccountID; NewcoinAddress naDstAccountID; STAmount saLimitAmount; - uint256 uLedger = mNetOps->getCurrentLedger(); - uint32 uAcceptRate = params.size() >= 6 ? lexical_cast_s(params[5u].asString()) : 0; + uint256 uLedger = mNetOps->getCurrentLedger(); + bool bLimitAmount = true; + bool bQualityIn = params.size() >= 6; + bool bQualityOut = params.size() >= 7; + uint32 uQualityIn = bQualityIn ? lexical_cast_s(params[5u].asString()) : 0; + uint32 uQualityOut = bQualityOut ? lexical_cast_s(params[6u].asString()) : 0; if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -1171,8 +1175,9 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) theConfig.FEE_DEFAULT, 0, // YYY No source tag naDstAccountID, - saLimitAmount, - uAcceptRate); + bLimitAmount, saLimitAmount, + bQualityIn, uQualityIn, + bQualityOut, uQualityOut); trans = mNetOps->submitTransaction(trans); @@ -1181,8 +1186,6 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) obj["seed"] = naSeed.humanSeed(); obj["srcAccountID"] = naSrcAccountID.humanAccountID(); obj["dstAccountID"] = naDstAccountID.humanAccountID(); - obj["limitAmount"] = saLimitAmount.getText(); - obj["acceptRate"] = uAcceptRate; return obj; } @@ -2133,7 +2136,7 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { "password_set", &RPCServer::doPasswordSet, 2, 3, false, optNetwork }, { "peers", &RPCServer::doPeers, 0, 0, true }, { "ripple_lines_get", &RPCServer::doRippleLinesGet, 1, 2, false, optCurrent|optClosed }, - { "ripple_line_set", &RPCServer::doRippleLineSet, 4, 6, false, optCurrent }, + { "ripple_line_set", &RPCServer::doRippleLineSet, 4, 7, false, optCurrent }, { "send", &RPCServer::doSend, 3, 7, false, optCurrent }, { "server_info", &RPCServer::doServerInfo, 0, 0, true }, { "stop", &RPCServer::doStop, 0, 0, true }, diff --git a/src/SerializedObject.h b/src/SerializedObject.h index be1bd051b..b7b98f1fc 100644 --- a/src/SerializedObject.h +++ b/src/SerializedObject.h @@ -55,6 +55,8 @@ enum SOE_Field sfHash, sfHighID, sfHighLimit, + sfHighQualityIn, + sfHighQualityOut, sfIdentifier, sfIndexes, sfIndexNext, @@ -67,6 +69,8 @@ enum SOE_Field sfLimitAmount, sfLowID, sfLowLimit, + sfLowQualityIn, + sfLowQualityOut, sfMessageKey, sfMinimumOffer, sfNextAcceptExpire, diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index a06d2859e..db4e8d2ba 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -36,6 +36,20 @@ enum SerializedTypeID STI_LEDGERENTRY = 102 }; +enum PathFlags +{ + PF_END = 0x00, // End of current path & path list. + PF_BOUNDRY = 0xFF, // End of current path & new path follows. + + PF_ACCOUNT = 0x01, + PF_OFFER = 0x02, + + PF_WANTED_CURRENCY = 0x10, + PF_WANTED_ISSUER = 0x20, + PF_REDEEM = 0x40, + PF_ISSUE = 0x80, +}; + class SerializedType { protected: diff --git a/src/Transaction.cpp b/src/Transaction.cpp index 9387f7d34..abee0b2f9 100644 --- a/src/Transaction.cpp +++ b/src/Transaction.cpp @@ -232,13 +232,23 @@ Transaction::pointer Transaction::sharedCreate( Transaction::pointer Transaction::setCreditSet( const NewcoinAddress& naPrivateKey, const NewcoinAddress& naDstAccountID, + bool bLimitAmount, const STAmount& saLimitAmount, - uint32 uAcceptRate) + bool bQualityIn, + uint32 uQualityIn, + bool bQualityOut, + uint32 uQualityOut) { mTransaction->setITFieldAccount(sfDestination, naDstAccountID); - mTransaction->setITFieldAmount(sfLimitAmount, saLimitAmount); - if (uAcceptRate) - mTransaction->setITFieldU32(sfAcceptRate, uAcceptRate); + + if (bLimitAmount) + mTransaction->setITFieldAmount(sfLimitAmount, saLimitAmount); + + if (bQualityIn) + mTransaction->setITFieldU32(sfAcceptRate, uQualityIn); + + if (bQualityOut) + mTransaction->setITFieldU32(sfAcceptRate, uQualityOut); sign(naPrivateKey); @@ -252,12 +262,19 @@ Transaction::pointer Transaction::sharedCreditSet( const STAmount& saFee, uint32 uSourceTag, const NewcoinAddress& naDstAccountID, + bool bLimitAmount, const STAmount& saLimitAmount, - uint32 uAcceptRate) + bool bQualityIn, + uint32 uQualityIn, + bool bQualityOut, + uint32 uQualityOut) { pointer tResult = boost::make_shared(ttCREDIT_SET, naPublicKey, naSourceAccount, uSeq, saFee, uSourceTag); - return tResult->setCreditSet(naPrivateKey, naDstAccountID, saLimitAmount, uAcceptRate); + return tResult->setCreditSet(naPrivateKey, naDstAccountID, + bLimitAmount, saLimitAmount, + bQualityIn, uQualityIn, + bQualityOut, uQualityOut); } // diff --git a/src/Transaction.h b/src/Transaction.h index f867ffd26..dd88c1ae7 100644 --- a/src/Transaction.h +++ b/src/Transaction.h @@ -68,8 +68,12 @@ private: Transaction::pointer setCreditSet( const NewcoinAddress& naPrivateKey, const NewcoinAddress& naDstAccountID, + bool bLimitAmount, const STAmount& saLimitAmount, - uint32 uAcceptRate); + bool bQualityIn, + uint32 uQualityIn, + bool bQualityOut, + uint32 uQualityOut); Transaction::pointer setNicknameSet( const NewcoinAddress& naPrivateKey, @@ -166,8 +170,12 @@ public: const STAmount& saFee, uint32 uSourceTag, const NewcoinAddress& naDstAccountID, + bool bLimitAmount, const STAmount& saLimitAmount, - uint32 uAcceptRate); + bool bQualityIn, + uint32 uQualityIn, + bool bQualityOut, + uint32 uQualityOut); // Set Nickname static Transaction::pointer sharedNicknameSet( diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index bb42e2a23..fca01e799 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -111,6 +111,7 @@ STAmount TransactionEngine::rippleHolds(const uint160& uAccountID, const uint160 return saBalance; } +// <-- saAmount: amount of uCurrency held by uAccountID. May be negative. STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID) { STAmount saAmount; @@ -138,7 +139,11 @@ STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint16 return saAmount; } +// Returns the funds available for uAccountID for a currency/issuer. +// Use when you need a default for rippling uAccountID's currency. // --> saDefault/currency/issuer +// <-- saFunds: Funds available. May be negative. +// If the issuer is the same as uAccountID, result is Default. STAmount TransactionEngine::accountFunds(const uint160& uAccountID, const STAmount& saDefault) { STAmount saFunds; @@ -582,28 +587,53 @@ TransactionEngineResult TransactionEngine::dirDelete( return terSUCCESS; } -// --> uRootIndex -// <-- uEntryIndex -// <-- uEntryNode -void TransactionEngine::dirFirst(const uint256& uRootIndex, uint256& uEntryIndex, uint64& uEntryNode) +// <-- true, if had a next entry. +bool TransactionEngine::dirFirst( + const uint256& uRootIndex, // --> Root of directory. + SLE::pointer& sleNode, // <-> current node + unsigned int& uDirEntry, // <-- next entry + uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. { - SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); + sleNode = entryCache(ltDIR_NODE, uRootIndex); + uDirEntry = 0; - STVector256 svIndexes = sleRoot->getIFieldV256(sfIndexes); + assert(sleNode); // We never probe for directories. + + return TransactionEngine::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); +} + +// <-- true, if had a next entry. +bool TransactionEngine::dirNext( + const uint256& uRootIndex, // --> Root of directory + SLE::pointer& sleNode, // <-> current node + unsigned int& uDirEntry, // <-> next entry + uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. +{ + STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); std::vector& vuiIndexes = svIndexes.peekValue(); - if (vuiIndexes.empty()) + if (uDirEntry == vuiIndexes.size()) { - uEntryNode = sleRoot->getIFieldU64(sfIndexNext); + uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); - SLE::pointer sleNext = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uEntryNode)); - uEntryIndex = sleNext->getIFieldV256(sfIndexes).peekValue()[0]; - } - else - { - uEntryIndex = vuiIndexes[0]; - uEntryNode = 0; + if (!uNodeNext) + { + uEntryIndex.zero(); + + return false; + } + else + { + sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); + uDirEntry = 0; + + return dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); + } } + + uEntryIndex = vuiIndexes[uDirEntry++]; + + return true; } // Set the authorized public key for an account. May also set the generator map. @@ -1329,10 +1359,15 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti return terNO_DST; } - STAmount saLimitAmount = txn.getITFieldAmount(sfLimitAmount); - uint160 uCurrency = saLimitAmount.getCurrency(); bool bFlipped = mTxnAccountID > uDstAccountID; uint32 uFlags = bFlipped ? lsfLowIndexed : lsfHighIndexed; + bool bLimitAmount = txn.getITFieldPresent(sfLimitAmount); + STAmount saLimitAmount = bLimitAmount ? txn.getITFieldAmount(sfLimitAmount) : STAmount(); + bool bQualityIn = txn.getITFieldPresent(sfQualityIn); + uint32 uQualityIn = bQualityIn ? txn.getITFieldU32(sfQualityIn) : 0; + bool bQualityOut = txn.getITFieldPresent(sfQualityOut); + uint32 uQualityOut = bQualityIn ? txn.getITFieldU32(sfQualityOut) : 0; + uint160 uCurrency = saLimitAmount.getCurrency(); STAmount saBalance(uCurrency); bool bAddIndex = false; bool bDelIndex = false; @@ -1364,11 +1399,39 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti } } #endif + if (!bDelIndex) { - bAddIndex = !(sleRippleState->getFlags() & uFlags); + if (bLimitAmount) + sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit: sfLowLimit , saLimitAmount); - sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit: sfLowLimit , saLimitAmount); + if (!bQualityIn) + { + nothing(); + } + else if (uQualityIn) + { + sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + } + else + { + sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); + } + + if (!bQualityOut) + { + nothing(); + } + else if (uQualityOut) + { + sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); + } + else + { + sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); + } + + bAddIndex = !(sleRippleState->getFlags() & uFlags); if (bAddIndex) sleRippleState->setFlag(uFlags); @@ -1401,6 +1464,10 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, saZero); sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID); sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID); + if (uQualityIn) + sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + if (uQualityOut) + sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); } if (bAddIndex) @@ -1517,19 +1584,9 @@ TransactionEngineResult TransactionEngine::doPasswordSet(const SerializedTransac } #ifdef WORK_IN_PROGRESS -TransactionEngineResult calcOfferFill(SAAmount& saSrc, paymentNode& pnSrc, paymentNode& pnDst) -{ - TransactionEngineResult terResult; - - if (!saSrc.isZero()) - { - - } - - return bSuccess; -} - +// XXX Need to adjust for fees. // Find offers to satisfy pnDst. +// - Does not adjust any balances as there is at least a forward pass to come. // --> pnDst.saWanted: currency and amount wanted // --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use. // --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, limit to use. @@ -1543,25 +1600,389 @@ TransactionEngineResult calcOfferFill(paymentNode& pnSrc, paymentNode& pnDst, bo { TransactionEngineResult terResult; - terResult = calcOfferFill(pnSrc.saIOURedeem, pnSrc, pnDst, bAllowPartial); - - if (terSUCCESS == terResult) + if (pnDst.saWanted.isNative()) { - terResult = calcOfferFill(pnSrc.saIOUIssue, pnSrc, pnDst, bAllowPartial) - } + // Transfer stamps. - if (terSUCCESS == terResult && !bAllowPartial) - { - STAmount saTotal = pnSrc.saIOURedeem; - saTotal += pnSrc.saIOUIssue; + STAmount saSrcFunds = pnSrc.saAccount->accountHolds(pnSrc.saAccount, uint160(0), uint160(0)); - if (saTotal != saWanted) + if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted)) + { + pnSrc.saSend = min(saSrcFunds, pnDst.saWanted); + pnDst.saReceive = pnSrc.saSend; + } + else + { terResult = terINSUF_PATH; + } + } + else + { + // Ripple funds. + { + prv->saSend = min(prv->account->saBalance(), cur->saWanted); + // Redeem to limit. + terResult = calcOfferFill( + accountHolds(pnSrc.saAccount, pnDst.saWanted.getCurrency(), pnDst.saWanted.getIssuer()), + pnSrc.saIOURedeem, + pnDst.saIOUForgive, + bAllowPartial); + + if (terSUCCESS == terResult) + { + // Issue to wanted. + terResult = calcOfferFill( + pnDst.saWanted, // As much as wanted is available, limited by credit limit. + pnSrc.saIOUIssue, + pnDst.saIOUAccept, + bAllowPartial); + } + + if (terSUCCESS == terResult && !bAllowPartial) + { + STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept; + + if (saTotal != saWanted) + terResult = terINSUF_PATH; + } } return terResult; } +// Get the next offer limited by funding. +// - Stop when becomes unfunded. +void TransactionEngine::calcOfferBridgeNext( + const uint256& uBookRoot, // --> Which order book to look in. + const uint256& uBookEnd, // --> Limit of how far to look. + uint256& uBookDirIndex, // <-> Current directory. <-- 0 = no offer available. + uint64& uBookDirNode, // <-> Which node. 0 = first. + unsigned int& uBookDirEntry, // <-> Entry in node. 0 = first. + STAmount& saOfferIn, // <-- How much to pay in, fee inclusive, to get saOfferOut out. + STAmount& saOfferOut // <-- How much offer pays out. + ) +{ + saOfferIn = 0; // XXX currency & issuer + saOfferOut = 0; // XXX currency & issuer + + bool bDone = false; + + while (!bDone) + { + uint256 uOfferIndex; + + // Get uOfferIndex. + dirNext(uBookRoot, uBookEnd, uBookDirIndex, uBookDirNode, uBookDirEntry, uOfferIndex); + + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); + STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); + + if (sleOffer->getIFieldPresent(sfGetsIssuer)) + saOfferPays.setIssuer(sleOffer->getIValueFieldAccount(sfGetsIssuer).getAccountID()); + + if (sleOffer->getIFieldPresent(sfPaysIssuer)) + saOfferGets.setIssuer(sleOffer->getIValueFieldAccount(sfPaysIssuer).getAccountID()); + + if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcOfferFirst: encountered expired offer"; + } + else + { + STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); + // Outbound fees are paid by offer owner. + // XXX Calculate outbound fee rate. + + if (saOfferPays.isNative()) + { + // No additional fees for stamps. + + nothing(); + } + else if (saOfferPays.getIssuer() == uOfferOwnerID) + { + // Offerer is issue own IOUs. + // No fees at this exact point, XXX receiving node may charge a fee. + // XXX Make sure has a credit line with receiver, limit by credit line. + + nothing(); + // XXX Broken - could be issuing or redeeming or both. + } + else + { + // Offer must be redeeming IOUs. + + // No additional + // XXX Broken + } + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded. + Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; + } + else if (saOfferFunds >= saOfferPays) + { + // Offer fully funded. + + // Account transfering funds in to offer always pays inbound fees. + // + saOfferIn = saOfferGets; // XXX Add in fees? + + saOfferOut = saOfferPays; + + bDone = true; + } + else + { + // Offer partially funded. + + // saOfferIn/saOfferFunds = saOfferGets/saOfferPays + // XXX Round such that all saOffer funds are exhausted. + saOfferIn = (saOfferFunds*saOfferGets)/saOfferPays; // XXX Add in fees? + saOfferOut = saOfferFunds; + + bDone = true; + } + } + + if (!bDone) + { + // mUnfunded.insert(uOfferIndex); + } + } + while (bNext); +} + +// If either currency is not stamps, then also calculates vs stamp bridge. +// --> saWanted: Limit of how much is wanted out. +// <-- saPay: How much to pay into the offer. +// <-- saGot: How much to the offer pays out. Never more than saWanted. +void TransactionEngine::calcNodeOfferReverse( + const uint160& uPayCurrency, + const uint160& uPayIssuerID, + const STAmount& saWanted, // Driver + + STAmount& saPay, + STAmount& saGot + ) const +{ + TransactionEngineResult terResult = tenUNKNOWN; + + bool bDirectNext = true; // True, if need to load. + uint256 uDirectQuality; + uint256 uDirectTip = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); + uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); + + bool bBridge = true; // True, if bridging active. False, missing an offer. + uint256 uBridgeQuality; + STAmount saBridgeIn; // Amount available. + STAmount saBridgeOut; + + bool bInNext = true; // True, if need to load. + STAmount saInIn; // Amount available. Consumed in loop. Limited by offer funding. + STAmount saInOut; + uint256 uInTip; // Current entry. + uint256 uInEnd; + unsigned int uInEntry; + + bool bOutNext = true; + STAmount saOutIn; + STAmount saOutOut; + uint256 uOutTip; + uint256 uOutEnd; + unsigned int uOutEntry; + + saPay.zero(); + saPay.setCurrency(uPayCurrency); + saPay.setIssuer(uPayIssuerID); + + saNeed = saWanted; + + if (!saWanted.isNative() && !uTakerCurrency.isZero()) + { + // Bridging + uInTip = Ledger::getBookBase(uPayCurrency, uPayIssuerID, uint160(0), uint160(0)); + uInEnd = Ledger::getQualityNext(uInTip); + uOutTip = Ledger::getBookBase(uint160(0), uint160(0), saWanted.getCurrency(), saWanted.getIssuer()); + uOutEnd = Ledger::getQualityNext(uInTip); + } + + while (tenUNKNOWN == terResult) + { + if (saNeed == saWanted) + { + // Got all. + saGot = saWanted; + terResult = terSUCCESS; + } + else + { + // Calculate next tips, if needed. + + if (bDirectNext) + { + // Find next direct offer. + uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); + if (!!uDirectTip) + { + sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); + + // XXX Need to calculate the real quality: including fees. + uDirectQuality = STAmount::getQualityNext(uDirectTip); + } + + bDirectNext = false; + } + + if (bBridge && (bInNext || bOutNext)) + { + // Bridging and need to calculate next bridge rate. + // A bridge can consist of multiple offers. As offer's are consumed, the effective rate changes. + + if (bInNext) + { +// sleInDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uInIndex, uInEnd)); + // Get the next funded offer. + offerBridgeNext(uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding. + bInNext = false; + } + + if (bOutNext) + { +// sleOutDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uOutIndex, uOutEnd)); + offerNext(uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut); + bOutNext = false; + } + + if (!uInIndex || !uOutIndex) + { + bBridge = false; // No more offers to bridge. + } + else + { + // Have bridge in and out entries. + // Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost. + + saBridgeOut.zero(); + + if (saInOut < saOutIn) + { + // Limit by in. + + // XXX Need to include fees in saBridgeIn. + saBridgeIn = saInIn; // All of in + // Limit bridge out: saInOut/saBridgeOut = saOutIn/saOutOut + // Round such that we would take all of in offer, otherwise would have leftovers. + saBridgeOut = (saInOut * saOutOut) / saOutIn; + } + else if (saInOut > saOutIn) + { + // Limit by out, if at all. + + // XXX Need to include fees in saBridgeIn. + // Limit bridge in:saInIn/saInOuts = aBridgeIn/saOutIn + // Round such that would take all of out offer. + saBridgeIn = (saInIn * saOutIn) / saInOuts; + saBridgeOut = saOutOut; // All of out. + } + else + { + // Entries match, + + // XXX Need to include fees in saBridgeIn. + saBridgeIn = saInIn; // All of in + saBridgeOut = saOutOut; // All of out. + } + + uBridgeQuality = STAmount::getRate(saBridgeIn, saBridgeOut); // Inclusive of fees. + } + } + + if (bBridge) + { + bUseBridge = !uDirectTip || (uBridgeQuality < uDirectQuality) + } + else if (!!uDirectTip) + { + bUseBridge = false + } + else + { + // No more offers. Declare success, even if none returned. + saGot = saWanted-saNeed; + terResult = terSUCCESS; + } + + if (terSUCCESS != terResult) + { + STAmount& saAvailIn = bUseBridge ? saBridgeIn : saDirectIn; + STAmount& saAvailOut = bUseBridge ? saBridgeOut : saDirectOut; + + if (saAvailOut > saNeed) + { + // Consume part of offer. Done. + + saNeed = 0; + saPay += (saNeed*saAvailIn)/saAvailOut; // Round up, prefer to pay more. + } + else + { + // Consume entire offer. + + saNeed -= saAvailOut; + saPay += saAvailIn; + + if (bUseBridge) + { + // Consume bridge out. + if (saOutOut == saAvailOut) + { + // Consume all. + saOutOut = 0; + saOutIn = 0; + bOutNext = true; + } + else + { + // Consume portion of bridge out, must be consuming all of bridge in. + // saOutIn/saOutOut = saSpent/saAvailOut + // Round? + saOutIn -= (saOutIn*saAvailOut)/saOutOut; + saOutOut -= saAvailOut; + } + + // Consume bridge in. + if (saOutIn == saAvailIn) + { + // Consume all. + saInOut = 0; + saInIn = 0; + bInNext = true; + } + else + { + // Consume portion of bridge in, must be consuming all of bridge out. + // saInIn/saInOut = saAvailIn/saPay + // Round? + saInOut -= (saInOut*saAvailIn)/saInIn; + saInIn -= saAvailIn; + } + } + else + { + bDirectNext = true; + } + } + } + } + } +} + // From the destination work towards the source calculating how much must be asked for. // --> bAllowPartial: If false, fail if can't meet requirements. // <-- bSuccess: true=success, false=insufficient funds. @@ -1570,63 +1991,112 @@ TransactionEngineResult calcOfferFill(paymentNode& pnSrc, paymentNode& pnDst, bo // --> [all]saWanted.mCurrency // --> [all]saAccount // <-> [0]saWanted.mAmount : --> limit, <-- actual +// XXX Disallow looping. +// XXX With multiple path and due to offers, must consider consumed. bool calcPaymentReverse(std::vector& pnNodes, bool bAllowPartial) { - bool bDone = false; - bool bSuccess = false; + TransactionEngineResult terResult = tenUNKNOWN; - // path: dst .. src + uIndex = pnNodes.size(); - while (!bDone) + while (tenUNKNOWN == terResult && uIndex--) { - if (cur->saWanted.isZero()) + // Calculate (1) sending by fullfilling next wants and (2) setting current wants. + + paymentNode& curPN = pnNodes[uIndex]; + paymentNode& prvPN = pnNodes[uIndex-1]; + paymentNode& nxtPN = pnNodes[uIndex+1]; + + if (!(uFlags & (PF_REDEEM|PF_ISSUE))) + { + // Redeem IOUs + terResult = tenBAD_PATH; + } + else if (curPN.saWanted.isZero()) { // Must want something. - terResult = terINVALID; - bDone = true; + terResult = tenBAD_AMOUNT; } - else if (cur->saWanted.isNative()) + else if (curPN->uFlags & PF_ACCOUNT) { - if (prv->how == direct) + // Account node. + // Rippling through this accounts balances. + // No currency change. + // Issuer change possible. + + SLE::pointer sleRippleCur = ; + SLE::pointer sleRippleNxt = ; + STAmount saBalanceCur = ; + + if ((uFlags & PF_REDEEM) && saBalanceCur.isPositive()) { - // Stamp transfer desired. - if (prv->prev()) - { - // Stamp transfer can not have previous entries. Only stamp ripple can. - terResult = terINVALID; - bDone = true; - } - else if (prv->account->saBalance() >= cur->saWanted) - { - // Transfer stamps. - prv->saSend = cur->saWanted; - bDone = true; - bSuccess = true; - } - else - { - // Insufficient funds for transfer - bDone = true; - } + // Redeem IOUs + + // XXX + curPN.saWanted += ___; + + bSent = true; + } + + if ((uFlags & PF_ISSUE) // Allowed to issue. + && !saWantedNxt.isZero() // Need to issue. + && !saBalanceCur.isPositive()) // Can issue. + { + // Issue IOUs + + // XXX + curPN.saWanted += ___; + bSent = true; + } + + } + else if (curPN->uFlags & PF_OFFER) + { + // Offer node. + // Ripple or transfering from previous node through this offer to next node. + // Current node has a credit line with next node. + // Next node will receive either its own IOUs or this nodes IOUs. + // We limit what this node sends by this nodes redeem and issue max. + // This allows path lists to be strictly redeem. + // XXX Make sure offer book was not previously mentioned. + + uint160 uPrvCurrency = curPN->uFlags & PF_WANTED_CURRENCY + ? curPN->saWanted.getCurrency() + : saSendMax.getCurrency(); + + uint160 uPrvIssuer = curPN->uFlags & PF_WANTED_ISSUER + ? curPN->saWanted.getIssuer() + : saSendMax.getIssuer(); + + calcNodeOfferReverse( + uTakerCurrency, + uTakerIssuer, + nxtPN->saWanted, // Driver. + + uTakerPaid, + uTakerGot, + uOwnerPaid, + uOwnerGot, + ); + + if (uOwnerPaid.isZero()) + { + terResult = terZERO; // Path contributes nothing. } else { - // Must convert to stamps via offer. - if (calcOfferFill(prv, cur, bAllowPartial)) - { + // Update wanted. - } - else - { - bDone = false; - } + // Save sent amount } } else { - // Rippling. - + assert(false); } + + if (tenUNKNOWN == terResult == curPN.saWanted.isZero()) + terResult = terZERO; // Path contributes nothing. } } @@ -1850,6 +2320,8 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction return tenRIPPLE_EMPTY; } #if 0 + // 1) Calc payment in reverse: do not modify sles. + // 2) Calc payment forward: do modify sles. std::vector spPath; BOOST_FOREACH(std::vector& spPath, spsPaths) @@ -2023,10 +2495,11 @@ TransactionEngineResult TransactionEngine::takeOffers( // Have an offer to consider. Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); + SLE::pointer sleBookNode; + unsigned int uBookEntry; uint256 uOfferIndex; - uint64 uOfferNode; - dirFirst(uTipIndex, uOfferIndex, uOfferNode); + dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index f75e50962..c51dcdeed 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -123,21 +123,28 @@ private: const uint256& uRootIndex, const uint256& uLedgerIndex); // Item being deleted - void dirFirst(const uint256& uRootIndex, uint256& uEntryIndex, uint64& uEntryNode); + bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); + bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); #ifdef WORK_IN_PROGRESS typedef struct { - STAmount saWanted; // What this node wants from upstream. + uint16 uFlags; // --> from path - STAmount saIOURedeem; // What this node will redeem downstream. - STAmount saIOUIssue; // What this node will issue downstream. - STAmount saSend; // Amount of stamps this node will send. + STAccount saAccount; // --> recieving/sending account + + STAmount saWanted; // --> What this node wants from upstream. + + // Maybe this should just be a bool: + STAmount saIOURedeemMax; // --> Max amount of IOUs to redeem downstream. + // Maybe this should just be a bool: + STAmount saIOUIssueMax; // --> Max Amount of IOUs to issue downstream. + + STAmount saIOURedeem; // <-- What this node will redeem downstream. + STAmount saIOUIssue; // <-- What this node will issue downstream. + STAmount saSend; // <-- Stamps this node will send downstream. - STAmount saIOUForgive; // Amount of IOUs to forgive. - STAmount saIOUAccept; // Amount of IOUs to accept. STAmount saRecieve; // Amount stamps to receive. - STAccount saAccount; } paymentNode; typedef struct { diff --git a/src/TransactionFormats.cpp b/src/TransactionFormats.cpp index e2e6ac427..a1af4d189 100644 --- a/src/TransactionFormats.cpp +++ b/src/TransactionFormats.cpp @@ -27,11 +27,10 @@ TransactionFormat InnerTxnFormats[]= { "CreditSet", ttCREDIT_SET, { { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, { S_FIELD(Destination), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(LimitAmount), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(AcceptRate), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(AcceptStart), STI_UINT32, SOE_IFFLAG, 2 }, - { S_FIELD(AcceptExpire), STI_UINT32, SOE_IFFLAG, 4 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 8 }, + { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, + { S_FIELD(LimitAmount), STI_AMOUNT, SOE_IFFLAG, 2 }, + { S_FIELD(QualityIn), STI_UINT32, SOE_IFFLAG, 4 }, + { S_FIELD(QualityOut), STI_UINT32, SOE_IFFLAG, 8 }, { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } }, diff --git a/src/main.cpp b/src/main.cpp index ea77e3ef9..5fd9c2687 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,7 +56,7 @@ void printHelp(const po::options_description& desc) cout << " password_set []" << endl; cout << " peers" << endl; cout << " ripple_lines_get || []" << endl; - cout << " ripple_line_set []" << endl; + cout << " ripple_line_set [] []" << endl; cout << " send [] [] []" << endl; cout << " stop" << endl; cout << " tx " << endl;