From 96733c287476b7279289e8884a357a1c827a7bf7 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 31 Mar 2013 16:15:45 -0700 Subject: [PATCH] Add trust auto clear. Fixes #28 --- src/cpp/ripple/LedgerEntrySet.cpp | 79 ++++++++++++++-- src/cpp/ripple/LedgerEntrySet.h | 1 + src/cpp/ripple/TrustSetTransactor.cpp | 17 +--- test/path-test.js | 128 ++++++++++++++++++++++++++ test/testutils.js | 2 +- 5 files changed, 200 insertions(+), 27 deletions(-) diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index d96a820fda..5f58c25932 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -150,8 +150,9 @@ void LedgerEntrySet::entryCreate(SLE::ref sle) case taaMODIFY: throw std::runtime_error("Create after modify"); + case taaCREATE: - throw std::runtime_error("Create after create"); // We could make this work + throw std::runtime_error("Create after create"); // This could be made to work case taaCACHED: throw std::runtime_error("Create after cache"); @@ -532,7 +533,7 @@ TER LedgerEntrySet::dirCount(const uint256& uRootIndex, uint32& uCount) // <-- uNodeDir: For deletion, present to make dirDelete efficient. // --> uRootIndex: The index of the base of the directory. Nodes are based off of this. // --> uLedgerIndex: Value to add to directory. -// We only append. This allow for things that watch append only structure to just monitor from the last node on ward. +// Only append. This allow for things that watch append only structure to just monitor from the last node on ward. // Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. TER LedgerEntrySet::dirAdd( uint64& uNodeDir, @@ -850,7 +851,7 @@ bool LedgerEntrySet::dirFirst( sleNode = entryCache(ltDIR_NODE, uRootIndex); uDirEntry = 0; - assert(sleNode); // We never probe for directories. + assert(sleNode); // Never probe for directories. return LedgerEntrySet::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); } @@ -1243,13 +1244,38 @@ TER LedgerEntrySet::trustCreate( ownerCountAdjust(!bSetDst ? uSrcAccountID : uDstAccountID, 1, sleAccount); - sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); + sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); // ONLY: Create ripple balance. } return terResult; } -// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. +TER LedgerEntrySet::trustDelete(SLE::ref sleRippleState, const uint160& uLowAccountID, const uint160& uHighAccountID) +{ + bool bLowNode = sleRippleState->isFieldPresent(sfLowNode); // Detect legacy dirs. + bool bHighNode = sleRippleState->isFieldPresent(sfHighNode); + uint64 uLowNode = sleRippleState->getFieldU64(sfLowNode); + uint64 uHighNode = sleRippleState->getFieldU64(sfHighNode); + TER terResult; + + cLog(lsTRACE) << "trustDelete: Deleting ripple line: low"; + terResult = dirDelete(false, uLowNode, Ledger::getOwnerDirIndex(uLowAccountID), sleRippleState->getIndex(), false, !bLowNode); + + if (tesSUCCESS == terResult) + { + cLog(lsTRACE) << "trustDelete: Deleting ripple line: high"; + terResult = dirDelete(false, uHighNode, Ledger::getOwnerDirIndex(uHighAccountID), sleRippleState->getIndex(), false, !bHighNode); + } + + cLog(lsINFO) << "trustDelete: Deleting ripple line: state"; + entryDelete(sleRippleState); + + return terResult; +} + +// Direct send w/o fees: +// - Redeeming IOUs and/or sending sender's own IOUs. +// - Create trust line of needed. TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) { uint160 uIssuerID = saAmount.getIssuer(); @@ -1306,14 +1332,46 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei % saAmount.getFullText() % saBalance.getFullText()); + bool bDelete = false; + uint32 uFlags; + + // YYY Could skip this if rippling in reverse. + if (saBefore.isPositive() // Sender balance was positive. + && !saBalance.isPositive() // Sender is zero or negative. + && isSetBit((uFlags = sleRippleState->getFieldU32(sfFlags)), !bSenderHigh ? lsfLowReserve : lsfHighReserve) // Sender reserve is set. + && !sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit) // Sender trust limit is 0. + && !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) // Sender quality in is 0. + && !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut)) // Sender quality out is 0. + { + // Clear the reserve of the sender, possibly delete the line! + SLE::pointer sleSender = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)); + + ownerCountAdjust(uSenderID, -1, sleSender); + + sleRippleState->setFieldU32(sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve)); // Clear reserve flag. + + bDelete = !saBalance // Balance is zero. + && !isSetBit(uFlags, bSenderHigh ? lsfLowReserve : lsfHighReserve); // Receiver reserve is clear. + } + if (bSenderHigh) saBalance.negate(); - sleRippleState->setFieldAmount(sfBalance, saBalance); + // Want to reflect balance to zero even if we are deleting line. + sleRippleState->setFieldAmount(sfBalance, saBalance); // ONLY: Adjust ripple balance. - entryModify(sleRippleState); + if (bDelete) + { + terResult = trustDelete(sleRippleState, + bSenderHigh ? uReceiverID : uSenderID, + !bSenderHigh ? uReceiverID : uSenderID); + } + else + { + entryModify(sleRippleState); - terResult = tesSUCCESS; + terResult = tesSUCCESS; + } } return terResult; @@ -1374,6 +1432,7 @@ TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiv } else if (saAmount.isNative()) { + // XRP send which does not check reserve and can do pure adjustment. SLE::pointer sleSender = !!uSenderID ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) : SLE::pointer(); @@ -1398,14 +1457,14 @@ TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiv } else { - sleSender->setFieldAmount(sfBalance, sleSender->getFieldAmount(sfBalance) - saAmount); + sleSender->setFieldAmount(sfBalance, sleSender->getFieldAmount(sfBalance) - saAmount); // Decrement XRP balance. entryModify(sleSender); } } if (tesSUCCESS == terResult && sleReceiver) { - sleReceiver->setFieldAmount(sfBalance, sleReceiver->getFieldAmount(sfBalance) + saAmount); + sleReceiver->setFieldAmount(sfBalance, sleReceiver->getFieldAmount(sfBalance) + saAmount); // Increment XRP balance. entryModify(sleReceiver); } diff --git a/src/cpp/ripple/LedgerEntrySet.h b/src/cpp/ripple/LedgerEntrySet.h index 09f9bb0cee..44e61b53f4 100644 --- a/src/cpp/ripple/LedgerEntrySet.h +++ b/src/cpp/ripple/LedgerEntrySet.h @@ -156,6 +156,7 @@ public: const STAmount& saSrcLimit, const uint32 uSrcQualityIn = 0, const uint32 uSrcQualityOut = 0); + TER trustDelete(SLE::ref sleRippleState, const uint160& uLowAccountID, const uint160& uHighAccountID); Json::Value getJson(int) const; void calcRawMeta(Serializer&, TER result, uint32 index); diff --git a/src/cpp/ripple/TrustSetTransactor.cpp b/src/cpp/ripple/TrustSetTransactor.cpp index 9333d51259..00925ea6ee 100644 --- a/src/cpp/ripple/TrustSetTransactor.cpp +++ b/src/cpp/ripple/TrustSetTransactor.cpp @@ -252,22 +252,7 @@ TER TrustSetTransactor::doApply() { // Can delete. - bool bLowNode = sleRippleState->isFieldPresent(sfLowNode); // Detect legacy dirs. - bool bHighNode = sleRippleState->isFieldPresent(sfHighNode); - uint64 uLowNode = sleRippleState->getFieldU64(sfLowNode); - uint64 uHighNode = sleRippleState->getFieldU64(sfHighNode); - - cLog(lsTRACE) << "doTrustSet: Deleting ripple line: low"; - terResult = mEngine->getNodes().dirDelete(false, uLowNode, Ledger::getOwnerDirIndex(uLowAccountID), sleRippleState->getIndex(), false, !bLowNode); - - if (tesSUCCESS == terResult) - { - cLog(lsTRACE) << "doTrustSet: Deleting ripple line: high"; - terResult = mEngine->getNodes().dirDelete(false, uHighNode, Ledger::getOwnerDirIndex(uHighAccountID), sleRippleState->getIndex(), false, !bHighNode); - } - - cLog(lsINFO) << "doTrustSet: Deleting ripple line: state"; - mEngine->entryDelete(sleRippleState); + terResult = mEngine->getNodes().trustDelete(sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue() < uReserveCreate) // Reserve is not scaled by load. diff --git a/test/path-test.js b/test/path-test.js index ee75afbd67..00587a2c1f 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -1274,4 +1274,132 @@ buster.testCase("Quality paths", { }); }, }); + +buster.testCase("Trust auto clear", { + 'setUp' : testutils.build_setup(), + // 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + 'tearDown' : testutils.build_teardown(), + + "trust normal clear" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/bob", + "bob" : "1000/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Verify credit limits."; + + testutils.verify_limit(self.remote, "bob", "1000/USD/alice", callback); + }, + function (callback) { + self.what = "Clear credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "0/USD/bob", + "bob" : "0/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Verify credit limits."; + + testutils.verify_limit(self.remote, "bob", "0/USD/alice", function (m) { + var success = m && 'remoteError' === m.error && 'entryNotFound' === m.remote.error; + + callback(!success); + }); + }, + // YYY Could verify owner counts are zero. + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "trust auto clear" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/bob", + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bob" : [ "50/USD/alice" ], + }, + callback); + }, + function (callback) { + self.what = "Clear credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "0/USD/bob", + }, + callback); + }, + function (callback) { + self.what = "Verify credit limits."; + + testutils.verify_limit(self.remote, "alice", "0/USD/bob", callback); + }, + function (callback) { + self.what = "Return funds."; + + testutils.payments(self.remote, + { + "alice" : [ "50/USD/bob" ], + }, + callback); + }, + function (callback) { + self.what = "Verify credit limit gone."; + + testutils.verify_limit(self.remote, "bob", "0/USD/alice", function (m) { + var success = m && 'remoteError' === m.error && 'entryNotFound' === m.remote.error; + + callback(!success); + }); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); // vim:sw=2:sts=2:ts=8:et diff --git a/test/testutils.js b/test/testutils.js index 39495f213a..0799ee97bc 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -226,7 +226,7 @@ var verify_limit = function (remote, src, amount, callback) { callback(); }) - .on('error', function (m) { + .once('error', function (m) { // console.log("error: %s", JSON.stringify(m)); callback(m);