Skip to content

Commit 9c8be26

Browse files
authored
Merge pull request #2183 from cyrossignol/global-status
refactor: Replace "GlobalStatus" state management
2 parents 907dd8e + 30022d0 commit 9c8be26

24 files changed

+872
-737
lines changed

src/gridcoin/staking/difficulty.cpp

+2-13
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ uint64_t GRC::GetStakeWeight(const CWallet& wallet)
227227
const int64_t now = GetAdjustedTime();
228228

229229
std::vector<std::pair<const CWalletTx*, unsigned int>> coins;
230-
GRC::MinerStatus::ReasonNotStakingCategory unused;
230+
GRC::MinerStatus::ErrorFlags unused;
231231
int64_t balance = 0;
232232

233233
LOCK2(cs_main, wallet.cs_wallet);
@@ -305,19 +305,8 @@ double GRC::GetEstimatedTimetoStake(bool ignore_staking_status, double dDiff, do
305305
return result;
306306
}
307307

308-
bool staking;
309-
bool able_to_stake;
310-
311-
{
312-
LOCK(g_miner_status.lock);
313-
314-
staking = g_miner_status.nLastCoinStakeSearchInterval && g_miner_status.WeightSum;
315-
316-
able_to_stake = g_miner_status.able_to_stake;
317-
}
318-
319308
// Get out early if not staking, ignore_staking_status is false, and not able_to_stake and set return value of 0.
320-
if (!ignore_staking_status && !staking && !able_to_stake)
309+
if (!ignore_staking_status && !g_miner_status.StakingActive() && !g_miner_status.StakingEnabled())
321310
{
322311
LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: Not staking: ETTS = %f", result);
323312
return result;

src/gridcoin/staking/status.cpp

+286-46
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,17 @@
22
// Distributed under the MIT/X11 software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include "gridcoin/staking/kernel.h"
56
#include "gridcoin/staking/status.h"
6-
7-
#include <algorithm>
7+
#include "ui_interface.h"
8+
#include "util.h"
9+
#include "wallet/wallet.h"
810

911
using namespace GRC;
12+
using SearchReport = MinerStatus::SearchReport;
13+
using EfficiencyReport = MinerStatus::EfficiencyReport;
1014

11-
namespace {
12-
//!
13-
//! \brief Text descriptions to display when a wallet cannot stake.
14-
//!
15-
//! The sequence of this array matches the items enumerated on
16-
//! MinerStatus::ReasonNotStakingCategory. Update this list as
17-
//! well when those change.
18-
//!
19-
constexpr const char* STAKING_ERROR_STRINGS[] {
20-
"None",
21-
"Disabled by configuration",
22-
"No Mature Coins",
23-
"No coins",
24-
"Entire balance reserved",
25-
"No UTXOs available due to reserve balance",
26-
"Wallet locked",
27-
"Testnet-only version",
28-
"Offline",
29-
};
30-
} // Anonymous namespace
15+
extern bool fQtActive;
3116

3217
// -----------------------------------------------------------------------------
3318
// Global Variables
@@ -39,45 +24,300 @@ MinerStatus g_miner_status;
3924
// Class: MinerStatus
4025
// -----------------------------------------------------------------------------
4126

42-
MinerStatus::MinerStatus(void)
27+
MinerStatus::MinerStatus() : m_error_flags(NONE)
28+
{
29+
}
30+
31+
std::string MinerStatus::FormatErrors(ErrorFlags error_flags)
32+
{
33+
std::string errors;
34+
35+
if (error_flags == NONE) {
36+
return errors;
37+
}
38+
39+
for (size_t i = NONE + 1; i < OUT_OF_BOUND; i <<= 1) {
40+
if (!(error_flags & i)) {
41+
continue;
42+
}
43+
44+
if (!errors.empty()) {
45+
errors += "; ";
46+
}
47+
48+
switch (i) {
49+
case DISABLED_BY_CONFIGURATION:
50+
// When disabled, skip any remaining alerts:
51+
errors = _("Disabled by configuration"); return errors;
52+
case NO_MATURE_COINS:
53+
errors += _("No mature coins"); break;
54+
case NO_COINS:
55+
errors += _("No coins"); break;
56+
case ENTIRE_BALANCE_RESERVED:
57+
errors += _("Entire balance reserved"); break;
58+
case NO_UTXOS_AVAILABLE_DUE_TO_RESERVE:
59+
errors += _("No UTXOs available due to reserve balance"); break;
60+
case WALLET_LOCKED:
61+
errors += _("Wallet locked"); break;
62+
case TESTNET_ONLY:
63+
errors += _("Testnet-only version"); break;
64+
case OFFLINE:
65+
errors += _("Offline"); break;
66+
}
67+
}
68+
69+
return errors;
70+
}
71+
72+
bool MinerStatus::StakingEnabled() const
4373
{
44-
Clear();
45-
ClearReasonsNotStaking();
46-
CreatedCnt = AcceptedCnt = KernelsFound = 0;
74+
return m_error_flags == NONE || m_error_flags == NO_MATURE_COINS;
4775
}
4876

49-
void MinerStatus::Clear()
77+
bool MinerStatus::StakingActive() const
5078
{
51-
WeightSum = ValueSum = WeightMin = WeightMax = 0;
52-
Version = 0;
53-
nLastCoinStakeSearchInterval = 0;
79+
LOCK(m_cs);
80+
81+
return m_search.m_timestamp != 0 && m_search.m_weight_sum != 0;
5482
}
5583

56-
bool MinerStatus::SetReasonNotStaking(ReasonNotStakingCategory not_staking_error)
84+
SearchReport MinerStatus::GetSearchReport() const
5785
{
58-
bool inserted = false;
86+
LOCK(m_cs);
87+
88+
return m_search;
89+
}
90+
91+
EfficiencyReport MinerStatus::GetEfficiencyReport() const
92+
{
93+
LOCK(m_cs);
94+
95+
return m_efficiency;
96+
}
97+
98+
std::optional<CWalletTx> MinerStatus::GetLastStake(CWallet& wallet)
99+
{
100+
CWalletTx stake_tx;
101+
uint256 cached_stake_tx_hash;
102+
103+
{
104+
LOCK(m_cs);
105+
cached_stake_tx_hash = m_last_pos_tx_hash;
106+
}
107+
108+
if (!cached_stake_tx_hash.IsNull()) {
109+
if (wallet.GetTransaction(cached_stake_tx_hash, stake_tx)) {
110+
return stake_tx;
111+
}
112+
}
113+
114+
const auto is_my_confirmed_stake = [](const CWalletTx& tx) {
115+
return tx.IsCoinStake() && tx.IsFromMe() && tx.GetDepthInMainChain() > 0;
116+
};
59117

60-
if (std::find(vReasonNotStaking.begin(), vReasonNotStaking.end(), not_staking_error) == vReasonNotStaking.end())
61118
{
62-
vReasonNotStaking.insert(vReasonNotStaking.end(), not_staking_error);
119+
LOCK2(cs_main, wallet.cs_wallet);
120+
121+
if (wallet.mapWallet.empty()) {
122+
return std::nullopt;
123+
}
124+
125+
auto latest_iter = wallet.mapWallet.cbegin();
126+
127+
for (auto iter = latest_iter; iter != wallet.mapWallet.cend(); ++iter) {
128+
if (iter->second.nTime > latest_iter->second.nTime
129+
&& is_my_confirmed_stake(iter->second))
130+
{
131+
latest_iter = iter;
132+
}
133+
}
134+
135+
if (latest_iter == wallet.mapWallet.cbegin()
136+
&& !is_my_confirmed_stake(latest_iter->second))
137+
{
138+
return std::nullopt;
139+
}
140+
141+
cached_stake_tx_hash = latest_iter->first;
142+
stake_tx = latest_iter->second;
143+
}
144+
145+
{
146+
LOCK(m_cs);
147+
m_last_pos_tx_hash = cached_stake_tx_hash;
148+
}
149+
150+
return stake_tx;
151+
}
63152

64-
if (not_staking_error != NONE)
65-
{
66-
if (!ReasonNotStaking.empty()) ReasonNotStaking += "; ";
67-
ReasonNotStaking += STAKING_ERROR_STRINGS[static_cast<int>(not_staking_error)];
68-
}
153+
std::string MinerStatus::FormatErrors() const
154+
{
155+
return FormatErrors(m_error_flags);
156+
}
69157

70-
if (not_staking_error > NO_MATURE_COINS) able_to_stake = false;
158+
void MinerStatus::AddError(ErrorFlags error_flag)
159+
{
160+
m_error_flags = static_cast<ErrorFlags>(m_error_flags | error_flag);
161+
}
162+
163+
void MinerStatus::UpdateCurrentErrors(ErrorFlags error_flags)
164+
{
165+
ClearErrors();
166+
AddError(error_flags);
71167

72-
inserted = true;
168+
// Avoid the lock on headless nodes:
169+
if (fQtActive) {
170+
LOCK(m_cs);
171+
uiInterface.MinerStatusChanged(StakingActive(), m_search.CoinWeight());
73172
}
173+
}
174+
175+
void MinerStatus::IncrementBlocksCreated()
176+
{
177+
LOCK(m_cs);
74178

75-
return inserted;
179+
++m_search.m_blocks_created;
76180
}
77181

78-
void MinerStatus::ClearReasonsNotStaking()
182+
void MinerStatus::UpdateLastSearch(
183+
bool kernel_found,
184+
int64_t search_timestamp,
185+
int block_version,
186+
uint64_t weight_sum,
187+
double value_sum,
188+
uint64_t weight_min,
189+
uint64_t weight_max,
190+
int64_t balance_weight)
79191
{
80-
vReasonNotStaking.clear();
81-
ReasonNotStaking.clear();
82-
able_to_stake = true;
192+
LOCK(m_cs);
193+
194+
if (kernel_found) {
195+
++m_search.m_kernels_found;
196+
}
197+
198+
m_search.m_block_version = block_version;
199+
m_search.m_weight_sum = weight_sum;
200+
m_search.m_value_sum = value_sum;
201+
m_search.m_weight_min = weight_min;
202+
m_search.m_weight_max = weight_max;
203+
204+
// search_timestamp has already been granularized to the stake time mask.
205+
// The StakeMiner loop has a sleep of 8 seconds. You can have no more than
206+
// one successful stake in STAKE_TIMESTAMP_MASK, so only increment the
207+
// counter if this iteration is with a search_timestamp in the next mask
208+
// interval. (When the UTXO count is low, with a sleep of 8 seconds, and a
209+
// nominal mask of 16 seconds, many times two stake UTXO loop traversals
210+
// will occur during the 16 seconds. Only one will result in a possible
211+
// stake.)
212+
//
213+
if (search_timestamp > m_search.m_timestamp) {
214+
m_efficiency.UpdateMetrics(weight_sum, balance_weight);
215+
}
216+
217+
m_search.m_timestamp = search_timestamp;
218+
219+
uiInterface.MinerStatusChanged(true, m_search.CoinWeight());
220+
}
221+
222+
void MinerStatus::UpdateLastStake(const uint256& coinstake_hash)
223+
{
224+
LOCK(m_cs);
225+
226+
m_search.m_blocks_accepted++;
227+
m_last_pos_tx_hash = coinstake_hash;
228+
}
229+
230+
void MinerStatus::ClearLastSearch()
231+
{
232+
LOCK(m_cs);
233+
234+
m_search.m_block_version = 0;
235+
m_search.m_weight_sum = 0;
236+
m_search.m_value_sum = 0;
237+
m_search.m_weight_min = 0;
238+
m_search.m_weight_max = 0;
239+
m_search.m_timestamp = 0;
240+
241+
uiInterface.MinerStatusChanged(false, 0);
242+
}
243+
244+
void MinerStatus::ClearLastStake()
245+
{
246+
LOCK(m_cs);
247+
248+
m_last_pos_tx_hash.SetNull();
249+
}
250+
251+
void MinerStatus::ClearErrors()
252+
{
253+
m_error_flags = NONE;
254+
}
255+
256+
// -----------------------------------------------------------------------------
257+
// Class: MinerStatus::SearchReport
258+
// -----------------------------------------------------------------------------
259+
260+
double SearchReport::CoinWeight() const
261+
{
262+
return m_weight_sum / 80.0;
263+
}
264+
265+
size_t SearchReport::KernelsRejected() const
266+
{
267+
return m_kernels_found - m_blocks_accepted;
268+
}
269+
270+
// -----------------------------------------------------------------------------
271+
// Class: MinerStatus::EfficiencyReport
272+
// -----------------------------------------------------------------------------
273+
274+
double EfficiencyReport::StakingLoopEfficiency() const
275+
{
276+
if (masked_time_intervals_elapsed <= 0) {
277+
return 0.0;
278+
}
279+
280+
return masked_time_intervals_covered * 100.0 / masked_time_intervals_elapsed;
281+
}
282+
283+
double EfficiencyReport::StakingEfficiency() const
284+
{
285+
if (ideal_cumulative_weight <= 0.0) {
286+
return 0.0;
287+
}
288+
289+
return actual_cumulative_weight * 100.0 / ideal_cumulative_weight;
290+
}
291+
292+
void EfficiencyReport::UpdateMetrics(uint64_t weight_sum, int64_t balance_weight)
293+
{
294+
const uint64_t prev_masked_time_intervals_elapsed = masked_time_intervals_elapsed;
295+
296+
++masked_time_intervals_covered;
297+
298+
// This is effectively sum of the weight of each stake attempt added back
299+
// to itself for cumulative total stake weight. This is the total effective
300+
// weight for the run time of the miner.
301+
actual_cumulative_weight += weight_sum;
302+
303+
// This is effectively the idealized weight equivalent of the balance times
304+
// the number of quantized (masked) staking periods that have elapsed since
305+
// the last trip through, added back for a cumulative total. The
306+
// calculation is done this way rather than just using the elapsed time to
307+
// ensure the masked time intervals are aligned in the calculations.
308+
const int64_t node_start_elapsed_seconds = g_timer.GetStartTime("default") / 1000;
309+
const int64_t now = GetTimeMillis() / 1000;
310+
const int64_t masked_time_elapsed = GRC::MaskStakeTime(now + GetTimeOffset())
311+
- GRC::MaskStakeTime(node_start_elapsed_seconds + GetTimeOffset());
312+
313+
masked_time_intervals_elapsed = masked_time_elapsed / (GRC::STAKE_TIMESTAMP_MASK + 1);
314+
315+
ideal_cumulative_weight += (double)balance_weight
316+
* (masked_time_intervals_elapsed - prev_masked_time_intervals_elapsed);
317+
318+
// masked_time_intervals_covered / masked_time_intervals_elapsed provides a
319+
// measure of the miner loop efficiency.
320+
//
321+
// actual_cumulative_weight / ideal_cumulative_weight provides a measure of
322+
// the overall mining efficiency compared to ideal.
83323
}

0 commit comments

Comments
 (0)