Freeze enforcing: (RIPD-399)

* Set enforce date: September 15, 2014
 * Enforce in stand alone mode
 * Enforce at source
 * Enforce intermediary nodes
 * Enforce global freeze in get paths out
 * Enforce global freeze in create offer
 * Don't consider frozen links a path out
 * Handle in getBookPage
 * Enforce in new offer transactors
This commit is contained in:
David Schwartz
2014-05-27 12:57:49 -07:00
committed by Nik Bougalis
parent 9eb34f542c
commit 7b936de32c
17 changed files with 258 additions and 51 deletions

View File

@@ -124,7 +124,7 @@ OfferStream::step ()
// NIKB NOTE The calling code also checks the funds, how expensive is
// looking up the funds twice?
Amount const owner_funds (view().accountFunds (
m_offer.account(), m_offer.amount().out));
m_offer.account(), m_offer.amount().out, fhZERO_IF_FROZEN));
// Check for unfunded offer
if (owner_funds <= zero)
@@ -133,7 +133,7 @@ OfferStream::step ()
// we haven't modified the balance and therefore the
// offer is "found unfunded" versus "became unfunded"
if (view_cancel().accountFunds (m_offer.account(),
m_offer.amount().out) == owner_funds)
m_offer.amount().out, fhZERO_IF_FROZEN) == owner_funds)
{
view_cancel().offerDelete (entry->getIndex());
if (m_journal.trace) m_journal.trace <<

View File

@@ -78,7 +78,8 @@ Amounts
Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
{
// Limit taker's input by available funds less fees
Amount const taker_funds (view ().accountFunds (taker, amount.in));
Amount const taker_funds (view ().accountFunds (
taker, amount.in, fhZERO_IF_FROZEN));
// Get fee rate paid by taker
std::uint32_t const taker_charge_rate (view ().rippleTransferRate (
@@ -102,7 +103,7 @@ Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
// Limit owner's output by available funds less fees
Amount const owner_funds (view ().accountFunds (
offer.account (), owner_amount.out));
offer.account (), owner_amount.out, fhZERO_IF_FROZEN));
// Get fee rate paid by owner
std::uint32_t const owner_charge_rate (view ().rippleTransferRate (
@@ -210,7 +211,8 @@ Taker::done () const
}
// We are finished if the taker is out of funds
return view().accountFunds (account(), m_remain.in) <= zero;
return view().accountFunds (
account(), m_remain.in, fhZERO_IF_FROZEN) <= zero;
}
TER

View File

@@ -257,6 +257,30 @@ Ledger::~Ledger ()
}
}
bool Ledger::enforceFreeze () const
{
// Temporarily, the freze code can run in either
// enforcing mode or non-enforcing mode. In
// non-enforcing mode, freeze flags can be
// manipulated, but freezing is not actually
// enforced. Once freeze enforcing has been
// enabled, this function can be removed
// Let freeze enforcement be tested
// If you wish to test non-enforcing mode,
// you must remove this line
if (getConfig().RUN_STANDALONE)
return true;
// Freeze enforcing date is September 15, 2014
static std::uint32_t const enforceDate =
iToSeconds (boost::posix_time::ptime (
boost::gregorian::date (2014, boost::gregorian::Sep, 15)));
return mParentCloseTime >= enforceDate;
}
void Ledger::setImmutable ()
{
// Updates the hash and marks the ledger and its maps immutable

View File

@@ -182,6 +182,8 @@ public:
mAccountStateMap->setLedgerSeq (mLedgerSeq);
}
bool enforceFreeze () const;
// ledger signature operations
void addRaw (Serializer & s) const;
void setRaw (Serializer & s, bool hasPrefix);

View File

@@ -1221,7 +1221,10 @@ LedgerEntrySet::rippleQualityIn (
// negative.
// <-- IOU's account has of issuer.
STAmount LedgerEntrySet::rippleHolds (
Account const& account, Currency const& currency, Account const& issuer)
Account const& account,
Currency const& currency,
Account const& issuer,
FreezeHandling zeroIfFrozen)
{
STAmount saBalance;
SLE::pointer sleRippleState = entryCache (ltRIPPLE_STATE,
@@ -1231,6 +1234,10 @@ STAmount LedgerEntrySet::rippleHolds (
{
saBalance.clear ({currency, issuer});
}
else if ((zeroIfFrozen == fhZERO_IF_FROZEN) && isFrozen (account, currency, issuer))
{
saBalance.clear (IssueRef (currency, issuer));
}
else if (account > issuer)
{
saBalance = sleRippleState->getFieldAmount (sfBalance);
@@ -1252,7 +1259,10 @@ STAmount LedgerEntrySet::rippleHolds (
//
// <-- saAmount: amount of currency held by account. May be negative.
STAmount LedgerEntrySet::accountHolds (
Account const& account, Currency const& currency, Account const& issuer)
Account const& account,
Currency const& currency,
Account const& issuer,
FreezeHandling zeroIfFrozen)
{
STAmount saAmount;
@@ -1282,7 +1292,7 @@ STAmount LedgerEntrySet::accountHolds (
}
else
{
saAmount = rippleHolds (account, currency, issuer);
saAmount = rippleHolds (account, currency, issuer, zeroIfFrozen);
WriteLog (lsTRACE, LedgerEntrySet) << "accountHolds:" <<
" account=" << to_string (account) <<
@@ -1292,6 +1302,46 @@ STAmount LedgerEntrySet::accountHolds (
return saAmount;
}
bool LedgerEntrySet::isGlobalFrozen (const Account& issuer)
{
if (!enforceFreeze () || isXRP (issuer))
return false;
SLE::pointer sle = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (issuer));
if (sle && sle->isFlag (lsfGlobalFreeze))
return true;
return false;
}
// Can the specified account spend the specified currency issued by
// the specified issuer or does the freeze flag prohibit it?
bool LedgerEntrySet::isFrozen(
Account const& account,
Currency const& currency,
Account const& issuer)
{
if (!enforceFreeze () || isXRP (currency))
return false;
SLE::pointer sle = entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (issuer));
if (sle && sle->isFlag (lsfGlobalFreeze))
return true;
if (issuer != account)
{
// Check if the issuer froze the line
sle = entryCache (ltRIPPLE_STATE,
Ledger::getRippleStateIndex (account, issuer, currency));
if (sle && sle->isFlag ((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
{
return true;
}
}
return false;
}
// Returns the funds available for account for a currency/issuer.
// Use when you need a default for rippling account's currency.
// XXX Should take into account quality?
@@ -1301,7 +1351,7 @@ STAmount LedgerEntrySet::accountHolds (
// If the issuer is the same as account, funds are unlimited, use result is
// saDefault.
STAmount LedgerEntrySet::accountFunds (
Account const& account, const STAmount& saDefault)
Account const& account, const STAmount& saDefault, FreezeHandling zeroIfFrozen)
{
STAmount saFunds;
@@ -1317,7 +1367,8 @@ STAmount LedgerEntrySet::accountFunds (
else
{
saFunds = accountHolds (
account, saDefault.getCurrency (), saDefault.getIssuer ());
account, saDefault.getCurrency (), saDefault.getIssuer (),
zeroIfFrozen);
WriteLog (lsTRACE, LedgerEntrySet) << "accountFunds:" <<
" account=" << to_string (account) <<

View File

@@ -51,6 +51,12 @@ enum LedgerEntryAction
taaCREATE, // Newly created.
};
enum FreezeHandling
{
fhIGNORE_FREEZE,
fhZERO_IF_FROZEN
};
class LedgerEntrySetEntry
: public CountedObject <LedgerEntrySetEntry>
{
@@ -145,6 +151,11 @@ public:
return mLedger;
}
bool enforceFreeze () const
{
return mLedger->enforceFreeze ();
}
// basic entry functions
SLE::pointer getEntry (uint256 const & index, LedgerEntryAction&);
LedgerEntryAction hasEntry (uint256 const & index) const;
@@ -221,6 +232,13 @@ public:
sfLowQualityOut, sfHighQualityOut);
}
bool isFrozen (
Account const& account,
Currency const& currency,
Account const& issuer);
bool isGlobalFrozen (const Account & issuer);
STAmount rippleTransferFee (
Account const& uSenderID, Account const& uReceiverID,
Account const& issuer, const STAmount & saAmount);
@@ -231,9 +249,9 @@ public:
STAmount accountHolds (
Account const& account, Currency const& currency,
Account const& issuer);
Account const& issuer, FreezeHandling freezeHandling);
STAmount accountFunds (
Account const& account, const STAmount & saDefault);
Account const& account, const STAmount & saDefault, FreezeHandling freezeHandling);
TER accountSend (
Account const& uSenderID, Account const& uReceiverID,
const STAmount & saAmount);
@@ -326,7 +344,7 @@ private:
STAmount rippleHolds (
Account const& account, Currency const& currency,
Account const& issuer);
Account const& issuer, FreezeHandling zeroIfFrozen);
};
} // ripple

View File

@@ -3056,6 +3056,9 @@ void NetworkOPsImp::getBookPage (
LedgerEntrySet lesActive (lpLedger, tapNONE, true);
const bool bGlobalFreeze = lesActive.isGlobalFrozen (book.out.account) ||
lesActive.isGlobalFrozen (book.in.account);
bool bDone = false;
bool bDirectAdvance = true;
@@ -3120,6 +3123,12 @@ void NetworkOPsImp::getBookPage (
// funded.
saOwnerFunds = saTakerGets;
}
else if (bGlobalFreeze)
{
// If either asset is globally frozen, consider all offers
// that aren't ours to be totally unfunded
saOwnerFunds.clear (IssueRef (book.out.currency, book.out.account));
}
else
{
auto umBalanceEntry = umBalance.find (uOfferOwnerID);
@@ -3135,7 +3144,7 @@ void NetworkOPsImp::getBookPage (
saOwnerFunds = lesActive.accountHolds (
uOfferOwnerID, book.out.currency,
book.out.account);
book.out.account, fhZERO_IF_FROZEN);
if (saOwnerFunds < zero)
{
@@ -3259,6 +3268,10 @@ void NetworkOPsImp::getBookPage (
auto uTransferRate = lesActive.rippleTransferRate (book.out.account);
const bool bGlobalFreeze = lesActive.isGlobalFrozen (book.out.account) ||
lesActive.isGlobalFrozen (book.in.account);
while (iLeft-- > 0 && obIterator.nextOffer ())
{
@@ -3276,6 +3289,12 @@ void NetworkOPsImp::getBookPage (
// If offer is selling issuer's own IOUs, it is fully funded.
saOwnerFunds = saTakerGets;
}
else if (bGlobalFreeze)
{
// If either asset is globally frozen, consider all offers
// that aren't ours to be totally unfunded
saOwnerFunds.clear (IssueRef (book.out.currency, book.out.account));
}
else
{
auto umBalanceEntry = umBalance.find (uOfferOwnerID);
@@ -3291,7 +3310,7 @@ void NetworkOPsImp::getBookPage (
// Did not find balance in table.
saOwnerFunds = lesActive.accountHolds (
uOfferOwnerID, book.out.currency, book.out.account);
uOfferOwnerID, book.out.currency, book.out.account, fhZERO_IF_FROZEN);
if (saOwnerFunds.isNegative ())
{

View File

@@ -602,6 +602,67 @@ TER PathState::expandPath (
return terStatus;
}
/** Check if an expanded path violates freeze rules */
void PathState::checkFreeze()
{
assert (nodes_.size() >= 2);
// A path with no intermediaries -- pure issue/redeem
// cannot be frozen.
if (nodes_.size() == 2)
return;
SLE::pointer sle;
for (std::size_t i = 0; i < (nodes_.size() - 1); ++i)
{
// Check each order book for a global freeze
if (nodes_[i].uFlags & STPathElement::typeIssuer)
{
sle = lesEntries.entryCache (ltACCOUNT_ROOT,
Ledger::getAccountRootIndex (nodes_[i].issue_.account));
if (sle && sle->isFlag (lsfGlobalFreeze))
{
terStatus = terNO_LINE;
return;
}
}
// Check each account change to make sure funds can leave
if (nodes_[i].uFlags & STPathElement::typeAccount)
{
Currency const& currencyID = nodes_[i].issue_.currency;
Account const& inAccount = nodes_[i].account_;
Account const& outAccount = nodes_[i+1].account_;
if (inAccount != outAccount)
{
sle = lesEntries.entryCache (ltACCOUNT_ROOT,
Ledger::getAccountRootIndex (outAccount));
if (sle && sle->isFlag (lsfGlobalFreeze))
{
terStatus = terNO_LINE;
return;
}
sle = lesEntries.entryCache (ltRIPPLE_STATE,
Ledger::getRippleStateIndex (inAccount,
outAccount, currencyID));
if (sle && sle->isFlag (
(outAccount > inAccount) ? lsfHighFreeze : lsfLowFreeze))
{
terStatus = terNO_LINE;
return;
}
}
}
}
}
/** Check if a sequence of three accounts violates the no ripple constrains
[first] -> [second] -> [third]
Disallowed if 'second' set no ripple on [first]->[second] and
@@ -716,8 +777,8 @@ TER PathState::checkNoRipple (
}
// Loop through all nodes that have a prior node and successor nodes
// These are the nodes whose no ripple constratints could be violated
for (auto i = 1; i < nodes_.size() - 1; ++i)
// These are the nodes whose no ripple constraints could be violated
for (int i = 1; i < nodes_.size() - 1; ++i)
{
if (nodes_[i - 1].isAccount() &&
nodes_[i].isAccount() &&

View File

@@ -97,6 +97,8 @@ class PathState : public CountedObject <PathState>
TER checkNoRipple (Account const& destinationAccountID,
Account const& sourceAccountID);
void checkFreeze ();
static bool lessPriority (PathState& lhs, PathState& rhs);
LedgerEntrySet& ledgerEntries() { return lesEntries; }

View File

@@ -507,31 +507,38 @@ int Pathfinder::getPathsOut (
int aFlags = sleAccount->getFieldU32(sfFlags);
bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0;
bool const bFrozen = (aFlags & lsfGlobalFreeze) != 0;
int count = 0;
AccountItems& rippleLines (mRLCache->getRippleLines (accountID));
for (auto const& item : rippleLines.getItems ())
if (!bFrozen)
{
RippleState* rspEntry = (RippleState*) item.get ();
for (auto const& item : mRLCache->getRippleLines (accountID).getItems ())
{
RippleState* rspEntry = (RippleState*) item.get ();
if (currencyID != rspEntry->getLimit ().getCurrency ())
{
if (currencyID != rspEntry->getLimit ().getCurrency ())
{
}
else if (rspEntry->getBalance () <= zero &&
(!rspEntry->getLimitPeer ()
|| -rspEntry->getBalance () >= rspEntry->getLimitPeer ()
|| (bAuthRequired && !rspEntry->getAuth ())))
{
}
else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ()))
count += 10000; // count a path to the destination extra
else if (rspEntry->getNoRipplePeer ())
{
// This probably isn't a useful path out
}
else if (rspEntry->getFreezePeer () && mLedger->enforceFreeze ())
{
// Not a useful path out
}
else
++count;
}
else if (rspEntry->getBalance () <= zero &&
(!rspEntry->getLimitPeer ()
|| -rspEntry->getBalance () >= rspEntry->getLimitPeer ()
|| (bAuthRequired && !rspEntry->getAuth ())))
{
}
else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ()))
count += 10000; // count a path to the destination extra
else if (rspEntry->getNoRipplePeer ())
{
// This probably isn't a useful path out
}
else
++count;
}
mPOMap[currencyAccount] = count;
return count;

View File

@@ -29,7 +29,10 @@ bool RippleCalc::addPathState(STPath const& path, TER& resultCode)
saDstAmountReq_, saMaxAmountReq_);
if (!pathState)
{
resultCode = temUNKNOWN;
return false;
}
pathState->expandPath (
mActiveLedger,
@@ -37,8 +40,11 @@ bool RippleCalc::addPathState(STPath const& path, TER& resultCode)
uDstAccountID_,
uSrcAccountID_);
if (tesSUCCESS == pathState->status())
pathState->checkNoRipple (uDstAccountID_, uSrcAccountID_);
if (pathState->status() == tesSUCCESS)
pathState->checkNoRipple (uDstAccountID_, uSrcAccountID_);
if (pathState->status() == tesSUCCESS && mActiveLedger.enforceFreeze ())
pathState->checkFreeze ();
pathState->setIndex (pathStateList_.size ());
@@ -47,17 +53,18 @@ bool RippleCalc::addPathState(STPath const& path, TER& resultCode)
<< " status: " << transToken (pathState->status());
// Return if malformed.
if (isTemMalformed (pathState->status())) {
if (isTemMalformed (pathState->status()))
{
resultCode = pathState->status();
return false;
}
if (tesSUCCESS == pathState->status())
if (pathState->status () == tesSUCCESS)
{
resultCode = tesSUCCESS;
resultCode = pathState->status();
pathStateList_.push_back (pathState);
}
else if (terNO_LINE != pathState->status())
else if (pathState->status () != terNO_LINE)
{
resultCode = pathState->status();
}
@@ -100,7 +107,6 @@ TER RippleCalc::rippleCalculate ()
// nodes.
// XXX Might also make a XRP bridge by default.
WriteLog (lsTRACE, RippleCalc)
<< "rippleCalc: Paths in set: " << spsPaths_.size ();

View File

@@ -124,7 +124,8 @@ TER PathCursor::advanceNode (bool const bReverse) const
// Funds left.
node().saOfferFunds = ledger().accountFunds (
node().offerOwnerAccount_,
node().saTakerGets);
node().saTakerGets,
fhZERO_IF_FROZEN);
node().bFundsDirty = false;
WriteLog (lsTRACE, RippleCalc)
@@ -321,7 +322,9 @@ TER PathCursor::advanceNode (bool const bReverse) const
// Only the current node is allowed to use the source.
node().saOfferFunds = ledger().accountFunds (
node().offerOwnerAccount_, node().saTakerGets);
node().offerOwnerAccount_,
node().saTakerGets,
fhZERO_IF_FROZEN);
// Funds held.
if (node().saOfferFunds <= zero)

View File

@@ -352,7 +352,8 @@ TER PathCursor::forwardLiquidityForAccount () const
ledger().accountHolds (
node().account_,
xrpCurrency(),
xrpAccount()));
xrpAccount(),
fhIGNORE_FREEZE)); // XRP can't be frozen
}

View File

@@ -224,7 +224,15 @@ CreateOffer::doApply ()
terResult = temBAD_ISSUER;
}
else if (view.accountFunds (mTxnAccountID, saTakerGets) <= zero)
else if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID))
{
m_journal.warning <<
"Offer involves frozen asset";
terResult = tecFROZEN;
}
else if (view.accountFunds (
mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero)
{
m_journal.warning <<
"delay: Offers must be at least partially funded.";
@@ -349,8 +357,8 @@ CreateOffer::doApply ()
"takeOffers: mTxnAccountID=" <<
to_string (mTxnAccountID);
m_journal.debug <<
"takeOffers: FUNDS=" <<
view.accountFunds (mTxnAccountID, saTakerGets).getFullText ();
"takeOffers: FUNDS=" << view.accountFunds (
mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN).getFullText ();
}
if (saTakerPays < zero || saTakerGets < zero)

View File

@@ -61,8 +61,9 @@ CreateOfferBridged::crossOffers (
(options.passive? "passive" : "") << std::endl <<
" taker: " << taker.account() << std::endl <<
" balances: " <<
view.accountFunds (taker.account(), taker_amount.in) << ", " <<
view.accountFunds (taker.account(), taker_amount.out);
view.accountFunds (taker.account(), taker_amount.in, fhIGNORE_FREEZE)
<< ", " <<
view.accountFunds (taker.account(), taker_amount.out, fhIGNORE_FREEZE);
TER cross_result (tesSUCCESS);

View File

@@ -51,6 +51,7 @@ bool transResultInfo (TER terCode, std::string& strToken, std::string& strHuman)
{ tecNO_AUTH, "tecNO_AUTH", "Not authorized to hold asset." },
{ tecNO_LINE, "tecNO_LINE", "No such line." },
{ tecINSUFF_FEE, "tecINSUFF_FEE", "Insufficient balance to pay fee." },
{ tecFROZEN, "tecFROZEN", "Asset is frozen." },
{ tefALREADY, "tefALREADY", "The exact transaction was already in this ledger." },
{ tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },

View File

@@ -184,6 +184,7 @@ enum TER // aka TransactionEngineResult
tecNO_AUTH = 134,
tecNO_LINE = 135,
tecINSUFF_FEE = 136,
tecFROZEN = 137,
};
inline bool isTelLocal(TER x)