예제 #1
0
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;
    }
}
예제 #3
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;
}
예제 #4
0
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;
}
예제 #5
0
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());
}
예제 #6
0
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;
}
예제 #7
0
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;
}
예제 #8
0
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;
}
예제 #9
0
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;
}
예제 #10
0
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;
}