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.isNegative() || !saPaid.isNative()) return temBAD_FEE; 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; }
// 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 temBAD_SRC_ACCOUNT; } // 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 ChangeTransactor::preCheck () { mTxnAccountID = mTxn.getSourceAccount ().getAccountID (); if (mTxnAccountID.isNonZero ()) { WriteLog (lsWARNING, ChangeTransactor) << "applyTransaction: bad source id"; return temBAD_SRC_ACCOUNT; } if (isSetBit (mParams, tapOPEN_LEDGER)) { WriteLog (lsWARNING, ChangeTransactor) << "Change transaction against open ledger"; return temINVALID; } return tesSUCCESS; }
// This is for debugging not end users. Output names can be changed without warning. Json::Value PathState::Node::getJson () const { Json::Value jvNode (Json::objectValue); Json::Value jvFlags (Json::arrayValue); jvNode["type"] = uFlags; if (isSetBit (uFlags, STPathElement::typeAccount) || !!uAccountID) jvFlags.append (!!isSetBit (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account"); if (isSetBit (uFlags, STPathElement::typeCurrency) || !!uCurrencyID) jvFlags.append (!!isSetBit (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency"); if (isSetBit (uFlags, STPathElement::typeIssuer) || !!uIssuerID) jvFlags.append (!!isSetBit (uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer"); jvNode["flags"] = jvFlags; if (!!uAccountID) jvNode["account"] = RippleAddress::createHumanAccountID (uAccountID); if (!!uCurrencyID) jvNode["currency"] = STAmount::createHumanCurrency (uCurrencyID); if (!!uIssuerID) jvNode["issuer"] = RippleAddress::createHumanAccountID (uIssuerID); if (saRevRedeem) jvNode["rev_redeem"] = saRevRedeem.getFullText (); if (saRevIssue) jvNode["rev_issue"] = saRevIssue.getFullText (); if (saRevDeliver) jvNode["rev_deliver"] = saRevDeliver.getFullText (); if (saFwdRedeem) jvNode["fwd_redeem"] = saFwdRedeem.getFullText (); if (saFwdIssue) jvNode["fwd_issue"] = saFwdIssue.getFullText (); if (saFwdDeliver) jvNode["fwd_deliver"] = saFwdDeliver.getFullText (); return jvNode; }
TER AccountSetTransactor::doApply () { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet>"; const uint32 uTxFlags = mTxn.getFlags (); const uint32 uFlagsIn = mTxnAccount->getFieldU32 (sfFlags); uint32 uFlagsOut = uFlagsIn; const uint32 uSetFlag = mTxn.getFieldU32 (sfSetFlag); const uint32 uClearFlag = mTxn.getFieldU32 (sfClearFlag); // legacy AccountSet flags bool bSetRequireDest = (uTxFlags & TxFlag::requireDestTag) || (uSetFlag == asfRequireDest); bool bClearRequireDest = (uTxFlags & tfOptionalDestTag) || (uClearFlag == asfRequireDest); bool bSetRequireAuth = (uTxFlags & tfRequireAuth) || (uSetFlag == asfRequireAuth); bool bClearRequireAuth = (uTxFlags & tfOptionalAuth) || (uClearFlag == asfRequireAuth); bool bSetDisallowXRP = (uTxFlags & tfDisallowXRP) || (uSetFlag == asfDisallowXRP); bool bClearDisallowXRP = (uTxFlags & tfAllowXRP) || (uClearFlag == asfDisallowXRP); if (uTxFlags & tfAccountSetMask) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } // // RequireAuth // if (bSetRequireAuth && bClearRequireAuth) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Malformed transaction: Contradictory flags set."; return temINVALID_FLAG; } if (bSetRequireAuth && !isSetBit (uFlagsIn, lsfRequireAuth)) { if (mTxnAccount->getFieldU32 (sfOwnerCount)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Retry: OwnerCount not zero."; return isSetBit(mParams, tapRETRY) ? terOWNERS : tecOWNERS; } WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Set RequireAuth."; uFlagsOut |= lsfRequireAuth; } if (bClearRequireAuth && isSetBit (uFlagsIn, lsfRequireAuth)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Clear RequireAuth."; uFlagsOut &= ~lsfRequireAuth; } // // RequireDestTag // if (bSetRequireDest && bClearRequireDest) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Malformed transaction: Contradictory flags set."; return temINVALID_FLAG; } if (bSetRequireDest && !isSetBit (uFlagsIn, lsfRequireDestTag)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Set lsfRequireDestTag."; uFlagsOut |= lsfRequireDestTag; } if (bClearRequireDest && isSetBit (uFlagsIn, lsfRequireDestTag)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Clear lsfRequireDestTag."; uFlagsOut &= ~lsfRequireDestTag; } // // DisallowXRP // if (bSetDisallowXRP && bClearDisallowXRP) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Malformed transaction: Contradictory flags set."; return temINVALID_FLAG; } if (bSetDisallowXRP && !isSetBit (uFlagsIn, lsfDisallowXRP)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Set lsfDisallowXRP."; uFlagsOut |= lsfDisallowXRP; } if (bClearDisallowXRP && isSetBit (uFlagsIn, lsfDisallowXRP)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Clear lsfDisallowXRP."; uFlagsOut &= ~lsfDisallowXRP; } // // DisableMaster // if ((uSetFlag == asfDisableMaster) && (uClearFlag == asfDisableMaster)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Malformed transaction: Contradictory flags set."; return temINVALID_FLAG; } if ((uSetFlag == asfDisableMaster) && !isSetBit (uFlagsIn, lsfDisableMaster)) { if (!mTxnAccount->isFieldPresent (sfRegularKey)) return tecNO_REGULAR_KEY; WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Set lsfDisableMaster."; uFlagsOut |= lsfDisableMaster; } if ((uClearFlag == asfDisableMaster) && isSetBit (uFlagsIn, lsfDisableMaster)) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: Clear lsfDisableMaster."; uFlagsOut &= ~lsfDisableMaster; } // // EmailHash // if (mTxn.isFieldPresent (sfEmailHash)) { uint128 uHash = mTxn.getFieldH128 (sfEmailHash); if (!uHash) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: unset email hash"; mTxnAccount->makeFieldAbsent (sfEmailHash); } else { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: set email hash"; mTxnAccount->setFieldH128 (sfEmailHash, uHash); } } // // WalletLocator // if (mTxn.isFieldPresent (sfWalletLocator)) { uint256 uHash = mTxn.getFieldH256 (sfWalletLocator); if (!uHash) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: unset wallet locator"; mTxnAccount->makeFieldAbsent (sfEmailHash); } else { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: set wallet locator"; mTxnAccount->setFieldH256 (sfWalletLocator, uHash); } } // // MessageKey // if (mTxn.isFieldPresent (sfMessageKey)) { Blob vucPublic = mTxn.getFieldVL (sfMessageKey); if (vucPublic.size () > PUBLIC_BYTES_MAX) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: message key too long"; return telBAD_PUBLIC_KEY; } else { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: set message key"; mTxnAccount->setFieldVL (sfMessageKey, vucPublic); } } // // Domain // if (mTxn.isFieldPresent (sfDomain)) { Blob vucDomain = mTxn.getFieldVL (sfDomain); if (vucDomain.empty ()) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: unset domain"; mTxnAccount->makeFieldAbsent (sfDomain); } else if (vucDomain.size () > DOMAIN_BYTES_MAX) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: domain too long"; return telBAD_DOMAIN; } else { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: set domain"; mTxnAccount->setFieldVL (sfDomain, vucDomain); } } // // TransferRate // if (mTxn.isFieldPresent (sfTransferRate)) { uint32 uRate = mTxn.getFieldU32 (sfTransferRate); if (!uRate || uRate == QUALITY_ONE) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: unset transfer rate"; mTxnAccount->makeFieldAbsent (sfTransferRate); } else if (uRate > QUALITY_ONE) { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: set transfer rate"; mTxnAccount->setFieldU32 (sfTransferRate, uRate); } else { WriteLog (lsINFO, AccountSetTransactor) << "AccountSet: bad transfer rate"; return temBAD_TRANSFER_RATE; } } if (uFlagsIn != uFlagsOut) mTxnAccount->setFieldU32 (sfFlags, uFlagsOut); WriteLog (lsINFO, AccountSetTransactor) << "AccountSet<"; return tesSUCCESS; }
TER TrustSetTransactor::doApply () { TER terResult = tesSUCCESS; WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet>"; const STAmount saLimitAmount = mTxn.getFieldAmount (sfLimitAmount); const bool bQualityIn = mTxn.isFieldPresent (sfQualityIn); const bool bQualityOut = mTxn.isFieldPresent (sfQualityOut); const uint160 uCurrencyID = saLimitAmount.getCurrency (); uint160 uDstAccountID = saLimitAmount.getIssuer (); const bool bHigh = mTxnAccountID > uDstAccountID; // true, iff current is high account. uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0; uint32 uQualityOut = bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0; if (!saLimitAmount.isLegalNet ()) return temBAD_AMOUNT; if (bQualityIn && QUALITY_ONE == uQualityIn) uQualityIn = 0; if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; const uint32 uTxFlags = mTxn.getFlags (); if (uTxFlags & tfTrustSetMask) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } const bool bSetAuth = isSetBit (uTxFlags, tfSetfAuth); const bool bSetNoRipple = isSetBit (uTxFlags, tfSetNoRipple); const bool bClearNoRipple = isSetBit (uTxFlags, tfClearNoRipple); if (bSetAuth && !isSetBit (mTxnAccount->getFieldU32 (sfFlags), lsfRequireAuth)) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } if (saLimitAmount.isNative ()) { WriteLog (lsINFO, TrustSetTransactor) << boost::str (boost::format ("doTrustSet: Malformed transaction: Native credit limit: %s") % saLimitAmount.getFullText ()); return temBAD_LIMIT; } if (saLimitAmount.isNegative ()) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Negative credit limit."; return temBAD_LIMIT; } // Check if destination makes sense. if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Destination account not specified."; return temDST_NEEDED; } if (mTxnAccountID == uDstAccountID) { SLE::pointer selDelete = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID)); if (selDelete) { WriteLog (lsWARNING, TrustSetTransactor) << "doTrustSet: Clearing redundant line."; return mEngine->getNodes ().trustDelete (selDelete, mTxnAccountID, uDstAccountID); } else { WriteLog (lsINFO, TrustSetTransactor) << "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) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Destination account does not exist."; return tecNO_DST; } const uint32 uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount); // The reserve required to create the line. const uint64 uReserveCreate = (uOwnerCount < 2) ? 0 : mEngine->getLedger ()->getReserve (uOwnerCount + 1); STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (mTxnAccountID); SLE::pointer sleRippleState = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID)); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; uint32 uLowQualityIn; uint32 uLowQualityOut; uint32 uHighQualityIn; uint32 uHighQualityOut; const uint160& uLowAccountID = !bHigh ? mTxnAccountID : uDstAccountID; const uint160& uHighAccountID = bHigh ? mTxnAccountID : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? mTxnAccount : sleDst; SLE::ref sleHighAccount = bHigh ? mTxnAccount : sleDst; // // Balances // saLowBalance = sleRippleState->getFieldAmount (sfBalance); saHighBalance = -saLowBalance; // // Limits // sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit); saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit); // // Quality in // if (!bQualityIn) { // Not setting. Just get it. uLowQualityIn = sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = sleRippleState->getFieldU32 (sfHighQualityIn); } else if (uQualityIn) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn); uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn); } if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; // // Quality out // if (!bQualityOut) { // Not setting. Just get it. uLowQualityOut = sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut); } else if (uQualityOut) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut); uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } const uint32 uFlagsIn = sleRippleState->getFieldU32 (sfFlags); uint32 uFlagsOut = uFlagsIn; if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance).isGEZero()) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; const bool bLowReserveSet = uLowQualityIn || uLowQualityOut || isSetBit (uFlagsOut, lsfLowNoRipple) || !!saLowLimit || saLowBalance.isPositive (); const bool bLowReserveClear = !bLowReserveSet; const bool bHighReserveSet = uHighQualityIn || uHighQualityOut || isSetBit (uFlagsOut, lsfHighNoRipple) || !!saHighLimit || saHighBalance.isPositive (); const bool bHighReserveClear = !bHighReserveSet; const bool bDefault = bLowReserveClear && bHighReserveClear; const bool bLowReserved = isSetBit (uFlagsIn, lsfLowReserve); const bool bHighReserved = isSetBit (uFlagsIn, lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. mEngine->getNodes ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. mEngine->getNodes ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. mEngine->getNodes ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. mEngine->getNodes ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || CURRENCY_BAD == uCurrencyID) { // Delete. terResult = mEngine->getNodes ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Insufficent reserve to add trust line."; // Another transaction could provide XRP to the account and then this transaction would succeed. terResult = tecINSUF_RESERVE_LINE; } else { mEngine->entryModify (sleRippleState); WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Modify ripple line"; } } // Line does not exist. else if (!saLimitAmount // Setting default limit. && (!bQualityIn || !uQualityIn) // Not setting quality in or setting default quality in. && (!bQualityOut || !uQualityOut)) // Not setting quality out or setting default quality out. { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Line does not exist. Insufficent reserve to create line."; // Another transaction could create the account and then this transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } else if (CURRENCY_BAD == uCurrencyID) { terResult = temBAD_CURRENCY; } else { STAmount saBalance = STAmount (uCurrencyID, ACCOUNT_ONE); // Zero balance in currency. WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Creating ripple line: " << Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString (); // Create a new ripple line. terResult = mEngine->getNodes ().trustCreate ( bHigh, mTxnAccountID, uDstAccountID, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID), mTxnAccount, bSetAuth, bSetNoRipple && !bClearNoRipple, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut); } WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet<"; return terResult; }
void Transactor::calculateFee () { mFeeDue = STAmount (mEngine->getLedger ()->scaleFeeLoad (calculateBaseFee (), isSetBit (mParams, tapADMIN))); }
// Set to a canonical path. // - Remove extra elements // - Assumes path is expanded. // // We do canonicalization to: // - Prevent waste in the ledger. // - Allow longer paths to be specified than would otherwise be allowed. // // Optimization theory: // - Can omit elements that the expansion routine derives. // - Can pack some elements into other elements. // // Rules: // - SendMax if not specified, defaults currency to send and if not sending XRP defaults issuer to sender. // - All paths start with the sender account. // - Currency and issuer is from SendMax. // - All paths end with the destination account. // // Optimization: // - An XRP output implies an offer node or destination node is next. // - A change in currency implies an offer node. // - A change in issuer... void PathState::setCanonical ( const PathState& psExpanded ) { assert (false); saInAct = psExpanded.saInAct; saOutAct = psExpanded.saOutAct; const uint160 uMaxCurrencyID = saInAct.getCurrency (); const uint160 uMaxIssuerID = saInAct.getIssuer (); const uint160 uOutCurrencyID = saOutAct.getCurrency (); const uint160 uOutIssuerID = saOutAct.getIssuer (); unsigned int uNode = 0; unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include. uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID; // FIXME: This can't be right uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID; uint160 uCurrencyID = uMaxCurrencyID; uint160 uIssuerID = uMaxIssuerID; // Node 0 is a composite of the sending account and saInAct. ++uNode; // skip node 0 // Last node is implied: Always skip last node --uEnd; // skip last node // saInAct // - currency is always the same as vpnNodes[0]. #if 1 if (uNode != uEnd && uMaxIssuerID != uAccountID) { // saInAct issuer is not the sender. This forces an implied node. // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd); // skip node 1 uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID; ++uNode; } #else if (uNode != uEnd) { // Have another node bool bKeep = false; if (uMaxIssuerID != uAccountID) { } if (uMaxCurrencyID) // Not sending XRP. { // Node 1 must be an account. if (uMaxIssuerID != uAccountID) { // Node 1 is required to specify issuer. bKeep = true; } else { // Node 1 must be an account } } else { // Node 1 must be an order book. bKeep = true; } if (bKeep) { uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID; uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID; ++uNode; // Keep it. } } #endif if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID) { // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd); // The next to last node is saOutAct if an issuer different from receiver is supplied. // The next to last node can be implied. --uEnd; } const Node& pnEnd = psExpanded.vpnNodes[uEnd]; if (uNode != uEnd && !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID) { // The current end node is an offer converting to saOutAct's currency and issuer and can be implied. // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd); --uEnd; } // Do not include uEnd. for (; uNode != uEnd; ++uNode) { // WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd); const Node& pnPrv = psExpanded.vpnNodes[uNode - 1]; const Node& pnCur = psExpanded.vpnNodes[uNode]; const Node& pnNxt = psExpanded.vpnNodes[uNode + 1]; const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount); bool bSkip = false; if (bCurAccount) { // Currently at an account. // Output is non-XRP and issuer is account. if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID) { // Account issues itself. // XXX Not good enough. Previous account must mention it. bSkip = true; } } else { // Currently at an offer. const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount); const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount); if (bPrvAccount && bNxtAccount // Offer surrounded by accounts. && pnPrv.uCurrencyID != pnNxt.uCurrencyID) { // Offer can be implied by currency change. // XXX What about issuer? bSkip = true; } } if (!bSkip) { // Copy node Node pnNew; bool bSetAccount = bCurAccount; bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID; // XXX What if we need the next account because we want to skip it? bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID; pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0) | (bSetCurrency ? STPathElement::typeCurrency : 0) | (bSetIssuer ? STPathElement::typeIssuer : 0); if (bSetAccount) pnNew.uAccountID = pnCur.uAccountID; if (bSetCurrency) { pnNew.uCurrencyID = pnCur.uCurrencyID; uCurrencyID = pnNew.uCurrencyID; } if (bSetIssuer) pnNew.uIssuerID = pnCur.uIssuerID; // XXX ^^^ What about setting uIssuerID? if (bSetCurrency && !uCurrencyID) uIssuerID.zero (); vpnNodes.push_back (pnNew); } } WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setCanonical: in=%s/%s out=%s/%s %s") % STAmount::createHumanCurrency (uMaxCurrencyID) % RippleAddress::createHumanAccountID (uMaxIssuerID) % STAmount::createHumanCurrency (uOutCurrencyID) % RippleAddress::createHumanAccountID (uOutIssuerID) % getJson ()); }
// Append a node and insert before it any implied nodes. // Offers may go back to back. // <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY TER PathState::pushNode ( const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) { Node pnCur; const bool bFirst = vpnNodes.empty (); const Node& pnPrv = bFirst ? Node () : vpnNodes.back (); // true, iff node is a ripple account. false, iff node is an offer node. const bool bAccount = isSetBit (iType, STPathElement::typeAccount); // true, iff currency supplied. // Currency is specified for the output of the current node. const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency); // Issuer is specified for the output of the current node. const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer); TER terResult = tesSUCCESS; WriteLog (lsTRACE, RippleCalc) << "pushNode> " << iType << ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-") << " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") << "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-"); pnCur.uFlags = iType; pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; if (iType & ~STPathElement::typeValidBits) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits."; terResult = temBAD_PATH; } else if (bIssuer && !pnCur.uCurrencyID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP."; terResult = temBAD_PATH; } else if (bIssuer && !uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer."; terResult = temBAD_PATH; } else if (!bAccount && !bCurrency && !bIssuer) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer."; terResult = temBAD_PATH; } else if (bAccount) { // Account link pnCur.uAccountID = uAccountID; pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP; pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (bFirst) { // The first node is always correct as is. nothing (); } else if (!uAccountID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account."; terResult = temBAD_PATH; } else { // Add required intermediate nodes to deliver to current account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account."; terResult = pushImply ( pnCur.uAccountID, // Current account. pnCur.uCurrencyID, // Wanted currency. !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer. // Note: pnPrv may no longer be the immediately previous node. } if (tesSUCCESS == terResult && !vpnNodes.empty ()) { const Node& pnBck = vpnNodes.back (); bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount); if (bBckAccount) { SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); if (!sleRippleState) { WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; WriteLog (lsTRACE, RippleCalc) << getJson (); terResult = terNO_LINE; } else { WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID)); bool bHigh = pnBck.uAccountID > pnCur.uAccountID; if (!sleBck) { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID); terResult = terNO_ACCOUNT; } else if ((isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth) && !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth))) && sleRippleState->getFieldAmount(sfBalance).isZero()) // CHECKME { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth."; terResult = terNO_AUTH; } else if (isSetBit (sleRippleState->getFieldU32 (sfFlags), bHigh ? lsfHighNoRipple : lsfLowNoRipple) && (vpnNodes.size() > 1)) { // If the link leaves the side that set no ripple, it must be the first link WriteLog (lsWARNING, RippleCalc) << "pushNode: illegal use of noRipple link"; terResult = terNO_AUTH; } if (tesSUCCESS == terResult) { STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID); STAmount saLimit; if (!saOwed.isPositive () && -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID))) { WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s") % saOwed % saLimit); terResult = tecPATH_DRY; } } } } } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } else { // Offer link // Offers bridge a change in currency & issuer or just a change in issuer. pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? !!pnPrv.uIssuerID ? pnPrv.uIssuerID // Default to previous issuer : pnPrv.uAccountID // Or previous account if no previous issuer. : ACCOUNT_XRP; pnCur.saRateMax = saZero; pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer."; terResult = temBAD_PATH; } else if (!!pnPrv.uAccountID) { // Previous is an account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer."; // Insert intermediary issuer account if needed. terResult = pushImply ( ACCOUNT_XRP, // Rippling, but offers don't have an account. pnPrv.uCurrencyID, pnPrv.uIssuerID); } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult)); return terResult; }
TER AccountSetTransactor::doApply() { cLog(lsINFO) << "AccountSet>"; const uint32 uTxFlags = mTxn.getFlags(); const uint32 uFlagsIn = mTxnAccount->getFieldU32(sfFlags); uint32 uFlagsOut = uFlagsIn; if (uTxFlags & tfAccountSetMask) { cLog(lsINFO) << "AccountSet: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } // // RequireAuth // if ((tfRequireAuth|tfOptionalAuth) == (uTxFlags & (tfRequireAuth|tfOptionalAuth))) { cLog(lsINFO) << "AccountSet: Malformed transaction: Contradictory flags set."; return temINVALID_FLAG; } if ((uTxFlags & tfRequireAuth) && !isSetBit(uFlagsIn, lsfRequireAuth)) { if (mTxn.getFieldU32(sfOwnerCount)) { cLog(lsINFO) << "AccountSet: Retry: OwnerCount not zero."; return terOWNERS; } cLog(lsINFO) << "AccountSet: Set RequireAuth."; uFlagsOut |= lsfRequireAuth; } if (uTxFlags & tfOptionalAuth) { cLog(lsINFO) << "AccountSet: Clear RequireAuth."; uFlagsOut &= ~lsfRequireAuth; } // // RequireDestTag // if ((tfRequireDestTag|tfOptionalDestTag) == (uTxFlags & (tfRequireDestTag|tfOptionalDestTag))) { cLog(lsINFO) << "AccountSet: Malformed transaction: Contradictory flags set."; return temINVALID_FLAG; } if (uTxFlags & tfRequireDestTag) { cLog(lsINFO) << "AccountSet: Set RequireDestTag."; uFlagsOut |= lsfRequireDestTag; } if (uTxFlags & tfOptionalDestTag) { cLog(lsINFO) << "AccountSet: Clear RequireDestTag."; uFlagsOut &= ~lsfRequireDestTag; } if (uFlagsIn != uFlagsOut) mTxnAccount->setFieldU32(sfFlags, uFlagsOut); // // EmailHash // if (mTxn.isFieldPresent(sfEmailHash)) { uint128 uHash = mTxn.getFieldH128(sfEmailHash); if (!uHash) { cLog(lsINFO) << "AccountSet: unset email hash"; mTxnAccount->makeFieldAbsent(sfEmailHash); } else { cLog(lsINFO) << "AccountSet: set email hash"; mTxnAccount->setFieldH128(sfEmailHash, uHash); } } // // WalletLocator // if (mTxn.isFieldPresent(sfWalletLocator)) { uint256 uHash = mTxn.getFieldH256(sfWalletLocator); if (!uHash) { cLog(lsINFO) << "AccountSet: unset wallet locator"; mTxnAccount->makeFieldAbsent(sfEmailHash); } else { cLog(lsINFO) << "AccountSet: set wallet locator"; mTxnAccount->setFieldH256(sfWalletLocator, uHash); } } // // MessageKey // if (mTxn.isFieldPresent(sfMessageKey)) { std::vector<unsigned char> vucPublic = mTxn.getFieldVL(sfMessageKey); if (vucPublic.size() > PUBLIC_BYTES_MAX) { cLog(lsINFO) << "AccountSet: message key too long"; return telBAD_PUBLIC_KEY; } else { cLog(lsINFO) << "AccountSet: set message key"; mTxnAccount->setFieldVL(sfMessageKey, vucPublic); } } // // Domain // if (mTxn.isFieldPresent(sfDomain)) { std::vector<unsigned char> vucDomain = mTxn.getFieldVL(sfDomain); if (vucDomain.empty()) { cLog(lsINFO) << "AccountSet: unset domain"; mTxnAccount->makeFieldAbsent(sfDomain); } else if (vucDomain.size() > DOMAIN_BYTES_MAX) { cLog(lsINFO) << "AccountSet: domain too long"; return telBAD_DOMAIN; } else { cLog(lsINFO) << "AccountSet: set domain"; mTxnAccount->setFieldVL(sfDomain, vucDomain); } } // // TransferRate // if (mTxn.isFieldPresent(sfTransferRate)) { uint32 uRate = mTxn.getFieldU32(sfTransferRate); if (!uRate || uRate == QUALITY_ONE) { cLog(lsINFO) << "AccountSet: unset transfer rate"; mTxnAccount->makeFieldAbsent(sfTransferRate); } else if (uRate > QUALITY_ONE) { cLog(lsINFO) << "AccountSet: set transfer rate"; mTxnAccount->setFieldU32(sfTransferRate, uRate); } else { cLog(lsINFO) << "AccountSet: bad transfer rate"; return temBAD_TRANSFER_RATE; } } cLog(lsINFO) << "AccountSet<"; return tesSUCCESS; }