diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index c96640ebd..8a048f931 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -206,6 +206,8 @@ void Application::setup() if (!theConfig.RUN_STANDALONE) updateTables(theConfig.LDB_IMPORT); + mFeatureTable.addInitialFeatures(); + if (theConfig.START_UP == Config::FRESH) { WriteLog (lsINFO, Application) << "Starting new Ledger"; @@ -437,6 +439,8 @@ void Application::startNewLedger() { Ledger::pointer firstLedger = boost::make_shared(rootAddress, SYSTEM_CURRENCY_START); assert(!!firstLedger->getAccountState(rootAddress)); + // WRITEME: Add any default features + // WRITEME: Set default fee/reserve firstLedger->updateHash(); firstLedger->setClosed(); firstLedger->setAccepted(); @@ -548,6 +552,12 @@ bool serverOkay(std::string& reason) return false; } + if (theApp->getOPs().isFeatureBlocked()) + { + reason = "Server version too old"; + return false; + } + return true; } diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index c03cb7a56..293bd8230 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -310,6 +310,20 @@ Json::Value RPCParser::parseEvented(const Json::Value& jvParams) return rpcError(rpcNO_EVENTS); } +// feature [] [true|false] +Json::Value RPCParser::parseFeature(const Json::Value& jvParams) +{ + Json::Value jvRequest(Json::objectValue); + + if (jvParams.size() > 0) + jvRequest["feature"] = jvParams[0u].asString(); + + if (jvParams.size() > 1) + jvRequest["vote"] = boost::lexical_cast(jvParams[1u].asString()); + + return jvRequest; +} + // get_counts [] Json::Value RPCParser::parseGetCounts(const Json::Value& jvParams) { @@ -733,6 +747,7 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) { "book_offers", &RPCParser::parseBookOffers, 2, 7 }, { "connect", &RPCParser::parseConnect, 1, 2 }, { "consensus_info", &RPCParser::parseAsIs, 0, 0 }, + { "feature", &RPCParser::parseFeature, 0, 2 }, { "get_counts", &RPCParser::parseGetCounts, 0, 1 }, { "json", &RPCParser::parseJson, 2, 2 }, { "ledger", &RPCParser::parseLedger, 0, 2 }, diff --git a/src/cpp/ripple/CallRPC.h b/src/cpp/ripple/CallRPC.h index 3ce69167a..0ce9a8630 100644 --- a/src/cpp/ripple/CallRPC.h +++ b/src/cpp/ripple/CallRPC.h @@ -22,6 +22,7 @@ protected: Json::Value parseDataStore(const Json::Value& jvParams); #endif Json::Value parseEvented(const Json::Value& jvParams); + Json::Value parseFeature(const Json::Value& jvParams); Json::Value parseGetCounts(const Json::Value& jvParams); Json::Value parseInternal(const Json::Value& jvParams); Json::Value parseJson(const Json::Value& jvParams); diff --git a/src/cpp/ripple/ChangeTransactor.cpp b/src/cpp/ripple/ChangeTransactor.cpp index fa9723732..790b82368 100644 --- a/src/cpp/ripple/ChangeTransactor.cpp +++ b/src/cpp/ripple/ChangeTransactor.cpp @@ -85,6 +85,10 @@ TER ChangeTransactor::applyFeature() featureObject->setFieldV256(sfFeatures, features); mEngine->entryModify(featureObject); + theApp->getFeatureTable().enableFeature(feature); + if (!theApp->getFeatureTable().isFeatureSupported(feature)) + theApp->getOPs().setFeatureBlocked(); + return tesSUCCESS; } diff --git a/src/cpp/ripple/FeatureTable.cpp b/src/cpp/ripple/FeatureTable.cpp index d4a725f64..9286e4e2e 100644 --- a/src/cpp/ripple/FeatureTable.cpp +++ b/src/cpp/ripple/FeatureTable.cpp @@ -1,13 +1,17 @@ SETUP_LOG (FeatureTable) +FeatureState* testFeature = NULL; + void FeatureTable::addInitialFeatures() { - // For each feature this version supports, call enableFeature. - // Permanent vetos can also be added here. + // For each feature this version supports, construct the FeatureState object by calling + // getCreateFeature. Set any vetoes or defaults. A pointer to the FeatureState can be stashed + + testFeature = addKnownFeature("1234", "testFeature", false); } -FeatureTable::FeatureState* FeatureTable::getCreateFeature(const uint256& featureHash, bool create) +FeatureState* FeatureTable::getCreateFeature(const uint256& featureHash, bool create) { // call with the mutex held featureMap_t::iterator it = mFeatureMap.find(featureHash); if (it == mFeatureMap.end()) @@ -36,6 +40,39 @@ FeatureTable::FeatureState* FeatureTable::getCreateFeature(const uint256& featur return &(it->second); } +uint256 FeatureTable::getFeature(const std::string& name) +{ + if (!name.empty()) + { + BOOST_FOREACH(featureMap_t::value_type& it, mFeatureMap) + { + if (name == it.second.mFriendlyName) + return it.first; + } + } + return uint256(); +} + +FeatureState* FeatureTable::addKnownFeature(const char *featureID, const char *friendlyName, bool veto) +{ + uint256 hash; + hash.SetHex(featureID); + if (hash.isZero()) + { + assert(false); + return NULL; + } + FeatureState* f = getCreateFeature(hash, true); + + if (friendlyName != NULL) + f->setFriendlyName(friendlyName); + + f->mVetoed = veto; + f->mSupported = true; + + return f; +} + bool FeatureTable::vetoFeature(const uint256& feature) { boost::mutex::scoped_lock sl(mMutex); @@ -83,6 +120,13 @@ bool FeatureTable::isFeatureEnabled(const uint256& feature) return s && s->mEnabled; } +bool FeatureTable::isFeatureSupported(const uint256& feature) +{ + boost::mutex::scoped_lock sl(mMutex); + FeatureState *s = getCreateFeature(feature, false); + return s && s->mSupported; +} + FeatureTable::featureList_t FeatureTable::getVetoedFeatures() { featureList_t ret; @@ -193,7 +237,22 @@ void FeatureTable::reportValidations(const FeatureSet& set) if (!changedFeatures.empty()) { - // WRITEME write changed features to SQL db + ScopedLock sl(theApp->getWalletDB()->getDBLock()); + Database* db = theApp->getWalletDB()->getDB(); + + db->executeSQL("BEGIN TRANSACTION;"); + BOOST_FOREACH(const uint256& hash, changedFeatures) + { + FeatureState& fState = mFeatureMap[hash]; + db->executeSQL(boost::str(boost::format( + "UPDATE Features SET FirstMajority = %d WHERE Hash = '%s';" + ) % fState.mFirstMajority % hash.GetHex())); + db->executeSQL(boost::str(boost::format( + "UPDATE Features SET LastMajority = %d WHERE Hash = '%s';" + ) % fState.mLastMajority % hash.GetHex())); + } + db->executeSQL("END TRANSACTION;"); + changedFeatures.clear(); } } @@ -246,12 +305,12 @@ void FeatureTable::doVoting(Ledger::ref lastClosedLedger, SHAMap::ref initialPos BOOST_FOREACH(const uint256& uFeature, lFeatures) { - WriteLog (lsWARNING, FeatureTable) << "We are voting for feature " << uFeature; + WriteLog (lsWARNING, FeatureTable) << "Voting for feature: " << uFeature; SerializedTransaction trans(ttFEATURE); trans.setFieldAccount(sfAccount, uint160()); trans.setFieldH256(sfFeature, uFeature); uint256 txID = trans.getTransactionID(); - WriteLog (lsWARNING, FeatureTable) << "Vote: " << txID; + WriteLog (lsWARNING, FeatureTable) << "Vote ID: " << txID; Serializer s; trans.add(s, true); @@ -271,46 +330,60 @@ Json::Value FeatureTable::getJson(int) boost::mutex::scoped_lock sl(mMutex); BOOST_FOREACH(const featureIt_t& it, mFeatureMap) { - Json::Value v(Json::objectValue); + setJson(ret[it.first.GetHex()] = Json::objectValue, it.second); + } + } + return ret; +} - v["supported"] = it.second.mSupported; +void FeatureTable::setJson(Json::Value& v, const FeatureState& fs) +{ + if (!fs.mFriendlyName.empty()) + v["name"] = fs.mFriendlyName; - if (it.second.mEnabled) - v["enabled"] = true; + v["supported"] = fs.mSupported; + v["vetoed"] = fs.mVetoed; + + if (fs.mEnabled) + v["enabled"] = true; + else + { + v["enabled"] = false; + if (mLastReport != 0) + { + if (fs.mLastMajority == 0) + { + v["majority"] = false; + } else { - v["enabled"] = false; - if (mLastReport != 0) + if (fs.mFirstMajority != 0) { - if (it.second.mLastMajority == 0) - v["majority"] = false; + if (fs.mFirstMajority == mFirstReport) + v["majority_start"] = "start"; else - { - if (it.second.mFirstMajority != 0) - { - if (it.second.mFirstMajority == mFirstReport) - v["majority_start"] = "start"; - else - v["majority_start"] = it.second.mFirstMajority; - } - if (it.second.mLastMajority != 0) - { - if (it.second.mLastMajority == mLastReport) - v["majority_until"] = "now"; - else - v["majority_until"] = it.second.mLastMajority; - } - } + v["majority_start"] = fs.mFirstMajority; + } + if (fs.mLastMajority != 0) + { + if (fs.mLastMajority == mLastReport) + v["majority_until"] = "now"; + else + v["majority_until"] = fs.mLastMajority; } } - - if (it.second.mVetoed) - v["veto"] = true; - - ret[it.first.GetHex()] = v; } } + if (fs.mVetoed) + v["veto"] = true; +} + +Json::Value FeatureTable::getJson(const uint256& feature) +{ + Json::Value ret = Json::objectValue; + boost::mutex::scoped_lock sl(mMutex); + setJson(ret[feature.GetHex()] = Json::objectValue, *getCreateFeature(feature, true)); return ret; } @@ -350,6 +423,7 @@ public: typedef typename std::map::value_type mapVType; BOOST_FOREACH(const mapVType& value, mVoteMap) { // Take most voted value between current and target, inclusive + // FIXME: Should take best value that can get a significant majority if ((value.first <= std::max(mTarget, mCurrent)) && (value.first >= std::min(mTarget, mCurrent)) && (value.second > weight)) diff --git a/src/cpp/ripple/FeatureTable.h b/src/cpp/ripple/FeatureTable.h index 634427fda..a7b12aec9 100644 --- a/src/cpp/ripple/FeatureTable.h +++ b/src/cpp/ripple/FeatureTable.h @@ -18,23 +18,38 @@ public: void addVote(const uint256& feature) { ++mVotes[feature]; } }; +class FeatureState +{ +public: + bool mVetoed; // We don't want this feature enabled + bool mEnabled; + bool mSupported; + bool mDefault; // Include in genesis ledger + + uint32 mFirstMajority; // First time we saw a majority (close time) + uint32 mLastMajority; // Most recent time we saw a majority (close time) + + std::string mFriendlyName; + + FeatureState() + : mVetoed(false), mEnabled(false), mSupported(false), mDefault(false), + mFirstMajority(0), mLastMajority(0) { ; } + + void setVeto() { mVetoed = true; } + void setDefault() { mDefault = true; } + bool isDefault() { return mDefault; } + bool isSupported() { return mSupported; } + bool isVetoed() { return mVetoed; } + bool isEnabled() { return mEnabled; } + const std::string& getFiendlyName() { return mFriendlyName; } + void setFriendlyName(const std::string& n) { mFriendlyName = n; } +}; + + class FeatureTable { protected: - class FeatureState - { - public: - bool mVetoed; // We don't want this feature enabled - bool mEnabled; - bool mSupported; - - uint32 mFirstMajority; // First time we saw a majority (close time) - uint32 mLastMajority; // Most recent time we saw a majority (close time) - - FeatureState() : mVetoed(false), mEnabled(false), mSupported(false), mFirstMajority(0), mLastMajority(0) { ; } - }; - typedef boost::unordered_map featureMap_t; typedef std::pair featureIt_t; typedef boost::unordered_set featureList_t; @@ -48,15 +63,19 @@ protected: FeatureState* getCreateFeature(const uint256& feature, bool create); bool shouldEnable (uint32 closeTime, const FeatureState& fs); + void setJson(Json::Value& v, const FeatureState&); public: FeatureTable(uint32 majorityTime, int majorityFraction) : mMajorityTime(majorityTime), mMajorityFraction(majorityFraction), mFirstReport(0), mLastReport(0) - { addInitialFeatures(); } + { ; } void addInitialFeatures(); + FeatureState* addKnownFeature(const char *featureID, const char *friendlyName, bool veto); + uint256 getFeature(const std::string& name); + bool vetoFeature(const uint256& feature); bool unVetoFeature(const uint256& feature); @@ -64,6 +83,7 @@ public: bool disableFeature(const uint256& feature); bool isFeatureEnabled(const uint256& feature); + bool isFeatureSupported(const uint256& feature); void setEnabledFeatures(const std::vector& features); void setSupportedFeatures(const std::vector& features); @@ -76,6 +96,7 @@ public: void reportValidations(const FeatureSet&); Json::Value getJson(int); + Json::Value getJson(const uint256&); void doValidation(Ledger::ref lastClosedLedger, STObject& baseValidation); void doVoting(Ledger::ref lastClosedLedger, SHAMap::ref initialPosition); diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 227f350eb..4e58876c5 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -1327,6 +1327,15 @@ std::vector< std::pair > Ledger::getLedgerHashes() return ret; } +std::vector Ledger::getLedgerFeatures() +{ + std::vector usFeatures; + SLE::pointer sleFeatures = getSLEi(getLedgerFeatureIndex()); + if (sleFeatures) + usFeatures = sleFeatures->getFieldV256(sfFeatures).peekValue(); + return usFeatures; +} + // XRP to XRP not allowed. // Currencies must have appropriate issuer. // Currencies or accounts must differ. diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index b7e3cf524..db56195ef 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -219,6 +220,7 @@ public: static uint256 getLedgerFeatureIndex(); static uint256 getLedgerFeeIndex(); + std::vector getLedgerFeatures(); std::vector getNeededTransactionHashes(int max, SHAMapSyncFilter* filter); std::vector getNeededAccountStateHashes(int max, SHAMapSyncFilter* filter); diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index eaeba324b..c713cb46c 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -31,6 +31,7 @@ void InfoSub::onSendEmpty() NetworkOPs::NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster) : mMode(omDISCONNECTED), mNeedNetworkLedger(false), mProposing(false), mValidating(false), + mFeatureBlocked(false), mNetTimer(io_service), mLedgerMaster(pLedgerMaster), mCloseTimeOffset(0), mLastCloseProposers(0), mLastCloseConvergeTime(1000 * LEDGER_IDLE_INTERVAL), mLastCloseTime(0), mLastValidationTime(0), mFetchPack("FetchPack", 2048, 20), mLastFetchPack(0), mFetchSeq(static_cast(-1)), @@ -553,6 +554,12 @@ Json::Value NetworkOPs::getOwnerInfo(Ledger::pointer lpLedger, const RippleAddre // Other // +void NetworkOPs::setFeatureBlocked() +{ + mFeatureBlocked = true; + setMode(omTRACKING); +} + void NetworkOPs::setStateTimer() { mNetTimer.expires_from_now(boost::posix_time::milliseconds(LEDGER_GRANULARITY)); @@ -1047,6 +1054,9 @@ void NetworkOPs::setMode(OperatingMode om) om = omCONNECTED; } + if ((om > omTRACKING) && mFeatureBlocked) + om = omTRACKING; + if (mMode == om) return; @@ -1265,6 +1275,8 @@ Json::Value NetworkOPs::getServerInfo(bool human, bool admin) info["complete_ledgers"] = theApp->getLedgerMaster().getCompleteLedgers(); + if (mFeatureBlocked) + info["feature_blocked"] = true; size_t fp = mFetchPack.getCacheSize(); if (fp != 0) diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index ec1927170..592573969 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -115,6 +115,7 @@ protected: OperatingMode mMode; bool mNeedNetworkLedger; bool mProposing, mValidating; + bool mFeatureBlocked; boost::posix_time::ptime mConnectTime; boost::asio::deadline_timer mNetTimer; boost::shared_ptr mConsensus; @@ -309,6 +310,8 @@ public: void setProposing(bool p, bool v) { mProposing = p; mValidating = v; } bool isProposing() { return mProposing; } bool isValidating() { return mValidating; } + bool isFeatureBlocked() { return mFeatureBlocked; } + void setFeatureBlocked(); void consensusViewChange(); int getPreviousProposers() { return mLastCloseProposers; } int getPreviousConvergeTime() { return mLastCloseConvergeTime; } diff --git a/src/cpp/ripple/RPCErr.cpp b/src/cpp/ripple/RPCErr.cpp index b445279e5..a7240ab9f 100644 --- a/src/cpp/ripple/RPCErr.cpp +++ b/src/cpp/ripple/RPCErr.cpp @@ -18,6 +18,7 @@ Json::Value rpcError(int iError, Json::Value jvResult) { rpcACT_MALFORMED, "actMalformed", "Account malformed." }, { rpcACT_NOT_FOUND, "actNotFound", "Account not found." }, { rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string." }, + { rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid." }, { rpcBAD_ISSUER, "badIssuer", "Issuer account malformed." }, { rpcBAD_MARKET, "badMarket", "No such market." }, { rpcBAD_SECRET, "badSecret", "Secret does not match account." }, diff --git a/src/cpp/ripple/RPCErr.h b/src/cpp/ripple/RPCErr.h index 060e010ca..e58d6d08c 100644 --- a/src/cpp/ripple/RPCErr.h +++ b/src/cpp/ripple/RPCErr.h @@ -47,6 +47,7 @@ enum { rpcACT_MALFORMED, rpcQUALITY_MALFORMED, rpcBAD_BLOB, + rpcBAD_FEATURE, rpcBAD_ISSUER, rpcBAD_MARKET, rpcBAD_SECRET, diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index 60605021d..0f9d0c6e0 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -2255,6 +2255,30 @@ static void textTime(std::string& text, int& seconds, const char *unitName, int text += "s"; } +Json::Value RPCHandler::doFeature(Json::Value jvRequest, int& cost, ScopedLock& mlh) +{ + if (!jvRequest.isMember("feature")) + { + Json::Value jvReply = Json::objectValue; + jvReply["features"] = theApp->getFeatureTable().getJson(0); + return jvReply; + } + + uint256 uFeature = theApp->getFeatureTable().getFeature(jvRequest["feature"].asString()); + if (uFeature.isZero()) + { + uFeature.SetHex(jvRequest["feature"].asString()); + if (uFeature.isZero()) + return rpcError(rpcBAD_FEATURE); + } + + if (!jvRequest.isMember("vote")) + return theApp->getFeatureTable().getJson(uFeature); + + // WRITEME + return rpcError(rpcNOT_SUPPORTED); +} + // { // min_count: // optional, defaults to 10 // } @@ -3461,6 +3485,7 @@ Json::Value RPCHandler::doCommand(const Json::Value& jvRequest, int iRole, int & { "consensus_info", &RPCHandler::doConsensusInfo, true, optNone }, { "get_counts", &RPCHandler::doGetCounts, true, optNone }, { "internal", &RPCHandler::doInternal, true, optNone }, + { "feature", &RPCHandler::doFeature, true, optNone }, { "ledger", &RPCHandler::doLedger, false, optNetwork }, { "ledger_accept", &RPCHandler::doLedgerAccept, true, optCurrent }, { "ledger_closed", &RPCHandler::doLedgerClosed, false, optClosed }, diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index 05e991d43..1a97efecc 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -57,6 +57,7 @@ class RPCHandler Json::Value doDataFetch(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doDataStore(Json::Value params, int& cost, ScopedLock& mlh); #endif + Json::Value doFeature(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doGetCounts(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doInternal(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doLedger(Json::Value params, int& cost, ScopedLock& mlh);