Exemple #1
0
static
std::uint32_t
rippleQuality (
    LedgerEntrySet& ledger,
    AccountID const& destination,
    AccountID const& source,
    Currency const& currency,
    SField const& sfLow,
    SField const& sfHigh)
{
    std::uint32_t uQuality (QUALITY_ONE);

    if (destination != source)
    {
        SLE::pointer sleRippleState (ledger.entryCache (ltRIPPLE_STATE,
            getRippleStateIndex (destination, source, currency)));

        // we should be able to assert(sleRippleState) here

        if (sleRippleState)
        {
            auto const& sfField = destination < source ? sfLow : sfHigh;

            uQuality = sleRippleState->isFieldPresent (sfField)
                ? sleRippleState->getFieldU32 (sfField)
                : QUALITY_ONE;

            if (!uQuality)
                uQuality = 1; // Avoid divide by zero.
        }
    }

    return uQuality;
}
Exemple #2
0
TER ChangeTransactor::applyFeature ()
{
    uint256 feature = mTxn.getFieldH256 (sfFeature);

    SLE::pointer featureObject = mEngine->entryCache (ltFEATURES, Ledger::getLedgerFeatureIndex ());

    if (!featureObject)
        featureObject = mEngine->entryCreate (ltFEATURES, Ledger::getLedgerFeatureIndex ());

    STVector256 features = featureObject->getFieldV256 (sfFeatures);

    if (features.hasValue (feature))
        return tefALREADY;

    features.addValue (feature);
    featureObject->setFieldV256 (sfFeatures, features);
    mEngine->entryModify (featureObject);

    getApp().getFeatureTable ().enableFeature (feature);

    if (!getApp().getFeatureTable ().isFeatureSupported (feature))
        getApp().getOPs ().setFeatureBlocked ();

    return tesSUCCESS;
}
Exemple #3
0
/** Check if a sequence of three accounts violates the no ripple constrains
    [first] -> [second] -> [third]
    Disallowed if 'second' set no ripple on [first]->[second] and
    [second]->[third]
*/
TER PathState::checkNoRipple (
    AccountID const& firstAccount,
    AccountID const& secondAccount,
    // This is the account whose constraints we are checking
    AccountID const& thirdAccount,
    Currency const& currency)
{
    // fetch the ripple lines into and out of this node
    SLE::pointer sleIn = view().peek (
        keylet::line(firstAccount, secondAccount, currency));
    SLE::pointer sleOut = view().peek (
        keylet::line(secondAccount, thirdAccount, currency));

    if (!sleIn || !sleOut)
    {
        terStatus = terNO_LINE;
    }
    else if (
        sleIn->getFieldU32 (sfFlags) &
            ((secondAccount > firstAccount) ? lsfHighNoRipple : lsfLowNoRipple) &&
        sleOut->getFieldU32 (sfFlags) &
            ((secondAccount > thirdAccount) ? lsfHighNoRipple : lsfLowNoRipple))
    {
        JLOG (j_.info)
            << "Path violates noRipple constraint between "
            << firstAccount << ", "
            << secondAccount << " and "
            << thirdAccount;

        terStatus = terNO_RIPPLE;
    }
    return terStatus;
}
// Look up the master public generator for a regular seed so we may index source accounts ids.
// --> naRegularSeed
// <-- naMasterGenerator
Json::Value getMasterGenerator (
    Ledger::ref lrLedger, const RippleAddress& naRegularSeed,
    RippleAddress& naMasterGenerator, NetworkOPs& netOps)
{
    RippleAddress       na0Public;      // To find the generator's index.
    RippleAddress       na0Private;     // To decrypt the master generator's cipher.
    RippleAddress       naGenerator = RippleAddress::createGeneratorPublic (naRegularSeed);

    na0Public.setAccountPublic (naGenerator, 0);
    na0Private.setAccountPrivate (naGenerator, naRegularSeed, 0);

    SLE::pointer        sleGen          = netOps.getGenerator (lrLedger, na0Public.getAccountID ());

    if (!sleGen)
    {
        // No account has been claimed or has had it password set for seed.
        return rpcError (rpcNO_ACCOUNT);
    }

    Blob    vucCipher           = sleGen->getFieldVL (sfGenerator);
    Blob    vucMasterGenerator  = na0Private.accountPrivateDecrypt (na0Public, vucCipher);

    if (vucMasterGenerator.empty ())
    {
        return rpcError (rpcFAIL_GEN_DECRYPT);
    }

    naMasterGenerator.setGenerator (vucMasterGenerator);

    return Json::Value (Json::objectValue);
}
Exemple #5
0
TER
SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
        Keylet const& ownerDirKeylet, Keylet const& signerListKeylet)
{
    // We have to examine the current SignerList so we know how much to
    // reduce the OwnerCount.
    SLE::pointer signers = view().peek (signerListKeylet);

    // If the signer list doesn't exist we've already succeeded in deleting it.
    if (!signers)
        return tesSUCCESS;

    STArray const& actualList = signers->getFieldArray (sfSignerEntries);
    int const removeFromOwnerCount = ownerCountDelta (actualList.size()) * -1;

    // Remove the node from the account directory.
    auto const hint = (*signers)[sfOwnerNode];

    auto viewJ = ctx_.app.journal ("View");
    TER const result  = dirDelete(ctx_.view(), false, hint,
        ownerDirKeylet.key, signerListKeylet.key, false, (hint == 0), viewJ);

    if (result == tesSUCCESS)
        adjustOwnerCount(view(),
            view().peek(accountKeylet), removeFromOwnerCount, viewJ);

    ctx_.view().erase (signers);

    return result;
}
Exemple #6
0
TER Change::applyAmendment ()
{
    uint256 amendment (mTxn.getFieldH256 (sfAmendment));

    SLE::pointer amendmentObject (mEngine->entryCache (
        ltAMENDMENTS, Ledger::getLedgerAmendmentIndex ()));

    if (!amendmentObject)
    {
        amendmentObject = mEngine->entryCreate(
            ltAMENDMENTS, Ledger::getLedgerAmendmentIndex());
    }

    STVector256 amendments (amendmentObject->getFieldV256 (sfAmendments));

    if (amendments.hasValue (amendment))
        return tefALREADY;

    amendments.addValue (amendment);
    amendmentObject->setFieldV256 (sfAmendments, amendments);
    mEngine->entryModify (amendmentObject);

    getApp().getAmendmentTable ().enable (amendment);

    if (!getApp().getAmendmentTable ().isSupported (amendment))
        getApp().getOPs ().setAmendmentBlocked ();

    return tesSUCCESS;
}
Exemple #7
0
/** Check if a sequence of three accounts violates the no ripple constrains
    [first] -> [second] -> [third]
    Disallowed if 'second' set no ripple on [first]->[second] and [second]->[third]
*/
void PathState::checkNoRipple (
    uint160 const& firstAccount,
    uint160 const& secondAccount,  // This is the account whose constraints we are checking
    uint160 const& thirdAccount,
    uint160 const& currency)
{
    // fetch the ripple lines into and out of this node
    SLE::pointer sleIn = lesEntries.entryCache (ltRIPPLE_STATE,
        Ledger::getRippleStateIndex (firstAccount, secondAccount, currency));
    SLE::pointer sleOut = lesEntries.entryCache (ltRIPPLE_STATE,
        Ledger::getRippleStateIndex (secondAccount, thirdAccount, currency));

    if (!sleIn || !sleOut)
    {
        terStatus = terNO_LINE;
    }
    else if (
        is_bit_set (sleIn->getFieldU32 (sfFlags),
            (secondAccount > firstAccount) ? lsfHighNoRipple : lsfLowNoRipple) &&
        is_bit_set (sleOut->getFieldU32 (sfFlags),
            (secondAccount > thirdAccount) ? lsfHighNoRipple : lsfLowNoRipple))
    {
        WriteLog (lsINFO, RippleCalc) << "Path violates noRipple constraint between " <<
            RippleAddress::createHumanAccountID (firstAccount) << ", " <<
            RippleAddress::createHumanAccountID (secondAccount) << " and " <<
            RippleAddress::createHumanAccountID (thirdAccount);

        terStatus = terNO_RIPPLE;
    }
}
Exemple #8
0
/** Check if an expanded path violates freeze rules */
void PathState::checkFreeze()
{
    assert (nodes_.size() >= 2);

    // A path with no intermediaries -- pure issue/redeem
    // cannot be frozen.
    if (nodes_.size() == 2)
        return;

    SLE::pointer sle;

    for (std::size_t i = 0; i < (nodes_.size() - 1); ++i)
    {
        // Check each order book for a global freeze
        if (nodes_[i].uFlags & STPathElement::typeIssuer)
        {
            sle = view().peek (keylet::account(nodes_[i].issue_.account));

            if (sle && sle->isFlag (lsfGlobalFreeze))
            {
                terStatus = terNO_LINE;
                return;
            }
        }

        // Check each account change to make sure funds can leave
        if (nodes_[i].uFlags & STPathElement::typeAccount)
        {
            Currency const& currencyID = nodes_[i].issue_.currency;
            AccountID const& inAccount = nodes_[i].account_;
            AccountID const& outAccount = nodes_[i+1].account_;

            if (inAccount != outAccount)
            {
                sle = view().peek (keylet::account(outAccount));

                if (sle && sle->isFlag (lsfGlobalFreeze))
                {
                    terStatus = terNO_LINE;
                    return;
                }

                sle = view().peek (keylet::line(inAccount,
                        outAccount, currencyID));

                if (sle && sle->isFlag (
                    (outAccount > inAccount) ? lsfHighFreeze : lsfLowFreeze))
                {
                    terStatus = terNO_LINE;
                    return;
                }
            }
        }
    }
}
ter
createoffer::checkacceptasset(issueref issue) const
{
    /* only valid for custom currencies */
    assert (!isxrp (issue.currency));
    assert (!isvbc (issue.currency));

    sle::pointer const issueraccount = mengine->entrycache (
        ltaccount_root, getaccountrootindex (issue.account));

    if (!issueraccount)
    {
        if (m_journal.warning) m_journal.warning <<
            "delay: can't receive ious from non-existent issuer: " <<
            to_string (issue.account);

        return (mparams & tapretry)
            ? terno_account
            : tecno_issuer;
    }

    if (issueraccount->getfieldu32 (sfflags) & lsfrequireauth)
    {
        sle::pointer const trustline (mengine->entrycache (
            ltripple_state, getripplestateindex (
                mtxnaccountid, issue.account, issue.currency)));

        if (!trustline)
        {
            return (mparams & tapretry)
                ? terno_line
                : tecno_line;
        }

        // entries have a canonical representation, determined by a
        // lexicographical "greater than" comparison employing strict weak
        // ordering. determine which entry we need to access.
        bool const canonical_gt (mtxnaccountid > issue.account);

        bool const is_authorized (trustline->getfieldu32 (sfflags) &
            (canonical_gt ? lsflowauth : lsfhighauth));

        if (!is_authorized)
        {
            if (m_journal.debug) m_journal.debug <<
                "delay: can't receive ious from issuer without auth.";

            return (mparams & tapretry)
                ? terno_auth
                : tecno_auth;
        }
    }

    return tessuccess;
}
Exemple #10
0
TER
CreateOffer::checkAcceptAsset(IssueRef issue) const
{
    /* Only valid for custom currencies */
    assert (!isXRP (issue.currency));

    SLE::pointer const issuerAccount = mEngine->entryCache (
        ltACCOUNT_ROOT, Ledger::getAccountRootIndex (issue.account));

    if (!issuerAccount)
    {
        if (m_journal.warning) m_journal.warning <<
            "delay: can't receive IOUs from non-existent issuer: " <<
            to_string (issue.account);

        return (mParams & tapRETRY)
            ? terNO_ACCOUNT
            : tecNO_ISSUER;
    }

    if (issuerAccount->getFieldU32 (sfFlags) & lsfRequireAuth)
    {
        SLE::pointer const trustLine (mEngine->entryCache (
            ltRIPPLE_STATE, Ledger::getRippleStateIndex (
                mTxnAccountID, issue.account, issue.currency)));

        if (!trustLine)
        {
            return (mParams & tapRETRY)
                ? terNO_LINE
                : tecNO_LINE;
        }

        // Entries have a canonical representation, determined by a
        // lexicographical "greater than" comparison employing strict weak
        // ordering. Determine which entry we need to access.
        bool const canonical_gt (mTxnAccountID > issue.account);

        bool const is_authorized (trustLine->getFieldU32 (sfFlags) &
            (canonical_gt ? lsfLowAuth : lsfHighAuth));

        if (!is_authorized)
        {
            if (m_journal.debug) m_journal.debug <<
                "delay: can't receive IOUs from issuer without auth.";

            return (mParams & tapRETRY)
                ? terNO_AUTH
                : tecNO_AUTH;
        }
    }

    return tesSUCCESS;
}
void AccountItems::fillItems (const uint160& accountID, Ledger::ref ledger)
{
    uint256 const rootIndex = Ledger::getOwnerDirIndex (accountID);
    uint256 currentIndex    = rootIndex;

    // VFALCO TODO Rewrite all infinite loops to have clear terminating
    //             conditions defined in one location.
    //
    while (1)
    {
        SLE::pointer ownerDir   = ledger->getDirNode (currentIndex);

        // VFALCO TODO Rewrite to not return from the middle of the function
        if (!ownerDir)
            return;

        BOOST_FOREACH (uint256 const & uNode, ownerDir->getFieldV256 (sfIndexes).peekValue ())
        {
            // VFALCO TODO rename getSLEi() to something legible.
            SLE::pointer sleCur = ledger->getSLEi (uNode);

            if (!sleCur)
            {
                // item in directory not in ledger
            }
            else
            {
                AccountItem::pointer item = mOfType->makeItem (accountID, sleCur);

                // VFALCO NOTE Under what conditions would makeItem() return nullptr?
                // DJS NOTE If the item wasn't one this particular AccountItems was interested in
                // (For example, if the owner is only interested in ripple lines and this is an offer)
                if (item)
                {
                    mItems.push_back (item);
                }
            }
        }

        std::uint64_t uNodeNext    = ownerDir->getFieldU64 (sfIndexNext);

        // VFALCO TODO Rewrite to not return from the middle of the function
        if (!uNodeNext)
            return;

        currentIndex = Ledger::getDirNodeIndex (rootIndex, uNodeNext);
    }
}
Exemple #12
0
TER
SetSignerList::destroySignerList ()
{
    auto const accountKeylet = keylet::account (account_);
    // Destroying the signer list is only allowed if either the master key
    // is enabled or there is a regular key.
    SLE::pointer ledgerEntry = view().peek (accountKeylet);
    if ((ledgerEntry->isFlag (lsfDisableMaster)) &&
        (!ledgerEntry->isFieldPresent (sfRegularKey)))
            return tecNO_ALTERNATIVE_KEY;

    auto const ownerDirKeylet = keylet::ownerDir (account_);
    auto const signerListKeylet = keylet::signers (account_);
    return removeSignersFromLedger(
        accountKeylet, ownerDirKeylet, signerListKeylet);
}
Exemple #13
0
TER Change::applyFee ()
{

    SLE::pointer feeObject = mEngine->entryCache (
        ltFEE_SETTINGS, Ledger::getLedgerFeeIndex ());

    if (!feeObject)
        feeObject = mEngine->entryCreate (
            ltFEE_SETTINGS, Ledger::getLedgerFeeIndex ());

    m_journal.trace << 
        "Previous fee object: " << feeObject->getJson (0);

    feeObject->setFieldU64 (
        sfBaseFee, mTxn.getFieldU64 (sfBaseFee));
    feeObject->setFieldU32 (
        sfReferenceFeeUnits, mTxn.getFieldU32 (sfReferenceFeeUnits));
    feeObject->setFieldU32 (
        sfReserveBase, mTxn.getFieldU32 (sfReserveBase));
    feeObject->setFieldU32 (
        sfReserveIncrement, mTxn.getFieldU32 (sfReserveIncrement));

    mEngine->entryModify (feeObject);

    m_journal.trace << 
        "New fee object: " << feeObject->getJson (0);
    m_journal.warning << "Fees have been changed";
    return tesSUCCESS;
}
void AccountItems::fillItems (Account const& accountID, Ledger::ref ledger)
{
    uint256 const rootIndex = Ledger::getOwnerDirIndex (accountID);
    uint256 currentIndex    = rootIndex;

    // VFALCO TODO Rewrite all infinite loops to have clear terminating
    //             conditions defined in one location.
    //
    while (1)
    {
        SLE::pointer ownerDir   = ledger->getDirNode (currentIndex);

        // VFALCO TODO Rewrite to not return from the middle of the function
        if (!ownerDir)
            return;

        for (auto const& uNode: ownerDir->getFieldV256 (sfIndexes).peekValue ())
        {
            // VFALCO TODO rename getSLEi() to something legible.
            SLE::pointer sleCur = ledger->getSLEi (uNode);

            if (sleCur)
            {
                // The item in the directory is in ledger
                auto item = mOfType->makeItem (accountID, sleCur);

                // makeItem() returns nullptr if the item wasn't one this
                // particular AccountItems was interested in - for example, if
                // the owner is only interested in ripple lines and this is an
                // offer.
                if (item)
                    mItems.push_back (item);
            }
        }

        std::uint64_t uNodeNext    = ownerDir->getFieldU64 (sfIndexNext);

        if (!uNodeNext)
            return;

        currentIndex = Ledger::getDirNodeIndex (rootIndex, uNodeNext);
    }
}
Exemple #15
0
/** Check if an expanded path violates freeze rules */
void PathState::checkFreeze()
{
    assert(vpnNodes.size() >= 2);
    
    // A path with no intermediaries -- pure issue/redeem
    // cannot be frozen.
    if(vpnNodes.size() == 2)
        return;
       
    for(std::size_t i = 0; i < (vpnNodes.size() - 1); ++i)
    {  
        // Check each account change to make sure funds can leave
        if(vpnNodes[i].uFlags & STPathElement::typeAccount)
        {
            uint160 const& currencyID = vpnNodes[i].uCurrencyID;
            uint160 const& inAccount = vpnNodes[i].uAccountID;
       
            uint160 const& issuingAccount = vpnNodes[i+1].uAccountID;
            
            if(inAccount != issuingAccount)
            {
                SLE::pointer sle = lesEntries.entryCache(ltACCOUNT_ROOT,
                    Ledger::getAccountRootIndex(issuingAccount));
                
                if(sle && sle->isFlag(lsfRequireAuth))
                {
                    sle = lesEntries.entryCache(ltRIPPLE_STATE,
                        Ledger::getRippleStateIndex(inAccount,
                        issuingAccount, currencyID));
                
                    if(sle && !sle->isFlag(
                        (issuingAccount > inAccount) ? lsfHighAuth : lsfLowAuth))
                    {
                        terStatus = terNO_LINE;
                        return;
                    }
                    
                }
            }
        }
    }
}
Exemple #16
0
void
SetSignerList::writeSignersToSLE (SLE::pointer const& ledgerEntry) const
{
    // Assign the quorum.
    ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);

    // For now, assign the default SignerListID.
    ledgerEntry->setFieldU32 (sfSignerListID, defaultSignerListID_);

    // Create the SignerListArray one SignerEntry at a time.
    STArray toLedger (signers_.size ());
    for (auto const& entry : signers_)
    {
        toLedger.emplace_back(sfSignerEntry);
        STObject& obj = toLedger.back();
        obj.reserve (2);
        obj.setAccountID (sfAccount, entry.account);
        obj.setFieldU16 (sfSignerWeight, entry.weight);
    }

    // Assign the SignerEntries.
    ledgerEntry->setFieldArray (sfSignerEntries, toLedger);
}
Exemple #17
0
TER
CancelTicket::doApply ()
{
    uint256 const ticketId = ctx_.tx.getFieldH256 (sfTicketID);

    // VFALCO This is highly suspicious, we're requiring that the
    //        transaction provide the return value of getTicketIndex?
    SLE::pointer sleTicket = view().peek (keylet::ticket(ticketId));

    if (!sleTicket)
        return tecNO_ENTRY;

    auto const ticket_owner =
        sleTicket->getAccountID (sfAccount);

    bool authorized =
        account_ == ticket_owner;

    // The target can also always remove a ticket
    if (!authorized && sleTicket->isFieldPresent (sfTarget))
        authorized = (account_ == sleTicket->getAccountID (sfTarget));

    // And finally, anyone can remove an expired ticket
    if (!authorized && sleTicket->isFieldPresent (sfExpiration))
    {
        using tp = NetClock::time_point;
        using d = tp::duration;
        auto const expiration = tp{d{sleTicket->getFieldU32(sfExpiration)}};

        if (view().parentCloseTime() >= expiration)
            authorized = true;
    }

    if (!authorized)
        return tecNO_PERMISSION;

    std::uint64_t const hint (sleTicket->getFieldU64 (sfOwnerNode));

    if (! ctx_.view().dirRemove(
            keylet::ownerDir(ticket_owner), hint, ticketId, false))
    {
        return tefBAD_LEDGER;
    }

    auto viewJ = ctx_.app.journal ("View");
    adjustOwnerCount(view(), view().peek(
        keylet::account(ticket_owner)), -1, viewJ);
    ctx_.view ().erase (sleTicket);

    return tesSUCCESS;
}
    LedgerEntry::pointer LedgerEntry::makeEntry(SLE::pointer sle)
    {
        switch (sle->getType())
        {
        case ltACCOUNT_ROOT:
            return LedgerEntry::pointer(new AccountEntry(sle));

        case ltRIPPLE_STATE:
            return LedgerEntry::pointer(new TrustLine(sle));

        case ltOFFER:
            return LedgerEntry::pointer(new OfferEntry(sle));

        case ltOWNERSHIP:
            return LedgerEntry::pointer(new Ownership(sle));

        case ltOBJECT_DESC:
            return LedgerEntry::pointer(new ObjectInfo(sle));

        }
        return(LedgerEntry::pointer());
    }
Exemple #19
0
    TER doApply () override
    {
        assert (mTxnAccount);

        uint256 const ticketId = mTxn.getFieldH256 (sfTicketID);

        SLE::pointer sleTicket = mEngine->view ().entryCache (ltTICKET, ticketId);

        if (!sleTicket)
            return tecNO_ENTRY;

        Account const ticket_owner (sleTicket->getFieldAccount160 (sfAccount));

        bool authorized (mTxnAccountID == ticket_owner);

        // The target can also always remove a ticket
        if (!authorized && sleTicket->isFieldPresent (sfTarget))
            authorized = (mTxnAccountID == sleTicket->getFieldAccount160 (sfTarget));

        // And finally, anyone can remove an expired ticket
        if (!authorized && sleTicket->isFieldPresent (sfExpiration))
        {
            std::uint32_t const expiration = sleTicket->getFieldU32 (sfExpiration);

            if (mEngine->getLedger ()->getParentCloseTimeNC () >= expiration)
                authorized = true;
        }

        if (!authorized)
            return tecNO_PERMISSION;

        std::uint64_t const hint (sleTicket->getFieldU64 (sfOwnerNode));

        TER const result = mEngine->view ().dirDelete (false, hint,
            getOwnerDirIndex (ticket_owner), ticketId, false, (hint == 0));

        mEngine->view ().decrementOwnerCount (mTxnAccount);
        mEngine->view ().entryDelete (sleTicket);

        return result;
    }
Exemple #20
0
TER
CreateOffer::doApply ()
{
    if (m_journal.debug) m_journal.debug <<
        "OfferCreate> " << mTxn.getJson (0);

    std::uint32_t const uTxFlags = mTxn.getFlags ();

    bool const bPassive (uTxFlags & tfPassive);
    bool const bImmediateOrCancel (uTxFlags & tfImmediateOrCancel);
    bool const bFillOrKill (uTxFlags & tfFillOrKill);
    bool const bSell  (uTxFlags & tfSell);

    STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
    STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);

    if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
        return temBAD_AMOUNT;

    auto const& uPaysIssuerID = saTakerPays.getIssuer ();
    auto const& uPaysCurrency = saTakerPays.getCurrency ();

    auto const& uGetsIssuerID = saTakerGets.getIssuer ();
    auto const& uGetsCurrency = saTakerGets.getCurrency ();

    bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
    bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));

    std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration);
    std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence);

    // FIXME understand why we use SequenceNext instead of current transaction
    //       sequence to determine the transaction. Why is the offer seuqnce
    //       number insufficient?

    std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
    std::uint32_t const uSequence = mTxn.getSequence ();

    const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence);

    if (m_journal.debug)
    {
        m_journal.debug <<
            "Creating offer node: " << to_string (uLedgerIndex) <<
            " uSequence=" << uSequence;

        if (bImmediateOrCancel)
            m_journal.debug << "Transaction: IoC set.";

        if (bFillOrKill)
            m_journal.debug << "Transaction: FoK set.";
    }

    // This is the original rate of this offer, and is the rate at which it will
    // be placed, even if crossing offers change the amounts.
    std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays);

    TER terResult (tesSUCCESS);

    // This is the ledger view that we work against. Transactions are applied
    // as we go on processing transactions.
    core::LedgerView& view (mEngine->view ());

    // This is a checkpoint with just the fees paid. If something goes wrong
    // with this transaction, we roll back to this ledger.
    core::LedgerView view_checkpoint (view);

    view.bumpSeq (); // Begin ledger variance.

    SLE::pointer sleCreator = mEngine->entryCache (
        ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID));

    if (uTxFlags & tfOfferCreateMask)
    {
        if (m_journal.debug) m_journal.debug <<
            "Malformed transaction: Invalid flags set.";

        terResult = temINVALID_FLAG;
    }
    else if (bImmediateOrCancel && bFillOrKill)
    {
        if (m_journal.debug) m_journal.debug <<
            "Malformed transaction: both IoC and FoK set.";

        terResult = temINVALID_FLAG;
    }
    else if (bHaveExpiration && !uExpiration)
    {
        m_journal.warning <<
            "Malformed offer: bad expiration";

        terResult = temBAD_EXPIRATION;
    }
    else if (saTakerPays.isNative () && saTakerGets.isNative ())
    {
        m_journal.warning <<
            "Malformed offer: XRP for XRP";

        terResult = temBAD_OFFER;
    }
    else if (saTakerPays <= zero || saTakerGets <= zero)
    {
        m_journal.warning <<
            "Malformed offer: bad amount";

        terResult = temBAD_OFFER;
    }
    else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
    {
        m_journal.warning <<
            "Malformed offer: redundant offer";

        terResult = temREDUNDANT;
    }
    // We don't allow a non-native currency to use the currency code XRP.
    else if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency)
    {
        m_journal.warning <<
            "Malformed offer: Bad currency.";

        terResult = temBAD_CURRENCY;
    }
    else if (saTakerPays.isNative () != !uPaysIssuerID ||
             saTakerGets.isNative () != !uGetsIssuerID)
    {
        m_journal.warning <<
            "Malformed offer: bad issuer";

        terResult = temBAD_ISSUER;
    }
    else if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID))
    {
        m_journal.warning <<
            "Offer involves frozen asset";

        terResult = tecFROZEN;
    }
    else if (view.accountFunds (
        mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero)
    {
        m_journal.warning <<
            "delay: Offers must be at least partially funded.";

        terResult = tecUNFUNDED_OFFER;
    }
    // This can probably be simplified to make sure that you cancel sequences
    // before the transaction sequence number.
    else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
    {
        if (m_journal.debug) m_journal.debug <<
            "uAccountSequenceNext=" << uAccountSequenceNext <<
            " uOfferSequence=" << uCancelSequence;

        terResult = temBAD_SEQUENCE;
    }

    if (terResult != tesSUCCESS)
    {
        if (m_journal.debug) m_journal.debug <<
            "final terResult=" << transToken (terResult);

        return terResult;
    }

    // Process a cancellation request that's passed along with an offer.
    if ((terResult == tesSUCCESS) && bHaveCancel)
    {
        uint256 const uCancelIndex (
            Ledger::getOfferIndex (mTxnAccountID, uCancelSequence));
        SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex);

        // It's not an error to not find the offer to cancel: it might have
        // been consumed or removed as we are processing.
        if (sleCancel)
        {
            m_journal.warning <<
                "Cancelling order with sequence " << uCancelSequence;

            terResult = view.offerDelete (sleCancel);
        }
    }

    // Expiration is defined in terms of the close time of the parent ledger,
    // because we definitively know the time that it closed but we do not
    // know the closing time of the ledger that is under construction.
    if (bHaveExpiration &&
        (mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration))
    {
        return tesSUCCESS;
    }

    // Make sure that we are authorized to hold what the taker will pay us.
    if (terResult == tesSUCCESS && !saTakerPays.isNative ())
        terResult = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID));

    bool crossed = false;
    bool const bOpenLedger (mParams & tapOPEN_LEDGER);

    if (terResult == tesSUCCESS)
    {
        // We reverse gets and pays because during offer crossing we are taking.
        core::Amounts const taker_amount (saTakerGets, saTakerPays);

        // The amount of the offer that we will need to place, after we finish
        // offer crossing processing. It may be equal to the original amount,
        // empty (fully crossed), or something in-between.
        core::Amounts place_offer;

        std::tie(terResult, place_offer) = crossOffers (view, taker_amount);

        if (terResult == tecFAILED_PROCESSING && bOpenLedger)
            terResult = telFAILED_PROCESSING;

        if (terResult == tesSUCCESS)
        {
            // We now need to reduce the offer by the cross flow. We reverse
            // in and out here, since during crossing we were takers.
            assert (saTakerPays.getCurrency () == place_offer.out.getCurrency ());
            assert (saTakerPays.getIssuer () == place_offer.out.getIssuer ());
            assert (saTakerGets.getCurrency () == place_offer.in.getCurrency ());
            assert (saTakerGets.getIssuer () == place_offer.in.getIssuer ());

            if (taker_amount != place_offer)
                crossed = true;

            if (m_journal.debug)
            {
                m_journal.debug << "Offer Crossing: " << transToken (terResult);

                if (terResult == tesSUCCESS)
                {
                    m_journal.debug <<
                        "    takerPays: " << saTakerPays.getFullText () <<
                        " -> " << place_offer.out.getFullText ();
                    m_journal.debug <<
                        "    takerGets: " << saTakerGets.getFullText () <<
                        " -> " << place_offer.in.getFullText ();
                }
            }

            saTakerPays = place_offer.out;
            saTakerGets = place_offer.in;
        }
    }

    if (terResult != tesSUCCESS)
    {
        m_journal.debug <<
            "final terResult=" << transToken (terResult);

        return terResult;
    }

    if (m_journal.debug)
    {
        m_journal.debug <<
            "takeOffers: saTakerPays=" <<saTakerPays.getFullText ();
        m_journal.debug <<
            "takeOffers: saTakerGets=" << saTakerGets.getFullText ();
        m_journal.debug <<
            "takeOffers: mTxnAccountID=" <<
            to_string (mTxnAccountID);
        m_journal.debug <<
            "takeOffers:         FUNDS=" << view.accountFunds (
            mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN).getFullText ();
    }

    if (saTakerPays < zero || saTakerGets < zero)
    {
        // Earlier, we verified that the amounts, as specified in the offer,
        // were not negative. That they are now suggests that something went
        // very wrong with offer crossing.
        m_journal.fatal << (crossed ? "Partially consumed" : "Full") <<
            " offer has negative component:" <<
            " pays=" << saTakerPays.getFullText () <<
            " gets=" << saTakerGets.getFullText ();

        assert (saTakerPays >= zero);
        assert (saTakerGets >= zero);
        return tefINTERNAL;
    }

    if (bFillOrKill && (saTakerPays != zero || saTakerGets != zero))
    {
        // Fill or kill and have leftovers.
        view.swapWith (view_checkpoint); // Restore with just fees paid.
        return tesSUCCESS;
    }

    // What the reserve would be if this offer was placed.
    auto const accountReserve (mEngine->getLedger ()->getReserve (
        sleCreator->getFieldU32 (sfOwnerCount) + 1));

    if (saTakerPays == zero ||                // Wants nothing more.
        saTakerGets == zero ||                // Offering nothing more.
        bImmediateOrCancel)                   // Do not persist.
    {
        // Complete as is.
    }
    else if (mPriorBalance.getNValue () < accountReserve)
    {
        // If we are here, the signing account had an insufficient reserve
        // *prior* to our processing. We use the prior balance to simplify
        // client writing and make the user experience better.

        if (bOpenLedger) // Ledger is not final, can vote no.
        {
            // Hope for more reserve to come in or more offers to consume. If we
            // specified a local error this transaction will not be retried, so
            // specify a tec to distribute the transaction and allow it to be
            // retried. In particular, it may have been successful to a
            // degree (partially filled) and if it hasn't, it might succeed.
            terResult = tecINSUF_RESERVE_OFFER;
        }
        else if (!crossed)
        {
            // Ledger is final, insufficent reserve to create offer, processed
            // nothing.
            terResult = tecINSUF_RESERVE_OFFER;
        }
        else
        {
            // Ledger is final, insufficent reserve to create offer, processed
            // something.

            // Consider the offer unfunded. Treat as tesSUCCESS.
        }
    }
    else
    {
        assert (saTakerPays > zero);
        assert (saTakerGets > zero);

        // We need to place the remainder of the offer into its order book.
        if (m_journal.debug) m_journal.debug <<
            "offer not fully consumed:" <<
            " saTakerPays=" << saTakerPays.getFullText () <<
            " saTakerGets=" << saTakerGets.getFullText ();

        std::uint64_t uOwnerNode;
        std::uint64_t uBookNode;
        uint256 uDirectory;

        // Add offer to owner's directory.
        terResult = view.dirAdd (uOwnerNode,
            Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
            std::bind (
                &Ledger::ownerDirDescriber, std::placeholders::_1,
                std::placeholders::_2, mTxnAccountID));

        if (tesSUCCESS == terResult)
        {
            // Update owner count.
            view.ownerCountAdjust (mTxnAccountID, 1, sleCreator);

            uint256 const uBookBase (Ledger::getBookBase (
                {{uPaysCurrency, uPaysIssuerID},
                    {uGetsCurrency, uGetsIssuerID}}));

            if (m_journal.debug) m_journal.debug <<
                "adding to book: " << to_string (uBookBase) <<
                " : " << saTakerPays.getHumanCurrency () <<
                "/" << to_string (saTakerPays.getIssuer ()) <<
                " -> " << saTakerGets.getHumanCurrency () <<
                "/" << to_string (saTakerGets.getIssuer ());

            // We use the original rate to place the offer.
            uDirectory = Ledger::getQualityIndex (uBookBase, uRate);

            // Add offer to order book.
            terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex,
                std::bind (
                    &Ledger::qualityDirDescriber, std::placeholders::_1,
                    std::placeholders::_2, saTakerPays.getCurrency (),
                    uPaysIssuerID, saTakerGets.getCurrency (),
                    uGetsIssuerID, uRate));
        }

        if (tesSUCCESS == terResult)
        {
            if (m_journal.debug)
            {
                m_journal.debug <<
                    "sfAccount=" <<
                    to_string (mTxnAccountID);
                m_journal.debug <<
                    "uPaysIssuerID=" <<
                    to_string (uPaysIssuerID);
                m_journal.debug <<
                    "uGetsIssuerID=" <<
                    to_string (uGetsIssuerID);
                m_journal.debug <<
                    "saTakerPays.isNative()=" <<
                    saTakerPays.isNative ();
                m_journal.debug <<
                    "saTakerGets.isNative()=" <<
                    saTakerGets.isNative ();
                m_journal.debug <<
                    "uPaysCurrency=" <<
                    saTakerPays.getHumanCurrency ();
                m_journal.debug <<
                    "uGetsCurrency=" <<
                    saTakerGets.getHumanCurrency ();
            }

            SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex));

            sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
            sleOffer->setFieldU32 (sfSequence, uSequence);
            sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
            sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
            sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
            sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
            sleOffer->setFieldU64 (sfBookNode, uBookNode);

            if (uExpiration)
                sleOffer->setFieldU32 (sfExpiration, uExpiration);

            if (bPassive)
                sleOffer->setFlag (lsfPassive);

            if (bSell)
                sleOffer->setFlag (lsfSell);

            if (m_journal.debug) m_journal.debug <<
                "final terResult=" << transToken (terResult) <<
                " sleOffer=" << sleOffer->getJson (0);
        }
    }

    if (terResult != tesSUCCESS)
    {
        m_journal.debug <<
            "final terResult=" << transToken (terResult);
    }

    return terResult;
}
Exemple #21
0
// Append a node and insert before it any implied nodes.
// Offers may go back to back.
// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
TER PathState::pushNode (
    const int iType,
    const uint160& uAccountID,
    const uint160& uCurrencyID,
    const uint160& uIssuerID)
{
    Node                pnCur;
    const bool          bFirst      = vpnNodes.empty ();
    const Node&         pnPrv       = bFirst ? Node () : vpnNodes.back ();
    // true, iff node is a ripple account. false, iff node is an offer node.
    const bool          bAccount    = is_bit_set (iType, STPathElement::typeAccount);
    // true, iff currency supplied.
    // Currency is specified for the output of the current node.
    const bool          bCurrency   = is_bit_set (iType, STPathElement::typeCurrency);
    // Issuer is specified for the output of the current node.
    const bool          bIssuer     = is_bit_set (iType, STPathElement::typeIssuer);
    TER                 terResult   = tesSUCCESS;

    WriteLog (lsTRACE, RippleCalc) << "pushNode> " <<
       iType <<
       ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-") <<
       " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") <<
       "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");

    pnCur.uFlags        = iType;
    pnCur.uCurrencyID   = bCurrency ? uCurrencyID : pnPrv.uCurrencyID;

    if (iType & ~STPathElement::typeValidBits)
    {
        WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";

        terResult   = temBAD_PATH;
    }
    else if (bIssuer && !pnCur.uCurrencyID)
    {
        WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for STR.";

        terResult   = temBAD_PATH;
    }
    else if (bIssuer && !uIssuerID)
    {
        WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";

        terResult   = temBAD_PATH;
    }
    else if (!bAccount && !bCurrency && !bIssuer)
    {
        WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";

        terResult   = temBAD_PATH;
    }
    else if (bAccount)
    {
        // Account link

        pnCur.uAccountID    = uAccountID;
        pnCur.uIssuerID     = bIssuer
                              ? uIssuerID
                              : !!pnCur.uCurrencyID
                              ? uAccountID
                              : ACCOUNT_STR;
        pnCur.saRevRedeem   = STAmount (pnCur.uCurrencyID, uAccountID);
        pnCur.saRevIssue    = STAmount (pnCur.uCurrencyID, uAccountID);
        pnCur.saRevDeliver  = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
        pnCur.saFwdDeliver  = pnCur.saRevDeliver;

        if (bFirst)
        {
            // The first node is always correct as is.

            nothing ();
        }
        else if (!uAccountID)
        {
            WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";

            terResult   = temBAD_PATH;
        }
        else
        {
            // Add required intermediate nodes to deliver to current account.
            WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";

            terResult   = pushImply (
                              pnCur.uAccountID,                                   // Current account.
                              pnCur.uCurrencyID,                                  // Wanted currency.
                              !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_STR);    // Account as wanted issuer.

            // Note: pnPrv may no longer be the immediately previous node.
        }

        if (tesSUCCESS == terResult && !vpnNodes.empty ())
        {
            const Node&     pnBck       = vpnNodes.back ();
            bool            bBckAccount = is_bit_set (pnBck.uFlags, STPathElement::typeAccount);

            if (bBckAccount)
            {
                SLE::pointer    sleRippleState  = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID));

                if (!sleRippleState)
                {
                    WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
                                                   << RippleAddress::createHumanAccountID (pnBck.uAccountID)
                                                   << " and "
                                                   << RippleAddress::createHumanAccountID (pnCur.uAccountID)
                                                   << " for "
                                                   << STAmount::createHumanCurrency (pnCur.uCurrencyID)
                                                   << "." ;

                    WriteLog (lsTRACE, RippleCalc) << getJson ();

                    terResult   = terNO_LINE;
                }
                else
                {
                    WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
                                                   << RippleAddress::createHumanAccountID (pnBck.uAccountID)
                                                   << " and "
                                                   << RippleAddress::createHumanAccountID (pnCur.uAccountID)
                                                   << " for "
                                                   << STAmount::createHumanCurrency (pnCur.uCurrencyID)
                                                   << "." ;

                    SLE::pointer        sleBck  = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
                    bool                bHigh   = pnBck.uAccountID > pnCur.uAccountID;

                    if (!sleBck)
                    {
                        WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);

                        terResult   = terNO_ACCOUNT;
                    }
                    else if ((is_bit_set (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
                             && !is_bit_set (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))) 
                    {
                        WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";

                        terResult   = terNO_AUTH;
                    }

                    if (tesSUCCESS == terResult)
                    {
                        STAmount    saOwed  = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID);
                        STAmount    saLimit;

                        if (saOwed <= zero
                                && -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)))
                        {
                            WriteLog (lsWARNING, RippleCalc) <<
                                "pushNode: dry:" <<
                                " saOwed=" << saOwed <<
                                " saLimit=" << saLimit;

                            terResult   = tecPATH_DRY;
                        }
                    }
                }
            }
        }

        if (tesSUCCESS == terResult)
        {
            vpnNodes.push_back (pnCur);
        }
    }
    else
    {
        // Offer link
        // Offers bridge a change in currency & issuer or just a change in issuer.
        pnCur.uIssuerID     = bIssuer
                              ? uIssuerID
                              : !!pnCur.uCurrencyID
                              ? !!pnPrv.uIssuerID
                              ? pnPrv.uIssuerID   // Default to previous issuer
                              : pnPrv.uAccountID  // Or previous account if no previous issuer.
                      : ACCOUNT_STR;
        pnCur.saRateMax     = saZero;
        pnCur.saRevDeliver  = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
        pnCur.saFwdDeliver  = pnCur.saRevDeliver;

        if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID)
        {
            WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer.";

            terResult   = temBAD_PATH;
        }
        else if(!LedgerDump::enactHistoricalQuirk (QuirkSameCurrencyOffer) &&
                pnPrv.uCurrencyID == pnCur.uCurrencyID &&
                pnPrv.uIssuerID == pnCur.uIssuerID)
        {
            WriteLog(lsDEBUG, RippleCalc) <<
                "pushNode: bad path: offer to same currency and issuer";
            terResult = temBAD_PATH;
        }
        else
        {
            // Previous is an account.
            WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";

            // Insert intermediary issuer account if needed.
            terResult   = pushImply (
                ACCOUNT_STR, // Rippling, but offers don't have an account.
                pnPrv.uCurrencyID,
                pnPrv.uIssuerID);
        }

        if (tesSUCCESS == terResult)
        {
            vpnNodes.push_back (pnCur);
        }
    }

    WriteLog (lsTRACE, RippleCalc) << "pushNode< : " << transToken (terResult);

    return terResult;
}
Exemple #22
0
    TER doApply () override
    {
        assert (mTxnAccount);

        // A ticket counts against the reserve of the issuing account, but we check
        // the starting balance because we want to allow dipping into the reserve to
        // pay fees.
        auto const accountReserve (mEngine->getLedger ()->getReserve (
            mTxnAccount->getFieldU32 (sfOwnerCount) + 1));

        if (mPriorBalance.getNValue () < accountReserve)
            return tecINSUFFICIENT_RESERVE;

        std::uint32_t expiration (0);

        if (mTxn.isFieldPresent (sfExpiration))
        {
            expiration = mTxn.getFieldU32 (sfExpiration);

            if (!expiration)
            {
                m_journal.warning <<
                    "Malformed ticket requestion: bad expiration";

                return temBAD_EXPIRATION;
            }

            if (mEngine->getLedger ()->getParentCloseTimeNC () >= expiration)
                return tesSUCCESS;
        }

        SLE::pointer sleTicket = mEngine->entryCreate (ltTICKET,
            Ledger::getTicketIndex (mTxnAccountID, mTxn.getSequence ()));

        sleTicket->setFieldAccount (sfAccount, mTxnAccountID);
        sleTicket->setFieldU32 (sfSequence, mTxn.getSequence ());

        if (expiration != 0)
            sleTicket->setFieldU32 (sfExpiration, expiration);

        if (mTxn.isFieldPresent (sfTarget))
        {
            Account const target_account (mTxn.getFieldAccount160 (sfTarget));

            SLE::pointer sleTarget = mEngine->entryCache (ltACCOUNT_ROOT,
                Ledger::getAccountRootIndex (target_account));

            // Destination account does not exist.
            if (!sleTarget)
                return tecNO_TARGET;

            // The issuing account is the default account to which the ticket
            // applies so don't bother saving it if that's what's specified.
            if (target_account != mTxnAccountID)
                sleTicket->setFieldAccount (sfTarget, target_account);
        }

        std::uint64_t hint;

        auto describer = [&](SLE::pointer p, bool b)
        {
            Ledger::ownerDirDescriber(p, b, mTxnAccountID);
        };
        TER result = mEngine->view ().dirAdd (
            hint,
            Ledger::getOwnerDirIndex (mTxnAccountID),
            sleTicket->getIndex (),
            describer);

        m_journal.trace <<
            "Creating ticket " << to_string (sleTicket->getIndex ()) <<
            ": " << transHuman (result);

        if (result != tesSUCCESS)
            return result;

        sleTicket->setFieldU64(sfOwnerNode, hint);

        // If we succeeded, the new entry counts agains the creator's reserve.
        mEngine->view ().incrementOwnerCount (mTxnAccount);

        return result;
    }
Exemple #23
0
    TER doApply () override
    {
        // Divvy if source or destination is non-native or if there are paths.
        std::uint32_t const uTxFlags = mTxn.getFlags ();
        bool const partialPaymentAllowed = uTxFlags & tfPartialPayment;
        bool const limitQuality = uTxFlags & tfLimitQuality;
        bool const defaultPathsAllowed = !(uTxFlags & tfNoDivvyDirect);
        bool const bPaths = mTxn.isFieldPresent (sfPaths);
        bool const bMax = mTxn.isFieldPresent (sfSendMax);
        AccountID const uDstAccountID (mTxn.getFieldAccount160 (sfDestination));
        STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
        STAmount maxSourceAmount;
        if (bMax)
            maxSourceAmount = mTxn.getFieldAmount (sfSendMax);
        else if (saDstAmount.native ())
            maxSourceAmount = saDstAmount;
        else
          maxSourceAmount = STAmount (
              {saDstAmount.getCurrency (), mTxnAccountID},
              saDstAmount.mantissa(), saDstAmount.exponent (),
              saDstAmount < zero);

        m_journal.trace <<
            "maxSourceAmount=" << maxSourceAmount.getFullText () <<
            " saDstAmount=" << saDstAmount.getFullText ();

        // Open a ledger for editing.
        auto const index = getAccountRootIndex (uDstAccountID);
        SLE::pointer sleDst (mEngine->view().entryCache (ltACCOUNT_ROOT, index));

        if (!sleDst)
        {
            // Destination account does not exist.
            if (!saDstAmount.native ())
            {
                m_journal.trace <<
                    "Delay transaction: Destination account does not exist.";

                // Another transaction could create the account and then this
                // transaction would succeed.
                return tecNO_DST;
            }
            else if (mParams & tapOPEN_LEDGER && partialPaymentAllowed)
            {
                // You cannot fund an account with a partial payment.
                // Make retry work smaller, by rejecting this.
                m_journal.trace <<
                    "Delay transaction: Partial payment not allowed to create account.";


                // Another transaction could create the account and then this
                // transaction would succeed.
                return telNO_DST_PARTIAL;
            }
            else if (saDstAmount < STAmount (mEngine->getLedger ()->getReserve (0)))
            {
                // getReserve() is the minimum amount that an account can have.
                // Reserve is not scaled by load.
                m_journal.trace <<
                    "Delay transaction: Destination account does not exist. " <<
                    "Insufficent payment to create account.";

                // TODO: dedupe
                // Another transaction could create the account and then this
                // transaction would succeed.
                return tecNO_DST_INSUF_XDV;
            }

            // Create the account.
            sleDst = std::make_shared<SLE>(ltACCOUNT_ROOT,
                getAccountRootIndex (uDstAccountID));
            sleDst->setFieldAccount (sfAccount, uDstAccountID);
            sleDst->setFieldU32 (sfSequence, 1);
            mEngine->view().entryCreate(sleDst);
        }
        else if ((sleDst->getFlags () & lsfRequireDestTag) &&
                 !mTxn.isFieldPresent (sfDestinationTag))
        {
            // The tag is basically account-specific information we don't
            // understand, but we can require someone to fill it in.

            // We didn't make this test for a newly-formed account because there's
            // no way for this field to be set.
            m_journal.trace << "Malformed transaction: DestinationTag required.";

            return tecDST_TAG_NEEDED;
        }
        else
        {
            // Tell the engine that we are intending to change the the destination
            // account.  The source account gets always charged a fee so it's always
            // marked as modified.
            mEngine->view().entryModify (sleDst);
        }

        TER terResult;

        bool const bDivvy = bPaths || bMax || !saDstAmount.native ();
        // XXX Should bMax be sufficient to imply divvy?

        if (bDivvy)
        {
            // Divvy payment with at least one intermediate step and uses
            // transitive balances.

            // Copy paths into an editable class.
            STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths);

            try
            {
                path::DivvyCalc::Input rcInput;
                rcInput.partialPaymentAllowed = partialPaymentAllowed;
                rcInput.defaultPathsAllowed = defaultPathsAllowed;
                rcInput.limitQuality = limitQuality;
                rcInput.deleteUnfundedOffers = true;
                rcInput.isLedgerOpen = static_cast<bool>(mParams & tapOPEN_LEDGER);

                bool pathTooBig = spsPaths.size () > MaxPathSize;

                for (auto const& path : spsPaths)
                    if (path.size () > MaxPathLength)
                        pathTooBig = true;

                if (rcInput.isLedgerOpen && pathTooBig)
                {
                    terResult = telBAD_PATH_COUNT; // Too many paths for proposed ledger.
                }
                else
                {

                    path::DivvyCalc::Output rc;
                    {
                        ScopedDeferCredits g (mEngine->view ());
                        rc = path::DivvyCalc::divvyCalculate (
                            mEngine->view (),
                            maxSourceAmount,
                            saDstAmount,
                            uDstAccountID,
                            mTxnAccountID,
                            spsPaths,
                            &rcInput);
                    }

                    // TODO: is this right?  If the amount is the correct amount, was
                    // the delivered amount previously set?
                    if (rc.result () == tesSUCCESS && rc.actualAmountOut != saDstAmount)
                        mEngine->view ().setDeliveredAmount (rc.actualAmountOut);

                    terResult = rc.result ();
                }

                // TODO(tom): what's going on here?
                if (isTerRetry (terResult))
                    terResult = tecPATH_DRY;

            }
            catch (std::exception const& e)
            {
                m_journal.trace <<
                    "Caught throw: " << e.what ();

                terResult = tefEXCEPTION;
            }
        }
        else
        {
            // Direct XDV payment.

            // uOwnerCount is the number of entries in this legder for this
            // account that require a reserve.
            auto const uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);

            // This is the total reserve in drops.
            std::uint64_t const uReserve =
                mEngine->getLedger ()->getReserve (uOwnerCount);

            // mPriorBalance is the balance on the sending account BEFORE the
            // fees were charged. We want to make sure we have enough reserve
            // to send. Allow final spend to use reserve for fee.
            auto const mmm = std::max(mTxn.getTransactionFee (),
                STAmount (uReserve));

            if (mPriorBalance < saDstAmount + mmm)
            {
                // Vote no. However the transaction might succeed, if applied in
                // a different order.
                m_journal.trace << "Delay transaction: Insufficient funds: " <<
                    " " << mPriorBalance.getText () <<
                    " / " << (saDstAmount + mmm).getText () <<
                    " (" << uReserve << ")";

                terResult = tecUNFUNDED_PAYMENT;
            }
            else
            {
                // The source account does have enough money, so do the
                // arithmetic for the transfer and make the ledger change.
                mTxnAccount->setFieldAmount (sfBalance,
                    mSourceBalance - saDstAmount);
                sleDst->setFieldAmount (sfBalance,
                    sleDst->getFieldAmount (sfBalance) + saDstAmount);

                // Re-arm the password change fee if we can and need to.
                if ((sleDst->getFlags () & lsfPasswordSpent))
                    sleDst->clearFlag (lsfPasswordSpent);

                terResult = tesSUCCESS;
            }
        }

        std::string strToken;
        std::string strHuman;

        if (transResultInfo (terResult, strToken, strHuman))
        {
            m_journal.trace <<
                strToken << ": " << strHuman;
        }
        else
        {
            assert (false);
        }

        return terResult;
    }
Exemple #24
0
TER TransactionEngine::applyTransaction (
    STTx const& txn,
    TransactionEngineParams params,
    bool& didApply)
{
    WriteLog (lsTRACE, TransactionEngine) << "applyTransaction>";
    didApply = false;
    assert (mLedger);
    uint256 const& txID = txn.getTransactionID ();
    mNodes.init (mLedger, txID, mLedger->getLedgerSeq (), params);

#ifdef BEAST_DEBUG
    if (1)
    {
        Serializer ser;
        txn.add (ser);
        SerializerIterator sit (ser);
        STTx s2 (sit);

        if (!s2.isEquivalent (txn))
        {
            WriteLog (lsFATAL, TransactionEngine) <<
                "Transaction serdes mismatch";
            WriteLog (lsINFO, TransactionEngine) << txn.getJson (0);
            WriteLog (lsFATAL, TransactionEngine) << s2.getJson (0);
            assert (false);
        }
    }
#endif

    if (!txID)
    {
        WriteLog (lsWARNING, TransactionEngine) <<
            "applyTransaction: invalid transaction id";
        return temINVALID;
    }

    TER terResult = Transactor::transact (txn, params, this);

    if (terResult == temUNKNOWN)
    {
        WriteLog (lsWARNING, TransactionEngine) <<
            "applyTransaction: Invalid transaction: unknown transaction type";
        return temUNKNOWN;
    }

    if (ShouldLog (lsDEBUG, TransactionEngine))
    {
        std::string strToken;
        std::string strHuman;

        transResultInfo (terResult, strToken, strHuman);

        WriteLog (lsDEBUG, TransactionEngine) <<
            "applyTransaction: terResult=" << strToken <<
            " : " << terResult <<
            " : " << strHuman;
    }

    if (isTesSuccess (terResult))
        didApply = true;
    else if (isTecClaim (terResult) && !(params & tapRETRY))
    {
        // only claim the transaction fee
        WriteLog (lsDEBUG, TransactionEngine) << "Reprocessing to only claim fee";
        mNodes.clear ();

        SLE::pointer txnAcct = entryCache (ltACCOUNT_ROOT,
            getAccountRootIndex (txn.getSourceAccount ()));

        if (!txnAcct)
            terResult = terNO_ACCOUNT;
        else
        {
            std::uint32_t t_seq = txn.getSequence ();
            std::uint32_t a_seq = txnAcct->getFieldU32 (sfSequence);

            if (a_seq < t_seq)
                terResult = terPRE_SEQ;
            else if (a_seq > t_seq)
                terResult = tefPAST_SEQ;
            else
            {
                STAmount fee        = txn.getTransactionFee ();
                STAmount balance    = txnAcct->getFieldAmount (sfBalance);
                STAmount balanceVBC = txnAcct->getFieldAmount(sfBalanceVBC);

                // We retry/reject the transaction if the account
                // balance is zero or we're applying against an open
                // ledger and the balance is less than the fee
                if ((balance == zero) || (balanceVBC.getNValue() == 0) ||
                    ((params & tapOPEN_LEDGER) && (balance < fee)))
                {
                    // Account has no funds or ledger is open
                    terResult = terINSUF_FEE_B;
                }
                else
                {
                    if (fee > balance)
                        fee = balance;
                    txnAcct->setFieldAmount (sfBalance, balance - fee);
                    txnAcct->setFieldAmount(sfBalanceVBC, balanceVBC);
                    txnAcct->setFieldU32 (sfSequence, t_seq + 1);
                    entryModify (txnAcct);
                    didApply = true;
                }
            }
        }
    }
    else
        WriteLog (lsDEBUG, TransactionEngine) << "Not applying transaction " << txID;

    if (didApply)
    {
        if (!checkInvariants (terResult, txn, params))
        {
            WriteLog (lsFATAL, TransactionEngine) <<
                "Transaction violates invariants";
            WriteLog (lsFATAL, TransactionEngine) <<
                txn.getJson (0);
            WriteLog (lsFATAL, TransactionEngine) <<
                transToken (terResult) << ": " << transHuman (terResult);
            WriteLog (lsFATAL, TransactionEngine) <<
                mNodes.getJson (0);
            didApply = false;
            terResult = tefINTERNAL;
        }
        else
        {
            // Transaction succeeded fully or (retries are not allowed and the
            // transaction could claim a fee)
            Serializer m;
            mNodes.calcRawMeta (m, terResult, mTxnSeq++);

            txnWrite ();

            Serializer s;
            txn.add (s);

            if (params & tapOPEN_LEDGER)
            {
                if (!mLedger->addTransaction (txID, s))
                {
                    WriteLog (lsFATAL, TransactionEngine) <<
                        "Tried to add transaction to open ledger that already had it";
                    assert (false);
                    throw std::runtime_error ("Duplicate transaction applied");
                }
            }
            else
            {
                if (!mLedger->addTransaction (txID, s, m))
                {
                    WriteLog (lsFATAL, TransactionEngine) <<
                        "Tried to add transaction to ledger that already had it";
                    assert (false);
                    throw std::runtime_error ("Duplicate transaction applied to closed ledger");
                }

                // Charge whatever fee they specified.
                STAmount saPaid = txn.getTransactionFee ();
                mLedger->destroyCoins (saPaid.getNValue ());
            }
        }
    }

    mTxnAccount.reset ();
    mNodes.clear ();

    if (!(params & tapOPEN_LEDGER) && isTemMalformed (terResult))
    {
        // XXX Malformed or failed transaction in closed ledger must bow out.
    }

    return terResult;
}
TER TransferTransactor::doApply ()
{
    WriteLog (lsTRACE, LedgerConsensus) << "Transfer transaction is applying\n\n\n\n\n\n";
    uint160 const uDstAccountID = mTxn.getFieldAccount160 (sfDestination);
    uint256 const objectId = mTxn.getObjectId ();
    WriteLog (lsTRACE, LedgerConsensus) << "Transfer transaction is applying";
    WriteLog (lsTRACE, LedgerConsensus) << "___________________________________applying";
    WriteLog (lsTRACE, LedgerConsensus) << "\n\n\n\n\n\n";


    WriteLog (lsTRACE, LedgerConsensus) << uDstAccountID;
    WriteLog (lsTRACE, LedgerConsensus) << objectId;


    if (!uDstAccountID)
    {
        m_journal.trace <<
            "Malformed transaction: Transfer destination account not specified.";

        return temDST_NEEDED;
    }
    WriteLog (lsTRACE, LedgerConsensus) << "Checking object in db \n\n\n\n\n\n";

    SLE::pointer sleObj (mEngine->entryCache (
        ltOWNERSHIP, objectId));
    WriteLog (lsTRACE, LedgerConsensus) << objectId << " \n\n\n\n\n\n";

    WriteLog (lsTRACE, LedgerConsensus) << "Checked object in db \n\n\n\n\n\n";
    WriteLog (lsTRACE, LedgerConsensus) << sleObj<< " \n\n\n\n\n\n";

    if (sleObj)
    {
        // Object  exists.
        WriteLog (lsTRACE, LedgerConsensus) << "Object  exists \n\n\n\n\n\n Source account";
        WriteLog (lsTRACE, LedgerConsensus) << mTxn.getSourceAccount().getAccountID();
        WriteLog (lsTRACE, LedgerConsensus) << "Object  exists \n\n\n\n\n\n New account";

        WriteLog (lsTRACE, LedgerConsensus) << uDstAccountID;


        if(sleObj->getFieldAccount160(sfAccount) != mTxn.getSourceAccount().getAccountID())
        {
            WriteLog (lsTRACE, LedgerConsensus) << "Not your object \n\n\n\n\n\n";
            return temMALFORMED;
        }

        SLE::pointer sleDst (mEngine->entryCache (
            ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID)));

        if (!sleDst)
        {
            WriteLog (lsTRACE, LedgerConsensus) << "Destination account doesnt exist \n\n\n\n\n\n";
            return temMALFORMED;
        }



        sleObj->setFieldAccount (sfAccount, uDstAccountID);

        WriteLog (lsTRACE, LedgerConsensus) << "New dstAccount is set \n\n\n\n\n\n";

    }
    else
    {
        WriteLog (lsTRACE, LedgerConsensus) << "You cannot make Transfer to non-existing object \n\n\n\n\n\n";
        return temMALFORMED;
    }

    WriteLog (lsTRACE, LedgerConsensus) << "Transfer applying is finished \n\n\n\n\n\n";


    return tesSUCCESS;
}
// Take as much as possible. Adjusts account balances. Charges fees on top to taker.
// -->    uBookBase: The order book to take against.
// -->  saTakerPays: What the taker offers (w/ issuer)
// -->  saTakerGets: What the taker wanted (w/ issuer)
// <--  saTakerPaid: What taker could have paid including saved not including fees. To reduce an offer.
// <--   saTakerGot: What taker got not including fees. To reduce an offer.
// <--    terResult: tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or tecFAILED_PROCESSING
// <--    bUnfunded: if tesSUCCESS, consider offer unfunded after taking.
TER OfferCreateTransactor::takeOffers (
    const bool          bOpenLedger,
    const bool          bPassive,
    const bool          bSell,
    uint256 const&      uBookBase,
    const uint160&      uTakerAccountID,
    SLE::ref            sleTakerAccount,
    const STAmount&     saTakerPays,
    const STAmount&     saTakerGets,
    STAmount&           saTakerPaid,
    STAmount&           saTakerGot,
    bool&               bUnfunded)
{
    // The book has the most elements. Take the perspective of the book.
    // Book is ordered for taker: taker pays / taker gets (smaller is better)

    // The order is for the other books currencys for get and pays are opposites.
    // We want the same ratio for the respective currencies.
    // So we swap paid and gets for determing take quality.

    assert (saTakerPays && saTakerGets);

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString ();

    LedgerEntrySet&         lesActive           = mEngine->getNodes ();
    const uint64            uTakeQuality        = STAmount::getRate (saTakerGets, saTakerPays);
    STAmount                saTakerRate         = STAmount::setRate (uTakeQuality);
    const uint160           uTakerPaysAccountID = saTakerPays.getIssuer ();
    const uint160           uTakerGetsAccountID = saTakerGets.getIssuer ();
    TER                     terResult           = temUNCERTAIN;

    boost::unordered_set<uint256>   usOfferUnfundedBecame;  // Offers that became unfunded.
    boost::unordered_set<uint160>   usAccountTouched;       // Accounts touched.

    saTakerPaid     = STAmount (saTakerPays.getCurrency (), saTakerPays.getIssuer ());
    saTakerGot      = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ());
    bUnfunded       = false;

    OrderBookIterator bookIterator (lesActive,
        saTakerPays.getCurrency(), saTakerPays.getIssuer(),
        saTakerGets.getCurrency(), saTakerGets.getIssuer());

    while ((temUNCERTAIN == terResult) && bookIterator.nextOffer())
    {
        STAmount        saTakerFunds    = lesActive.accountFunds (uTakerAccountID, saTakerPays);
        STAmount        saSubTakerPays  = saTakerPays - saTakerPaid; // How much more to spend.
        STAmount        saSubTakerGets  = saTakerGets - saTakerGot; // How much more is wanted.
        uint64          uTipQuality     = bookIterator.getCurrentQuality();

        if (!saTakerFunds.isPositive ())
        {
            // Taker is out of funds. Don't create the offer.
            bUnfunded = true;
            terResult = tesSUCCESS;
        }
        else if (!saSubTakerPays.isPositive() || !saSubTakerGets.isPositive())
        {
            // Offer is completely consumed
            terResult = tesSUCCESS;
        }
        else if ((uTakeQuality < uTipQuality)
                 || (bPassive && uTakeQuality == uTipQuality))
        {
            // Offer does not cross this offer
            STAmount    saTipRate           = STAmount::setRate (uTipQuality);

            WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d")
                    % uTakeQuality
                    % (uTakeQuality == uTipQuality
                       ? '='
                       : uTakeQuality < uTipQuality
                       ? '<'
                       : '>')
                    % uTipQuality
                    % saTakerRate
                    % (saTakerRate == saTipRate
                       ? '='
                       : saTakerRate < saTipRate
                       ? '<'
                       : '>')
                    % saTipRate
                    % bPassive);

            terResult   = tesSUCCESS;
        }
        else
        {
            // We have a crossing offer to consider.

            SLE::pointer    sleOffer        = bookIterator.getCurrentOffer ();

            if (!sleOffer)
            { // offer is in directory but not in ledger
                uint256 offerIndex = bookIterator.getCurrentIndex ();
                WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: offer not found : " << offerIndex;
                usMissingOffers.insert (missingOffer_t (
                    bookIterator.getCurrentIndex (), bookIterator.getCurrentDirectory ()));
            }
            else
            {
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0);

                const uint160&  uOfferOwnerID   = sleOffer->getFieldAccount160 (sfAccount);
                STAmount        saOfferPays     = sleOffer->getFieldAmount (sfTakerGets);
                STAmount        saOfferGets     = sleOffer->getFieldAmount (sfTakerPays);

                STAmount        saOfferFunds;   // Funds of offer owner to payout.
                bool            bValid;

                bValid  =  bValidOffer (
                               sleOffer, uOfferOwnerID, saOfferPays, saOfferGets,
                               uTakerAccountID,
                               usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched,
                               saOfferFunds);

                if (bValid)
                {
                    STAmount    saSubTakerPaid;
                    STAmount    saSubTakerGot;
                    STAmount    saTakerIssuerFee;
                    STAmount    saOfferIssuerFee;
                    STAmount    saOfferRate = STAmount::setRate (uTipQuality);

                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPays: " << saTakerPays.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPaid: " << saTakerPaid.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:   saTakerFunds: " << saTakerFunds.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:   saOfferFunds: " << saOfferFunds.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferPays: " << saOfferPays.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferGets: " << saOfferGets.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferRate: " << saOfferRate.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPays: " << saSubTakerPays.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGets: " << saSubTakerGets.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPays: " << saTakerPays.getFullText ();
                    WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerGets: " << saTakerGets.getFullText ();

                    bool    bOfferDelete    = STAmount::applyOffer (
                                                  bSell,
                                                  lesActive.rippleTransferRate (uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID),
                                                  lesActive.rippleTransferRate (uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID),
                                                  saOfferRate,
                                                  saOfferFunds,
                                                  saTakerFunds,
                                                  saOfferPays,
                                                  saOfferGets,
                                                  saSubTakerPays,
                                                  saSubTakerGets,
                                                  saSubTakerPaid,
                                                  saSubTakerGot,
                                                  saTakerIssuerFee,
                                                  saOfferIssuerFee);

                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText ();
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:  saSubTakerGot: " << saSubTakerGot.getFullText ();

                    // Adjust offer

                    // Offer owner will pay less.  Subtract what taker just got.
                    sleOffer->setFieldAmount (sfTakerGets, saOfferPays -= saSubTakerGot);

                    // Offer owner will get less.  Subtract what owner just paid.
                    sleOffer->setFieldAmount (sfTakerPays, saOfferGets -= saSubTakerPaid);

                    mEngine->entryModify (sleOffer);

                    if (bOfferDelete)
                    {
                        // Offer now fully claimed or now unfunded.
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete.";

                        usOfferUnfundedBecame.insert (sleOffer->getIndex()); // Delete unfunded offer on success.

                        // Offer owner's account is no longer pristine.
                        usAccountTouched.insert (uOfferOwnerID);
                    }
                    else if (saSubTakerGot)
                    {
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer partial claim.";

                        if (!saOfferPays.isPositive () || !saOfferGets.isPositive ())
                        {
                            WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: ILLEGAL OFFER RESULT.";
                            bUnfunded   = true;
                            terResult   = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
                        }
                    }
                    else
                    {
                        // Taker got nothing, probably due to rounding. Consider taker unfunded.
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: No claim.";

                        bUnfunded   = true;
                        terResult   = tesSUCCESS;                   // Done.
                    }

                    assert (uTakerGetsAccountID == saSubTakerGot.getIssuer ());
                    assert (uTakerPaysAccountID == saSubTakerPaid.getIssuer ());

                    if (!bUnfunded)
                    {
                        // Distribute funds. The sends charge appropriate fees which are implied by offer.

                        terResult   = lesActive.accountSend (uOfferOwnerID, uTakerAccountID, saSubTakerGot);            // Offer owner pays taker.

                        if (tesSUCCESS == terResult)
                            terResult   = lesActive.accountSend (uTakerAccountID, uOfferOwnerID, saSubTakerPaid);           // Taker pays offer owner.

                        if (bSell)
                        {
                            // Sell semantics:
                            // Reduce amount considered received to original offer's rate.
                            // Not by crossing rate, which is higher.
                            STAmount saEffectiveGot = STAmount::divide(saSubTakerPaid, saTakerRate, saTakerGets);
                            saSubTakerGot = std::min(saEffectiveGot, saSubTakerGot);
                        }
                        else
                        {
                            // Buy semantics: Reduce amount considered paid by taker's rate. Not by actual cost which is lower.
                            // That is, take less as to just satify our buy requirement.
                            STAmount    saTakerCould    = saTakerPays - saTakerPaid;    // Taker could pay.

                            if (saTakerFunds < saTakerCould)
                                saTakerCould    = saTakerFunds;

                            STAmount    saTakerUsed = STAmount::multiply (saSubTakerGot, saTakerRate, saTakerPays);

                            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saTakerCould: " << saTakerCould.getFullText ();
                            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:  saSubTakerGot: " << saSubTakerGot.getFullText ();
                            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerRate: " << saTakerRate.getFullText ();
                            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerUsed: " << saTakerUsed.getFullText ();

                            saSubTakerPaid  = std::min (saTakerCould, saTakerUsed);
                        }

                        saTakerPaid     += saSubTakerPaid;
                        saTakerGot      += saSubTakerGot;

                        if (tesSUCCESS == terResult)
                            terResult   = temUNCERTAIN;
                    }
                }
            }
        }
    }

    if (temUNCERTAIN == terResult)
        terResult = tesSUCCESS;

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult);

    if (tesSUCCESS == terResult)
    {
        // On success, delete offers that became unfunded.
        BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedBecame)
        {
            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: became unfunded: " << uOfferIndex.ToString ();

            terResult   = lesActive.offerDelete (uOfferIndex);

            if (tesSUCCESS != terResult)
                break;
        }
// Take as much as possible. Adjusts account balances. Charges fees on top to taker.
// -->    uBookBase: The order book to take against.
// -->  saTakerPays: What the taker offers (w/ issuer)
// -->  saTakerGets: What the taker wanted (w/ issuer)
// <--  saTakerPaid: What taker could have paid including saved not including fees. To reduce an offer.
// <--   saTakerGot: What taker got not including fees. To reduce an offer.
// <--    terResult: tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or tecFAILED_PROCESSING
// <--    bUnfunded: if tesSUCCESS, consider offer unfunded after taking.
TER OfferCreateTransactor::takeOffers (
    const bool          bOpenLedger,
    const bool          bPassive,
    const bool          bSell,
    uint256 const&      uBookBase,
    const uint160&      uTakerAccountID,
    SLE::ref            sleTakerAccount,
    const STAmount&     saTakerPays,
    const STAmount&     saTakerGets,
    STAmount&           saTakerPaid,
    STAmount&           saTakerGot,
    bool&               bUnfunded)
{
    // The book has the most elements. Take the perspective of the book.
    // Book is ordered for taker: taker pays / taker gets (smaller is better)

    // The order is for the other books currencys for get and pays are opposites.
    // We want the same ratio for the respective currencies.
    // So we swap paid and gets for determing take quality.

    assert (saTakerPays && saTakerGets);

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString ();

    LedgerEntrySet&         lesActive           = mEngine->getNodes ();
    uint256                 uTipIndex           = uBookBase;
    const uint256           uBookEnd            = Ledger::getQualityNext (uBookBase);
    const uint64            uTakeQuality        = STAmount::getRate (saTakerGets, saTakerPays);
    STAmount                saTakerRate         = STAmount::setRate (uTakeQuality);
    const uint160           uTakerPaysAccountID = saTakerPays.getIssuer ();
    const uint160           uTakerGetsAccountID = saTakerGets.getIssuer ();
    TER                     terResult           = temUNCERTAIN;

    boost::unordered_set<uint256>   usOfferUnfundedBecame;  // Offers that became unfunded.
    boost::unordered_set<uint160>   usAccountTouched;       // Accounts touched.

    saTakerPaid     = STAmount (saTakerPays.getCurrency (), saTakerPays.getIssuer ());
    saTakerGot      = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ());
    bUnfunded       = false;

    while (temUNCERTAIN == terResult)
    {
        SLE::pointer    sleOfferDir;
        uint64          uTipQuality     = 0;
        STAmount        saTakerFunds    = lesActive.accountFunds (uTakerAccountID, saTakerPays);
        STAmount        saSubTakerPays  = saTakerPays - saTakerPaid; // How much more to spend.
        STAmount        saSubTakerGets  = saTakerGets - saTakerGot; // How much more is wanted.

        // Figure out next offer to take, if needed.
        if (saTakerFunds.isPositive ()          // Taker has funds available.
                && saSubTakerPays.isPositive ()
                && saSubTakerGets.isPositive ())
        {
            sleOfferDir     = mEngine->entryCache (ltDIR_NODE, lesActive.getNextLedgerIndex (uTipIndex, uBookEnd));

            if (sleOfferDir)
            {
                uTipIndex       = sleOfferDir->getIndex ();
                uTipQuality     = Ledger::getQuality (uTipIndex);

                WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: possible counter offer found: uTipQuality=%d uTipIndex=%s")
                        % uTipQuality
                        % uTipIndex.ToString ());

            }
            else
            {
                WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: counter offer book is empty: "
                        << uTipIndex.ToString ()
                        << " ... "
                        << uBookEnd.ToString ();
            }
        }

        if (!saTakerFunds.isPositive ())                    // Taker has no funds.
        {
            // Done. Ran out of funds on previous round. As fees aren't calculated directly in this routine, funds are checked here.
            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: done: taker unfunded.";

            bUnfunded   = true;                             // Don't create an order.
            terResult   = tesSUCCESS;
        }
        else if (!sleOfferDir                               // No offer directory to take.
                 || uTakeQuality < uTipQuality                   // No offers of sufficient quality available.
                 || (bPassive && uTakeQuality == uTipQuality))
        {
            // Done.
            STAmount    saTipRate           = sleOfferDir ? STAmount::setRate (uTipQuality) : saTakerRate;

            WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: dir=%d uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d")
                    % !!sleOfferDir
                    % uTakeQuality
                    % (uTakeQuality == uTipQuality
                       ? '='
                       : uTakeQuality < uTipQuality
                       ? '<'
                       : '>')
                    % uTipQuality
                    % saTakerRate
                    % (saTakerRate == saTipRate
                       ? '='
                       : saTakerRate < saTipRate
                       ? '<'
                       : '>')
                    % saTipRate
                    % bPassive);

            terResult   = tesSUCCESS;
        }
        else
        {
            // Have an offer directory to consider.
            WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: considering dir: " << sleOfferDir->getJson (0);

            SLE::pointer    sleBookNode;
            unsigned int    uBookEntry;
            uint256         uOfferIndex;

            lesActive.dirFirst (uTipIndex, sleBookNode, uBookEntry, uOfferIndex);

            SLE::pointer    sleOffer        = mEngine->entryCache (ltOFFER, uOfferIndex);

            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0);

            const uint160   uOfferOwnerID   = sleOffer->getFieldAccount160 (sfAccount);
            STAmount        saOfferPays     = sleOffer->getFieldAmount (sfTakerGets);
            STAmount        saOfferGets     = sleOffer->getFieldAmount (sfTakerPays);

            STAmount        saOfferFunds;   // Funds of offer owner to payout.
            bool            bValid;

            bValid  =  bValidOffer (
                           sleOfferDir, uOfferIndex, uOfferOwnerID, saOfferPays, saOfferGets,
                           uTakerAccountID,
                           usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched,
                           saOfferFunds);

            if (bValid)
            {
                STAmount    saSubTakerPaid;
                STAmount    saSubTakerGot;
                STAmount    saTakerIssuerFee;
                STAmount    saOfferIssuerFee;
                STAmount    saOfferRate = STAmount::setRate (uTipQuality);

                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPays: " << saTakerPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPaid: " << saTakerPaid.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saTakerFunds: " << saTakerFunds.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saOfferFunds: " << saOfferFunds.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferPays: " << saOfferPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferGets: " << saOfferGets.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferRate: " << saOfferRate.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPays: " << saSubTakerPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGets: " << saSubTakerGets.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPays: " << saTakerPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerGets: " << saTakerGets.getFullText ();

                bool    bOfferDelete    = STAmount::applyOffer (
                                              bSell,
                                              lesActive.rippleTransferRate (uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID),
                                              lesActive.rippleTransferRate (uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID),
                                              saOfferRate,
                                              saOfferFunds,
                                              saTakerFunds,
                                              saOfferPays,
                                              saOfferGets,
                                              saSubTakerPays,
                                              saSubTakerGets,
                                              saSubTakerPaid,
                                              saSubTakerGot,
                                              saTakerIssuerFee,
                                              saOfferIssuerFee);

                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:  saSubTakerGot: " << saSubTakerGot.getFullText ();

                // Adjust offer

                // Offer owner will pay less.  Subtract what taker just got.
                sleOffer->setFieldAmount (sfTakerGets, saOfferPays -= saSubTakerGot);

                // Offer owner will get less.  Subtract what owner just paid.
                sleOffer->setFieldAmount (sfTakerPays, saOfferGets -= saSubTakerPaid);

                mEngine->entryModify (sleOffer);

                if (bOfferDelete)
                {
                    // Offer now fully claimed or now unfunded.
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete.";

                    usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success.

                    // Offer owner's account is no longer pristine.
                    usAccountTouched.insert (uOfferOwnerID);
                }
                else if (saSubTakerGot)
                {
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer partial claim.";

                    if (!saOfferPays.isPositive () || !saOfferGets.isPositive ())
                    {
                        WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: ILLEGAL OFFER RESULT.";
                        bUnfunded   = true;
                        terResult   = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
                    }
                }
                else
                {
                    // Taker got nothing, probably due to rounding. Consider taker unfunded.
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: No claim.";

                    bUnfunded   = true;
                    terResult   = tesSUCCESS;                   // Done.
                }

                assert (uTakerGetsAccountID == saSubTakerGot.getIssuer ());
                assert (uTakerPaysAccountID == saSubTakerPaid.getIssuer ());

                if (!bUnfunded)
                {
                    // Distribute funds. The sends charge appropriate fees which are implied by offer.

                    terResult   = lesActive.accountSend (uOfferOwnerID, uTakerAccountID, saSubTakerGot);            // Offer owner pays taker.

                    if (tesSUCCESS == terResult)
                        terResult   = lesActive.accountSend (uTakerAccountID, uOfferOwnerID, saSubTakerPaid);           // Taker pays offer owner.

                    if (!bSell)
                    {
                        // Buy semantics: Reduce amount considered paid by taker's rate. Not by actual cost which is lower.
                        // That is, take less as to just satify our buy requirement.
                        STAmount    saTakerCould    = saTakerPays - saTakerPaid;    // Taker could pay.

                        if (saTakerFunds < saTakerCould)
                            saTakerCould    = saTakerFunds;

                        STAmount    saTakerUsed = STAmount::multiply (saSubTakerGot, saTakerRate, saTakerPays);

                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saTakerCould: " << saTakerCould.getFullText ();
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:  saSubTakerGot: " << saSubTakerGot.getFullText ();
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerRate: " << saTakerRate.getFullText ();
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerUsed: " << saTakerUsed.getFullText ();

                        saSubTakerPaid  = std::min (saTakerCould, saTakerUsed);
                    }

                    saTakerPaid     += saSubTakerPaid;
                    saTakerGot      += saSubTakerGot;

                    if (tesSUCCESS == terResult)
                        terResult   = temUNCERTAIN;
                }
            }
        }
    }

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult);

    if (tesSUCCESS == terResult)
    {
        // On success, delete offers that became unfunded.
        BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedBecame)
        {
            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: became unfunded: " << uOfferIndex.ToString ();

            terResult   = lesActive.offerDelete (uOfferIndex);

            if (tesSUCCESS != terResult)
                break;
        }
Exemple #28
0
TER TrustSetTransactor::doApply ()
{
    TER         terResult       = tesSUCCESS;
    WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet>";

    const STAmount      saLimitAmount   = mTxn.getFieldAmount (sfLimitAmount);
    const bool          bQualityIn      = mTxn.isFieldPresent (sfQualityIn);
    const bool          bQualityOut     = mTxn.isFieldPresent (sfQualityOut);
    const uint160       uCurrencyID     = saLimitAmount.getCurrency ();
    uint160             uDstAccountID   = saLimitAmount.getIssuer ();
    const bool          bHigh           = mTxnAccountID > uDstAccountID;        // true, iff current is high account.

    uint32              uQualityIn      = bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0;
    uint32              uQualityOut     = bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0;

    if (!saLimitAmount.isLegalNet ())
        return temBAD_AMOUNT;

    if (bQualityIn && QUALITY_ONE == uQualityIn)
        uQualityIn  = 0;

    if (bQualityOut && QUALITY_ONE == uQualityOut)
        uQualityOut = 0;

    const uint32        uTxFlags        = mTxn.getFlags ();

    if (uTxFlags & tfTrustSetMask)
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Invalid flags set.";

        return temINVALID_FLAG;
    }

    const bool          bSetAuth        = isSetBit (uTxFlags, tfSetfAuth);
    const bool          bSetNoRipple    = isSetBit (uTxFlags, tfSetNoRipple);
    const bool          bClearNoRipple  = isSetBit (uTxFlags, tfClearNoRipple);

    if (bSetAuth && !isSetBit (mTxnAccount->getFieldU32 (sfFlags), lsfRequireAuth))
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Retry: Auth not required.";

        return tefNO_AUTH_REQUIRED;
    }

    if (saLimitAmount.isNative ())
    {
        WriteLog (lsINFO, TrustSetTransactor) << boost::str (boost::format ("doTrustSet: Malformed transaction: Native credit limit: %s")
                                              % saLimitAmount.getFullText ());

        return temBAD_LIMIT;
    }

    if (saLimitAmount.isNegative ())
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Negative credit limit.";

        return temBAD_LIMIT;
    }

    // Check if destination makes sense.
    if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE)
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Destination account not specified.";

        return temDST_NEEDED;
    }

    if (mTxnAccountID == uDstAccountID)
    {
        SLE::pointer        selDelete   = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID));

        if (selDelete)
        {
            WriteLog (lsWARNING, TrustSetTransactor) << "doTrustSet: Clearing redundant line.";

            return mEngine->getNodes ().trustDelete (selDelete, mTxnAccountID, uDstAccountID);
        }
        else
        {
            WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Can not extend credit to self.";

            return temDST_IS_SRC;
        }
    }

    SLE::pointer        sleDst          = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID));

    if (!sleDst)
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Destination account does not exist.";

        return tecNO_DST;
    }

    const uint32        uOwnerCount     = mTxnAccount->getFieldU32 (sfOwnerCount);
    // The reserve required to create the line.
    const uint64        uReserveCreate  = (uOwnerCount < 2) ? 0 : mEngine->getLedger ()->getReserve (uOwnerCount + 1);

    STAmount            saLimitAllow    = saLimitAmount;
    saLimitAllow.setIssuer (mTxnAccountID);

    SLE::pointer        sleRippleState  = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID));

    if (sleRippleState)
    {
        STAmount        saLowBalance;
        STAmount        saLowLimit;
        STAmount        saHighBalance;
        STAmount        saHighLimit;
        uint32          uLowQualityIn;
        uint32          uLowQualityOut;
        uint32          uHighQualityIn;
        uint32          uHighQualityOut;
        const uint160&  uLowAccountID   = !bHigh ? mTxnAccountID : uDstAccountID;
        const uint160&  uHighAccountID  =  bHigh ? mTxnAccountID : uDstAccountID;
        SLE::ref        sleLowAccount   = !bHigh ? mTxnAccount : sleDst;
        SLE::ref        sleHighAccount  =  bHigh ? mTxnAccount : sleDst;

        //
        // Balances
        //

        saLowBalance    = sleRippleState->getFieldAmount (sfBalance);
        saHighBalance   = -saLowBalance;

        //
        // Limits
        //

        sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow);

        saLowLimit  = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit);
        saHighLimit =  bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit);

        //
        // Quality in
        //

        if (!bQualityIn)
        {
            // Not setting. Just get it.

            uLowQualityIn   = sleRippleState->getFieldU32 (sfLowQualityIn);
            uHighQualityIn  = sleRippleState->getFieldU32 (sfHighQualityIn);
        }
        else if (uQualityIn)
        {
            // Setting.

            sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn);

            uLowQualityIn   = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn);
            uHighQualityIn  =  bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn);
        }
        else
        {
            // Clearing.

            sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn);

            uLowQualityIn   = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn);
            uHighQualityIn  =  bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn);
        }

        if (QUALITY_ONE == uLowQualityIn)   uLowQualityIn   = 0;

        if (QUALITY_ONE == uHighQualityIn)  uHighQualityIn  = 0;

        //
        // Quality out
        //

        if (!bQualityOut)
        {
            // Not setting. Just get it.

            uLowQualityOut  = sleRippleState->getFieldU32 (sfLowQualityOut);
            uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut);
        }
        else if (uQualityOut)
        {
            // Setting.

            sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut);

            uLowQualityOut  = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut);
            uHighQualityOut =  bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut);
        }
        else
        {
            // Clearing.

            sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut);

            uLowQualityOut  = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut);
            uHighQualityOut =  bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut);
        }

        const uint32    uFlagsIn        = sleRippleState->getFieldU32 (sfFlags);
        uint32          uFlagsOut       = uFlagsIn;

        if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance).isGEZero())
        {
            uFlagsOut           |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple);
        }
        else if (bClearNoRipple && !bSetNoRipple)
        {
            uFlagsOut           &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple);
        }

        if (QUALITY_ONE == uLowQualityOut)  uLowQualityOut  = 0;

        if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0;


        const bool  bLowReserveSet      = uLowQualityIn || uLowQualityOut ||
                                          isSetBit (uFlagsOut, lsfLowNoRipple) ||
                                          !!saLowLimit || saLowBalance.isPositive ();
        const bool  bLowReserveClear    = !bLowReserveSet;

        const bool  bHighReserveSet     = uHighQualityIn || uHighQualityOut ||
                                          isSetBit (uFlagsOut, lsfHighNoRipple) ||
                                          !!saHighLimit || saHighBalance.isPositive ();
        const bool  bHighReserveClear   = !bHighReserveSet;

        const bool  bDefault            = bLowReserveClear && bHighReserveClear;

        const bool  bLowReserved        = isSetBit (uFlagsIn, lsfLowReserve);
        const bool  bHighReserved       = isSetBit (uFlagsIn, lsfHighReserve);

        bool        bReserveIncrease    = false;

        if (bSetAuth)
        {
            uFlagsOut           |= (bHigh ? lsfHighAuth : lsfLowAuth);
        }

        if (bLowReserveSet && !bLowReserved)
        {
            // Set reserve for low account.

            mEngine->getNodes ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount);
            uFlagsOut           |= lsfLowReserve;

            if (!bHigh)
                bReserveIncrease    = true;
        }

        if (bLowReserveClear && bLowReserved)
        {
            // Clear reserve for low account.

            mEngine->getNodes ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount);
            uFlagsOut   &= ~lsfLowReserve;
        }

        if (bHighReserveSet && !bHighReserved)
        {
            // Set reserve for high account.

            mEngine->getNodes ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount);
            uFlagsOut   |= lsfHighReserve;

            if (bHigh)
                bReserveIncrease    = true;
        }

        if (bHighReserveClear && bHighReserved)
        {
            // Clear reserve for high account.

            mEngine->getNodes ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount);
            uFlagsOut   &= ~lsfHighReserve;
        }

        if (uFlagsIn != uFlagsOut)
            sleRippleState->setFieldU32 (sfFlags, uFlagsOut);

        if (bDefault || CURRENCY_BAD == uCurrencyID)
        {
            // Delete.

            terResult   = mEngine->getNodes ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID);
        }
        else if (bReserveIncrease
                 && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load.
        {
            WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Insufficent reserve to add trust line.";

            // Another transaction could provide XRP to the account and then this transaction would succeed.
            terResult   = tecINSUF_RESERVE_LINE;
        }
        else
        {
            mEngine->entryModify (sleRippleState);

            WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Modify ripple line";
        }
    }
    // Line does not exist.
    else if (!saLimitAmount                                     // Setting default limit.
             && (!bQualityIn || !uQualityIn)                         // Not setting quality in or setting default quality in.
             && (!bQualityOut || !uQualityOut))                      // Not setting quality out or setting default quality out.
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Redundant: Setting non-existent ripple line to defaults.";

        return tecNO_LINE_REDUNDANT;
    }
    else if (mPriorBalance.getNValue () < uReserveCreate)       // Reserve is not scaled by load.
    {
        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Line does not exist. Insufficent reserve to create line.";

        // Another transaction could create the account and then this transaction would succeed.
        terResult   = tecNO_LINE_INSUF_RESERVE;
    }
    else if (CURRENCY_BAD == uCurrencyID)
    {
        terResult   = temBAD_CURRENCY;
    }
    else
    {
        STAmount    saBalance   = STAmount (uCurrencyID, ACCOUNT_ONE);  // Zero balance in currency.

        WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Creating ripple line: "
                                              << Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString ();

        // Create a new ripple line.
        terResult   = mEngine->getNodes ().trustCreate (
                          bHigh,
                          mTxnAccountID,
                          uDstAccountID,
                          Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID),
                          mTxnAccount,
                          bSetAuth,
                          bSetNoRipple && !bClearNoRipple,
                          saBalance,
                          saLimitAllow,       // Limit for who is being charged.
                          uQualityIn,
                          uQualityOut);
    }

    WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet<";

    return terResult;
}
// --> strIdent: public key, account ID, or regular seed.
// --> bStrict: Only allow account id or public key.
// <-- bIndex: true if iIndex > 0 and used the index.
Json::Value accountFromString (Ledger::ref lrLedger, RippleAddress& naAccount,
                               bool& bIndex, const std::string& strIdent,
                               const int iIndex, const bool bStrict, NetworkOPs& netOps)
{
    RippleAddress   naSeed;

    if (naAccount.setAccountPublic (strIdent) || naAccount.setAccountID (strIdent))
    {
        // Got the account.
        bIndex  = false;
    }
    else if (bStrict)
    {
        return naAccount.setAccountID (strIdent, Base58::getBitcoinAlphabet ())
               ? rpcError (rpcACT_BITCOIN)
               : rpcError (rpcACT_MALFORMED);
    }
    // Must be a seed.
    else if (!naSeed.setSeedGeneric (strIdent))
    {
        return rpcError (rpcBAD_SEED);
    }
    else
    {
        // We allow the use of the seeds to access #0.
        // This is poor practice and merely for debuging convenience.
        RippleAddress       naRegular0Public;
        RippleAddress       naRegular0Private;

        RippleAddress       naGenerator     = RippleAddress::createGeneratorPublic (naSeed);

        naRegular0Public.setAccountPublic (naGenerator, 0);
        naRegular0Private.setAccountPrivate (naGenerator, naSeed, 0);

        //      Account uGeneratorID    = naRegular0Public.getAccountID();
        SLE::pointer        sleGen          = netOps.getGenerator (lrLedger, naRegular0Public.getAccountID ());

        if (!sleGen)
        {
            // Didn't find a generator map, assume it is a master generator.
        }
        else
        {
            // Found master public key.
            Blob    vucCipher               = sleGen->getFieldVL (sfGenerator);
            Blob    vucMasterGenerator      = naRegular0Private.accountPrivateDecrypt (naRegular0Public, vucCipher);

            if (vucMasterGenerator.empty ())
            {
                rpcError (rpcNO_GEN_DECRYPT);
            }

            naGenerator.setGenerator (vucMasterGenerator);
        }

        bIndex  = !iIndex;

        naAccount.setAccountPublic (naGenerator, iIndex);
    }

    return Json::Value (Json::objectValue);
}
bool TransactionEngine::checkInvariants (TER result, const SerializedTransaction& txn, TransactionEngineParams params)
{
#if 0
    uint32                  txnSeq      = txn.getFieldU32 (sfSequence);

    LedgerEntryAction       leaAction;

    uint256                 srcActId    = Ledger::getAccountRootIndex (txn.getFieldAccount (sfAccount));
    SLE::pointer            origSrcAct  = mLedger->getSLE (srcActId);
    SLE::pointer            newSrcAct   = mNodes.getEntry (srcActId, leaAction);

    if (!newSrcAct || !origSrcAct)
    {
        WriteLog (lsFATAL, TransactionEngine) << "Transaction created or destroyed its issuing account";
        assert (false);
        return tefINTERNAL;
    }

    if ((newSrcAct->getFieldU32 (sfSequence) != (txnSeq + 1)) ||
            (origSrcAct->getFieldU32 (sfSequence) != txnSeq))
    {
        WriteLog (lsFATAL, TransactionEngine) << "Transaction mangles sequence numbers";
        WriteLog (lsFATAL, TransactionEngine) << "t:" << txnSeq << " o: " << origSrcAct->getFieldU32 (sfSequence)
                                              << " n: " << newSrcAct->getFieldU32 (sfSequence);
        assert (false);
        return tefINTERNAL;
    }

    int64 xrpChange = txn.getFieldAmount (sfFee).getSNValue ();

    for (LedgerEntrySet::const_iterator it = mNodes.begin (), end = mNodes.end (); it != end; ++it)
    {
        const LedgerEntrySetEntry& entry = it->second;

        if (entry.mAction == taaMODIFY)
        {
#if 0

            if (entry.mEntry->getType () == ltRIPPLE_STATE)
            {
                // if this transaction pushes a ripple state over its limit, make sure it also modifies
                // an offer placed by that same user
            }

#endif

            if (entry.mEntry->getType () == ltACCOUNT_ROOT)
            {
                // account modified
                xrpChange += entry.mEntry->getFieldAmount (sfBalance).getSNValue ();
                xrpChange -= mLedger->getSLE (it->first)->getFieldAmount (sfBalance).getSNValue ();
            }
        }
        else if (entry.mAction == taaCREATE)
        {
            if (entry.mEntry->getType () == ltRIPPLE_STATE)
            {
                if (entry.mEntry->getFieldAmount (sfLowLimit).getIssuer () ==
                        entry.mEntry->getFieldAmount (sfHighLimit).getIssuer ())
                {
                    WriteLog (lsFATAL, TransactionEngine) << "Ripple line to self";
                    assert (false);
                    return tefINTERNAL;
                }
            }

            if (entry.mEntry->getType () == ltACCOUNT_ROOT) // account created
                xrpChange += entry.mEntry->getFieldAmount (sfBalance).getSNValue ();
        }
    }

    if (xrpChange != 0)
    {
        WriteLog (lsFATAL, TransactionEngine) << "Transaction creates/destroys XRP";
        assert (false);
        return tefINTERNAL;
    }

#endif

    return true;
}