-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathmain.go
475 lines (418 loc) · 19.1 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
// Copyright (c) 2017-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"github.com/decred/dcrd/rpcclient/v8"
"github.com/decred/dcrd/wire"
)
// Contains a certain block version's count of blocks in the
// rolling window (which has a length of activeNetParams.BlockUpgradeNumToCheck)
type blockVersions struct {
RollingWindowLookBacks []int
}
type intervalVersionCounts struct {
Version uint32
Count []uint32
StaticInterval string
}
const (
// numberOfIntervals is the number of intervals to use when calling
// getstakeversioninfo
numberOfIntervals = 4
)
var (
// templateInformation is the template holding the active network
// parameters.
templateInformation *templateFields
agendaTitles = map[string]string{
"sdiffalgorithm": "Change PoS Staking Algorithm",
"lnsupport": "Start Lightning Network Support",
"lnfeatures": "Enable Lightning Network Features",
"fixlnseqlocks": "Update Sequence Lock Rules",
"headercommitments": "Enable Block Header Commitments",
"treasury": "Enable Decentralized Treasury",
"reverttreasurypolicy": "Revert Treasury Expenditure Policy",
"explicitverupgrades": "Explicit Version Upgrades",
"autorevocations": "Automatic Ticket Revocations",
"changesubsidysplit": "Change PoW/PoS Subsidy Split",
"changesubsidysplitr2": "Change PoW/PoS Subsidy Split To 1/89",
"blake3pow": "Change PoW to BLAKE3 and ASERT",
}
longAgendaDescriptions = map[string]string{
"sdiffalgorithm": "Specifies a proposed replacement algorithm for determining the stake difficulty (commonly called the ticket price). This proposal resolves all issues with a new algorithm that adheres to the referenced ideals.",
"lnsupport": "The <a href='https://lightning.network/' target='_blank' rel='noopener noreferrer'>Lightning Network</a> is the most directly useful application of smart contracts to date since it allows for off-chain transactions that optionally settle on-chain. This infrastructure has clear benefits for both scaling and privacy. Decred is optimally positioned for this integration.",
"lnfeatures": "The <a href='https://lightning.network/' target='_blank' rel='noopener noreferrer'>Lightning Network</a> is the most directly useful application of smart contracts to date since it allows for off-chain transactions that optionally settle on-chain. This infrastructure has clear benefits for both scaling and privacy. Decred is optimally positioned for this integration.",
"fixlnseqlocks": "In order to fully support the <a href='https://lightning.network/' target='_blank' rel='noopener noreferrer'>Lightning Network</a>, the current sequence lock consensus rules need to be modified.",
"headercommitments": "Proposed modifications to the Decred block header to increase the security and efficiency of lightweight clients, as well as adding infrastructure to enable future scalability enhancements.",
"treasury": "In May 2019, Decred stakeholders approved the development of <a href='https://proposals.decred.org/proposals/c96290a' target='_blank' rel='noopener noreferrer'>a proposed solution</a> to further decentralize the process of spending from the Decred treasury.",
"reverttreasurypolicy": "Change the algorithm used to calculate Treasury spending limits such that it enforces the policy originally approved by stakeholders in the <a href='https://proposals.decred.org/proposals/c96290a' target='_blank' rel='noopener noreferrer'>Decentralized Treasury proposal</a>.",
"explicitverupgrades": "Modifications to Decred transaction and scripting language version enforcement which will simplify deployment and integration of future consensus changes across the Decred ecosystem.",
"autorevocations": "Changes to ticket revocation transactions and block acceptance criteria in order to enable <a href='https://proposals.decred.org/record/e2d7b7d' target='_blank' rel='noopener noreferrer'>automatic ticket revocations</a>, significantly improving the user experience for stakeholders.",
"changesubsidysplit": "<a href='https://proposals.decred.org/record/427e1d4' target='_blank' rel='noopener noreferrer'>Proposal</a> to modify to the block reward subsidy split such that 10% goes to Proof-of-Work and 80% goes to Proof-of-Stake.",
"changesubsidysplitr2": "Modify the block reward subsidy split such that 1% goes to Proof-of-Work (PoW) and 89% goes to Proof-of-Stake (PoS). The Treasury subsidy remains at 10%.",
"blake3pow": "<a href='https://proposals.decred.org/record/a8501bc' target='_blank' rel='noopener noreferrer'>Stakeholders voted</a> to change the Proof-of-Work hash function to BLAKE3. This consensus change will also update the difficulty algorithm to ASERT (Absolutely Scheduled Exponentially weighted Rising Targets).",
}
)
// updatetemplateInformation is called on startup and upon every block connected notification received.
func updatetemplateInformation(ctx context.Context, dcrdClient *rpcclient.Client, latestBlockHeader *wire.BlockHeader) {
log.Println("Updating vote information")
hash := latestBlockHeader.BlockHash()
height := int64(latestBlockHeader.Height)
log.Printf("Current best block height: %d", height)
// Set Current block height
templateInformation.BlockHeight = height
// Request GetStakeVersions to receive information about past block versions.
//
// Request twice as many, so we can populate the rolling block version window's first
stakeVersionResults, err := dcrdClient.GetStakeVersions(ctx, hash.String(),
int32(activeNetParams.BlockUpgradeNumToCheck*2))
if err != nil {
log.Printf("GetStakeVersions error: %v", err)
return
}
blockVersionsFound := make(map[int32]*blockVersions)
blockVersionsHeights := make([]int64, activeNetParams.BlockUpgradeNumToCheck)
elementNum := 0
// The algorithm starts at the middle of the GetStakeVersionResults and decrements backwards toward
// the beginning of the list. This is due to GetStakeVersionResults.StakeVersions being ordered
// from most recent blocks to oldest. (ie [0] == current, [len] == oldest). So by starting in the middle
// we then can calculate that first blocks rolling window result then become one block 'more recent'
// and calculate that blocks rolling window results.
for i := len(stakeVersionResults.StakeVersions)/2 - 1; i >= 0; i-- {
// Calculate the last block element in the window
windowEnd := i + int(activeNetParams.BlockUpgradeNumToCheck)
// blockVersionsHeights lets us have a correctly ordered list of blockheights for xaxis label
blockVersionsHeights[elementNum] = stakeVersionResults.StakeVersions[i].Height
// Define rolling window range for this current block (i)
stakeVersionsWindow := stakeVersionResults.StakeVersions[i:windowEnd]
for _, stakeVersion := range stakeVersionsWindow {
// Try to get an existing blockVersions struct (pointer)
theseBlockVersions, ok := blockVersionsFound[stakeVersion.BlockVersion]
if !ok {
// Had not found this block version yet
theseBlockVersions = &blockVersions{}
blockVersionsFound[stakeVersion.BlockVersion] = theseBlockVersions
theseBlockVersions.RollingWindowLookBacks =
make([]int, activeNetParams.BlockUpgradeNumToCheck)
// Need to populate "back" to fill in values for previously missed window
for k := 0; k < elementNum; k++ {
theseBlockVersions.RollingWindowLookBacks[k] = 0
}
theseBlockVersions.RollingWindowLookBacks[elementNum] = 1
} else {
// Already had that block version, so increment
theseBlockVersions.RollingWindowLookBacks[elementNum]++
}
}
elementNum++
}
templateInformation.BlockVersionsHeights = blockVersionsHeights
templateInformation.BlockVersions = blockVersionsFound
stakeVersionsWindow := stakeVersionResults.StakeVersions[:activeNetParams.BlockUpgradeNumToCheck]
blockVersionsCounts := make(map[int32]int64)
for _, sv := range stakeVersionsWindow {
blockVersionsCounts[sv.BlockVersion]++
}
var mostPopularBlockVersion int32
mostPopularBlockVersionCount := int64(0)
for v, count := range blockVersionsCounts {
if count > mostPopularBlockVersionCount {
mostPopularBlockVersion = v
mostPopularBlockVersionCount = count
}
}
log.Printf("Most popular block version in the last %d blocks: v%d (%d blocks)",
len(stakeVersionsWindow), mostPopularBlockVersion, blockVersionsCounts[mostPopularBlockVersion])
templateInformation.BlockVersionCurrent = mostPopularBlockVersion
templateInformation.BlockVersionNext = blockVersion
blockCountPercentage := 100 * float64(blockVersionsCounts[blockVersion]) / float64(activeNetParams.BlockUpgradeNumToCheck)
templateInformation.BlockVersionNextPercentage = blockCountPercentage
if blockVersionsCounts[blockVersion] >= int64(activeNetParams.BlockRejectNumRequired) {
templateInformation.BlockVersionSuccess = true
}
// Voting intervals ((height-4096) mod 2016)
blocksIntoStakeVersionInterval := (height - activeNetParams.StakeValidationHeight) %
activeNetParams.StakeVersionInterval
// Stake versions per block in current voting interval (getstakeversions hash blocksIntoInterval)
intervalStakeVersions, err := dcrdClient.GetStakeVersions(ctx, hash.String(),
int32(blocksIntoStakeVersionInterval))
if err != nil {
log.Printf("GetStakeVersions error: %v", err)
return
}
// Tally missed votes so far in this interval
missedVotesStakeInterval := 0
for _, stakeVersionResult := range intervalStakeVersions.StakeVersions {
missedVotesStakeInterval += int(activeNetParams.TicketsPerBlock) - len(stakeVersionResult.Votes)
}
// Vote tallies for previous intervals
stakeVersionInfo, err := dcrdClient.GetStakeVersionInfo(ctx, numberOfIntervals)
if err != nil {
log.Println(err)
return
}
numIntervals := len(stakeVersionInfo.Intervals)
if numIntervals == 0 {
log.Println("StakeVersion info did not return usable information, intervals empty")
return
}
templateInformation.StakeVersionsIntervals = stakeVersionInfo.Intervals
minimumNeededVoteVersions := uint32(100)
// Hacky way of populating the Vote Version bar graph
// Each element in each dataset needs counts for each interval
// For example:
// version 1: [100, 200, 0, 400]
var CurrentSVIEndHeightHeight = int64(0)
var stakeVersionIntervalResults []intervalVersionCounts
stakeVersionLabels := make([]string, numIntervals)
// Oldest to newest interval (charts are left to right)
for i := 0; i < numIntervals; i++ {
interval := &stakeVersionInfo.Intervals[numIntervals-1-i]
stakeVersionLabels[i] = fmt.Sprintf("%v - %v", interval.StartHeight, interval.EndHeight-1)
if i == numIntervals-1 {
CurrentSVIEndHeightHeight = interval.StartHeight + activeNetParams.StakeVersionInterval - 1
templateInformation.CurrentSVIStartHeight = interval.StartHeight
templateInformation.CurrentSVIEndHeight = CurrentSVIEndHeightHeight
}
versionloop:
for _, versionCount := range interval.VoteVersions {
// Is this a vote version we've seen in a previous interval?
for k, result := range stakeVersionIntervalResults {
if result.Version == versionCount.Version {
stakeVersionIntervalResults[k].Count[i] = versionCount.Count
continue versionloop
}
}
if versionCount.Count > minimumNeededVoteVersions {
stakeVersionIntervalResult := intervalVersionCounts{
Version: versionCount.Version,
Count: make([]uint32, numIntervals),
}
stakeVersionIntervalResult.Count[i] = versionCount.Count
stakeVersionIntervalResults = append(stakeVersionIntervalResults, stakeVersionIntervalResult)
}
}
}
blocksRemainingStakeInterval := CurrentSVIEndHeightHeight - height
timeLeftDuration := activeNetParams.TargetTimePerBlock * time.Duration(blocksRemainingStakeInterval)
templateInformation.StakeVersionTimeRemaining = fmtDuration(timeLeftDuration)
stakeVersionLabels[numIntervals-1] = "Current Interval"
currentInterval := stakeVersionInfo.Intervals[0]
maxPossibleVotes := activeNetParams.StakeVersionInterval*int64(activeNetParams.TicketsPerBlock) -
int64(missedVotesStakeInterval)
templateInformation.StakeVersionIntervalResults = stakeVersionIntervalResults
templateInformation.StakeVersionIntervalLabels = stakeVersionLabels
templateInformation.StakeVersionCurrent = latestBlockHeader.StakeVersion
var mostPopularVersion, mostPopularVersionCount uint32
for _, stakeVersion := range currentInterval.VoteVersions {
if stakeVersion.Version > latestBlockHeader.StakeVersion &&
stakeVersion.Count > mostPopularVersionCount {
mostPopularVersion = stakeVersion.Version
mostPopularVersionCount = stakeVersion.Count
}
}
templateInformation.StakeVersionMostPopularPercentage = float64(mostPopularVersionCount) / float64(maxPossibleVotes) * 100
templateInformation.StakeVersionMostPopular = mostPopularVersion
svis, err := AllStakeVersionIntervals(ctx, dcrdClient, height)
if err != nil {
log.Printf("Error stake version intervals: %v", err)
return
}
templateInformation.PosUpgrade.Completed = false
// Check if upgrade to the latest version occurred in a previous SVI
upgradeOccurred, svi := svis.GetStakeVersionUpgradeSVI(svis.MaxVoteVersion)
if upgradeOccurred {
templateInformation.StakeVersionMostPopularPercentage = 100
templateInformation.PosUpgrade.Completed = true
templateInformation.PosUpgrade.UpgradeInterval = svi
}
// Check if Phase Upgrading or Voting
if templateInformation.PosUpgrade.Completed && templateInformation.BlockVersionSuccess {
templateInformation.IsUpgrading = false
} else {
templateInformation.IsUpgrading = true
}
templateInformation.Agendas, err = agendasForVersions(ctx, dcrdClient, height, svis)
if err != nil {
log.Printf("Error getting agendas: %v", err)
return
}
// Assume all agendas have been voted and are pending activation
templateInformation.PendingActivation = true
templateInformation.RulesActivated = true
for _, agenda := range templateInformation.Agendas {
// Check to see if all agendas are pending activation
if !agenda.IsLockedIn() {
templateInformation.PendingActivation = false
}
if !agenda.IsActive() {
templateInformation.RulesActivated = false
}
}
}
// main wraps mainCore, which does all the work, because deferred functions do
// not run after os.Exit().
func main() {
os.Exit(mainCore())
}
func mainCore() int {
cfg, err := loadConfig()
if err != nil {
return 1
}
// Chans for rpccclient notification handlers
connectChan := make(chan wire.BlockHeader, 100)
// Read in current dcrd cert
var dcrdCerts []byte
if !cfg.DisableTLS {
dcrdCerts, err = os.ReadFile(cfg.RPCCert)
if err != nil {
log.Printf("Failed to read dcrd cert file at %v: %v",
cfg.RPCCert, err)
return 1
}
}
// Set up notification handler that will release ntfns when new blocks connect
ntfnHandlersDaemon := rpcclient.NotificationHandlers{
OnBlockConnected: func(serializedBlockHeader []byte, _ [][]byte) {
var blockHeader wire.BlockHeader
errLocal := blockHeader.FromBytes(serializedBlockHeader)
if errLocal != nil {
log.Printf("Failed to deserialize block header: %v", errLocal)
return
}
log.Printf("Received new block %v (height %d)", blockHeader.BlockHash(),
blockHeader.Height)
connectChan <- blockHeader
},
}
// rpclient configuration
connCfgDaemon := &rpcclient.ConnConfig{
Host: cfg.RPCHost,
Endpoint: "ws",
User: cfg.RPCUser,
Pass: cfg.RPCPass,
Certificates: dcrdCerts,
DisableTLS: cfg.DisableTLS,
}
log.Printf("Attempting to connect to dcrd RPC %s as user %s "+
"using certificate %s", cfg.RPCHost, cfg.RPCUser, cfg.RPCCert)
// Attempt to connect rpcclient and daemon
dcrdClient, err := rpcclient.New(connCfgDaemon, &ntfnHandlersDaemon)
if err != nil {
log.Printf("Failed to start dcrd rpcclient: %v", err)
return 1
}
defer func() {
log.Printf("Disconnecting from dcrd.")
dcrdClient.Disconnect()
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Subscribe to block notifications
if err = dcrdClient.NotifyBlocks(ctx); err != nil {
log.Printf("Failed to start register daemon rpc client for "+
"block notifications: %v\n", err)
return 1
}
// Only accept a single CTRL+C
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
// Start waiting for the interrupt signal
go func() {
<-c
signal.Stop(c)
// Close the channel so multiple goroutines can get the message
log.Println("CTRL+C hit. Closing.")
cancel()
}()
// Get the current best block (height and hash)
hash, err := dcrdClient.GetBestBlockHash(ctx)
if err != nil {
log.Println(err)
return 1
}
// Request the current block header
latestBlockHeader, err := dcrdClient.GetBlockHeader(ctx, hash)
if err != nil {
log.Println(err)
return 1
}
// Run an initial templateInforation update based on current change
updatetemplateInformation(ctx, dcrdClient, latestBlockHeader)
// Run goroutine for notifications
var wg sync.WaitGroup
wg.Add(1)
go func() {
for {
select {
case blkHdr := <-connectChan:
log.Printf("Block %v (height %v) connected",
blkHdr.BlockHash(), blkHdr.Height)
updatetemplateInformation(ctx, dcrdClient, &blkHdr)
case <-ctx.Done():
log.Println("Closing dcrvotingweb")
wg.Done()
return
}
}
}()
// Create new web UI to deal with HTML templates and provide the
// http.HandleFunc for the web server
webUI, err := NewWebUI()
if err != nil {
log.Printf("NewWebUI failed: %v", err)
os.Exit(1)
}
webUI.TemplateData = templateInformation
// Register OS signal (USR1 on non-Windows platforms) to reload templates
webUI.UseSIGToReloadTemplates()
noDirListing := func(h http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/") {
webUI.homePage(w, r)
return
}
h.ServeHTTP(w, r)
})
}
// URL handlers for js/css/fonts/images
http.HandleFunc("/", webUI.homePage)
http.Handle("/js/", noDirListing(http.StripPrefix("/js/", http.FileServer(http.Dir("public/js/")))))
http.Handle("/css/", noDirListing(http.StripPrefix("/css/", http.FileServer(http.Dir("public/css/")))))
http.Handle("/fonts/", noDirListing(http.StripPrefix("/fonts/", http.FileServer(http.Dir("public/fonts/")))))
http.Handle("/images/", noDirListing(http.StripPrefix("/images/", http.FileServer(http.Dir("public/images/")))))
// Start http server listening and serving, but no way to signal to quit
go func() {
log.Printf("Starting webserver on %v", cfg.Listen)
err = http.ListenAndServe(cfg.Listen, nil) // #nosec G114 - Ignore linter warning: "G114: Use of net/http serve function that has no support for setting timeouts (gosec)"
if err != nil {
log.Printf("Failed to bind http server: %v", err)
cancel()
}
}()
// Wait for goroutines, such as the block connected handler loop
wg.Wait()
return 0
}
// fmtDuration will convert a Duration into a human readable string formatted "0d 0h 0m".
func fmtDuration(dur time.Duration) string {
dur = dur.Round(time.Minute)
days := dur / (time.Hour * 24)
dur -= days * (time.Hour * 24)
hours := dur / time.Hour
dur -= hours * time.Hour
mins := dur / time.Minute
return fmt.Sprintf("%dd %dh %dm", days, hours, mins)
}