示例#1
3
TEST_F(LedgerUnitTest, testRegisteredDecreeHandlerExecuted)
{
    class CustomHandler : public paxos::DecreeHandler
    {
    public:

        CustomHandler()
            : is_executed(false)
        {
        }

        virtual void operator()(std::string entry) override
        {
            is_executed = true;
        }

        bool is_executed;
    };

    auto handler = std::make_shared<CustomHandler>();

    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);
    ledger.RegisterHandler(paxos::DecreeType::AddReplicaDecree, handler);
    ledger.Append(paxos::Decree(paxos::Replica("a_author"), 1, "AAAAA", paxos::DecreeType::AddReplicaDecree));

    ASSERT_TRUE(handler->is_executed);
}
示例#2
2
ter pathcursor::liquidity (ledgerentryset const& lescheckpoint) const
{
    ter resultcode = tecpath_dry;
    pathcursor pc = *this;

    ledger() = lescheckpoint.duplicate ();
    for (pc.nodeindex_ = pc.nodesize(); pc.nodeindex_--; )
    {
        writelog (lstrace, ripplecalc)
            << "reverseliquidity>"
            << " nodeindex=" << pc.nodeindex_
            << ".issue_.account=" << to_string (pc.node().issue_.account);

        resultcode  = pc.reverseliquidity();

        writelog (lstrace, ripplecalc)
            << "reverseliquidity< "
            << "nodeindex=" << pc.nodeindex_
            << " resultcode=" << transtoken (resultcode)
            << " transferrate_=" << pc.node().transferrate_
            << "/" << resultcode;

        if (resultcode != tessuccess)
            break;
    }

    // vfalco-fixme this generates errors
    // writelog (lstrace, ripplecalc)
    //     << "nextincrement: path after reverse: " << pathstate_.getjson ();

    if (resultcode != tessuccess)
        return resultcode;

    // do forward.
    ledger() = lescheckpoint.duplicate ();
    for (pc.nodeindex_ = 0; pc.nodeindex_ < pc.nodesize(); ++pc.nodeindex_)
    {
        writelog (lstrace, ripplecalc)
            << "forwardliquidity> nodeindex=" << nodeindex_;

        resultcode = pc.forwardliquidity();
        if (resultcode != tessuccess)
            return resultcode;

        writelog (lstrace, ripplecalc)
            << "forwardliquidity<"
            << " nodeindex:" << pc.nodeindex_
            << " resultcode:" << resultcode;

        if (pathstate_.isdry())
            resultcode = tecpath_dry;
    }
    return resultcode;
}
示例#3
2
TEST_F(LedgerUnitTest, testLedgerAppendChangesIsEmptyStatus)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);

    ASSERT_TRUE(ledger.IsEmpty());

    ledger.Append(paxos::Decree(paxos::Replica("an_author"), 1, "decree_contents", paxos::DecreeType::AddReplicaDecree));

    ASSERT_FALSE(ledger.IsEmpty());
}
示例#4
1
TEST_F(LedgerUnitTest, testEmptyTailReturnsDefaultDecree)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);
    paxos::Decree expected, actual = ledger.Tail();

    ASSERT_EQ(expected.author.hostname, actual.author.hostname);
    ASSERT_EQ(expected.author.port, actual.author.port);
    ASSERT_EQ(expected.number, actual.number);
    ASSERT_EQ(expected.content, actual.content);
}
示例#5
0
TEST_F(LedgerUnitTest, testAppendIgnoresDuplicateDecrees)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);
    ledger.Append(paxos::Decree(paxos::Replica("a_author"), 1, "a_content", paxos::DecreeType::UserDecree));
    ledger.Append(paxos::Decree(paxos::Replica("a_author"), 1, "a_content", paxos::DecreeType::UserDecree));

    ASSERT_EQ(GetQueueSize(queue), 1);
}
示例#6
0
TEST_F(LedgerUnitTest, testRemoveDecrementsTheSize)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);

    ledger.Append(paxos::Decree(paxos::Replica("an_author"), 1, "decree_contents", paxos::DecreeType::UserDecree));
    ledger.Remove();

    ASSERT_EQ(GetQueueSize(queue), 0);
}
示例#7
0
TEST_F(LedgerUnitTest, testEmptyNextWithFutureDecree)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);
    paxos::Decree expected, actual = ledger.Next(paxos::Decree(paxos::Replica("b_author"), 2, "b_content", paxos::DecreeType::UserDecree));

    ASSERT_EQ(expected.author.hostname, actual.author.hostname);
    ASSERT_EQ(expected.author.port, actual.author.port);
    ASSERT_EQ(expected.number, actual.number);
    ASSERT_EQ(expected.content, actual.content);
}
示例#8
0
TEST_F(LedgerUnitTest, testDecreeHandlerOnAppend)
{
    std::string concatenated_content;
    auto handler = [&](std::string entry) { concatenated_content += entry; };

    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(
        queue,
        std::make_shared<paxos::CompositeHandler>(handler));
    ledger.Append(paxos::Decree(paxos::Replica("a_author"), 1, "AAAAA", paxos::DecreeType::UserDecree));
    ledger.Append(paxos::Decree(paxos::Replica("b_author"), 2, "BBBBB", paxos::DecreeType::UserDecree));

    ASSERT_EQ(concatenated_content, "AAAAABBBBB");
}
示例#9
0
TEST_F(LedgerUnitTest, testAppendWritesOutOfOrderDecreeWithOrderedRoot)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);
    paxos::Decree current_decree(paxos::Replica("a_author"), 2, "a_content", paxos::DecreeType::UserDecree);
    current_decree.root_number = 1;
    ledger.Append(current_decree);

    paxos::Decree next_decree(paxos::Replica("a_author"), 1, "a_content", paxos::DecreeType::UserDecree);
    next_decree.root_number = 2;
    ledger.Append(next_decree);

    ASSERT_EQ(GetQueueSize(queue), 2);
}
示例#10
0
TEST_F(LedgerUnitTest, testTailWithMultipleDecrees)
{
    std::stringstream ss;
    auto queue = std::make_shared<paxos::RolloverQueue<paxos::Decree>>(ss);
    paxos::Ledger ledger(queue);
    ledger.Append(paxos::Decree(paxos::Replica("a_author"), 1, "a_content", paxos::DecreeType::UserDecree));
    ledger.Append(paxos::Decree(paxos::Replica("b_author"), 2, "b_content", paxos::DecreeType::UserDecree));

    paxos::Decree expected = paxos::Decree(paxos::Replica("b_author"), 2, "b_content", paxos::DecreeType::UserDecree), actual = ledger.Tail();

    ASSERT_EQ(expected.author.hostname, actual.author.hostname);
    ASSERT_EQ(expected.author.port, actual.author.port);
    ASSERT_EQ(expected.number, actual.number);
    ASSERT_EQ(expected.content, actual.content);
}
示例#11
0
int main(int argc, char** argv) {
  ledger_rest::args args(argc, argv);

  ledger_rest::stderr_logger logger(args.get_log_level());
  ledger_rest::ledger_rest_runnable ledger(args, logger);

  ledger_rest::mhd mhd(args, logger, ledger);

  std::list<ledger_rest::runnable*> runners{ &mhd, &ledger };
  ledger_rest::runner runner(logger, runners);

  ledger_rest::set_runner(&runner);
  if (std::signal(SIGINT, ledger_rest::stop_runner) == SIG_ERR) {
    logger.log(5, "Error setting signal handler.");
    exit(EXIT_FAILURE);
  }

  runner.run();
  return 0;
}
ter pathcursor::reverseliquidity () const
{
    // every account has a transfer rate for its issuances.

    // tomove: the account charges
    // a fee when third parties transfer that account's own issuances.

    // node.transferrate_ caches the output transfer rate for this node.
    node().transferrate_ = amountfromrate (
        rippletransferrate (ledger(), node().issue_.account));

    if (node().isaccount ())
        return reverseliquidityforaccount ();

    // otherwise the node is an offer.
    if (isnative (nextnode().account_))
    {
        writelog (lstrace, ripplecalc)
            << "reverseliquidityforoffer: "
            << "offer --> offer: nodeindex_=" << nodeindex_;
        return tessuccess;

        // this control structure ensures delivernodereverse is only called for the
        // rightmost offer in a chain of offers - which means that
        // delivernodereverse has to take all of those offers into consideration.
    }

    // next is an account node, resolve current offer node's deliver.
    stamount sadeliveract;

    writelog (lstrace, ripplecalc)
        << "reverseliquidityforoffer: offer --> account:"
        << " nodeindex_=" << nodeindex_
        << " sarevdeliver=" << node().sarevdeliver;

    // the next node wants the current node to deliver this much:
    return delivernodereverse (
        nextnode().account_,
        node().sarevdeliver,
        sadeliveract);
}
示例#13
0
TER PathCursor::deliverNodeForward (
    AccountID const& uInAccountID,    // --> Input owner's account.
    STAmount const& saInReq,        // --> Amount to deliver.
    STAmount& saInAct,              // <-- Amount delivered, this invocation.
    STAmount& saInFees) const       // <-- Fees charged, this invocation.
{
    TER resultCode   = tesSUCCESS;

    // Don't deliver more than wanted.
    // Zeroed in reverse pass.
    node().directory.restart(multiQuality_);

    saInAct.clear (saInReq);
    saInFees.clear (saInReq);

    int loopCount = 0;

    // XXX Perhaps make sure do not exceed node().saRevDeliver as another way to
    // stop?
    while (resultCode == tesSUCCESS && saInAct + saInFees < saInReq)
    {
        // Did not spend all inbound deliver funds.
        if (++loopCount > CALC_NODE_DELIVER_MAX_LOOPS)
        {
            WriteLog (lsWARNING, RippleCalc)
                << "deliverNodeForward: max loops cndf";
            return telFAILED_PROCESSING;
        }

        // Determine values for pass to adjust saInAct, saInFees, and
        // node().saFwdDeliver.
        advanceNode (saInAct, false);

        // If needed, advance to next funded offer.

        if (resultCode != tesSUCCESS)
        {
        }
        else if (!node().offerIndex_)
        {
            WriteLog (lsWARNING, RippleCalc)
                << "deliverNodeForward: INTERNAL ERROR: Ran out of offers.";
            return telFAILED_PROCESSING;
        }
        else if (resultCode == tesSUCCESS)
        {
            // Doesn't charge input. Input funds are in limbo.
            // There's no fee if we're transferring XRP, if the sender is the
            // issuer, or if the receiver is the issuer.
            bool noFee = isXRP (previousNode().issue_)
                || uInAccountID == previousNode().issue_.account
                || node().offerOwnerAccount_ == previousNode().issue_.account;
            const STAmount saInFeeRate = noFee ? saOne
                : previousNode().transferRate_;  // Transfer rate of issuer.

            // First calculate assuming no output fees: saInPassAct,
            // saInPassFees, saOutPassAct.

            // Offer maximum out - limited by funds with out fees.
            auto saOutFunded = std::min (
                node().saOfferFunds, node().saTakerGets);

            // Offer maximum out - limit by most to deliver.
            auto saOutPassFunded = std::min (
                saOutFunded,
                node().saRevDeliver - node().saFwdDeliver);

            // Offer maximum in - Limited by by payout.
            auto saInFunded = mulRound (
                saOutPassFunded,
                node().saOfrRate,
                node().saTakerPays.issue (),
                true);

            // Offer maximum in with fees.
            auto saInTotal = mulRound (saInFunded, saInFeeRate,
                saInFunded.issue (), true);
            auto saInRemaining = saInReq - saInAct - saInFees;

            if (saInRemaining < zero)
                saInRemaining.clear();

            // In limited by remaining.
            auto saInSum = std::min (saInTotal, saInRemaining);

            // In without fees.
            auto saInPassAct = std::min (
                node().saTakerPays, divRound (
                    saInSum, saInFeeRate, saInSum.issue (), true));

            // Out limited by in remaining.
            auto outPass = divRound (
                saInPassAct, node().saOfrRate, node().saTakerGets.issue (), true);
            STAmount saOutPassMax    = std::min (saOutPassFunded, outPass);

            STAmount saInPassFeesMax = saInSum - saInPassAct;

            // Will be determined by next node().
            STAmount saOutPassAct;

            // Will be determined by adjusted saInPassAct.
            STAmount saInPassFees;

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeForward:"
                << " nodeIndex_=" << nodeIndex_
                << " saOutFunded=" << saOutFunded
                << " saOutPassFunded=" << saOutPassFunded
                << " node().saOfferFunds=" << node().saOfferFunds
                << " node().saTakerGets=" << node().saTakerGets
                << " saInReq=" << saInReq
                << " saInAct=" << saInAct
                << " saInFees=" << saInFees
                << " saInFunded=" << saInFunded
                << " saInTotal=" << saInTotal
                << " saInSum=" << saInSum
                << " saInPassAct=" << saInPassAct
                << " saOutPassMax=" << saOutPassMax;

            // FIXME: We remove an offer if WE didn't want anything out of it?
            if (!node().saTakerPays || saInSum <= zero)
            {
                WriteLog (lsDEBUG, RippleCalc)
                    << "deliverNodeForward: Microscopic offer unfunded.";

                // After math offer is effectively unfunded.
                pathState_.unfundedOffers().push_back (node().offerIndex_);
                node().bEntryAdvance = true;
                continue;
            }

            if (!saInFunded)
            {
                // Previous check should catch this.
                WriteLog (lsWARNING, RippleCalc)
                    << "deliverNodeForward: UNREACHABLE REACHED";

                // After math offer is effectively unfunded.
                pathState_.unfundedOffers().push_back (node().offerIndex_);
                node().bEntryAdvance = true;
                continue;
            }

            if (!isXRP(nextNode().account_))
            {
                // ? --> OFFER --> account
                // Input fees: vary based upon the consumed offer's owner.
                // Output fees: none as XRP or the destination account is the
                // issuer.

                saOutPassAct = saOutPassMax;
                saInPassFees = saInPassFeesMax;

                WriteLog (lsTRACE, RippleCalc)
                    << "deliverNodeForward: ? --> OFFER --> account:"
                    << " offerOwnerAccount_="
                    << node().offerOwnerAccount_
                    << " nextNode().account_="
                    << nextNode().account_
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutFunded=" << saOutFunded;

                // Output: Debit offer owner, send XRP or non-XPR to next
                // account.
                resultCode = ledger().accountSend (
                    node().offerOwnerAccount_,
                    nextNode().account_,
                    saOutPassAct);

                if (resultCode != tesSUCCESS)
                    break;
            }
            else
            {
                // ? --> OFFER --> offer
                //
                // Offer to offer means current order book's output currency and
                // issuer match next order book's input current and issuer.
                //
                // Output fees: possible if issuer has fees and is not on either
                // side.
                STAmount saOutPassFees;

                // Output fees vary as the next nodes offer owners may vary.
                // Therefore, immediately push through output for current offer.
                resultCode = increment().deliverNodeForward (
                    node().offerOwnerAccount_,  // --> Current holder.
                    saOutPassMax,             // --> Amount available.
                    saOutPassAct,             // <-- Amount delivered.
                    saOutPassFees);           // <-- Fees charged.

                if (resultCode != tesSUCCESS)
                    break;

                if (saOutPassAct == saOutPassMax)
                {
                    // No fees and entire output amount.

                    saInPassFees = saInPassFeesMax;
                }
                else
                {
                    // Fraction of output amount.
                    // Output fees are paid by offer owner and not passed to
                    // previous.

                    assert (saOutPassAct < saOutPassMax);
                    auto inPassAct = mulRound (
                        saOutPassAct, node().saOfrRate, saInReq.issue (), true);
                    saInPassAct = std::min (node().saTakerPays, inPassAct);
                    auto inPassFees = mulRound (
                        saInPassAct, saInFeeRate, saInPassAct.issue (), true);
                    saInPassFees    = std::min (saInPassFeesMax, inPassFees);
                }

                // Do outbound debiting.
                // Send to issuer/limbo total amount including fees (issuer gets
                // fees).
                auto const& id = isXRP(node().issue_) ?
                        xrpAccount() : node().issue_.account;
                auto outPassTotal = saOutPassAct + saOutPassFees;
                ledger().accountSend (
                    node().offerOwnerAccount_,
                    id,
                    outPassTotal);

                WriteLog (lsTRACE, RippleCalc)
                    << "deliverNodeForward: ? --> OFFER --> offer:"
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutPassFees=" << saOutPassFees;
            }

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeForward: "
                << " nodeIndex_=" << nodeIndex_
                << " node().saTakerGets=" << node().saTakerGets
                << " node().saTakerPays=" << node().saTakerPays
                << " saInPassAct=" << saInPassAct
                << " saInPassFees=" << saInPassFees
                << " saOutPassAct=" << saOutPassAct
                << " saOutFunded=" << saOutFunded;

            // Funds were spent.
            node().bFundsDirty = true;

            // Do inbound crediting.
            //
            // Credit offer owner from in issuer/limbo (input transfer fees left
            // with owner).  Don't attempt to have someone credit themselves, it
            // is redundant.
            if (isXRP (previousNode().issue_.currency)
                || uInAccountID != node().offerOwnerAccount_)
            {
                auto id = !isXRP(previousNode().issue_.currency) ?
                        uInAccountID : xrpAccount();
                resultCode = ledger().accountSend (
                    id,
                    node().offerOwnerAccount_,
                    saInPassAct);

                if (resultCode != tesSUCCESS)
                    break;
            }

            // Adjust offer.
            //
            // Fees are considered paid from a seperate budget and are not named
            // in the offer.
            STAmount saTakerGetsNew  = node().saTakerGets - saOutPassAct;
            STAmount saTakerPaysNew  = node().saTakerPays - saInPassAct;

            if (saTakerPaysNew < zero || saTakerGetsNew < zero)
            {
                WriteLog (lsWARNING, RippleCalc)
                    << "deliverNodeForward: NEGATIVE:"
                    << " saTakerPaysNew=" << saTakerPaysNew
                    << " saTakerGetsNew=" << saTakerGetsNew;

                resultCode = telFAILED_PROCESSING;
                break;
            }

            node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
            node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);

            ledger().entryModify (node().sleOffer);

            if (saOutPassAct == saOutFunded || saTakerGetsNew == zero)
            {
                // Offer became unfunded.

                WriteLog (lsDEBUG, RippleCalc)
                    << "deliverNodeForward: unfunded:"
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutFunded=" << saOutFunded;

                pathState_.unfundedOffers().push_back (node().offerIndex_);
                node().bEntryAdvance   = true;
            }
            else
            {
                CondLog (saOutPassAct >= saOutFunded, lsWARNING, RippleCalc)
                    << "deliverNodeForward: TOO MUCH:"
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutFunded=" << saOutFunded;

                assert (saOutPassAct < saOutFunded);
            }

            saInAct += saInPassAct;
            saInFees += saInPassFees;

            // Adjust amount available to next node().
            node().saFwdDeliver = std::min (node().saRevDeliver,
                                        node().saFwdDeliver + saOutPassAct);
        }
    }

    WriteLog (lsTRACE, RippleCalc)
        << "deliverNodeForward<"
        << " nodeIndex_=" << nodeIndex_
        << " saInAct=" << saInAct
        << " saInFees=" << saInFees;

    return resultCode;
}
示例#14
0
// To deliver from an order book, when computing
TER PathCursor::deliverNodeReverse (
    Account const& uOutAccountID,  // --> Output owner's account.
    STAmount const& saOutReq,      // --> Funds requested to be
                                   // delivered for an increment.
    STAmount& saOutAct) const      // <-- Funds actually delivered for an
                                   // increment.
{
    TER resultCode   = tesSUCCESS;

    node().directory.restart(multiQuality_);

    // Accumulation of what the previous node must deliver.
    // Possible optimization: Note this gets zeroed on each increment, ideally
    // only on first increment, then it could be a limit on the forward pass.
    saOutAct.clear (saOutReq);

    WriteLog (lsTRACE, RippleCalc)
        << "deliverNodeReverse>"
        << " saOutAct=" << saOutAct
        << " saOutReq=" << saOutReq
        << " saPrvDlvReq=" << previousNode().saRevDeliver;

    assert (saOutReq != zero);

    int loopCount = 0;

    // While we did not deliver as much as requested:
    while (saOutAct < saOutReq)
    {
        if (++loopCount > CALC_NODE_DELIVER_MAX_LOOPS)
        {
            WriteLog (lsFATAL, RippleCalc) << "loop count exceeded";
            return telFAILED_PROCESSING;
        }

        resultCode = advanceNode (saOutAct, true);
        // If needed, advance to next funded offer.

        if (resultCode != tesSUCCESS || !node().offerIndex_)
            // Error or out of offers.
            break;

        auto const hasFee = node().offerOwnerAccount_ == node().issue_.account
            || uOutAccountID == node().issue_.account;
        // Issuer sending or receiving.

        const STAmount saOutFeeRate = hasFee
            ? saOne             // No fee.
            : node().transferRate_;   // Transfer rate of issuer.

        WriteLog (lsTRACE, RippleCalc)
            << "deliverNodeReverse:"
            << " offerOwnerAccount_="
            << node().offerOwnerAccount_
            << " uOutAccountID="
            << uOutAccountID
            << " node().issue_.account="
            << node().issue_.account
            << " node().transferRate_=" << node().transferRate_
            << " saOutFeeRate=" << saOutFeeRate;

        if (multiQuality_)
        {
            // In multi-quality mode, ignore rate.
        }
        else if (!node().saRateMax)
        {
            // Set initial rate.
            node().saRateMax = saOutFeeRate;

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: Set initial rate:"
                << " node().saRateMax=" << node().saRateMax
                << " saOutFeeRate=" << saOutFeeRate;
        }
        else if (saOutFeeRate > node().saRateMax)
        {
            // Offer exceeds initial rate.
            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: Offer exceeds initial rate:"
                << " node().saRateMax=" << node().saRateMax
                << " saOutFeeRate=" << saOutFeeRate;

            break;  // Done. Don't bother looking for smaller transferRates.
        }
        else if (saOutFeeRate < node().saRateMax)
        {
            // Reducing rate. Additional offers will only considered for this
            // increment if they are at least this good.
            //
            // At this point, the overall rate is reducing, while the overall
            // rate is not saOutFeeRate, it would be wrong to add anything with
            // a rate above saOutFeeRate.
            //
            // The rate would be reduced if the current offer was from the
            // issuer and the previous offer wasn't.

            node().saRateMax   = saOutFeeRate;

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: Reducing rate:"
                << " node().saRateMax=" << node().saRateMax;
        }

        // Amount that goes to the taker.
        STAmount saOutPassReq = std::min (
            std::min (node().saOfferFunds, node().saTakerGets),
            saOutReq - saOutAct);

        // Maximum out - assuming no out fees.
        STAmount saOutPassAct = saOutPassReq;

        // Amount charged to the offer owner.
        //
        // The fee goes to issuer. The fee is paid by offer owner and not passed
        // as a cost to taker.
        //
        // Round down: prefer liquidity rather than microscopic fees.
        STAmount saOutPlusFees   = STAmount::mulRound (
            saOutPassAct, saOutFeeRate, false);
        // Offer out with fees.

        WriteLog (lsTRACE, RippleCalc)
            << "deliverNodeReverse:"
            << " saOutReq=" << saOutReq
            << " saOutAct=" << saOutAct
            << " node().saTakerGets=" << node().saTakerGets
            << " saOutPassAct=" << saOutPassAct
            << " saOutPlusFees=" << saOutPlusFees
            << " node().saOfferFunds=" << node().saOfferFunds;

        if (saOutPlusFees > node().saOfferFunds)
        {
            // Offer owner can not cover all fees, compute saOutPassAct based on
            // node().saOfferFunds.
            saOutPlusFees   = node().saOfferFunds;

            // Round up: prefer liquidity rather than microscopic fees. But,
            // limit by requested.
            auto fee = STAmount::divRound (saOutPlusFees, saOutFeeRate, true);
            saOutPassAct = std::min (saOutPassReq, fee);

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: Total exceeds fees:"
                << " saOutPassAct=" << saOutPassAct
                << " saOutPlusFees=" << saOutPlusFees
                << " node().saOfferFunds=" << node().saOfferFunds;
        }

        // Compute portion of input needed to cover actual output.
        auto outputFee = STAmount::mulRound (
            saOutPassAct, node().saOfrRate, node().saTakerPays, true);
        STAmount saInPassReq = std::min (node().saTakerPays, outputFee);
        STAmount saInPassAct;

        WriteLog (lsTRACE, RippleCalc)
            << "deliverNodeReverse:"
            << " outputFee=" << outputFee
            << " saInPassReq=" << saInPassReq
            << " node().saOfrRate=" << node().saOfrRate
            << " saOutPassAct=" << saOutPassAct
            << " saOutPlusFees=" << saOutPlusFees;

        if (!saInPassReq) // FIXME: This is bogus
        {
            // After rounding did not want anything.
            WriteLog (lsDEBUG, RippleCalc)
                << "deliverNodeReverse: micro offer is unfunded.";

            node().bEntryAdvance   = true;
            continue;
        }
        // Find out input amount actually available at current rate.
        else if (!isXRP(previousNode().account_))
        {
            // account --> OFFER --> ?
            // Due to node expansion, previous is guaranteed to be the issuer.
            //
            // Previous is the issuer and receiver is an offer, so no fee or
            // quality.
            //
            // Previous is the issuer and has unlimited funds.
            //
            // Offer owner is obtaining IOUs via an offer, so credit line limits
            // are ignored.  As limits are ignored, don't need to adjust
            // previous account's balance.

            saInPassAct = saInPassReq;

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: account --> OFFER --> ? :"
                << " saInPassAct=" << saInPassAct;
        }
        else
        {
            // offer --> OFFER --> ?
            // Compute in previous offer node how much could come in.

            // TODO(tom): Fix nasty recursion here!
            resultCode = increment(-1).deliverNodeReverse(
                node().offerOwnerAccount_,
                saInPassReq,
                saInPassAct);

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: offer --> OFFER --> ? :"
                << " saInPassAct=" << saInPassAct;
        }

        if (resultCode != tesSUCCESS)
            break;

        if (saInPassAct < saInPassReq)
        {
            // Adjust output to conform to limited input.
            auto outputRequirements = STAmount::divRound (
                saInPassAct, node().saOfrRate, node().saTakerGets, true);
            saOutPassAct = std::min (saOutPassReq, outputRequirements);
            auto outputFees = STAmount::mulRound (
                saOutPassAct, saOutFeeRate, true);
            saOutPlusFees   = std::min (node().saOfferFunds, outputFees);

            WriteLog (lsTRACE, RippleCalc)
                << "deliverNodeReverse: adjusted:"
                << " saOutPassAct=" << saOutPassAct
                << " saOutPlusFees=" << saOutPlusFees;
        }
        else
        {
            // TODO(tom): more logging here.
            assert (saInPassAct == saInPassReq);
        }

        // Funds were spent.
        node().bFundsDirty = true;

        // Want to deduct output to limit calculations while computing reverse.
        // Don't actually need to send.
        //
        // Sending could be complicated: could fund a previous offer not yet
        // visited.  However, these deductions and adjustments are tenative.
        //
        // Must reset balances when going forward to perform actual transfers.
        resultCode   = ledger().accountSend (
            node().offerOwnerAccount_, node().issue_.account, saOutPassAct);

        if (resultCode != tesSUCCESS)
            break;

        // Adjust offer
        STAmount saTakerGetsNew  = node().saTakerGets - saOutPassAct;
        STAmount saTakerPaysNew  = node().saTakerPays - saInPassAct;

        if (saTakerPaysNew < zero || saTakerGetsNew < zero)
        {
            WriteLog (lsWARNING, RippleCalc)
                << "deliverNodeReverse: NEGATIVE:"
                << " node().saTakerPaysNew=" << saTakerPaysNew
                << " node().saTakerGetsNew=%s" << saTakerGetsNew;

            resultCode = telFAILED_PROCESSING;
            break;
        }

        node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
        node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);

        ledger().entryModify (node().sleOffer);

        if (saOutPassAct == node().saTakerGets)
        {
            // Offer became unfunded.
            WriteLog (lsDEBUG, RippleCalc)
                << "deliverNodeReverse: offer became unfunded.";

            node().bEntryAdvance   = true;
            // XXX When don't we want to set advance?
        }
        else
        {
            assert (saOutPassAct < node().saTakerGets);
        }

        saOutAct += saOutPassAct;
        // Accumulate what is to be delivered from previous node.
        previousNode().saRevDeliver += saInPassAct;
    }

    CondLog (saOutAct > saOutReq, lsWARNING, RippleCalc)
        << "deliverNodeReverse: TOO MUCH:"
        << " saOutAct=" << saOutAct
        << " saOutReq=" << saOutReq;

    assert (saOutAct <= saOutReq);

    if (resultCode == tesSUCCESS && !saOutAct)
        resultCode = tecPATH_DRY;
    // Unable to meet request, consider path dry.
    // Design invariant: if nothing was actually delivered, return tecPATH_DRY.

    WriteLog (lsTRACE, RippleCalc)
        << "deliverNodeReverse<"
        << " saOutAct=" << saOutAct
        << " saOutReq=" << saOutReq
        << " saPrvDlvReq=" << previousNode().saRevDeliver;

    return resultCode;
}
ter pathcursor::delivernodeforward (
    account const& uinaccountid,    // --> input owner's account.
    stamount const& sainreq,        // --> amount to deliver.
    stamount& sainact,              // <-- amount delivered, this invocation.
    stamount& sainfees) const       // <-- fees charged, this invocation.
{
    ter resultcode   = tessuccess;

    // don't deliver more than wanted.
    // zeroed in reverse pass.
    node().directory.restart(multiquality_);

    sainact.clear (sainreq);
    sainfees.clear (sainreq);

    int loopcount = 0;

    // xxx perhaps make sure do not exceed node().sarevdeliver as another way to
    // stop?
    while (resultcode == tessuccess && sainact + sainfees < sainreq)
    {
        // did not spend all inbound deliver funds.
        if (++loopcount > calc_node_deliver_max_loops)
        {
            writelog (lswarning, ripplecalc)
                << "delivernodeforward: max loops cndf";
            return telfailed_processing;
        }

        // determine values for pass to adjust sainact, sainfees, and
        // node().safwddeliver.
        advancenode (sainact, false);

        // if needed, advance to next funded offer.

        if (resultcode != tessuccess)
        {
        }
        else if (!node().offerindex_)
        {
            writelog (lswarning, ripplecalc)
                << "delivernodeforward: internal error: ran out of offers.";
            return telfailed_processing;
        }
        else if (resultcode == tessuccess)
        {
            // doesn't charge input. input funds are in limbo.
            // there's no fee if we're transferring xrp, if the sender is the
            // issuer, or if the receiver is the issuer.
            bool nofee = isnative (previousnode().issue_)
                || uinaccountid == previousnode().issue_.account
                || node().offerowneraccount_ == previousnode().issue_.account;
            const stamount sainfeerate = nofee ? saone
                : previousnode().transferrate_;  // transfer rate of issuer.

            // first calculate assuming no output fees: sainpassact,
            // sainpassfees, saoutpassact.

            // offer maximum out - limited by funds with out fees.
            auto saoutfunded = std::min (
                node().saofferfunds, node().satakergets);

            // offer maximum out - limit by most to deliver.
            auto saoutpassfunded = std::min (
                saoutfunded,
                node().sarevdeliver - node().safwddeliver);

            // offer maximum in - limited by by payout.
            auto sainfunded = mulround (
                saoutpassfunded,
                node().saofrrate,
                node().satakerpays,
                true);

            // offer maximum in with fees.
            auto saintotal = mulround (sainfunded, sainfeerate, true);
            auto sainremaining = sainreq - sainact - sainfees;

            if (sainremaining < zero)
                sainremaining.clear();

            // in limited by remaining.
            auto sainsum = std::min (saintotal, sainremaining);

            // in without fees.
            auto sainpassact = std::min (
                node().satakerpays, divround (
                    sainsum, sainfeerate, true));

            // out limited by in remaining.
            auto outpass = divround (
                sainpassact, node().saofrrate, node().satakergets, true);
            stamount saoutpassmax    = std::min (saoutpassfunded, outpass);

            stamount sainpassfeesmax = sainsum - sainpassact;

            // will be determined by next node().
            stamount saoutpassact;

            // will be determined by adjusted sainpassact.
            stamount sainpassfees;

            writelog (lstrace, ripplecalc)
                << "delivernodeforward:"
                << " nodeindex_=" << nodeindex_
                << " saoutfunded=" << saoutfunded
                << " saoutpassfunded=" << saoutpassfunded
                << " node().saofferfunds=" << node().saofferfunds
                << " node().satakergets=" << node().satakergets
                << " sainreq=" << sainreq
                << " sainact=" << sainact
                << " sainfees=" << sainfees
                << " sainfunded=" << sainfunded
                << " saintotal=" << saintotal
                << " sainsum=" << sainsum
                << " sainpassact=" << sainpassact
                << " saoutpassmax=" << saoutpassmax;

            // fixme: we remove an offer if we didn't want anything out of it?
            if (!node().satakerpays || sainsum <= zero)
            {
                writelog (lsdebug, ripplecalc)
                    << "delivernodeforward: microscopic offer unfunded.";

                // after math offer is effectively unfunded.
                pathstate_.unfundedoffers().push_back (node().offerindex_);
                node().bentryadvance = true;
                continue;
            }

            if (!sainfunded)
            {
                // previous check should catch this.
                writelog (lswarning, ripplecalc)
                    << "delivernodeforward: unreachable reached";

                // after math offer is effectively unfunded.
                pathstate_.unfundedoffers().push_back (node().offerindex_);
                node().bentryadvance = true;
                continue;
            }

            if (!isnative(nextnode().account_))
            {
                // ? --> offer --> account
                // input fees: vary based upon the consumed offer's owner.
                // output fees: none as xrp or the destination account is the
                // issuer.

                saoutpassact = saoutpassmax;
                sainpassfees = sainpassfeesmax;

                writelog (lstrace, ripplecalc)
                    << "delivernodeforward: ? --> offer --> account:"
                    << " offerowneraccount_="
                    << node().offerowneraccount_
                    << " nextnode().account_="
                    << nextnode().account_
                    << " saoutpassact=" << saoutpassact
                    << " saoutfunded=" << saoutfunded;

                // output: debit offer owner, send xrp or non-xpr to next
                // account.
                resultcode = ledger().accountsend (
                    node().offerowneraccount_,
                    nextnode().account_,
                    saoutpassact);

                if (resultcode != tessuccess)
                    break;
            }
            else
            {
                // ? --> offer --> offer
                //
                // offer to offer means current order book's output currency and
                // issuer match next order book's input current and issuer.
                //
                // output fees: possible if issuer has fees and is not on either
                // side.
                stamount saoutpassfees;

                // output fees vary as the next nodes offer owners may vary.
                // therefore, immediately push through output for current offer.
                resultcode = increment().delivernodeforward (
                    node().offerowneraccount_,  // --> current holder.
                    saoutpassmax,             // --> amount available.
                    saoutpassact,             // <-- amount delivered.
                    saoutpassfees);           // <-- fees charged.

                if (resultcode != tessuccess)
                    break;

                if (saoutpassact == saoutpassmax)
                {
                    // no fees and entire output amount.

                    sainpassfees = sainpassfeesmax;
                }
                else
                {
                    // fraction of output amount.
                    // output fees are paid by offer owner and not passed to
                    // previous.

                    assert (saoutpassact < saoutpassmax);
                    auto inpassact = mulround (
                        saoutpassact, node().saofrrate, sainreq, true);
                    sainpassact = std::min (node().satakerpays, inpassact);
                    auto inpassfees = mulround (
                        sainpassact, sainfeerate, true);
                    sainpassfees    = std::min (sainpassfeesmax, inpassfees);
                }

                // do outbound debiting.
                // send to issuer/limbo total amount including fees (issuer gets
                // fees).
                auto const& id = isxrp(node().issue_) ?
                    xrpaccount() : (isvbc(node().issue_) ? vbcaccount() : node().issue_.account);
                auto outpasstotal = saoutpassact + saoutpassfees;
                ledger().accountsend (
                    node().offerowneraccount_,
                    id,
                    outpasstotal);

                writelog (lstrace, ripplecalc)
                    << "delivernodeforward: ? --> offer --> offer:"
                    << " saoutpassact=" << saoutpassact
                    << " saoutpassfees=" << saoutpassfees;
            }

            writelog (lstrace, ripplecalc)
                << "delivernodeforward: "
                << " nodeindex_=" << nodeindex_
                << " node().satakergets=" << node().satakergets
                << " node().satakerpays=" << node().satakerpays
                << " sainpassact=" << sainpassact
                << " sainpassfees=" << sainpassfees
                << " saoutpassact=" << saoutpassact
                << " saoutfunded=" << saoutfunded;

            // funds were spent.
            node().bfundsdirty = true;

            // do inbound crediting.
            //
            // credit offer owner from in issuer/limbo (input transfer fees left
            // with owner).  don't attempt to have someone credit themselves, it
            // is redundant.
            if (isnative (previousnode().issue_.currency)
                || uinaccountid != node().offerowneraccount_)
            {
				auto id = !isxrp(previousnode().issue_.currency) ?
                        (isvbc(previousnode().issue_.currency) ? vbcaccount() : uinaccountid) : xrpaccount();

                resultcode = ledger().accountsend (
                    id,
                    node().offerowneraccount_,
                    sainpassact);

                if (resultcode != tessuccess)
                    break;
            }

            // adjust offer.
            //
            // fees are considered paid from a seperate budget and are not named
            // in the offer.
            stamount satakergetsnew  = node().satakergets - saoutpassact;
            stamount satakerpaysnew  = node().satakerpays - sainpassact;

            if (satakerpaysnew < zero || satakergetsnew < zero)
            {
                writelog (lswarning, ripplecalc)
                    << "delivernodeforward: negative:"
                    << " satakerpaysnew=" << satakerpaysnew
                    << " satakergetsnew=" << satakergetsnew;

                resultcode = telfailed_processing;
                break;
            }

            node().sleoffer->setfieldamount (sftakergets, satakergetsnew);
            node().sleoffer->setfieldamount (sftakerpays, satakerpaysnew);

            ledger().entrymodify (node().sleoffer);

            if (saoutpassact == saoutfunded || satakergetsnew == zero)
            {
                // offer became unfunded.

                writelog (lswarning, ripplecalc)
                    << "delivernodeforward: unfunded:"
                    << " saoutpassact=" << saoutpassact
                    << " saoutfunded=" << saoutfunded;

                pathstate_.unfundedoffers().push_back (node().offerindex_);
                node().bentryadvance   = true;
            }
            else
            {
                condlog (saoutpassact >= saoutfunded, lswarning, ripplecalc)
                    << "delivernodeforward: too much:"
                    << " saoutpassact=" << saoutpassact
                    << " saoutfunded=" << saoutfunded;

                assert (saoutpassact < saoutfunded);
            }

            sainact += sainpassact;
            sainfees += sainpassfees;

            // adjust amount available to next node().
            node().safwddeliver = std::min (node().sarevdeliver,
                                        node().safwddeliver + saoutpassact);
        }
    }

    writelog (lstrace, ripplecalc)
        << "delivernodeforward<"
        << " nodeindex_=" << nodeindex_
        << " sainact=" << sainact
        << " sainfees=" << sainfees;

    return resultcode;
}