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); }
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; }
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()); }
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); }
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); }
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); }
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); }
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"); }
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); }
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); }
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); }
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; }
// 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; }