2
2
// Distributed under the MIT/X11 software license, see the accompanying
3
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
5
+ #include " gridcoin/staking/kernel.h"
5
6
#include " gridcoin/staking/status.h"
6
-
7
- #include < algorithm>
7
+ #include " ui_interface.h"
8
+ #include " util.h"
9
+ #include " wallet/wallet.h"
8
10
9
11
using namespace GRC ;
12
+ using SearchReport = MinerStatus::SearchReport;
13
+ using EfficiencyReport = MinerStatus::EfficiencyReport;
10
14
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 ;
31
16
32
17
// -----------------------------------------------------------------------------
33
18
// Global Variables
@@ -39,45 +24,300 @@ MinerStatus g_miner_status;
39
24
// Class: MinerStatus
40
25
// -----------------------------------------------------------------------------
41
26
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
43
73
{
44
- Clear ();
45
- ClearReasonsNotStaking ();
46
- CreatedCnt = AcceptedCnt = KernelsFound = 0 ;
74
+ return m_error_flags == NONE || m_error_flags == NO_MATURE_COINS;
47
75
}
48
76
49
- void MinerStatus::Clear ()
77
+ bool MinerStatus::StakingActive () const
50
78
{
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 ;
54
82
}
55
83
56
- bool MinerStatus::SetReasonNotStaking (ReasonNotStakingCategory not_staking_error)
84
+ SearchReport MinerStatus::GetSearchReport () const
57
85
{
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
+ };
59
117
60
- if (std::find (vReasonNotStaking.begin (), vReasonNotStaking.end (), not_staking_error) == vReasonNotStaking.end ())
61
118
{
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
+ }
63
152
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
+ }
69
157
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);
71
167
72
- inserted = true ;
168
+ // Avoid the lock on headless nodes:
169
+ if (fQtActive ) {
170
+ LOCK (m_cs);
171
+ uiInterface.MinerStatusChanged (StakingActive (), m_search.CoinWeight ());
73
172
}
173
+ }
174
+
175
+ void MinerStatus::IncrementBlocksCreated ()
176
+ {
177
+ LOCK (m_cs);
74
178
75
- return inserted ;
179
+ ++m_search. m_blocks_created ;
76
180
}
77
181
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)
79
191
{
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.
83
323
}
0 commit comments