diff --git a/docs/software/admin.md b/docs/software/admin.md index 192ad90368..299d6c827a 100644 --- a/docs/software/admin.md +++ b/docs/software/admin.md @@ -217,35 +217,33 @@ IMPORTANT: ## Network configuration -The network itself has network wide settings, for example: +The network itself has network wide settings that can be updated. This is done by validators voting for and agreeing to new values. + +A node can be configured to vote for upgrades using the `upgrades` endpoint . see [`commands.md`](commands.md) for more information. + +The network settings are: * the version of the protocol used to process transactions - * the maximum number of transactions that can be included in a ledger + * the maximum number of transactions that can be included in a given ledger close * the cost (fee) associated with processing operations + * the base reserve used to calculate the lumen balance needed to store things in the ledger -See the section on "Network wide settings" in the [example config](https://github.com/stellar/stellar-core/blob/master/docs/stellar-core_example.cfg) -for more details on those parameters. - -When a network value is not the same as the one specified in its configuration, -a validator will start to vote to update the network to the value specified in the -configuration during ledgers following the date specified in `PREFERRED_UPGRADE_DATETIME`. +When the network time is later than the `upgradetime` specified in +the upgrade settings, the validator will vote to update the network +to the value specified in the upgrade setting. -When a validator is voting to change network values, the output of `info` will -contain information about the vote. This can be useful to spot a misconfigured -validator (if the operator didn't know about a network wide change for example). +When a validator is armed to change network values, the output of `info` will +contain information about the vote. For a new value to be adopted, the same level of consensus between nodes needs -to be reached than for transaction sets. +to be reached as for transaction sets. ### Important notes on network wide settings Changes to network wide settings have to be orchestrated properly between validators as well as non validating nodes: * a change is vetted between operators (changes can be bundled) -* an effective date in the future is picked for the change to take effect (controlled by `PREFERRED_UPGRADE_DATETIME`) - * as soon as the date is decided, a best practice is to update - the configuration file before upgrading the binaries (as updating the version of stellar core - may trigger a vote for the new protocol version supported by that version of core) -* if applicable, communication is sent out to consumers of the network +* an effective date in the future is picked for the change to take effect (controlled by `upgradetime`) +* if applicable, communication is sent out to all network users An improper plan may cause issues such as: * nodes missing consensus (aka "getting stuck"), and having to use history to rejoin @@ -500,6 +498,22 @@ Run: 4. `$ stellar-core` - on each node to start it. +## Upgrading network settings + +Read the section on [`network-configuration`](admin.md#network-configuration) for process to follow. + +Example here is to upgrade the protocol version to version 9 on January-31-2018. + +1. `$ stellar-core -c 'upgrades?mode=set&upgradetime=2018-01-31T20:00:00Z&protocolversion=9'` + +2. `$ stellar-core -c info` +At this point `info` will tell you that the node is setup to vote for this upgrade: +``` + "status" : [ + "Armed with network upgrades: upgradetime=2018-01-31T20:00:00Z, protocolversion=9" + ] +``` + # Understanding the availability and health of your instance ## General info Run `$ stellar-core --c 'info'` diff --git a/docs/software/commands.md b/docs/software/commands.md index 2dd9e510f0..0bf8029db9 100644 --- a/docs/software/commands.md +++ b/docs/software/commands.md @@ -141,6 +141,27 @@ debugging purpose). error: set when status is "ERROR". Base64 encoded, XDR serialized 'TransactionResult' +* **upgrades** + * `/upgrades?mode=get`
+ retrieves the currently configured upgrade settings
+ * `/upgrades?mode=clear`
+ clears any upgrade settings
+ * `/upgrades?mode=set&upgradetime=DATETIME&[basefee=NUM]&[basereserve=NUM]&[maxtxsize=NUM]&[protocolversion=NUM]`
+ upgradetime is a required date (UTC) in the form 1970-01-01T00:00:00Z.
+ * fee (uint32) This is what you would prefer the base fee to be. It is + in stroops
+ * basereserve (uint32) This is what you would prefer the base reserve + to be. It is in stroops.
+ * maxtxsize (uint32) This defines the maximum number of transactions + to include in a ledger. When too many transactions are pending, + surge pricing is applied. The instance picks the top maxtxsize + transactions locally to be considered in the next ledger.Where + transactions are ordered by transaction fee(lower fee transactions + are held for later).
+ * protocolversion (uint32) defines the protocol version to upgrade to. + When specified it must match the protocol version supported by the + node
+ ### The following HTTP commands are exposed on test instances * **generateload** `/generateload[?accounts=N&txs=M&txrate=(R|auto)]`
diff --git a/docs/stellar-core_example.cfg b/docs/stellar-core_example.cfg index 9095f74e0a..17fdd7ac52 100644 --- a/docs/stellar-core_example.cfg +++ b/docs/stellar-core_example.cfg @@ -168,37 +168,6 @@ NODE_SEED="SBI3CZU7XZEWVXU7OZLW5MMUQAP334JFOPXSLTPOH43IRTEQ2QYXU5RG self" # See QUORUM_SET below. NODE_IS_VALIDATOR=false -########################### -# Network wide settings -# Be careful when changing those value as those settings allow to -# update network wide settings. -# Validators will need to reach consensus before taking effect -# In general, you need to ensure that those values match -# the network ones to avoid voting rollbacks. -# The value of the protocol version is tied to the version -# of stellar-core used to run the validator (and cannot be set in config) - -# Date and Time in UTC when this node should start voting -# for upgrades -PREFERRED_UPGRADE_DATETIME=2017-03-01T00:00:00Z - -# DESIRED_BASE_FEE (integer) default 100 -# This is what you would prefer the base fee to be. It is in stroops. -DESIRED_BASE_FEE=100 - -# DESIRED_BASE_RESERVE (integer) default 100000000 -# This is what you would prefer the base reserve to be. It is in stroops. -DESIRED_BASE_RESERVE=100000000 - -# DESIRED_MAX_TX_PER_LEDGER (integer) default 50 -# This defines the maximum number of transactions to include in a ledger -# When too many transactions are pending, surge pricing is applied: -# * The instance picks the top MAX_TX_PER_LEDGER transactions locally to be -# considered in the next ledger. Where transactions are ordered by -# transaction fee (lower fee transactions are held for later). - -DESIRED_MAX_TX_PER_LEDGER=50 - ########################### # Consensus settings diff --git a/docs/versioning.md b/docs/versioning.md index 504b3195ad..e177a608c0 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -27,8 +27,8 @@ the SCP messages for the current ledger (ie: abstain). A node considers a step invalid either because: * they do not understand it, for example a new upgrade type not implemented -* its value differs for this node configuration -* network time is before PREFERRED_UPGRADE_DATETIME +* its value differs for this node's scheduled upgrade setting +* network time is before the scheduled upgrade datetime Upgrades are applied after applying the transaction set. It is done this way because the transaction set is validated against the last closed ledger, @@ -37,19 +37,18 @@ without risking invalidating transactions for the current ledger. Supported upgrades are encoded using LedgerUpgradeType. -Following configuration options are responsible for upgrades: -* PREFERRED_UPGRADE_DATETIME - sets minimum time for node to accept and +Upgrades are specified with: +* upgradetime - the minimum time for node to accept and nominate upgrades -* DESIRED_BASE_FEE - upgrades value of baseFee in ledger header, uses upgrade +* basefee - upgrades value of baseFee in ledger header, uses upgrade type LEDGER_UPGRADE_BASE_FEE -* DESIRED_MAX_TX_PER_LEDGER - upgrades value of maxTxSetSize in ledger header, +* maxtxsize - upgrades value of maxTxSetSize in ledger header, uses upgrade type LEDGER_UPGRADE_MAX_TX_SET_SIZE -* DESIRED_BASE_RESERVE - upgrades value of baseReserve in ledger header, uses +* basereserve - upgrades value of baseReserve in ledger header, uses upgrade type LEDGER_UPGRADE_BASE_RESERVE - -Additionally nodes vote for changes to the ledgerVersion field with its -non-configurable build-in LEDGER_PROTOCOL_VERSION field (which uses upgrade -type LEDGER_UPGRADE_VERSION). +* protocolversion - upgrades value of ledgerVersion in ledger header, uses + upgrade type LEDGER_UPGRADE_VERSION (when specified it has to match the + supported version number) #### Limitations of the current implementation There is an assumption that validator operators are either paying attention to network wide proposals diff --git a/src/herder/Herder.h b/src/herder/Herder.h index 598cfee0b5..b8d047b48c 100644 --- a/src/herder/Herder.h +++ b/src/herder/Herder.h @@ -5,6 +5,7 @@ // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 #include "TxSetFrame.h" +#include "Upgrades.h" #include "lib/json/json-forwards.h" #include "overlay/StellarXDR.h" #include "scp/SCP.h" @@ -94,8 +95,8 @@ class Herder virtual void bootstrap() = 0; - // restores SCP state based on the last messages saved on disk - virtual void restoreSCPState() = 0; + // restores Herder's state from disk + virtual void restoreState() = 0; virtual bool recvSCPQuorumSet(Hash const& hash, SCPQuorumSet const& qset) = 0; @@ -126,6 +127,11 @@ class Herder // lookup a nodeID in config and in SCP messages virtual bool resolveNodeID(std::string const& s, PublicKey& retKey) = 0; + // sets the upgrades that should be applied during consensus + virtual void setUpgrades(Upgrades::UpgradeParameters const& upgrades) = 0; + // gets the upgrades that are scheduled by this node + virtual std::string getUpgradesJson() = 0; + virtual ~Herder() { } diff --git a/src/herder/HerderImpl.cpp b/src/herder/HerderImpl.cpp index 744661d970..831d0cd4a2 100644 --- a/src/herder/HerderImpl.cpp +++ b/src/herder/HerderImpl.cpp @@ -72,7 +72,6 @@ HerderImpl::SCPMetrics::SCPMetrics(Application& app) HerderImpl::HerderImpl(Application& app) : mPendingTransactions(4) , mPendingEnvelopes(app, *this) - , mUpgrades(app.getConfig()) , mHerderSCPDriver(app, *this, mUpgrades, mPendingEnvelopes) , mLastSlotSaved(0) , mTrackingTimer(app) @@ -190,6 +189,17 @@ HerderImpl::valueExternalized(uint64 slotIndex, StellarValue const& value) static_cast(slotIndex), getSCP().getExternalizingState(slotIndex)); + // reflect upgrades with the ones included in this SCP round + { + bool updated; + auto newUpgrades = mUpgrades.removeUpgrades( + value.upgrades.begin(), value.upgrades.end(), updated); + if (updated) + { + setUpgrades(newUpgrades); + } + } + // tell the LedgerManager that this value got externalized // LedgerManager will perform the proper action based on its internal // state: apply, trigger catchup, etc @@ -748,7 +758,7 @@ HerderImpl::triggerNextLedger(uint32_t ledgerSeqToTrigger) 0); // see if we need to include some upgrades - auto upgrades = mUpgrades.upgradesFor(lcl.header); + auto upgrades = mUpgrades.createUpgradesFor(lcl.header); for (auto const& upgrade : upgrades) { Value v(xdr::xdr_to_opaque(upgrade)); @@ -765,24 +775,42 @@ HerderImpl::triggerNextLedger(uint32_t ledgerSeqToTrigger) } } - if (!upgrades.empty()) + mHerderSCPDriver.nominate(slotIndex, newProposedValue, proposedSet, + lcl.header.scpValue); +} + +void +HerderImpl::setUpgrades(Upgrades::UpgradeParameters const& upgrades) +{ + mUpgrades.setParameters(upgrades, mApp.getConfig()); + persistUpgrades(); + + auto desc = mUpgrades.toString(); + + if (!desc.empty()) { - auto message = fmt::format( - "Proposing herder configuration upgrades: {0}; network " - "values for LCL are: {1}", - Upgrades::toString(upgrades), Upgrades::toString(lcl.header)); - CLOG(INFO, "Herder") << message; - mApp.getStatusManager().setStatusMessage( - StatusCategory::REQUIRES_UPGRADES, message); + auto message = fmt::format("Armed with network upgrades: {}", desc); + auto prev = mApp.getStatusManager().getStatusMessage( + StatusCategory::REQUIRES_UPGRADES); + if (prev != message) + { + CLOG(INFO, "Herder") << message; + mApp.getStatusManager().setStatusMessage( + StatusCategory::REQUIRES_UPGRADES, message); + } } else { + CLOG(INFO, "Herder") << "Network upgrades cleared"; mApp.getStatusManager().removeStatusMessage( StatusCategory::REQUIRES_UPGRADES); } +} - mHerderSCPDriver.nominate(slotIndex, newProposedValue, proposedSet, - lcl.header.scpValue); +std::string +HerderImpl::getUpgradesJson() +{ + return mUpgrades.getParameters().toJson(); } bool @@ -956,6 +984,42 @@ HerderImpl::restoreSCPState() } } +void +HerderImpl::persistUpgrades() +{ + auto s = mUpgrades.getParameters().toJson(); + mApp.getPersistentState().setState(PersistentState::kLedgerUpgrades, s); +} + +void +HerderImpl::restoreUpgrades() +{ + std::string s = + mApp.getPersistentState().getState(PersistentState::kLedgerUpgrades); + if (!s.empty()) + { + Upgrades::UpgradeParameters p; + p.fromJson(s); + try + { + // use common code to set status + setUpgrades(p); + } + catch (std::exception e) + { + CLOG(INFO, "Herder") << "Error restoring upgrades '" << e.what() + << "' with upgrades '" << s << "'"; + } + } +} + +void +HerderImpl::restoreState() +{ + restoreSCPState(); + restoreUpgrades(); +} + void HerderImpl::trackingHeartBeat() { diff --git a/src/herder/HerderImpl.h b/src/herder/HerderImpl.h index c992191cac..0f3ec1e2bf 100644 --- a/src/herder/HerderImpl.h +++ b/src/herder/HerderImpl.h @@ -47,8 +47,7 @@ class HerderImpl : public Herder // Bootstraps the HerderImpl if we're creating a new Network void bootstrap() override; - // restores SCP state based on the last messages saved on disk - void restoreSCPState() override; + void restoreState() override; SCP& getSCP(); HerderSCPDriver& @@ -81,6 +80,9 @@ class HerderImpl : public Herder void triggerNextLedger(uint32_t ledgerSeqToTrigger) override; + void setUpgrades(Upgrades::UpgradeParameters const& upgrades) override; + std::string getUpgradesJson() override; + bool resolveNodeID(std::string const& s, PublicKey& retKey) override; void dumpInfo(Json::Value& ret, size_t limit) override; @@ -133,6 +135,12 @@ class HerderImpl : public Herder // saves the SCP messages that the instance sent out last void persistSCPState(uint64 slot); + // restores SCP state based on the last messages saved on disk + void restoreSCPState(); + + // saves upgrade parameters + void persistUpgrades(); + void restoreUpgrades(); // called every time we get ledger externalized // ensures that if we don't hear from the network, we throw the herder into diff --git a/src/herder/HerderSCPDriver.cpp b/src/herder/HerderSCPDriver.cpp index 190079bfd7..91f453b63a 100644 --- a/src/herder/HerderSCPDriver.cpp +++ b/src/herder/HerderSCPDriver.cpp @@ -272,7 +272,8 @@ HerderSCPDriver::validateValueHelper(uint64_t slotIndex, } SCPDriver::ValidationLevel -HerderSCPDriver::validateValue(uint64_t slotIndex, Value const& value) +HerderSCPDriver::validateValue(uint64_t slotIndex, Value const& value, + bool nomination) { StellarValue b; try @@ -293,7 +294,8 @@ HerderSCPDriver::validateValue(uint64_t slotIndex, Value const& value) for (size_t i = 0; i < b.upgrades.size(); i++) { LedgerUpgradeType thisUpgradeType; - if (!mUpgrades.isValid(b.closeTime, b.upgrades[i], thisUpgradeType)) + if (!mUpgrades.isValid(b.closeTime, b.upgrades[i], thisUpgradeType, + nomination, mApp.getConfig())) { CLOG(TRACE, "Herder") << "HerderSCPDriver::validateValue invalid step at index " @@ -343,7 +345,8 @@ HerderSCPDriver::extractValidValue(uint64_t slotIndex, Value const& value) LedgerUpgradeType thisUpgradeType; for (auto it = b.upgrades.begin(); it != b.upgrades.end();) { - if (!mUpgrades.isValid(b.closeTime, *it, thisUpgradeType)) + if (!mUpgrades.isValid(b.closeTime, *it, thisUpgradeType, true, + mApp.getConfig())) { it = b.upgrades.erase(it); } diff --git a/src/herder/HerderSCPDriver.h b/src/herder/HerderSCPDriver.h index 403bfe2520..93f33a3dfc 100644 --- a/src/herder/HerderSCPDriver.h +++ b/src/herder/HerderSCPDriver.h @@ -94,7 +94,8 @@ class HerderSCPDriver : public SCPDriver // value validation SCPDriver::ValidationLevel validateValue(uint64_t slotIndex, - Value const& value) override; + Value const& value, + bool nomination) override; Value extractValidValue(uint64_t slotIndex, Value const& value) override; // value marshaling diff --git a/src/herder/HerderTests.cpp b/src/herder/HerderTests.cpp index e10d689342..cebf7a5dec 100644 --- a/src/herder/HerderTests.cpp +++ b/src/herder/HerderTests.cpp @@ -303,7 +303,7 @@ TEST_CASE("txset", "[herder]") TEST_CASE("surge", "[herder]") { Config cfg(getTestConfig()); - cfg.DESIRED_MAX_TX_PER_LEDGER = 5; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER = 5; VirtualClock clock; Application::pointer app = createTestApplication(clock, cfg); @@ -313,7 +313,7 @@ TEST_CASE("surge", "[herder]") auto& lm = app->getLedgerManager(); app->getLedgerManager().getCurrentLedgerHeader().maxTxSetSize = - cfg.DESIRED_MAX_TX_PER_LEDGER; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER; // set up world auto root = TestAccount::createRoot(*app); @@ -416,7 +416,7 @@ TEST_CASE("surge", "[herder]") TEST_CASE("SCP Driver", "[herder]") { Config cfg(getTestConfig()); - cfg.DESIRED_MAX_TX_PER_LEDGER = 5; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER = 5; VirtualClock clock; Application::pointer app = createTestApplication(clock, cfg); @@ -424,7 +424,7 @@ TEST_CASE("SCP Driver", "[herder]") app->start(); app->getLedgerManager().getCurrentLedgerHeader().maxTxSetSize = - cfg.DESIRED_MAX_TX_PER_LEDGER; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER; auto const& lcl = app->getLedgerManager().getLastClosedLedgerHeader(); diff --git a/src/herder/Upgrades.cpp b/src/herder/Upgrades.cpp index e0949b4f11..b951a648db 100644 --- a/src/herder/Upgrades.cpp +++ b/src/herder/Upgrades.cpp @@ -6,18 +6,86 @@ #include "main/Config.h" #include "util/Logging.h" #include "util/Timer.h" +#include +#include #include #include +namespace cereal +{ +template +void +save(Archive& ar, stellar::Upgrades::UpgradeParameters const& p) +{ + ar(make_nvp("time", stellar::VirtualClock::to_time_t(p.mUpgradeTime))); + ar(make_nvp("version", p.mProtocolVersion)); + ar(make_nvp("fee", p.mBaseFee)); + ar(make_nvp("maxtxsize", p.mMaxTxSize)); + ar(make_nvp("reserve", p.mBaseReserve)); +} + +template +void +load(Archive& ar, stellar::Upgrades::UpgradeParameters& o) +{ + time_t t; + ar(make_nvp("time", t)); + o.mUpgradeTime = stellar::VirtualClock::from_time_t(t); + ar(make_nvp("version", o.mProtocolVersion)); + ar(make_nvp("fee", o.mBaseFee)); + ar(make_nvp("maxtxsize", o.mMaxTxSize)); + ar(make_nvp("reserve", o.mBaseReserve)); +} +} // namespace cereal + namespace stellar { +std::string +Upgrades::UpgradeParameters::toJson() const +{ + std::ostringstream out; + { + cereal::JSONOutputArchive ar(out); + cereal::save(ar, *this); + } + return out.str(); +} + +void +Upgrades::UpgradeParameters::fromJson(std::string const& s) +{ + std::istringstream in(s); + { + cereal::JSONInputArchive ar(in); + cereal::load(ar, *this); + } +} + +Upgrades::Upgrades(UpgradeParameters const& params) : mParams(params) +{ +} -Upgrades::Upgrades(Config const& cfg) : mCfg{cfg} +void +Upgrades::setParameters(UpgradeParameters const& params, Config const& cfg) { + if (params.mProtocolVersion && + *params.mProtocolVersion != cfg.LEDGER_PROTOCOL_VERSION) + { + throw std::invalid_argument( + fmt::format("Protocol version error: supported is {}, passed is {}", + cfg.LEDGER_PROTOCOL_VERSION, *params.mProtocolVersion)); + } + mParams = params; +} + +Upgrades::UpgradeParameters const& +Upgrades::getParameters() const +{ + return mParams; } std::vector -Upgrades::upgradesFor(LedgerHeader const& header) const +Upgrades::createUpgradesFor(LedgerHeader const& header) const { auto result = std::vector{}; if (!timeForUpgrade(header.scpValue.closeTime)) @@ -25,25 +93,26 @@ Upgrades::upgradesFor(LedgerHeader const& header) const return result; } - if (header.ledgerVersion != mCfg.LEDGER_PROTOCOL_VERSION) + if (mParams.mProtocolVersion && + (header.ledgerVersion != *mParams.mProtocolVersion)) { result.emplace_back(LEDGER_UPGRADE_VERSION); - result.back().newLedgerVersion() = mCfg.LEDGER_PROTOCOL_VERSION; + result.back().newLedgerVersion() = *mParams.mProtocolVersion; } - if (header.baseFee != mCfg.DESIRED_BASE_FEE) + if (mParams.mBaseFee && (header.baseFee != *mParams.mBaseFee)) { result.emplace_back(LEDGER_UPGRADE_BASE_FEE); - result.back().newBaseFee() = mCfg.DESIRED_BASE_FEE; + result.back().newBaseFee() = *mParams.mBaseFee; } - if (header.maxTxSetSize != mCfg.DESIRED_MAX_TX_PER_LEDGER) + if (mParams.mMaxTxSize && (header.maxTxSetSize != *mParams.mMaxTxSize)) { result.emplace_back(LEDGER_UPGRADE_MAX_TX_SET_SIZE); - result.back().newMaxTxSetSize() = mCfg.DESIRED_MAX_TX_PER_LEDGER; + result.back().newMaxTxSetSize() = *mParams.mMaxTxSize; } - if (header.baseReserve != mCfg.DESIRED_BASE_RESERVE) + if (mParams.mBaseReserve && (header.baseReserve != *mParams.mBaseReserve)) { result.emplace_back(LEDGER_UPGRADE_BASE_RESERVE); - result.back().newBaseReserve() = mCfg.DESIRED_BASE_RESERVE; + result.back().newBaseReserve() = *mParams.mBaseReserve; } return result; @@ -80,53 +149,98 @@ Upgrades::toString(LedgerUpgrade const& upgrade) switch (upgrade.type()) { case LEDGER_UPGRADE_VERSION: - return fmt::format("PROTOCOL_VERSION={0}", upgrade.newLedgerVersion()); + return fmt::format("protocolversion={0}", upgrade.newLedgerVersion()); case LEDGER_UPGRADE_BASE_FEE: - return fmt::format("BASE_FEE={0}", upgrade.newBaseFee()); + return fmt::format("basefee={0}", upgrade.newBaseFee()); case LEDGER_UPGRADE_MAX_TX_SET_SIZE: - return fmt::format("MAX_TX_SET_SIZE={0}", upgrade.newMaxTxSetSize()); + return fmt::format("maxtxsetsize={0}", upgrade.newMaxTxSetSize()); case LEDGER_UPGRADE_BASE_RESERVE: - return fmt::format("BASE_RESERVE={0}", upgrade.newBaseReserve()); + return fmt::format("basereserve={0}", upgrade.newBaseReserve()); default: return ""; } } std::string -Upgrades::toString(std::vector const& upgrades) +Upgrades::toString() const { - if (upgrades.empty()) - { - return {}; - } + fmt::MemoryWriter r; - auto result = std::string{}; - for (auto const& upgrade : upgrades) - { - if (!result.empty()) + auto appendInfo = [&](std::string const& s, optional const& o) { + if (o) { - result += ", "; + if (!r.size()) + { + r << "upgradetime=" + << VirtualClock::pointToISOString(mParams.mUpgradeTime); + } + r << ", " << s << "=" << *o; } - result += toString(upgrade); - } + }; + appendInfo("protocolversion", mParams.mProtocolVersion); + appendInfo("basefee", mParams.mBaseFee); + appendInfo("basereserve", mParams.mBaseReserve); + appendInfo("maxtxsize", mParams.mMaxTxSize); - return fmt::format("[{0}]", result); + return r.str(); } -std::string -Upgrades::toString(LedgerHeader const& header) +Upgrades::UpgradeParameters +Upgrades::removeUpgrades(std::vector::const_iterator beginUpdates, + std::vector::const_iterator endUpdates, + bool& updated) { - return fmt::format("PROTOCOL_VERSION={0}, BASE_FEE={1}, " - "MAX_TX_SET_SIZE={2}, BASE_RESERVE={3}", - header.ledgerVersion, header.baseFee, - header.maxTxSetSize, header.baseReserve); + updated = false; + UpgradeParameters res = mParams; + + auto resetParam = [&](optional& o, uint32 v) { + if (o && *o == v) + { + o.reset(); + updated = true; + } + }; + + for (auto it = beginUpdates; it != endUpdates; it++) + { + auto& u = *it; + LedgerUpgrade lu; + try + { + xdr::xdr_from_opaque(u, lu); + } + catch (xdr::xdr_runtime_error&) + { + continue; + } + switch (lu.type()) + { + case LEDGER_UPGRADE_VERSION: + resetParam(res.mProtocolVersion, lu.newLedgerVersion()); + break; + case LEDGER_UPGRADE_BASE_FEE: + resetParam(res.mBaseFee, lu.newBaseFee()); + break; + case LEDGER_UPGRADE_MAX_TX_SET_SIZE: + resetParam(res.mMaxTxSize, lu.newMaxTxSetSize()); + break; + case LEDGER_UPGRADE_BASE_RESERVE: + resetParam(res.mBaseReserve, lu.newBaseReserve()); + break; + default: + // skip unknown + break; + } + } + return res; } bool Upgrades::isValid(uint64_t closeTime, UpgradeType const& upgrade, - LedgerUpgradeType& upgradeType) const + LedgerUpgradeType& upgradeType, bool nomination, + Config const& cfg) const { - if (!timeForUpgrade(closeTime)) + if (nomination && !timeForUpgrade(closeTime)) { return false; } @@ -142,31 +256,50 @@ Upgrades::isValid(uint64_t closeTime, UpgradeType const& upgrade, return false; } - bool res; + bool res = true; switch (lupgrade.type()) { case LEDGER_UPGRADE_VERSION: { uint32 newVersion = lupgrade.newLedgerVersion(); - res = (newVersion == mCfg.LEDGER_PROTOCOL_VERSION); + if (nomination) + { + res = mParams.mProtocolVersion && + (newVersion == *mParams.mProtocolVersion); + } + // only upgrade to the latest supported version of the protocol + // is allowed + res = res && (newVersion == cfg.LEDGER_PROTOCOL_VERSION); } break; case LEDGER_UPGRADE_BASE_FEE: { uint32 newFee = lupgrade.newBaseFee(); - res = (newFee == mCfg.DESIRED_BASE_FEE); + if (nomination) + { + res = mParams.mBaseFee && (newFee == *mParams.mBaseFee); + } + res = res && (newFee != 0); } break; case LEDGER_UPGRADE_MAX_TX_SET_SIZE: { uint32 newMax = lupgrade.newMaxTxSetSize(); - res = (newMax == mCfg.DESIRED_MAX_TX_PER_LEDGER); + if (nomination) + { + res = mParams.mMaxTxSize && (newMax == *mParams.mMaxTxSize); + } + res = res && (newMax != 0); } break; case LEDGER_UPGRADE_BASE_RESERVE: { uint32 newReserve = lupgrade.newBaseReserve(); - res = (newReserve == mCfg.DESIRED_BASE_RESERVE); + if (nomination) + { + res = mParams.mBaseReserve && (newReserve == *mParams.mBaseReserve); + } + res = res && (newReserve != 0); } break; default: @@ -182,6 +315,6 @@ Upgrades::isValid(uint64_t closeTime, UpgradeType const& upgrade, bool Upgrades::timeForUpgrade(uint64_t time) const { - return mCfg.PREFERRED_UPGRADE_DATETIME <= VirtualClock::from_time_t(time); + return mParams.mUpgradeTime <= VirtualClock::from_time_t(time); } } diff --git a/src/herder/Upgrades.h b/src/herder/Upgrades.h index 59f38e9ff2..53d6bcd119 100644 --- a/src/herder/Upgrades.h +++ b/src/herder/Upgrades.h @@ -6,6 +6,9 @@ #include "xdr/Stellar-ledger.h" +#include "main/Config.h" +#include "util/Timer.h" +#include "util/optional.h" #include #include @@ -18,10 +21,43 @@ struct LedgerUpgrade; class Upgrades { public: - explicit Upgrades(Config const& cfg); + struct UpgradeParameters + { + UpgradeParameters() + { + } + UpgradeParameters(Config const& cfg) + { + mUpgradeTime = cfg.TESTING_UPGRADE_DATETIME; + mProtocolVersion = + make_optional(cfg.LEDGER_PROTOCOL_VERSION); + mBaseFee = make_optional(cfg.TESTING_UPGRADE_DESIRED_FEE); + mMaxTxSize = + make_optional(cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER); + mBaseReserve = make_optional(cfg.TESTING_UPGRADE_RESERVE); + } + VirtualClock::time_point mUpgradeTime; + optional mProtocolVersion; + optional mBaseFee; + optional mMaxTxSize; + optional mBaseReserve; + + std::string toJson() const; + void fromJson(std::string const& s); + }; + + Upgrades() + { + } + explicit Upgrades(UpgradeParameters const& params); + + void setParameters(UpgradeParameters const& params, Config const& cfg); + + UpgradeParameters const& getParameters() const; // create upgrades for given ledger - std::vector upgradesFor(LedgerHeader const& header) const; + std::vector + createUpgradesFor(LedgerHeader const& header) const; // apply upgrade to ledger header static void applyTo(LedgerUpgrade const& upgrade, LedgerHeader& header); @@ -29,19 +65,24 @@ class Upgrades // convert upgrade value to string static std::string toString(LedgerUpgrade const& upgrade); - // convert upgrades vector to string - static std::string toString(std::vector const& upgrades); - - // convert upgrades from herder to string - static std::string toString(LedgerHeader const& header); - // returns true if upgrade is a valid upgrade step // in which case it also sets upgradeType bool isValid(uint64_t closeTime, UpgradeType const& upgrade, - LedgerUpgradeType& upgradeType) const; + LedgerUpgradeType& upgradeType, bool nomination, + Config const& cfg) const; + + // constructs a human readable string that represents + // the pending upgrades + std::string toString() const; + + // sets updated to true if some upgrades were removed + UpgradeParameters + removeUpgrades(std::vector::const_iterator beginUpdates, + std::vector::const_iterator endUpdates, + bool& updated); private: - Config const& mCfg; + UpgradeParameters mParams; bool timeForUpgrade(uint64_t time) const; }; diff --git a/src/herder/UpgradesTests.cpp b/src/herder/UpgradesTests.cpp index 11b91769e3..981d49dedf 100644 --- a/src/herder/UpgradesTests.cpp +++ b/src/herder/UpgradesTests.cpp @@ -19,15 +19,22 @@ using namespace stellar; struct LedgerUpgradeableData { - uint32_t ledgerVersion; - uint32_t baseFee; - uint32_t maxTxSetSize; - uint32_t baseReserve; + LedgerUpgradeableData() + { + } + LedgerUpgradeableData(uint32_t v, uint32_t f, uint32_t txs, uint32_t r) + : ledgerVersion(v), baseFee(f), maxTxSetSize(txs), baseReserve(r) + { + } + uint32_t ledgerVersion{0}; + uint32_t baseFee{0}; + uint32_t maxTxSetSize{0}; + uint32_t baseReserve{0}; }; struct LedgerUpgradeNode { - LedgerUpgradeableData starting; + LedgerUpgradeableData desiredUpgrades; VirtualClock::time_point preferredUpgradeDatetime; }; @@ -40,8 +47,7 @@ struct LedgerUpgradeCheck void simulateUpgrade(std::vector const& nodes, std::vector const& checks, - bool proposeUpgradeAfterCatchedUp = false, - std::vector const& afterCatchedUp = {}) + bool checkUpgradeStatus = false) { auto networkID = sha256(getTestConfig().NETWORK_PASSPHRASE); historytestutils::TmpDirHistoryConfigurator configurator{}; @@ -57,29 +63,42 @@ simulateUpgrade(std::vector const& nodes, keys.push_back( SecretKey::fromSeed(sha256("NODE_SEED_" + std::to_string(i)))); configs.push_back(simulation->newConfig()); - configs.back().LEDGER_PROTOCOL_VERSION = - nodes[i].starting.ledgerVersion; - configs.back().DESIRED_BASE_FEE = nodes[i].starting.baseFee; - configs.back().DESIRED_MAX_TX_PER_LEDGER = - nodes[i].starting.maxTxSetSize; - configs.back().DESIRED_BASE_RESERVE = nodes[i].starting.baseReserve; - configs.back().PREFERRED_UPGRADE_DATETIME = - nodes[i].preferredUpgradeDatetime; - + // disable upgrade from config + configs.back().TESTING_UPGRADE_DATETIME = VirtualClock::time_point(); // first node can write to history, all can read configurator.configure(configs.back(), i == 0); } + // first two only depend on each other + // this allows to test for v-blocking properties + // on the 3rd node auto qSet = SCPQuorumSet{}; qSet.threshold = 2; qSet.validators.push_back(keys[0].getPublicKey()); qSet.validators.push_back(keys[1].getPublicKey()); qSet.validators.push_back(keys[2].getPublicKey()); + auto setUpgrade = [](optional& o, uint32 v) { + o = make_optional(v); + }; // create nodes for (size_t i = 0; i < nodes.size(); i++) { - simulation->addNode(keys[i], qSet, &configs[i]); + auto app = simulation->addNode(keys[i], qSet, &configs[i]); + + auto& upgradeTime = nodes[i].preferredUpgradeDatetime; + + if (upgradeTime.time_since_epoch().count() != 0) + { + auto& du = nodes[i].desiredUpgrades; + Upgrades::UpgradeParameters upgrades; + setUpgrade(upgrades.mBaseFee, du.baseFee); + setUpgrade(upgrades.mBaseReserve, du.baseReserve); + setUpgrade(upgrades.mMaxTxSize, du.maxTxSetSize); + setUpgrade(upgrades.mProtocolVersion, du.ledgerVersion); + upgrades.mUpgradeTime = upgradeTime; + app->getHerder().setUpgrades(upgrades); + } } HistoryManager::initializeHistoryArchive( @@ -129,50 +148,20 @@ simulateUpgrade(std::vector const& nodes, }); }; - if (afterCatchedUp.size() != 0) - { - // we need to wait until some nodes notice that they are not - // externalizing anymore because of upgrades disagreement - auto atLeastOneNotSynced = [&]() { return !allSynced(); }; - simulation->crankUntil(atLeastOneNotSynced, - 2 * Herder::CONSENSUS_STUCK_TIMEOUT_SECONDS, - false); - - auto checkpointFrequency = - simulation->getNode(keys.begin()->getPublicKey()) - ->getHistoryManager() - .getCheckpointFrequency(); - - // and then wait until it catches up again ans assumes upgrade values - // from the history - simulation->crankUntil(allSynced, - 2 * checkpointFrequency * - Herder::EXP_LEDGER_TIMESPAN_SECONDS, - false); - statesMatch(afterCatchedUp); - } - else - { - // all nodes are synced as there was no disagreement about upgrades - REQUIRE(allSynced()); - } + // all nodes are synced as there was no disagreement about upgrades + REQUIRE(allSynced()); - if (proposeUpgradeAfterCatchedUp) + if (checkUpgradeStatus) { - // at least one node should show message thats its upgrades from config - // are different than network values - auto atLeastOneMessagesAboutUpgrades = [&]() { - return std::any_of( - std::begin(keys), std::end(keys), [&](SecretKey const& key) { - auto const& node = simulation->getNode(key.getPublicKey()); - return !node->getStatusManager() - .getStatusMessage( - StatusCategory::REQUIRES_UPGRADES) - .empty(); - }); - }; - simulation->crankUntil(atLeastOneMessagesAboutUpgrades, - 2 * Herder::EXP_LEDGER_TIMESPAN_SECONDS, false); + // at least one node should show message thats it has some + // pending upgrades + REQUIRE(std::any_of( + std::begin(keys), std::end(keys), [&](SecretKey const& key) { + auto const& node = simulation->getNode(key.getPublicKey()); + return !node->getStatusManager() + .getStatusMessage(StatusCategory::REQUIRES_UPGRADES) + .empty(); + })); } } @@ -222,28 +211,30 @@ testListUpgrades(VirtualClock::time_point preferredUpgradeDatetime, { auto cfg = getTestConfig(); cfg.LEDGER_PROTOCOL_VERSION = 10; - cfg.DESIRED_BASE_FEE = 100; - cfg.DESIRED_MAX_TX_PER_LEDGER = 50; - cfg.DESIRED_BASE_RESERVE = 100000000; - cfg.PREFERRED_UPGRADE_DATETIME = preferredUpgradeDatetime; + cfg.TESTING_UPGRADE_DESIRED_FEE = 100; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER = 50; + cfg.TESTING_UPGRADE_RESERVE = 100000000; + cfg.TESTING_UPGRADE_DATETIME = preferredUpgradeDatetime; auto header = LedgerHeader{}; header.ledgerVersion = cfg.LEDGER_PROTOCOL_VERSION; - header.baseFee = cfg.DESIRED_BASE_FEE; - header.baseReserve = cfg.DESIRED_BASE_RESERVE; - header.maxTxSetSize = cfg.DESIRED_MAX_TX_PER_LEDGER; + header.baseFee = cfg.TESTING_UPGRADE_DESIRED_FEE; + header.baseReserve = cfg.TESTING_UPGRADE_RESERVE; + header.maxTxSetSize = cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER; header.scpValue.closeTime = VirtualClock::to_time_t(genesis(0, 0)); auto protocolVersionUpgrade = makeProtocolVersionUpgrade(cfg.LEDGER_PROTOCOL_VERSION); - auto baseFeeUpgrade = makeBaseFeeUpgrade(cfg.DESIRED_BASE_FEE); - auto txCountUpgrade = makeTxCountUpgrade(cfg.DESIRED_MAX_TX_PER_LEDGER); - auto baseReserveUpgrade = makeBaseReserveUpgrade(cfg.DESIRED_BASE_RESERVE); + auto baseFeeUpgrade = makeBaseFeeUpgrade(cfg.TESTING_UPGRADE_DESIRED_FEE); + auto txCountUpgrade = + makeTxCountUpgrade(cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER); + auto baseReserveUpgrade = + makeBaseReserveUpgrade(cfg.TESTING_UPGRADE_RESERVE); SECTION("protocol version upgrade needed") { header.ledgerVersion--; - auto upgrades = Upgrades{cfg}.upgradesFor(header); + auto upgrades = Upgrades{cfg}.createUpgradesFor(header); auto expected = shouldListAny ? std::vector{protocolVersionUpgrade} : std::vector{}; @@ -253,7 +244,7 @@ testListUpgrades(VirtualClock::time_point preferredUpgradeDatetime, SECTION("base fee upgrade needed") { header.baseFee /= 2; - auto upgrades = Upgrades{cfg}.upgradesFor(header); + auto upgrades = Upgrades{cfg}.createUpgradesFor(header); auto expected = shouldListAny ? std::vector{baseFeeUpgrade} : std::vector{}; @@ -263,7 +254,7 @@ testListUpgrades(VirtualClock::time_point preferredUpgradeDatetime, SECTION("tx count upgrade needed") { header.maxTxSetSize /= 2; - auto upgrades = Upgrades{cfg}.upgradesFor(header); + auto upgrades = Upgrades{cfg}.createUpgradesFor(header); auto expected = shouldListAny ? std::vector{txCountUpgrade} : std::vector{}; @@ -273,7 +264,7 @@ testListUpgrades(VirtualClock::time_point preferredUpgradeDatetime, SECTION("base reserve upgrade needed") { header.baseReserve /= 2; - auto upgrades = Upgrades{cfg}.upgradesFor(header); + auto upgrades = Upgrades{cfg}.createUpgradesFor(header); auto expected = shouldListAny ? std::vector{baseReserveUpgrade} : std::vector{}; @@ -286,7 +277,7 @@ testListUpgrades(VirtualClock::time_point preferredUpgradeDatetime, header.baseFee /= 2; header.maxTxSetSize /= 2; header.baseReserve /= 2; - auto upgrades = Upgrades{cfg}.upgradesFor(header); + auto upgrades = Upgrades{cfg}.createUpgradesFor(header); auto expected = shouldListAny ? std::vector{protocolVersionUpgrade, @@ -318,83 +309,144 @@ testValidateUpgrades(VirtualClock::time_point preferredUpgradeDatetime, { auto cfg = getTestConfig(); cfg.LEDGER_PROTOCOL_VERSION = 10; - cfg.DESIRED_BASE_FEE = 100; - cfg.DESIRED_MAX_TX_PER_LEDGER = 50; - cfg.DESIRED_BASE_RESERVE = 100000000; - cfg.PREFERRED_UPGRADE_DATETIME = preferredUpgradeDatetime; + cfg.TESTING_UPGRADE_DESIRED_FEE = 100; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER = 50; + cfg.TESTING_UPGRADE_RESERVE = 100000000; + cfg.TESTING_UPGRADE_DATETIME = preferredUpgradeDatetime; auto checkTime = VirtualClock::to_time_t(genesis(0, 0)); auto ledgerUpgradeType = LedgerUpgradeType{}; - SECTION("invalid upgrade data") - { - REQUIRE(!Upgrades{cfg}.isValid(checkTime, UpgradeType{}, - ledgerUpgradeType)); - } + auto checkWith = [&](bool nomination) { + SECTION("invalid upgrade data") + { + REQUIRE(!Upgrades{cfg}.isValid(checkTime, UpgradeType{}, + ledgerUpgradeType, nomination, cfg)); + } - SECTION("version") - { - REQUIRE(canBeValid == Upgrades{cfg}.isValid( - checkTime, - toUpgradeType(makeProtocolVersionUpgrade(10)), - ledgerUpgradeType)); - REQUIRE(!Upgrades{cfg}.isValid( - checkTime, toUpgradeType(makeProtocolVersionUpgrade(9)), - ledgerUpgradeType)); - REQUIRE(!Upgrades{cfg}.isValid( - checkTime, toUpgradeType(makeProtocolVersionUpgrade(11)), - ledgerUpgradeType)); - } + SECTION("version") + { + if (nomination) + { + REQUIRE(canBeValid == + Upgrades{cfg}.isValid( + checkTime, + toUpgradeType(makeProtocolVersionUpgrade(10)), + ledgerUpgradeType, nomination, cfg)); + } + else + { + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeProtocolVersionUpgrade(10)), + ledgerUpgradeType, nomination, cfg)); + } + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeProtocolVersionUpgrade(9)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeProtocolVersionUpgrade(11)), + ledgerUpgradeType, nomination, cfg)); + } - SECTION("base fee") - { - REQUIRE(canBeValid == - Upgrades{cfg}.isValid(checkTime, - toUpgradeType(makeBaseFeeUpgrade(100)), - ledgerUpgradeType)); - REQUIRE(!Upgrades{cfg}.isValid(checkTime, - toUpgradeType(makeBaseFeeUpgrade(99)), - ledgerUpgradeType)); - REQUIRE(!Upgrades{cfg}.isValid(checkTime, - toUpgradeType(makeBaseFeeUpgrade(101)), - ledgerUpgradeType)); - } + SECTION("base fee") + { + if (nomination) + { + REQUIRE(canBeValid == + Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseFeeUpgrade(100)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseFeeUpgrade(99)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseFeeUpgrade(101)), + ledgerUpgradeType, nomination, cfg)); + } + else + { + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseFeeUpgrade(100)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseFeeUpgrade(99)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseFeeUpgrade(101)), + ledgerUpgradeType, nomination, cfg)); + } + REQUIRE(!Upgrades{cfg}.isValid(checkTime, + toUpgradeType(makeBaseFeeUpgrade(0)), + ledgerUpgradeType, nomination, cfg)); + } - SECTION("tx count") - { - REQUIRE(canBeValid == - Upgrades{cfg}.isValid(checkTime, - toUpgradeType(makeTxCountUpgrade(50)), - ledgerUpgradeType)); - REQUIRE(!Upgrades{cfg}.isValid(checkTime, - toUpgradeType(makeTxCountUpgrade(49)), - ledgerUpgradeType)); - REQUIRE(!Upgrades{cfg}.isValid(checkTime, - toUpgradeType(makeTxCountUpgrade(51)), - ledgerUpgradeType)); - } + SECTION("tx count") + { + if (nomination) + { + REQUIRE(canBeValid == Upgrades{cfg}.isValid( + checkTime, + toUpgradeType(makeTxCountUpgrade(50)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeTxCountUpgrade(49)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeTxCountUpgrade(51)), + ledgerUpgradeType, nomination, cfg)); + } + else + { + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeTxCountUpgrade(50)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeTxCountUpgrade(49)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeTxCountUpgrade(51)), + ledgerUpgradeType, nomination, cfg)); + } + REQUIRE(!Upgrades{cfg}.isValid(checkTime, + toUpgradeType(makeTxCountUpgrade(0)), + ledgerUpgradeType, nomination, cfg)); + } - SECTION("valid reserve") - { - REQUIRE(canBeValid == - Upgrades{cfg}.isValid( + SECTION("reserve") + { + if (nomination) + { + REQUIRE(canBeValid == + Upgrades{cfg}.isValid( + checkTime, + toUpgradeType(makeBaseReserveUpgrade(100000000)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseReserveUpgrade(99999999)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseReserveUpgrade(100000001)), + ledgerUpgradeType, nomination, cfg)); + } + else + { + REQUIRE(Upgrades{cfg}.isValid( checkTime, toUpgradeType(makeBaseReserveUpgrade(100000000)), - ledgerUpgradeType)); - } - - SECTION("too small reserve") - { - REQUIRE(!Upgrades{cfg}.isValid( - checkTime, toUpgradeType(makeBaseReserveUpgrade(99999999)), - ledgerUpgradeType)); - } - - SECTION("too big reserve") - { - REQUIRE(!Upgrades{cfg}.isValid( - checkTime, toUpgradeType(makeBaseReserveUpgrade(100000001)), - ledgerUpgradeType)); - } + ledgerUpgradeType, nomination, cfg)); + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseReserveUpgrade(99999999)), + ledgerUpgradeType, nomination, cfg)); + REQUIRE(Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseReserveUpgrade(100000001)), + ledgerUpgradeType, nomination, cfg)); + } + REQUIRE(!Upgrades{cfg}.isValid( + checkTime, toUpgradeType(makeBaseReserveUpgrade(0)), + ledgerUpgradeType, nomination, cfg)); + } + }; + checkWith(true); + checkWith(false); } TEST_CASE("validate upgrades when no time set for upgrade", "[upgrades]") @@ -411,10 +463,11 @@ TEST_CASE("validate upgrades at upgrade time", "[upgrades]") testValidateUpgrades(genesis(0, 0), true); } -TEST_CASE("upgrade in LedgerCloseData changes herder values", "[upgrades]") +TEST_CASE("Ledger Manager applies upgrades properly", "[upgrades]") { VirtualClock clock; - auto app = Application::create(clock, getTestConfig(0)); + auto cfg = getTestConfig(0); + auto app = Application::create(clock, cfg); app->start(); auto const& lcl = app->getLedgerManager().getLastClosedLedgerHeader(); @@ -442,8 +495,9 @@ TEST_CASE("upgrade in LedgerCloseData changes herder values", "[upgrades]") SECTION("ledger version") { - REQUIRE(executeUpgrade(makeProtocolVersionUpgrade(4)) - .header.ledgerVersion == 4); + REQUIRE(executeUpgrade( + makeProtocolVersionUpgrade(cfg.LEDGER_PROTOCOL_VERSION)) + .header.ledgerVersion == cfg.LEDGER_PROTOCOL_VERSION); } SECTION("base fee") @@ -468,12 +522,13 @@ TEST_CASE("upgrade in LedgerCloseData changes herder values", "[upgrades]") SECTION("all") { auto header = - executeUpgrades({toUpgradeType(makeProtocolVersionUpgrade(4)), + executeUpgrades({toUpgradeType(makeProtocolVersionUpgrade( + cfg.LEDGER_PROTOCOL_VERSION)), toUpgradeType(makeBaseFeeUpgrade(1000)), toUpgradeType(makeTxCountUpgrade(1300)), toUpgradeType(makeBaseReserveUpgrade(1000))}) .header; - REQUIRE(header.ledgerVersion == 4); + REQUIRE(header.ledgerVersion == cfg.LEDGER_PROTOCOL_VERSION); REQUIRE(header.baseFee == 1000); REQUIRE(header.maxTxSetSize == 1300); REQUIRE(header.baseReserve == 1000); @@ -482,76 +537,91 @@ TEST_CASE("upgrade in LedgerCloseData changes herder values", "[upgrades]") TEST_CASE("simulate upgrades", "[herder][upgrades]") { + auto epoch = VirtualClock::from_time_t(0); // no upgrade is done auto noUpgrade = - LedgerUpgradeableData{LedgerManager::GENESIS_LEDGER_VERSION, + LedgerUpgradeableData(LedgerManager::GENESIS_LEDGER_VERSION, LedgerManager::GENESIS_LEDGER_BASE_FEE, LedgerManager::GENESIS_LEDGER_MAX_TX_SIZE, - LedgerManager::GENESIS_LEDGER_BASE_RESERVE}; + LedgerManager::GENESIS_LEDGER_BASE_RESERVE); // all values are upgraded auto upgrade = - LedgerUpgradeableData{LedgerManager::GENESIS_LEDGER_VERSION + 1, + LedgerUpgradeableData(Config::CURRENT_LEDGER_PROTOCOL_VERSION, LedgerManager::GENESIS_LEDGER_BASE_FEE + 1, LedgerManager::GENESIS_LEDGER_MAX_TX_SIZE + 1, - LedgerManager::GENESIS_LEDGER_BASE_RESERVE + 1}; + LedgerManager::GENESIS_LEDGER_BASE_RESERVE + 1); SECTION("0 of 3 vote - dont upgrade") { - auto nodes = std::vector{ - {noUpgrade, {}}, {noUpgrade, {}}, {noUpgrade, {}}}; + auto nodes = std::vector{{}, {}, {}}; auto checks = std::vector{ - {genesis(0, 30), {noUpgrade, noUpgrade, noUpgrade}}}; + {genesis(0, 10), {noUpgrade, noUpgrade, noUpgrade}}}; simulateUpgrade(nodes, checks); } SECTION("1 of 3 vote, dont upgrade") { - auto nodes = std::vector{ - {upgrade, {}}, {noUpgrade, {}}, {noUpgrade, {}}}; + auto nodes = + std::vector{{upgrade, genesis(0, 0)}, {}, {}}; auto checks = std::vector{ - {genesis(0, 30), {noUpgrade, noUpgrade, noUpgrade}}}; + {genesis(0, 10), {noUpgrade, noUpgrade, noUpgrade}}}; simulateUpgrade(nodes, checks, true); } - SECTION("2 of 3 vote - 2 upgrade, 1 after catchup") + SECTION("2 of 3 vote (v-blocking) - 3 upgrade") { auto nodes = std::vector{ - {upgrade, {}}, {upgrade, {}}, {noUpgrade, {}}}; + {upgrade, genesis(0, 0)}, {upgrade, genesis(0, 0)}, {}}; auto checks = std::vector{ - {genesis(0, 30), {upgrade, upgrade, noUpgrade}}}; - simulateUpgrade(nodes, checks, true, {upgrade, upgrade, upgrade}); + {genesis(0, 10), {upgrade, upgrade, upgrade}}}; + simulateUpgrade(nodes, checks); } SECTION("3 of 3 vote - upgrade") { - auto nodes = std::vector{{upgrade, genesis(1, 0)}, - {upgrade, genesis(1, 0)}, - {upgrade, genesis(1, 0)}}; + auto nodes = std::vector{{upgrade, genesis(0, 15)}, + {upgrade, genesis(0, 15)}, + {upgrade, genesis(0, 15)}}; auto checks = std::vector{ - {genesis(0, 30), {noUpgrade, noUpgrade, noUpgrade}}, - {genesis(1, 30), {upgrade, upgrade, upgrade}}}; + {genesis(0, 10), {noUpgrade, noUpgrade, noUpgrade}}, + {genesis(0, 21), {upgrade, upgrade, upgrade}}}; simulateUpgrade(nodes, checks); } - SECTION("1 of 3 vote early - 3 upgrade late") + SECTION("3 votes for bogus fee - all 3 upgrade but ignore bad fee") { - auto nodes = std::vector{{upgrade, genesis(0, 30)}, - {upgrade, genesis(1, 0)}, - {upgrade, genesis(1, 0)}}; + auto upgradeBadFee = upgrade; + upgradeBadFee.baseFee = 0; + auto expectedResult = upgradeBadFee; + expectedResult.baseFee = LedgerManager::GENESIS_LEDGER_BASE_FEE; + auto nodes = + std::vector{{upgradeBadFee, genesis(0, 0)}, + {upgradeBadFee, genesis(0, 0)}, + {upgradeBadFee, genesis(0, 0)}}; auto checks = std::vector{ - {genesis(0, 45), {noUpgrade, noUpgrade, noUpgrade}}, - {genesis(1, 15), {upgrade, upgrade, upgrade}}}; - simulateUpgrade(nodes, checks); + {genesis(0, 10), {expectedResult, expectedResult, expectedResult}}}; + simulateUpgrade(nodes, checks, true); } - SECTION("2 of 3 vote early - 2 upgrade early, 1 after catchup") + SECTION("1 of 3 vote early - 2 upgrade late") { - auto nodes = std::vector{{upgrade, genesis(0, 30)}, + auto nodes = std::vector{{upgrade, genesis(0, 10)}, {upgrade, genesis(0, 30)}, - {upgrade, genesis(1, 0)}}; + {upgrade, genesis(0, 30)}}; auto checks = std::vector{ - {genesis(0, 15), {noUpgrade, noUpgrade, noUpgrade}}, - {genesis(0, 45), {upgrade, upgrade, noUpgrade}}}; - simulateUpgrade(nodes, checks, false, {upgrade, upgrade, upgrade}); + {genesis(0, 20), {noUpgrade, noUpgrade, noUpgrade}}, + {genesis(0, 36), {upgrade, upgrade, upgrade}}}; + simulateUpgrade(nodes, checks); + } + + SECTION("2 of 3 vote early (v-blocking) - 3 upgrade anyways") + { + auto nodes = std::vector{{upgrade, genesis(0, 10)}, + {upgrade, genesis(0, 10)}, + {upgrade, genesis(0, 30)}}; + auto checks = std::vector{ + {genesis(0, 9), {noUpgrade, noUpgrade, noUpgrade}}, + {genesis(0, 20), {upgrade, upgrade, upgrade}}}; + simulateUpgrade(nodes, checks); } } diff --git a/src/ledger/LedgerManagerImpl.cpp b/src/ledger/LedgerManagerImpl.cpp index 521e07cd73..225ddd0cc8 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -207,9 +207,9 @@ LedgerManagerImpl::startNewLedger() if (cfg.USE_CONFIG_FOR_GENESIS) { ledger.ledgerVersion = cfg.LEDGER_PROTOCOL_VERSION; - ledger.baseFee = cfg.DESIRED_BASE_FEE; - ledger.baseReserve = cfg.DESIRED_BASE_RESERVE; - ledger.maxTxSetSize = cfg.DESIRED_MAX_TX_PER_LEDGER; + ledger.baseFee = cfg.TESTING_UPGRADE_DESIRED_FEE; + ledger.baseReserve = cfg.TESTING_UPGRADE_RESERVE; + ledger.maxTxSetSize = cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER; } startNewLedger(std::move(ledger)); diff --git a/src/ledger/LedgerPerformanceTests.cpp b/src/ledger/LedgerPerformanceTests.cpp index 5e40024369..7e80d04af2 100644 --- a/src/ledger/LedgerPerformanceTests.cpp +++ b/src/ledger/LedgerPerformanceTests.cpp @@ -122,7 +122,7 @@ class LedgerPerformanceTests : public Simulation void closeLedger(vector txs) { - auto baseFee = mApp->getConfig().DESIRED_BASE_FEE; + auto baseFee = mApp->getConfig().TESTING_UPGRADE_DESIRED_FEE; LoadGenerator::TxMetrics txm(mApp->getMetrics()); TxSetFramePtr txSet = make_shared( mApp->getLedgerManager().getLastClosedLedgerHeader().hash); diff --git a/src/main/ApplicationImpl.cpp b/src/main/ApplicationImpl.cpp index a65d25d8e6..17ed32169b 100644 --- a/src/main/ApplicationImpl.cpp +++ b/src/main/ApplicationImpl.cpp @@ -304,6 +304,11 @@ ApplicationImpl::start() { mDatabase->upgradeToCurrentSchema(); + if (mConfig.TESTING_UPGRADE_DATETIME.time_since_epoch().count() != 0) + { + mHerder->setUpgrades(mConfig); + } + if (mPersistentState->getState(PersistentState::kForceSCPOnNextLaunch) == "true") { @@ -339,8 +344,8 @@ ApplicationImpl::start() "Unable to restore last-known ledger state"); } - // restores the SCP state before starting overlay - mHerder->restoreSCPState(); + // restores Herder's state before starting overlay + mHerder->restoreState(); // perform maintenance tasks if configured to do so // for now, we only perform it when CATCHUP_COMPLETE is not set if (mConfig.MAINTENANCE_ON_STARTUP && !mConfig.CATCHUP_COMPLETE) diff --git a/src/main/CommandHandler.cpp b/src/main/CommandHandler.cpp index 0aa7417665..21a6f0490f 100644 --- a/src/main/CommandHandler.cpp +++ b/src/main/CommandHandler.cpp @@ -66,43 +66,79 @@ CommandHandler::CommandHandler(Application& app) : mApp(app) mServer->add404(std::bind(&CommandHandler::fileNotFound, this, _1, _2)); - mServer->addRoute("bans", std::bind(&CommandHandler::bans, this, _1, _2)); - mServer->addRoute("catchup", - std::bind(&CommandHandler::catchup, this, _1, _2)); - mServer->addRoute("checkdb", - std::bind(&CommandHandler::checkdb, this, _1, _2)); + mServer->addRoute("bans", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::bans, _1, _2)); + mServer->addRoute("catchup", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::catchup, _1, _2)); + mServer->addRoute("checkdb", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::checkdb, _1, _2)); mServer->addRoute("checkpoint", - std::bind(&CommandHandler::checkpoint, this, _1, _2)); - mServer->addRoute("connect", - std::bind(&CommandHandler::connect, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::checkpoint, _1, _2)); + mServer->addRoute("connect", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::connect, _1, _2)); mServer->addRoute("dropcursor", - std::bind(&CommandHandler::dropcursor, this, _1, _2)); - mServer->addRoute("droppeer", - std::bind(&CommandHandler::dropPeer, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::dropcursor, _1, _2)); + mServer->addRoute("droppeer", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::dropPeer, _1, _2)); mServer->addRoute("generateload", - std::bind(&CommandHandler::generateLoad, this, _1, _2)); - mServer->addRoute("info", std::bind(&CommandHandler::info, this, _1, _2)); - mServer->addRoute("ll", std::bind(&CommandHandler::ll, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::generateLoad, _1, _2)); + mServer->addRoute("info", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::info, _1, _2)); + mServer->addRoute("ll", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::ll, _1, _2)); mServer->addRoute("logrotate", - std::bind(&CommandHandler::logRotate, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::logRotate, _1, _2)); mServer->addRoute("maintenance", - std::bind(&CommandHandler::maintenance, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::maintenance, _1, _2)); mServer->addRoute("manualclose", - std::bind(&CommandHandler::manualClose, this, _1, _2)); - mServer->addRoute("metrics", - std::bind(&CommandHandler::metrics, this, _1, _2)); - mServer->addRoute("peers", std::bind(&CommandHandler::peers, this, _1, _2)); - mServer->addRoute("quorum", - std::bind(&CommandHandler::quorum, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::manualClose, _1, _2)); + mServer->addRoute("metrics", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::metrics, _1, _2)); + mServer->addRoute("peers", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::peers, _1, _2)); + mServer->addRoute("quorum", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::quorum, _1, _2)); mServer->addRoute("setcursor", - std::bind(&CommandHandler::setcursor, this, _1, _2)); - mServer->addRoute("scp", std::bind(&CommandHandler::scpInfo, this, _1, _2)); - mServer->addRoute("testacc", - std::bind(&CommandHandler::testAcc, this, _1, _2)); - mServer->addRoute("testtx", - std::bind(&CommandHandler::testTx, this, _1, _2)); - mServer->addRoute("tx", std::bind(&CommandHandler::tx, this, _1, _2)); - mServer->addRoute("unban", std::bind(&CommandHandler::unban, this, _1, _2)); + std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::setcursor, _1, _2)); + mServer->addRoute("scp", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::scpInfo, _1, _2)); + mServer->addRoute("testacc", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::testAcc, _1, _2)); + mServer->addRoute("testtx", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::testTx, _1, _2)); + mServer->addRoute("tx", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::tx, _1, _2)); + mServer->addRoute("upgrades", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::upgrades, _1, _2)); + mServer->addRoute("unban", std::bind(&CommandHandler::safeRouter, this, + &CommandHandler::unban, _1, _2)); +} + +void +CommandHandler::safeRouter(CommandHandler::HandlerRoute route, + std::string const& params, std::string& retStr) +{ + try + { + route(this, params, retStr); + } + catch (std::exception& e) + { + retStr = + (fmt::MemoryWriter() << "{\"exception\": \"" << e.what() << "\"}") + .str(); + } + catch (...) + { + retStr = "{\"exception\": \"generic\"}"; + } } void @@ -279,6 +315,25 @@ CommandHandler::fileNotFound(std::string const& params, std::string& retStr) "returns a JSON object
" "wasReceived: boolean, true if transaction was queued properly
" "result: base64 encoded, XDR serialized 'TransactionResult'
" + "

/upgrades?mode=(get|set|clear)&[upgradetime=DATETIME]&" + "[basefee=NUM]&[basereserve=NUM]&[maxtxsize=NUM]&[protocolversion=NUM]" + "

" + "gets, sets or clears upgrades.
" + "When mode=set, upgradetime is a required date in the ISO 8601 " + "date format (UTC) in the form 1970-01-01T00:00:00Z.
" + "fee (uint32) This is what you would prefer the base fee to be. It is " + "in stroops
" + "basereserve (uint32) This is what you would prefer the base reserve " + "to be. It is in stroops.
" + "maxtxsize (uint32) This defines the maximum number of transactions " + "to include in a ledger. When too many transactions are pending, " + "surge pricing is applied. The instance picks the top maxtxsize" + " transactions locally to be considered in the next ledger.Where " + "transactions are ordered by transaction fee(lower fee transactions" + " are held for later).
" + "protocolversion (uint32) defines the protocol version to upgrade to." + " When specified it must match the protocol version supported by the" + " node
" "

/dropcursor?id=XYZ

deletes the tracking cursor with " "identified by `id`. See `setcursor` for more information" "

/setcursor?id=ID&cursor=N

sets or creates a cursor " @@ -331,8 +386,9 @@ template bool parseNumParam(std::map const& map, std::string const& key, T& val, std::string& retStr, - Requirement requirement) + Requirement requirement, bool& valueUpdated) { + valueUpdated = false; auto i = map.find(key); if (i != map.end()) { @@ -343,11 +399,22 @@ parseNumParam(std::map const& map, retStr = fmt::format("Failed to parse '{}' argument", key); return false; } + valueUpdated = true; return true; } return requirement == Requirement::OPTIONAL_REQ; } +template +bool +parseNumParam(std::map const& map, + std::string const& key, T& val, std::string& retStr, + Requirement requirement) +{ + bool valueUpdated; + return parseNumParam(map, key, val, retStr, requirement, valueUpdated); +} + void CommandHandler::generateLoad(std::string const& params, std::string& retStr) { @@ -679,44 +746,104 @@ CommandHandler::unban(std::string const& params, std::string& retStr) } void -CommandHandler::quorum(std::string const& params, std::string& retStr) +CommandHandler::upgrades(std::string const& params, std::string& retStr) { - Json::Value root; std::map retMap; http::server::server::parseParams(params, retMap); - - NodeID n; - - try + auto s = retMap["mode"]; + if (s.empty()) + { + retStr = "mode required"; + return; + } + if (s == "get") + { + retStr = mApp.getHerder().getUpgradesJson(); + } + else if (s == "set") { - std::string nID = retMap["node"]; + Upgrades::UpgradeParameters p; - if (nID.empty()) + auto upgradeTime = retMap["upgradetime"]; + std::tm tm; + try { - n = mApp.getConfig().NODE_SEED.getPublicKey(); + tm = VirtualClock::isoStringToTm(upgradeTime); } - else + catch (std::exception) { - if (!mApp.getHerder().resolveNodeID(nID, n)) + retStr = + fmt::format("could not parse upgradetime: '{}'", upgradeTime); + return; + } + p.mUpgradeTime = VirtualClock::tmToPoint(tm); + + auto addParam = [&](std::string const& name, + stellar::optional& f) { + uint32 v; + bool updated; + if (!parseNumParam(retMap, name, v, retStr, + Requirement::OPTIONAL_REQ, updated)) { - throw std::invalid_argument("unknown name"); + retStr = (fmt::MemoryWriter() + << fmt::format("could not parse {}: '{}'\n", name, + retMap[name]) + << retStr) + .str(); } - } - - mApp.getHerder().dumpQuorumInfo(root, n, retMap["compact"] == "true"); + else if (updated) + { + f = stellar::make_optional(v); + } + else + { + f.reset(); + } + }; + addParam("basefee", p.mBaseFee); + addParam("basereserve", p.mBaseReserve); + addParam("maxtxsize", p.mMaxTxSize); + addParam("protocolversion", p.mProtocolVersion); - retStr = root.toStyledString(); + mApp.getHerder().setUpgrades(p); } - catch (std::exception& e) + else if (s == "clear") { - retStr = - (fmt::MemoryWriter() << "{\"exception\": \"" << e.what() << "\"}") - .str(); + Upgrades::UpgradeParameters p; + mApp.getHerder().setUpgrades(p); } - catch (...) + else { - retStr = "{\"exception\": \"generic\"}"; + retStr = fmt::format("Unknown mode: {}", s); + } +} + +void +CommandHandler::quorum(std::string const& params, std::string& retStr) +{ + Json::Value root; + std::map retMap; + http::server::server::parseParams(params, retMap); + + NodeID n; + + std::string nID = retMap["node"]; + + if (nID.empty()) + { + n = mApp.getConfig().NODE_SEED.getPublicKey(); } + else + { + if (!mApp.getHerder().resolveNodeID(nID, n)) + { + throw std::invalid_argument("unknown name"); + } + } + + mApp.getHerder().dumpQuorumInfo(root, n, retMap["compact"] == "true"); + + retStr = root.toStyledString(); } void @@ -804,61 +931,48 @@ CommandHandler::tx(std::string const& params, std::string& retStr) if (params.compare(0, prefix.size(), prefix) == 0) { TransactionEnvelope envelope; - try + std::string blob = params.substr(prefix.size()); + std::vector binBlob; + bn::decode_b64(blob, binBlob); + + xdr::xdr_from_opaque(binBlob, envelope); + TransactionFramePtr transaction = + TransactionFrame::makeTransactionFromWire(mApp.getNetworkID(), + envelope); + if (transaction) { - std::string blob = params.substr(prefix.size()); - std::vector binBlob; - bn::decode_b64(blob, binBlob); - - xdr::xdr_from_opaque(binBlob, envelope); - TransactionFramePtr transaction = - TransactionFrame::makeTransactionFromWire(mApp.getNetworkID(), - envelope); - if (transaction) + // add it to our current set + // and make sure it is valid + Herder::TransactionSubmitStatus status = + mApp.getHerder().recvTransaction(transaction); + + if (status == Herder::TX_STATUS_PENDING) { - // add it to our current set - // and make sure it is valid - Herder::TransactionSubmitStatus status = - mApp.getHerder().recvTransaction(transaction); + StellarMessage msg; + msg.type(TRANSACTION); + msg.transaction() = envelope; + mApp.getOverlayManager().broadcastMessage(msg); + } - if (status == Herder::TX_STATUS_PENDING) - { - StellarMessage msg; - msg.type(TRANSACTION); - msg.transaction() = envelope; - mApp.getOverlayManager().broadcastMessage(msg); - } + output << "{" + << "\"status\": " + << "\"" << TX_STATUS_STRING[status] << "\""; + if (status == Herder::TX_STATUS_ERROR) + { + std::string resultBase64; + auto resultBin = xdr::xdr_to_opaque(transaction->getResult()); + resultBase64.reserve(bn::encoded_size64(resultBin.size()) + 1); + resultBase64 = bn::encode_b64(resultBin); - output << "{" - << "\"status\": " - << "\"" << TX_STATUS_STRING[status] << "\""; - if (status == Herder::TX_STATUS_ERROR) - { - std::string resultBase64; - auto resultBin = - xdr::xdr_to_opaque(transaction->getResult()); - resultBase64.reserve(bn::encoded_size64(resultBin.size()) + - 1); - resultBase64 = bn::encode_b64(resultBin); - - output << " , \"error\": \"" << resultBase64 << "\""; - } - output << "}"; + output << " , \"error\": \"" << resultBase64 << "\""; } - } - catch (std::exception& e) - { - output << "{\"exception\": \"" << e.what() << "\"}"; - } - catch (...) - { - output << "{\"exception\": \"generic\"}"; + output << "}"; } } else { - output << "{\"exception\": \"Must specify a tx blob: tx?blob=\"}"; + throw std::invalid_argument("Must specify a tx blob: tx?blob=\"}"); } retStr = output.str(); diff --git a/src/main/CommandHandler.h b/src/main/CommandHandler.h index f50391ed02..380886e733 100644 --- a/src/main/CommandHandler.h +++ b/src/main/CommandHandler.h @@ -17,10 +17,16 @@ class Application; class CommandHandler { + typedef std::function + HandlerRoute; Application& mApp; std::unique_ptr mServer; + void safeRouter(HandlerRoute route, std::string const& params, + std::string& retStr); + public: CommandHandler(Application& app); @@ -50,5 +56,6 @@ class CommandHandler void testAcc(std::string const& params, std::string& retStr); void testTx(std::string const& params, std::string& retStr); void unban(std::string const& params, std::string& retStr); + void upgrades(std::string const& params, std::string& retStr); }; } diff --git a/src/main/Config.cpp b/src/main/Config.cpp index d8e972acac..0b95e0a56f 100644 --- a/src/main/Config.cpp +++ b/src/main/Config.cpp @@ -20,7 +20,7 @@ namespace stellar { using xdr::operator<; -const int Config::CURRENT_LEDGER_PROTOCOL_VERSION = 9; +const uint32 Config::CURRENT_LEDGER_PROTOCOL_VERSION = 9; Config::Config() : NODE_SEED(SecretKey::random()) { @@ -53,9 +53,9 @@ Config::Config() : NODE_SEED(SecretKey::random()) LOG_FILE_PATH = "stellar-core.%datetime{%Y.%M.%d-%H:%m:%s}.log"; BUCKET_DIR_PATH = "buckets"; - DESIRED_BASE_FEE = LedgerManager::GENESIS_LEDGER_BASE_FEE; - DESIRED_BASE_RESERVE = LedgerManager::GENESIS_LEDGER_BASE_RESERVE; - DESIRED_MAX_TX_PER_LEDGER = 50; + TESTING_UPGRADE_DESIRED_FEE = LedgerManager::GENESIS_LEDGER_BASE_FEE; + TESTING_UPGRADE_RESERVE = LedgerManager::GENESIS_LEDGER_BASE_RESERVE; + TESTING_UPGRADE_MAX_TX_PER_LEDGER = 50; HTTP_PORT = DEFAULT_PEER_PORT + 1; PUBLIC_HTTP_PORT = false; @@ -226,47 +226,6 @@ Config::load(std::string const& filename) } PUBLIC_HTTP_PORT = item.second->as()->value(); } - else if (item.first == "DESIRED_BASE_FEE") - { - if (!item.second->as()) - { - throw std::invalid_argument("invalid DESIRED_BASE_FEE"); - } - int64_t f = item.second->as()->value(); - if (f < 0 || f >= UINT32_MAX) - { - throw std::invalid_argument("invalid DESIRED_BASE_FEE"); - } - DESIRED_BASE_FEE = (uint32_t)f; - } - else if (item.first == "DESIRED_BASE_RESERVE") - { - if (!item.second->as()) - { - throw std::invalid_argument("invalid DESIRED_BASE_RESERVE"); - } - int64_t f = item.second->as()->value(); - if (f < 0 || f >= UINT32_MAX) - { - throw std::invalid_argument("invalid DESIRED_BASE_RESERVE"); - } - DESIRED_BASE_RESERVE = (uint32_t)f; - } - else if (item.first == "DESIRED_MAX_TX_PER_LEDGER") - { - if (!item.second->as()) - { - throw std::invalid_argument( - "invalid DESIRED_MAX_TX_PER_LEDGER"); - } - int64_t f = item.second->as()->value(); - if (f <= 0 || f >= UINT32_MAX) - { - throw std::invalid_argument( - "invalid DESIRED_MAX_TX_PER_LEDGER"); - } - DESIRED_MAX_TX_PER_LEDGER = (uint32_t)f; - } else if (item.first == "FAILURE_SAFETY") { if (!item.second->as()) @@ -666,16 +625,6 @@ Config::load(std::string const& filename) } NTP_SERVER = item.second->as()->value(); } - else if (item.first == "PREFERRED_UPGRADE_DATETIME") - { - if (!item.second->as()) - { - throw std::invalid_argument( - "invalid PREFERRED_UPGRADE_DATETIME"); - } - PREFERRED_UPGRADE_DATETIME = VirtualClock::tmToPoint( - item.second->as()->value()); - } else if (item.first == "INVARIANT_CHECKS") { if (!item.second->is_array()) diff --git a/src/main/Config.h b/src/main/Config.h index 39e1bcbf7e..ffc4266a93 100644 --- a/src/main/Config.h +++ b/src/main/Config.h @@ -32,7 +32,7 @@ class Config : public std::enable_shared_from_this std::string expandNodeID(std::string const& s) const; public: - static const int CURRENT_LEDGER_PROTOCOL_VERSION; + static const uint32 CURRENT_LEDGER_PROTOCOL_VERSION; typedef std::shared_ptr pointer; @@ -123,7 +123,7 @@ class Config : public std::enable_shared_from_this bool UNSAFE_QUORUM; uint32_t LEDGER_PROTOCOL_VERSION; - VirtualClock::time_point PREFERRED_UPGRADE_DATETIME; + VirtualClock::time_point TESTING_UPGRADE_DATETIME; // note: all versions in the range // [OVERLAY_PROTOCOL_MIN_VERSION, OVERLAY_PROTOCOL_VERSION] must be handled @@ -132,9 +132,9 @@ class Config : public std::enable_shared_from_this std::string VERSION_STR; std::string LOG_FILE_PATH; std::string BUCKET_DIR_PATH; - uint32_t DESIRED_BASE_FEE; // in stroops - uint32_t DESIRED_BASE_RESERVE; // in stroops - uint32_t DESIRED_MAX_TX_PER_LEDGER; + uint32_t TESTING_UPGRADE_DESIRED_FEE; // in stroops + uint32_t TESTING_UPGRADE_RESERVE; // in stroops + uint32_t TESTING_UPGRADE_MAX_TX_PER_LEDGER; unsigned short HTTP_PORT; // what port to listen for commands bool PUBLIC_HTTP_PORT; // if you accept commands from not localhost int HTTP_MAX_CLIENT; // maximum number of http clients, i.e backlog diff --git a/src/main/PersistentState.cpp b/src/main/PersistentState.cpp index 0a7fd45149..0fa390df9c 100644 --- a/src/main/PersistentState.cpp +++ b/src/main/PersistentState.cpp @@ -14,7 +14,8 @@ using namespace std; string PersistentState::mapping[kLastEntry] = { "lastclosedledger", "historyarchivestate", "forcescponnextlaunch", - "lastscpdata", "databaseschema", "networkpassphrase"}; + "lastscpdata", "databaseschema", "networkpassphrase", + "ledgerupgrades"}; string PersistentState::kSQLCreateStatement = "CREATE TABLE IF NOT EXISTS storestate (" diff --git a/src/main/PersistentState.h b/src/main/PersistentState.h index 66a040aecc..5f4127d1cb 100644 --- a/src/main/PersistentState.h +++ b/src/main/PersistentState.h @@ -23,6 +23,7 @@ class PersistentState kLastSCPData, kDatabaseSchema, kNetworkPassphrase, + kLedgerUpgrades, kLastEntry, }; diff --git a/src/scp/BallotProtocol.cpp b/src/scp/BallotProtocol.cpp index 5c92205d90..2e0d8f645b 100644 --- a/src/scp/BallotProtocol.cpp +++ b/src/scp/BallotProtocol.cpp @@ -1904,7 +1904,8 @@ BallotProtocol::validateValues(SCPStatement const& st) SCPDriver::ValidationLevel res = SCPDriver::kFullyValidatedValue; for (auto const& v : values) { - auto tr = mSlot.getSCPDriver().validateValue(mSlot.getSlotIndex(), v); + auto tr = + mSlot.getSCPDriver().validateValue(mSlot.getSlotIndex(), v, false); if (tr != SCPDriver::kFullyValidatedValue) { if (tr == SCPDriver::kInvalidValue) diff --git a/src/scp/NominationProtocol.cpp b/src/scp/NominationProtocol.cpp index a3243a5a1c..7dec031f8c 100644 --- a/src/scp/NominationProtocol.cpp +++ b/src/scp/NominationProtocol.cpp @@ -74,7 +74,7 @@ NominationProtocol::isSubsetHelper(xdr::xvector const& p, SCPDriver::ValidationLevel NominationProtocol::validateValue(Value const& v) { - return mSlot.getSCPDriver().validateValue(mSlot.getSlotIndex(), v); + return mSlot.getSCPDriver().validateValue(mSlot.getSlotIndex(), v, true); } Value diff --git a/src/scp/SCPDriver.h b/src/scp/SCPDriver.h index 6baa9c835b..fa37379fc5 100644 --- a/src/scp/SCPDriver.h +++ b/src/scp/SCPDriver.h @@ -51,6 +51,7 @@ class SCPDriver // the validity checks, kMaybeValidValue can be returned. This will cause // the current slot to be marked as a non validating slot: the local node // will abstain from emiting its position. + // validation can be *more* restrictive during nomination as needed enum ValidationLevel { kInvalidValue, // value is invalid for sure @@ -58,7 +59,7 @@ class SCPDriver kMaybeValidValue // value may be valid }; virtual ValidationLevel - validateValue(uint64 slotIndex, Value const& value) + validateValue(uint64 slotIndex, Value const& value, bool nomination) { return kMaybeValidValue; } diff --git a/src/scp/SCPTests.cpp b/src/scp/SCPTests.cpp index aea606ef94..1a677428c9 100644 --- a/src/scp/SCPTests.cpp +++ b/src/scp/SCPTests.cpp @@ -68,7 +68,8 @@ class TestSCP : public SCPDriver } SCPDriver::ValidationLevel - validateValue(uint64 slotIndex, Value const& value) override + validateValue(uint64 slotIndex, Value const& value, + bool nomination) override { return SCPDriver::kFullyValidatedValue; } diff --git a/src/simulation/CoreTests.cpp b/src/simulation/CoreTests.cpp index 1d71ebcced..4d6c71848d 100644 --- a/src/simulation/CoreTests.cpp +++ b/src/simulation/CoreTests.cpp @@ -394,13 +394,13 @@ newLoadTestApp(VirtualClock& clock) #endif getTestConfig(0, Config::TESTDB_ON_DISK_SQLITE); cfg.RUN_STANDALONE = false; - cfg.DESIRED_MAX_TX_PER_LEDGER = 10000; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER = 10000; Application::pointer appPtr = Application::create(clock, cfg); appPtr->start(); // force maxTxSetSize to avoid throwing txSets on the floor during the first // ledger close appPtr->getLedgerManager().getCurrentLedgerHeader().maxTxSetSize = - cfg.DESIRED_MAX_TX_PER_LEDGER; + cfg.TESTING_UPGRADE_MAX_TX_PER_LEDGER; return appPtr; } diff --git a/src/simulation/LoadGenerator.cpp b/src/simulation/LoadGenerator.cpp index ca7d5acdfc..cee1ad7aa7 100644 --- a/src/simulation/LoadGenerator.cpp +++ b/src/simulation/LoadGenerator.cpp @@ -919,7 +919,7 @@ LoadGenerator::TxInfo::execute(Application& app) return false; } } - recordExecution(app.getConfig().DESIRED_BASE_FEE); + recordExecution(app.getConfig().TESTING_UPGRADE_DESIRED_FEE); return true; } diff --git a/src/test/test.cpp b/src/test/test.cpp index a49f122d10..0ef0c97fed 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -89,6 +89,9 @@ getTestConfig(int instanceNumber, Config::TestDbMode mode) thisConfig.ALLOW_LOCALHOST_FOR_TESTING = true; + // this forces to pick up any other potential upgrades + thisConfig.TESTING_UPGRADE_DATETIME = VirtualClock::from_time_t(1); + // Tests are run in standalone by default, meaning that no external // listening interfaces are opened (all sockets must be manually created // and connected loopback sockets), no external connections are @@ -185,20 +188,20 @@ test(int argc, char* const* argv, el::Level ll, } void -for_versions_to(int to, Application& app, std::function const& f) +for_versions_to(uint32 to, Application& app, std::function const& f) { for_versions(1, to, app, f); } void -for_versions_from(int from, Application& app, +for_versions_from(uint32 from, Application& app, std::function const& f) { for_versions(from, Config::CURRENT_LEDGER_PROTOCOL_VERSION, app, f); } void -for_versions_from(std::vector const& versions, Application& app, +for_versions_from(std::vector const& versions, Application& app, std::function const& f) { for_versions(versions, app, f); @@ -212,14 +215,14 @@ for_all_versions(Application& app, std::function const& f) } void -for_versions(int from, int to, Application& app, +for_versions(uint32 from, uint32 to, Application& app, std::function const& f) { if (from > to) { return; } - auto versions = std::vector{}; + auto versions = std::vector{}; versions.resize(to - from + 1); std::iota(std::begin(versions), std::end(versions), from); @@ -227,7 +230,7 @@ for_versions(int from, int to, Application& app, } void -for_versions(std::vector const& versions, Application& app, +for_versions(std::vector const& versions, Application& app, std::function const& f) { auto previousVersion = app.getLedgerManager().getCurrentLedgerVersion(); @@ -247,11 +250,11 @@ for_versions(std::vector const& versions, Application& app, } void -for_all_versions_except(std::vector const& versions, Application& app, +for_all_versions_except(std::vector const& versions, Application& app, std::function const& f) { - int lastExcept = 0; - for (int except : versions) + uint32 lastExcept = 0; + for (uint32 except : versions) { for_versions(lastExcept + 1, except - 1, app, f); lastExcept = except; diff --git a/src/test/test.h b/src/test/test.h index 9b8e02b1a9..74e860507c 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -21,23 +21,24 @@ int test(int argc, char* const* argv, el::Level logLevel, extern bool force_sqlite; -void for_versions_to(int to, Application& app, +void for_versions_to(uint32 to, Application& app, std::function const& f); -void for_versions_from(int from, Application& app, +void for_versions_from(uint32 from, Application& app, std::function const& f); -void for_versions_from(std::vector const& versions, Application& app, +void for_versions_from(std::vector const& versions, Application& app, std::function const& f); void for_all_versions(Application& app, std::function const& f); -void for_versions(int from, int to, Application& app, +void for_versions(uint32 from, uint32 to, Application& app, std::function const& f); -void for_versions(std::vector const& versions, Application& app, +void for_versions(std::vector const& versions, Application& app, std::function const& f); -void for_all_versions_except(std::vector const& versions, Application& app, +void for_all_versions_except(std::vector const& versions, + Application& app, std::function const& f); } diff --git a/src/transactions/PaymentTests.cpp b/src/transactions/PaymentTests.cpp index 0bdcdc6462..34fe3b2c45 100644 --- a/src/transactions/PaymentTests.cpp +++ b/src/transactions/PaymentTests.cpp @@ -1723,7 +1723,7 @@ TEST_CASE("payment fees", "[tx][payment]") SECTION("fee equal to base reserve") { auto cfg = getTestConfig(1); - cfg.DESIRED_BASE_FEE = 100000000; + cfg.TESTING_UPGRADE_DESIRED_FEE = 100000000; VirtualClock clock; auto app = createTestApplication(clock, cfg); @@ -1833,7 +1833,7 @@ TEST_CASE("payment fees", "[tx][payment]") SECTION("fee bigger than base reserve") { auto cfg = getTestConfig(1); - cfg.DESIRED_BASE_FEE = 200000000; + cfg.TESTING_UPGRADE_DESIRED_FEE = 200000000; VirtualClock clock; auto app = createTestApplication(clock, cfg); diff --git a/src/util/Timer.cpp b/src/util/Timer.cpp index 401080dd1b..f85bb3c987 100644 --- a/src/util/Timer.cpp +++ b/src/util/Timer.cpp @@ -7,6 +7,7 @@ #include "util/GlobalChecks.h" #include "util/Logging.h" #include +#include #include namespace stellar @@ -128,6 +129,28 @@ VirtualClock::tmToPoint(tm t) return VirtualClock::time_point() + std::chrono::seconds(tt); } +std::tm +VirtualClock::isoStringToTm(std::string const& iso) +{ + std::tm res; + int y, M, d, h, m, s; + if (std::sscanf(iso.c_str(), "%d-%d-%dT%d:%d:%dZ", &y, &M, &d, &h, &m, + &s) != 6) + { + throw std::invalid_argument("Could not parse iso date"); + } + res.tm_year = y - 1900; + res.tm_mon = M - 1; + res.tm_mday = d; + res.tm_hour = h; + res.tm_min = m; + res.tm_sec = s; + res.tm_isdst = 0; + res.tm_wday = 0; + res.tm_yday = 0; + return res; +} + std::string VirtualClock::tmToISOString(std::tm const& tm) { diff --git a/src/util/Timer.h b/src/util/Timer.h index 58d23abf5c..e1b89bc499 100644 --- a/src/util/Timer.h +++ b/src/util/Timer.h @@ -92,6 +92,7 @@ class VirtualClock static std::tm pointToTm(time_point); static VirtualClock::time_point tmToPoint(tm t); + static std::tm isoStringToTm(std::string const& iso); static std::string tmToISOString(std::tm const& tm); static std::string pointToISOString(time_point point); diff --git a/src/util/optional.h b/src/util/optional.h index 2669d03adf..4adbb8430e 100644 --- a/src/util/optional.h +++ b/src/util/optional.h @@ -4,6 +4,9 @@ // under the Apache License, Version 2.0. See the COPYING file at the root // of this distribution or at http://www.apache.org/licenses/LICENSE-2.0 +#include "cereal/cereal.hpp" +#include + namespace stellar { template using optional = std::shared_ptr; @@ -22,3 +25,32 @@ nullopt() return std::shared_ptr(); }; } + +namespace cereal +{ +template +void +save(Archive& ar, stellar::optional const& opt) +{ + ar(make_nvp("has", !!opt)); + if (opt) + { + ar(make_nvp("val", *opt)); + } +} + +template +void +load(Archive& ar, stellar::optional& o) +{ + bool isSet; + o.reset(); + ar(make_nvp("has", isSet)); + if (isSet) + { + T v; + ar(make_nvp("val", v)); + o = stellar::make_optional(v); + } +} +} // namespace cereal