Exemple #1
0
bool LedgerFacade::hasAccountRoot () const
{
    if (!netOPs_) // Unit testing.
        return true;

    SLE::pointer const sleAccountRoot =
        netOPs_->getSLEi (ledger_, getAccountRootIndex (accountID_));

    return static_cast <bool> (sleAccountRoot);
}
Exemple #2
0
AccountState::AccountState (RippleAddress const& naAccountID)
    : mAccountID (naAccountID)
    , mValid (false)
{
    if (naAccountID.isValid ())
    {
        mValid = true;

        mLedgerEntry = std::make_shared <STLedgerEntry> (
                           ltACCOUNT_ROOT, getAccountRootIndex (naAccountID));

        mLedgerEntry->setFieldAccount (sfAccount, naAccountID.getAccountID ());
    }
}
Exemple #3
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 #4
0
// {
//   account: <account>|<account_public_key>
//   account_index: <number>        // optional, defaults to 0.
//   ledger_hash : <ledger>
//   ledger_index : <ledger_index>
//   limit: integer                 // optional
//   marker: opaque                 // optional, resume previous query
// }
Json::Value doAccountLines (RPC::Context& context)
{
    auto const& params (context.params);
    if (! params.isMember (jss::account))
        return RPC::missing_field_error (jss::account);

    Ledger::pointer ledger;
    Json::Value result (RPC::lookupLedger (params, ledger, context.netOps));
    if (! ledger)
        return result;

    std::string strIdent (params[jss::account].asString ());
    bool bIndex (params.isMember (jss::account_index));
    int iIndex (bIndex ? params[jss::account_index].asUInt () : 0);
    DivvyAddress divvyAddress;

    auto jv = RPC::accountFromString (
        divvyAddress, bIndex, strIdent, iIndex, false);
    if (! jv.empty ())
    {
        for (auto it = jv.begin (); it != jv.end (); ++it)
            result[it.memberName ()] = it.key ();

        return result;
    }

    if (! ledger->exists(getAccountRootIndex(
            divvyAddress.getAccountID())))
        return rpcError (rpcACT_NOT_FOUND);

    std::string strPeer (params.isMember (jss::peer)
        ? params[jss::peer].asString () : "");
    bool bPeerIndex (params.isMember (jss::peer_index));
    int iPeerIndex (bIndex ? params[jss::peer_index].asUInt () : 0);

    DivvyAddress divvyAddressPeer;

    if (! strPeer.empty ())
    {
        result[jss::peer] = divvyAddress.humanAccountID ();

        if (bPeerIndex)
            result[jss::peer_index] = iPeerIndex;

        result = RPC::accountFromString (
            divvyAddressPeer, bPeerIndex, strPeer, iPeerIndex, false);

        if (! result.empty ())
            return result;
    }

    AccountID raPeerAccount;
    if (divvyAddressPeer.isValid ())
        raPeerAccount = divvyAddressPeer.getAccountID ();

    unsigned int limit;
    if (params.isMember (jss::limit))
    {
        auto const& jvLimit (params[jss::limit]);
        if (! jvLimit.isIntegral ())
            return RPC::expected_field_error (jss::limit, "unsigned integer");

        limit = jvLimit.isUInt () ? jvLimit.asUInt () :
            std::max (0, jvLimit.asInt ());

        if (context.role != Role::ADMIN)
        {
            limit = std::max (RPC::Tuning::minLinesPerRequest,
                std::min (limit, RPC::Tuning::maxLinesPerRequest));
        }
    }
    else
    {
        limit = RPC::Tuning::defaultLinesPerRequest;
    }

    Json::Value& jsonLines (result[jss::lines] = Json::arrayValue);
    AccountID const& raAccount(divvyAddress.getAccountID ());
    VisitData visitData = { {}, raAccount, divvyAddressPeer, raPeerAccount };
    unsigned int reserve (limit);
    uint256 startAfter;
    std::uint64_t startHint;

    if (params.isMember (jss::marker))
    {
        // We have a start point. Use limit - 1 from the result and use the
        // very last one for the resume.
        Json::Value const& marker (params[jss::marker]);

        if (! marker.isString ())
            return RPC::expected_field_error (jss::marker, "string");

        startAfter.SetHex (marker.asString ());
        auto const sleLine = fetch(*ledger, startAfter,
            getApp().getSLECache());

        if (sleLine == nullptr || sleLine->getType () != ltRIPPLE_STATE)
            return rpcError (rpcINVALID_PARAMS);

        if (sleLine->getFieldAmount (sfLowLimit).getIssuer () == raAccount)
            startHint = sleLine->getFieldU64 (sfLowNode);
        else if (sleLine->getFieldAmount (sfHighLimit).getIssuer () == raAccount)
            startHint = sleLine->getFieldU64 (sfHighNode);
        else
            return rpcError (rpcINVALID_PARAMS);

        // Caller provided the first line (startAfter), add it as first result
        auto const line = DivvyState::makeItem (raAccount, sleLine);
        if (line == nullptr)
            return rpcError (rpcINVALID_PARAMS);

        addLine (jsonLines, *line);
        visitData.items.reserve (reserve);
    }
    else
    {
        startHint = 0;
        // We have no start point, limit should be one higher than requested.
        visitData.items.reserve (++reserve);
    }

    if (! forEachItemAfter(*ledger, raAccount, getApp().getSLECache(),
            startAfter, startHint, reserve,
        [&visitData](std::shared_ptr<SLE const> const& sleCur)
        {
            auto const line =
                DivvyState::makeItem (visitData.accountID, sleCur);
            if (line != nullptr &&
                (! visitData.divvyAddressPeer.isValid () ||
                visitData.raPeerAccount == line->getAccountIDPeer ()))
            {
                visitData.items.emplace_back (line);
                return true;
            }

            return false;
        }))
    {
        return rpcError (rpcINVALID_PARAMS);
    }

    if (visitData.items.size () == reserve)
    {
        result[jss::limit] = limit;

        DivvyState::pointer line (visitData.items.back ());
        result[jss::marker] = to_string (line->key());
        visitData.items.pop_back ();
    }

    result[jss::account] = divvyAddress.humanAccountID ();

    for (auto const& item : visitData.items)
        addLine (jsonLines, *item.get ());

    context.loadType = Resource::feeMediumBurdenRPC;
    return result;
}
Exemple #5
0
Json::Value doAccountObjects (RPC::Context& context)
{
    auto const& params = context.params;
    if (! params.isMember (jss::account))
        return RPC::missing_field_error (jss::account);

    Ledger::pointer ledger;
    auto result = RPC::lookupLedger (params, ledger, context.netOps);
    if (ledger == nullptr)
        return result;

    DivvyAddress raAccount;
    {
        bool bIndex;
        auto const strIdent = params[jss::account].asString ();
        auto iIndex = context.params.isMember (jss::account_index)
            ? context.params[jss::account_index].asUInt () : 0;
        auto jv = RPC::accountFromString (
            raAccount, bIndex, strIdent, iIndex, false);
        if (! jv.empty ())
        {
            for (auto it = jv.begin (); it != jv.end (); ++it)
                result[it.memberName ()] = it.key ();

            return result;
        }
    }

    if (! ledger->exists(getAccountRootIndex(
            raAccount.getAccountID())))
        return rpcError (rpcACT_NOT_FOUND);

    auto type = ltINVALID;
    if (params.isMember (jss::type))
    {
        using filter_types = std::vector <std::pair <std::string, LedgerEntryType>>;
        static
        beast::static_initializer <filter_types> const types ([]() -> filter_types {
            return {
                { "account", ltACCOUNT_ROOT },
                { "amendments", ltAMENDMENTS },
                { "directory", ltDIR_NODE },
                { "fee", ltFEE_SETTINGS },
                { "hashes", ltLEDGER_HASHES },
                { "offer", ltOFFER },
                { "state", ltRIPPLE_STATE },
                { "ticket", ltTICKET }
            };
        }());

        auto const& p = params[jss::type];
        if (! p.isString ())
            return RPC::expected_field_error (jss::type, "string");

        auto const filter = p.asString ();
        auto iter = std::find_if (types->begin (), types->end (),
            [&filter](decltype (types->front ())& t)
                { return t.first == filter; });
        if (iter == types->end ())
            return RPC::invalid_field_error (jss::type);

        type = iter->second;
    }

    auto limit = RPC::Tuning::defaultObjectsPerRequest;
    if (params.isMember (jss::limit))
    {
        auto const& jvLimit = params[jss::limit];
        if (! jvLimit.isIntegral ())
            return RPC::expected_field_error (jss::limit, "unsigned integer");

        limit = jvLimit.isUInt () ? jvLimit.asUInt () :
            std::max (0, jvLimit.asInt ());

        if (context.role != Role::ADMIN)
        {
            limit = std::max (RPC::Tuning::minObjectsPerRequest,
                std::min (limit, RPC::Tuning::maxObjectsPerRequest));
        }
    }

    uint256 dirIndex;
    uint256 entryIndex;
    if (params.isMember (jss::marker))
    {
        auto const& marker = params[jss::marker];
        if (! marker.isString ())
            return RPC::expected_field_error (jss::marker, "string");

        std::stringstream ss (marker.asString ());
        std::string s;
        if (!std::getline(ss, s, ','))
            return RPC::invalid_field_error (jss::marker);

        if (! dirIndex.SetHex (s))
            return RPC::invalid_field_error (jss::marker);

        if (! std::getline (ss, s, ','))
            return RPC::invalid_field_error (jss::marker);

        if (! entryIndex.SetHex (s))
            return RPC::invalid_field_error (jss::marker);
    }

    if (! RPC::getAccountObjects (*ledger, raAccount.getAccountID (), type,
        dirIndex, entryIndex, limit, result))
    {
        return RPC::invalid_field_error (jss::marker);
    }

    result[jss::account] = raAccount.humanAccountID ();
    context.loadType = Resource::feeMediumBurdenRPC;
    return result;
}
Exemple #6
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;
}
Exemple #7
0
    TER doApply () override
    {
        // Ripple 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 & tfNoRippleDirect);
        bool const bPaths = mTxn.isFieldPresent (sfPaths);
        bool const bMax = mTxn.isFieldPresent (sfSendMax);
        Account const uDstAccountID (mTxn.getFieldAccount160 (sfDestination));
        STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount));
        STAmount maxSourceAmount;
        if (bMax)
            maxSourceAmount = mTxn.getFieldAmount (sfSendMax);
        else if (saDstAmount.isNative ())
            maxSourceAmount = saDstAmount;
        else
          maxSourceAmount = STAmount (
              {saDstAmount.getCurrency (), mTxnAccountID},
              saDstAmount.mantissa(), saDstAmount.exponent (),
              saDstAmount < zero);
        auto const& uSrcCurrency = maxSourceAmount.getCurrency ();
        auto const& uDstCurrency = saDstAmount.getCurrency ();

        // isZero() is XRP.  FIX!
        bool const bXRPDirect = uSrcCurrency.isZero () && uDstCurrency.isZero ();

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

        if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount))
            return temBAD_AMOUNT;

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

            return temINVALID_FLAG;
        }
        else if (!uDstAccountID)
        {
            m_journal.trace <<
                "Malformed transaction: Payment destination account not specified.";

            return temDST_NEEDED;
        }
        else if (bMax && maxSourceAmount <= zero)
        {
            m_journal.trace <<
                "Malformed transaction: bad max amount: " << maxSourceAmount.getFullText ();

            return temBAD_AMOUNT;
        }
        else if (saDstAmount <= zero)
        {
            m_journal.trace <<
                "Malformed transaction: bad dst amount: " << saDstAmount.getFullText ();

            return temBAD_AMOUNT;
        }
        else if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency)
        {
            m_journal.trace <<
                "Malformed transaction: Bad currency.";

            return temBAD_CURRENCY;
        }
        else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths)
        {
            // You're signing yourself a payment.
            // If bPaths is true, you might be trying some arbitrage.
            m_journal.trace <<
                "Malformed transaction: Redundant transaction:" <<
                " src=" << to_string (mTxnAccountID) <<
                " dst=" << to_string (uDstAccountID) <<
                " src_cur=" << to_string (uSrcCurrency) <<
                " dst_cur=" << to_string (uDstCurrency);

            return temREDUNDANT;
        }
        else if (bMax && maxSourceAmount == saDstAmount &&
                 maxSourceAmount.getCurrency () == saDstAmount.getCurrency ())
        {
            // Consistent but redundant transaction.
            m_journal.trace <<
                "Malformed transaction: Redundant SendMax.";

            return temREDUNDANT_SEND_MAX;
        }
        else if (bXRPDirect && bMax)
        {
            // Consistent but redundant transaction.
            m_journal.trace <<
                "Malformed transaction: SendMax specified for XRP to XRP.";

            return temBAD_SEND_XRP_MAX;
        }
        else if (bXRPDirect && bPaths)
        {
            // XRP is sent without paths.
            m_journal.trace <<
                "Malformed transaction: Paths specified for XRP to XRP.";

            return temBAD_SEND_XRP_PATHS;
        }
        else if (bXRPDirect && partialPaymentAllowed)
        {
            // Consistent but redundant transaction.
            m_journal.trace <<
                "Malformed transaction: Partial payment specified for XRP to XRP.";

            return temBAD_SEND_XRP_PARTIAL;
        }
        else if (bXRPDirect && limitQuality)
        {
            // Consistent but redundant transaction.
            m_journal.trace <<
                "Malformed transaction: Limit quality specified for XRP to XRP.";

            return temBAD_SEND_XRP_LIMIT;
        }
        else if (bXRPDirect && !defaultPathsAllowed)
        {
            // Consistent but redundant transaction.
            m_journal.trace <<
                "Malformed transaction: No ripple direct specified for XRP to XRP.";

            return temBAD_SEND_XRP_NO_DIRECT;
        }

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

        if (!sleDst)
        {
            // Destination account does not exist.
            if (!saDstAmount.isNative ())
            {
                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.getNValue () < 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_XRP;
            }

            // Create the account.
            auto const newIndex = getAccountRootIndex (uDstAccountID);
            sleDst = mEngine->entryCreate (ltACCOUNT_ROOT, newIndex);
            sleDst->setFieldAccount (sfAccount, uDstAccountID);
            sleDst->setFieldU32 (sfSequence, 1);
        }
        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 tefDST_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->entryModify (sleDst);
        }

        TER terResult;

        bool const bRipple = bPaths || bMax || !saDstAmount.isNative ();
        // XXX Should bMax be sufficient to imply ripple?

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

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

            try
            {
                path::RippleCalc::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
                {
                    auto rc = path::RippleCalc::rippleCalculate (
                        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 XRP payment.

            // uOwnerCount is the number of entries in this legder for this account
            // that require a reserve.

            std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount));

            // This is the total reserve in drops.
            // TODO(tom): there should be a class for this.
            std::uint64_t const uReserve (mEngine->getLedger ()->getReserve (uOwnerCount));

            // mPriorBalance is the balance on the sending account BEFORE the fees were charged.
            //
            // Make sure have enough reserve to send. Allow final spend to use
            // reserve for fee.
            auto const mmm = std::max(uReserve, mTxn.getTransactionFee ().getNValue ());
            if (mPriorBalance < saDstAmount + mmm)
            {
                // Vote no.
                // However, transaction might succeed, if applied in a different order.
                m_journal.trace << "Delay transaction: Insufficient funds: " <<
                    " " << mPriorBalance.getText () <<
                    " / " << (saDstAmount + uReserve).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 #8
0
// {
//   account: <account>|<account_public_key>
//   account_index: <number>        // optional, defaults to 0.
//   ledger_hash : <ledger>
//   ledger_index : <ledger_index>
//   limit: integer                 // optional
//   marker: opaque                 // optional, resume previous query
// }
Json::Value doAccountOffers (RPC::Context& context)
{
    auto const& params (context.params);
    if (! params.isMember (jss::account))
        return RPC::missing_field_error (jss::account);

    Ledger::pointer ledger;
    Json::Value result (RPC::lookupLedger (params, ledger, context.netOps));
    if (! ledger)
        return result;

    std::string strIdent (params[jss::account].asString ());
    bool bIndex (params.isMember (jss::account_index));
    int const iIndex (bIndex ? params[jss::account_index].asUInt () : 0);
    DivvyAddress divvyAddress;

    Json::Value const jv = RPC::accountFromString (
        divvyAddress, bIndex, strIdent, iIndex, false);
    if (! jv.empty ())
    {
        for (Json::Value::const_iterator it (jv.begin ()); it != jv.end (); ++it)
            result[it.memberName ()] = it.key ();

        return result;
    }

    // Get info on account.
    result[jss::account] = divvyAddress.humanAccountID ();

    if (bIndex)
        result[jss::account_index] = iIndex;

    if (! ledger->exists(getAccountRootIndex(
            divvyAddress.getAccountID())))
        return rpcError (rpcACT_NOT_FOUND);

    unsigned int limit;
    if (params.isMember (jss::limit))
    {
        auto const& jvLimit (params[jss::limit]);
        if (! jvLimit.isIntegral ())
            return RPC::expected_field_error (jss::limit, "unsigned integer");

        limit = jvLimit.isUInt () ? jvLimit.asUInt () :
            std::max (0, jvLimit.asInt ());

        if (context.role != Role::ADMIN)
        {
            limit = std::max (RPC::Tuning::minOffersPerRequest,
                std::min (limit, RPC::Tuning::maxOffersPerRequest));
        }
    }
    else
    {
        limit = RPC::Tuning::defaultOffersPerRequest;
    }

    AccountID const& raAccount (divvyAddress.getAccountID ());
    Json::Value& jsonOffers (result[jss::offers] = Json::arrayValue);
    std::vector <std::shared_ptr<SLE const>> offers;
    unsigned int reserve (limit);
    uint256 startAfter;
    std::uint64_t startHint;

    if (params.isMember(jss::marker))
    {
        // We have a start point. Use limit - 1 from the result and use the
        // very last one for the resume.
        Json::Value const& marker (params[jss::marker]);

        if (! marker.isString ())
            return RPC::expected_field_error (jss::marker, "string");

        startAfter.SetHex (marker.asString ());
        auto const sleOffer = fetch (*ledger, startAfter,
            getApp().getSLECache());

        if (sleOffer == nullptr ||
            sleOffer->getType () != ltOFFER ||
            raAccount != sleOffer->getFieldAccount160 (sfAccount))
        {
            return rpcError (rpcINVALID_PARAMS);
        }

        startHint = sleOffer->getFieldU64(sfOwnerNode);

        // Caller provided the first offer (startAfter), add it as first result
        Json::Value& obj (jsonOffers.append (Json::objectValue));
        sleOffer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]);
        sleOffer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]);
        obj[jss::seq] = sleOffer->getFieldU32 (sfSequence);
        obj[jss::flags] = sleOffer->getFieldU32 (sfFlags);

        offers.reserve (reserve);
    }
    else
    {
        startHint = 0;
        // We have no start point, limit should be one higher than requested.
        offers.reserve (++reserve);
    }

    if (! forEachItemAfter(*ledger, raAccount, getApp().getSLECache(),
            startAfter, startHint, reserve,
        [&offers](std::shared_ptr<SLE const> const& offer)
        {
            if (offer->getType () == ltOFFER)
            {
                offers.emplace_back (offer);
                return true;
            }

            return false;
        }))
    {
        return rpcError (rpcINVALID_PARAMS);
    }

    if (offers.size () == reserve)
    {
        result[jss::limit] = limit;

        result[jss::marker] = to_string (offers.back ()->getIndex ());
        offers.pop_back ();
    }

    for (auto const& offer : offers)
    {
        Json::Value& obj (jsonOffers.append (Json::objectValue));
        offer->getFieldAmount (sfTakerPays).setJson (obj[jss::taker_pays]);
        offer->getFieldAmount (sfTakerGets).setJson (obj[jss::taker_gets]);
        obj[jss::seq] = offer->getFieldU32 (sfSequence);
        obj[jss::flags] = offer->getFieldU32 (sfFlags);
    }

    context.loadType = Resource::feeMediumBurdenRPC;
    return result;
}