Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/bitcoind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ static bool ParseArgs(ArgsManager& args, int argc, char* argv[])
}

g_software_expiry = args.GetIntArg("-softwareexpiry", DEFAULT_SOFTWARE_EXPIRY);
if (IsThisSoftwareExpired(GetTime())) {
tfm::format(std::cerr, "This software is expired, and may be out of consensus. You must choose to upgrade or override this expiration.\n");
if (IsThisSoftwareExpiringSoon(GetTime())) {
tfm::format(std::cerr, "Warning: This software expires soon, and may fall out of consensus. You must choose to upgrade, or override this expiration with the 'softwareexpiry' option (Unix datetime, or 0 for no expiry).\n");
} else if (IsThisSoftwareExpired(GetTime())) {
tfm::format(std::cerr, "This software is expired, and may be out of consensus. You must choose to upgrade, or override this expiration with the 'softwareexpiry' option (Unix datetime, or 0 for no expiry).\n");
exit(EXIT_FAILURE);
}

Expand Down
9 changes: 9 additions & 0 deletions src/clientversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,12 @@ bool IsThisSoftwareExpired(int64_t nTime)
}
return (nTime > g_software_expiry);
}

bool IsThisSoftwareExpiringSoon(int64_t ntime)
{
if (g_software_expiry <= 0) {
return false;
}

return (ntime <= g_software_expiry && ntime > g_software_expiry - SOFTWARE_EXPIRY_WARN_PERIOD);
}
5 changes: 5 additions & 0 deletions src/clientversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ static constexpr int64_t SECONDS_PER_YEAR = 31558060;
static constexpr int POSIX_EPOCH_YEAR = 1970;
static constexpr int64_t DEFAULT_SOFTWARE_EXPIRY_OFFSET = 26784000; // Around Nov 7
static constexpr int64_t DEFAULT_SOFTWARE_EXPIRY = ((COPYRIGHT_YEAR - POSIX_EPOCH_YEAR) * SECONDS_PER_YEAR) + (SECONDS_PER_YEAR * 2) + DEFAULT_SOFTWARE_EXPIRY_OFFSET;

static constexpr int64_t SECONDS_PER_WEEK = 604800;
static constexpr int64_t SOFTWARE_EXPIRY_WARN_PERIOD = SECONDS_PER_WEEK * 4;

extern int64_t g_software_expiry;

bool IsThisSoftwareExpired(int64_t nTime);
bool IsThisSoftwareExpiringSoon(int64_t nTime);

#endif // WINDRES_PREPROC

Expand Down
21 changes: 18 additions & 3 deletions src/qt/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

#include <QApplication>
#include <QDebug>
#include <QInputDialog>
#include <QLatin1String>
#include <QLibraryInfo>
#include <QLocale>
Expand Down Expand Up @@ -654,9 +655,23 @@ int GuiMain(int argc, char* argv[])
initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);

g_software_expiry = gArgs.GetIntArg("-softwareexpiry", DEFAULT_SOFTWARE_EXPIRY);
if (IsThisSoftwareExpired(GetTime())) {
QMessageBox::critical(nullptr, QObject::tr("Software expired"), QObject::tr("This software is expired, and may be out of consensus. You must choose to upgrade or override this expiration."));
return EXIT_FAILURE;
if (IsThisSoftwareExpiringSoon(GetTime())) {
QMessageBox::warning(nullptr, QObject::tr("Software expires soon"), QObject::tr("This software expires soon, and may fall out of consensus. You must choose to upgrade, or override this expiration with the 'softwareexpiry' option (Unix datetime, or 0 for no expiry)."));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably shouldn't pause startup waiting for a response

} else if (IsThisSoftwareExpired(GetTime())) {
bool override_expiry_ok;
QString override_expiry = QInputDialog::getText(
nullptr,
QObject::tr("Software Expired"),
QObject::tr("This software is expired, and may be out of consensus.\nYou must choose to upgrade, or override this expiration by typing:\nI accept this software may be unsafe."),
QLineEdit::Normal,
"",
&override_expiry_ok);
if (!override_expiry_ok || override_expiry != QObject::tr("I accept this software may be unsafe.")) {
return EXIT_FAILURE;
}

gArgs.ModifyRWConfigFile("softwareexpiry", "0");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rwconf is kind of deprecated at this point (superceded by settings.json)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ModifyRWConfigFile function has a default-true arg for also_settings_json. I've confirmed that this actually updates the settings.json file (I've been launching with -softwareexpiry=X for testing purposes).

void ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, bool also_settings_json = true);

void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, const bool also_settings_json)

m_settings.rw_settings[setting_change.first] = setting_change.second;

void ArgsManager::ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, const bool also_settings_json)

Is there some other approach we want to take, or all good?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably shouldn't persist forever, and should reset to default if the user upgrades?

Copy link
Author

@pithosian pithosian Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we want to approach that?

I think if we don't introduce an update detection mechanism, we have to go with 0. If we set eg 2 years in the future, then in a year the user updates to a brand new version, that version would end up erroneously expiring in 1 year.

The approach which springs to mind is adding an additional config value, like uiexpiryoverrideversion, and setting it to the current version when overriding software expiry. Then on launch we could check whether the uiexpiryoverrideversion is the currently running version (and not an empty string) and, if not, clear uiexpiryoverrideversion and softwareexpiry in the rwconf/settings.json. If the user manually configured softwareexpiry in their bitcoin.conf, it would be untouched.

We could also generalize this with just a lastexecutedversion value instead, always set to the current version, and have some handler for when we detect a change.

g_software_expiry = 0;
}

#ifdef ENABLE_WALLET
Expand Down
3 changes: 2 additions & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4458,12 +4458,13 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
if (IsThisSoftwareExpired(block.nTime)) {
// Wait an extra day before we start rejecting blocks
CBlockIndex const *blockindex_old = pindexPrev;
for (int i = 0; i < 144; ++i) {
for (int i = 0; i < std::min(144, pindexPrev->nHeight); ++i) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it should be

Suggested change
for (int i = 0; i < std::min(144, pindexPrev->nHeight); ++i) {
for (int i = 0; i < std::min(144, pindexPrev->nHeight - 1); ++i) {

?

But to avoid re-calculating it every iteration, better to offset the starting index.

assert(blockindex_old);
blockindex_old = blockindex_old->pprev;
}
assert(blockindex_old);
if (IsThisSoftwareExpired(blockindex_old->GetMedianTimePast())) {
chainman.GetNotifications().fatalError(_("This software is expired, and may be out of consensus. You must choose to upgrade, or override this expiration with the 'softwareexpiry' option (Unix datetime, or 0 for no expiry)."));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use warningSet so it shows up in RPC, -alertnotify, etc?

return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "node-expired", "node software has expired");
}
}
Expand Down