diff --git a/src/cpp/ripple/AccountSetTransactor.cpp b/src/cpp/ripple/AccountSetTransactor.cpp index 7834dd1bd9..9ffa635e50 100644 --- a/src/cpp/ripple/AccountSetTransactor.cpp +++ b/src/cpp/ripple/AccountSetTransactor.cpp @@ -19,6 +19,38 @@ TER AccountSetTransactor::doApply() return temINVALID_FLAG; } + // + // RequireAuth + // + + if ((tfRequireAuth|tfOptionalAuth) == (uTxFlags & (tfRequireAuth|tfOptionalAuth))) + { + cLog(lsINFO) << "AccountSet: Malformed transaction: Contradictory flags set."; + + return temINVALID_FLAG; + } + + if ((uTxFlags & tfRequireAuth) && !isSetBit(uFlagsIn, lsfRequireAuth)) + { + if (mTxn.getFieldU32(sfOwnerCount)) + { + cLog(lsINFO) << "AccountSet: Retry: OwnerCount not zero."; + + return terOWNERS; + } + + cLog(lsINFO) << "AccountSet: Set RequireAuth."; + + uFlagsOut |= lsfRequireAuth; + } + + if (uTxFlags & tfOptionalAuth) + { + cLog(lsINFO) << "AccountSet: Clear RequireAuth."; + + uFlagsOut &= ~lsfRequireAuth; + } + // // RequireDestTag // diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 4bd70f53b2..99c00018f0 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -1147,15 +1147,16 @@ STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint1 } TER LedgerEntrySet::trustCreate( - const bool bSrcHigh, // Who to charge with reserve. + const bool bSrcHigh, const uint160& uSrcAccountID, - SLE::ref sleSrcAccount, const uint160& uDstAccountID, - const uint256& uIndex, - const STAmount& saSrcBalance, // Issuer should be ACCOUNT_ONE - const STAmount& saSrcLimit, - const uint32 uSrcQualityIn, - const uint32 uSrcQualityOut) + const uint256& uIndex, // --> ripple state entry + SLE::ref sleAccount, // --> the account being set. + const bool bAuth, // --> authorize account. + const STAmount& saBalance, // --> balance of account being set. Issuer should be ACCOUNT_ONE + const STAmount& saLimit, // --> limit for account being set. Issuer should be the account being set. + const uint32 uQualityIn, + const uint32 uQualityOut) { const uint160& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID; const uint160& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID; @@ -1182,23 +1183,33 @@ TER LedgerEntrySet::trustCreate( if (tesSUCCESS == terResult) { - sleRippleState->setFieldU64(sfLowNode, uLowNode); + const bool bSetDst = saLimit.getIssuer() == uDstAccountID; + const bool bSetHigh = bSrcHigh ^ bSetDst; + + sleRippleState->setFieldU64(sfLowNode, uLowNode); // Remember deletion hints. sleRippleState->setFieldU64(sfHighNode, uHighNode); - sleRippleState->setFieldAmount(!bSrcHigh ? sfLowLimit : sfHighLimit, saSrcLimit); - sleRippleState->setFieldAmount( bSrcHigh ? sfLowLimit : sfHighLimit, STAmount(saSrcBalance.getCurrency(), uDstAccountID)); + sleRippleState->setFieldAmount(!bSetHigh ? sfLowLimit : sfHighLimit, saLimit); + sleRippleState->setFieldAmount( bSetHigh ? sfLowLimit : sfHighLimit, STAmount(saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID)); - if (uSrcQualityIn) - sleRippleState->setFieldU32(bSrcHigh ? sfHighQualityIn : sfLowQualityIn, uSrcQualityIn); + if (uQualityIn) + sleRippleState->setFieldU32(!bSetHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - if (uSrcQualityOut) - sleRippleState->setFieldU32(bSrcHigh ? sfHighQualityOut : sfLowQualityOut, uSrcQualityIn); + if (uQualityOut) + sleRippleState->setFieldU32(!bSetHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - sleRippleState->setFieldU32(sfFlags, !bSrcHigh ? lsfLowReserve : lsfHighReserve); + uint32 uFlags = !bSetHigh ? lsfLowReserve : lsfHighReserve; - ownerCountAdjust(uSrcAccountID, 1, sleSrcAccount); + if (bAuth) + { + uFlags |= (!bSetHigh ? lsfLowAuth : lsfHighAuth); + } - sleRippleState->setFieldAmount(sfBalance, bSrcHigh ? -saSrcBalance: saSrcBalance); + sleRippleState->setFieldU32(sfFlags, uFlags); + + ownerCountAdjust(!bSetDst ? uSrcAccountID : uDstAccountID, 1, sleAccount); + + sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); } return terResult; @@ -1223,25 +1234,25 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei if (!sleRippleState) { - STAmount saSrcLimit = STAmount(uCurrencyID, uSenderID); - STAmount saBalance = saAmount; + STAmount saReceiverLimit = STAmount(uCurrencyID, uReceiverID); + STAmount saBalance = saAmount; saBalance.setIssuer(ACCOUNT_ONE); - cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s (%s) -> %s : %s") + cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s (0) -> %s : %s") % RippleAddress::createHumanAccountID(uSenderID) - % saBalance.getFullText() % RippleAddress::createHumanAccountID(uReceiverID) % saAmount.getFullText()); terResult = trustCreate( bSenderHigh, uSenderID, - entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)), uReceiverID, uIndex, + entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)), + false, saBalance, - saSrcLimit); + saReceiverLimit); } else { diff --git a/src/cpp/ripple/LedgerEntrySet.h b/src/cpp/ripple/LedgerEntrySet.h index 159ef43747..197a552084 100644 --- a/src/cpp/ripple/LedgerEntrySet.h +++ b/src/cpp/ripple/LedgerEntrySet.h @@ -132,9 +132,10 @@ public: TER trustCreate( const bool bSrcHigh, const uint160& uSrcAccountID, - SLE::ref sleSrcAccount, const uint160& uDstAccountID, const uint256& uIndex, + SLE::ref sleAccount, + const bool bAuth, const STAmount& saSrcBalance, const STAmount& saSrcLimit, const uint32 uSrcQualityIn = 0, diff --git a/src/cpp/ripple/LedgerFormats.h b/src/cpp/ripple/LedgerFormats.h index 7e575650ff..a491a58e3c 100644 --- a/src/cpp/ripple/LedgerFormats.h +++ b/src/cpp/ripple/LedgerFormats.h @@ -41,6 +41,7 @@ enum LedgerSpecificFlags // ltACCOUNT_ROOT lsfPasswordSpent = 0x00010000, // True, if password set fee is spent. lsfRequireDestTag = 0x00020000, // True, to require a DestinationTag for payments. + lsfRequireAuth = 0x00040000, // True, to require a authorization to hold IOUs. // ltOFFER lsfPassive = 0x00010000, @@ -48,6 +49,8 @@ enum LedgerSpecificFlags // ltRIPPLE_STATE lsfLowReserve = 0x00010000, // True, if entry counts toward reserve. lsfHighReserve = 0x00020000, + lsfLowAuth = 0x00040000, + lsfHighAuth = 0x00080000, }; class LedgerEntryFormat diff --git a/src/cpp/ripple/TransactionErr.cpp b/src/cpp/ripple/TransactionErr.cpp index 953a8aeb29..3ffe6d639a 100644 --- a/src/cpp/ripple/TransactionErr.cpp +++ b/src/cpp/ripple/TransactionErr.cpp @@ -35,6 +35,7 @@ bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) { tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." }, { tefCREATED, "tefCREATED", "Can't add an already created account." }, { tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." }, + { tefNO_AUTH_REQUIRED, "tefNO_AUTH_REQUIRED", "Auth is not required." }, { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past." }, { telLOCAL_ERROR, "telLOCAL_ERROR", "Local failure." }, @@ -81,6 +82,7 @@ bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, { terNO_LINE, "terNO_LINE", "No such line." }, { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." }, + { terOWNERS, "terOWNERS", "Non-zero owner count." }, { tesSUCCESS, "tesSUCCESS", "The transaction was applied." }, }; diff --git a/src/cpp/ripple/TransactionErr.h b/src/cpp/ripple/TransactionErr.h index b5bfeea5c2..97f33f0918 100644 --- a/src/cpp/ripple/TransactionErr.h +++ b/src/cpp/ripple/TransactionErr.h @@ -78,6 +78,7 @@ enum TER // aka TransactionEngineResult tefCREATED, tefEXCEPTION, tefGEN_IN_USE, + tefNO_AUTH_REQUIRED, // Can't set auth if auth is not required. tefPAST_SEQ, // -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent) @@ -94,6 +95,7 @@ enum TER // aka TransactionEngineResult terINSUF_FEE_B, // Can't pay fee, therefore don't burden network. terNO_ACCOUNT, // Can't pay fee, therefore don't burden network. terNO_LINE, // Internal flag. + terOWNERS, // Can't succeed with non-zero owner count. terPRE_SEQ, // Can't pay fee, no point in forwarding, therefore don't burden network. // 0: S Success (success) diff --git a/src/cpp/ripple/TransactionFormats.h b/src/cpp/ripple/TransactionFormats.h index f761c5c70a..ceb5c9b7d8 100644 --- a/src/cpp/ripple/TransactionFormats.h +++ b/src/cpp/ripple/TransactionFormats.h @@ -62,7 +62,9 @@ const int TransactionMaxLen = 1048576; // AccountSet flags: const uint32 tfRequireDestTag = 0x00010000; const uint32 tfOptionalDestTag = 0x00020000; -const uint32 tfAccountSetMask = ~(tfRequireDestTag|tfOptionalDestTag); +const uint32 tfRequireAuth = 0x00040000; +const uint32 tfOptionalAuth = 0x00080000; +const uint32 tfAccountSetMask = ~(tfRequireDestTag|tfOptionalDestTag|tfRequireAuth|tfOptionalAuth); // OfferCreate flags: const uint32 tfPassive = 0x00010000; @@ -72,8 +74,11 @@ const uint32 tfOfferCreateMask = ~(tfPassive); const uint32 tfNoRippleDirect = 0x00010000; const uint32 tfPartialPayment = 0x00020000; const uint32 tfLimitQuality = 0x00040000; - const uint32 tfPaymentMask = ~(tfPartialPayment|tfLimitQuality|tfNoRippleDirect); +// TrustSet flags: +const uint32 tfSetfAuth = 0x00010000; +const uint32 tfTrustSetMask = ~(tfSetfAuth); + #endif // vim:ts=4 diff --git a/src/cpp/ripple/TrustSetTransactor.cpp b/src/cpp/ripple/TrustSetTransactor.cpp index ca852b82f3..4a66bcd78f 100644 --- a/src/cpp/ripple/TrustSetTransactor.cpp +++ b/src/cpp/ripple/TrustSetTransactor.cpp @@ -27,14 +27,21 @@ TER TrustSetTransactor::doApply() const uint32 uTxFlags = mTxn.getFlags(); - if (uTxFlags) + if (uTxFlags & tfTrustSetMask) { cLog(lsINFO) << "doTrustSet: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } - // Check if destination makes sense. + const bool bSetAuth = isSetBit(uTxFlags, tfSetfAuth); + + if (bSetAuth && !isSetBit(mTxnAccount->getFieldU32(sfFlags), lsfRequireAuth)) + { + cLog(lsINFO) << "doTrustSet: Retry: Auth not required."; + + return tefNO_AUTH_REQUIRED; + } if (saLimitAmount.isNegative()) { @@ -42,13 +49,16 @@ TER TrustSetTransactor::doApply() return temBAD_LIMIT; } - else if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) + + // Check if destination makes sense. + if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) { cLog(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; return temDST_NEEDED; } - else if (mTxnAccountID == uDstAccountID) + + if (mTxnAccountID == uDstAccountID) { cLog(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; @@ -185,6 +195,11 @@ TER TrustSetTransactor::doApply() bool bReserveIncrease = false; + if (bSetAuth) + { + uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); + } + if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. @@ -287,13 +302,14 @@ TER TrustSetTransactor::doApply() // Create a new ripple line. terResult = mEngine->getNodes().trustCreate( - bHigh, // Who to charge with reserve. + bHigh, mTxnAccountID, - mTxnAccount, uDstAccountID, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID), + mTxnAccount, + bSetAuth, saBalance, - saLimitAllow, + saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut); } diff --git a/test/offer-test.js b/test/offer-test.js index ebfd1721a8..9e4fd417bb 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -73,7 +73,7 @@ buster.testCase("Offer tests", { }); }, - "offer create then crossing offer, no trust lines" : + "offer create then crossing offer, no trust lines with self" : function (done) { var self = this; @@ -109,6 +109,261 @@ buster.testCase("Offer tests", { }); }, + // rippled broken: Balances are wrong. + "Offer create then crossing offer with XRP. Reverse order." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "100000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("bob", "1/USD/mtgox", "4000.0") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("alice", "150000.0", "50/USD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "499/USD/mtgox", String(100000000000+4000000000-2*(Remote.fees['default'].to_number())) ], + "bob" : [ "1/USD/mtgox", String(100000000000-4000000000-2*(Remote.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + + "Offer create then crossing offer with XRP." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "100000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("alice", "150000.0", "50/USD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("bob", "1/USD/mtgox", "4000.0") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "499/USD/mtgox", String(100000000000+4000000000-2*(Remote.fees['default'].to_number())) ], + "bob" : [ "1/USD/mtgox", String(100000000000-4000000000-2*(Remote.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + + "Offer create then crossing offer with XRP with limit override." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "100000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", +// "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("alice", "150000.0", "50/USD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Display ledger"; + + self.remote.request_ledger('current', true) + .on('success', function (m) { + console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); + + callback(); + }) + .request(); + }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("bob", "1/USD/mtgox", "4000.0") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "499/USD/mtgox", String(100000000000+4000000000-2*(Remote.fees['default'].to_number())) ], + "bob" : [ "1/USD/mtgox", String(100000000000-4000000000-1*(Remote.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + "offer_create then ledger_accept then offer_cancel then ledger_accept." : function (done) { var self = this;