diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 98dd13905..6c9c000ca 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -206,9 +206,10 @@ class CMainParams : public CChainParams { consensus.nFutureForkBlock = 420420; updateManager.Add - ( // V17 voting blocks 419328-427391 in mainnet, 4032 voting, 4032 grace period, active at 427392 - Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 4032, 419328, 1, 3, 1, false, VoteThreshold(80, 60, 5), VoteThreshold(0, 0, 1), false, 427392) - ); + ( // V17 voting blocks 419328-427391 in mainnet, 4032 voting, 4032 grace period, active at 427392 + Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 4032, 419328, 1, 3, 1, false, + VoteThreshold(80, 60, 5), VoteThreshold(0, 0, 1), false, 427392) + ); // updateManager.Add // ( // Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), 7, 100, 100000, 5, 10, 5, false, VoteThreshold(85, 85, 1), VoteThreshold(95, 95, 1)) @@ -371,22 +372,22 @@ class CTestNetParams : public CChainParams { consensus.nFutureForkBlock = 1000; updateManager.Add - ( - Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 10, 0, 10, 100, 10, false, VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) - ); + ( + Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 1440, 25920, 7, 365, 7, false, + VoteThreshold(95, 85, 5), VoteThreshold(0, 0, 1)) + ); updateManager.Add - ( -// bit 1, 720 block/round, voting start at block 10080, 7 rounds to lock-in and 7 rounds off grace period - Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), - 1, //bit - 1440, //roundSize - 21600, // startHeight - 7, //votingPeriod - 365, //votingMaxRounds - 7, // gracePeriod - false, // forceUpdate - VoteThreshold(85, 85, 1), //minerThreshold - VoteThreshold(0, 0, 1)) //nodeThreshold + ( + Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), + 1, //bit + 1440, //roundSize + 27360, // startHeight + 7, //votingPeriod + 365, //votingMaxRounds + 7, // gracePeriod + false, // forceUpdate + VoteThreshold(85, 85, 1), //minerThreshold + VoteThreshold(0, 0, 1)) //nodeThreshold ); // The best chain should have at least this much work. @@ -530,13 +531,15 @@ class CDevNetParams : public CChainParams { consensus.nFutureForkBlock = 1; updateManager.Add - ( - Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 10, 0, 10, 100, 10, false, VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) - ); + ( + Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 10, 0, 10, 100, 10, false, + VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) + ); updateManager.Add - ( - Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), 1, 10, 100, 5, 10, 5, false, VoteThreshold(85, 85, 1), VoteThreshold(0, 0, 1)) - ); + ( + Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), 1, 10, 100, 5, 10, 5, false, + VoteThreshold(85, 85, 1), VoteThreshold(0, 0, 1)) + ); // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000"); @@ -633,7 +636,8 @@ class CDevNetParams : public CChainParams { }; } - void UpdateDevnetSubsidyAndDiffParameters(int nMinimumDifficultyBlocks, int nHighSubsidyBlocks, int nHighSubsidyFactor) { + void + UpdateDevnetSubsidyAndDiffParameters(int nMinimumDifficultyBlocks, int nHighSubsidyBlocks, int nHighSubsidyFactor) { consensus.nMinimumDifficultyBlocks = nMinimumDifficultyBlocks; consensus.nHighSubsidyBlocks = nHighSubsidyBlocks; consensus.nHighSubsidyFactor = nHighSubsidyFactor; @@ -699,13 +703,15 @@ class CRegTestParams : public CChainParams { consensus.nFutureForkBlock = 1; updateManager.Add - ( - Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 10, 0, 10, 100, 10, false, VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) - ); + ( + Update(EUpdate::DEPLOYMENT_V17, std::string("v17"), 0, 10, 0, 10, 100, 10, false, + VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) + ); updateManager.Add - ( - Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), 1, 10, 100, 10, 100, 10, false, VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) - ); + ( + Update(EUpdate::ROUND_VOTING, std::string("Round Voting"), 1, 10, 100, 10, 100, 10, false, + VoteThreshold(95, 95, 5), VoteThreshold(0, 0, 1)) + ); // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -801,26 +807,26 @@ class CRegTestParams : public CChainParams { consensus.llmqTypePlatform = Consensus::LLMQ_100_67; } - // void - // UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, - // int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff) { - // consensus.vDeployments[d].nStartTime = nStartTime; - // consensus.vDeployments[d].nTimeout = nTimeout; - // if (nWindowSize != -1) { - // consensus.vDeployments[d].nWindowSize = nWindowSize; - // } - // if (nThresholdStart != -1) { - // consensus.vDeployments[d].nThresholdStart = nThresholdStart; - // } - // if (nThresholdMin != -1) { - // consensus.vDeployments[d].nThresholdMin = nThresholdMin; - // } - // if (nFalloffCoeff != -1) { - // consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff; - // } - // } - - // void UpdateVersionBitsParametersFromArgs(const ArgsManager &args); + // void + // UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, + // int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff) { + // consensus.vDeployments[d].nStartTime = nStartTime; + // consensus.vDeployments[d].nTimeout = nTimeout; + // if (nWindowSize != -1) { + // consensus.vDeployments[d].nWindowSize = nWindowSize; + // } + // if (nThresholdStart != -1) { + // consensus.vDeployments[d].nThresholdStart = nThresholdStart; + // } + // if (nThresholdMin != -1) { + // consensus.vDeployments[d].nThresholdMin = nThresholdMin; + // } + // if (nFalloffCoeff != -1) { + // consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff; + // } + // } + + // void UpdateVersionBitsParametersFromArgs(const ArgsManager &args); void UpdateBudgetParameters(int nSmartnodePaymentsStartBlock, int nBudgetPaymentsStartBlock, int nSuperblockStartBlock) { @@ -966,7 +972,7 @@ const CChainParams &Params() { return *globalChainParams; } -UpdateManager& Updates() { +UpdateManager &Updates() { assert(globalChainParams); return globalChainParams->Updates(); } @@ -1034,7 +1040,8 @@ void CChainParams::UpdateLLMQParams(size_t totalMnCount, int height, bool lowLLM consensus.llmqs[Consensus::LLMQ_50_60] = Consensus::llmq10_60; consensus.llmqs[Consensus::LLMQ_400_60] = Consensus::llmq20_60; consensus.llmqs[Consensus::LLMQ_400_85] = Consensus::llmq20_85; - } else if ((totalMnCount < 4000 && isTestNet) || (totalMnCount < 600 && !isTestNet)) { + } else if ((((height >= 24280 && totalMnCount < 600) || (height < 24280 && totalMnCount < 4000)) && isTestNet) + || (totalMnCount < 600 && !isTestNet)) { consensus.llmqs[Consensus::LLMQ_50_60] = Consensus::llmq50_60; consensus.llmqs[Consensus::LLMQ_400_60] = Consensus::llmq40_60; consensus.llmqs[Consensus::LLMQ_400_85] = Consensus::llmq40_85; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 580d3bcea..980532d04 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1404,7 +1404,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest &request) { const CoinStatsHashType hash_type = ParseHashType(request.params[0], CoinStatsHashType::HASH_SERIALIZED); - CCoinsView * coins_view = WITH_LOCK(cs_main, + CCoinsView *coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB()); NodeContext &node = EnsureNodeContext(request.context); if (GetUTXOStats(coins_view, stats, hash_type, node.rpc_interruption_point)) { @@ -1605,33 +1605,49 @@ UniValue getblockchaininfo(const JSONRPCRequest &request) { }}, }}, }}, - {RPCResult::Type::OBJ_DYN, "bip9_softforks", "status of BIP9 softforks in progress", + {RPCResult::Type::OBJ_DYN, "rip1_softforks", "status of RIP1 softforks in progress", { {RPCResult::Type::OBJ, "xxxx", "name of the softfork", { {RPCResult::Type::STR, "status", "one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\""}, - {RPCResult::Type::NUM, "bit", - "the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)"}, - {RPCResult::Type::NUM_TIME, "start_time", - "the minimum median time past of a block at which the bit gains its meaning"}, - {RPCResult::Type::NUM_TIME, "timeout", - "the median time past of a block at which the deployment is considered failed if not yet locked in"}, - {RPCResult::Type::NUM, "since", - "height of the first block to which the status applies"}, - {RPCResult::Type::OBJ, "statistics", - "numeric statistics about BIP9 signalling for a softfork", + {RPCResult::Type::NUM, "start_height", + "the block height where voting starts"}, + {RPCResult::Type::NUM, "round_size", + "number of block per round of voting"}, + {RPCResult::Type::NUM, "voting_period", + "Number of rounds required for voting threshold check"}, + {RPCResult::Type::OBJ, "miners", + "miner numeric statistics about RIP1 signalling for a softfork", { - {RPCResult::Type::NUM, "period", - "the length in blocks of the BIP9 signalling period"}, + {RPCResult::Type::NUM, "mean_percentage", + "the mean approved percentage for this current voting period"}, + {RPCResult::Type::NUM, "weighted_yes", + "the number of approved votes for this voting period"}, + {RPCResult::Type::NUM, "weight", + "the number of votes for this voting period"}, + {RPCResult::Type::NUM, "samples", + "the number of round has been passed"}, {RPCResult::Type::NUM, "threshold", - "the number of blocks with the version bit set required to activate the feature"}, - {RPCResult::Type::NUM, "elapsed", - "the number of blocks elapsed since the beginning of the current period"}, - {RPCResult::Type::NUM, "count", - "the number of blocks with the version bit set in the current period"}, - {RPCResult::Type::BOOL, "possible", - "returns false if there are not enough blocks left in this period to pass activation threshold"}, + "current pass activation threshold"}, + {RPCResult::Type::BOOL, "approved", + "returns false if miners has not yet approved"}, + }}, + {RPCResult::Type::OBJ, "nodes", + "node numeric statistics about RIP1 signalling for a softfork", + { + {RPCResult::Type::NUM, "mean_percentage", + "the mean of approved percentage for this current voting period"}, + {RPCResult::Type::NUM, "weighted_yes", + "the number of approved votes for this voting period"}, + {RPCResult::Type::NUM, "weight", + "the number of votes for this voting period"}, + {RPCResult::Type::NUM, "samples", + "the number of round has been passed"}, + {RPCResult::Type::NUM, "threshold", + "current pass activation threshold"}, + {RPCResult::Type::BOOL, "approved", + "returns false if nodes has not approved"}, }}, }}, }}, @@ -1642,7 +1658,6 @@ UniValue getblockchaininfo(const JSONRPCRequest &request) { + HelpExampleRpc("getblockchaininfo", "") }, }.Check(request); - LOCK(cs_main); std::string strChainName = gArgs.IsArgSet("-devnet") ? gArgs.GetDevNetName() : Params().NetworkIDString(); @@ -1680,7 +1695,7 @@ UniValue getblockchaininfo(const JSONRPCRequest &request) { const Consensus::Params &consensusParams = Params().GetConsensus(); UniValue softforks(UniValue::VARR); - UniValue bip9_softforks(UniValue::VOBJ); + UniValue rip1_softforks(UniValue::VOBJ); // sorted by activation block // softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams)); // softforks.push_back(SoftForkDesc("bip66", 3, tip, consensusParams)); @@ -1689,17 +1704,27 @@ UniValue getblockchaininfo(const JSONRPCRequest &request) { // BIP9SoftForkDescPushBack(bip9_softforks, consensusParams, static_cast(pos)); // } for (int i = 0; i < static_cast(EUpdate::MAX_VERSION_BITS_DEPLOYMENTS); ++i) { - StateInfo state = Updates().State( static_cast(i), ::ChainActive().Tip()); + StateInfo state = Updates().State(static_cast(i), ::ChainActive().Tip()); if (state.State == EUpdateState::Unknown) continue; - const Update* update = Updates().GetUpdate(static_cast(i)); + const Update *update = Updates().GetUpdate(static_cast(i)); UniValue rv(UniValue::VOBJ); switch (state.State) { - case EUpdateState::Defined: rv.pushKV("status", "defined"); break; - case EUpdateState::Failed: rv.pushKV("status", "failed"); break; - case EUpdateState::LockedIn: rv.pushKV("status", "locked_in"); break; - case EUpdateState::Voting: rv.pushKV("status", "voting"); break; - case EUpdateState::Active: rv.pushKV("status", "active"); break; + case EUpdateState::Defined: + rv.pushKV("status", "defined"); + break; + case EUpdateState::Failed: + rv.pushKV("status", "failed"); + break; + case EUpdateState::LockedIn: + rv.pushKV("status", "locked_in"); + break; + case EUpdateState::Voting: + rv.pushKV("status", "voting"); + break; + case EUpdateState::Active: + rv.pushKV("status", "active"); + break; } if (state.State == EUpdateState::Voting) { rv.pushKV("bit", update->Bit()); @@ -1707,10 +1732,31 @@ UniValue getblockchaininfo(const JSONRPCRequest &request) { rv.pushKV("start_height", update->StartHeight()); rv.pushKV("round_size", update->RoundSize()); rv.pushKV("voting_period", update->VotingPeriod()); - bip9_softforks.pushKV(update->Name(), rv); + if (state.voteStats.currentMinerThreshold) { + UniValue minerStats(UniValue::VOBJ); + minerStats.pushKV("mean_percentage", state.voteStats.minerUpdateResult.MeanPercent()); + minerStats.pushKV("weighted_yes", state.voteStats.minerUpdateResult.GetWeightedYes()); + minerStats.pushKV("weight", state.voteStats.minerUpdateResult.GetWeight()); + minerStats.pushKV("samples", state.voteStats.minerUpdateResult.GetSamples()); + minerStats.pushKV("threshold", state.voteStats.currentMinerThreshold); + minerStats.pushKV("approved", state.voteStats.minersApproved); + rv.pushKV("miners", minerStats); + + } + if (state.voteStats.currentNodeThreshold) { + UniValue minerStats(UniValue::VOBJ); + minerStats.pushKV("mean_percentage", state.voteStats.nodeUpdateResult.MeanPercent()); + minerStats.pushKV("weighted_yes", state.voteStats.nodeUpdateResult.GetWeightedYes()); + minerStats.pushKV("weight", state.voteStats.nodeUpdateResult.GetWeight()); + minerStats.pushKV("samples", state.voteStats.nodeUpdateResult.GetSamples()); + minerStats.pushKV("threshold", state.voteStats.currentNodeThreshold); + minerStats.pushKV("approved", state.voteStats.nodesApproved); + rv.pushKV("nodes", minerStats); + } + rip1_softforks.pushKV(update->Name(), rv); } obj.pushKV("softforks", softforks); - obj.pushKV("bip9_softforks", bip9_softforks); + obj.pushKV("rip1_softforks", rip1_softforks); obj.pushKV("warnings", GetWarnings(false)); return obj; } diff --git a/src/update/update.cpp b/src/update/update.cpp index aa953a482..bf9cf267f 100644 --- a/src/update/update.cpp +++ b/src/update/update.cpp @@ -26,505 +26,490 @@ const int64_t VoteResult::scaleFactor = 100 * 100; // Scaled arithmetic (value 0.1234 represented by integer 1234) -std::ostream& operator<<(std::ostream& out, EUpdateState state) -{ - switch (state) - { - case EUpdateState::Defined : out << "Defined"; break; - case EUpdateState::Voting : out << "Voting"; break; - case EUpdateState::LockedIn : out << "Locked In"; break; - case EUpdateState::Active : out << "Active"; break; - case EUpdateState::Failed : out << "Failed"; break; - } - return out; +std::ostream &operator<<(std::ostream &out, EUpdateState state) { + switch (state) { + case EUpdateState::Defined : + out << "Defined"; + break; + case EUpdateState::Voting : + out << "Voting"; + break; + case EUpdateState::LockedIn : + out << "Locked In"; + break; + case EUpdateState::Active : + out << "Active"; + break; + case EUpdateState::Failed : + out << "Failed"; + break; + } + return out; } -std::ostream& operator<<(std::ostream& out, const Update& u) -{ - return u.Print(out); +std::ostream &operator<<(std::ostream &out, const Update &u) { + return u.Print(out); } -VoteResult operator+(const VoteResult& lhs, const VoteResult &rhs) -{ - VoteResult result = lhs; - result += rhs; - return result; +VoteResult operator+(const VoteResult &lhs, const VoteResult &rhs) { + VoteResult result = lhs; + result += rhs; + return result; } -std::ostream& operator<<(std::ostream &os, VoteResult voteResult) { return voteResult.Print(os); } - -std::string Update::ToString() const -{ - return strprintf("%20s(%3d): Bit: %2d, Round Size: %4d, Start Height: %7d, Voting Period: %4d, Max Rounds: %4d, Grace Rounds: %3d, " - "Miners: Start: %3d, Min: %3d, FalloffCoeff: %3d, " - "Nodes: Start: %3d, Min: %3d, FalloffCoeff: %3d, " - "HeightActivated: %7d, Failed: %d\n", - name, (int)updateId, bit, roundSize, startHeight, votingPeriod, votingMaxRounds, graceRounds, - minerThreshold.ThresholdStart(), minerThreshold.ThresholdMin(), minerThreshold.FalloffCoeff(), - nodeThreshold.ThresholdStart(), nodeThreshold.ThresholdMin(), nodeThreshold.FalloffCoeff(), - heightActivated, failed - ); +std::ostream &operator<<(std::ostream &os, VoteResult voteResult) { return voteResult.Print(os); } + +std::string Update::ToString() const { + return strprintf( + "%20s(%3d): Bit: %2d, Round Size: %4d, Start Height: %7d, Voting Period: %4d, Max Rounds: %4d, Grace Rounds: %3d, " + "Miners: Start: %3d, Min: %3d, FalloffCoeff: %3d, " + "Nodes: Start: %3d, Min: %3d, FalloffCoeff: %3d, " + "HeightActivated: %7d, Failed: %d\n", + name, (int) updateId, bit, roundSize, startHeight, votingPeriod, votingMaxRounds, graceRounds, + minerThreshold.ThresholdStart(), minerThreshold.ThresholdMin(), minerThreshold.FalloffCoeff(), + nodeThreshold.ThresholdStart(), nodeThreshold.ThresholdMin(), nodeThreshold.FalloffCoeff(), + heightActivated, failed + ); } -std::string VoteResult::ToString() const -{ - return strprintf("VoteResult(%6.4lf/%d,%d), Mean: %d", (double)weightedYes / scaleFactor, weight, samples, MeanPercent()); +std::string VoteResult::ToString() const { + return strprintf("VoteResult(%6.4lf/%d,%d), Mean: %d", (double) weightedYes / scaleFactor, weight, samples, + MeanPercent()); } -VoteResult MinerRoundVoting::GetVote(const CBlockIndex* blockIndex, const Update& update) -{ - // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, MinerRoundVoting\n", blockIndex->nHeight); - - // assert(height % update.RoundSize == 0); - if (blockIndex->nHeight % update.RoundSize() != 0) - { - LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, Partial rounds not accepted\n", blockIndex->nHeight); - return VoteResult(0, update.RoundSize()); // Assume everyone voted no - } - - if (blockIndex->nHeight < update.StartHeight() + update.RoundSize()) - { - // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, MinerRoundVoting - Before start, returning 0\n", blockIndex->nHeight); - return VoteResult(0, update.RoundSize()); // Assume everyone voted no - } - - // Check cache first: - if (cache.count({ update.UpdateId(), blockIndex })) - { - // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, returning cached result\n"); - return cache[{ update.UpdateId(), blockIndex }]; - } - - int64_t yesCount = 0; - const CBlockIndex* curIndex = blockIndex; - for (int64_t i = update.RoundSize(); i > 0; --i) - { - curIndex = curIndex->pprev; - if (curIndex == nullptr) - { - LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, BlockIndex is null, internal error\n"); - break; - } - // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, Getting sample\n", curIndex->nHeight); - if (curIndex->nVersion & update.BitMask()) - { - // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, Yes\n", curIndex->nHeight); - ++yesCount; - } - } - VoteResult vote(yesCount, update.RoundSize()); - LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, vote.ToString().c_str()); - cache[{ update.UpdateId(), blockIndex }] = vote; - return vote; +VoteResult MinerRoundVoting::GetVote(const CBlockIndex *blockIndex, const Update &update) { + // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, MinerRoundVoting\n", blockIndex->nHeight); + + // assert(height % update.RoundSize == 0); + if (blockIndex->nHeight % update.RoundSize() != 0) { + LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, Partial rounds not accepted\n", + blockIndex->nHeight); + return VoteResult(0, update.RoundSize()); // Assume everyone voted no + } + + if (blockIndex->nHeight < update.StartHeight() + update.RoundSize()) { + // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, MinerRoundVoting - Before start, returning 0\n", blockIndex->nHeight); + return VoteResult(0, update.RoundSize()); // Assume everyone voted no + } + + // Check cache first: + if (cache.count({update.UpdateId(), blockIndex})) { + // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, returning cached result\n"); + return cache[{update.UpdateId(), blockIndex}]; + } + + int64_t yesCount = 0; + const CBlockIndex *curIndex = blockIndex; + for (int64_t i = update.RoundSize(); i > 0; --i) { + curIndex = curIndex->pprev; + if (curIndex == nullptr) { + LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, BlockIndex is null, internal error\n"); + break; + } + // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, Getting sample\n", curIndex->nHeight); + if (curIndex->nVersion & update.BitMask()) { + // LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, Yes\n", curIndex->nHeight); + ++yesCount; + } + } + VoteResult vote(yesCount, update.RoundSize()); + LogPrint(BCLog::UPDATES, "Updates: MinerRoundVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, + vote.ToString().c_str()); + cache[{update.UpdateId(), blockIndex}] = vote; + return vote; } -VoteResult NodeRoundVoting::GetVote(const CBlockIndex* blockIndex, const Update& update) -{ - // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, NodeRoundVoting\n", blockIndex->nHeight); - - // assert(height % update.RoundSize == 0); - if (blockIndex->nHeight % update.RoundSize() != 0) - { - LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, Partial rounds not accepted\n", blockIndex->nHeight); - return VoteResult(); - } - - if (blockIndex->nHeight < update.StartHeight() + update.RoundSize()) - { - LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, NodeRoundVoting - Before start\n", blockIndex->nHeight); - return VoteResult(); - } - - // Dump the cache: - // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting: Vote Cache\n"); - // for (auto it: cache) - // { - // LogPrint(BCLog::UPDATES, " Updates: Height: %d, %s\n", it.first.second->nHeight, it.second.ToString().c_str()); - // } - - // Check cache first: - if (cache.count({ update.UpdateId(), blockIndex })) - { - // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, returning cached result\n"); - return cache[{ update.UpdateId(), blockIndex }]; - } - - const CBlockIndex* curIndex = blockIndex; - VoteResult vote; - for (int64_t i = update.RoundSize(); i > 0; --i) - { - curIndex = curIndex->pprev; - if (curIndex == nullptr) - { - LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, BlockIndex is null, internal error\n"); - break; - } - - // Search all transactions for final quorum commitments: - CBlock block; - bool r = ReadBlockFromDisk(block, curIndex, Params().GetConsensus()); - assert(r); - - // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Transactions: %d\n", curIndex->nHeight, block.vtx.size()); - - for (size_t i = 1; i < block.vtx.size(); i++) - { - auto& tx = block.vtx[i]; - - if (tx->nType == TRANSACTION_QUORUM_COMMITMENT) - { - llmq::CFinalCommitmentTxPayload qc; - if (!GetTxPayload(*tx, qc)) { - LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitment - GetTxPayload failed\n", curIndex->nHeight, i); +VoteResult NodeRoundVoting::GetVote(const CBlockIndex *blockIndex, const Update &update) { + // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, NodeRoundVoting\n", blockIndex->nHeight); + + // assert(height % update.RoundSize == 0); + if (blockIndex->nHeight % update.RoundSize() != 0) { + LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, Partial rounds not accepted\n", + blockIndex->nHeight); + return VoteResult(); + } + + if (blockIndex->nHeight < update.StartHeight() + update.RoundSize()) { + LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, NodeRoundVoting - Before start\n", + blockIndex->nHeight); + return VoteResult(); + } + + // Dump the cache: + // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting: Vote Cache\n"); + // for (auto it: cache) + // { + // LogPrint(BCLog::UPDATES, " Updates: Height: %d, %s\n", it.first.second->nHeight, it.second.ToString().c_str()); + // } + + // Check cache first: + if (cache.count({update.UpdateId(), blockIndex})) { + // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, returning cached result\n"); + return cache[{update.UpdateId(), blockIndex}]; + } + + const CBlockIndex *curIndex = blockIndex; + VoteResult vote; + for (int64_t i = update.RoundSize(); i > 0; --i) { + curIndex = curIndex->pprev; + if (curIndex == nullptr) { + LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, BlockIndex is null, internal error\n"); + break; + } + + // Search all transactions for final quorum commitments: + CBlock block; + bool r = ReadBlockFromDisk(block, curIndex, Params().GetConsensus()); + assert(r); + + // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Transactions: %d\n", curIndex->nHeight, block.vtx.size()); + + for (size_t i = 1; i < block.vtx.size(); i++) { + auto &tx = block.vtx[i]; + + if (tx->nType == TRANSACTION_QUORUM_COMMITMENT) { + llmq::CFinalCommitmentTxPayload qc; + if (!GetTxPayload(*tx, qc)) { + LogPrint(BCLog::UPDATES, + "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitment - GetTxPayload failed\n", + curIndex->nHeight, i); + } + if (qc.commitment.IsNull()) { + continue; + } + + // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitmentx - ValidMembers: %3d, Signers: %3d\n", curIndex->nHeight, i, qc.commitment.CountValidMembers(), qc.commitment.CountSigners()); + if (qc.commitment.CountValidMembers()) { + int64_t samples = qc.commitment.CountValidMembers(); + for (const auto it: qc.commitment.quorumUpdateVotes) { + if (update.Bit() == it.bit) { + LogPrint(BCLog::UPDATES, + "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitment - ValidMembers: %3d, Signers: %3d, yes: %3d\n", + curIndex->nHeight, i, qc.commitment.CountValidMembers(), + qc.commitment.CountSigners(), it.votes); + vote += VoteResult(it.votes, samples); + } + } + } + + // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitment\n", curIndex->nHeight, i); } - if (qc.commitment.IsNull()) { - continue; - } - - // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitmentx - ValidMembers: %3d, Signers: %3d\n", curIndex->nHeight, i, qc.commitment.CountValidMembers(), qc.commitment.CountSigners()); - if (qc.commitment.CountValidMembers()) - { - int64_t samples = qc.commitment.CountValidMembers(); - for (const auto it : qc.commitment.quorumUpdateVotes) { - if (update.Bit() == it.bit) { - LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitment - ValidMembers: %3d, Signers: %3d, yes: %3d\n", curIndex->nHeight, i, qc.commitment.CountValidMembers(), qc.commitment.CountSigners(), it.votes); - vote += VoteResult(it.votes, samples); - } - } - } - - // LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting, Height: %d, Tx: %d, Quorum Commitment\n", curIndex->nHeight, i); - } - } - } - LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, vote.ToString().c_str()); - cache[{ update.UpdateId(), blockIndex }] = vote; - return vote; + } + } + LogPrint(BCLog::UPDATES, "Updates: NodeRoundVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, + vote.ToString().c_str()); + cache[{update.UpdateId(), blockIndex}] = vote; + return vote; } -VoteResult MinerUpdateVoting::GetVote(const CBlockIndex* blockIndex, const Update &update) -{ - // assert(height % update.roundSize == 0); - if (blockIndex->nHeight < update.StartHeight() + update.RoundSize() * update.VotingPeriod()) - { - return VoteResult(0, update.RoundSize() * update.VotingPeriod()); // Assume everyone voted no for the entire period - } - - // Simply collect all of the round votes for a full average in the voting period: - VoteResult vote; - for (int lookback = 0; lookback < update.VotingPeriod(); ++lookback) - { - const CBlockIndex* roundBlockIndex = blockIndex->GetAncestor(blockIndex->nHeight - lookback * update.RoundSize()); - if (roundBlockIndex == nullptr) - break; // Nothing more - VoteResult roundVote = minerRoundVoting->GetVote(roundBlockIndex, update); - // LogPrint(BCLog::UPDATES, "Updates: MinerUpdateVoting::GetVote - retrieved vote for height %d: %s\n", roundBlockIndex->nHeight, roundVote.ToString()); - vote += roundVote; - } - LogPrint(BCLog::UPDATES, "Updates: MinerUpdateVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, vote.ToString().c_str()); - return vote; +VoteResult MinerUpdateVoting::GetVote(const CBlockIndex *blockIndex, const Update &update) { + // assert(height % update.roundSize == 0); + if (blockIndex->nHeight < update.StartHeight() + update.RoundSize() * update.VotingPeriod()) { + return VoteResult(0, + update.RoundSize() * update.VotingPeriod()); // Assume everyone voted no for the entire period + } + + // Simply collect all of the round votes for a full average in the voting period: + VoteResult vote; + for (int lookback = 0; lookback < update.VotingPeriod(); ++lookback) { + const CBlockIndex *roundBlockIndex = blockIndex->GetAncestor( + blockIndex->nHeight - lookback * update.RoundSize()); + if (roundBlockIndex == nullptr) + break; // Nothing more + VoteResult roundVote = minerRoundVoting->GetVote(roundBlockIndex, update); + // LogPrint(BCLog::UPDATES, "Updates: MinerUpdateVoting::GetVote - retrieved vote for height %d: %s\n", roundBlockIndex->nHeight, roundVote.ToString()); + vote += roundVote; + } + LogPrint(BCLog::UPDATES, "Updates: MinerUpdateVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, + vote.ToString().c_str()); + return vote; } -VoteResult NodeUpdateVoting::GetVote(const CBlockIndex* blockIndex, const Update &update) -{ - // assert(height % update.roundSize == 0); - if (blockIndex->nHeight < update.StartHeight() + update.RoundSize() * update.VotingPeriod()) - { - return VoteResult(0, update.RoundSize() * update.VotingPeriod()); // Assume everyone voted no for the entire period - } - - // Simply collect all of the round votes for a full average in the voting period: - VoteResult vote; - for (int lookback = 0; lookback < update.VotingPeriod(); ++lookback) - { - const CBlockIndex* roundBlockIndex = blockIndex->GetAncestor(blockIndex->nHeight - lookback * update.RoundSize()); - if (roundBlockIndex == nullptr) - break; // Nothing more - VoteResult roundVote = nodeRoundVoting->GetVote(roundBlockIndex, update); - // LogPrint(BCLog::UPDATES, "Updates: NodeUpdateVoting::GetVote - retrieved vote for height %d: %s\n", roundBlockIndex->nHeight, roundVote.ToString()); - vote += roundVote; - } - LogPrint(BCLog::UPDATES, "Updates: NodeUpdateVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, vote.ToString().c_str()); - return vote; +VoteResult NodeUpdateVoting::GetVote(const CBlockIndex *blockIndex, const Update &update) { + // assert(height % update.roundSize == 0); + if (blockIndex->nHeight < update.StartHeight() + update.RoundSize() * update.VotingPeriod()) { + return VoteResult(0, + update.RoundSize() * update.VotingPeriod()); // Assume everyone voted no for the entire period + } + + // Simply collect all of the round votes for a full average in the voting period: + VoteResult vote; + for (int lookback = 0; lookback < update.VotingPeriod(); ++lookback) { + const CBlockIndex *roundBlockIndex = blockIndex->GetAncestor( + blockIndex->nHeight - lookback * update.RoundSize()); + if (roundBlockIndex == nullptr) + break; // Nothing more + VoteResult roundVote = nodeRoundVoting->GetVote(roundBlockIndex, update); + // LogPrint(BCLog::UPDATES, "Updates: NodeUpdateVoting::GetVote - retrieved vote for height %d: %s\n", roundBlockIndex->nHeight, roundVote.ToString()); + vote += roundVote; + } + LogPrint(BCLog::UPDATES, "Updates: NodeUpdateVoting::GetVote, Height: %7d, %s\n", blockIndex->nHeight, + vote.ToString().c_str()); + return vote; } UpdateManager::UpdateManager() : - minerRoundVoting(), - nodeRoundVoting(), - minerUpdateVoting(&minerRoundVoting), - nodeUpdateVoting(&nodeRoundVoting) -{ + minerRoundVoting(), + nodeRoundVoting(), + minerUpdateVoting(&minerRoundVoting), + nodeUpdateVoting(&nodeRoundVoting) { } UpdateManager::~UpdateManager() {}; -bool UpdateManager::Add(Update update) -{ - // Check for existence first and erase if exist - auto it = updates.find(update.UpdateId()); - if (it != updates.end()) - updates.erase(it); - updates.emplace(update.UpdateId(), update); - // LogPrint(BCLog::UPDATES, "Updates: UpdateManager Added: %s\n", update.ToString()); - LogPrintf("Updates: UpdateManager Added: %s\n", update.ToString()); - return true; +bool UpdateManager::Add(Update update) { + // Check for existence first and erase if exist + auto it = updates.find(update.UpdateId()); + if (it != updates.end()) + updates.erase(it); + updates.emplace(update.UpdateId(), update); + // LogPrint(BCLog::UPDATES, "Updates: UpdateManager Added: %s\n", update.ToString()); + LogPrintf("Updates: UpdateManager Added: %s\n", update.ToString()); + return true; } -const Update* UpdateManager::GetUpdate(enum EUpdate eUpdate) const -{ - auto it = updates.find(eUpdate); - if (it != updates.end()) - return &(it->second); - else - return nullptr; +const Update *UpdateManager::GetUpdate(enum EUpdate eUpdate) const { + auto it = updates.find(eUpdate); + if (it != updates.end()) + return &(it->second); + else + return nullptr; } -bool UpdateManager::IsActive(enum EUpdate eUpdate, const CBlockIndex* blockIndex) -{ - return State(eUpdate, blockIndex).State == EUpdateState::Active; +bool UpdateManager::IsActive(enum EUpdate eUpdate, const CBlockIndex *blockIndex) { + return State(eUpdate, blockIndex).State == EUpdateState::Active; } -bool UpdateManager::IsAssetsActive(const CBlockIndex* blockIndex) -{ - return IsActive(EUpdate::ROUND_VOTING, blockIndex); +bool UpdateManager::IsAssetsActive(const CBlockIndex *blockIndex) { + return IsActive(EUpdate::ROUND_VOTING, blockIndex); } -StateInfo UpdateManager::State(enum EUpdate eUpdate, const CBlockIndex* blockIndex) -{ - const Update* update = GetUpdate(eUpdate); - if (!update || blockIndex == nullptr) - { - return { EUpdateState::Unknown, -1 }; - } - - // Check for forced updates (bypass voting logic and associated overhead for old updates): - if (update->HeightActivated() >= 0) - { - if (blockIndex->nHeight >= update->HeightActivated()) { - return { update->Failed() ? EUpdateState::Failed : EUpdateState::Active , update->HeightActivated() }; - } - else { - return { EUpdateState::Defined, -1 }; - } - } - - LOCK(updateMutex); - - // Dump the final state cache: - // LogPrint(BCLog::UPDATES, "Updates: FinalState cache\n"); - // for (auto const& finalState : finalStates) - // { - // LogPrint(BCLog::UPDATES, " Update: %s, FinalState: %d, FinalHeight: %7d\n", GetUpdate(finalState.first)->Name().c_str(), finalState.second.State, finalState.second.FinalHeight); - // } - - // // Dump the state cache: - // LogPrint(BCLog::UPDATES, "Updates: State cache\n"); - // for (auto const& state : states) - // { - // UpdateCacheKey key = state.first; - // StateInfo stateInfo = state.second; - // // LogPrint(BCLog::UPDATES, " Updates: key.first: %d, key.second: %p\n", (int)key.first, key.second); - // // LogPrint(BCLog::UPDATES, " Updates: stateInfo.first: %d, stateInfo.second: %d\n", (int)stateInfo.State, stateInfo.FinalHeight); - // const Update* update = GetUpdate(key.first); - // LogPrint(BCLog::UPDATES, " Updates: Update: %s, Height: %6d, State: %9d, FinalHeight: %6d, BlockIndex: %p\n", GetUpdate(key.first)->Name().c_str(), key.second->nHeight, stateInfo.State, stateInfo.FinalHeight, key.second); - // } - - // See if this proposal is in final state for quick return: - if (finalStates.count(eUpdate)) - { - StateInfo stateInfo = finalStates[eUpdate]; - if (blockIndex->nHeight >= stateInfo.FinalHeight) - { - // LogPrint(BCLog::UPDATES, "Updates: Update: %s, State: %d, FinalHeight: %7d - fast return\n", update->Name().c_str(), stateInfo.State, stateInfo.FinalHeight); - return stateInfo; - } - } - - int64_t roundSize = update->RoundSize(); - int64_t startHeight = update->StartHeight(); - int64_t lastVotingHeight = update->StartHeight() + roundSize * (update->VotingMaxRounds() - 1); - int64_t votingPeriod = update->VotingPeriod(); - int64_t roundNumber = (blockIndex->nHeight - startHeight) / roundSize; - - // If we are not within one round of starting, just return defined: - if (roundNumber < -1) - { - return { EUpdateState::Defined, -1 }; - } - - LogPrint(BCLog::UPDATES, "Updates: Update: %s, roundSize: %d, startHeight: %7d, lastVotingHeight: %d, votingPeriod: %d, roundNumber: %d\n", update->Name().c_str(), roundSize, startHeight, lastVotingHeight, votingPeriod, roundNumber); - - // The state of each round is stored at the height of the first block AFTER the round (height % roundSize == 0) - LogPrint(BCLog::UPDATES, "Updates: Update: %s, State: blockIndex: %p, Height: %7d\n", update->Name().c_str(), (void*)blockIndex, blockIndex->nHeight); - if (blockIndex != nullptr) - { - blockIndex = blockIndex->GetAncestor(blockIndex->nHeight - blockIndex->nHeight % roundSize); - } - LogPrint(BCLog::UPDATES, "Updates: Update: %s, State: blockIndex: %p, Height: %7d after normalization\n", update->Name().c_str(), (void*)blockIndex, blockIndex->nHeight); - - // Walk backwards in steps of roundSize to find a BlockIndex whose information is known - const CBlockIndex* pIndexPrev = blockIndex; - std::vector vToCompute; - while (states.count({ eUpdate, pIndexPrev }) == 0) - { - // LogPrint(BCLog::UPDATES, "Updates: State: pIndexPrev: %p, Height: %7d, count: %d\n", (void*)pIndexPrev, pIndexPrev->nHeight, states.count({eUpdate, pIndexPrev})); - - if (pIndexPrev == nullptr || pIndexPrev->nHeight == 0) - { - states[{eUpdate, pIndexPrev}] = { EUpdateState::Defined, -1 }; // The genesis block is by definition defined. - LogPrint(BCLog::UPDATES, "Updates: Genesis block, state: Defined\n"); - break; - } - if (pIndexPrev->nHeight < startHeight) - { - states[{eUpdate, pIndexPrev}] = { EUpdateState::Defined, -1 }; // One round before the starting height - LogPrint(BCLog::UPDATES, "Updates: Height: %7d, Before startHeight, state: Defined\n", pIndexPrev->nHeight); - break; - } - - vToCompute.push_back(pIndexPrev); - pIndexPrev = pIndexPrev->GetAncestor(pIndexPrev->nHeight - roundSize); - } - LogPrint(BCLog::UPDATES, "Updates: Lookback complete. Rounds to compute: %d\n", vToCompute.size()); - - // At this point, states[{eUpdate, pIndexPrev}] is known - assert(states.count({eUpdate, pIndexPrev})); - StateInfo stateInfo = states[{eUpdate, pIndexPrev}]; - - // Now walk forward and compute the state of descendants of pindexPrev - while (!vToCompute.empty()) - { - StateInfo stateNext = stateInfo; - pIndexPrev = vToCompute.back(); - vToCompute.pop_back(); - - // LogPrint(BCLog::UPDATES, "Updates: Update: %s, Height: %7d, Compute - starting state: %d\n", update->Name().c_str(), pIndexPrev->nHeight, stateInfo.State); - - switch (stateInfo.State) - { - case EUpdateState::Defined: - { - if (pIndexPrev->nHeight > lastVotingHeight) - { - stateNext.State = EUpdateState::Failed; - stateNext.FinalHeight = pIndexPrev->nHeight; - // Save the final state: - finalStates[eUpdate] = stateNext; - // No longer need to continue checking rounds: - vToCompute.clear(); - break; - } - - if (pIndexPrev->nHeight >= startHeight) - { - stateNext.State = EUpdateState::Voting; - stateNext.FinalHeight = -1; - } - // Fall through to collect the vote for the first round +StateInfo UpdateManager::State(enum EUpdate eUpdate, const CBlockIndex *blockIndex) { + const Update *update = GetUpdate(eUpdate); + VoteStats voteStats = {VoteResult(), VoteResult(), 0, 0, false, false}; + if (!update || blockIndex == nullptr) { + return {EUpdateState::Unknown, -1, voteStats}; + } + + // Check for forced updates (bypass voting logic and associated overhead for old updates): + if (update->HeightActivated() >= 0) { + if (blockIndex->nHeight >= update->HeightActivated()) { + return {update->Failed() ? EUpdateState::Failed : EUpdateState::Active, update->HeightActivated(), + voteStats}; + } else { + return {EUpdateState::Defined, -1, voteStats}; + } + } + + LOCK(updateMutex); + + // Dump the final state cache: + // LogPrint(BCLog::UPDATES, "Updates: FinalState cache\n"); + // for (auto const& finalState : finalStates) + // { + // LogPrint(BCLog::UPDATES, " Update: %s, FinalState: %d, FinalHeight: %7d\n", GetUpdate(finalState.first)->Name().c_str(), finalState.second.State, finalState.second.FinalHeight); + // } + + // // Dump the state cache: + // LogPrint(BCLog::UPDATES, "Updates: State cache\n"); + // for (auto const& state : states) + // { + // UpdateCacheKey key = state.first; + // StateInfo stateInfo = state.second; + // // LogPrint(BCLog::UPDATES, " Updates: key.first: %d, key.second: %p\n", (int)key.first, key.second); + // // LogPrint(BCLog::UPDATES, " Updates: stateInfo.first: %d, stateInfo.second: %d\n", (int)stateInfo.State, stateInfo.FinalHeight); + // const Update* update = GetUpdate(key.first); + // LogPrint(BCLog::UPDATES, " Updates: Update: %s, Height: %6d, State: %9d, FinalHeight: %6d, BlockIndex: %p\n", GetUpdate(key.first)->Name().c_str(), key.second->nHeight, stateInfo.State, stateInfo.FinalHeight, key.second); + // } + + // See if this proposal is in final state for quick return: + if (finalStates.count(eUpdate)) { + StateInfo stateInfo = finalStates[eUpdate]; + if (blockIndex->nHeight >= stateInfo.FinalHeight) { + // LogPrint(BCLog::UPDATES, "Updates: Update: %s, State: %d, FinalHeight: %7d - fast return\n", update->Name().c_str(), stateInfo.State, stateInfo.FinalHeight); + return stateInfo; + } + } + + int64_t roundSize = update->RoundSize(); + int64_t startHeight = update->StartHeight(); + int64_t lastVotingHeight = update->StartHeight() + roundSize * (update->VotingMaxRounds() - 1); + int64_t votingPeriod = update->VotingPeriod(); + int64_t roundNumber = (blockIndex->nHeight - startHeight) / roundSize; + + // If we are not within one round of starting, just return defined: + if (roundNumber < -1) { + return {EUpdateState::Defined, -1, voteStats}; + } + + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, roundSize: %d, startHeight: %7d, lastVotingHeight: %d, votingPeriod: %d, roundNumber: %d\n", + update->Name().c_str(), roundSize, startHeight, lastVotingHeight, votingPeriod, roundNumber); + + // The state of each round is stored at the height of the first block AFTER the round (height % roundSize == 0) + LogPrint(BCLog::UPDATES, "Updates: Update: %s, State: blockIndex: %p, Height: %7d\n", update->Name().c_str(), + (void *) blockIndex, blockIndex->nHeight); + if (blockIndex != nullptr) { + blockIndex = blockIndex->GetAncestor(blockIndex->nHeight - blockIndex->nHeight % roundSize); + } + LogPrint(BCLog::UPDATES, "Updates: Update: %s, State: blockIndex: %p, Height: %7d after normalization\n", + update->Name().c_str(), (void *) blockIndex, blockIndex->nHeight); + + // Walk backwards in steps of roundSize to find a BlockIndex whose information is known + const CBlockIndex *pIndexPrev = blockIndex; + std::vector vToCompute; + while (states.count({eUpdate, pIndexPrev}) == 0) { + // LogPrint(BCLog::UPDATES, "Updates: State: pIndexPrev: %p, Height: %7d, count: %d\n", (void*)pIndexPrev, pIndexPrev->nHeight, states.count({eUpdate, pIndexPrev})); + + if (pIndexPrev == nullptr || pIndexPrev->nHeight == 0) { + states[{eUpdate, pIndexPrev}] = {EUpdateState::Defined, -1, + voteStats}; // The genesis block is by definition defined. + LogPrint(BCLog::UPDATES, "Updates: Genesis block, state: Defined\n"); + break; + } + if (pIndexPrev->nHeight < startHeight) { + states[{eUpdate, pIndexPrev}] = {EUpdateState::Defined, -1, + voteStats}; // One round before the starting height + LogPrint(BCLog::UPDATES, "Updates: Height: %7d, Before startHeight, state: Defined\n", pIndexPrev->nHeight); + break; + } + + vToCompute.push_back(pIndexPrev); + pIndexPrev = pIndexPrev->GetAncestor(pIndexPrev->nHeight - roundSize); + } + LogPrint(BCLog::UPDATES, "Updates: Lookback complete. Rounds to compute: %d\n", vToCompute.size()); + + // At this point, states[{eUpdate, pIndexPrev}] is known + assert(states.count({eUpdate, pIndexPrev})); + StateInfo stateInfo = states[{eUpdate, pIndexPrev}]; + + // Now walk forward and compute the state of descendants of pindexPrev + while (!vToCompute.empty()) { + StateInfo stateNext = stateInfo; + pIndexPrev = vToCompute.back(); + vToCompute.pop_back(); + + // LogPrint(BCLog::UPDATES, "Updates: Update: %s, Height: %7d, Compute - starting state: %d\n", update->Name().c_str(), pIndexPrev->nHeight, stateInfo.State); + + switch (stateInfo.State) { + case EUpdateState::Defined: { + if (pIndexPrev->nHeight > lastVotingHeight) { + stateNext.State = EUpdateState::Failed; + stateNext.FinalHeight = pIndexPrev->nHeight; + stateNext.voteStats = stateInfo.voteStats; + // Save the final state: + finalStates[eUpdate] = stateNext; + // No longer need to continue checking rounds: + vToCompute.clear(); + break; + } + + if (pIndexPrev->nHeight >= startHeight) { + stateNext.State = EUpdateState::Voting; + stateNext.voteStats = stateInfo.voteStats; + stateNext.FinalHeight = -1; + } + // Fall through to collect the vote for the first round } - case EUpdateState::Voting: - { - if (pIndexPrev->nHeight > lastVotingHeight) - { - if (update->ForcedUpdate()) - { - stateNext = { EUpdateState::LockedIn, pIndexPrev->nHeight + roundSize * update->GraceRounds() }; - LogPrint(BCLog::UPDATES, "Updates: Update: %s, Voting: Height: %7d, Voting failed, forcing update, Activation at height: %7d\n", update->Name().c_str(), pIndexPrev->nHeight, stateNext.FinalHeight); - } - else - { - stateNext = { EUpdateState::Failed, pIndexPrev->nHeight }; - } - break; - } - - // Check Miner votes: - bool minersApprove = false; - { - VoteResult minerUpdateResult = minerUpdateVoting.GetVote(pIndexPrev, *update); - - // double confidenceLow = minerUpdateResult.ComputeConfidenceIntervalLow() * 100.0; - int64_t minerMean = minerUpdateResult.MeanPercent(); - int64_t currentThreshold = update->MinerThreshold().GetThreshold(roundNumber); - if (minerMean >= currentThreshold) - { - LogPrint(BCLog::UPDATES, "Updates: Update: %s, Voting: Height: %7d, Miners %s, Threshold: %3d - Miners approve\n", - update->Name().c_str(), pIndexPrev->nHeight, minerUpdateResult.ToString().c_str(), currentThreshold); - minersApprove = true; - } - else - { - LogPrint(BCLog::UPDATES, "Updates: Update: %s, Voting: Height: %7d, Miners %s, Threshold: %3d\n", - update->Name().c_str(), pIndexPrev->nHeight, minerUpdateResult.ToString().c_str(), currentThreshold); - } - } - - // Check Node votes: - bool nodesApprove = false; - { - VoteResult nodeUpdateResult = nodeUpdateVoting.GetVote(pIndexPrev, *update); - - int64_t nodeMean = nodeUpdateResult.MeanPercent(); - int64_t currentThreshold = update->NodeThreshold().GetThreshold(roundNumber); - if (nodeMean >= currentThreshold) - { - LogPrint(BCLog::UPDATES, "Updates: Update: %s, Voting: Height: %7d, Nodes %s, Threshold: %3d - Nodes approve\n", - update->Name().c_str(), pIndexPrev->nHeight, nodeUpdateResult.ToString().c_str(), currentThreshold); - nodesApprove = true; - } - else - { - LogPrint(BCLog::UPDATES, "Updates: Update: %s, Voting: Height: %7d, Nodes %s, Threshold: %3d\n", - update->Name().c_str(), pIndexPrev->nHeight, nodeUpdateResult.ToString().c_str(), currentThreshold); - } - } - - if (minersApprove && nodesApprove) - { - stateNext = { EUpdateState::LockedIn, pIndexPrev->nHeight + roundSize * update->GraceRounds() }; - LogPrint(BCLog::UPDATES, "Updates: Update: %s, Voting: Height: %7d, Proposal has been locked in, Activation at height: %7d\n", update->Name().c_str(), pIndexPrev->nHeight, stateNext.FinalHeight); - } - break; + case EUpdateState::Voting: { + if (pIndexPrev->nHeight > lastVotingHeight) { + if (update->ForcedUpdate()) { + stateNext = {EUpdateState::LockedIn, pIndexPrev->nHeight + roundSize * update->GraceRounds(), + voteStats}; + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, Voting: Height: %7d, Voting failed, forcing update, Activation at height: %7d\n", + update->Name().c_str(), pIndexPrev->nHeight, stateNext.FinalHeight); + } else { + stateNext = {EUpdateState::Failed, pIndexPrev->nHeight, stateInfo.voteStats}; + } + break; + } + + // Check Miner votes: + { + VoteResult minerUpdateResult = minerUpdateVoting.GetVote(pIndexPrev, *update); + voteStats.minerUpdateResult = minerUpdateResult; + // double confidenceLow = minerUpdateResult.ComputeConfidenceIntervalLow() * 100.0; + int64_t minerMean = minerUpdateResult.MeanPercent(); + voteStats.currentMinerThreshold = update->MinerThreshold().GetThreshold(roundNumber); + if (minerMean >= voteStats.currentMinerThreshold) { + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, Voting: Height: %7d, Miners %s, Threshold: %3d - Miners approve\n", + update->Name().c_str(), pIndexPrev->nHeight, minerUpdateResult.ToString().c_str(), + voteStats.currentMinerThreshold); + voteStats.minersApproved = true; + } else { + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, Voting: Height: %7d, Miners %s, Threshold: %3d\n", + update->Name().c_str(), pIndexPrev->nHeight, minerUpdateResult.ToString().c_str(), + voteStats.currentMinerThreshold); + } + } + + // Check Node votes: + { + VoteResult nodeUpdateResult = nodeUpdateVoting.GetVote(pIndexPrev, *update); + voteStats.nodeUpdateResult = nodeUpdateResult; + int64_t nodeMean = nodeUpdateResult.MeanPercent(); + voteStats.currentNodeThreshold = update->NodeThreshold().GetThreshold(roundNumber); + if (nodeMean >= voteStats.currentNodeThreshold) { + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, Voting: Height: %7d, Nodes %s, Threshold: %3d - Nodes approve\n", + update->Name().c_str(), pIndexPrev->nHeight, nodeUpdateResult.ToString().c_str(), + voteStats.currentNodeThreshold); + voteStats.nodesApproved = true; + } else { + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, Voting: Height: %7d, Nodes %s, Threshold: %3d\n", + update->Name().c_str(), pIndexPrev->nHeight, nodeUpdateResult.ToString().c_str(), + voteStats.currentNodeThreshold); + } + } + + if (voteStats.minersApproved && voteStats.nodesApproved) { + stateNext = {EUpdateState::LockedIn, pIndexPrev->nHeight + roundSize * update->GraceRounds(), + voteStats}; + LogPrint(BCLog::UPDATES, + "Updates: Update: %s, Voting: Height: %7d, Proposal has been locked in, Activation at height: %7d\n", + update->Name().c_str(), pIndexPrev->nHeight, stateNext.FinalHeight); + } + break; } - case EUpdateState::LockedIn: - { - // Wait for the grace period: - if (pIndexPrev->nHeight >= stateNext.FinalHeight) - { - stateNext.State = EUpdateState::Active; - // Save the final state: - finalStates[eUpdate] = stateNext; - // No longer need to continue checking rounds: - vToCompute.clear(); - } - break; + case EUpdateState::LockedIn: { + // Wait for the grace period: + if (pIndexPrev->nHeight >= stateNext.FinalHeight) { + stateNext.State = EUpdateState::Active; + stateNext.voteStats = stateInfo.voteStats; + // Save the final state: + finalStates[eUpdate] = stateNext; + // No longer need to continue checking rounds: + vToCompute.clear(); + } + break; } case EUpdateState::Failed: - case EUpdateState::Active: - { - // Nothing happens, these are terminal states. - break; + case EUpdateState::Active: { + // Nothing happens, these are terminal states. + break; } - } - // LogPrint(BCLog::UPDATES, "Updates: Update: %s, Caching %d for Height: %7d\n", update->Name().c_str(), stateNext.State, pIndexPrev->nHeight); - states[{ eUpdate, pIndexPrev }] = stateInfo = stateNext; - } - return stateInfo; + } + // LogPrint(BCLog::UPDATES, "Updates: Update: %s, Caching %d for Height: %7d\n", update->Name().c_str(), stateNext.State, pIndexPrev->nHeight); + states[{eUpdate, pIndexPrev}] = stateInfo = stateNext; + } + return stateInfo; } -uint32_t UpdateManager::ComputeBlockVersion(const CBlockIndex* blockIndex) -{ - LOCK(updateMutex); - uint32_t nVersion = VERSIONBITS_TOP_BITS; - for (auto const& update: updates) - { - StateInfo si = State(update.first, blockIndex); - if (si.State == EUpdateState::Voting || si.State == EUpdateState::LockedIn) - nVersion |= update.second.BitMask(); - } - return nVersion; +uint32_t UpdateManager::ComputeBlockVersion(const CBlockIndex *blockIndex) { + LOCK(updateMutex); + uint32_t nVersion = VERSIONBITS_TOP_BITS; + for (auto const &update: updates) { + StateInfo si = State(update.first, blockIndex); + if (si.State == EUpdateState::Voting || si.State == EUpdateState::LockedIn) + nVersion |= update.second.BitMask(); + } + return nVersion; } diff --git a/src/update/update.h b/src/update/update.h index 6441591e3..93ebbdf31 100644 --- a/src/update/update.h +++ b/src/update/update.h @@ -13,22 +13,20 @@ #include #include -enum class EUpdate -{ - DEPLOYMENT_V17 = 0, - ROUND_VOTING = 1, +enum class EUpdate { + DEPLOYMENT_V17 = 0, + ROUND_VOTING = 1, - MAX_VERSION_BITS_DEPLOYMENTS + MAX_VERSION_BITS_DEPLOYMENTS }; -enum class EUpdateState -{ - Unknown = 0, // Update does not exist or state is unknown - Defined, // Proposed update is defined, but voting has not begun. - Voting, // Voting on proposed update is active. - LockedIn, // Vote was successful, we are in the grace period for remaining miners/nodes to update. - Active, // Update has been enacted - it is active and logic should be in place. - Failed // Update has failed and will not be enacted. +enum class EUpdateState { + Unknown = 0, // Update does not exist or state is unknown + Defined, // Proposed update is defined, but voting has not begun. + Voting, // Voting on proposed update is active. + LockedIn, // Vote was successful, we are in the grace period for remaining miners/nodes to update. + Active, // Update has been enacted - it is active and logic should be in place. + Failed // Update has failed and will not be enacted. }; /** What block version to use for new blocks (pre versionbits) */ @@ -41,295 +39,333 @@ static const int32_t VERSIONBITS_TOP_MASK = 0xE0000000UL; static const int32_t VERSIONBITS_NUM_BITS = 29; -std::ostream& operator<<(std::ostream& out, EUpdateState state); +std::ostream &operator<<(std::ostream &out, EUpdateState state); -typedef std::pair UpdateCacheKey; +typedef std::pair UpdateCacheKey; -class VoteThreshold -{ - public : - VoteThreshold(int64_t thresholdStart, int64_t thresholdMin, int64_t falloffCoeff) : thresholdStart(thresholdStart), thresholdMin(thresholdMin), falloffCoeff(falloffCoeff) - { - if - ( - (thresholdMin < 0) || - (thresholdStart < thresholdMin) || - (thresholdStart > 100) || - (falloffCoeff < 1) - ) - { +class VoteThreshold { +public : + VoteThreshold(int64_t thresholdStart, int64_t thresholdMin, int64_t falloffCoeff) : thresholdStart(thresholdStart), + thresholdMin(thresholdMin), + falloffCoeff(falloffCoeff) { + if + ( + (thresholdMin < 0) || + (thresholdStart < thresholdMin) || + (thresholdStart > 100) || + (falloffCoeff < 1) + ) { throw std::invalid_argument("Invalid VoteThreshold arguments"); - } - } - - int64_t ThresholdStart() const { return thresholdStart; } - int64_t ThresholdMin() const { return thresholdMin; } - int64_t FalloffCoeff() const { return falloffCoeff; } - - int64_t GetThreshold(int64_t roundNumber) const - { - return std::max(thresholdStart - roundNumber * roundNumber / falloffCoeff, thresholdMin); - } - - private : - int64_t thresholdStart; // Initial threshold at start of voting - int64_t thresholdMin; // Minimum allowed threshold - int64_t falloffCoeff; // Reduces ThresholdStart each round + } + } + + int64_t ThresholdStart() const { return thresholdStart; } + + int64_t ThresholdMin() const { return thresholdMin; } + + int64_t FalloffCoeff() const { return falloffCoeff; } + + int64_t GetThreshold(int64_t roundNumber) const { + return std::max(thresholdStart - roundNumber * roundNumber / falloffCoeff, thresholdMin); + } + +private : + int64_t thresholdStart; // Initial threshold at start of voting + int64_t thresholdMin; // Minimum allowed threshold + int64_t falloffCoeff; // Reduces ThresholdStart each round }; -class Update -{ - public : - Update(EUpdate updateId, std::string name, int bit, int64_t roundSize, int64_t startHeight, int64_t votingPeriod, int64_t votingMaxRounds, int64_t graceRounds, bool forcedUpdate, - VoteThreshold minerThreshold, VoteThreshold nodeThreshold, bool failed = false, int64_t heightActivated = -1) : - updateId(updateId), - name(name), - bit(bit), - roundSize(roundSize), - startHeight(startHeight), - votingPeriod(votingPeriod), - votingMaxRounds(votingMaxRounds), - graceRounds(graceRounds), - forcedUpdate(forcedUpdate), - minerThreshold(minerThreshold), - nodeThreshold(nodeThreshold), - failed(failed), - heightActivated(heightActivated) - { - // Validate input: - if - ( - (bit < 0 || bit > 28) || - (roundSize < 1) || - (startHeight < 0) || - (votingPeriod < 1) || - (votingMaxRounds < 1) || - (graceRounds < 0) || - (heightActivated < -1) || - (startHeight % roundSize != 0) || - (votingPeriod > votingMaxRounds) - ) - { +class Update { +public : + Update(EUpdate updateId, std::string name, int bit, int64_t roundSize, int64_t startHeight, int64_t votingPeriod, + int64_t votingMaxRounds, int64_t graceRounds, bool forcedUpdate, + VoteThreshold minerThreshold, VoteThreshold nodeThreshold, bool failed = false, int64_t heightActivated = -1) + : + updateId(updateId), + name(name), + bit(bit), + roundSize(roundSize), + startHeight(startHeight), + votingPeriod(votingPeriod), + votingMaxRounds(votingMaxRounds), + graceRounds(graceRounds), + forcedUpdate(forcedUpdate), + minerThreshold(minerThreshold), + nodeThreshold(nodeThreshold), + failed(failed), + heightActivated(heightActivated) { + // Validate input: + if + ( + (bit < 0 || bit > 28) || + (roundSize < 1) || + (startHeight < 0) || + (votingPeriod < 1) || + (votingMaxRounds < 1) || + (graceRounds < 0) || + (heightActivated < -1) || + (startHeight % roundSize != 0) || + (votingPeriod > votingMaxRounds) + ) { throw std::invalid_argument("Invalid argument(s) for Update " + name); - } - } - - EUpdate UpdateId() const { return updateId; } - const std::string& Name() const { return name; } - int Bit() const { return bit; } - int64_t RoundSize() const { return roundSize; } - int64_t StartHeight() const { return startHeight; } - int64_t VotingPeriod() const { return votingPeriod; } - int64_t VotingMaxRounds() const { return votingMaxRounds; } - int64_t GraceRounds() const { return graceRounds; } - bool ForcedUpdate() const { return forcedUpdate; } - const VoteThreshold& MinerThreshold() const { return minerThreshold; } - const VoteThreshold& NodeThreshold() const { return nodeThreshold; } - bool Failed() const { return failed; } - int64_t HeightActivated() const { return heightActivated; } - uint32_t BitMask() const { return ((uint32_t)1) << bit; } - - std::string ToString() const; - - std::ostream& Print(std::ostream& os) const - { - os << ToString(); - return os; - } - - private : - // Core information: - EUpdate updateId; // Enum of proposed update - std::string name; // Name of proposed update - int bit; // Bit indicating the vote in version field - int64_t roundSize; // Blocks forming a single round (each round starts at height % roundSize == 0) - int64_t startHeight; // When Voting starts (must be a round starting height) - int64_t votingPeriod; // Number of rounds required for voting threshold check - int64_t votingMaxRounds; // Proposed update expires after startHeight + roundSize * votingMaxRounds - int64_t graceRounds; // After successful vote, state is locked in for this many rounds before going active. - bool forcedUpdate; // If the threshold is not reached at expiration, lock it in after the grace period anyway. - - // Thresholds - VoteThreshold minerThreshold; - VoteThreshold nodeThreshold; - - // Flags for activation height to bypass vote checking (old votes) - bool failed; // True if the proposed update failed and should be ignored - int64_t heightActivated; // -1 if proposed update should be evaluated. Set to height when activated to bypass evaluation (old votes, for performance). + } + } + + EUpdate UpdateId() const { return updateId; } + + const std::string &Name() const { return name; } + + int Bit() const { return bit; } + + int64_t RoundSize() const { return roundSize; } + + int64_t StartHeight() const { return startHeight; } + + int64_t VotingPeriod() const { return votingPeriod; } + + int64_t VotingMaxRounds() const { return votingMaxRounds; } + + int64_t GraceRounds() const { return graceRounds; } + + bool ForcedUpdate() const { return forcedUpdate; } + + const VoteThreshold &MinerThreshold() const { return minerThreshold; } + + const VoteThreshold &NodeThreshold() const { return nodeThreshold; } + + bool Failed() const { return failed; } + + int64_t HeightActivated() const { return heightActivated; } + + uint32_t BitMask() const { return ((uint32_t) 1) << bit; } + + std::string ToString() const; + + std::ostream &Print(std::ostream &os) const { + os << ToString(); + return os; + } + +private : + // Core information: + EUpdate updateId; // Enum of proposed update + std::string name; // Name of proposed update + int bit; // Bit indicating the vote in version field + int64_t roundSize; // Blocks forming a single round (each round starts at height % roundSize == 0) + int64_t startHeight; // When Voting starts (must be a round starting height) + int64_t votingPeriod; // Number of rounds required for voting threshold check + int64_t votingMaxRounds; // Proposed update expires after startHeight + roundSize * votingMaxRounds + int64_t graceRounds; // After successful vote, state is locked in for this many rounds before going active. + bool forcedUpdate; // If the threshold is not reached at expiration, lock it in after the grace period anyway. + + // Thresholds + VoteThreshold minerThreshold; + VoteThreshold nodeThreshold; + + // Flags for activation height to bypass vote checking (old votes) + bool failed; // True if the proposed update failed and should be ignored + int64_t heightActivated; // -1 if proposed update should be evaluated. Set to height when activated to bypass evaluation (old votes, for performance). }; -std::ostream& operator<<(std::ostream& out, const Update& u); +std::ostream &operator<<(std::ostream &out, const Update &u); // (Height, state) -typedef std::map EUpdateStateCache; +typedef std::map EUpdateStateCache; // RoundVoteCache... -class VoteResult -{ - public : - VoteResult(int64_t yes = 0, int64_t sampleSize = 0) : weightedYes(0), weight(0), samples(0) - { - if ((yes < 0) || (sampleSize < 0) || (yes > sampleSize)) - { +class VoteResult { +public : + VoteResult(int64_t yes = 0, int64_t sampleSize = 0) : weightedYes(0), weight(0), samples(0) { + if ((yes < 0) || (sampleSize < 0) || (yes > sampleSize)) { throw std::invalid_argument("Invalid arguments to VoteResult constructor."); - } + } - if (sampleSize > 0) - { + if (sampleSize > 0) { int64_t percent = scaleFactor * yes / sampleSize; weightedYes = percent * sampleSize; - weight = sampleSize; - samples = 1; - } - } - - int64_t MeanPercent() const - { - return (weight == 0) ? 0 : ((weightedYes / weight) + 50) / 100; // Round off to the nearest percent - } - - VoteResult& operator+=(const VoteResult& rhs) - { - weightedYes += rhs.weightedYes; - weight += rhs.weight; - samples += rhs.samples; - return *this; - } - - std::string ToString() const; - std::ostream& Print(std::ostream& os) const - { - os << ToString(); - return os; - } - - private : - static const int64_t scaleFactor; - - int64_t weightedYes; - int64_t weight; - int64_t samples; + weight = sampleSize; + samples = 1; + } + } + + int64_t MeanPercent() const { + return (weight == 0) ? 0 : ((weightedYes / weight) + 50) / 100; // Round off to the nearest percent + } + + VoteResult &operator+=(const VoteResult &rhs) { + weightedYes += rhs.weightedYes; + weight += rhs.weight; + samples += rhs.samples; + return *this; + } + + VoteResult &operator=(const VoteResult &vr) { + if(this == &vr) { + return *this; + } + weightedYes = vr.weightedYes; + weight = vr.weight; + samples = vr.samples; + return *this; + } + + std::string ToString() const; + + std::ostream &Print(std::ostream &os) const { + os << ToString(); + return os; + } + + int64_t GetWeightedYes() const { + return weightedYes; + } + + int64_t GetSamples() const { + return samples; + } + + int64_t GetWeight() const { + return weight; + } + +private : + static const int64_t scaleFactor; + + int64_t weightedYes; + int64_t weight; + int64_t samples; }; -VoteResult operator+(const VoteResult& lhs, const VoteResult &rhs); - -std::ostream& operator<<(std::ostream &os, VoteResult voteResult); - -class IRoundVoting -{ - public : - virtual ~IRoundVoting() {}; - /// @brief Return voting results for proposed update at the round ending one block before blockIndex. - /// @details A round vote is only returned for completed rounds (all blocks available in the round). - /// The only valid heights are where blockIndex->nHeight % update.roundSize == 0. - /// @param blockIndex First block after the round (blockIndex->nHeight % update.roundSize == 0). - /// Partial rounds are never calculated. - /// @param update Proposed update being evaluated. - /// @return Vote results for the full round ending before immediately blockIndex. If the round is before the - /// proposed update is active, an empty vote result is returned (0% voted yes). - virtual VoteResult GetVote(const CBlockIndex* blockIndex, const Update& update) = 0; +VoteResult operator+(const VoteResult &lhs, const VoteResult &rhs); + +std::ostream &operator<<(std::ostream &os, VoteResult voteResult); + +class IRoundVoting { +public : + virtual ~IRoundVoting() {}; + + /// @brief Return voting results for proposed update at the round ending one block before blockIndex. + /// @details A round vote is only returned for completed rounds (all blocks available in the round). + /// The only valid heights are where blockIndex->nHeight % update.roundSize == 0. + /// @param blockIndex First block after the round (blockIndex->nHeight % update.roundSize == 0). + /// Partial rounds are never calculated. + /// @param update Proposed update being evaluated. + /// @return Vote results for the full round ending before immediately blockIndex. If the round is before the + /// proposed update is active, an empty vote result is returned (0% voted yes). + virtual VoteResult GetVote(const CBlockIndex *blockIndex, const Update &update) = 0; }; -class IUpdateVoting -{ - public : - virtual ~IUpdateVoting() {}; - - /// @brief Return voting results for proposed update for all rounds in the votingPeriod ending immediately before blockIndex. - /// @details A proposed update vote is a combination of all rounds in the votingPeriod that ends immediately before blockIndex. - /// Rounds before the start of the proposed update are treated as a 0% vote and are included in the results. - /// @param blockIndex First block following the last round for update check (blockIndex->nHeight % update.roundSize == 0). - /// @param update Update being evaluated. - /// @return Cumulative voting results for the last votingPeriod rounds. - virtual VoteResult GetVote(const CBlockIndex* blockIndex, const Update& update) = 0; +class IUpdateVoting { +public : + virtual ~IUpdateVoting() {}; + + /// @brief Return voting results for proposed update for all rounds in the votingPeriod ending immediately before blockIndex. + /// @details A proposed update vote is a combination of all rounds in the votingPeriod that ends immediately before blockIndex. + /// Rounds before the start of the proposed update are treated as a 0% vote and are included in the results. + /// @param blockIndex First block following the last round for update check (blockIndex->nHeight % update.roundSize == 0). + /// @param update Update being evaluated. + /// @return Cumulative voting results for the last votingPeriod rounds. + virtual VoteResult GetVote(const CBlockIndex *blockIndex, const Update &update) = 0; }; -class MinerRoundVoting : public IRoundVoting -{ - public : - MinerRoundVoting() {}; - virtual ~MinerRoundVoting() {}; +class MinerRoundVoting : public IRoundVoting { +public : + MinerRoundVoting() {}; - VoteResult GetVote(const CBlockIndex* blockIndex, const Update& update) override; + virtual ~MinerRoundVoting() {}; - private : - std::map cache; + VoteResult GetVote(const CBlockIndex *blockIndex, const Update &update) override; + +private : + std::map cache; }; -class NodeRoundVoting : public IRoundVoting -{ - public : - NodeRoundVoting() {}; - virtual ~NodeRoundVoting() {}; +class NodeRoundVoting : public IRoundVoting { +public : + NodeRoundVoting() {}; + + virtual ~NodeRoundVoting() {}; - VoteResult GetVote(const CBlockIndex* blockIndex, const Update& update) override; + VoteResult GetVote(const CBlockIndex *blockIndex, const Update &update) override; - private : - std::map cache; +private : + std::map cache; }; -class MinerUpdateVoting : public IUpdateVoting -{ - public : - MinerUpdateVoting(MinerRoundVoting* minerRoundVoting) : minerRoundVoting(minerRoundVoting) {}; - virtual ~MinerUpdateVoting() {}; +class MinerUpdateVoting : public IUpdateVoting { +public : + MinerUpdateVoting(MinerRoundVoting *minerRoundVoting) : minerRoundVoting(minerRoundVoting) {}; - VoteResult GetVote(const CBlockIndex* blockIndex, const Update &update) override; + virtual ~MinerUpdateVoting() {}; - protected: - MinerRoundVoting* minerRoundVoting; + VoteResult GetVote(const CBlockIndex *blockIndex, const Update &update) override; + +protected: + MinerRoundVoting *minerRoundVoting; }; -class NodeUpdateVoting : public IUpdateVoting -{ - public : - NodeUpdateVoting(NodeRoundVoting* nodeRoundVoting) : nodeRoundVoting(nodeRoundVoting) {}; - virtual ~NodeUpdateVoting() {}; +class NodeUpdateVoting : public IUpdateVoting { +public : + NodeUpdateVoting(NodeRoundVoting *nodeRoundVoting) : nodeRoundVoting(nodeRoundVoting) {}; + + virtual ~NodeUpdateVoting() {}; - VoteResult GetVote(const CBlockIndex* blockIndex, const Update &update) override; + VoteResult GetVote(const CBlockIndex *blockIndex, const Update &update) override; - protected: - NodeRoundVoting* nodeRoundVoting; +protected: + NodeRoundVoting *nodeRoundVoting; }; -typedef struct StateInfo -{ - EUpdateState State; - int64_t FinalHeight; // Active height when state in (LockedIn, Active), Failed height when state == Failed +typedef struct VoteStats { + VoteResult nodeUpdateResult; + VoteResult minerUpdateResult; + int64_t currentMinerThreshold; + int64_t currentNodeThreshold; + bool nodesApproved; + bool minersApproved; +} VoteStats; + +typedef struct StateInfo { + EUpdateState State; + int64_t FinalHeight; // Active height when state in (LockedIn, Active), Failed height when state == Failed + VoteStats voteStats; } StateInfo; -class UpdateManager -{ - public: - UpdateManager(); - virtual ~UpdateManager(); +class UpdateManager { +public: + UpdateManager(); + + virtual ~UpdateManager(); + + bool Add(Update update); + + const Update *GetUpdate(enum EUpdate eUpdate) const; + + bool IsActive(enum EUpdate eUpdate, const CBlockIndex *blockIndex); - bool Add(Update update); + bool IsAssetsActive(const CBlockIndex *blockIndex); - const Update* GetUpdate(enum EUpdate eUpdate) const; - bool IsActive(enum EUpdate eUpdate, const CBlockIndex* blockIndex); - bool IsAssetsActive(const CBlockIndex* blockIndex); + StateInfo State(enum EUpdate eUpdate, const CBlockIndex *blockIndex); - StateInfo State(enum EUpdate eUpdate, const CBlockIndex* blockIndex); + uint32_t ComputeBlockVersion(const CBlockIndex *blockIndex); - uint32_t ComputeBlockVersion(const CBlockIndex* blockIndex); +private : + typedef std::unordered_map UpdateMap; + typedef std::unordered_map FinalStateMap; // Caches final states and heights of all completed proposals - private : - typedef std::unordered_map UpdateMap; - typedef std::unordered_map FinalStateMap; // Caches final states and heights of all completed proposals + typedef std::unordered_map StateMap; - typedef std::unordered_map StateMap; + RecursiveMutex updateMutex; + MinerRoundVoting minerRoundVoting; + NodeRoundVoting nodeRoundVoting; + MinerUpdateVoting minerUpdateVoting; + NodeUpdateVoting nodeUpdateVoting; + FinalStateMap finalStates; + UpdateMap updates; // Update parameters (does not contain states) + std::map states; - RecursiveMutex updateMutex; - MinerRoundVoting minerRoundVoting; - NodeRoundVoting nodeRoundVoting; - MinerUpdateVoting minerUpdateVoting; - NodeUpdateVoting nodeUpdateVoting; - FinalStateMap finalStates; - UpdateMap updates; // Update parameters (does not contain states) - std::map states; }; #endif // RAPTOREUM_UPDATE_HPP