Add ripple quality in and out.

This commit is contained in:
Arthur Britto
2012-07-24 15:02:09 -07:00
parent 276f6f4da1
commit aababa680f
10 changed files with 642 additions and 115 deletions

View File

@@ -64,8 +64,10 @@ LedgerEntryFormat LedgerFormats[]=
{ S_FIELD(LowLimit), STI_AMOUNT, SOE_REQUIRED, 0 }, { S_FIELD(LowLimit), STI_AMOUNT, SOE_REQUIRED, 0 },
{ S_FIELD(HighID), STI_ACCOUNT, SOE_REQUIRED, 0 }, { S_FIELD(HighID), STI_ACCOUNT, SOE_REQUIRED, 0 },
{ S_FIELD(HighLimit), STI_AMOUNT, SOE_REQUIRED, 0 }, { S_FIELD(HighLimit), STI_AMOUNT, SOE_REQUIRED, 0 },
{ S_FIELD(QualityIn), STI_UINT32, SOE_IFFLAG, 1 }, { S_FIELD(LowQualityIn), STI_UINT32, SOE_IFFLAG, 1 },
{ S_FIELD(QualityOut), STI_UINT32, SOE_IFFLAG, 2 }, { 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 }, { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 },
{ sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } }
}, },

View File

@@ -1125,7 +1125,7 @@ Json::Value RPCServer::doPeers(const Json::Value& params)
return obj; return obj;
} }
// ripple_line_set <seed> <paying_account> <destination_account> <limit_amount> [<currency>] [<accept_rate>] // ripple_line_set <seed> <paying_account> <destination_account> <limit_amount> [<currency>] [<quality_in>] [<quality_out>]
Json::Value RPCServer::doRippleLineSet(const Json::Value& params) Json::Value RPCServer::doRippleLineSet(const Json::Value& params)
{ {
NewcoinAddress naSeed; NewcoinAddress naSeed;
@@ -1133,7 +1133,11 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params)
NewcoinAddress naDstAccountID; NewcoinAddress naDstAccountID;
STAmount saLimitAmount; STAmount saLimitAmount;
uint256 uLedger = mNetOps->getCurrentLedger(); uint256 uLedger = mNetOps->getCurrentLedger();
uint32 uAcceptRate = params.size() >= 6 ? lexical_cast_s<uint32>(params[5u].asString()) : 0; bool bLimitAmount = true;
bool bQualityIn = params.size() >= 6;
bool bQualityOut = params.size() >= 7;
uint32 uQualityIn = bQualityIn ? lexical_cast_s<uint32>(params[5u].asString()) : 0;
uint32 uQualityOut = bQualityOut ? lexical_cast_s<uint32>(params[6u].asString()) : 0;
if (!naSeed.setSeedGeneric(params[0u].asString())) if (!naSeed.setSeedGeneric(params[0u].asString()))
{ {
@@ -1171,8 +1175,9 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params)
theConfig.FEE_DEFAULT, theConfig.FEE_DEFAULT,
0, // YYY No source tag 0, // YYY No source tag
naDstAccountID, naDstAccountID,
saLimitAmount, bLimitAmount, saLimitAmount,
uAcceptRate); bQualityIn, uQualityIn,
bQualityOut, uQualityOut);
trans = mNetOps->submitTransaction(trans); trans = mNetOps->submitTransaction(trans);
@@ -1181,8 +1186,6 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params)
obj["seed"] = naSeed.humanSeed(); obj["seed"] = naSeed.humanSeed();
obj["srcAccountID"] = naSrcAccountID.humanAccountID(); obj["srcAccountID"] = naSrcAccountID.humanAccountID();
obj["dstAccountID"] = naDstAccountID.humanAccountID(); obj["dstAccountID"] = naDstAccountID.humanAccountID();
obj["limitAmount"] = saLimitAmount.getText();
obj["acceptRate"] = uAcceptRate;
return obj; 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 }, { "password_set", &RPCServer::doPasswordSet, 2, 3, false, optNetwork },
{ "peers", &RPCServer::doPeers, 0, 0, true }, { "peers", &RPCServer::doPeers, 0, 0, true },
{ "ripple_lines_get", &RPCServer::doRippleLinesGet, 1, 2, false, optCurrent|optClosed }, { "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 }, { "send", &RPCServer::doSend, 3, 7, false, optCurrent },
{ "server_info", &RPCServer::doServerInfo, 0, 0, true }, { "server_info", &RPCServer::doServerInfo, 0, 0, true },
{ "stop", &RPCServer::doStop, 0, 0, true }, { "stop", &RPCServer::doStop, 0, 0, true },

View File

@@ -55,6 +55,8 @@ enum SOE_Field
sfHash, sfHash,
sfHighID, sfHighID,
sfHighLimit, sfHighLimit,
sfHighQualityIn,
sfHighQualityOut,
sfIdentifier, sfIdentifier,
sfIndexes, sfIndexes,
sfIndexNext, sfIndexNext,
@@ -67,6 +69,8 @@ enum SOE_Field
sfLimitAmount, sfLimitAmount,
sfLowID, sfLowID,
sfLowLimit, sfLowLimit,
sfLowQualityIn,
sfLowQualityOut,
sfMessageKey, sfMessageKey,
sfMinimumOffer, sfMinimumOffer,
sfNextAcceptExpire, sfNextAcceptExpire,

View File

@@ -36,6 +36,20 @@ enum SerializedTypeID
STI_LEDGERENTRY = 102 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 class SerializedType
{ {
protected: protected:

View File

@@ -232,13 +232,23 @@ Transaction::pointer Transaction::sharedCreate(
Transaction::pointer Transaction::setCreditSet( Transaction::pointer Transaction::setCreditSet(
const NewcoinAddress& naPrivateKey, const NewcoinAddress& naPrivateKey,
const NewcoinAddress& naDstAccountID, const NewcoinAddress& naDstAccountID,
bool bLimitAmount,
const STAmount& saLimitAmount, const STAmount& saLimitAmount,
uint32 uAcceptRate) bool bQualityIn,
uint32 uQualityIn,
bool bQualityOut,
uint32 uQualityOut)
{ {
mTransaction->setITFieldAccount(sfDestination, naDstAccountID); mTransaction->setITFieldAccount(sfDestination, naDstAccountID);
if (bLimitAmount)
mTransaction->setITFieldAmount(sfLimitAmount, saLimitAmount); mTransaction->setITFieldAmount(sfLimitAmount, saLimitAmount);
if (uAcceptRate)
mTransaction->setITFieldU32(sfAcceptRate, uAcceptRate); if (bQualityIn)
mTransaction->setITFieldU32(sfAcceptRate, uQualityIn);
if (bQualityOut)
mTransaction->setITFieldU32(sfAcceptRate, uQualityOut);
sign(naPrivateKey); sign(naPrivateKey);
@@ -252,12 +262,19 @@ Transaction::pointer Transaction::sharedCreditSet(
const STAmount& saFee, const STAmount& saFee,
uint32 uSourceTag, uint32 uSourceTag,
const NewcoinAddress& naDstAccountID, const NewcoinAddress& naDstAccountID,
bool bLimitAmount,
const STAmount& saLimitAmount, const STAmount& saLimitAmount,
uint32 uAcceptRate) bool bQualityIn,
uint32 uQualityIn,
bool bQualityOut,
uint32 uQualityOut)
{ {
pointer tResult = boost::make_shared<Transaction>(ttCREDIT_SET, naPublicKey, naSourceAccount, uSeq, saFee, uSourceTag); pointer tResult = boost::make_shared<Transaction>(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);
} }
// //

View File

@@ -68,8 +68,12 @@ private:
Transaction::pointer setCreditSet( Transaction::pointer setCreditSet(
const NewcoinAddress& naPrivateKey, const NewcoinAddress& naPrivateKey,
const NewcoinAddress& naDstAccountID, const NewcoinAddress& naDstAccountID,
bool bLimitAmount,
const STAmount& saLimitAmount, const STAmount& saLimitAmount,
uint32 uAcceptRate); bool bQualityIn,
uint32 uQualityIn,
bool bQualityOut,
uint32 uQualityOut);
Transaction::pointer setNicknameSet( Transaction::pointer setNicknameSet(
const NewcoinAddress& naPrivateKey, const NewcoinAddress& naPrivateKey,
@@ -166,8 +170,12 @@ public:
const STAmount& saFee, const STAmount& saFee,
uint32 uSourceTag, uint32 uSourceTag,
const NewcoinAddress& naDstAccountID, const NewcoinAddress& naDstAccountID,
bool bLimitAmount,
const STAmount& saLimitAmount, const STAmount& saLimitAmount,
uint32 uAcceptRate); bool bQualityIn,
uint32 uQualityIn,
bool bQualityOut,
uint32 uQualityOut);
// Set Nickname // Set Nickname
static Transaction::pointer sharedNicknameSet( static Transaction::pointer sharedNicknameSet(

View File

@@ -111,6 +111,7 @@ STAmount TransactionEngine::rippleHolds(const uint160& uAccountID, const uint160
return saBalance; 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 TransactionEngine::accountHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID)
{ {
STAmount saAmount; STAmount saAmount;
@@ -138,7 +139,11 @@ STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint16
return saAmount; 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 // --> 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 TransactionEngine::accountFunds(const uint160& uAccountID, const STAmount& saDefault)
{ {
STAmount saFunds; STAmount saFunds;
@@ -582,30 +587,55 @@ TransactionEngineResult TransactionEngine::dirDelete(
return terSUCCESS; return terSUCCESS;
} }
// --> uRootIndex // <-- true, if had a next entry.
// <-- uEntryIndex bool TransactionEngine::dirFirst(
// <-- uEntryNode const uint256& uRootIndex, // --> Root of directory.
void TransactionEngine::dirFirst(const uint256& uRootIndex, uint256& uEntryIndex, uint64& uEntryNode) 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<uint256>& vuiIndexes = svIndexes.peekValue(); std::vector<uint256>& 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)); if (!uNodeNext)
uEntryIndex = sleNext->getIFieldV256(sfIndexes).peekValue()[0]; {
uEntryIndex.zero();
return false;
} }
else else
{ {
uEntryIndex = vuiIndexes[0]; sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext));
uEntryNode = 0; 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. // Set the authorized public key for an account. May also set the generator map.
TransactionEngineResult TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) TransactionEngineResult TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator)
{ {
@@ -1329,10 +1359,15 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti
return terNO_DST; return terNO_DST;
} }
STAmount saLimitAmount = txn.getITFieldAmount(sfLimitAmount);
uint160 uCurrency = saLimitAmount.getCurrency();
bool bFlipped = mTxnAccountID > uDstAccountID; bool bFlipped = mTxnAccountID > uDstAccountID;
uint32 uFlags = bFlipped ? lsfLowIndexed : lsfHighIndexed; 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); STAmount saBalance(uCurrency);
bool bAddIndex = false; bool bAddIndex = false;
bool bDelIndex = false; bool bDelIndex = false;
@@ -1364,12 +1399,40 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti
} }
} }
#endif #endif
if (!bDelIndex) 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) if (bAddIndex)
sleRippleState->setFlag(uFlags); sleRippleState->setFlag(uFlags);
@@ -1401,6 +1464,10 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti
sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, saZero); sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, saZero);
sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID); sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID);
sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID); sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID);
if (uQualityIn)
sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn);
if (uQualityOut)
sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut);
} }
if (bAddIndex) if (bAddIndex)
@@ -1517,19 +1584,9 @@ TransactionEngineResult TransactionEngine::doPasswordSet(const SerializedTransac
} }
#ifdef WORK_IN_PROGRESS #ifdef WORK_IN_PROGRESS
TransactionEngineResult calcOfferFill(SAAmount& saSrc, paymentNode& pnSrc, paymentNode& pnDst) // XXX Need to adjust for fees.
{
TransactionEngineResult terResult;
if (!saSrc.isZero())
{
}
return bSuccess;
}
// Find offers to satisfy pnDst. // 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 // --> pnDst.saWanted: currency and amount wanted
// --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use. // --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use.
// --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, 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; TransactionEngineResult terResult;
terResult = calcOfferFill(pnSrc.saIOURedeem, pnSrc, pnDst, bAllowPartial); if (pnDst.saWanted.isNative())
{
// Transfer stamps.
STAmount saSrcFunds = pnSrc.saAccount->accountHolds(pnSrc.saAccount, uint160(0), uint160(0));
if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted))
{
pnSrc.saSend = min(saSrcFunds, pnDst.saWanted);
pnDst.saReceive = pnSrc.saSend;
}
else
{
terResult = terINSUF_PATH;
}
}
else
{
// Ripple funds.
{
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) if (terSUCCESS == terResult)
{ {
terResult = calcOfferFill(pnSrc.saIOUIssue, pnSrc, pnDst, bAllowPartial) // 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) if (terSUCCESS == terResult && !bAllowPartial)
{ {
STAmount saTotal = pnSrc.saIOURedeem; STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept;
saTotal += pnSrc.saIOUIssue;
if (saTotal != saWanted) if (saTotal != saWanted)
terResult = terINSUF_PATH; terResult = terINSUF_PATH;
} }
}
return terResult; 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. // From the destination work towards the source calculating how much must be asked for.
// --> bAllowPartial: If false, fail if can't meet requirements. // --> bAllowPartial: If false, fail if can't meet requirements.
// <-- bSuccess: true=success, false=insufficient funds. // <-- bSuccess: true=success, false=insufficient funds.
@@ -1570,63 +1991,112 @@ TransactionEngineResult calcOfferFill(paymentNode& pnSrc, paymentNode& pnDst, bo
// --> [all]saWanted.mCurrency // --> [all]saWanted.mCurrency
// --> [all]saAccount // --> [all]saAccount
// <-> [0]saWanted.mAmount : --> limit, <-- actual // <-> [0]saWanted.mAmount : --> limit, <-- actual
// XXX Disallow looping.
// XXX With multiple path and due to offers, must consider consumed.
bool calcPaymentReverse(std::vector<paymentNode>& pnNodes, bool bAllowPartial) bool calcPaymentReverse(std::vector<paymentNode>& pnNodes, bool bAllowPartial)
{ {
bool bDone = false; TransactionEngineResult terResult = tenUNKNOWN;
bool bSuccess = false;
// 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. // Must want something.
terResult = terINVALID; terResult = tenBAD_AMOUNT;
bDone = true;
} }
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. // Redeem IOUs
if (prv->prev())
{ // XXX
// Stamp transfer can not have previous entries. Only stamp ripple can. curPN.saWanted += ___;
terResult = terINVALID;
bDone = true; bSent = true;
} }
else if (prv->account->saBalance() >= cur->saWanted)
if ((uFlags & PF_ISSUE) // Allowed to issue.
&& !saWantedNxt.isZero() // Need to issue.
&& !saBalanceCur.isPositive()) // Can issue.
{ {
// Transfer stamps. // Issue IOUs
prv->saSend = cur->saWanted;
bDone = true; // XXX
bSuccess = true; curPN.saWanted += ___;
bSent = true;
} }
else
{
// Insufficient funds for transfer
bDone = true;
}
}
else
{
// Must convert to stamps via offer.
if (calcOfferFill(prv, cur, bAllowPartial))
{
} }
else else if (curPN->uFlags & PF_OFFER)
{ {
bDone = false; // 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.
else // We limit what this node sends by this nodes redeem and issue max.
{ // This allows path lists to be strictly redeem.
// Rippling. // 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
{
// Update wanted.
// Save sent amount
}
}
else
{
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; return tenRIPPLE_EMPTY;
} }
#if 0 #if 0
// 1) Calc payment in reverse: do not modify sles.
// 2) Calc payment forward: do modify sles.
std::vector<STPath> spPath; std::vector<STPath> spPath;
BOOST_FOREACH(std::vector<STPath>& spPath, spsPaths) BOOST_FOREACH(std::vector<STPath>& spPath, spsPaths)
@@ -2023,10 +2495,11 @@ TransactionEngineResult TransactionEngine::takeOffers(
// Have an offer to consider. // Have an offer to consider.
Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0);
SLE::pointer sleBookNode;
unsigned int uBookEntry;
uint256 uOfferIndex; uint256 uOfferIndex;
uint64 uOfferNode;
dirFirst(uTipIndex, uOfferIndex, uOfferNode); dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex);
SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex);

View File

@@ -123,21 +123,28 @@ private:
const uint256& uRootIndex, const uint256& uRootIndex,
const uint256& uLedgerIndex); // Item being deleted 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 #ifdef WORK_IN_PROGRESS
typedef struct { typedef struct {
STAmount saWanted; // What this node wants from upstream. uint16 uFlags; // --> from path
STAmount saIOURedeem; // What this node will redeem downstream. STAccount saAccount; // --> recieving/sending account
STAmount saIOUIssue; // What this node will issue downstream.
STAmount saSend; // Amount of stamps this node will send. 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. STAmount saRecieve; // Amount stamps to receive.
STAccount saAccount;
} paymentNode; } paymentNode;
typedef struct { typedef struct {

View File

@@ -27,11 +27,10 @@ TransactionFormat InnerTxnFormats[]=
{ "CreditSet", ttCREDIT_SET, { { "CreditSet", ttCREDIT_SET, {
{ S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 },
{ S_FIELD(Destination), STI_ACCOUNT, SOE_REQUIRED, 0 }, { S_FIELD(Destination), STI_ACCOUNT, SOE_REQUIRED, 0 },
{ S_FIELD(LimitAmount), STI_AMOUNT, SOE_REQUIRED, 0 }, { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 },
{ S_FIELD(AcceptRate), STI_UINT32, SOE_IFFLAG, 1 }, { S_FIELD(LimitAmount), STI_AMOUNT, SOE_IFFLAG, 2 },
{ S_FIELD(AcceptStart), STI_UINT32, SOE_IFFLAG, 2 }, { S_FIELD(QualityIn), STI_UINT32, SOE_IFFLAG, 4 },
{ S_FIELD(AcceptExpire), STI_UINT32, SOE_IFFLAG, 4 }, { S_FIELD(QualityOut), STI_UINT32, SOE_IFFLAG, 8 },
{ S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 8 },
{ S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 },
{ sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } }
}, },

View File

@@ -56,7 +56,7 @@ void printHelp(const po::options_description& desc)
cout << " password_set <master_seed> <regular_seed> [<account>]" << endl; cout << " password_set <master_seed> <regular_seed> [<account>]" << endl;
cout << " peers" << endl; cout << " peers" << endl;
cout << " ripple_lines_get <account>|<nickname>|<account_public_key> [<index>]" << endl; cout << " ripple_lines_get <account>|<nickname>|<account_public_key> [<index>]" << endl;
cout << " ripple_line_set <seed> <paying_account> <destination_account> <limit_amount> <currency> [<account_rate>]" << endl; cout << " ripple_line_set <seed> <paying_account> <destination_account> <limit_amount> <currency> [<quality_in>] [<quality_out>]" << endl;
cout << " send <seed> <paying_account> <account_id> <amount> [<currency>] [<send_max>] [<send_currency>]" << endl; cout << " send <seed> <paying_account> <account_id> <amount> [<currency>] [<send_max>] [<send_currency>]" << endl;
cout << " stop" << endl; cout << " stop" << endl;
cout << " tx <id>" << endl; cout << " tx <id>" << endl;