bool CreateAccountOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame::pointer destAccount; Database& db = ledgerManager.getDatabase(); destAccount = AccountFrame::loadAccount(mCreateAccount.destination, db); if (!destAccount) { if (mCreateAccount.startingBalance < ledgerManager.getMinBalance(0)) { // not over the minBalance to make an account metrics.NewMeter({"op-create-account", "failure", "low-reserve"}, "operation").Mark(); innerResult().code(CREATE_ACCOUNT_LOW_RESERVE); return false; } else { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if (mSourceAccount->getAccount().balance < (minBalance + mCreateAccount.startingBalance)) { // they don't have enough to send metrics.NewMeter({"op-create-account", "failure", "underfunded"}, "operation").Mark(); innerResult().code(CREATE_ACCOUNT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= mCreateAccount.startingBalance; mSourceAccount->storeChange(delta, db); destAccount = make_shared<AccountFrame>(mCreateAccount.destination); destAccount->getAccount().seqNum = delta.getHeaderFrame().getStartingSequenceNumber(); destAccount->getAccount().balance = mCreateAccount.startingBalance; destAccount->storeAdd(delta, db); metrics.NewMeter({"op-create-account", "success", "apply"}, "operation").Mark(); innerResult().code(CREATE_ACCOUNT_SUCCESS); return true; } } else { metrics.NewMeter({"op-create-account", "failure", "already-exist"}, "operation").Mark(); innerResult().code(CREATE_ACCOUNT_ALREADY_EXIST); return false; } }
void TxSetFrame::surgePricingFilter(LedgerManager const& lm) { size_t max = lm.getMaxTxSetSize(); if (mTransactions.size() > max) { // surge pricing in effect! CLOG(WARNING, "Herder") << "surge pricing in effect! " << mTransactions.size(); // determine the fee ratio for each account map<AccountID, double> accountFeeMap; for (auto& tx : mTransactions) { double r = tx->getFeeRatio(lm); double now = accountFeeMap[tx->getSourceID()]; if (now == 0) accountFeeMap[tx->getSourceID()] = r; else if (r < now) accountFeeMap[tx->getSourceID()] = r; } // sort tx by amount of fee they have paid // remove the bottom that aren't paying enough std::vector<TransactionFramePtr> tempList = mTransactions; std::sort(tempList.begin(), tempList.end(), SurgeSorter(accountFeeMap)); for (auto iter = tempList.begin() + max; iter != tempList.end(); iter++) { removeTx(*iter); } } }
bool CreateAccountOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame destAccount; Database& db = ledgerManager.getDatabase(); if (!AccountFrame::loadAccount(mCreateAccount.destination, destAccount, db)) { if (mCreateAccount.startingBalance < ledgerManager.getMinBalance(0)) { // not over the minBalance to make an account innerResult().code(CREATE_ACCOUNT_LOW_RESERVE); return false; } else { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if (mSourceAccount->getAccount().balance < (minBalance + mCreateAccount.startingBalance)) { // they don't have enough to send innerResult().code(CREATE_ACCOUNT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= mCreateAccount.startingBalance; mSourceAccount->storeChange(delta, db); destAccount.getAccount().accountID = mCreateAccount.destination; destAccount.getAccount().seqNum = delta.getHeaderFrame().getStartingSequenceNumber(); destAccount.getAccount().balance = mCreateAccount.startingBalance; destAccount.storeAdd(delta, db); innerResult().code(CREATE_ACCOUNT_SUCCESS); return true; } } else { innerResult().code(CREATE_ACCOUNT_ALREADY_EXIST); return false; } }
bool ManageDataOpFrame::doApply(Application& app, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); auto dataFrame = DataFrame::loadData(mSourceAccount->getID(), mManageData.dataName, db); if(mManageData.dataValue) { if(!dataFrame) { // create a new data entry if(!mSourceAccount->addNumEntries(1, ledgerManager)) { app.getMetrics().NewMeter({ "op-manage-data", "invalid", "low reserve" }, "operation").Mark(); innerResult().code(MANAGE_DATA_LOW_RESERVE); return false; } dataFrame= std::make_shared<DataFrame>(); dataFrame->getData().accountID= mSourceAccount->getID(); dataFrame->getData().dataName = mManageData.dataName; dataFrame->getData().dataValue = *mManageData.dataValue; dataFrame->storeAdd(delta, db); mSourceAccount->storeChange(delta, db); } else { // modify an existing entry dataFrame->getData().dataValue = *mManageData.dataValue; dataFrame->storeChange(delta, db); } } else { // delete an existing piece of data if(!dataFrame) { app.getMetrics().NewMeter({ "op-manage-data", "invalid", "not-found" }, "operation").Mark(); innerResult().code(MANAGE_DATA_NAME_NOT_FOUND); return false; } mSourceAccount->addNumEntries(-1, ledgerManager); mSourceAccount->storeChange(delta, db); dataFrame->storeDelete(delta, db); } innerResult().code(MANAGE_DATA_SUCCESS); app.getMetrics().NewMeter({"op-manage-data", "success", "apply"}, "operation") .Mark(); return true; }
void TransactionFrame::storeTransaction(LedgerManager& ledgerManager, LedgerDelta const& delta, int txindex, SHA256& resultHasher) const { auto txBytes(xdr::xdr_to_opaque(mEnvelope)); auto txResultBytes(xdr::xdr_to_opaque(getResultPair())); resultHasher.add(txResultBytes); std::string txBody = base64::encode( reinterpret_cast<const unsigned char*>(txBytes.data()), txBytes.size()); std::string txResult = base64::encode( reinterpret_cast<const unsigned char*>(txResultBytes.data()), txResultBytes.size()); xdr::opaque_vec<> txMeta(delta.getTransactionMeta()); std::string meta = base64::encode( reinterpret_cast<const unsigned char*>(txMeta.data()), txMeta.size()); string txIDString(binToHex(getContentsHash())); auto timer = ledgerManager.getDatabase().getInsertTimer("txhistory"); soci::statement st = (ledgerManager.getDatabase().getSession().prepare << "INSERT INTO txhistory (txid, ledgerseq, txindex, txbody, " "txresult, txmeta) VALUES " "(:id,:seq,:txindex,:txb,:txres,:meta)", soci::use(txIDString), soci::use(ledgerManager.getCurrentLedgerHeader().ledgerSeq), soci::use(txindex), soci::use(txBody), soci::use(txResult), soci::use(meta)); st.execute(true); if (st.get_affected_rows() != 1) { throw std::runtime_error("Could not update data in SQL"); } }
bool PaymentOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame destAccount; // if sending to self directly, just mark as success if (mPayment.destination == getSourceID() && mPayment.path.empty()) { innerResult().code(PAYMENT_SUCCESS); return true; } Database& db = ledgerManager.getDatabase(); if (!AccountFrame::loadAccount(mPayment.destination, destAccount, db)) { // this tx is creating an account if (mPayment.currency.type() == CURRENCY_TYPE_NATIVE) { if (mPayment.amount < ledgerManager.getMinBalance(0)) { // not over the minBalance to make an account innerResult().code(PAYMENT_LOW_RESERVE); return false; } else { destAccount.getAccount().accountID = mPayment.destination; destAccount.getAccount().seqNum = delta.getHeaderFrame().getStartingSequenceNumber(); destAccount.getAccount().balance = 0; destAccount.storeAdd(delta, db); } } else { // trying to send credit to an unmade account innerResult().code(PAYMENT_NO_DESTINATION); return false; } } return sendNoCreate(destAccount, delta, ledgerManager); }
// make sure the deleted Account hasn't issued credit // make sure we aren't holding any credit // make sure the we delete all the offers // make sure the we delete all the trustlines // move the XLM to the new account bool MergeOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame::pointer otherAccount; Database& db = ledgerManager.getDatabase(); otherAccount = AccountFrame::loadAccount(mOperation.body.destination(), db); if (!otherAccount) { innerResult().code(ACCOUNT_MERGE_NO_ACCOUNT); return false; } if (TrustFrame::hasIssued(getSourceID(), db)) { innerResult().code(ACCOUNT_MERGE_CREDIT_HELD); return false; } std::vector<TrustFrame::pointer> lines; TrustFrame::loadLines(getSourceID(), lines, db); for(auto &l : lines) { if(l->getBalance() > 0) { innerResult().code(ACCOUNT_MERGE_HAS_CREDIT); return false; } } // delete offers std::vector<OfferFrame::pointer> offers; OfferFrame::loadOffers(getSourceID(), offers, db); for (auto& offer : offers) { offer->storeDelete(delta, db); } // delete trust lines for (auto& l : lines) { l->storeDelete(delta, db); } otherAccount->getAccount().balance += mSourceAccount->getAccount().balance; otherAccount->storeChange(delta, db); mSourceAccount->storeDelete(delta, db); innerResult().code(ACCOUNT_MERGE_SUCCESS); return true; }
int64_t AccountFrame::getBalanceAboveReserve(LedgerManager const& lm) const { int64_t avail = getBalance() - lm.getMinBalance(mAccountEntry.numSubEntries); if (avail < 0) { // nothing can leave this account if below the reserve // (this can happen if the reserve is raised) avail = 0; } return avail; }
// returns true if successfully updated, // false if balance is not sufficient bool AccountFrame::addNumEntries(int count, LedgerManager const& lm) { int newEntriesCount = mAccountEntry.numSubEntries + count; if (newEntriesCount < 0) { throw std::runtime_error("invalid account state"); } if (getBalance() < lm.getMinBalance(newEntriesCount)) { // balance too low return false; } mAccountEntry.numSubEntries = newEntriesCount; return true; }
void LedgerHeaderFrame::storeInsert(LedgerManager& ledgerManager) const { if (!isValid(mHeader)) { throw std::runtime_error("invalid ledger header (insert)"); } getHash(); string hash(binToHex(mHash)), prevHash(binToHex(mHeader.previousLedgerHash)), bucketListHash(binToHex(mHeader.bucketListHash)); auto headerBytes(xdr::xdr_to_opaque(mHeader)); std::string headerEncoded; headerEncoded = decoder::encode_b64(headerBytes); auto& db = ledgerManager.getDatabase(); // note: columns other than "data" are there to faciliate lookup/processing auto prep = db.getPreparedStatement( "INSERT INTO ledgerheaders " "(ledgerhash, prevhash, bucketlisthash, ledgerseq, closetime, data) " "VALUES " "(:h, :ph, :blh, :seq, :ct, :data)"); auto& st = prep.statement(); st.exchange(use(hash)); st.exchange(use(prevHash)); st.exchange(use(bucketListHash)); st.exchange(use(mHeader.ledgerSeq)); st.exchange(use(mHeader.scpValue.closeTime)); st.exchange(use(headerEncoded)); st.define_and_bind(); { auto timer = db.getInsertTimer("ledger-header"); st.execute(true); } if (st.get_affected_rows() != 1) { throw std::runtime_error("Could not update data in SQL"); } }
void TransactionFrame::prepareResult(LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); int64_t fee = getResult().feeCharged; if (fee > 0) { int64_t avail = mSigningAccount->getAccount().balance; if (avail < fee) { // take all their balance to be safe fee = avail; } mSigningAccount->setSeqNum(mEnvelope.tx.seqNum); mSigningAccount->getAccount().balance -= fee; delta.getHeader().feePool += fee; mSigningAccount->storeChange(delta, db); } }
bool AllowTrustOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { if (!(mSourceAccount->getAccount().flags & AUTH_REQUIRED_FLAG)) { // this account doesn't require authorization to hold credit innerResult().code(ALLOW_TRUST_TRUST_NOT_REQUIRED); return false; } if (!(mSourceAccount->getAccount().flags & AUTH_REVOCABLE_FLAG) && !mAllowTrust.authorize) { innerResult().code(ALLOW_TRUST_CANT_REVOKE); return false; } Currency ci; ci.type(CURRENCY_TYPE_ALPHANUM); ci.alphaNum().currencyCode = mAllowTrust.currency.currencyCode(); ci.alphaNum().issuer = getSourceID(); Database& db = ledgerManager.getDatabase(); TrustFrame::pointer trustLine; trustLine = TrustFrame::loadTrustLine(mAllowTrust.trustor, ci, db); if (!trustLine) { innerResult().code(ALLOW_TRUST_NO_TRUST_LINE); return false; } innerResult().code(ALLOW_TRUST_SUCCESS); trustLine->setAuthorized(mAllowTrust.authorize); trustLine->storeChange(delta, db); return true; }
bool PathPaymentOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame::pointer destination; Database& db = ledgerManager.getDatabase(); destination = AccountFrame::loadAccount(mPathPayment.destination, db); if (!destination) { metrics.NewMeter({"op-path-payment", "failure", "no-destination"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_DESTINATION); return false; } innerResult().code(PATH_PAYMENT_SUCCESS); // tracks the last amount that was traded int64_t curBReceived = mPathPayment.destAmount; Asset curB = mPathPayment.destAsset; // update balances, walks backwards // build the full path to the destination, starting with sendAsset std::vector<Asset> fullPath; fullPath.emplace_back(mPathPayment.sendAsset); fullPath.insert(fullPath.end(), mPathPayment.path.begin(), mPathPayment.path.end()); // update last balance in the chain { if (curB.type() == ASSET_TYPE_NATIVE) { destination->getAccount().balance += curBReceived; destination->storeChange(delta, db); } else { TrustFrame::pointer destLine; destLine = TrustFrame::loadTrustLine(destination->getID(), curB, db); if (!destLine) { metrics.NewMeter({"op-path-payment", "failure", "no-trust"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_TRUST); return false; } if (!destLine->isAuthorized()) { metrics.NewMeter( {"op-path-payment", "failure", "not-authorized"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NOT_AUTHORIZED); return false; } if (!destLine->addBalance(curBReceived)) { metrics.NewMeter({"op-path-payment", "failure", "line-full"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_LINE_FULL); return false; } destLine->storeChange(delta, db); } innerResult().success().last = SimplePaymentResult(destination->getID(), curB, curBReceived); } // now, walk the path backwards for (int i = (int)fullPath.size() - 1; i >= 0; i--) { int64_t curASent, actualCurBReceived; Asset const& curA = fullPath[i]; if (curA == curB) { continue; } OfferExchange oe(delta, ledgerManager); // curA -> curB OfferExchange::ConvertResult r = oe.convertWithOffers(curA, INT64_MAX, curASent, curB, curBReceived, actualCurBReceived, nullptr); switch (r) { case OfferExchange::eFilterStop: assert(false); // no filter -> should not happen break; case OfferExchange::eOK: if (curBReceived == actualCurBReceived) { break; } // fall through case OfferExchange::ePartial: metrics.NewMeter({"op-path-payment", "failure", "too-few-offers"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_TOO_FEW_OFFERS); return false; } assert(curBReceived == actualCurBReceived); curBReceived = curASent; // next round, we need to send enough curB = curA; // add offers that got taken on the way // insert in front to match the path's order auto& offers = innerResult().success().offers; offers.insert(offers.begin(), oe.getOfferTrail().begin(), oe.getOfferTrail().end()); } // last step: we've reached the first account in the chain, update its // balance int64_t curBSent; curBSent = curBReceived; if (curBSent > mPathPayment.sendMax) { // make sure not over the max metrics.NewMeter({"op-path-payment", "failure", "over-send-max"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_OVER_SENDMAX); return false; } if (curB.type() == ASSET_TYPE_NATIVE) { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if ((mSourceAccount->getAccount().balance - curBSent) < minBalance) { // they don't have enough to send metrics.NewMeter({"op-path-payment", "failure", "underfunded"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= curBSent; mSourceAccount->storeChange(delta, db); } else { AccountFrame::pointer issuer; if(curB.type()==ASSET_TYPE_CREDIT_ALPHANUM4) issuer = AccountFrame::loadAccount(curB.alphaNum4().issuer, db); else if(curB.type()==ASSET_TYPE_CREDIT_ALPHANUM12) issuer = AccountFrame::loadAccount(curB.alphaNum12().issuer, db); if (!issuer) { metrics.NewMeter({"op-path-payment", "failure", "no-issuer"}, "operation").Mark(); throw std::runtime_error("sendCredit Issuer not found"); } TrustFrame::pointer sourceLineFrame; sourceLineFrame = TrustFrame::loadTrustLine(getSourceID(), curB, db); if (!sourceLineFrame) { metrics.NewMeter({"op-path-payment", "failure", "src-no-trust"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_SRC_NO_TRUST); return false; } if (!sourceLineFrame->isAuthorized()) { metrics.NewMeter( {"op-path-payment", "failure", "src-not-authorized"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_SRC_NOT_AUTHORIZED); return false; } if (!sourceLineFrame->addBalance(-curBSent)) { metrics.NewMeter({"op-path-payment", "failure", "underfunded"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_UNDERFUNDED); return false; } sourceLineFrame->storeChange(delta, db); } metrics.NewMeter({"op-path-payment", "success", "apply"}, "operation") .Mark(); return true; }
// you are selling sheep for wheat // need to check the counter offers selling wheat for sheep // see if this is modifying an old offer // see if this offer crosses any existing offers bool ManageOfferOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); if (!checkOfferValid(metrics, db, delta)) { return false; } Asset const& sheep = mManageOffer.selling; Asset const& wheat = mManageOffer.buying; bool creatingNewOffer = false; uint64_t offerID = mManageOffer.offerID; if (offerID) { // modifying an old offer mSellSheepOffer = OfferFrame::loadOffer(getSourceID(), offerID, db, &delta); if (!mSellSheepOffer) { metrics.NewMeter({"op-manage-offer", "invalid", "not-found"}, "operation").Mark(); innerResult().code(MANAGE_OFFER_NOT_FOUND); return false; } // rebuild offer based off the manage offer mSellSheepOffer->getOffer() = buildOffer( getSourceID(), mManageOffer, mSellSheepOffer->getOffer().flags); mPassive = mSellSheepOffer->getFlags() & PASSIVE_FLAG; } else { // creating a new Offer creatingNewOffer = true; LedgerEntry le; le.data.type(OFFER); le.data.offer() = buildOffer(getSourceID(), mManageOffer, mPassive ? PASSIVE_FLAG : 0); mSellSheepOffer = std::make_shared<OfferFrame>(le); } int64_t maxSheepSend = mSellSheepOffer->getAmount(); int64_t maxAmountOfSheepCanSell; innerResult().code(MANAGE_OFFER_SUCCESS); soci::transaction sqlTx(db.getSession()); LedgerDelta tempDelta(delta); if (mManageOffer.amount == 0) { mSellSheepOffer->getOffer().amount = 0; } else { if (sheep.type() == ASSET_TYPE_NATIVE) { maxAmountOfSheepCanSell = mSourceAccount->getBalanceAboveReserve(ledgerManager); } else { maxAmountOfSheepCanSell = mSheepLineA->getBalance(); } // the maximum is defined by how much wheat it can receive int64_t maxWheatCanSell; if (wheat.type() == ASSET_TYPE_NATIVE) { maxWheatCanSell = INT64_MAX; } else { maxWheatCanSell = mWheatLineA->getMaxAmountReceive(); if (maxWheatCanSell == 0) { metrics.NewMeter({"op-manage-offer", "invalid", "line-full"}, "operation").Mark(); innerResult().code(MANAGE_OFFER_LINE_FULL); return false; } } Price const& sheepPrice = mSellSheepOffer->getPrice(); { int64_t maxSheepBasedOnWheat; if (!bigDivide(maxSheepBasedOnWheat, maxWheatCanSell, sheepPrice.d, sheepPrice.n)) { maxSheepBasedOnWheat = INT64_MAX; } if (maxAmountOfSheepCanSell > maxSheepBasedOnWheat) { maxAmountOfSheepCanSell = maxSheepBasedOnWheat; } } // amount of sheep for sale is the lesser of amount we can sell and // amount // put in the offer if (maxAmountOfSheepCanSell < maxSheepSend) { maxSheepSend = maxAmountOfSheepCanSell; } int64_t sheepSent, wheatReceived; OfferExchange oe(tempDelta, ledgerManager); const Price maxWheatPrice(sheepPrice.d, sheepPrice.n); OfferExchange::ConvertResult r = oe.convertWithOffers( sheep, maxSheepSend, sheepSent, wheat, maxWheatCanSell, wheatReceived, [this, &maxWheatPrice](OfferFrame const& o) { if (o.getOfferID() == mSellSheepOffer->getOfferID()) { // don't let the offer cross itself when updating it return OfferExchange::eSkip; } if ((mPassive && (o.getPrice() >= maxWheatPrice)) || (o.getPrice() > maxWheatPrice)) { return OfferExchange::eStop; } if (o.getSellerID() == getSourceID()) { // we are crossing our own offer innerResult().code(MANAGE_OFFER_CROSS_SELF); return OfferExchange::eStop; } return OfferExchange::eKeep; }); switch (r) { case OfferExchange::eOK: case OfferExchange::ePartial: break; case OfferExchange::eFilterStop: if (innerResult().code() != MANAGE_OFFER_SUCCESS) { return false; } break; } // updates the result with the offers that got taken on the way for (auto const& oatom : oe.getOfferTrail()) { innerResult().success().offersClaimed.push_back(oatom); } if (wheatReceived > 0) { // it's OK to use mSourceAccount, mWheatLineA and mSheepLineA // here as OfferExchange won't cross offers from source account if (wheat.type() == ASSET_TYPE_NATIVE) { mSourceAccount->getAccount().balance += wheatReceived; mSourceAccount->storeChange(delta, db); } else { if (!mWheatLineA->addBalance(wheatReceived)) { // this would indicate a bug in OfferExchange throw std::runtime_error("offer claimed over limit"); } mWheatLineA->storeChange(delta, db); } if (sheep.type() == ASSET_TYPE_NATIVE) { mSourceAccount->getAccount().balance -= sheepSent; mSourceAccount->storeChange(delta, db); } else { if (!mSheepLineA->addBalance(-sheepSent)) { // this would indicate a bug in OfferExchange throw std::runtime_error("offer sold more than balance"); } mSheepLineA->storeChange(delta, db); } } // recomputes the amount of sheep for sale mSellSheepOffer->getOffer().amount = maxSheepSend - sheepSent; } if (mSellSheepOffer->getOffer().amount > 0) { // we still have sheep to sell so leave an offer if (creatingNewOffer) { // make sure we don't allow us to add offers when we don't have // the minbalance if (!mSourceAccount->addNumEntries(1, ledgerManager)) { metrics.NewMeter({"op-manage-offer", "invalid", "low reserve"}, "operation").Mark(); innerResult().code(MANAGE_OFFER_LOW_RESERVE); return false; } mSellSheepOffer->mEntry.data.offer().offerID = tempDelta.getHeaderFrame().generateID(); innerResult().success().offer.effect(MANAGE_OFFER_CREATED); mSellSheepOffer->storeAdd(tempDelta, db); mSourceAccount->storeChange(tempDelta, db); } else { innerResult().success().offer.effect(MANAGE_OFFER_UPDATED); mSellSheepOffer->storeChange(tempDelta, db); } innerResult().success().offer.offer() = mSellSheepOffer->getOffer(); } else { innerResult().success().offer.effect(MANAGE_OFFER_DELETED); if (!creatingNewOffer) { mSellSheepOffer->storeDelete(tempDelta, db); mSourceAccount->addNumEntries(-1, ledgerManager); mSourceAccount->storeChange(tempDelta, db); } } sqlTx.commit(); tempDelta.commit(); metrics.NewMeter({"op-create-offer", "success", "apply"}, "operation") .Mark(); return true; }
bool PaymentOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame destination; // if sending to self directly, just mark as success if (mPayment.destination == getSourceID()) { innerResult().code(PAYMENT_SUCCESS); return true; } Database& db = ledgerManager.getDatabase(); if (!AccountFrame::loadAccount(mPayment.destination, destination, db)) { innerResult().code(PAYMENT_NO_DESTINATION); return false; } // build a pathPaymentOp Operation op; op.sourceAccount = mOperation.sourceAccount; op.body.type(PATH_PAYMENT); PathPaymentOp& ppOp = op.body.pathPaymentOp(); ppOp.sendCurrency = mPayment.currency; ppOp.destCurrency = mPayment.currency; ppOp.destAmount = mPayment.amount; ppOp.sendMax = mPayment.amount; ppOp.destination = mPayment.destination; OperationResult opRes; opRes.code(opINNER); opRes.tr().type(PATH_PAYMENT); PathPaymentOpFrame ppayment(op, opRes, mParentTx); ppayment.setSourceAccountPtr(mSourceAccount); if (!ppayment.doCheckValid() || !ppayment.doApply(delta, ledgerManager)) { if (ppayment.getResultCode() != opINNER) { throw std::runtime_error("Unexpected error code from pathPayment"); } PaymentResultCode res; switch (PathPaymentOpFrame::getInnerCode(ppayment.getResult())) { case PATH_PAYMENT_UNDERFUNDED: res = PAYMENT_UNDERFUNDED; break; case PATH_PAYMENT_NO_DESTINATION: res = PAYMENT_NO_DESTINATION; break; case PATH_PAYMENT_NO_TRUST: res = PAYMENT_NO_TRUST; break; case PATH_PAYMENT_NOT_AUTHORIZED: res = PAYMENT_NOT_AUTHORIZED; break; case PATH_PAYMENT_LINE_FULL: res = PAYMENT_LINE_FULL; break; default: throw std::runtime_error("Unexpected error code from pathPayment"); } innerResult().code(res); return false; } assert(PathPaymentOpFrame::getInnerCode(ppayment.getResult()) == PATH_PAYMENT_SUCCESS); innerResult().code(PAYMENT_SUCCESS); return true; }
bool AllowTrustOpFrame::doApply(Application& app, LedgerDelta& delta, LedgerManager& ledgerManager) { if (ledgerManager.getCurrentLedgerVersion() > 2) { if (mAllowTrust.trustor == getSourceID()) { // since version 3 it is not // allowed to use ALLOW_TRUST on // self app.getMetrics() .NewMeter({"op-allow-trust", "failure", "trust-self"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_SELF_NOT_ALLOWED); return false; } } if (!(mSourceAccount->getAccount().flags & AUTH_REQUIRED_FLAG)) { // this account doesn't require authorization to // hold credit app.getMetrics() .NewMeter({"op-allow-trust", "failure", "not-required"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_TRUST_NOT_REQUIRED); return false; } if (!(mSourceAccount->getAccount().flags & AUTH_REVOCABLE_FLAG) && !mAllowTrust.authorize) { app.getMetrics() .NewMeter({"op-allow-trust", "failure", "cant-revoke"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_CANT_REVOKE); return false; } Asset ci; ci.type(mAllowTrust.asset.type()); if (mAllowTrust.asset.type() == ASSET_TYPE_CREDIT_ALPHANUM4) { ci.alphaNum4().assetCode = mAllowTrust.asset.assetCode4(); ci.alphaNum4().issuer = getSourceID(); } else if (mAllowTrust.asset.type() == ASSET_TYPE_CREDIT_ALPHANUM12) { ci.alphaNum12().assetCode = mAllowTrust.asset.assetCode12(); ci.alphaNum12().issuer = getSourceID(); } Database& db = ledgerManager.getDatabase(); TrustFrame::pointer trustLine; trustLine = TrustFrame::loadTrustLine(mAllowTrust.trustor, ci, db, &delta); if (!trustLine) { app.getMetrics() .NewMeter({"op-allow-trust", "failure", "no-trust-line"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_NO_TRUST_LINE); return false; } app.getMetrics() .NewMeter({"op-allow-trust", "success", "apply"}, "operation") .Mark(); innerResult().code(ALLOW_TRUST_SUCCESS); trustLine->setAuthorized(mAllowTrust.authorize); trustLine->storeChange(delta, db); return true; }
bool ChangeTrustOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); auto tlI = TrustFrame::loadTrustLineIssuer(getSourceID(), mChangeTrust.line, db, delta); auto& trustLine = tlI.first; auto& issuer = tlI.second; if (trustLine) { // we are modifying an old trustline if (mChangeTrust.limit < trustLine->getBalance()) { // Can't drop the limit below the balance you are holding with them metrics.NewMeter({"op-change-trust", "failure", "invalid-limit"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_INVALID_LIMIT); return false; } if (mChangeTrust.limit == 0) { // line gets deleted trustLine->storeDelete(delta, db); mSourceAccount->addNumEntries(-1, ledgerManager); mSourceAccount->storeChange(delta, db); } else { if (!issuer) { metrics.NewMeter({"op-change-trust", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_NO_ISSUER); return false; } trustLine->getTrustLine().limit = mChangeTrust.limit; trustLine->storeChange(delta, db); } metrics.NewMeter({"op-change-trust", "success", "apply"}, "operation") .Mark(); innerResult().code(CHANGE_TRUST_SUCCESS); return true; } else { // new trust line if (mChangeTrust.limit == 0) { metrics.NewMeter({"op-change-trust", "failure", "invalid-limit"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_INVALID_LIMIT); return false; } if (!issuer) { metrics.NewMeter({"op-change-trust", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_NO_ISSUER); return false; } trustLine = std::make_shared<TrustFrame>(); auto& tl = trustLine->getTrustLine(); tl.accountID = getSourceID(); tl.asset = mChangeTrust.line; tl.limit = mChangeTrust.limit; tl.balance = 0; trustLine->setAuthorized(!issuer->isAuthRequired()); if (!mSourceAccount->addNumEntries(1, ledgerManager)) { metrics.NewMeter({"op-change-trust", "failure", "low-reserve"}, "operation").Mark(); innerResult().code(CHANGE_TRUST_LOW_RESERVE); return false; } mSourceAccount->storeChange(delta, db); trustLine->storeAdd(delta, db); metrics.NewMeter({"op-change-trust", "success", "apply"}, "operation") .Mark(); innerResult().code(CHANGE_TRUST_SUCCESS); return true; } }
int64_t AccountFrame::getMinimumBalance(LedgerManager const& lm) const { return lm.getMinBalance(mAccountEntry.numSubEntries); }
// work backward to determine how much they need to send to get the // specified amount of currency to the recipient bool PaymentOpFrame::sendNoCreate(AccountFrame& destination, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); bool multi_mode = mPayment.path.size() != 0; if (multi_mode) { innerResult().code(PAYMENT_SUCCESS_MULTI); } else { innerResult().code(PAYMENT_SUCCESS); } // tracks the last amount that was traded int64_t curBReceived = mPayment.amount; Currency curB = mPayment.currency; // update balances, walks backwards // update last balance in the chain { if (curB.type() == CURRENCY_TYPE_NATIVE) { destination.getAccount().balance += curBReceived; destination.storeChange(delta, db); } else { TrustFrame destLine; if (!TrustFrame::loadTrustLine(destination.getID(), curB, destLine, db)) { innerResult().code(PAYMENT_NO_TRUST); return false; } if (!destLine.isAuthorized()) { innerResult().code(PAYMENT_NOT_AUTHORIZED); return false; } if (!destLine.addBalance(curBReceived)) { innerResult().code(PAYMENT_LINE_FULL); return false; } destLine.storeChange(delta, db); } if (multi_mode) { innerResult().multi().last = SimplePaymentResult(destination.getID(), curB, curBReceived); } } if (multi_mode) { // now, walk the path backwards for (int i = (int)mPayment.path.size() - 1; i >= 0; i--) { int64_t curASent, actualCurBReceived; Currency const& curA = mPayment.path[i]; OfferExchange oe(delta, ledgerManager); // curA -> curB OfferExchange::ConvertResult r = oe.convertWithOffers(curA, INT64_MAX, curASent, curB, curBReceived, actualCurBReceived, nullptr); switch (r) { case OfferExchange::eFilterStop: assert(false); // no filter -> should not happen break; case OfferExchange::eOK: if (curBReceived == actualCurBReceived) { break; } // fall through case OfferExchange::ePartial: innerResult().code(PAYMENT_TOO_FEW_OFFERS); return false; } assert(curBReceived == actualCurBReceived); curBReceived = curASent; // next round, we need to send enough curB = curA; // add offers that got taken on the way // insert in front to match the path's order auto& offers = innerResult().multi().offers; offers.insert(offers.begin(), oe.getOfferTrail().begin(), oe.getOfferTrail().end()); } } // last step: we've reached the first account in the chain, update its // balance int64_t curBSent; curBSent = curBReceived; if (curBSent > mPayment.sendMax) { // make sure not over the max innerResult().code(PAYMENT_OVER_SENDMAX); return false; } if (curB.type() == CURRENCY_TYPE_NATIVE) { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if (mSourceAccount->getAccount().balance < (minBalance + curBSent)) { // they don't have enough to send innerResult().code(PAYMENT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= curBSent; mSourceAccount->storeChange(delta, db); } else { AccountFrame issuer; if (!AccountFrame::loadAccount(curB.alphaNum().issuer, issuer, db)) { throw std::runtime_error("sendCredit Issuer not found"); } TrustFrame sourceLineFrame; if (!TrustFrame::loadTrustLine(getSourceID(), curB, sourceLineFrame, db)) { innerResult().code(PAYMENT_UNDERFUNDED); return false; } if (!sourceLineFrame.addBalance(-curBSent)) { innerResult().code(PAYMENT_UNDERFUNDED); return false; } sourceLineFrame.storeChange(delta, db); } return true; }
bool SetOptionsOpFrame::doApply(Application& app, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); AccountEntry& account = mSourceAccount->getAccount(); if (mSetOptions.inflationDest) { AccountFrame::pointer inflationAccount; AccountID inflationID = *mSetOptions.inflationDest; inflationAccount = AccountFrame::loadAccount(delta, inflationID, db); if (!inflationAccount) { app.getMetrics() .NewMeter({"op-set-options", "failure", "invalid-inflation"}, "operation") .Mark(); innerResult().code(SET_OPTIONS_INVALID_INFLATION); return false; } account.inflationDest.activate() = inflationID; } if (mSetOptions.clearFlags) { if ((*mSetOptions.clearFlags & allAccountAuthFlags) && mSourceAccount->isImmutableAuth()) { app.getMetrics() .NewMeter({"op-set-options", "failure", "cant-change"}, "operation") .Mark(); innerResult().code(SET_OPTIONS_CANT_CHANGE); return false; } account.flags = account.flags & ~*mSetOptions.clearFlags; } if (mSetOptions.setFlags) { if ((*mSetOptions.setFlags & allAccountAuthFlags) && mSourceAccount->isImmutableAuth()) { app.getMetrics() .NewMeter({"op-set-options", "failure", "cant-change"}, "operation") .Mark(); innerResult().code(SET_OPTIONS_CANT_CHANGE); return false; } account.flags = account.flags | *mSetOptions.setFlags; } if (mSetOptions.homeDomain) { account.homeDomain = *mSetOptions.homeDomain; } if (mSetOptions.masterWeight) { account.thresholds[THRESHOLD_MASTER_WEIGHT] = *mSetOptions.masterWeight & UINT8_MAX; } if (mSetOptions.lowThreshold) { account.thresholds[THRESHOLD_LOW] = *mSetOptions.lowThreshold & UINT8_MAX; } if (mSetOptions.medThreshold) { account.thresholds[THRESHOLD_MED] = *mSetOptions.medThreshold & UINT8_MAX; } if (mSetOptions.highThreshold) { account.thresholds[THRESHOLD_HIGH] = *mSetOptions.highThreshold & UINT8_MAX; } if (mSetOptions.signer) { auto& signers = account.signers; if (mSetOptions.signer->weight) { // add or change signer bool found = false; for (auto& oldSigner : signers) { if (oldSigner.key == mSetOptions.signer->key) { oldSigner.weight = mSetOptions.signer->weight; found = true; } } if (!found) { if (signers.size() == signers.max_size()) { app.getMetrics() .NewMeter( {"op-set-options", "failure", "too-many-signers"}, "operation") .Mark(); innerResult().code(SET_OPTIONS_TOO_MANY_SIGNERS); return false; } if (!mSourceAccount->addNumEntries(1, ledgerManager)) { app.getMetrics() .NewMeter({"op-set-options", "failure", "low-reserve"}, "operation") .Mark(); innerResult().code(SET_OPTIONS_LOW_RESERVE); return false; } signers.push_back(*mSetOptions.signer); } } else { // delete signer auto it = signers.begin(); while (it != signers.end()) { Signer& oldSigner = *it; if (oldSigner.key == mSetOptions.signer->key) { it = signers.erase(it); mSourceAccount->addNumEntries(-1, ledgerManager); } else { it++; } } } mSourceAccount->setUpdateSigners(); } app.getMetrics() .NewMeter({"op-set-options", "success", "apply"}, "operation") .Mark(); innerResult().code(SET_OPTIONS_SUCCESS); mSourceAccount->storeChange(delta, db); return true; }
bool InflationOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { LedgerDelta inflationDelta(delta); auto& lcl = inflationDelta.getHeader(); time_t closeTime = lcl.scpValue.closeTime; uint32_t seq = lcl.inflationSeq; time_t inflationTime = (INFLATION_START_TIME + seq * INFLATION_FREQUENCY); if (closeTime < inflationTime) { metrics.NewMeter({"op-inflation", "failure", "not-time"}, "operation") .Mark(); innerResult().code(INFLATION_NOT_TIME); return false; } /* Inflation is calculated using the following 1. calculate tally of votes based on "inflationDest" set on each account 2. take the top accounts (by vote) that get at least .05% of the vote 3. If no accounts are over this threshold then the extra goes back to the inflation pool */ int64_t totalVotes = lcl.totalCoins; int64_t minBalance = bigDivide(totalVotes, INFLATION_WIN_MIN_PERCENT, TRILLION); std::vector<AccountFrame::InflationVotes> winners; auto& db = ledgerManager.getDatabase(); AccountFrame::processForInflation( [&](AccountFrame::InflationVotes const& votes) { if (votes.mVotes >= minBalance) { winners.push_back(votes); return true; } return false; }, INFLATION_NUM_WINNERS, db); int64 amountToDole = bigDivide(lcl.totalCoins, INFLATION_RATE_TRILLIONTHS, TRILLION); amountToDole += lcl.feePool; lcl.feePool = 0; lcl.inflationSeq++; // now credit each account innerResult().code(INFLATION_SUCCESS); auto& payouts = innerResult().payouts(); int64 leftAfterDole = amountToDole; for (auto const& w : winners) { AccountFrame::pointer winner; int64 toDoleThisWinner = bigDivide(amountToDole, w.mVotes, totalVotes); if (toDoleThisWinner == 0) continue; winner = AccountFrame::loadAccount(w.mInflationDest, db); if (winner) { leftAfterDole -= toDoleThisWinner; lcl.totalCoins += toDoleThisWinner; winner->getAccount().balance += toDoleThisWinner; winner->storeChange(inflationDelta, db); payouts.emplace_back(w.mInflationDest, toDoleThisWinner); } } // put back in fee pool as unclaimed funds lcl.feePool += leftAfterDole; inflationDelta.commit(); metrics.NewMeter({"op-inflation", "success", "apply"}, "operation").Mark(); return true; }
bool InflationOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { LedgerDelta inflationDelta(delta); auto& lcl = inflationDelta.getHeader(); time_t closeTime = lcl.scpValue.closeTime; uint32_t seq = lcl.inflationSeq; time_t inflationTime = (INFLATION_START_TIME + seq * INFLATION_FREQUENCY); if (closeTime < inflationTime) { metrics.NewMeter({"op-inflation", "failure", "not-time"}, "operation") .Mark(); innerResult().code(INFLATION_NOT_TIME); return false; } /* Inflation is calculated using the following 1. calculate tally of votes based on "inflationDest" set on each account 2. take the top accounts (by vote) that exceed 1.5% (INFLATION_WIN_MIN_PERCENT) of votes, up to 50 accounts (INFLATION_NUM_WINNERS) exception: if no account crosses the INFLATION_WIN_MIN_PERCENT, the top 50 is used 3. share the coins between those accounts proportionally to the number of votes they got. */ int64_t totalVotes = 0; bool first = true; int64_t minBalance = bigDivide(lcl.totalCoins, INFLATION_WIN_MIN_PERCENT, TRILLION); std::vector<AccountFrame::InflationVotes> winners; auto& db = ledgerManager.getDatabase(); AccountFrame::processForInflation( [&](AccountFrame::InflationVotes const& votes) { if (first && votes.mVotes < minBalance) { // need to take the entire set if nobody crossed the threshold minBalance = 0; } first = false; bool res; if (votes.mVotes >= minBalance) { totalVotes += votes.mVotes; winners.push_back(votes); res = true; } else { res = false; } return res; }, INFLATION_NUM_WINNERS, db); int64 amountToDole = bigDivide(lcl.totalCoins, INFLATION_RATE_TRILLIONTHS, TRILLION); amountToDole += lcl.feePool; lcl.feePool = 0; lcl.inflationSeq++; // now credit each account innerResult().code(INFLATION_SUCCESS); auto& payouts = innerResult().payouts(); if (totalVotes != 0) { for (auto const& w : winners) { AccountFrame::pointer winner; int64 toDoleThisWinner = bigDivide(amountToDole, w.mVotes, totalVotes); if (toDoleThisWinner == 0) continue; winner = AccountFrame::loadAccount(w.mInflationDest, db); if (winner) { lcl.totalCoins += toDoleThisWinner; winner->getAccount().balance += toDoleThisWinner; winner->storeChange(inflationDelta, db); payouts.emplace_back(w.mInflationDest, toDoleThisWinner); } else { // put back in fee pool as unclaimed funds lcl.feePool += toDoleThisWinner; } } } else { // put back in fee pool as unclaimed funds lcl.feePool += amountToDole; } inflationDelta.commit(); metrics.NewMeter({"op-inflation", "success", "apply"}, "operation").Mark(); return true; }
bool PathPaymentOpFrame::doApply(medida::MetricsRegistry& metrics, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); innerResult().code(PATH_PAYMENT_SUCCESS); // tracks the last amount that was traded int64_t curBReceived = mPathPayment.destAmount; Asset curB = mPathPayment.destAsset; // update balances, walks backwards // build the full path to the destination, starting with sendAsset std::vector<Asset> fullPath; fullPath.emplace_back(mPathPayment.sendAsset); fullPath.insert(fullPath.end(), mPathPayment.path.begin(), mPathPayment.path.end()); bool bypassIssuerCheck = false; // if the payment doesn't involve intermediate accounts // and the destination is the issuer we don't bother // checking if the destination account even exist // so that it's always possible to send credits back to its issuer bypassIssuerCheck = (curB.type() != ASSET_TYPE_NATIVE) && (fullPath.size() == 1) && (mPathPayment.sendAsset == mPathPayment.destAsset) && (getIssuer(curB) == mPathPayment.destination); AccountFrame::pointer destination; if (!bypassIssuerCheck) { destination = AccountFrame::loadAccount(mPathPayment.destination, db); if (!destination) { metrics.NewMeter({"op-path-payment", "failure", "no-destination"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_DESTINATION); return false; } } // update last balance in the chain if (curB.type() == ASSET_TYPE_NATIVE) { destination->getAccount().balance += curBReceived; destination->storeChange(delta, db); } else { TrustFrame::pointer destLine; if (bypassIssuerCheck) { destLine = TrustFrame::loadTrustLine(mPathPayment.destination, curB, db); } else { auto tlI = TrustFrame::loadTrustLineIssuer(mPathPayment.destination, curB, db); if (!tlI.second) { metrics.NewMeter({"op-path-payment", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_ISSUER); innerResult().noIssuer() = curB; return false; } destLine = tlI.first; } if (!destLine) { metrics.NewMeter({"op-path-payment", "failure", "no-trust"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_TRUST); return false; } if (!destLine->isAuthorized()) { metrics.NewMeter({"op-path-payment", "failure", "not-authorized"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NOT_AUTHORIZED); return false; } if (!destLine->addBalance(curBReceived)) { metrics.NewMeter({"op-path-payment", "failure", "line-full"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_LINE_FULL); return false; } destLine->storeChange(delta, db); } innerResult().success().last = SimplePaymentResult(mPathPayment.destination, curB, curBReceived); // now, walk the path backwards for (int i = (int)fullPath.size() - 1; i >= 0; i--) { int64_t curASent, actualCurBReceived; Asset const& curA = fullPath[i]; if (curA == curB) { continue; } if (curA.type() != ASSET_TYPE_NATIVE) { if (!AccountFrame::loadAccount(getIssuer(curA), db)) { metrics.NewMeter({"op-path-payment", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_ISSUER); innerResult().noIssuer() = curA; return false; } } OfferExchange oe(delta, ledgerManager); // curA -> curB OfferExchange::ConvertResult r = oe.convertWithOffers( curA, INT64_MAX, curASent, curB, curBReceived, actualCurBReceived, [this, &metrics](OfferFrame const& o) { if (o.getSellerID() == getSourceID()) { // we are crossing our own offer, potentially invalidating // mSourceAccount (balance or numSubEntries) metrics.NewMeter({"op-path-payment", "failure", "offer-cross-self"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_OFFER_CROSS_SELF); return OfferExchange::eStop; } return OfferExchange::eKeep; }); switch (r) { case OfferExchange::eFilterStop: return false; case OfferExchange::eOK: if (curBReceived == actualCurBReceived) { break; } // fall through case OfferExchange::ePartial: metrics.NewMeter({"op-path-payment", "failure", "too-few-offers"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_TOO_FEW_OFFERS); return false; } assert(curBReceived == actualCurBReceived); curBReceived = curASent; // next round, we need to send enough curB = curA; // add offers that got taken on the way // insert in front to match the path's order auto& offers = innerResult().success().offers; offers.insert(offers.begin(), oe.getOfferTrail().begin(), oe.getOfferTrail().end()); } // last step: we've reached the first account in the chain, update its // balance int64_t curBSent; curBSent = curBReceived; if (curBSent > mPathPayment.sendMax) { // make sure not over the max metrics.NewMeter({"op-path-payment", "failure", "over-send-max"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_OVER_SENDMAX); return false; } if (curB.type() == ASSET_TYPE_NATIVE) { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if ((mSourceAccount->getAccount().balance - curBSent) < minBalance) { // they don't have enough to send metrics.NewMeter({"op-path-payment", "failure", "underfunded"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= curBSent; mSourceAccount->storeChange(delta, db); } else { TrustFrame::pointer sourceLineFrame; if (bypassIssuerCheck) { sourceLineFrame = TrustFrame::loadTrustLine(getSourceID(), curB, db); } else { auto tlI = TrustFrame::loadTrustLineIssuer(getSourceID(), curB, db); if (!tlI.second) { metrics.NewMeter({"op-path-payment", "failure", "no-issuer"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_NO_ISSUER); innerResult().noIssuer() = curB; return false; } sourceLineFrame = tlI.first; } if (!sourceLineFrame) { metrics.NewMeter({"op-path-payment", "failure", "src-no-trust"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_SRC_NO_TRUST); return false; } if (!sourceLineFrame->isAuthorized()) { metrics.NewMeter( {"op-path-payment", "failure", "src-not-authorized"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_SRC_NOT_AUTHORIZED); return false; } if (!sourceLineFrame->addBalance(-curBSent)) { metrics.NewMeter({"op-path-payment", "failure", "underfunded"}, "operation").Mark(); innerResult().code(PATH_PAYMENT_UNDERFUNDED); return false; } sourceLineFrame->storeChange(delta, db); } metrics.NewMeter({"op-path-payment", "success", "apply"}, "operation") .Mark(); return true; }