static void createTestAccounts(Application& app, int nbAccounts, std::function<int64(int)> getBalance, std::function<int(int)> getVote) { // set up world SecretKey root = getRoot(app.getNetworkID()); SequenceNumber rootSeq = getAccountSeqNum(root, app) + 1; auto& lm = app.getLedgerManager(); auto& db = app.getDatabase(); int64 setupBalance = lm.getMinBalance(0); LedgerDelta delta(lm.getCurrentLedgerHeader(), app.getDatabase()); for (int i = 0; i < nbAccounts; i++) { int64 bal = getBalance(i); if (bal >= 0) { SecretKey to = getTestAccount(i); applyCreateAccountTx(app, root, to, rootSeq++, setupBalance); AccountFrame::pointer act; act = loadAccount(to, app); act->getAccount().balance = bal; act->getAccount().inflationDest.activate() = getTestAccount(getVote(i)).getPublicKey(); act->storeChange(delta, db); } } }
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; } }
SequenceNumber getSeq(SecretKey const& k, Application& app) { AccountFrame::pointer account; account = AccountFrame::loadAccount(k.getPublicKey(), app.getDatabase()); if (account) { return account->getSeqNum(); } return 0; }
// 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; }
bool LoadGenerator::loadAccount(TestAccount& account, Database& database) { AccountFrame::pointer ret; ret = AccountFrame::loadAccount(account.getPublicKey(), database); if (!ret) { return false; } account.setSequenceNumber(ret->getSeqNum()); return true; }
AccountFrame::pointer TransactionFrame::loadAccount(Application& app, AccountID const& accountID) { AccountFrame::pointer res; if (mSigningAccount && mSigningAccount->getID() == accountID) { res = mSigningAccount; } else { res = make_shared<AccountFrame>(); bool ok = AccountFrame::loadAccount(accountID, *res, app.getDatabase()); if (!ok) { res.reset(); } } return res; }
void TransactionFrame::setSourceAccountPtr(AccountFrame::pointer signingAccount) { if (!signingAccount) { if (mEnvelope.tx.sourceAccount != signingAccount->getID()) { throw std::invalid_argument("wrong account"); } } mSigningAccount = signingAccount; }
AccountFrame::pointer AccountFrame::loadAccount(AccountID const& accountID, Database& db) { LedgerKey key; key.type(ACCOUNT); key.account().accountID = accountID; if (cachedEntryExists(key, db)) { auto p = getCachedEntry(key, db); return p ? std::make_shared<AccountFrame>(*p) : nullptr; } std::string actIDStrKey = PubKeyUtils::toStrKey(accountID); std::string publicKey, inflationDest, creditAuthKey; std::string homeDomain, thresholds; soci::indicator inflationDestInd; AccountFrame::pointer res = make_shared<AccountFrame>(accountID); AccountEntry& account = res->getAccount(); auto prep = db.getPreparedStatement("SELECT balance, seqnum, numsubentries, " "inflationdest, homedomain, thresholds, " "flags, lastmodified " "FROM accounts WHERE accountid=:v1"); auto& st = prep.statement(); st.exchange(into(account.balance)); st.exchange(into(account.seqNum)); st.exchange(into(account.numSubEntries)); st.exchange(into(inflationDest, inflationDestInd)); st.exchange(into(homeDomain)); st.exchange(into(thresholds)); st.exchange(into(account.flags)); st.exchange(into(res->getLastModified())); st.exchange(use(actIDStrKey)); st.define_and_bind(); { auto timer = db.getSelectTimer("account"); st.execute(true); } if (!st.got_data()) { putCachedEntry(key, nullptr, db); return nullptr; } account.homeDomain = homeDomain; bn::decode_b64(thresholds.begin(), thresholds.end(), res->mAccountEntry.thresholds.begin()); if (inflationDestInd == soci::i_ok) { account.inflationDest.activate() = PubKeyUtils::fromStrKey(inflationDest); } account.signers.clear(); if (account.numSubEntries != 0) { auto signers = loadSigners(db, actIDStrKey); account.signers.insert(account.signers.begin(), signers.begin(), signers.end()); } res->normalize(); res->mUpdateSigners = false; assert(res->isValid()); res->mKeyCalculated = false; res->putCachedEntry(db); return res; }
static void doInflation(Application& app, int nbAccounts, std::function<int64(int)> getBalance, std::function<int(int)> getVote, int expectedWinnerCount) { using xdr::operator==; // simulate the expected inflation based off the current ledger state std::map<int, int64> balances; // load account balances for (int i = 0; i < nbAccounts; i++) { if (getBalance(i) < 0) { balances[i] = -1; requireNoAccount(getTestAccount(i), app); } else { AccountFrame::pointer act; act = loadAccount(getTestAccount(i), app); balances[i] = act->getBalance(); // double check that inflationDest is setup properly if (act->getAccount().inflationDest) { REQUIRE(getTestAccount(getVote(i)).getPublicKey() == *act->getAccount().inflationDest); } else { REQUIRE(getVote(i) < 0); } } } LedgerManager& lm = app.getLedgerManager(); LedgerHeader& cur = lm.getCurrentLedgerHeader(); cur.feePool = 10000; int64 expectedTotcoins = cur.totalCoins; int64 expectedFees = cur.feePool; std::vector<int64> expectedBalances; auto root = getRoot(app.getNetworkID()); TransactionFramePtr txFrame = createInflation( app.getNetworkID(), root, getAccountSeqNum(root, app) + 1); expectedFees += txFrame->getFee(); expectedBalances = simulateInflation(nbAccounts, expectedTotcoins, expectedFees, [&](int i) { return balances[i]; }, getVote); // perform actual inflation { LedgerDelta delta(lm.getCurrentLedgerHeader(), app.getDatabase()); REQUIRE(applyCheck(txFrame, delta, app)); delta.commit(); } // verify ledger state LedgerHeader& cur2 = lm.getCurrentLedgerHeader(); REQUIRE(cur2.totalCoins == expectedTotcoins); REQUIRE(cur2.feePool == expectedFees); // verify balances InflationResult const& infResult = getFirstResult(*txFrame).tr().inflationResult(); auto const& payouts = infResult.payouts(); int actualChanges = 0; for (int i = 0; i < nbAccounts; i++) { auto const& k = getTestAccount(i); if (expectedBalances[i] < 0) { requireNoAccount(k, app); REQUIRE(balances[i] < 0); // account didn't get deleted } else { AccountFrame::pointer act; act = loadAccount(k, app); REQUIRE(expectedBalances[i] == act->getBalance()); if (expectedBalances[i] != balances[i]) { REQUIRE(balances[i] >= 0); actualChanges++; bool found = false; for (auto const& p : payouts) { if (p.destination == k.getPublicKey()) { int64 computedFromResult = balances[i] + p.amount; REQUIRE(computedFromResult == expectedBalances[i]); found = true; break; } } REQUIRE(found); } } } REQUIRE(actualChanges == expectedWinnerCount); REQUIRE(expectedWinnerCount == payouts.size()); }
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; }
const int64_t assetMultiplier = 1000000; int64_t trustLineLimit = INT64_MAX; int64_t trustLineStartingBalance = 20000 * assetMultiplier; Asset idrCur = makeAsset(gateway, "IDR"); Asset usdCur = makeAsset(gateway, "USD"); // sets up gateway account const int64_t gatewayPayment = minBalance2 + morePayment; applyCreateAccountTx(app, root, gateway, rootSeq++, gatewayPayment); SequenceNumber gateway_seq = getAccountSeqNum(gateway, app) + 1; AccountFrame::pointer a1Account, rootAccount; rootAccount = loadAccount(root, app); a1Account = loadAccount(a1, app); REQUIRE(rootAccount->getMasterWeight() == 1); REQUIRE(rootAccount->getHighThreshold() == 0); REQUIRE(rootAccount->getLowThreshold() == 0); REQUIRE(rootAccount->getMediumThreshold() == 0); REQUIRE(a1Account->getBalance() == paymentAmount); REQUIRE(a1Account->getMasterWeight() == 1); REQUIRE(a1Account->getHighThreshold() == 0); REQUIRE(a1Account->getLowThreshold() == 0); REQUIRE(a1Account->getMediumThreshold() == 0); // root did 2 transactions at this point REQUIRE(rootAccount->getBalance() == (100000000000000000 - paymentAmount - gatewayPayment - txfee * 2));
OfferExchange::CrossOfferResult OfferExchange::crossOffer(OfferFrame& sellingWheatOffer, int64_t maxWheatReceived, int64_t& numWheatReceived, int64_t maxSheepSend, int64_t& numSheepSend) { Asset& sheep = sellingWheatOffer.getOffer().buying; Asset& wheat = sellingWheatOffer.getOffer().selling; AccountID& accountBID = sellingWheatOffer.getOffer().sellerID; Database& db = mLedgerManager.getDatabase(); AccountFrame::pointer accountB; accountB = AccountFrame::loadAccount(accountBID, db); if (!accountB) { throw std::runtime_error( "invalid database state: offer must have matching account"); } TrustFrame::pointer wheatLineAccountB; if (wheat.type() != ASSET_TYPE_NATIVE) { wheatLineAccountB = TrustFrame::loadTrustLine(accountBID, wheat, db); if (!wheatLineAccountB) { throw std::runtime_error( "invalid database state: offer must have matching trust line"); } } TrustFrame::pointer sheepLineAccountB; if (sheep.type() == ASSET_TYPE_NATIVE) { numWheatReceived = INT64_MAX; } else { sheepLineAccountB = TrustFrame::loadTrustLine(accountBID, sheep, db); if (!sheepLineAccountB) { throw std::runtime_error( "invalid database state: offer must have matching trust line"); } // compute numWheatReceived based on what the account can receive int64_t sellerMaxSheep = sheepLineAccountB->getMaxAmountReceive(); if (!bigDivide(numWheatReceived, sellerMaxSheep, sellingWheatOffer.getOffer().price.d, sellingWheatOffer.getOffer().price.n)) { numWheatReceived = INT64_MAX; } } // adjust numWheatReceived with what the seller has { int64_t wheatCanSell; if (wheat.type() == ASSET_TYPE_NATIVE) { // can only send above the minimum balance wheatCanSell = accountB->getBalanceAboveReserve(mLedgerManager); } else { if (wheatLineAccountB->isAuthorized()) { wheatCanSell = wheatLineAccountB->getBalance(); } else { wheatCanSell = 0; } } if (numWheatReceived > wheatCanSell) { numWheatReceived = wheatCanSell; } } // you can receive the lesser of the amount of wheat offered or // the amount the guy has if (numWheatReceived >= sellingWheatOffer.getOffer().amount) { numWheatReceived = sellingWheatOffer.getOffer().amount; } else { // update the offer based on the balance (to determine if it should be // deleted or not) // note that we don't need to write into the db at this point as the // actual update // is done further down sellingWheatOffer.getOffer().amount = numWheatReceived; } bool reducedOffer = false; if (numWheatReceived > maxWheatReceived) { numWheatReceived = maxWheatReceived; reducedOffer = true; } // this guy can get X wheat to you. How many sheep does that get him? if (!bigDivide(numSheepSend, numWheatReceived, sellingWheatOffer.getOffer().price.n, sellingWheatOffer.getOffer().price.d)) { numSheepSend = INT64_MAX; } if (numSheepSend > maxSheepSend) { // reduce the number even more if there is a limit on Sheep numSheepSend = maxSheepSend; reducedOffer = true; } // bias towards seller (this cannot overflow at this point) numWheatReceived = bigDivide(numSheepSend, sellingWheatOffer.getOffer().price.d, sellingWheatOffer.getOffer().price.n); bool offerTaken = false; if (numWheatReceived == 0 || numSheepSend == 0) { if (reducedOffer) { return eOfferCantConvert; } else { // force delete the offer as it represents a bogus offer numWheatReceived = 0; numSheepSend = 0; offerTaken = true; } } offerTaken = offerTaken || sellingWheatOffer.getOffer().amount <= numWheatReceived; if (offerTaken) { // entire offer is taken sellingWheatOffer.storeDelete(mDelta, db); accountB->addNumEntries(-1, mLedgerManager); accountB->storeChange(mDelta, db); } else { sellingWheatOffer.getOffer().amount -= numWheatReceived; sellingWheatOffer.storeChange(mDelta, db); } // Adjust balances if (sheep.type() == ASSET_TYPE_NATIVE) { accountB->getAccount().balance += numSheepSend; accountB->storeChange(mDelta, db); } else { if (!sheepLineAccountB->addBalance(numSheepSend)) { return eOfferCantConvert; } sheepLineAccountB->storeChange(mDelta, db); } if (wheat.type() == ASSET_TYPE_NATIVE) { accountB->getAccount().balance -= numWheatReceived; accountB->storeChange(mDelta, db); } else { if (!wheatLineAccountB->addBalance(-numWheatReceived)) { return eOfferCantConvert; } wheatLineAccountB->storeChange(mDelta, db); } mOfferTrail.push_back( ClaimOfferAtom(accountB->getID(), sellingWheatOffer.getOfferID(), wheat, numWheatReceived, sheep, numSheepSend)); return offerTaken ? eOfferTaken : eOfferPartial; }
AccountFrame::pointer AccountFrame::loadAccount(AccountID const& accountID, Database& db) { LedgerKey key; key.type(ACCOUNT); key.account().accountID = accountID; if (cachedEntryExists(key, db)) { auto p = getCachedEntry(key, db); return p ? std::make_shared<AccountFrame>(*p) : nullptr; } std::string actIDStrKey = PubKeyUtils::toStrKey(accountID); std::string publicKey, inflationDest, creditAuthKey; std::string homeDomain, thresholds; soci::indicator inflationDestInd, homeDomainInd, thresholdsInd; soci::session& session = db.getSession(); AccountFrame::pointer res = make_shared<AccountFrame>(accountID); AccountEntry& account = res->getAccount(); { auto timer = db.getSelectTimer("account"); session << "SELECT balance, seqnum, numsubentries, " "inflationdest, homedomain, thresholds, flags " "FROM accounts WHERE accountid=:v1", into(account.balance), into(account.seqNum), into(account.numSubEntries), into(inflationDest, inflationDestInd), into(homeDomain, homeDomainInd), into(thresholds, thresholdsInd), into(account.flags), use(actIDStrKey); } if (!session.got_data()) { putCachedEntry(key, nullptr, db); return nullptr; } if (homeDomainInd == soci::i_ok) { account.homeDomain = homeDomain; } if (thresholdsInd == soci::i_ok) { bn::decode_b64(thresholds.begin(), thresholds.end(), res->mAccountEntry.thresholds.begin()); } if (inflationDestInd == soci::i_ok) { account.inflationDest.activate() = PubKeyUtils::fromStrKey(inflationDest); } account.signers.clear(); if (account.numSubEntries != 0) { string pubKey; Signer signer; auto prep = db.getPreparedStatement("SELECT publickey, weight from " "signers where accountid =:id"); auto& st = prep.statement(); st.exchange(use(actIDStrKey)); st.exchange(into(pubKey)); st.exchange(into(signer.weight)); st.define_and_bind(); { auto timer = db.getSelectTimer("signer"); st.execute(true); } while (st.got_data()) { signer.pubKey = PubKeyUtils::fromStrKey(pubKey); account.signers.push_back(signer); st.fetch(); } } res->normalize(); res->mUpdateSigners = false; res->mKeyCalculated = false; res->putCachedEntry(db); return res; }
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; }