TER SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet, Keylet const& ownerDirKeylet, Keylet const& signerListKeylet) { // We have to examine the current SignerList so we know how much to // reduce the OwnerCount. SLE::pointer signers = view().peek (signerListKeylet); // If the signer list doesn't exist we've already succeeded in deleting it. if (!signers) return tesSUCCESS; STArray const& actualList = signers->getFieldArray (sfSignerEntries); int const removeFromOwnerCount = ownerCountDelta (actualList.size()) * -1; // Remove the node from the account directory. auto const hint = (*signers)[sfOwnerNode]; auto viewJ = ctx_.app.journal ("View"); TER const result = dirDelete(ctx_.view(), false, hint, ownerDirKeylet.key, signerListKeylet.key, false, (hint == 0), viewJ); if (result == tesSUCCESS) adjustOwnerCount(view(), view().peek(accountKeylet), removeFromOwnerCount, viewJ); ctx_.view().erase (signers); return result; }
TER SetSignerList::replaceSignerList () { auto const accountKeylet = keylet::account (account_); auto const ownerDirKeylet = keylet::ownerDir (account_); auto const signerListKeylet = keylet::signers (account_); // This may be either a create or a replace. Preemptively remove any // old signer list. May reduce the reserve, so this is done before // checking the reserve. if (TER const ter = removeSignersFromLedger ( accountKeylet, ownerDirKeylet, signerListKeylet)) return ter; auto const sle = view().peek(accountKeylet); // Compute new reserve. Verify the account has funds to meet the reserve. auto const oldOwnerCount = (*sle)[sfOwnerCount]; std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ()); auto const newReserve = view().fees().accountReserve(oldOwnerCount + addedOwnerCount); // We check the reserve against the starting balance because we want to // allow dipping into the reserve to pay fees. This behavior is consistent // with CreateTicket. if (mPriorBalance < newReserve) return tecINSUFFICIENT_RESERVE; // Everything's ducky. Add the ltSIGNER_LIST to the ledger. auto signerList = std::make_shared<SLE>(signerListKeylet); view().insert (signerList); writeSignersToSLE (signerList); auto viewJ = ctx_.app.journal ("View"); // Add the signer list to the account's directory. std::uint64_t hint; TER result = dirAdd(ctx_.view (), hint, ownerDirKeylet.key, signerListKeylet.key, describeOwnerDir (account_), viewJ); JLOG(j_.trace) << "Create signer list for account " << toBase58(account_) << ": " << transHuman (result); if (result != tesSUCCESS) return result; signerList->setFieldU64 (sfOwnerNode, hint); // If we succeeded, the new entry counts against the creator's reserve. adjustOwnerCount(view(), sle, addedOwnerCount, viewJ); return result; }
TER CancelTicket::doApply () { uint256 const ticketId = ctx_.tx.getFieldH256 (sfTicketID); // VFALCO This is highly suspicious, we're requiring that the // transaction provide the return value of getTicketIndex? SLE::pointer sleTicket = view().peek (keylet::ticket(ticketId)); if (!sleTicket) return tecNO_ENTRY; auto const ticket_owner = sleTicket->getAccountID (sfAccount); bool authorized = account_ == ticket_owner; // The target can also always remove a ticket if (!authorized && sleTicket->isFieldPresent (sfTarget)) authorized = (account_ == sleTicket->getAccountID (sfTarget)); // And finally, anyone can remove an expired ticket if (!authorized && sleTicket->isFieldPresent (sfExpiration)) { using tp = NetClock::time_point; using d = tp::duration; auto const expiration = tp{d{sleTicket->getFieldU32(sfExpiration)}}; if (view().parentCloseTime() >= expiration) authorized = true; } if (!authorized) return tecNO_PERMISSION; std::uint64_t const hint (sleTicket->getFieldU64 (sfOwnerNode)); if (! ctx_.view().dirRemove( keylet::ownerDir(ticket_owner), hint, ticketId, false)) { return tefBAD_LEDGER; } auto viewJ = ctx_.app.journal ("View"); adjustOwnerCount(view(), view().peek( keylet::account(ticket_owner)), -1, viewJ); ctx_.view ().erase (sleTicket); return tesSUCCESS; }
TER SetTrust::doApply () { TER terResult = tesSUCCESS; STAmount const saLimitAmount (tx().getFieldAmount (sfLimitAmount)); bool const bQualityIn (tx().isFieldPresent (sfQualityIn)); bool const bQualityOut (tx().isFieldPresent (sfQualityOut)); Currency const currency (saLimitAmount.getCurrency ()); AccountID uDstAccountID (saLimitAmount.getIssuer ()); // true, iff current is high account. bool const bHigh = account_ > uDstAccountID; auto const sle = view().peek( keylet::account(account_)); std::uint32_t const uOwnerCount = sle->getFieldU32 (sfOwnerCount); // The reserve required to create the line. Note that we allow up to // two trust lines without requiring a reserve because being able to // exchange currencies is a powerful Ripple feature. // // This is also a security feature: if you're a gateway and you want to // be able to let someone use your services, you would otherwise have to // give them enough XRP to cover the incremental reserve for their trust // line. If they had no intention of using your services, they could use // the XRP for their own purposes. So we make it possible for gateways // to fund accounts in a way where there's no incentive to trick them // into creating an account you have no intention of using. XRPAmount const reserveCreate ((uOwnerCount < 2) ? XRPAmount (zero) : view().fees().accountReserve(uOwnerCount + 1)); std::uint32_t uQualityIn (bQualityIn ? tx().getFieldU32 (sfQualityIn) : 0); std::uint32_t uQualityOut (bQualityOut ? tx().getFieldU32 (sfQualityOut) : 0); if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; std::uint32_t const uTxFlags = tx().getFlags (); bool const bSetAuth = (uTxFlags & tfSetfAuth); bool const bSetNoRipple = (uTxFlags & tfSetNoRipple); bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); auto viewJ = ctx_.app.journal ("View"); if (bSetAuth && !(sle->getFieldU32 (sfFlags) & lsfRequireAuth)) { j_.trace << "Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } if (account_ == uDstAccountID) { // The only purpose here is to allow a mistakenly created // trust line to oneself to be deleted. If no such trust // lines exist now, why not remove this code and simply // return an error? SLE::pointer sleDelete = view().peek ( keylet::line(account_, uDstAccountID, currency)); if (sleDelete) { j_.warning << "Clearing redundant line."; return trustDelete (view(), sleDelete, account_, uDstAccountID, viewJ); } else { j_.trace << "Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } } SLE::pointer sleDst = view().peek (keylet::account(uDstAccountID)); if (!sleDst) { j_.trace << "Delay transaction: Destination account does not exist."; return tecNO_DST; } STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (account_); SLE::pointer sleRippleState = view().peek ( keylet::line(account_, uDstAccountID, currency)); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; std::uint32_t uLowQualityIn; std::uint32_t uLowQualityOut; std::uint32_t uHighQualityIn; std::uint32_t uHighQualityOut; auto const& uLowAccountID = !bHigh ? account_ : uDstAccountID; auto const& uHighAccountID = bHigh ? account_ : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? sle : sleDst; SLE::ref sleHighAccount = bHigh ? sle : 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); } std::uint32_t const uFlagsIn (sleRippleState->getFieldU32 (sfFlags)); std::uint32_t uFlagsOut (uFlagsIn); if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance) >= zero) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (bSetFreeze && !bClearFreeze && !sle->isFlag (lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; bool const bLowDefRipple = sleLowAccount->getFlags() & lsfDefaultRipple; bool const bHighDefRipple = sleHighAccount->getFlags() & lsfDefaultRipple; bool const bLowReserveSet = uLowQualityIn || uLowQualityOut || ((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple || (uFlagsOut & lsfLowFreeze) || saLowLimit || saLowBalance > zero; bool const bLowReserveClear = !bLowReserveSet; bool const bHighReserveSet = uHighQualityIn || uHighQualityOut || ((uFlagsOut & lsfHighNoRipple) == 0) != bHighDefRipple || (uFlagsOut & lsfHighFreeze) || saHighLimit || saHighBalance > zero; bool const bHighReserveClear = !bHighReserveSet; bool const bDefault = bLowReserveClear && bHighReserveClear; bool const bLowReserved = (uFlagsIn & lsfLowReserve); bool const bHighReserved = (uFlagsIn & lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. adjustOwnerCount(view(), sleLowAccount, 1, viewJ); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. adjustOwnerCount(view(), sleLowAccount, -1, viewJ); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. adjustOwnerCount(view(), sleHighAccount, 1, viewJ); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. adjustOwnerCount(view(), sleHighAccount, -1, viewJ); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || badCurrency() == currency) { // Delete. terResult = trustDelete (view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); } // Reserve is not scaled by load. else if (bReserveIncrease && mPriorBalance < reserveCreate) { j_.trace << "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 { view().update (sleRippleState); j_.trace << "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. (! ((view().flags() & tapENABLE_TESTING) || view().rules().enabled(featureTrustSetAuth, ctx_.config.features)) || ! bSetAuth)) { j_.trace << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance < reserveCreate) // Reserve is not scaled by load. { j_.trace << "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 { // Zero balance in currency. STAmount saBalance ({currency, noAccount()}); uint256 index (getRippleStateIndex ( account_, uDstAccountID, currency)); j_.trace << "doTrustSet: Creating ripple line: " << to_string (index); // Create a new ripple line. terResult = trustCreate (view(), bHigh, account_, uDstAccountID, index, sle, bSetAuth, bSetNoRipple && !bClearNoRipple, bSetFreeze && !bClearFreeze, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut, viewJ); } return terResult; }
TER CreateTicket::doApply () { auto const sle = view().peek(keylet::account(account_)); // A ticket counts against the reserve of the issuing account, but we // check the starting balance because we want to allow dipping into the // reserve to pay fees. { auto const reserve = view().fees().accountReserve( sle->getFieldU32(sfOwnerCount) + 1); if (mPriorBalance < reserve) return tecINSUFFICIENT_RESERVE; } NetClock::time_point expiration{}; if (ctx_.tx.isFieldPresent (sfExpiration)) { expiration = NetClock::time_point(NetClock::duration(ctx_.tx[sfExpiration])); if (view().parentCloseTime() >= expiration) return tesSUCCESS; } SLE::pointer sleTicket = std::make_shared<SLE>(ltTICKET, getTicketIndex (account_, ctx_.tx.getSequence ())); sleTicket->setAccountID (sfAccount, account_); sleTicket->setFieldU32 (sfSequence, ctx_.tx.getSequence ()); if (expiration != NetClock::time_point{}) sleTicket->setFieldU32 (sfExpiration, expiration.time_since_epoch().count()); view().insert (sleTicket); if (ctx_.tx.isFieldPresent (sfTarget)) { AccountID const target_account (ctx_.tx.getAccountID (sfTarget)); SLE::pointer sleTarget = view().peek (keylet::account(target_account)); // Destination account does not exist. if (!sleTarget) return tecNO_TARGET; // The issuing account is the default account to which the ticket // applies so don't bother saving it if that's what's specified. if (target_account != account_) sleTicket->setAccountID (sfTarget, target_account); } auto viewJ = ctx_.app.journal ("View"); auto const page = dirAdd(view(), keylet::ownerDir (account_), sleTicket->key(), false, describeOwnerDir (account_), viewJ); JLOG(j_.trace()) << "Creating ticket " << to_string (sleTicket->key()) << ": " << (page ? "success" : "failure"); if (!page) return tecDIR_FULL; sleTicket->setFieldU64(sfOwnerNode, *page); // If we succeeded, the new entry counts against the // creator's reserve. adjustOwnerCount(view(), sle, 1, viewJ); return tesSUCCESS; }