mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 19:15:54 +00:00
Merge branch 'master' of github.com:jedmccaleb/NewCoin into api2
Conflicts: test/config.js
This commit is contained in:
@@ -275,7 +275,10 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger,
|
|||||||
if (it != mEntries.end())
|
if (it != mEntries.end())
|
||||||
{
|
{
|
||||||
if (it->second.mAction == taaDELETE)
|
if (it->second.mAction == taaDELETE)
|
||||||
|
{
|
||||||
|
cLog(lsFATAL) << "Trying to thread to deleted node";
|
||||||
return SLE::pointer();
|
return SLE::pointer();
|
||||||
|
}
|
||||||
if (it->second.mAction == taaCACHED)
|
if (it->second.mAction == taaCACHED)
|
||||||
it->second.mAction = taaMODIFY;
|
it->second.mAction = taaMODIFY;
|
||||||
if (it->second.mSeq != mSeq)
|
if (it->second.mSeq != mSeq)
|
||||||
@@ -288,7 +291,10 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger,
|
|||||||
|
|
||||||
boost::unordered_map<uint256, SLE::pointer>::iterator me = newMods.find(node);
|
boost::unordered_map<uint256, SLE::pointer>::iterator me = newMods.find(node);
|
||||||
if (me != newMods.end())
|
if (me != newMods.end())
|
||||||
|
{
|
||||||
|
assert(me->second);
|
||||||
return me->second;
|
return me->second;
|
||||||
|
}
|
||||||
|
|
||||||
SLE::pointer ret = ledger->getSLE(node);
|
SLE::pointer ret = ledger->getSLE(node);
|
||||||
if (ret)
|
if (ret)
|
||||||
@@ -306,6 +312,7 @@ bool LedgerEntrySet::threadTx(const RippleAddress& threadTo, Ledger::ref ledger,
|
|||||||
SLE::pointer sle = getForMod(Ledger::getAccountRootIndex(threadTo.getAccountID()), ledger, newMods);
|
SLE::pointer sle = getForMod(Ledger::getAccountRootIndex(threadTo.getAccountID()), ledger, newMods);
|
||||||
if (!sle)
|
if (!sle)
|
||||||
{
|
{
|
||||||
|
cLog(lsFATAL) << "Threading to non-existent account: " << threadTo.humanAccountID();
|
||||||
assert(false);
|
assert(false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -471,15 +471,13 @@ TER RippleCalc::calcNodeDeliverRev(
|
|||||||
return terResult;
|
return terResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deliver maximum amount of funds from previous node.
|
// For current offer, get input from deliver/limbo and output to next account or deliver for next offers.
|
||||||
// Goal: Make progress consuming the offer.
|
|
||||||
TER RippleCalc::calcNodeDeliverFwd(
|
TER RippleCalc::calcNodeDeliverFwd(
|
||||||
const unsigned int uNode, // 0 < uNode < uLast
|
const unsigned int uNode, // 0 < uNode < uLast
|
||||||
PathState::ref pspCur,
|
PathState::ref pspCur,
|
||||||
const bool bMultiQuality,
|
const bool bMultiQuality,
|
||||||
const uint160& uInAccountID, // --> Input owner's account.
|
const uint160& uInAccountID, // --> Input owner's account.
|
||||||
const STAmount& saInFunds, // --> Funds available for delivery and fees.
|
const STAmount& saInReq, // --> Amount to deliver.
|
||||||
const STAmount& saInReq, // --> Limit to deliver.
|
|
||||||
STAmount& saInAct, // <-- Amount delivered.
|
STAmount& saInAct, // <-- Amount delivered.
|
||||||
STAmount& saInFees) // <-- Fees charged.
|
STAmount& saInFees) // <-- Fees charged.
|
||||||
{
|
{
|
||||||
@@ -490,7 +488,9 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
PaymentNode& pnNxt = pspCur->vpnNodes[uNode+1];
|
PaymentNode& pnNxt = pspCur->vpnNodes[uNode+1];
|
||||||
|
|
||||||
const uint160& uNxtAccountID = pnNxt.uAccountID;
|
const uint160& uNxtAccountID = pnNxt.uAccountID;
|
||||||
|
const uint160& uCurCurrencyID = pnCur.uCurrencyID;
|
||||||
const uint160& uCurIssuerID = pnCur.uIssuerID;
|
const uint160& uCurIssuerID = pnCur.uIssuerID;
|
||||||
|
const uint160& uPrvCurrencyID = pnPrv.uCurrencyID;
|
||||||
const uint160& uPrvIssuerID = pnPrv.uIssuerID;
|
const uint160& uPrvIssuerID = pnPrv.uIssuerID;
|
||||||
const STAmount& saTransferRate = pnPrv.saTransferRate;
|
const STAmount& saTransferRate = pnPrv.saTransferRate;
|
||||||
|
|
||||||
@@ -500,17 +500,18 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
|
|
||||||
uDirectTip = 0; // Restart book searching.
|
uDirectTip = 0; // Restart book searching.
|
||||||
|
|
||||||
saInAct.zero(saInFunds);
|
saInAct.zero(saInReq);
|
||||||
saInFees.zero(saInFunds);
|
saInFees.zero(saInReq);
|
||||||
|
saCurDeliverAct.zero(uCurCurrencyID, uCurIssuerID);
|
||||||
|
|
||||||
while (tesSUCCESS == terResult
|
while (tesSUCCESS == terResult
|
||||||
&& saInAct != saInReq // Did not deliver limit.
|
&& saInAct + saInFees != saInReq) // Did not deliver all funds.
|
||||||
&& saInAct + saInFees != saInFunds) // Did not deliver all funds.
|
|
||||||
{
|
{
|
||||||
terResult = calcNodeAdvance(uNode, pspCur, bMultiQuality, false); // If needed, advance to next funded offer.
|
terResult = calcNodeAdvance(uNode, pspCur, bMultiQuality, false); // If needed, advance to next funded offer.
|
||||||
|
|
||||||
if (tesSUCCESS == terResult)
|
if (tesSUCCESS == terResult)
|
||||||
{
|
{
|
||||||
|
// Doesn't charge input. Input funds are in limbo.
|
||||||
bool& bEntryAdvance = pnCur.bEntryAdvance;
|
bool& bEntryAdvance = pnCur.bEntryAdvance;
|
||||||
STAmount& saOfrRate = pnCur.saOfrRate;
|
STAmount& saOfrRate = pnCur.saOfrRate;
|
||||||
uint256& uOfferIndex = pnCur.uOfferIndex;
|
uint256& uOfferIndex = pnCur.uOfferIndex;
|
||||||
@@ -521,9 +522,11 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
STAmount& saTakerPays = pnCur.saTakerPays;
|
STAmount& saTakerPays = pnCur.saTakerPays;
|
||||||
STAmount& saTakerGets = pnCur.saTakerGets;
|
STAmount& saTakerGets = pnCur.saTakerGets;
|
||||||
|
|
||||||
const STAmount saInFeeRate = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending.
|
const STAmount saInFeeRate = !!uPrvCurrencyID
|
||||||
|
? uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending.
|
||||||
? saOne // No fee.
|
? saOne // No fee.
|
||||||
: saTransferRate; // Transfer rate of issuer.
|
: saTransferRate // Transfer rate of issuer.
|
||||||
|
: saOne;
|
||||||
|
|
||||||
//
|
//
|
||||||
// First calculate assuming no output fees.
|
// First calculate assuming no output fees.
|
||||||
@@ -532,11 +535,11 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees.
|
STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees.
|
||||||
STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout.
|
STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout.
|
||||||
STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees.
|
STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees.
|
||||||
STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds.
|
STAmount saInSum = std::min(saInTotal, saInReq-saInAct-saInFees); // In limited by saInReq.
|
||||||
STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees.
|
STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees.
|
||||||
STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out.
|
STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out.
|
||||||
|
|
||||||
STAmount saInPassFees(saInFunds.getCurrency(), saInFunds.getIssuer());
|
STAmount saInPassFees(saInReq.getCurrency(), saInReq.getIssuer());
|
||||||
STAmount saOutPassAct(saOfferFunds.getCurrency(), saOfferFunds.getIssuer());
|
STAmount saOutPassAct(saOfferFunds.getCurrency(), saOfferFunds.getIssuer());
|
||||||
|
|
||||||
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s")
|
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s")
|
||||||
@@ -551,22 +554,21 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
{
|
{
|
||||||
// ? --> OFFER --> account
|
// ? --> OFFER --> account
|
||||||
// Input fees: vary based upon the consumed offer's owner.
|
// Input fees: vary based upon the consumed offer's owner.
|
||||||
// Output fees: none as the destination account is the issuer.
|
// Output fees: none as XRP or the destination account is the issuer.
|
||||||
|
|
||||||
// XXX This doesn't claim input.
|
|
||||||
// XXX Assumes input is in limbo. XXX Check.
|
|
||||||
|
|
||||||
// Debit offer owner.
|
|
||||||
lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax);
|
|
||||||
|
|
||||||
saOutPassAct = saOutPassMax;
|
saOutPassAct = saOutPassMax;
|
||||||
|
|
||||||
|
// Debit offer owner, send XRP or non-XPR to next account.
|
||||||
|
lesActive.accountSend(uOfrOwnerID, uNxtAccountID, saOutPassAct);
|
||||||
|
|
||||||
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s")
|
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s")
|
||||||
% saOutPassAct);
|
% saOutPassAct);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// ? --> OFFER --> offer
|
// ? --> OFFER --> offer
|
||||||
|
// Offer to offer means current order book's output currency and issuer match next order book's input current and
|
||||||
|
// issuer.
|
||||||
STAmount saOutPassFees;
|
STAmount saOutPassFees;
|
||||||
|
|
||||||
terResult = RippleCalc::calcNodeDeliverFwd(
|
terResult = RippleCalc::calcNodeDeliverFwd(
|
||||||
@@ -575,16 +577,22 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
bMultiQuality,
|
bMultiQuality,
|
||||||
uOfrOwnerID,
|
uOfrOwnerID,
|
||||||
saOutPassMax,
|
saOutPassMax,
|
||||||
saOutPassMax,
|
|
||||||
saOutPassAct, // <-- Amount delivered.
|
saOutPassAct, // <-- Amount delivered.
|
||||||
saOutPassFees); // <-- Fees charged.
|
saOutPassFees); // <-- Fees charged.
|
||||||
|
|
||||||
if (tesSUCCESS != terResult)
|
if (tesSUCCESS != terResult)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Offer maximum in limited by next payout.
|
// Offer maximum in split into fees by next payout.
|
||||||
saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate);
|
saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate);
|
||||||
saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct;
|
saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct;
|
||||||
|
|
||||||
|
// Do outbound debiting.
|
||||||
|
// Send to issuer/limbo total amount (no fees to issuer).
|
||||||
|
lesActive.accountSend(uOfrOwnerID, !!uCurCurrencyID ? uCurIssuerID : ACCOUNT_XRP, saOutPassAct);
|
||||||
|
|
||||||
|
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> offer: saOutPassAct=%s")
|
||||||
|
% saOutPassAct);
|
||||||
}
|
}
|
||||||
|
|
||||||
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s")
|
cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s")
|
||||||
@@ -596,13 +604,12 @@ TER RippleCalc::calcNodeDeliverFwd(
|
|||||||
// Funds were spent.
|
// Funds were spent.
|
||||||
bFundsDirty = true;
|
bFundsDirty = true;
|
||||||
|
|
||||||
// Credit issuer transfer fees.
|
// Do inbound crediting.
|
||||||
lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassFees);
|
// Credit offer owner from in issuer/limbo (don't take transfer fees).
|
||||||
|
lesActive.accountSend(!!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP, uOfrOwnerID, saInPassAct);
|
||||||
// Credit offer owner from offer.
|
|
||||||
lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassAct);
|
|
||||||
|
|
||||||
// Adjust offer
|
// Adjust offer
|
||||||
|
// Fees are considered paid from a seperate budget and are not named in the offer.
|
||||||
sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPassAct);
|
sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPassAct);
|
||||||
sleOffer->setFieldAmount(sfTakerPays, saTakerPays - saInPassAct);
|
sleOffer->setFieldAmount(sfTakerPays, saTakerPays - saInPassAct);
|
||||||
|
|
||||||
@@ -661,7 +668,7 @@ TER RippleCalc::calcNodeOfferRev(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called to drive the from the first offer node in a chain.
|
// Called to drive the from the first offer node in a chain.
|
||||||
// - Offer input is limbo.
|
// - Offer input is in issuer/limbo.
|
||||||
// - Current offers consumed.
|
// - Current offers consumed.
|
||||||
// - Current offer owners debited.
|
// - Current offer owners debited.
|
||||||
// - Transfer fees credited to issuer.
|
// - Transfer fees credited to issuer.
|
||||||
@@ -688,7 +695,6 @@ TER RippleCalc::calcNodeOfferFwd(
|
|||||||
bMultiQuality,
|
bMultiQuality,
|
||||||
pnPrv.uAccountID,
|
pnPrv.uAccountID,
|
||||||
pnPrv.saFwdDeliver,
|
pnPrv.saFwdDeliver,
|
||||||
pnPrv.saFwdDeliver,
|
|
||||||
saInAct,
|
saInAct,
|
||||||
saInFees);
|
saInFees);
|
||||||
|
|
||||||
@@ -843,6 +849,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
|
|||||||
|
|
||||||
const uint160& uCurrencyID = pnCur.uCurrencyID;
|
const uint160& uCurrencyID = pnCur.uCurrencyID;
|
||||||
|
|
||||||
|
// XXX Don't look up quality for XRP
|
||||||
const uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE;
|
const uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE;
|
||||||
const uint32 uQualityOut = uNode != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE;
|
const uint32 uQualityOut = uNode != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE;
|
||||||
|
|
||||||
@@ -908,15 +915,16 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
|
|||||||
|| !saNxtOwed.isNegative() // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: Sender holding next IOUs.
|
|| !saNxtOwed.isNegative() // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: Sender holding next IOUs.
|
||||||
|| -saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed.
|
|| -saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed.
|
||||||
|
|
||||||
if (bPrvAccount && bNxtAccount)
|
|
||||||
{
|
|
||||||
if (!uNode)
|
if (!uNode)
|
||||||
{
|
{
|
||||||
// ^ --> ACCOUNT --> account|offer
|
// ^ --> ACCOUNT --> account|offer
|
||||||
// Nothing to do, there is no previous to adjust.
|
// Nothing to do, there is no previous to adjust.
|
||||||
|
|
||||||
nothing();
|
nothing();
|
||||||
}
|
}
|
||||||
else if (uNode == uLast)
|
else if (bPrvAccount && bNxtAccount)
|
||||||
|
{
|
||||||
|
if (uNode == uLast)
|
||||||
{
|
{
|
||||||
// account --> ACCOUNT --> $
|
// account --> ACCOUNT --> $
|
||||||
// Overall deliverable.
|
// Overall deliverable.
|
||||||
@@ -1157,7 +1165,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The reverse pass has been narrowing by credit available and inflating by fees as it worked backwards.
|
// The reverse pass has been narrowing by credit available and inflating by fees as it worked backwards.
|
||||||
// Now, push through the actual amount to each node and adjust balances.
|
// Now, for the current account node, take the actual amount from previous and adjust forward balances.
|
||||||
//
|
//
|
||||||
// Perform balance adjustments between previous and current node.
|
// Perform balance adjustments between previous and current node.
|
||||||
// - The previous node: specifies what to push through to current.
|
// - The previous node: specifies what to push through to current.
|
||||||
@@ -1165,6 +1173,8 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC
|
|||||||
// Then, compute current node's output for next node.
|
// Then, compute current node's output for next node.
|
||||||
// - Current node: specify what to push through to next.
|
// - Current node: specify what to push through to next.
|
||||||
// - Output to next node is computed as input minus quality or transfer fee.
|
// - Output to next node is computed as input minus quality or transfer fee.
|
||||||
|
// - If next node is an offer and output is non-XRP then we are the issuer and do not need to push funds.
|
||||||
|
// - If next node is an offer and output is XRP then we need to deliver funds to limbo.
|
||||||
TER RippleCalc::calcNodeAccountFwd(
|
TER RippleCalc::calcNodeAccountFwd(
|
||||||
const unsigned int uNode, // 0 <= uNode <= uLast
|
const unsigned int uNode, // 0 <= uNode <= uLast
|
||||||
PathState::ref pspCur,
|
PathState::ref pspCur,
|
||||||
@@ -1186,6 +1196,8 @@ TER RippleCalc::calcNodeAccountFwd(
|
|||||||
const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID;
|
const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID;
|
||||||
const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue.
|
const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue.
|
||||||
|
|
||||||
|
const uint160& uCurIssuerID = pnCur.uIssuerID;
|
||||||
|
|
||||||
const uint160& uCurrencyID = pnCur.uCurrencyID;
|
const uint160& uCurrencyID = pnCur.uCurrencyID;
|
||||||
|
|
||||||
uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE;
|
uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE;
|
||||||
@@ -1216,6 +1228,9 @@ TER RippleCalc::calcNodeAccountFwd(
|
|||||||
const STAmount& saCurDeliverReq = pnCur.saRevDeliver;
|
const STAmount& saCurDeliverReq = pnCur.saRevDeliver;
|
||||||
STAmount& saCurDeliverAct = pnCur.saFwdDeliver;
|
STAmount& saCurDeliverAct = pnCur.saFwdDeliver;
|
||||||
|
|
||||||
|
// For !uNode
|
||||||
|
STAmount& saCurSendMaxPass = pspCur->saInPass; // Report how much pass sends.
|
||||||
|
|
||||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s")
|
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s")
|
||||||
% uNode
|
% uNode
|
||||||
% uLast
|
% uLast
|
||||||
@@ -1242,7 +1257,6 @@ TER RippleCalc::calcNodeAccountFwd(
|
|||||||
const STAmount saCurSendMaxReq = pspCur->saInReq.isNegative()
|
const STAmount saCurSendMaxReq = pspCur->saInReq.isNegative()
|
||||||
? pspCur->saInReq // Negative for no limit, doing a calculation.
|
? pspCur->saInReq // Negative for no limit, doing a calculation.
|
||||||
: pspCur->saInReq-pspCur->saInAct; // request - done.
|
: pspCur->saInReq-pspCur->saInAct; // request - done.
|
||||||
STAmount& saCurSendMaxPass = pspCur->saInPass; // Report how much pass sends.
|
|
||||||
|
|
||||||
saCurRedeemAct = saCurRedeemReq
|
saCurRedeemAct = saCurRedeemReq
|
||||||
// Redeem requested.
|
// Redeem requested.
|
||||||
@@ -1343,21 +1357,24 @@ TER RippleCalc::calcNodeAccountFwd(
|
|||||||
}
|
}
|
||||||
else if (bPrvAccount && !bNxtAccount)
|
else if (bPrvAccount && !bNxtAccount)
|
||||||
{
|
{
|
||||||
// account --> ACCOUNT --> offer
|
if (uNode)
|
||||||
cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer"));
|
{
|
||||||
|
// Non-XRP, current node is the issuer.
|
||||||
|
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer"));
|
||||||
|
|
||||||
saCurDeliverAct.zero(saCurDeliverReq);
|
saCurDeliverAct.zero(saCurDeliverReq);
|
||||||
|
|
||||||
// redeem -> issue.
|
// redeem -> issue/deliver.
|
||||||
// wants to redeem and current would and can issue.
|
// Previous wants to redeem.
|
||||||
// If redeeming cur to next is done, this implies can issue.
|
// Current is issuing to an offer so leave funds in account as "limbo".
|
||||||
if (saPrvRedeemReq) // Previous wants to redeem.
|
if (saPrvRedeemReq) // Previous wants to redeem.
|
||||||
{
|
{
|
||||||
// Rate : 1.0 : transfer_rate
|
// Rate : 1.0 : transfer_rate
|
||||||
|
// XXX Is having the transfer rate here correct?
|
||||||
calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax);
|
calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
// issue -> issue
|
// issue -> issue/deliver
|
||||||
if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs.
|
if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs.
|
||||||
&& saPrvIssueReq) // Previous wants to issue. To next must be ok.
|
&& saPrvIssueReq) // Previous wants to issue. To next must be ok.
|
||||||
{
|
{
|
||||||
@@ -1366,9 +1383,46 @@ TER RippleCalc::calcNodeAccountFwd(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adjust prv --> cur balance : take all inbound
|
// Adjust prv --> cur balance : take all inbound
|
||||||
// XXX Currency must be in amount.
|
|
||||||
lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false);
|
lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Delivering amount requested from downstream.
|
||||||
|
saCurDeliverAct = saCurDeliverReq;
|
||||||
|
|
||||||
|
// If limited, then limit by send max and available.
|
||||||
|
if (!pspCur->saInReq.isNegative())
|
||||||
|
{
|
||||||
|
saCurDeliverAct = pspCur->saInReq-pspCur->saInAct;
|
||||||
|
|
||||||
|
// Limit XRP by available. No limit for non-XRP as issuer.
|
||||||
|
if (!uCurAccountID)
|
||||||
|
saCurDeliverAct = std::min(saCurDeliverAct, lesActive.accountHolds(uCurAccountID, CURRENCY_XRP, ACCOUNT_XRP));
|
||||||
|
|
||||||
|
}
|
||||||
|
saCurSendMaxPass = saCurDeliverAct; // Record amount sent for pass.
|
||||||
|
|
||||||
|
if (!!uCurrencyID)
|
||||||
|
{
|
||||||
|
// Non-XRP, current node is the issuer.
|
||||||
|
// We could be delivering to multiple accounts, so we don't know which ripple balance will be adjusted. Assume
|
||||||
|
// just issuing.
|
||||||
|
|
||||||
|
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- !XRP --> offer"));
|
||||||
|
|
||||||
|
// As the issuer, would only issue.
|
||||||
|
// Don't need to actually deliver. As from delivering leave in the issuer as limbo.
|
||||||
|
nothing();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- XRP --> offer"));
|
||||||
|
|
||||||
|
// Deliver XRP to limbo.
|
||||||
|
lesActive.accountSend(uCurAccountID, ACCOUNT_XRP, saCurDeliverAct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (!bPrvAccount && bNxtAccount)
|
else if (!bPrvAccount && bNxtAccount)
|
||||||
{
|
{
|
||||||
if (uNode == uLast)
|
if (uNode == uLast)
|
||||||
|
|||||||
@@ -163,7 +163,6 @@ public:
|
|||||||
PathState::ref pspCur,
|
PathState::ref pspCur,
|
||||||
const bool bMultiQuality,
|
const bool bMultiQuality,
|
||||||
const uint160& uInAccountID,
|
const uint160& uInAccountID,
|
||||||
const STAmount& saInFunds,
|
|
||||||
const STAmount& saInReq,
|
const STAmount& saInReq,
|
||||||
STAmount& saInAct,
|
STAmount& saInAct,
|
||||||
STAmount& saInFees);
|
STAmount& saInFees);
|
||||||
|
|||||||
@@ -189,6 +189,36 @@ int ValidationCollection::getLoadRatio(bool overLoaded)
|
|||||||
return (goodNodes * 100) / (goodNodes + badNodes);
|
return (goodNodes * 100) / (goodNodes + badNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::list<SerializedValidation::pointer> ValidationCollection::getCurrentTrustedValidations()
|
||||||
|
{
|
||||||
|
uint32 cutoff = theApp->getOPs().getNetworkTimeNC() - LEDGER_VAL_INTERVAL;
|
||||||
|
|
||||||
|
std::list<SerializedValidation::pointer> ret;
|
||||||
|
|
||||||
|
boost::mutex::scoped_lock sl(mValidationLock);
|
||||||
|
boost::unordered_map<uint160, SerializedValidation::pointer>::iterator it = mCurrentValidations.begin();
|
||||||
|
while (it != mCurrentValidations.end())
|
||||||
|
{
|
||||||
|
if (!it->second) // contains no record
|
||||||
|
it = mCurrentValidations.erase(it);
|
||||||
|
else if (it->second->getSignTime() < cutoff)
|
||||||
|
{ // contains a stale record
|
||||||
|
mStaleValidations.push_back(it->second);
|
||||||
|
it->second.reset();
|
||||||
|
condWrite();
|
||||||
|
it = mCurrentValidations.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // contains a live record
|
||||||
|
if (it->second->isTrusted())
|
||||||
|
ret.push_back(it->second);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
boost::unordered_map<uint256, currentValidationCount>
|
boost::unordered_map<uint256, currentValidationCount>
|
||||||
ValidationCollection::getCurrentValidations(uint256 currentLedger)
|
ValidationCollection::getCurrentValidations(uint256 currentLedger)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ public:
|
|||||||
int getLoadRatio(bool overLoaded);
|
int getLoadRatio(bool overLoaded);
|
||||||
|
|
||||||
boost::unordered_map<uint256, currentValidationCount> getCurrentValidations(uint256 currentLedger);
|
boost::unordered_map<uint256, currentValidationCount> getCurrentValidations(uint256 currentLedger);
|
||||||
|
std::list<SerializedValidation::pointer> getCurrentTrustedValidations();
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
void sweep() { mValidations.sweep(); }
|
void sweep() { mValidations.sweep(); }
|
||||||
|
|||||||
@@ -176,12 +176,12 @@ Request.prototype.accounts = function (accounts) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
// --> trusted: truthy, if remote is trusted
|
// --> trusted: truthy, if remote is trusted
|
||||||
var Remote = function (trusted, websocket_ip, websocket_port, trace) {
|
var Remote = function (opts, trace) {
|
||||||
this.trusted = trusted;
|
this.trusted = opts.trusted;
|
||||||
this.websocket_ip = websocket_ip;
|
this.websocket_ip = opts.websocket_ip;
|
||||||
this.websocket_port = websocket_port;
|
this.websocket_port = opts.websocket_port;
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
this.trace = trace;
|
this.trace = opts.trace || trace;
|
||||||
this._ledger_current_index = undefined;
|
this._ledger_current_index = undefined;
|
||||||
this._ledger_hash = undefined;
|
this._ledger_hash = undefined;
|
||||||
this._ledger_time = undefined;
|
this._ledger_time = undefined;
|
||||||
@@ -219,10 +219,10 @@ var Remote = function (trusted, websocket_ip, websocket_port, trace) {
|
|||||||
|
|
||||||
Remote.prototype = new EventEmitter;
|
Remote.prototype = new EventEmitter;
|
||||||
|
|
||||||
Remote.from_config = function (name, trace) {
|
Remote.from_config = function (obj, trace) {
|
||||||
var serverConfig = exports.config.servers[name];
|
var serverConfig = 'string' === typeof obj ? exports.config.servers[obj] : obj;
|
||||||
|
|
||||||
var remote = new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, trace);
|
var remote = new Remote(serverConfig, trace);
|
||||||
|
|
||||||
for (var account in exports.config.accounts) {
|
for (var account in exports.config.accounts) {
|
||||||
var accountInfo = exports.config.accounts[account];
|
var accountInfo = exports.config.accounts[account];
|
||||||
@@ -450,12 +450,12 @@ Remote.prototype._connect_message = function (ws, json) {
|
|||||||
unexpected = true;
|
unexpected = true;
|
||||||
}
|
}
|
||||||
else if ('success' === message.status) {
|
else if ('success' === message.status) {
|
||||||
if (this.trace) console.log("remote: response: %s", json);
|
if (this.trace) console.log("remote: response: %s", JSON.stringify(message, undefined, 2));
|
||||||
|
|
||||||
request.emit('success', message.result);
|
request.emit('success', message.result);
|
||||||
}
|
}
|
||||||
else if (message.error) {
|
else if (message.error) {
|
||||||
if (this.trace) console.log("remote: error: %s", json);
|
if (this.trace) console.log("remote: error: %s", JSON.stringify(message, undefined, 2));
|
||||||
|
|
||||||
request.emit('error', {
|
request.emit('error', {
|
||||||
'error' : 'remoteError',
|
'error' : 'remoteError',
|
||||||
@@ -700,7 +700,7 @@ Remote.prototype.submit = function (transaction) {
|
|||||||
if (transaction.secret && !this.trusted)
|
if (transaction.secret && !this.trusted)
|
||||||
{
|
{
|
||||||
transaction.emit('error', {
|
transaction.emit('error', {
|
||||||
'result' : 'serverUntrusted',
|
'result' : 'tejServerUntrusted',
|
||||||
'result_message' : "Attempt to give a secret to an untrusted server."
|
'result_message' : "Attempt to give a secret to an untrusted server."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1026,6 +1026,7 @@ var SUBMIT_LOST = 8; // Give up tracking.
|
|||||||
var Transaction = function (remote) {
|
var Transaction = function (remote) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
this.callback = undefined;
|
||||||
this.remote = remote;
|
this.remote = remote;
|
||||||
this.secret = undefined;
|
this.secret = undefined;
|
||||||
this.build_path = true;
|
this.build_path = true;
|
||||||
@@ -1108,14 +1109,25 @@ Transaction.prototype.set_state = function (state) {
|
|||||||
// XXX Don't allow a submit without knowing ledger_index.
|
// XXX Don't allow a submit without knowing ledger_index.
|
||||||
// XXX Have a network canSubmit(), post events for following.
|
// XXX Have a network canSubmit(), post events for following.
|
||||||
// XXX Also give broader status for tracking through network disconnects.
|
// XXX Also give broader status for tracking through network disconnects.
|
||||||
Transaction.prototype.submit = function () {
|
// callback = function (status, info) {
|
||||||
|
// // status is final status. Only works under a ledger_accepting conditions.
|
||||||
|
// switch status:
|
||||||
|
// case 'tesSUCCESS': all is well.
|
||||||
|
// case 'tejServerUntrusted': sending secret to untrusted server.
|
||||||
|
// case 'tejInvalidAccount': locally detected error.
|
||||||
|
// case 'tejLost': locally gave up looking
|
||||||
|
// default: some other TER
|
||||||
|
// }
|
||||||
|
Transaction.prototype.submit = function (callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var tx_json = this.tx_json;
|
var tx_json = this.tx_json;
|
||||||
|
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
if ('string' !== typeof tx_json.Account)
|
if ('string' !== typeof tx_json.Account)
|
||||||
{
|
{
|
||||||
this.emit('error', {
|
(this.callback || this.emit)('error', {
|
||||||
'error' : 'invalidAccount',
|
'error' : 'tejInvalidAccount',
|
||||||
'error_message' : 'Bad account.'
|
'error_message' : 'Bad account.'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -1134,11 +1146,12 @@ Transaction.prototype.submit = function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) {
|
if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) {
|
||||||
// There are listeners for 'final', 'lost', or 'pending' arrange to emit them.
|
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
|
||||||
|
|
||||||
this.submit_index = this.remote._ledger_current_index;
|
this.submit_index = this.remote._ledger_current_index;
|
||||||
|
|
||||||
|
// When a ledger closes, look for the result.
|
||||||
var on_ledger_closed = function (ledger_hash, ledger_index) {
|
var on_ledger_closed = function (ledger_hash, ledger_index) {
|
||||||
var stop = false;
|
var stop = false;
|
||||||
|
|
||||||
@@ -1148,6 +1161,11 @@ Transaction.prototype.submit = function () {
|
|||||||
.on('success', function (message) {
|
.on('success', function (message) {
|
||||||
self.set_state(message.metadata.TransactionResult);
|
self.set_state(message.metadata.TransactionResult);
|
||||||
self.emit('final', message);
|
self.emit('final', message);
|
||||||
|
|
||||||
|
if (self.callback)
|
||||||
|
self.callback(message.metadata.TransactionResult, message);
|
||||||
|
|
||||||
|
stop = true;
|
||||||
})
|
})
|
||||||
.on('error', function (message) {
|
.on('error', function (message) {
|
||||||
if ('remoteError' === message.error
|
if ('remoteError' === message.error
|
||||||
@@ -1155,6 +1173,10 @@ Transaction.prototype.submit = function () {
|
|||||||
if (self.submit_index + SUBMIT_LOST < ledger_index) {
|
if (self.submit_index + SUBMIT_LOST < ledger_index) {
|
||||||
self.set_state('client_lost'); // Gave up.
|
self.set_state('client_lost'); // Gave up.
|
||||||
self.emit('lost');
|
self.emit('lost');
|
||||||
|
|
||||||
|
if (self.callback)
|
||||||
|
self.callback('tejLost', message);
|
||||||
|
|
||||||
stop = true;
|
stop = true;
|
||||||
}
|
}
|
||||||
else if (self.submit_index + SUBMIT_MISSING < ledger_index) {
|
else if (self.submit_index + SUBMIT_MISSING < ledger_index) {
|
||||||
@@ -1170,12 +1192,18 @@ Transaction.prototype.submit = function () {
|
|||||||
.request();
|
.request();
|
||||||
|
|
||||||
if (stop) {
|
if (stop) {
|
||||||
self.removeListener('ledger_closed', on_ledger_closed);
|
self.remote.removeListener('ledger_closed', on_ledger_closed);
|
||||||
self.emit('final', message);
|
self.emit('final', message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.remote.on('ledger_closed', on_ledger_closed);
|
this.remote.on('ledger_closed', on_ledger_closed);
|
||||||
|
|
||||||
|
if (this.callback) {
|
||||||
|
this.on('error', function (message) {
|
||||||
|
self.callback(message.error, message);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set_state('client_submitted');
|
this.set_state('client_submitted');
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
var path = require("path");
|
var path = require("path");
|
||||||
|
|
||||||
// Where to find the binary.
|
// Where to find the binary.
|
||||||
//exports.rippled = path.join(process.cwd(), "newcoin_master");
|
exports.rippled = path.resolve("build/rippled");
|
||||||
exports.rippled = path.join(process.cwd(), "Debug/newcoin");
|
|
||||||
|
|
||||||
exports.server_default = "alpha";
|
exports.server_default = "alpha";
|
||||||
|
|
||||||
@@ -15,13 +14,12 @@ exports.servers = {
|
|||||||
// A local test server.
|
// A local test server.
|
||||||
"alpha" : {
|
"alpha" : {
|
||||||
'trusted' : true,
|
'trusted' : true,
|
||||||
'no_server' : true,
|
|
||||||
// "peer_ip" : "0.0.0.0",
|
// "peer_ip" : "0.0.0.0",
|
||||||
// "peer_port" : 51235,
|
// "peer_port" : 51235,
|
||||||
'rpc_ip' : "0.0.0.0",
|
'rpc_ip' : "0.0.0.0",
|
||||||
'rpc_port' : 5005,
|
'rpc_port' : 5005,
|
||||||
'websocket_ip' : "127.0.0.1",
|
'websocket_ip' : "127.0.0.1",
|
||||||
'websocket_port' : 5006,
|
'websocket_port' : 6005,
|
||||||
// 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h",
|
// 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h",
|
||||||
// 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta"
|
// 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta"
|
||||||
}
|
}
|
||||||
@@ -534,5 +534,86 @@ buster.testCase("Offer tests", {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"//ripple cross currency payment" :
|
||||||
|
// alice --> [XRP --> carol --> USD/mtgox] --> bob
|
||||||
|
|
||||||
|
function (done) {
|
||||||
|
var self = this;
|
||||||
|
var seq;
|
||||||
|
|
||||||
|
self.remote.set_trace();
|
||||||
|
|
||||||
|
async.waterfall([
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Create accounts.";
|
||||||
|
|
||||||
|
testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "carol", "mtgox"], callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Set limits.";
|
||||||
|
|
||||||
|
testutils.credit_limits(self.remote,
|
||||||
|
{
|
||||||
|
"carol" : "1000/USD/mtgox",
|
||||||
|
"bob" : "2000/USD/mtgox"
|
||||||
|
},
|
||||||
|
callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Distribute funds.";
|
||||||
|
|
||||||
|
testutils.payments(self.remote,
|
||||||
|
{
|
||||||
|
"mtgox" : "500/USD/carol"
|
||||||
|
},
|
||||||
|
callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Create offer.";
|
||||||
|
|
||||||
|
self.remote.transaction()
|
||||||
|
.offer_create("carol", "500", "50/USD/mtgox")
|
||||||
|
.on('proposed', function (m) {
|
||||||
|
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
|
||||||
|
callback(m.result !== 'tesSUCCESS');
|
||||||
|
|
||||||
|
seq = m.tx_json.Sequence;
|
||||||
|
})
|
||||||
|
.submit();
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Alice send USD/mtgox converting from XRP.";
|
||||||
|
|
||||||
|
self.remote.transaction()
|
||||||
|
.payment("alice", "bob", "25/USD/mtgox")
|
||||||
|
.send_max("333")
|
||||||
|
.on('proposed', function (m) {
|
||||||
|
console.log("proposed: %s", JSON.stringify(m));
|
||||||
|
|
||||||
|
callback(m.result !== 'tesSUCCESS');
|
||||||
|
})
|
||||||
|
.submit();
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Verify balances.";
|
||||||
|
|
||||||
|
testutils.verify_balances(self.remote,
|
||||||
|
{
|
||||||
|
"alice" : [ "0/USD/mtgox", "500" ],
|
||||||
|
"bob" : "100/USD/mtgox",
|
||||||
|
},
|
||||||
|
callback);
|
||||||
|
},
|
||||||
|
function (callback) {
|
||||||
|
self.what = "Verify offer consumed.";
|
||||||
|
|
||||||
|
testutils.verify_offer_not_found(self.remote, "bob", seq, callback);
|
||||||
|
},
|
||||||
|
], function (error) {
|
||||||
|
buster.refute(error, self.what);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
// vim:sw=2:sts=2:ts=8
|
// vim:sw=2:sts=2:ts=8
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Server.prototype.once = function (e, c) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Server.prototype.serverPath = function() {
|
Server.prototype.serverPath = function() {
|
||||||
return "tmp/server/" + this.name;
|
return path.resolve("tmp/server", this.name);
|
||||||
};
|
};
|
||||||
|
|
||||||
Server.prototype.configPath = function() {
|
Server.prototype.configPath = function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user