// Copyright (c) 2012-2015, The CryptoNote developers, The Bytecoin developers
//
// This file is part of Bytecoin.
//
// Bytecoin is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Bytecoin is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Bytecoin. If not, see .
#pragma once
#include
#include
#include
#include "cryptonote_core/Currency.h"
#include "cryptonote_config.h"
#include
namespace CryptoNote {
class UpgradeDetectorBase {
public:
enum : uint64_t {
UNDEF_HEIGHT = static_cast(-1),
};
};
static_assert(CryptoNote::UpgradeDetectorBase::UNDEF_HEIGHT == UINT64_C(0xFFFFFFFFFFFFFFFF), "UpgradeDetectorBase::UNDEF_HEIGHT has invalid value");
template
class BasicUpgradeDetector : public UpgradeDetectorBase {
public:
BasicUpgradeDetector(const Currency& currency, BC& blockchain, uint8_t targetVersion, Logging::ILogger& log) :
m_currency(currency),
m_blockchain(blockchain),
m_targetVersion(targetVersion),
m_votingCompleteHeight(UNDEF_HEIGHT),
logger(log, "upgrade") { }
bool init() {
if (m_currency.upgradeHeight() == UNDEF_HEIGHT) {
if (m_blockchain.empty()) {
m_votingCompleteHeight = UNDEF_HEIGHT;
} else if (m_targetVersion - 1 == m_blockchain.back().bl.majorVersion) {
m_votingCompleteHeight = findVotingCompleteHeight(m_blockchain.size() - 1);
} else if (m_targetVersion <= m_blockchain.back().bl.majorVersion) {
auto it = std::lower_bound(m_blockchain.begin(), m_blockchain.end(), m_targetVersion,
[](const typename BC::value_type& b, uint8_t v) { return b.bl.majorVersion < v; });
if (!(it != m_blockchain.end() && it->bl.majorVersion == m_targetVersion)) { logger(Logging::ERROR, Logging::BRIGHT_RED) << "Internal error: upgrade height isn't found"; return false; }
uint64_t upgradeHeight = it - m_blockchain.begin();
m_votingCompleteHeight = findVotingCompleteHeight(upgradeHeight);
if (!(m_votingCompleteHeight != UNDEF_HEIGHT)) { logger(Logging::ERROR, Logging::BRIGHT_RED) << "Internal error: voting complete height isn't found, upgrade height = " << upgradeHeight; return false; }
} else {
m_votingCompleteHeight = UNDEF_HEIGHT;
}
} else if (!m_blockchain.empty()) {
if (m_blockchain.size() <= m_currency.upgradeHeight() + 1) {
if (!(m_blockchain.back().bl.majorVersion == m_targetVersion - 1)) { logger(Logging::ERROR, Logging::BRIGHT_RED) << "Internal error: block at height " << (m_blockchain.size() - 1) << " has invalid version " <<
static_cast(m_blockchain.back().bl.majorVersion) << ", expected " << static_cast(m_targetVersion); return false; }
} else {
int blockVersionAtUpgradeHeight = m_blockchain[m_currency.upgradeHeight()].bl.majorVersion;
if (!(blockVersionAtUpgradeHeight == m_targetVersion - 1)) { logger(Logging::ERROR, Logging::BRIGHT_RED) << "Internal error: block at height " << m_currency.upgradeHeight() << " has invalid version " <<
blockVersionAtUpgradeHeight << ", expected " << static_cast(m_targetVersion - 1); return false; }
int blockVersionAfterUpgradeHeight = m_blockchain[m_currency.upgradeHeight() + 1].bl.majorVersion;
if (!(blockVersionAfterUpgradeHeight == m_targetVersion)) { logger(Logging::ERROR, Logging::BRIGHT_RED) << "Internal error: block at height " << (m_currency.upgradeHeight() + 1) << " has invalid version " <<
blockVersionAfterUpgradeHeight << ", expected " << static_cast(m_targetVersion); return false; }
}
}
return true;
}
uint8_t targetVersion() const { return m_targetVersion; }
uint64_t votingCompleteHeight() const { return m_votingCompleteHeight; }
uint64_t upgradeHeight() const {
if (m_currency.upgradeHeight() == UNDEF_HEIGHT) {
return m_votingCompleteHeight == UNDEF_HEIGHT ? UNDEF_HEIGHT : m_currency.calculateUpgradeHeight(m_votingCompleteHeight);
} else {
return m_currency.upgradeHeight();
}
}
void blockPushed() {
assert(!m_blockchain.empty());
if (m_currency.upgradeHeight() != UNDEF_HEIGHT) {
if (m_blockchain.size() <= m_currency.upgradeHeight() + 1) {
assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1);
} else {
assert(m_blockchain.back().bl.majorVersion == m_targetVersion);
}
} else if (m_votingCompleteHeight != UNDEF_HEIGHT) {
assert(m_blockchain.size() > m_votingCompleteHeight);
if (m_blockchain.size() <= upgradeHeight()) {
assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1);
if (m_blockchain.size() % (60 * 60 / m_currency.difficultyTarget()) == 0) {
logger(Logging::TRACE, Logging::BRIGHT_GREEN) << "###### UPGRADE is going to happen after height " << upgradeHeight() << "!";
}
} else if (m_blockchain.size() == upgradeHeight() + 1) {
assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1);
logger(Logging::TRACE, Logging::BRIGHT_GREEN) << "###### UPGRADE has happened! Starting from height " << (upgradeHeight() + 1) <<
" blocks with major version below " << static_cast(m_targetVersion) << " will be rejected!";
} else {
assert(m_blockchain.back().bl.majorVersion == m_targetVersion);
}
} else {
uint64_t lastBlockHeight = m_blockchain.size() - 1;
if (isVotingComplete(lastBlockHeight)) {
m_votingCompleteHeight = lastBlockHeight;
logger(Logging::TRACE, Logging::BRIGHT_GREEN) << "###### UPGRADE voting complete at height " << m_votingCompleteHeight <<
"! UPGRADE is going to happen after height " << upgradeHeight() << "!";
}
}
}
void blockPopped() {
if (m_votingCompleteHeight != UNDEF_HEIGHT) {
assert(m_currency.upgradeHeight() == UNDEF_HEIGHT);
if (m_blockchain.size() == m_votingCompleteHeight) {
logger(Logging::TRACE, Logging::BRIGHT_YELLOW) << "###### UPGRADE after height " << upgradeHeight() << " has been cancelled!";
m_votingCompleteHeight = UNDEF_HEIGHT;
} else {
assert(m_blockchain.size() > m_votingCompleteHeight);
}
}
}
private:
uint64_t findVotingCompleteHeight(uint64_t probableUpgradeHeight) {
assert(m_currency.upgradeHeight() == UNDEF_HEIGHT);
uint64_t probableVotingCompleteHeight = probableUpgradeHeight > m_currency.maxUpgradeDistance() ?
probableUpgradeHeight - m_currency.maxUpgradeDistance() : 0;
for (size_t i = probableVotingCompleteHeight; i <= probableUpgradeHeight; ++i) {
if (isVotingComplete(i)) {
return i;
}
}
return UNDEF_HEIGHT;
}
bool isVotingComplete(uint64_t height) {
assert(m_currency.upgradeHeight() == UNDEF_HEIGHT);
assert(m_currency.upgradeVotingWindow() > 1);
assert(m_currency.upgradeVotingThreshold() > 0 && m_currency.upgradeVotingThreshold() <= 100);
if (height < static_cast(m_currency.upgradeVotingWindow()) - 1) {
return false;
}
unsigned int voteCounter = 0;
for (size_t i = height + 1 - m_currency.upgradeVotingWindow(); i <= height; ++i) {
const auto& b = m_blockchain[i].bl;
voteCounter += (b.majorVersion == m_targetVersion - 1) && (b.minorVersion == BLOCK_MINOR_VERSION_1) ? 1 : 0;
}
return m_currency.upgradeVotingThreshold() * m_currency.upgradeVotingWindow() <= 100 * voteCounter;
}
private:
Logging::LoggerRef logger;
const Currency& m_currency;
BC& m_blockchain;
uint8_t m_targetVersion;
uint64_t m_votingCompleteHeight;
};
}