TER SetAccount::preclaim(PreclaimContext const& ctx) { auto const id = ctx.tx[sfAccount]; std::uint32_t const uTxFlags = ctx.tx.getFlags(); auto const sle = ctx.view.read( keylet::account(id)); std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags); std::uint32_t const uSetFlag = ctx.tx.getFieldU32(sfSetFlag); // legacy AccountSet flags bool bSetRequireAuth = (uTxFlags & tfRequireAuth) || (uSetFlag == asfRequireAuth); // // RequireAuth // if (bSetRequireAuth && !(uFlagsIn & lsfRequireAuth)) { if (!dirIsEmpty(ctx.view, keylet::ownerDir(id))) { JLOG(ctx.j.trace) << "Retry: Owner directory not empty."; return (ctx.flags & tapRETRY) ? terOWNERS : tecOWNERS; } } return tesSUCCESS; }
TER CancelOffer::doApply () { std::uint32_t const uOfferSequence = tx().getFieldU32 (sfOfferSequence); auto const sle = view().read( keylet::account(account_)); if (sle->getFieldU32 (sfSequence) - 1 <= uOfferSequence) { j_.trace << "Malformed transaction: " << "Sequence " << uOfferSequence << " is invalid."; return temBAD_SEQUENCE; } uint256 const offerIndex (getOfferIndex (account_, uOfferSequence)); auto sleOffer = view().peek ( keylet::offer(offerIndex)); if (sleOffer) { j_.debug << "Trying to cancel offer #" << uOfferSequence; return offerDelete (view(), sleOffer, ctx_.app.journal ("View")); } j_.debug << "Offer #" << uOfferSequence << " can't be found."; return tesSUCCESS; }
static Rate rippleQuality ( ReadView const& view, AccountID const& destination, AccountID const& source, Currency const& currency, SField const& sfLow, SField const& sfHigh) { if (destination == source) return parityRate; auto const& sfField = destination < source ? sfLow : sfHigh; auto const sle = view.read( keylet::line(destination, source, currency)); if (!sle || !sle->isFieldPresent (sfField)) return parityRate; auto quality = sle->getFieldU32 (sfField); // Avoid divide by zero. NIKB CHECKME: if we // allow zero qualities now, then we shouldn't. if (quality == 0) quality = 1; return Rate{ quality }; }
bool SerializedLedgerEntry::thread (uint256 const& txID, uint32 ledgerSeq, uint256& prevTxID, uint32& prevLedgerID) { uint256 oldPrevTxID = getFieldH256 (sfPreviousTxnID); WriteLog (lsTRACE, SerializedLedgerLog) << "Thread Tx:" << txID << " prev:" << oldPrevTxID; if (oldPrevTxID == txID) { // this transaction is already threaded assert (getFieldU32 (sfPreviousTxnLgrSeq) == ledgerSeq); return false; } prevTxID = oldPrevTxID; prevLedgerID = getFieldU32 (sfPreviousTxnLgrSeq); setFieldH256 (sfPreviousTxnID, txID); setFieldU32 (sfPreviousTxnLgrSeq, ledgerSeq); return true; }
void nflags::operator()(Env& env) const { auto const sle = env.le(account_); if (sle->isFieldPresent(sfFlags)) env.test.expect((sle->getFieldU32(sfFlags) & mask_) == 0); else env.test.pass(); }
bool STLedgerEntry::thread (uint256 const& txID, std::uint32_t ledgerSeq, uint256& prevTxID, std::uint32_t& prevLedgerID) { uint256 oldPrevTxID = getFieldH256 (sfPreviousTxnID); JLOG (debugLog().info()) << "Thread Tx:" << txID << " prev:" << oldPrevTxID; if (oldPrevTxID == txID) { // this transaction is already threaded assert (getFieldU32 (sfPreviousTxnLgrSeq) == ledgerSeq); return false; } prevTxID = oldPrevTxID; prevLedgerID = getFieldU32 (sfPreviousTxnLgrSeq); setFieldH256 (sfPreviousTxnID, txID); setFieldU32 (sfPreviousTxnLgrSeq, ledgerSeq); return true; }
NotTEC Transactor::checkSeq (PreclaimContext const& ctx) { auto const id = ctx.tx.getAccountID(sfAccount); auto const sle = ctx.view.read( keylet::account(id)); if (!sle) { JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist " << toBase58(ctx.tx.getAccountID(sfAccount)); return terNO_ACCOUNT; } std::uint32_t const t_seq = ctx.tx.getSequence (); std::uint32_t const a_seq = sle->getFieldU32 (sfSequence); if (t_seq != a_seq) { if (a_seq < t_seq) { JLOG(ctx.j.trace()) << "applyTransaction: has future sequence number " << "a_seq=" << a_seq << " t_seq=" << t_seq; return terPRE_SEQ; } if (ctx.view.txExists(ctx.tx.getTransactionID ())) return tefALREADY; JLOG(ctx.j.trace()) << "applyTransaction: has past sequence number " << "a_seq=" << a_seq << " t_seq=" << t_seq; return tefPAST_SEQ; } if (ctx.tx.isFieldPresent (sfAccountTxnID) && (sle->getFieldH256 (sfAccountTxnID) != ctx.tx.getFieldH256 (sfAccountTxnID))) return tefWRONG_PRIOR; if (ctx.tx.isFieldPresent (sfLastLedgerSequence) && (ctx.view.seq() > ctx.tx.getFieldU32 (sfLastLedgerSequence))) return tefMAX_LEDGER; return tesSUCCESS; }
TER SetTrust::preclaim(PreclaimContext const& ctx) { auto const id = ctx.tx[sfAccount]; auto const sle = ctx.view.read( keylet::account(id)); std::uint32_t const uTxFlags = ctx.tx.getFlags(); bool const bSetAuth = (uTxFlags & tfSetfAuth); if (bSetAuth && !(sle->getFieldU32(sfFlags) & lsfRequireAuth)) { JLOG(ctx.j.trace) << "Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } auto const saLimitAmount = ctx.tx[sfLimitAmount]; auto const currency = saLimitAmount.getCurrency(); auto const uDstAccountID = saLimitAmount.getIssuer(); if (id == uDstAccountID) { // Prevent trustline to self from being created, // unless one has somehow already been created // (in which case doApply will clean it up). auto const sleDelete = ctx.view.read( keylet::line(id, uDstAccountID, currency)); if (!sleDelete) { JLOG(ctx.j.trace) << "Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } } return tesSUCCESS; }
void fill_seq (Json::Value& jv, ReadView const& view) { if (jv.isMember(jss::Sequence)) return; auto const account = parseBase58<AccountID>( jv[jss::Account].asString()); if (! account) Throw<parse_error> ( "unexpected invalid Account"); auto const ar = view.read( keylet::account(*account)); if (! ar) Throw<parse_error> ( "unexpected missing account root"); jv[jss::Sequence] = ar->getFieldU32(sfSequence); }
// { // account: <account>|<account_public_key> // account_index: <number> // optional, defaults to 0. // ledger_hash : <ledger> // ledger_index : <ledger_index> // limit: integer // optional // marker: opaque // optional, resume previous query // } Json::Value doAccountOffers (RPC::Context& context) { auto const& params (context.params); if (! params.isMember (jss::account)) return RPC::missing_field_error (jss::account); Ledger::pointer ledger; Json::Value result (RPC::lookupLedger (params, ledger, context.netOps)); if (! ledger) return result; std::string strIdent (params[jss::account].asString ()); bool bIndex (params.isMember (jss::account_index)); int const iIndex (bIndex ? params[jss::account_index].asUInt () : 0); DivvyAddress divvyAddress; Json::Value const jv = RPC::accountFromString ( divvyAddress, bIndex, strIdent, iIndex, false); if (! jv.empty ()) { for (Json::Value::const_iterator it (jv.begin ()); it != jv.end (); ++it) result[it.memberName ()] = it.key (); return result; } // Get info on account. result[jss::account] = divvyAddress.humanAccountID (); if (bIndex) result[jss::account_index] = iIndex; if (! ledger->exists(getAccountRootIndex( divvyAddress.getAccountID()))) return rpcError (rpcACT_NOT_FOUND); unsigned int limit; if (params.isMember (jss::limit)) { auto const& jvLimit (params[jss::limit]); if (! jvLimit.isIntegral ()) return RPC::expected_field_error (jss::limit, "unsigned integer"); limit = jvLimit.isUInt () ? jvLimit.asUInt () : std::max (0, jvLimit.asInt ()); if (context.role != Role::ADMIN) { limit = std::max (RPC::Tuning::minOffersPerRequest, std::min (limit, RPC::Tuning::maxOffersPerRequest)); } } else { limit = RPC::Tuning::defaultOffersPerRequest; } AccountID const& raAccount (divvyAddress.getAccountID ()); Json::Value& jsonOffers (result[jss::offers] = Json::arrayValue); std::vector <std::shared_ptr<SLE const>> offers; unsigned int reserve (limit); uint256 startAfter; std::uint64_t startHint; if (params.isMember(jss::marker)) { // We have a start point. Use limit - 1 from the result and use the // very last one for the resume. Json::Value const& marker (params[jss::marker]); if (! marker.isString ()) return RPC::expected_field_error (jss::marker, "string"); startAfter.SetHex (marker.asString ()); auto const sleOffer = fetch (*ledger, startAfter, getApp().getSLECache()); if (sleOffer == nullptr || sleOffer->getType () != ltOFFER || raAccount != sleOffer->getFieldAccount160 (sfAccount)) { return rpcError (rpcINVALID_PARAMS); } startHint = sleOffer->getFieldU64(sfOwnerNode); // Caller provided the first offer (startAfter), add it as first result Json::Value& obj (jsonOffers.append (Json::objectValue)); sleOffer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]); sleOffer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]); obj[jss::seq] = sleOffer->getFieldU32 (sfSequence); obj[jss::flags] = sleOffer->getFieldU32 (sfFlags); offers.reserve (reserve); } else { startHint = 0; // We have no start point, limit should be one higher than requested. offers.reserve (++reserve); } if (! forEachItemAfter(*ledger, raAccount, getApp().getSLECache(), startAfter, startHint, reserve, [&offers](std::shared_ptr<SLE const> const& offer) { if (offer->getType () == ltOFFER) { offers.emplace_back (offer); return true; } return false; })) { return rpcError (rpcINVALID_PARAMS); } if (offers.size () == reserve) { result[jss::limit] = limit; result[jss::marker] = to_string (offers.back ()->getIndex ()); offers.pop_back (); } for (auto const& offer : offers) { Json::Value& obj (jsonOffers.append (Json::objectValue)); offer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]); offer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]); obj[jss::seq] = offer->getFieldU32 (sfSequence); obj[jss::flags] = offer->getFieldU32 (sfFlags); } context.loadType = Resource::feeMediumBurdenRPC; return result; }
std::uint32_t STLedgerEntry::getThreadedLedger () const { return getFieldU32 (sfPreviousTxnLgrSeq); }
NetClock::time_point STValidation::getSignTime () const { return NetClock::time_point{NetClock::duration{getFieldU32(sfSigningTime)}}; }
uint32 SerializedValidation::getFlags() const { return getFieldU32(sfFlags); }
uint32 SerializedValidation::getSignTime() const { return getFieldU32(sfSigningTime); }
std::uint32_t SerializedLedgerEntry::getThreadedLedger () { return getFieldU32 (sfPreviousTxnLgrSeq); }
// Append a node, then create and insert before it any implied nodes. Order // book nodes may go back to back. // // For each non-matching pair of IssuedCurrency, there's an order book. // // <-- resultCode: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, // terNO_LINE, tecPATH_DRY TER PathState::pushNode ( const int iType, AccountID const& account, // If not specified, means an order book. Currency const& currency, // If not specified, default to previous. AccountID const& issuer) // If not specified, default to previous. { path::Node node; const bool pathIsEmpty = nodes_.empty (); // TODO(tom): if pathIsEmpty, we probably don't need to do ANYTHING below. // Indeed, we might just not even call pushNode in the first place! auto const& backNode = pathIsEmpty ? path::Node () : nodes_.back (); // true, iff node is a ripple account. false, iff node is an offer node. const bool hasAccount = (iType & STPathElement::typeAccount); // Is currency specified for the output of the current node? const bool hasCurrency = (iType & STPathElement::typeCurrency); // Issuer is specified for the output of the current node. const bool hasIssuer = (iType & STPathElement::typeIssuer); TER resultCode = tesSUCCESS; JLOG (j_.trace) << "pushNode> " << iType << ": " << (hasAccount ? to_string(account) : std::string("-")) << " " << (hasCurrency ? to_string(currency) : std::string("-")) << "/" << (hasIssuer ? to_string(issuer) : std::string("-")) << "/"; node.uFlags = iType; node.issue_.currency = hasCurrency ? currency : backNode.issue_.currency; // TODO(tom): we can probably just return immediately whenever we hit an // error in these next pages. if (iType & ~STPathElement::typeAll) { // Of course, this could never happen. JLOG (j_.debug) << "pushNode: bad bits."; resultCode = temBAD_PATH; } else if (hasIssuer && isXRP (node.issue_)) { JLOG (j_.debug) << "pushNode: issuer specified for XRP."; resultCode = temBAD_PATH; } else if (hasIssuer && !issuer) { JLOG (j_.debug) << "pushNode: specified bad issuer."; resultCode = temBAD_PATH; } else if (!hasAccount && !hasCurrency && !hasIssuer) { // You can't default everything to the previous node as you would make // no progress. JLOG (j_.debug) << "pushNode: offer must specify at least currency or issuer."; resultCode = temBAD_PATH; } else if (hasAccount) { // Account link node.account_ = account; node.issue_.account = hasIssuer ? issuer : (isXRP (node.issue_) ? xrpAccount() : account); // Zero value - for accounts. node.saRevRedeem = STAmount ({node.issue_.currency, account}); node.saRevIssue = node.saRevRedeem; // For order books only - zero currency with the issuer ID. node.saRevDeliver = STAmount (node.issue_); node.saFwdDeliver = node.saRevDeliver; if (pathIsEmpty) { // The first node is always correct as is. } else if (!account) { JLOG (j_.debug) << "pushNode: specified bad account."; resultCode = temBAD_PATH; } else { // Add required intermediate nodes to deliver to current account. JLOG (j_.trace) << "pushNode: imply for account."; resultCode = pushImpliedNodes ( node.account_, node.issue_.currency, isXRP(node.issue_.currency) ? xrpAccount() : account); // Note: backNode may no longer be the immediately previous node. } if (resultCode == tesSUCCESS && !nodes_.empty ()) { auto const& backNode = nodes_.back (); if (backNode.isAccount()) { auto sleRippleState = view().peek( keylet::line(backNode.account_, node.account_, backNode.issue_.currency)); // A "RippleState" means a balance betweeen two accounts for a // specific currency. if (!sleRippleState) { JLOG (j_.trace) << "pushNode: No credit line between " << backNode.account_ << " and " << node.account_ << " for " << node.issue_.currency << "." ; JLOG (j_.trace) << getJson (); resultCode = terNO_LINE; } else { JLOG (j_.trace) << "pushNode: Credit line found between " << backNode.account_ << " and " << node.account_ << " for " << node.issue_.currency << "." ; auto sleBck = view().peek ( keylet::account(backNode.account_)); // Is the source account the highest numbered account ID? bool bHigh = backNode.account_ > node.account_; if (!sleBck) { JLOG (j_.warning) << "pushNode: delay: can't receive IOUs from " << "non-existent issuer: " << backNode.account_; resultCode = terNO_ACCOUNT; } else if ((sleBck->getFieldU32 (sfFlags) & lsfRequireAuth) && !(sleRippleState->getFieldU32 (sfFlags) & (bHigh ? lsfHighAuth : lsfLowAuth)) && sleRippleState->getFieldAmount(sfBalance) == zero) { JLOG (j_.warning) << "pushNode: delay: can't receive IOUs from " << "issuer without auth."; resultCode = terNO_AUTH; } if (resultCode == tesSUCCESS) { STAmount saOwed = creditBalance (view(), node.account_, backNode.account_, node.issue_.currency); STAmount saLimit; if (saOwed <= zero) { saLimit = creditLimit (view(), node.account_, backNode.account_, node.issue_.currency); if (-saOwed >= saLimit) { JLOG (j_.debug) << "pushNode: dry:" << " saOwed=" << saOwed << " saLimit=" << saLimit; resultCode = tecPATH_DRY; } } } } } } if (resultCode == tesSUCCESS) nodes_.push_back (node); } else { // Offer link. // // Offers bridge a change in currency and issuer, or just a change in // issuer. if (hasIssuer) node.issue_.account = issuer; else if (isXRP (node.issue_.currency)) node.issue_.account = xrpAccount(); else if (isXRP (backNode.issue_.account)) node.issue_.account = backNode.account_; else node.issue_.account = backNode.issue_.account; node.saRateMax = STAmount::saZero; node.saRevDeliver = STAmount (node.issue_); node.saFwdDeliver = node.saRevDeliver; if (!isConsistent (node.issue_)) { JLOG (j_.debug) << "pushNode: currency is inconsistent with issuer."; resultCode = temBAD_PATH; } else if (backNode.issue_ == node.issue_) { JLOG (j_.debug) << "pushNode: bad path: offer to same currency and issuer"; resultCode = temBAD_PATH; } else { JLOG (j_.trace) << "pushNode: imply for offer."; // Insert intermediary issuer account if needed. resultCode = pushImpliedNodes ( xrpAccount(), // Rippling, but offers don't have an account. backNode.issue_.currency, backNode.issue_.account); } if (resultCode == tesSUCCESS) nodes_.push_back (node); } JLOG (j_.trace) << "pushNode< : " << transToken (resultCode); return resultCode; }
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; }
// account_dividend [account] Json::Value doAccountDividend (RPC::Context& context) { auto const& params (context.params); if (! params.isMember (jss::account)) return RPC::missing_field_error (jss::account); std::shared_ptr<ReadView const> ledger; auto result = RPC::lookupLedger (ledger, context); if (! ledger) return result; std::string strIdent (params[jss::account].asString ()); AccountID accountID; if (auto jv = RPC::accountFromString (accountID, strIdent)) { for (auto it = jv.begin (); it != jv.end (); ++it) result[it.memberName ()] = it.key (); return result; } auto accountSLE = ledger->read (keylet::account (accountID)); if (!accountSLE) return rpcError (rpcACT_NOT_FOUND); result[jss::account] = context.app.accountIDCache ().toBase58 (accountID); std::uint32_t baseLedgerSeq = 0; auto dividendSLE = ledger->read (keylet::dividend ()); if (dividendSLE) { if (dividendSLE->getFieldU8 (sfDividendState) != DividendMaster::DivState_Done) { return RPC::make_error (rpcNOT_READY, "Dividend in progress"); } Json::Value resumeToken; baseLedgerSeq = dividendSLE->getFieldU32 (sfDividendLedger); auto txns = context.netOps.getTxsAccount ( accountID, baseLedgerSeq, ledger->info ().seq, false, resumeToken, 1, context.role == Role::ADMIN, "Dividend"); if (!txns.empty ()) { auto& txn = txns.begin ()->first->getSTransaction (); result["DividendCoins"] = to_string (txn->getFieldU64 (sfDividendCoins)); result["DividendCoinsVBC"] = to_string (txn->getFieldU64 (sfDividendCoinsVBC)); result["DividendCoinsVBCRank"] = to_string (txn->getFieldU64 (sfDividendCoinsVBCRank)); result["DividendCoinsVBCSprd"] = to_string (txn->getFieldU64 (sfDividendCoinsVBCSprd)); result["DividendTSprd"] = to_string (txn->getFieldU64 (sfDividendTSprd)); result["DividendVRank"] = to_string (txn->getFieldU64 (sfDividendVRank)); result["DividendVSprd"] = to_string (txn->getFieldU64 (sfDividendVSprd)); result["DividendLedger"] = to_string (txn->getFieldU32 (sfDividendLedger)); return result; } } result["DividendCoins"] = "0"; result["DividendCoinsVBC"] = "0"; result["DividendCoinsVBCRank"] = "0"; result["DividendCoinsVBCSprd"] = "0"; result["DividendTSprd"] = "0"; result["DividendVRank"] = "0"; result["DividendVSprd"] = "0"; result["DividendLedger"] = to_string (baseLedgerSeq); return result; }
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; }
NotTEC Transactor::checkMultiSign (PreclaimContext const& ctx) { auto const id = ctx.tx.getAccountID(sfAccount); // Get mTxnAccountID's SignerList and Quorum. std::shared_ptr<STLedgerEntry const> sleAccountSigners = ctx.view.read (keylet::signers(id)); // If the signer list doesn't exist the account is not multi-signing. if (!sleAccountSigners) { JLOG(ctx.j.trace()) << "applyTransaction: Invalid: Not a multi-signing account."; return tefNOT_MULTI_SIGNING; } // We have plans to support multiple SignerLists in the future. The // presence and defaulted value of the SignerListID field will enable that. assert (sleAccountSigners->isFieldPresent (sfSignerListID)); assert (sleAccountSigners->getFieldU32 (sfSignerListID) == 0); auto accountSigners = SignerEntries::deserialize (*sleAccountSigners, ctx.j, "ledger"); if (accountSigners.second != tesSUCCESS) return accountSigners.second; // Get the array of transaction signers. STArray const& txSigners (ctx.tx.getFieldArray (sfSigners)); // Walk the accountSigners performing a variety of checks and see if // the quorum is met. // Both the multiSigners and accountSigners are sorted by account. So // matching multi-signers to account signers should be a simple // linear walk. *All* signers must be valid or the transaction fails. std::uint32_t weightSum = 0; auto iter = accountSigners.first.begin (); for (auto const& txSigner : txSigners) { AccountID const txSignerAcctID = txSigner.getAccountID (sfAccount); // Attempt to match the SignerEntry with a Signer; while (iter->account < txSignerAcctID) { if (++iter == accountSigners.first.end ()) { JLOG(ctx.j.trace()) << "applyTransaction: Invalid SigningAccount.Account."; return tefBAD_SIGNATURE; } } if (iter->account != txSignerAcctID) { // The SigningAccount is not in the SignerEntries. JLOG(ctx.j.trace()) << "applyTransaction: Invalid SigningAccount.Account."; return tefBAD_SIGNATURE; } // We found the SigningAccount in the list of valid signers. Now we // need to compute the accountID that is associated with the signer's // public key. auto const spk = txSigner.getFieldVL (sfSigningPubKey); if (!publicKeyType (makeSlice(spk))) { JLOG(ctx.j.trace()) << "checkMultiSign: signing public key type is unknown"; return tefBAD_SIGNATURE; } AccountID const signingAcctIDFromPubKey = calcAccountID(PublicKey (makeSlice(spk))); // Verify that the signingAcctID and the signingAcctIDFromPubKey // belong together. Here is are the rules: // // 1. "Phantom account": an account that is not in the ledger // A. If signingAcctID == signingAcctIDFromPubKey and the // signingAcctID is not in the ledger then we have a phantom // account. // B. Phantom accounts are always allowed as multi-signers. // // 2. "Master Key" // A. signingAcctID == signingAcctIDFromPubKey, and signingAcctID // is in the ledger. // B. If the signingAcctID in the ledger does not have the // asfDisableMaster flag set, then the signature is allowed. // // 3. "Regular Key" // A. signingAcctID != signingAcctIDFromPubKey, and signingAcctID // is in the ledger. // B. If signingAcctIDFromPubKey == signingAcctID.RegularKey (from // ledger) then the signature is allowed. // // No other signatures are allowed. (January 2015) // In any of these cases we need to know whether the account is in // the ledger. Determine that now. auto sleTxSignerRoot = ctx.view.read (keylet::account(txSignerAcctID)); if (signingAcctIDFromPubKey == txSignerAcctID) { // Either Phantom or Master. Phantoms automatically pass. if (sleTxSignerRoot) { // Master Key. Account may not have asfDisableMaster set. std::uint32_t const signerAccountFlags = sleTxSignerRoot->getFieldU32 (sfFlags); if (signerAccountFlags & lsfDisableMaster) { JLOG(ctx.j.trace()) << "applyTransaction: Signer:Account lsfDisableMaster."; return tefMASTER_DISABLED; } } } else { // May be a Regular Key. Let's find out. // Public key must hash to the account's regular key. if (!sleTxSignerRoot) { JLOG(ctx.j.trace()) << "applyTransaction: Non-phantom signer lacks account root."; return tefBAD_SIGNATURE; } if (!sleTxSignerRoot->isFieldPresent (sfRegularKey)) { JLOG(ctx.j.trace()) << "applyTransaction: Account lacks RegularKey."; return tefBAD_SIGNATURE; } if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID (sfRegularKey)) { JLOG(ctx.j.trace()) << "applyTransaction: Account doesn't match RegularKey."; return tefBAD_SIGNATURE; } } // The signer is legitimate. Add their weight toward the quorum. weightSum += iter->weight; } // Cannot perform transaction if quorum is not met. if (weightSum < sleAccountSigners->getFieldU32 (sfSignerQuorum)) { JLOG(ctx.j.trace()) << "applyTransaction: Signers failed to meet quorum."; return tefBAD_QUORUM; } // Met the quorum. Continue. return tesSUCCESS; }
std::uint32_t STValidation::getFlags () const { return getFieldU32 (sfFlags); }
TER SetAccount::doApply () { std::uint32_t const uTxFlags = ctx_.tx.getFlags (); auto const sle = view().peek( keylet::account(account_)); std::uint32_t const uFlagsIn = sle->getFieldU32 (sfFlags); std::uint32_t uFlagsOut = uFlagsIn; std::uint32_t const uSetFlag = ctx_.tx.getFieldU32 (sfSetFlag); std::uint32_t const uClearFlag = ctx_.tx.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); bool sigWithMaster = false; { auto const blob = ctx_.tx.getSigningPubKey(); if (!blob.empty ()) { auto const signingPubKey = RippleAddress::createAccountPublic(blob); if (calcAccountID(signingPubKey) == account_) sigWithMaster = true; } } // // RequireAuth // if (bSetRequireAuth && !(uFlagsIn & lsfRequireAuth)) { j_.trace << "Set RequireAuth."; uFlagsOut |= lsfRequireAuth; } if (bClearRequireAuth && (uFlagsIn & lsfRequireAuth)) { j_.trace << "Clear RequireAuth."; uFlagsOut &= ~lsfRequireAuth; } // // RequireDestTag // if (bSetRequireDest && !(uFlagsIn & lsfRequireDestTag)) { j_.trace << "Set lsfRequireDestTag."; uFlagsOut |= lsfRequireDestTag; } if (bClearRequireDest && (uFlagsIn & lsfRequireDestTag)) { j_.trace << "Clear lsfRequireDestTag."; uFlagsOut &= ~lsfRequireDestTag; } // // DisallowXRP // if (bSetDisallowXRP && !(uFlagsIn & lsfDisallowXRP)) { j_.trace << "Set lsfDisallowXRP."; uFlagsOut |= lsfDisallowXRP; } if (bClearDisallowXRP && (uFlagsIn & lsfDisallowXRP)) { j_.trace << "Clear lsfDisallowXRP."; uFlagsOut &= ~lsfDisallowXRP; } // // DisableMaster // if ((uSetFlag == asfDisableMaster) && !(uFlagsIn & lsfDisableMaster)) { if (!sigWithMaster) { j_.trace << "Must use master key to disable master key."; return tecNEED_MASTER_KEY; } if ((!sle->isFieldPresent (sfRegularKey)) && (!view().peek (keylet::signers (account_)))) { // Account has no regular key or multi-signer signer list. // Prevent transaction changes until we're ready. if (view().rules().enabled(featureMultiSign, ctx_.app.config().features)) return tecNO_ALTERNATIVE_KEY; return tecNO_REGULAR_KEY; } j_.trace << "Set lsfDisableMaster."; uFlagsOut |= lsfDisableMaster; } if ((uClearFlag == asfDisableMaster) && (uFlagsIn & lsfDisableMaster)) { j_.trace << "Clear lsfDisableMaster."; uFlagsOut &= ~lsfDisableMaster; } // // DefaultRipple // if (uSetFlag == asfDefaultRipple) { uFlagsOut |= lsfDefaultRipple; } else if (uClearFlag == asfDefaultRipple) { uFlagsOut &= ~lsfDefaultRipple; } // // NoFreeze // if (uSetFlag == asfNoFreeze) { if (!sigWithMaster && !(uFlagsIn & lsfDisableMaster)) { j_.trace << "Can't use regular key to set NoFreeze."; return tecNEED_MASTER_KEY; } j_.trace << "Set NoFreeze flag"; uFlagsOut |= lsfNoFreeze; } // Anyone may set global freeze if (uSetFlag == asfGlobalFreeze) { j_.trace << "Set GlobalFreeze flag"; uFlagsOut |= lsfGlobalFreeze; } // If you have set NoFreeze, you may not clear GlobalFreeze // This prevents those who have set NoFreeze from using // GlobalFreeze strategically. if ((uSetFlag != asfGlobalFreeze) && (uClearFlag == asfGlobalFreeze) && ((uFlagsOut & lsfNoFreeze) == 0)) { j_.trace << "Clear GlobalFreeze flag"; uFlagsOut &= ~lsfGlobalFreeze; } // // Track transaction IDs signed by this account in its root // if ((uSetFlag == asfAccountTxnID) && !sle->isFieldPresent (sfAccountTxnID)) { j_.trace << "Set AccountTxnID"; sle->makeFieldPresent (sfAccountTxnID); } if ((uClearFlag == asfAccountTxnID) && sle->isFieldPresent (sfAccountTxnID)) { j_.trace << "Clear AccountTxnID"; sle->makeFieldAbsent (sfAccountTxnID); } // // EmailHash // if (ctx_.tx.isFieldPresent (sfEmailHash)) { uint128 const uHash = ctx_.tx.getFieldH128 (sfEmailHash); if (!uHash) { j_.trace << "unset email hash"; sle->makeFieldAbsent (sfEmailHash); } else { j_.trace << "set email hash"; sle->setFieldH128 (sfEmailHash, uHash); } } // // WalletLocator // if (ctx_.tx.isFieldPresent (sfWalletLocator)) { uint256 const uHash = ctx_.tx.getFieldH256 (sfWalletLocator); if (!uHash) { j_.trace << "unset wallet locator"; sle->makeFieldAbsent (sfWalletLocator); } else { j_.trace << "set wallet locator"; sle->setFieldH256 (sfWalletLocator, uHash); } } // // MessageKey // if (ctx_.tx.isFieldPresent (sfMessageKey)) { Blob const messageKey = ctx_.tx.getFieldVL (sfMessageKey); if (messageKey.empty ()) { j_.debug << "set message key"; sle->makeFieldAbsent (sfMessageKey); } else { j_.debug << "set message key"; sle->setFieldVL (sfMessageKey, messageKey); } } // // Domain // if (ctx_.tx.isFieldPresent (sfDomain)) { Blob const domain = ctx_.tx.getFieldVL (sfDomain); if (domain.empty ()) { j_.trace << "unset domain"; sle->makeFieldAbsent (sfDomain); } else { j_.trace << "set domain"; sle->setFieldVL (sfDomain, domain); } } // // TransferRate // if (ctx_.tx.isFieldPresent (sfTransferRate)) { std::uint32_t uRate = ctx_.tx.getFieldU32 (sfTransferRate); if (uRate == 0 || uRate == QUALITY_ONE) { j_.trace << "unset transfer rate"; sle->makeFieldAbsent (sfTransferRate); } else if (uRate > QUALITY_ONE) { j_.trace << "set transfer rate"; sle->setFieldU32 (sfTransferRate, uRate); } } if (uFlagsIn != uFlagsOut) sle->setFieldU32 (sfFlags, uFlagsOut); return tesSUCCESS; }