├── backend └── stakepoold │ ├── .gitignore │ ├── rpc │ ├── regen.sh │ └── server │ │ └── log.go │ ├── semver.go │ ├── stakepool │ ├── log.go │ └── client.go │ ├── userdata │ ├── log.go │ └── userdata.go │ ├── README.md │ ├── ntfnhandlers.go │ ├── params.go │ └── log.go ├── docs ├── img │ └── architecture.png ├── version-history.md └── release-note-1.1.1.md ├── public ├── images │ ├── favicon.png │ ├── code-example.png │ ├── 1-panel-desktop.png │ ├── 1-panel-mobile.png │ ├── 1-panel-tablet.png │ ├── 2-panel-desktop.png │ ├── 2-panel-mobile.png │ ├── 2-panel-tablet.png │ ├── 3-panel-desktop.png │ ├── 3-panel-mobile.png │ ├── 3-panel-tablet.png │ ├── code-example@2x.png │ ├── code-example@3x.png │ ├── favicon │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-70x70.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── ic_launcher_hdpi.png │ │ ├── ic_launcher_mdpi.png │ │ ├── ic_launcher_xhdpi.png │ │ ├── ic_launcher_xxhdpi.png │ │ ├── ic_launcher_xxxhdpi.png │ │ ├── apple-touch-icon-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-114x114.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-144x144.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── browserconfig.xml │ │ └── manifest.json │ ├── primary-positive-full-color.png │ ├── primary-positive-full-color@2x.png │ ├── primary-positive-full-color@3x.png │ ├── arrow-prev.svg │ ├── arrow-next.svg │ ├── arrow-down.svg │ ├── arrow-back.svg │ ├── status.svg │ ├── arrow-back--blue.svg │ ├── close-cross-icon.svg │ ├── group-1119.svg │ ├── info-icon-sm.svg │ ├── info-icon.svg │ ├── info-icon-lg.svg │ ├── warning-sign.svg │ ├── notifications │ │ ├── Success.svg │ │ ├── key-success.svg │ │ ├── Ticket-failed.svg │ │ ├── Ticket-success.svg │ │ ├── ticketVoted-1.svg │ │ ├── ticketVoted.svg │ │ ├── key-failed.svg │ │ └── error-icon.svg │ ├── arrow-up-down.svg │ ├── logo.svg │ ├── group-1669.svg │ ├── indicator-finished.svg │ ├── symbol-8-1.svg │ ├── indicator-upcoming.svg │ ├── group-519.svg │ ├── circular-graphic-with-quarter-portion.svg │ ├── symbol-5-1-1.svg │ ├── symbol-9-1.svg │ ├── indicator-in-progress.svg │ ├── indicator-failed.svg │ ├── group-1079.svg │ ├── group-511.svg │ ├── symbol-5-1.svg │ ├── group-1120.svg │ ├── wallet-icon.svg │ ├── group-510.svg │ ├── servers.svg │ ├── group-526.svg │ ├── group-442.svg │ ├── group-517.svg │ ├── primary-positive-full-color.svg │ ├── group-505.svg │ └── group-1342.svg ├── css │ └── fonts │ │ ├── SourceSansPro-It │ │ ├── SourceSansPro-It.eot │ │ ├── SourceSansPro-It.ttf │ │ ├── SourceSansPro-It.ttf.woff │ │ └── SourceSansPro-It.ttf.woff2 │ │ ├── SourceCodePro-Regular │ │ ├── SourceCodePro-Regular.eot │ │ ├── SourceCodePro-Regular.ttf │ │ ├── SourceCodePro-Regular.ttf.woff │ │ └── SourceCodePro-Regular.ttf.woff2 │ │ ├── SourceSansPro-Regular │ │ ├── SourceSansPro-Regular.eot │ │ ├── SourceSansPro-Regular.ttf │ │ ├── SourceSansPro-Regular.ttf.woff │ │ └── SourceSansPro-Regular.ttf.woff2 │ │ ├── SourceSansPro-Semibold │ │ ├── SourceSansPro-Semibold.eot │ │ ├── SourceSansPro-Semibold.ttf │ │ ├── SourceSansPro-Semibold.ttf.woff │ │ └── SourceSansPro-Semibold.ttf.woff2 │ │ └── SourceSansPro-SemiboldIt │ │ ├── SourceSansPro-SemiboldIt.eot │ │ ├── SourceSansPro-SemiboldIt.ttf │ │ ├── SourceSansPro-SemiboldIt.ttf.woff │ │ └── SourceSansPro-SemiboldIt.ttf.woff2 └── js │ ├── vendor │ ├── dataTables.bootstrap4.min.js │ ├── d3-path.v1.min.js │ ├── d3-collection.v1.min.js │ └── d3-format.v1.min.js │ ├── global.js │ ├── admintickets.js │ ├── stats.js │ └── index.js ├── .gitignore ├── system ├── sigs_windows.go ├── sigs_unix.go ├── sigs.go ├── log.go ├── controller.go └── middleware.go ├── helpers ├── template.go ├── address.go └── address_test.go ├── views ├── emailupdate.html ├── emailverify.html ├── error.html ├── passwordreset.html ├── auth │ ├── login.html │ └── register.html ├── captcha.html ├── passwordupdate.html ├── footer.html ├── admin │ ├── status.html │ └── tickets.html ├── header.html ├── settings.html ├── voting.html └── main.html ├── stakepooldclient ├── semver.go └── log.go ├── .github └── workflows │ └── go.yml ├── internal └── version │ ├── README.md │ └── version.go ├── goclean.sh ├── models ├── log.go └── user_test.go ├── signal ├── log.go └── signal.go ├── controllers ├── log.go └── captcha.go ├── sample-dcrwallet.conf ├── zipassets.sh ├── go.mod ├── sample-stakepoold.conf ├── poolapi └── apijson.go ├── LICENSE ├── apiClientExample.sh ├── params.go ├── sample-nginx.conf ├── log.go ├── config_test.go └── sample-dcrstakepool.conf /backend/stakepoold/.gitignore: -------------------------------------------------------------------------------- 1 | stakepoold 2 | vendor 3 | *~ 4 | .vscode 5 | -------------------------------------------------------------------------------- /docs/img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/docs/img/architecture.png -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /backend/stakepoold/rpc/regen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | protoc -I. api.proto --go_out=plugins=grpc:stakepoolrpc 4 | -------------------------------------------------------------------------------- /public/images/code-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/code-example.png -------------------------------------------------------------------------------- /public/images/1-panel-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/1-panel-desktop.png -------------------------------------------------------------------------------- /public/images/1-panel-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/1-panel-mobile.png -------------------------------------------------------------------------------- /public/images/1-panel-tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/1-panel-tablet.png -------------------------------------------------------------------------------- /public/images/2-panel-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/2-panel-desktop.png -------------------------------------------------------------------------------- /public/images/2-panel-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/2-panel-mobile.png -------------------------------------------------------------------------------- /public/images/2-panel-tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/2-panel-tablet.png -------------------------------------------------------------------------------- /public/images/3-panel-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/3-panel-desktop.png -------------------------------------------------------------------------------- /public/images/3-panel-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/3-panel-mobile.png -------------------------------------------------------------------------------- /public/images/3-panel-tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/3-panel-tablet.png -------------------------------------------------------------------------------- /public/images/code-example@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/code-example@2x.png -------------------------------------------------------------------------------- /public/images/code-example@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/code-example@3x.png -------------------------------------------------------------------------------- /public/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/favicon.ico -------------------------------------------------------------------------------- /public/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/images/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /public/images/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /public/images/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /public/images/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /public/images/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /public/images/favicon/ic_launcher_hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/ic_launcher_hdpi.png -------------------------------------------------------------------------------- /public/images/favicon/ic_launcher_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/ic_launcher_mdpi.png -------------------------------------------------------------------------------- /public/images/favicon/ic_launcher_xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/ic_launcher_xhdpi.png -------------------------------------------------------------------------------- /public/images/favicon/ic_launcher_xxhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/ic_launcher_xxhdpi.png -------------------------------------------------------------------------------- /public/images/favicon/ic_launcher_xxxhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/ic_launcher_xxxhdpi.png -------------------------------------------------------------------------------- /public/images/primary-positive-full-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/primary-positive-full-color.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/images/primary-positive-full-color@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/primary-positive-full-color@2x.png -------------------------------------------------------------------------------- /public/images/primary-positive-full-color@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/primary-positive-full-color@3x.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/images/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/images/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-It/SourceSansPro-It.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-It/SourceSansPro-It.eot -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-It/SourceSansPro-It.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-It/SourceSansPro-It.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.toml 2 | dcrstakepool 3 | .vscode/ 4 | .idea/ 5 | controllers/config.go* 6 | testing/ 7 | *.orig 8 | debug 9 | public/**/*.gz 10 | vendor 11 | -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-It/SourceSansPro-It.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-It/SourceSansPro-It.ttf.woff -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-It/SourceSansPro-It.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-It/SourceSansPro-It.ttf.woff2 -------------------------------------------------------------------------------- /public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.eot -------------------------------------------------------------------------------- /public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.eot -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.eot -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.ttf -------------------------------------------------------------------------------- /public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.ttf.woff -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.ttf.woff -------------------------------------------------------------------------------- /public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceCodePro-Regular/SourceCodePro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Regular/SourceSansPro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.ttf.woff -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.eot -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.ttf -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-Semibold/SourceSansPro-Semibold.ttf.woff2 -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.ttf.woff -------------------------------------------------------------------------------- /public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/dcrstakepool/HEAD/public/css/fonts/SourceSansPro-SemiboldIt/SourceSansPro-SemiboldIt.ttf.woff2 -------------------------------------------------------------------------------- /system/sigs_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package system 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | func ReloadTemplatesSig(_ *Application) { 10 | fmt.Println("Signals are unsupported on Windows.") 11 | } 12 | -------------------------------------------------------------------------------- /public/images/arrow-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/arrow-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/arrow-back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/status.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/arrow-back--blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/close-cross-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /system/sigs_unix.go: -------------------------------------------------------------------------------- 1 | // +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris 2 | 3 | package system 4 | 5 | import "syscall" 6 | 7 | // ReloadTemplatesSig forces the html templates to be reloaded by signalling 8 | // SIGUSR1. 9 | func ReloadTemplatesSig(app *Application) { 10 | reloadTemplatesSig(syscall.SIGUSR1, app) 11 | } 12 | -------------------------------------------------------------------------------- /public/images/group-1119.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /helpers/template.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | ) 7 | 8 | // Parse parses html templates and returns the template as a string. 9 | func Parse(t *template.Template, name string, data interface{}) (string, error) { 10 | var doc bytes.Buffer 11 | err := t.ExecuteTemplate(&doc, name, data) 12 | if err != nil { 13 | return "", err 14 | } 15 | return doc.String(), nil 16 | } 17 | -------------------------------------------------------------------------------- /public/images/info-icon-sm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/info-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/info-icon-lg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/warning-sign.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/notifications/Success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/arrow-up-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /system/sigs.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | ) 7 | 8 | func reloadTemplatesSig(sig os.Signal, app *Application) { 9 | sigChan := make(chan os.Signal, 1) 10 | signal.Notify(sigChan, sig) 11 | 12 | go func() { 13 | for { 14 | sigr := <-sigChan 15 | log.Infof("Received: %s", sig) 16 | if sigr == sig { 17 | err := app.LoadTemplates(app.TemplatesPath) 18 | log.Infof("LoadTemplates() executed.") 19 | if err != nil { 20 | log.Errorf("LoadTemplates failed: %v", err) 21 | } 22 | } 23 | } 24 | }() 25 | } 26 | -------------------------------------------------------------------------------- /public/images/group-1669.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/images/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #091440 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/indicator-finished.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/images/symbol-8-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/images/indicator-upcoming.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/group-519.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/images/circular-graphic-with-quarter-portion.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /views/emailupdate.html: -------------------------------------------------------------------------------- 1 | {{define "emailupdate"}} 2 |
3 |
4 |
5 |
6 | {{with .FlashSuccess}} 7 |

Email Successfully Updated

8 |

You may now Login with your new email address.

9 | {{else}} 10 |

Email Update Error

11 | {{range .FlashError}} 12 |

{{.}}

13 | {{end}} 14 | {{end}} 15 |
16 |
17 |
18 |
19 | {{end}} 20 | -------------------------------------------------------------------------------- /public/images/notifications/key-success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /views/emailverify.html: -------------------------------------------------------------------------------- 1 | {{define "emailverify"}} 2 |
3 |
4 |
5 |
6 | 7 | {{with .FlashSuccess}} 8 |

Email Successfully Verified

9 |

You may now Login with your new email address.

10 | {{else}} 11 |

Email Verification Error

12 | {{range .FlashError}} 13 |

{{.}}

14 | {{end}} 15 | {{end}} 16 |
17 |
18 |
19 |
20 | {{end}} 21 | -------------------------------------------------------------------------------- /public/images/symbol-5-1-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/symbol-9-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /backend/stakepoold/semver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import "fmt" 8 | 9 | type semver struct { 10 | major, minor, patch uint32 11 | } 12 | 13 | func semverCompatible(required, actual semver) bool { 14 | switch { 15 | case required.major != actual.major: 16 | return false 17 | case required.minor > actual.minor: 18 | return false 19 | case required.minor == actual.minor && required.patch > actual.patch: 20 | return false 21 | default: 22 | return true 23 | } 24 | } 25 | 26 | func (s semver) String() string { 27 | return fmt.Sprintf("%d.%d.%d", s.major, s.minor, s.patch) 28 | } 29 | -------------------------------------------------------------------------------- /stakepooldclient/semver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package stakepooldclient 6 | 7 | import "fmt" 8 | 9 | type semver struct { 10 | major, minor, patch uint32 11 | } 12 | 13 | func semverCompatible(required, actual semver) bool { 14 | switch { 15 | case required.major != actual.major: 16 | return false 17 | case required.minor > actual.minor: 18 | return false 19 | case required.minor == actual.minor && required.patch > actual.patch: 20 | return false 21 | default: 22 | return true 23 | } 24 | } 25 | 26 | func (s semver) String() string { 27 | return fmt.Sprintf("%d.%d.%d", s.major, s.minor, s.patch) 28 | } 29 | -------------------------------------------------------------------------------- /public/images/notifications/Ticket-failed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/indicator-in-progress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/images/notifications/Ticket-success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/images/indicator-failed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | name: Go CI 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | go: [1.14, 1.15] 10 | steps: 11 | - name: Set up Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ${{ matrix.go }} 15 | - name: Check out source 16 | uses: actions/checkout@v2 17 | - name: Install Linters 18 | run: "curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.33.0" 19 | - name: Build 20 | env: 21 | GO111MODULE: "on" 22 | run: go build ./... 23 | - name: Test 24 | env: 25 | GO111MODULE: "on" 26 | run: | 27 | ./goclean.sh 28 | -------------------------------------------------------------------------------- /internal/version/README.md: -------------------------------------------------------------------------------- 1 | version 2 | ======= 3 | 4 | [![Build Status](https://github.com/decred/dcrstakepool/workflows/Build%20and%20Test/badge.svg)](https://github.com/decred/dcrstakepool/actions) 5 | [![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) 6 | [![Doc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrstakepool/internal/version) 7 | 8 | Package version provides a single location to house the version information for 9 | dcrstakepool and other utilities provided in the same repository. 10 | 11 | ## Installation and Updating 12 | 13 | This package is internal and therefore is neither directly installed nor needs 14 | to be manually updated. 15 | 16 | ## License 17 | 18 | Package version is licensed under the [copyfree](http://copyfree.org) ISC 19 | License. 20 | -------------------------------------------------------------------------------- /goclean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # The script does automatic checking on a Go package and its sub-packages, including: 3 | # 1. gofmt (http://golang.org/cmd/gofmt/) 4 | # 2. go vet (http://golang.org/cmd/vet) 5 | # 3. gosimple (https://github.com/dominikh/go-simple) 6 | # 4. unconvert (https://github.com/mdempsky/unconvert) 7 | # 5. ineffassign (https://github.com/gordonklaus/ineffassign) 8 | # 6. race detector (http://blog.golang.org/race-detector) 9 | # 7. test coverage (http://blog.golang.org/cover) 10 | 11 | set -ex 12 | 13 | # run tests 14 | env GORACE="halt_on_error=1" go test -race ./... 15 | 16 | # golangci-lint (github.com/golangci/golangci-lint) is used to run each each 17 | # static checker. 18 | 19 | # check linters 20 | golangci-lint run --disable-all --deadline=10m \ 21 | --enable=gofmt \ 22 | --enable=vet \ 23 | --enable=gosimple \ 24 | --enable=unconvert \ 25 | --enable=ineffassign 26 | -------------------------------------------------------------------------------- /models/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package models 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /signal/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package signal 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /system/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package system 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /controllers/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package controllers 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /stakepooldclient/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package stakepooldclient 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /backend/stakepoold/rpc/server/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package server 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /backend/stakepoold/stakepool/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package stakepool 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /backend/stakepoold/userdata/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2015 The btcsuite developers 2 | // Copyright (c) 2018 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package userdata 7 | 8 | import "github.com/decred/slog" 9 | 10 | // log is a logger that is initialized with no output filters. This 11 | // means the package will not perform any logging by default until the caller 12 | // requests it. 13 | var log = slog.Disabled 14 | 15 | // DisableLog disables all library log output. Logging output is disabled 16 | // by default until either UseLogger or SetLogWriter are called. 17 | func DisableLog() { 18 | log = slog.Disabled 19 | } 20 | 21 | // UseLogger uses a specified Logger to output package logging info. 22 | // This should be used in preference to SetLogWriter if the caller is also 23 | // using slog. 24 | func UseLogger(logger slog.Logger) { 25 | log = logger 26 | } 27 | -------------------------------------------------------------------------------- /views/error.html: -------------------------------------------------------------------------------- 1 | {{define "error"}} 2 |
3 |
4 | 5 |
6 | 7 |
8 |
9 |

{{if .RateLimited}}Rate Limited{{else}}Error{{end}}

10 |

11 | {{if .RateLimited}} 12 | Your request has been rate limited to lighten the load on the servers. Please retry your request. 13 | {{else}} 14 | An error occurred. Please retry your request or contact the VSP admin if the error continues. Wallets are still online and voting. 15 | {{end}} 16 |

17 |
18 |
19 |
20 |
21 |
22 | 23 | {{end}} 24 | -------------------------------------------------------------------------------- /sample-dcrwallet.conf: -------------------------------------------------------------------------------- 1 | ; Derive 10000 addresses from the specified extended public key for use 2 | ; as user's payment addresses to the cold wallet/fee collecting wallet. 3 | ; xpub portion needs to match coldwalletextpub from dcrstakepool configuration. 4 | ; The amount of addresses MUST be set to 10000. This limitation will be removed 5 | ; in a future release. 6 | ;stakepoolcoldextkey=xpub:10000 7 | 8 | ; Fees as a percentage. 7.5 = 7.5%. Precision of 2, 7.99 = 7.99%. 9 | ; Needs to match dcrstakepool's configuration. 10 | ;poolfees=7.5 11 | 12 | ; Probably need this but depends on your setup. 13 | ;rpclisten=0.0.0.0 14 | 15 | ; Debug is very useful to see more activity. 16 | debuglevel=debug 17 | 18 | ; stakepoold will do the voting, dcrwallet should not vote. 19 | enablevoting=0 20 | 21 | ; Useful to make sure the wallet is unlocked at startup. 22 | promptpass=1 23 | 24 | ; Stay on testnet until everything is well tested. 25 | testnet=1 26 | 27 | ; RPC auth stuff. 28 | ;username=user 29 | ;password=pass 30 | ;dcrdusername=user 31 | ;dcrdpassword=pass 32 | -------------------------------------------------------------------------------- /public/images/notifications/ticketVoted-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/images/notifications/ticketVoted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /backend/stakepoold/README.md: -------------------------------------------------------------------------------- 1 | stakepoold 2 | ==== 3 | 4 | The goal of stakepoold is to communicate with dcrd/dcrwallet/dcrstakepool via client/server gRPC in order to handle all stakepool functions that are currently in dcrwallet or are undefined/unhandled. 5 | 6 | ## First: 7 | 8 | Receive, store, and act on (vote) per-user voting policy from dcrstakepool. 9 | 10 | #### Steps 11 | 12 | 1. stakepoold skeleton code with testnet/mainnet flags with per-network vote version 13 | 2. wire up stakepoold to get notified of winners, set votebits according to prefs/vote version, ask wallet to sign, send 14 | 3. add user voting policy interface to dcrstakepool 15 | 4. send dcrstakepool user voting policy config to stakepoold and store it 16 | 17 | ## Second: 18 | 19 | Rip out all stakepool-related configuration from the wallet. (ticket adding, multisig scripts, fee checking, votebits modification RPCs) 20 | 21 | #### Steps 22 | 23 | 1. Migrate the rest of the stakepool-related functionality from wallet to stakepoold. 24 | 2. Modify dcrstakepool to cope with changes. dcrstakepool should not need to talk to dcrwallet directly anymore. -------------------------------------------------------------------------------- /public/images/group-1079.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/images/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Decred", 3 | "icons": [ 4 | { 5 | "src": "/assets/images/favicon/ic_launcher_mdpi.png?v=vMddPLeYWy", 6 | "sizes": "48x48", 7 | "type": "image/png" 8 | }, 9 | { 10 | "src": "/assets/images/favicon/ic_launcher_hdpi.png?v=vMddPLeYWy", 11 | "sizes": "72x72", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "/assets/images/favicon/ic_launcher_xhdpi.png?v=vMddPLeYWy", 16 | "sizes": "96x96", 17 | "type": "image/png" 18 | }, 19 | { 20 | "src": "/assets/images/favicon/ic_launcher_xxhdpi.png?v=vMddPLeYWy", 21 | "sizes": "144x144", 22 | "type": "image/png" 23 | }, 24 | { 25 | "src": "/assets/images/favicon/ic_launcher_xxxhdpi.png?v=vMddPLeYWy", 26 | "sizes": "192x192", 27 | "type": "image/png" 28 | } 29 | ], 30 | "theme_color": "#091440", 31 | "background_color": "#091440", 32 | "start_url": "/", 33 | "display": "browser" 34 | } 35 | -------------------------------------------------------------------------------- /public/images/group-511.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/images/symbol-5-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /helpers/address.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package helpers 6 | 7 | import ( 8 | "github.com/decred/dcrd/chaincfg/v3" 9 | "github.com/decred/dcrd/dcrec" 10 | "github.com/decred/dcrd/dcrutil/v3" 11 | "github.com/decred/dcrd/hdkeychain/v3" 12 | ) 13 | 14 | const ( 15 | // ExternalBranch is a helper value that needs to 16 | // match dcrwallet's udb.ExternalBranch 17 | ExternalBranch uint32 = 0 18 | ) 19 | 20 | // DCRUtilAddressFromExtendedKey parses the public address of a hd extended key 21 | // using a secp256k1 elliptic curve into a ECDSA public key, compresses it using 22 | // ripemd160, and wraps it in a dcrutil AddressPubKeyHash in order to easily 23 | // obtain its human readable formats. Returns an error upon a parsing error or 24 | // if key is for the wrong network. 25 | func DCRUtilAddressFromExtendedKey(key *hdkeychain.ExtendedKey, params *chaincfg.Params) (*dcrutil.AddressPubKeyHash, error) { 26 | return dcrutil.NewAddressPubKeyHash(dcrutil.Hash160(key.SerializedPubKey()), params, dcrec.STEcdsaSecp256k1) 27 | } 28 | -------------------------------------------------------------------------------- /public/images/group-1120.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/images/wallet-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /zipassets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script helps prepare pre-zipped static assets for use with reverse proxy 4 | # features like nginx's gzip_static. 5 | 6 | echo "Gzipping assets for use with gzip_static..." 7 | find ./public -type f -name "*.gz" -execdir rm {} \; 8 | # Use GNU parallel if it is installed. 9 | if [ -x "$(command -v parallel)" ]; then 10 | if [ -x "$(command -v 7za)" ]; then 11 | find ./public -type f -not -name "*.gz" | parallel --will-cite --bar 7za a -tgzip -mx=9 -mpass=13 {}.gz {} > /dev/null 12 | else 13 | find ./public -type f -not -name "*.gz" | parallel --will-cite --bar gzip -k9f {} > /dev/null 14 | fi 15 | elif [ -x "$(command -v 7za)" ]; then 16 | find ./public -type f -not -name "*.gz" -execdir 7za a -tgzip -mx=9 -mpass=13 {}.gz {} \; > /dev/null 17 | else 18 | find ./public -type f -not -name "*.gz" -execdir gzip -k9f {} \; > /dev/null 19 | fi 20 | 21 | # Clean up incompressible files. 22 | find ./public -type f -name "*.png.gz" -execdir rm {} \; 23 | find ./public -type f -name "*.eot.gz" -execdir rm {} \; 24 | find ./public -type f -name "*.gz.gz" -execdir rm {} \; 25 | find ./public -type f -name "*.woff*.gz" -execdir rm {} \; 26 | -------------------------------------------------------------------------------- /views/passwordreset.html: -------------------------------------------------------------------------------- 1 | {{define "passwordreset"}} 2 |
3 |
4 |
5 |
6 | 7 |
8 |

Password Reset

9 | 10 | {{range .FlashError}} 11 |
12 |
13 |
14 |

{{.}}

15 |
16 |
17 | {{end}} 18 | 19 | {{if .FlashSuccess}} 20 | {{range .FlashSuccess}}

{{.}}

{{end}} 21 | 22 | {{else if .CaptchaDone}} 23 |
24 | 25 | {{ $.csrfField }} 26 | 27 |
28 | 29 | {{else}} 30 | {{ template "captcha" . }} 31 | {{end}} 32 |
33 |
34 | 35 |
36 |
37 |
38 | {{end}} 39 | -------------------------------------------------------------------------------- /public/images/group-510.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/servers.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/group-526.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/group-442.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /views/auth/login.html: -------------------------------------------------------------------------------- 1 | {{define "auth/login"}} 2 |
3 |
4 |
5 | 6 |
7 |
8 | Login 9 | Register 10 |
11 | 12 |

Login

13 | 14 |
15 |
16 | 17 | {{if .FlashError}} 18 |
19 | {{end}} 20 |
21 | {{range .FlashError}} 22 |
23 | {{.}} 24 |
25 | {{end}} 26 |
27 | 28 | 29 | {{ $.csrfField }} 30 | 31 | 32 | 33 |
34 |
35 |
36 | {{end}} 37 | 38 | -------------------------------------------------------------------------------- /views/captcha.html: -------------------------------------------------------------------------------- 1 | {{define "captcha"}} 2 | 3 |
4 | 5 | {{range .CaptchaError}} 6 |
7 |
8 |
9 |

{{.}}

10 |
11 |
12 | {{end}} 13 | 14 |
15 |

{{ .CaptchaMsg }}

16 |
17 | 18 |
19 | your captcha image 20 |
21 | 22 | 23 |
24 |
25 | 26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | {{ $.csrfField }} 34 |
35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 | {{end}} 43 | -------------------------------------------------------------------------------- /public/images/notifications/key-failed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/notifications/error-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /views/passwordupdate.html: -------------------------------------------------------------------------------- 1 | {{define "passwordupdate"}} 2 |
3 |
4 | 5 |
6 | {{if .FlashSuccess}} 7 |
8 | 9 |
10 |

Password Successfully Updated

11 |

You may now Login with your new password.

12 |
13 |
14 | {{else}} 15 | 37 | {{end}} 38 |
39 |
40 |
41 | 42 | {{end}} 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/decred/dcrstakepool 2 | 3 | go 1.13 4 | 5 | require ( 6 | decred.org/dcrwallet v1.6.0-rc4 7 | github.com/DATA-DOG/go-sqlmock v1.5.0 8 | github.com/dajohi/goemail v1.0.1 9 | github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f 10 | github.com/decred/dcrd/blockchain/stake/v3 v3.0.0 11 | github.com/decred/dcrd/certgen v1.1.1 12 | github.com/decred/dcrd/chaincfg/chainhash v1.0.2 13 | github.com/decred/dcrd/chaincfg/v3 v3.0.0 14 | github.com/decred/dcrd/dcrec v1.0.0 15 | github.com/decred/dcrd/dcrutil/v3 v3.0.0 16 | github.com/decred/dcrd/hdkeychain/v3 v3.0.0 17 | github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.3.0 18 | github.com/decred/dcrd/rpcclient/v6 v6.0.2 19 | github.com/decred/dcrd/wire v1.4.0 20 | github.com/decred/dcrdata/api/types/v5 v5.0.1 21 | github.com/decred/dcrdata/db/dbtypes/v2 v2.2.1 22 | github.com/decred/slog v1.1.0 23 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 24 | github.com/go-gorp/gorp v2.2.0+incompatible 25 | github.com/go-sql-driver/mysql v1.5.0 26 | github.com/golang/protobuf v1.4.3 27 | github.com/gorilla/csrf v1.7.0 28 | github.com/gorilla/securecookie v1.1.1 29 | github.com/gorilla/sessions v1.2.1 30 | github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7 31 | github.com/jrick/logrotate v1.0.0 32 | github.com/lib/pq v1.8.0 // indirect 33 | github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect 34 | github.com/poy/onpar v1.0.1 // indirect 35 | github.com/stretchr/testify v1.6.1 // indirect 36 | github.com/zenazn/goji v1.0.1 37 | github.com/ziutek/mymysql v1.5.4 // indirect 38 | golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 39 | google.golang.org/grpc v1.33.2 40 | ) 41 | -------------------------------------------------------------------------------- /sample-stakepoold.conf: -------------------------------------------------------------------------------- 1 | ; Specified extended public key is used to generate fee payment addresses 2 | ; which are presented to the user. 3 | ; Should match dcrstakepool's coldwalletextpub configuration and dcrwallet's 4 | ; stakepoolcoldextkey configuration. 5 | ;coldwalletextpub=xpub 6 | 7 | ; Fees as a percentage. 7.5 = 7.5%. Precision of 2, 7.99 = 7.99%. 8 | ; Should match dcrstakepool and dcrwallet's configuration. 9 | ;poolfees=7.5 10 | 11 | ; Stay on testnet until everything is well tested. Ideally, you should run 12 | ; on testnet with lots of tickets as a benchmark to ensure votes are cast 13 | ; within 100ms. 14 | testnet=1 15 | 16 | ; Database configuration defaults to these, change as needed. 17 | ;dbhost=localhost 18 | ;dbport=3306 19 | ;dbname=stakepool 20 | ;dbuser=stakepool 21 | 22 | ; No default password so you need to specify one. 23 | ;dbpassword= 24 | 25 | ; You should have dcrd running on localhost so winning tickets notifications 26 | ; and vote relaying is fast. 27 | dcrdhost=127.0.0.1 28 | dcrdcert=../.dcrd/rpc.cert 29 | ;dcrduser=user 30 | ;dcrdpassword=pass 31 | 32 | ; dcrwallet should be running on localhost so wallet RPCs are fast. 33 | wallethost=127.0.0.1 34 | walletcert=../.dcrwallet/rpc.cert 35 | ;walletuser=user 36 | ;walletpassword=pass 37 | 38 | ; Default is localhost. Probably want to uncomment to enable listening on all 39 | ; interfaces unless you have VPN/tunneling setup. 40 | ;rpclisten=0.0.0.0 41 | 42 | ; Debug logging level. 43 | ; Valid levels are {trace, debug, info, warn, error, critical} 44 | ; You may also specify =,=,... to set 45 | ; log level for individual subsystems. Use stakepoold --debuglevel=show to list 46 | ; available subsystems. 47 | ; debuglevel=info 48 | -------------------------------------------------------------------------------- /signal/signal.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2014 The btcsuite developers 2 | // Copyright (c) 2019 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | // Package signal provides a shutdown context and listener. 7 | package signal 8 | 9 | import ( 10 | "context" 11 | "os" 12 | "os/signal" 13 | ) 14 | 15 | // shutdownSignaled is closed whenever shutdown is invoked through an interrupt 16 | // signal. Any contexts created using withShutdownChannel are cancelled when 17 | // this is closed. 18 | var shutdownSignaled = make(chan struct{}) 19 | 20 | // WithShutdownCancel creates a copy of a context that is cancelled whenever 21 | // shutdown is invoked through an interrupt signal. 22 | func WithShutdownCancel(ctx context.Context) context.Context { 23 | ctx, cancel := context.WithCancel(ctx) 24 | go func() { 25 | <-shutdownSignaled 26 | cancel() 27 | }() 28 | return ctx 29 | } 30 | 31 | // ShutdownListener listens for shutdown requests and cancels all contexts 32 | // created from withShutdownCancel. This function never returns and is intended 33 | // to be spawned in a new goroutine. 34 | func ShutdownListener() { 35 | interruptChannel := make(chan os.Signal, 1) 36 | // Only accept a single CTRL+C. 37 | signal.Notify(interruptChannel, os.Interrupt) 38 | 39 | // Listen for the initial shutdown signal 40 | sig := <-interruptChannel 41 | log.Infof("Received signal (%s). Shutting down...", sig) 42 | 43 | // Cancel all contexts created from withShutdownCancel. 44 | close(shutdownSignaled) 45 | 46 | // Listen for any more shutdown signals and log that shutdown has already 47 | // been signaled. 48 | for { 49 | <-interruptChannel 50 | log.Info("Shutdown signaled. Already shutting down...") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/images/group-517.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /backend/stakepoold/ntfnhandlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/decred/dcrd/chaincfg/chainhash" 7 | "github.com/decred/dcrd/rpcclient/v6" 8 | "github.com/decred/dcrstakepool/backend/stakepoold/stakepool" 9 | ) 10 | 11 | // Define notification handlers 12 | func getNodeNtfnHandlers(spd *stakepool.Stakepoold) *rpcclient.NotificationHandlers { 13 | return &rpcclient.NotificationHandlers{ 14 | OnNewTickets: func(blockHash *chainhash.Hash, blockHeight int64, _ int64, tickets []*chainhash.Hash) { 15 | nt := stakepool.NewTicketsForBlock{ 16 | BlockHash: blockHash, 17 | BlockHeight: blockHeight, 18 | NewTickets: tickets, 19 | } 20 | spd.NewTicketsChan <- nt 21 | }, 22 | OnSpentAndMissedTickets: func(blockHash *chainhash.Hash, blockHeight int64, _ int64, tickets map[chainhash.Hash]bool) { 23 | ticketsFixed := make(map[*chainhash.Hash]bool) 24 | for ticketHash, spent := range tickets { 25 | ticketHash := ticketHash 26 | ticketsFixed[&ticketHash] = spent 27 | } 28 | smt := stakepool.SpentMissedTicketsForBlock{ 29 | BlockHash: blockHash, 30 | BlockHeight: blockHeight, 31 | SmTickets: ticketsFixed, 32 | } 33 | // Wait for a wallet connection if not connected. 34 | <-spd.WalletConnection.Connected() 35 | spd.SpentmissedTicketsChan <- smt 36 | }, 37 | OnWinningTickets: func(blockHash *chainhash.Hash, blockHeight int64, winningTickets []*chainhash.Hash) { 38 | wt := stakepool.WinningTicketsForBlock{ 39 | BlockHash: blockHash, 40 | BlockHeight: blockHeight, 41 | WinningTickets: winningTickets, 42 | } 43 | spd.WinningTicketsChan <- wt 44 | }, 45 | } 46 | } 47 | 48 | func getWalletNtfnHandlers() *rpcclient.NotificationHandlers { 49 | return &rpcclient.NotificationHandlers{ 50 | OnUnknownNotification: func(method string, params []json.RawMessage) { 51 | log.Infof("ignoring notification %v", method) 52 | log.Tracef("%#v", params) 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/js/vendor/dataTables.bootstrap4.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | DataTables Bootstrap 4 integration 3 | ©2011-2017 SpryMedia Ltd - datatables.net/license 4 | */ 5 | (function(b){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return b(a,window,document)}):"object"===typeof exports?module.exports=function(a,d){a||(a=window);if(!d||!d.fn.dataTable)d=require("datatables.net")(a,d).$;return b(d,a,a.document)}:b(jQuery,window,document)})(function(b,a,d,m){var f=b.fn.dataTable;b.extend(!0,f.defaults,{dom:"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", 6 | renderer:"bootstrap"});b.extend(f.ext.classes,{sWrapper:"dataTables_wrapper dt-bootstrap4",sFilterInput:"form-control form-control-sm",sLengthSelect:"custom-select custom-select-sm form-control form-control-sm",sProcessing:"dataTables_processing card",sPageButton:"paginate_button page-item"});f.ext.renderer.pageButton.bootstrap=function(a,h,r,s,j,n){var o=new f.Api(a),t=a.oClasses,k=a.oLanguage.oPaginate,u=a.oLanguage.oAria.paginate||{},e,g,p=0,q=function(d,f){var l,h,i,c,m=function(a){a.preventDefault(); 7 | !b(a.currentTarget).hasClass("disabled")&&o.page()!=a.data.action&&o.page(a.data.action).draw("page")};l=0;for(h=f.length;l", 8 | {"class":t.sPageButton+" "+g,id:0===r&&"string"===typeof c?a.sTableId+"_"+c:null}).append(b("",{href:"#","aria-controls":a.sTableId,"aria-label":u[c],"data-dt-idx":p,tabindex:a.iTabIndex,"class":"page-link"}).html(e)).appendTo(d),a.oApi._fnBindAction(i,{action:c},m),p++)}},i;try{i=b(h).find(d.activeElement).data("dt-idx")}catch(v){}q(b(h).empty().html('
    ').children("ul"),s);i!==m&&b(h).find("[data-dt-idx="+i+"]").focus()};return f}); 9 | -------------------------------------------------------------------------------- /poolapi/apijson.go: -------------------------------------------------------------------------------- 1 | package poolapi 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // Response is the JSON API response to all requests and holds data related to 8 | // the request if successful. 9 | type Response struct { 10 | Status string `json:"status"` 11 | Message string `json:"message"` 12 | Data *json.RawMessage `json:"data,omitempty"` 13 | } 14 | 15 | // TODO: make JSON tags lower-case and add "_" between words 16 | 17 | // PurchaseInfo is a JSON data struct related to a user's ticket purchases. 18 | type PurchaseInfo struct { 19 | PoolAddress string `json:"PoolAddress"` 20 | PoolFees float64 `json:"PoolFees"` 21 | Script string `json:"Script"` 22 | TicketAddress string `json:"TicketAddress"` 23 | VoteBits uint16 `json:"VoteBits"` 24 | VoteBitsVersion uint32 `json:"VoteBitsVersion"` 25 | } 26 | 27 | // Stats is a JSON data struct with information about the pool. 28 | type Stats struct { 29 | AllMempoolTix uint32 `json:"AllMempoolTix"` 30 | APIVersionsSupported []int `json:"APIVersionsSupported"` 31 | BlockHeight int64 `json:"BlockHeight"` 32 | Difficulty float64 `json:"Difficulty"` 33 | Expired uint32 `json:"Expired"` 34 | Immature uint32 `json:"Immature"` 35 | Live uint32 `json:"Live"` 36 | Missed uint32 `json:"Missed"` 37 | OwnMempoolTix uint32 `json:"OwnMempoolTix"` 38 | PoolSize uint32 `json:"PoolSize"` 39 | ProportionLive float64 `json:"ProportionLive"` 40 | ProportionMissed float64 `json:"ProportionMissed"` 41 | Revoked uint32 `json:"Revoked"` 42 | TotalSubsidy float64 `json:"TotalSubsidy"` 43 | Voted uint32 `json:"Voted"` 44 | Network string `json:"Network"` 45 | PoolEmail string `json:"PoolEmail"` 46 | PoolFees float64 `json:"PoolFees"` 47 | PoolStatus string `json:"PoolStatus"` 48 | UserCount int64 `json:"UserCount"` 49 | UserCountActive int64 `json:"UserCountActive"` 50 | Version string `json:"Version"` 51 | } 52 | -------------------------------------------------------------------------------- /public/js/vendor/d3-path.v1.min.js: -------------------------------------------------------------------------------- 1 | // https://d3js.org/d3-path/ v1.0.8 Copyright 2019 Mike Bostock 2 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t=t||self).d3=t.d3||{})}(this,function(t){"use strict";var i=Math.PI,s=2*i,h=s-1e-6;function e(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function _(){return new e}e.prototype=_.prototype={constructor:e,moveTo:function(t,i){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+i)},closePath:function(){null!==this._x1&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(t,i){this._+="L"+(this._x1=+t)+","+(this._y1=+i)},quadraticCurveTo:function(t,i,s,h){this._+="Q"+ +t+","+ +i+","+(this._x1=+s)+","+(this._y1=+h)},bezierCurveTo:function(t,i,s,h,e,_){this._+="C"+ +t+","+ +i+","+ +s+","+ +h+","+(this._x1=+e)+","+(this._y1=+_)},arcTo:function(t,s,h,e,_){t=+t,s=+s,h=+h,e=+e,_=+_;var n=this._x1,o=this._y1,r=h-t,a=e-s,u=n-t,f=o-s,c=u*u+f*f;if(_<0)throw new Error("negative radius: "+_);if(null===this._x1)this._+="M"+(this._x1=t)+","+(this._y1=s);else if(c>1e-6)if(Math.abs(f*r-a*u)>1e-6&&_){var x=h-n,y=e-o,M=r*r+a*a,l=x*x+y*y,d=Math.sqrt(M),p=Math.sqrt(c),v=_*Math.tan((i-Math.acos((M+c-l)/(2*d*p)))/2),b=v/p,w=v/d;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*u)+","+(s+b*f)),this._+="A"+_+","+_+",0,0,"+ +(f*x>u*y)+","+(this._x1=t+w*r)+","+(this._y1=s+w*a)}else this._+="L"+(this._x1=t)+","+(this._y1=s);else;},arc:function(t,e,_,n,o,r){t=+t,e=+e,r=!!r;var a=(_=+_)*Math.cos(n),u=_*Math.sin(n),f=t+a,c=e+u,x=1^r,y=r?n-o:o-n;if(_<0)throw new Error("negative radius: "+_);null===this._x1?this._+="M"+f+","+c:(Math.abs(this._x1-f)>1e-6||Math.abs(this._y1-c)>1e-6)&&(this._+="L"+f+","+c),_&&(y<0&&(y=y%s+s),y>h?this._+="A"+_+","+_+",0,1,"+x+","+(t-a)+","+(e-u)+"A"+_+","+_+",0,1,"+x+","+(this._x1=f)+","+(this._y1=c):y>1e-6&&(this._+="A"+_+","+_+",0,"+ +(y>=i)+","+x+","+(this._x1=t+_*Math.cos(o))+","+(this._y1=e+_*Math.sin(o))))},rect:function(t,i,s,h){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+i)+"h"+ +s+"v"+ +h+"h"+-s+"Z"},toString:function(){return this._}},t.path=_,Object.defineProperty(t,"__esModule",{value:!0})}); 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2020 The Decred developers 4 | Copyright (c) 2016, Jonathan Chappelow 5 | Copyright (c) 2014 Pawel Szymanski (pawel@mikesz.com) 6 | Copyright (c) HARUYAMA Seigo 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | The ISC License (ISC) 27 | 28 | Copyright (c) 2013-2016 The btcsuite developers 29 | Copyright (c) 2015-2018 The Decred developers 30 | 31 | Permission to use, copy, modify, and distribute this software for any 32 | purpose with or without fee is hereby granted, provided that the above 33 | copyright notice and this permission notice appear in all copies. 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 36 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 37 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 38 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 39 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 40 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 41 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 42 | -------------------------------------------------------------------------------- /views/auth/register.html: -------------------------------------------------------------------------------- 1 | {{define "auth/register"}} 2 |
    3 |
    4 |
    5 | 6 |
    7 | 11 | 12 |

    Register

    13 | 14 | {{range .FlashError}} 15 |
    16 |
    17 |
    18 |

    {{.}}

    19 |
    20 |
    21 | {{end}} 22 | 23 | {{if .IsClosed }} 24 |
    25 |
    26 |

    {{.ClosePoolMsg}}

    27 |
    28 |
    29 | 30 | {{else if .FlashSuccess}} 31 | {{range .FlashSuccess}} 32 |

    {{.}}

    33 | {{end}} 34 | 35 | {{else if .CaptchaDone}} 36 |
    37 | 38 | 39 | 40 | 41 | {{ $.csrfField }} 42 |
    43 | {{else}} 44 | {{ template "captcha" . }} 45 | {{end}} 46 |
    47 | 48 |
    49 |
    50 |
    51 | {{end}} 52 | -------------------------------------------------------------------------------- /models/user_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNewUserToken(t *testing.T) { 9 | token := NewUserToken() 10 | // For test data. 11 | t.Logf("%s, %#v", token, [userTokenSize]byte(token)) 12 | } 13 | 14 | func TestUserTokenFromStr(t *testing.T) { 15 | tests := []struct { 16 | testName string 17 | token string 18 | want UserToken 19 | wantErr bool 20 | }{ 21 | { 22 | "valid", 23 | "e7e2d71c3e97faefc0c752e212fc53e9", 24 | UserToken{0xe7, 0xe2, 0xd7, 0x1c, 25 | 0x3e, 0x97, 0xfa, 0xef, 26 | 0xc0, 0xc7, 0x52, 0xe2, 27 | 0x12, 0xfc, 0x53, 0xe9}, 28 | false, 29 | }, 30 | { 31 | "invalid length", 32 | "e2d71c3e97faefc0c752e212fc53e9", 33 | UserToken{0xe7, 0xe2, 0xd7, 0x1c, 34 | 0x3e, 0x97, 0xfa, 0xef, 35 | 0xc0, 0xc7, 0x52, 0xe2, 36 | 0x12, 0xfc, 0x53, 0xe9}, 37 | true, 38 | }, 39 | { 40 | "invalid chars", 41 | "xxx71c3e97faefc0c752e212fc53e9", 42 | UserToken{0xe7, 0xe2, 0xd7, 0x1c, 43 | 0x3e, 0x97, 0xfa, 0xef, 44 | 0xc0, 0xc7, 0x52, 0xe2, 45 | 0x12, 0xfc, 0x53, 0xe9}, 46 | true, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.testName, func(t *testing.T) { 51 | got, err := UserTokenFromStr(tt.token) 52 | if (err != nil) != tt.wantErr { 53 | t.Errorf("UserTokenFromStr() error = %v, wantErr %v", err, tt.wantErr) 54 | return 55 | } 56 | if tt.wantErr { 57 | return 58 | } 59 | if !reflect.DeepEqual(got, tt.want) { 60 | t.Errorf("UserTokenFromStr() = %v, want %v", got, tt.want) 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestUserToken_String(t *testing.T) { 67 | tests := []struct { 68 | testName string 69 | ut UserToken 70 | want string 71 | }{ 72 | { 73 | "valid", 74 | UserToken{0xe7, 0xe2, 0xd7, 0x1c, 75 | 0x3e, 0x97, 0xfa, 0xef, 76 | 0xc0, 0xc7, 0x52, 0xe2, 77 | 0x12, 0xfc, 0x53, 0xe9}, 78 | "e7e2d71c3e97faefc0c752e212fc53e9", 79 | }, 80 | } 81 | for _, tt := range tests { 82 | t.Run(tt.testName, func(t *testing.T) { 83 | if got := tt.ut.String(); got != tt.want { 84 | t.Errorf("UserToken.String() = %v, want %v", got, tt.want) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /public/images/primary-positive-full-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/group-505.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /public/js/global.js: -------------------------------------------------------------------------------- 1 | //mobile menu triggering 2 | $('#dismiss').on('click', function () { 3 | $('#sidebar, .menu-trigger').removeClass('active'); 4 | }); 5 | 6 | $('#sidebarCollapse').on('click', function () { 7 | $('#sidebar, .menu-trigger').addClass('active'); 8 | }); 9 | 10 | //disables the form submit buttons until the inputs are filled in 11 | function submitState(elSelector) { 12 | 13 | var $form = $(elSelector), 14 | $requiredInputs = $form.find('input:required'), 15 | $submit = $form.find('input[type="submit"]'); 16 | 17 | $submit.attr('disabled', 'disabled'); 18 | 19 | $requiredInputs.keyup(function () { 20 | 21 | $form.data('empty', 'false'); 22 | 23 | $requiredInputs.each(function() { 24 | if ($(this).val() === '') { 25 | $form.data('empty', 'true'); 26 | } 27 | }); 28 | 29 | if ($form.data('empty') === 'true') { 30 | $submit.attr('disabled', 'disabled').attr('title', 'fill in all required fields'); 31 | } else { 32 | $submit.removeAttr('disabled').attr('title', 'click to submit'); 33 | } 34 | }); 35 | } 36 | submitState('#Login'); 37 | submitState('#Register'); 38 | submitState('#Password'); 39 | submitState('#Reset'); 40 | submitState('#ChangeEmail'); 41 | submitState('#ChangePassword'); 42 | submitState('#captcha-form'); 43 | 44 | // hide input error when input's value changes 45 | $('.err-form-control').on("change paste keyup", function() { 46 | // reset styling of input 47 | $(this).removeClass('err-form-control'); 48 | // hides the error icon 49 | $(this).next().fadeOut(); 50 | // remove error text 51 | $(this).parent().next().fadeOut(); 52 | }); 53 | 54 | $(document).ready(function () { 55 | // Display elements with class js-only 56 | var els = document.getElementsByClassName("js-only") 57 | for (var i = 0; i < els.length; i++) { 58 | els[i].classList.remove('d-none') 59 | } 60 | }); 61 | 62 | $(document).ready(function () { 63 | // display close buttons on snackbar notifications 64 | $('.snackbar-close-button-top').removeClass('d-none'); 65 | // add click listener to close buttons 66 | $('.snackbar-close-button-top').on('click', function(e){ 67 | $(this).parent().parent().fadeOut(); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /views/footer.html: -------------------------------------------------------------------------------- 1 | {{define "footer"}} 2 |
    3 |
    4 |
    5 | 12 |
    13 |
    14 |
    15 | 16 | 17 | 18 | 19 | 20 | 21 | {{/* Homepage caorousel */}} 22 | 23 | 24 | {{/* Homepage VSP table */}} 25 | 26 | 27 | 28 | {{/* dcrstakepool custom js */}} 29 | 30 | 31 | {{if .IsAdminTickets }} 32 | 33 | {{ end }} 34 | 35 | {{if .IsStats }} 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {{end}} 50 | 51 | {{ if .IsIndex }} 52 | 53 | {{end}} 54 | 55 | 56 | 57 | {{end}} 58 | -------------------------------------------------------------------------------- /views/admin/status.html: -------------------------------------------------------------------------------- 1 | {{define "admin/status"}} 2 |
    3 |
    4 | 5 |
    6 |
    7 | 8 |
    9 |

    10 | Back-end Status 11 |

    12 |
    13 | 14 | 15 |
    16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{ range .BackendStatus }} 30 | 31 | 32 | 33 | 36 | 37 | {{ with .WalletStatus }} 38 | 39 | 42 | 43 | 44 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | {{else}} 56 | 57 | 58 | 59 | {{end}} 60 | 61 | {{end}} 62 | 63 |
    HostStakepoold RPC StatusDaemonConnectedUnlockedVotingVoteVersion
    {{ .Host }}{{ .RPCStatus }}{{ .DaemonConnected }}{{ .Unlocked }}{{ .Voting }}{{ .VoteVersion }}Cannot get wallet stats
    64 |
    65 |
    66 | 67 |
    68 |
    69 |
    70 |
    71 | {{end}} -------------------------------------------------------------------------------- /controllers/captcha.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "path" 7 | "strings" 8 | "time" 9 | 10 | "github.com/dchest/captcha" 11 | "github.com/zenazn/goji/web" 12 | ) 13 | 14 | type captchaHandler struct { 15 | ImgWidth int 16 | ImgHeight int 17 | } 18 | 19 | // CaptchaServe writes and serves captchas. 20 | func (controller *MainController) CaptchaServe(c web.C, w http.ResponseWriter, r *http.Request) { 21 | // Get the captcha id by stripping the file extension. 22 | _, file := path.Split(r.URL.Path) 23 | ext := path.Ext(file) 24 | id := strings.TrimSuffix(file, ext) 25 | if ext != ".png" || id == "" { 26 | http.NotFound(w, r) 27 | return 28 | } 29 | 30 | h := controller.captchaHandler 31 | 32 | if r.FormValue("reload") != "" { 33 | captcha.Reload(id) 34 | } 35 | 36 | w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") 37 | w.Header().Set("Pragma", "no-cache") 38 | w.Header().Set("Expires", "0") 39 | 40 | var content bytes.Buffer 41 | w.Header().Set("Content-Type", "image/png") 42 | err := captcha.WriteImage(&content, id, h.ImgWidth, h.ImgHeight) 43 | if err != nil { 44 | http.Error(w, "failed to generate captcha image", http.StatusInternalServerError) 45 | } 46 | 47 | http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes())) 48 | } 49 | 50 | // CaptchaVerify verifies that the provided captcha matches the on screen text 51 | // and sets the CaptchaDone session value. 52 | func (controller *MainController) CaptchaVerify(c web.C, w http.ResponseWriter, r *http.Request) { 53 | id, solution := r.FormValue("captchaId"), r.FormValue("captchaSolution") 54 | if id == "" { 55 | http.Error(w, "invalid captcha id", http.StatusBadRequest) 56 | return 57 | } 58 | 59 | session := controller.GetSession(c) 60 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 61 | if captcha.VerifyString(id, solution) { 62 | session.Values["CaptchaDone"] = true 63 | } else { 64 | session.Values["CaptchaDone"] = false 65 | session.AddFlash("Captcha verification failed. Please try again.", 66 | "captchaFailed") 67 | } 68 | 69 | if err := session.Save(r, w); err != nil { 70 | log.Criticalf("session.Save() failed: %v", err) 71 | http.Error(w, "failed to save session", http.StatusInternalServerError) 72 | } 73 | 74 | ref := r.Referer() 75 | if ref == "" { 76 | ref = "/" 77 | } 78 | http.Redirect(w, r, ref, http.StatusFound) 79 | } 80 | -------------------------------------------------------------------------------- /public/js/admintickets.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Change checkbox state if user clicks anywhere on the row. 3 | // Not just the actual checkbox 4 | var rows = document.getElementsByTagName("tr") 5 | for (var i = 0; i < rows.length; i++) { 6 | rows[i].addEventListener("click", function (e) { 7 | if (e.target.tagName == "A") return; 8 | box = this.querySelector("input[type=checkbox]"); 9 | if (box.checked) { 10 | $(box).closest('tr').removeClass('bg-checked'); 11 | } else { 12 | $(box).closest('tr').addClass('bg-checked'); 13 | } 14 | box.checked = !box.checked 15 | }); 16 | } 17 | 18 | })(); 19 | 20 | //controls tables with selections and checkboxes 21 | var selectMsg = "Select all"; 22 | var deselectMsg = "Deselect all"; 23 | 24 | $("#select_all_ignored").click(toggleCheckBoxes("#ignored_table")); 25 | $("#select_all_added").click(toggleCheckBoxes("#added_table")); 26 | 27 | function toggleCheckBoxes(tableId) { 28 | return function (clicked) { 29 | var table = $(tableId); 30 | if ($(this)[0].text == deselectMsg) { 31 | table.find(':checkbox').prop('checked', false); 32 | table.find('tr').removeClass('bg-checked'); 33 | $(this).text(selectMsg); 34 | $('.update-btn').removeClass("d-flex"); 35 | } 36 | else { 37 | table.find(':checkbox').prop('checked', true); 38 | table.find('tr').addClass('bg-checked'); 39 | $(this).text(deselectMsg); 40 | $('.update-btn').addClass('d-flex'); 41 | } 42 | }; 43 | } 44 | 45 | $(function () { 46 | $('td:last-child input').change(function () { 47 | $(this).closest('tr').toggleClass("bg-checked", this.checked); 48 | }); 49 | }); 50 | 51 | $(function () { 52 | $('td:last-child input').change(function () { 53 | $(this).closest('tr').toggleClass("bg-checked", this.checked); 54 | }); 55 | }); 56 | 57 | $('.control-checkbox input').change(function () { 58 | if ($(this).is(":checked")) { 59 | $('.update-btn').addClass("d-flex"); 60 | } else { 61 | var flag = 0; 62 | $('.control-checkbox input').each(function () { 63 | if ($(this).is(":checked")) { 64 | $('.update-btn').addClass("d-flex"); 65 | flag = 1; 66 | } 67 | if (flag == 0) { 68 | $('.update-btn').removeClass('d-flex'); 69 | } 70 | }); 71 | } 72 | }); -------------------------------------------------------------------------------- /apiClientExample.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script registers a new account at a stakepool, waits a bit 3 | # for the email verification link to be clicked, submits an address, 4 | # requests the ticket purchasing information for the account, and 5 | # then requests the pool's statistics. 6 | 7 | apiKEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0ODE3MzAzNjYsImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6ODAwMCIsImxvZ2dlZEluQXMiOjY0fQ.DS49iqN3hFjAqwTnUKIbJ-Mg3sdwKUVE_diwp7dTok4" 8 | apiURL="http://127.0.0.1:8000/api/v1" 9 | #cookieFile=$(mktemp /tmp/stakepoolapiclient.XXXXXXXXXX) 10 | email="example@example.com" 11 | password="blake256" 12 | pubKeyAddr="TkKnJTAQTQ42nAwLj9wSXJcrKadSJneNkXT9LW25kqBKH646N4Bws" 13 | secsForEmailVerification=15 14 | 15 | apiCmd() { 16 | # $1 = cmd, $2 = data 17 | if [ -z "$2" ]; then 18 | echo "running curl -s -H \"Authorization: Bearer $apiKEY\" $apiURL/$1" 19 | r=$(curl -s -H "Authorization: Bearer $apiKEY" $apiURL/$1) 20 | else 21 | echo "running curl -s -H \"Authorization: Bearer $apiKEY\" --data $2 $apiURL/$1" 22 | r=$(curl -s -H "Authorization: Bearer $apiKEY" --data "$2" $apiURL/$1) 23 | fi 24 | 25 | if [ -z "$r" ]; then 26 | fatal "command $1 failed" 27 | fi 28 | 29 | status=$(echo $r |jq -r .status) 30 | 31 | if [ "$status" == "error" ]; then 32 | echo "WARNING: command returned with error" 33 | fi 34 | # can add additional processing of the result here 35 | echo $r 36 | } 37 | 38 | cleanUp() { 39 | echo "removing $cookieFile" 40 | rm $cookieFile 41 | } 42 | 43 | fatal() { 44 | echo >&2 "$1" 45 | #cleanUp 46 | exit 1 47 | } 48 | 49 | updateCsrfToken() { 50 | csrftoken=$(grep XSRF-TOKEN $cookieFile |awk '{print $7}') 51 | } 52 | 53 | waitForVerification() { 54 | echo "sleeping $secsForEmailVerification seconds to allow email verification link to be clicked" 55 | for i in `seq $secsForEmailVerification -1 1`; 56 | do echo "$i seconds left to verify email"; sleep 1 57 | done 58 | } 59 | 60 | command -v jq >/dev/null 2>&1 || { fatal "I require the binary jq (https://stedolan.github.io/jq/) but it's not installed. Aborting."; } 61 | 62 | #echo "using cookieFile $cookieFile" 63 | 64 | #apiCmd "startsession" 65 | 66 | #apiCmd "register" "email=$email&password=$password&passwordrepeat=$password" 67 | 68 | #waitForVerification 69 | 70 | #apiCmd "login" "email=$email&password=$password" 71 | 72 | apiCmd "address" "UserPubKeyAddr=$pubKeyAddr" 73 | 74 | apiCmd "getpurchaseinfo" 75 | 76 | apiCmd "stats" 77 | 78 | #cleanUp 79 | -------------------------------------------------------------------------------- /views/header.html: -------------------------------------------------------------------------------- 1 | {{define "header"}} 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 | {{.Title}} 40 | 41 | 42 | 43 | 44 | {{end}} 45 | -------------------------------------------------------------------------------- /params.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2014 The btcsuite developers 2 | // Copyright (c) 2015-2019 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/decred/dcrd/chaincfg/v3" 10 | "github.com/decred/dcrd/wire" 11 | ) 12 | 13 | // activeNetParams is a pointer to the parameters specific to the 14 | // currently active decred network. 15 | var activeNetParams = &mainNetParams 16 | 17 | // params is used to group parameters for various networks such as the main 18 | // network and test networks. 19 | type params struct { 20 | *chaincfg.Params 21 | StakepooldRPCServerPort string 22 | } 23 | 24 | // mainNetParams contains parameters specific to the main network 25 | // (wire.MainNet). NOTE: The RPC port is intentionally different than the 26 | // reference implementation because dcrd does not handle wallet requests. The 27 | // separate wallet process listens on the well-known port and forwards requests 28 | // it does not handle on to dcrd. This approach allows the wallet process 29 | // to emulate the full reference implementation RPC API. 30 | var mainNetParams = params{ 31 | Params: chaincfg.MainNetParams(), 32 | StakepooldRPCServerPort: "9113", 33 | } 34 | 35 | // testNet3Params contains parameters specific to the test network (version 0) 36 | // (wire.TestNet). NOTE: The RPC port is intentionally different than the 37 | // reference implementation - see the mainNetParams comment for details. 38 | 39 | var testNet3Params = params{ 40 | Params: chaincfg.TestNet3Params(), 41 | StakepooldRPCServerPort: "19113", 42 | } 43 | 44 | // simNetParams contains parameters specific to the simulation test network 45 | // (wire.SimNet). 46 | var simNetParams = params{ 47 | Params: chaincfg.SimNetParams(), 48 | StakepooldRPCServerPort: "19560", 49 | } 50 | 51 | // netName returns the name used when referring to a decred network. At the 52 | // time of writing, dcrd currently places blocks for testnet version 0 in the 53 | // data and log directory "testnet", which does not match the Name field of the 54 | // chaincfg parameters. This function can be used to override this directory name 55 | // as "testnet" when the passed active network matches wire.TestNet. 56 | // 57 | // A proper upgrade to move the data and log directories for this network to 58 | // "testnet" is planned for the future, at which point this function can be 59 | // removed and the network parameter's name used instead. 60 | func netName(chainParams *params) string { 61 | switch chainParams.Net { 62 | case wire.TestNet3: 63 | return "testnet3" 64 | default: 65 | return chainParams.Name 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/js/vendor/d3-collection.v1.min.js: -------------------------------------------------------------------------------- 1 | // https://d3js.org/d3-collection/ v1.0.7 Copyright 2018 Mike Bostock 2 | !function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(n.d3=n.d3||{})}(this,function(n){"use strict";function t(){}function e(n,e){var r=new t;if(n instanceof t)n.each(function(n,t){r.set(t,n)});else if(Array.isArray(n)){var i,u=-1,o=n.length;if(null==e)for(;++u=f.length)return null!=n&&r.sort(n),null!=t?t(r):r;for(var s,c,h,l=-1,v=r.length,p=f[i++],y=e(),d=u();++lf.length)return e;var i,u=c[r-1];return null!=t&&r>=f.length?i=e.entries():(i=[],e.each(function(t,e){i.push({key:e,values:n(t,r)})})),null!=u?i.sort(function(n,t){return u(n.key,t.key)}):i}(a(n,0,u,o),0)},key:function(n){return f.push(n),s},sortKeys:function(n){return c[f.length-1]=n,s},sortValues:function(t){return n=t,s},rollup:function(n){return t=n,s}}},n.set=c,n.map=e,n.keys=function(n){var t=[];for(var e in n)t.push(e);return t},n.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},n.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},Object.defineProperty(n,"__esModule",{value:!0})}); 3 | -------------------------------------------------------------------------------- /public/images/group-1342.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /backend/stakepoold/params.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2014 The btcsuite developers 2 | // Copyright (c) 2015-2016 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "github.com/decred/dcrd/chaincfg/v3" 10 | "github.com/decred/dcrd/wire" 11 | ) 12 | 13 | // activeNetParams is a pointer to the parameters specific to the 14 | // currently active decred network. 15 | var activeNetParams = &mainNetParams 16 | 17 | // params is used to group parameters for various networks such as the main 18 | // network and test networks. 19 | type params struct { 20 | *chaincfg.Params 21 | DcrdRPCServerPort string 22 | RPCServerPort string 23 | WalletRPCServerPort string 24 | } 25 | 26 | // mainNetParams contains parameters specific to the main network 27 | // (wire.MainNet). NOTE: The RPC port is intentionally different than the 28 | // reference implementation because dcrd does not handle wallet requests. The 29 | // separate wallet process listens on the well-known port and forwards requests 30 | // it does not handle on to dcrd. This approach allows the wallet process 31 | // to emulate the full reference implementation RPC API. 32 | var mainNetParams = params{ 33 | Params: chaincfg.MainNetParams(), 34 | DcrdRPCServerPort: "9109", 35 | RPCServerPort: "9113", 36 | WalletRPCServerPort: "9110", 37 | } 38 | 39 | // testNet3Params contains parameters specific to the test network (version 0) 40 | // (wire.TestNet). NOTE: The RPC port is intentionally different than the 41 | // reference implementation - see the mainNetParams comment for details. 42 | var testNet3Params = params{ 43 | Params: chaincfg.TestNet3Params(), 44 | DcrdRPCServerPort: "19109", 45 | RPCServerPort: "19113", 46 | WalletRPCServerPort: "19110", 47 | } 48 | 49 | // simNetParams contains parameters specific to the simulation test network 50 | // (wire.SimNet). 51 | var simNetParams = params{ 52 | Params: chaincfg.SimNetParams(), 53 | DcrdRPCServerPort: "19556", 54 | RPCServerPort: "19560", 55 | WalletRPCServerPort: "19557", 56 | } 57 | 58 | // netName returns the name used when referring to a decred network. At the 59 | // time of writing, dcrd currently places blocks for testnet version 0 in the 60 | // data and log directory "testnet", which does not match the Name field of the 61 | // chaincfg parameters. This function can be used to override this directory name 62 | // as "testnet" when the passed active network matches wire.TestNet. 63 | // 64 | // A proper upgrade to move the data and log directories for this network to 65 | // "testnet" is planned for the future, at which point this function can be 66 | // removed and the network parameter's name used instead. 67 | func netName(chainParams *params) string { 68 | switch chainParams.Net { 69 | case wire.TestNet3: 70 | return "testnet3" 71 | default: 72 | return chainParams.Name 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/js/stats.js: -------------------------------------------------------------------------------- 1 | var drawChart = function (containerID) { 2 | 3 | container = document.querySelector(containerID); 4 | 5 | labels = [ 6 | container.getAttribute("data-label1"), 7 | container.getAttribute("data-label2"), 8 | ]; 9 | colors = [ 10 | container.getAttribute("data-color1"), 11 | container.getAttribute("data-color2"), 12 | ]; 13 | data = { 14 | a: container.getAttribute("data-value1"), 15 | b: container.getAttribute("data-value2"), 16 | }; 17 | 18 | // create the svg canvas 19 | var svg = d3.select(containerID) 20 | .append("svg") 21 | .attr("width", 288) 22 | .attr("height", 350) 23 | .append("g") 24 | .attr("transform", "translate(" + 144 + "," + 190 + ")"); 25 | 26 | // set the color scale 27 | var color = d3.scaleOrdinal() 28 | .domain(data) 29 | .range(colors); 30 | 31 | // draw a legend using dots and labels 32 | svg 33 | .selectAll("mydots") 34 | .data(labels) 35 | .enter() 36 | .append("circle") 37 | .attr("cx", -100) 38 | .attr("cy", function (d, i) { return -150 - i * 25 }) // -150 is where the first dot appears. 25 is the distance between dots. 39 | .attr("r", 7) 40 | .style("fill", function (d) { return color(d) }); 41 | 42 | svg 43 | .selectAll("mylabels") 44 | .data(labels) 45 | .enter() 46 | .append("text") 47 | .attr("x", -80) 48 | .attr("y", function (d, i) { return -150 - i * 25 }) // -150 is where the first dot appears. 25 is the distance between dots. 49 | .style("fill", "#000") 50 | .text(function (d) { return d }) 51 | .attr("text-anchor", "left") 52 | .style("font-size", 13) 53 | .style("alignment-baseline", "middle"); 54 | 55 | // compute the position of each group on the pie 56 | var pie = d3.pie().value(function (d) { return d.value; }); 57 | var data_ready = pie(d3.entries(data)); 58 | 59 | // shape helper to build arcs 60 | var arcGenerator = d3.arc() 61 | .innerRadius(0) 62 | .outerRadius(125); 63 | 64 | // draw the pie chart 65 | svg 66 | .selectAll('pieSlices') 67 | .data(data_ready) 68 | .enter() 69 | .append('path') 70 | .attr('d', arcGenerator) 71 | .attr('fill', function (d) { return (color(d.data.key)) }); 72 | 73 | // Now add the annotation. Use the centroid method to get the best coordinates 74 | svg 75 | .selectAll('pieSlices') 76 | .data(data_ready) 77 | .enter() 78 | .append('text') 79 | .text(function (d) { return d.data.value <= 0 ? "" : d.data.value }) 80 | .attr("transform", function (d) { return "translate(" + arcGenerator.centroid(d) + ")"; }) 81 | .style("text-anchor", "middle") 82 | .style("font-size", 15) 83 | .style("fill", "#fff"); 84 | }; 85 | 86 | drawChart("#chart1"); 87 | drawChart("#chart2"); 88 | drawChart("#chart3"); 89 | -------------------------------------------------------------------------------- /system/controller.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "time" 7 | 8 | "github.com/decred/dcrstakepool/helpers" 9 | "github.com/decred/dcrstakepool/models" 10 | "github.com/go-gorp/gorp" 11 | "github.com/gorilla/sessions" 12 | "github.com/zenazn/goji/web" 13 | ) 14 | 15 | // Controller is the type of the main web controller. 16 | type Controller struct { 17 | } 18 | 19 | // GetSession returns the session stored in the header. 20 | func (controller *Controller) GetSession(c web.C) *sessions.Session { 21 | return c.Env["Session"].(*sessions.Session) 22 | } 23 | 24 | // GetTemplate returns the template stored in the header. 25 | func (controller *Controller) GetTemplate(c web.C) *template.Template { 26 | return c.Env["Template"].(*template.Template) 27 | } 28 | 29 | // GetDbMap returns the DbMap stored in the header. 30 | func (controller *Controller) GetDbMap(c web.C) *gorp.DbMap { 31 | return c.Env["DbMap"].(*gorp.DbMap) 32 | } 33 | 34 | // IsCaptchaDone returns the CaptchaDone value stored in the header. 35 | func (controller *Controller) IsCaptchaDone(c web.C) bool { 36 | done, ok := c.Env["CaptchaDone"].(bool) 37 | return done && ok 38 | } 39 | 40 | // Parse parses html templates and returns the template as a string. 41 | func (controller *Controller) Parse(t *template.Template, name string, data interface{}) string { 42 | var doc bytes.Buffer 43 | err := t.ExecuteTemplate(&doc, name, data) 44 | if err != nil { 45 | log.Warnf("ExecuteTemplate error: %v", err) 46 | } 47 | return doc.String() 48 | } 49 | 50 | // CheckPasswordResetToken checks that the input token string is valid, 51 | // recognized by the DB, and not expired. Flash messages are added to the 52 | // session for any token failure, and the return indicates if all checks have 53 | // passed. The UserToken and PasswordReset objects are also returned. 54 | func (controller *Controller) CheckPasswordResetToken(tokenStr string, c web.C) (models.UserToken, *models.PasswordReset, bool) { 55 | session := controller.GetSession(c) 56 | dbMap := controller.GetDbMap(c) 57 | 58 | var token models.UserToken 59 | 60 | // Check that the token is set. 61 | if tokenStr == "" { 62 | session.AddFlash("No password update token present.", 63 | "passwordupdateError") 64 | return token, nil, false 65 | } 66 | 67 | // Check that the token is valid. 68 | var err error 69 | token, err = models.UserTokenFromStr(tokenStr) 70 | if err != nil { 71 | session.AddFlash("Email verification token not valid.", 72 | "passwordupdateError") 73 | return token, nil, false 74 | } 75 | 76 | // Check that the token is recognized. 77 | passwordReset, err := helpers.PasswordResetTokenExists(dbMap, token) 78 | if err != nil { 79 | log.Debugf(`Password update token "%v" not found in DB: %v`, token, err) 80 | session.AddFlash("Password update token not recognized.", 81 | "passwordupdateError") 82 | return token, nil, false 83 | } 84 | 85 | // Check that the token is not expired. 86 | expTime := time.Unix(passwordReset.Expires, 0) 87 | if expTime.Before(time.Now()) { 88 | session.AddFlash("Password update token has expired.", 89 | "passwordupdateError") 90 | return token, passwordReset, false 91 | } 92 | 93 | return token, passwordReset, true 94 | } 95 | -------------------------------------------------------------------------------- /views/settings.html: -------------------------------------------------------------------------------- 1 | {{define "settings"}} 2 |
    3 | 4 |
    5 |
    6 | 7 | {{range .FlashSuccess}} 8 |
    9 |
    10 |
    11 |

    {{.}}

    12 |
    13 |
    14 | {{end}} 15 | 16 | {{range .FlashError}} 17 |
    18 |
    19 |
    20 |

    {{.}}

    21 |
    22 |
    23 | {{end}} 24 | 25 |
    26 |
    27 |

    Change Email Address

    28 |
    29 | {{if .CaptchaDone}} 30 |
    31 |
    32 |
    33 |
    34 | 35 |
    36 | 37 |
    38 | 39 |
    40 | 41 |
    42 |
    43 |
    44 | {{ $.csrfField }} 45 | 46 | 47 |
    48 |
    49 | {{else}} 50 | {{ template "captcha" . }} 51 | {{end}} 52 | 53 |
    54 | 55 |
    56 |
    57 |

    Change Password

    58 |
    59 |
    60 |
    61 |
    62 | 63 |
    64 | 65 |
    66 | 67 |
    68 | 69 |
    70 | 71 |
    72 | 73 |
    74 |
    75 |
    76 | {{ $.csrfField }} 77 | 78 | 79 |
    80 |
    81 |
    82 |
    83 |
    84 | 85 | {{end}} 86 | 87 | -------------------------------------------------------------------------------- /helpers/address_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | package helpers 5 | 6 | import ( 7 | "errors" 8 | "testing" 9 | 10 | "github.com/decred/dcrd/chaincfg/v3" 11 | "github.com/decred/dcrd/hdkeychain/v3" 12 | ) 13 | 14 | var ( 15 | xpubTestNet = "tpubVpQL1h9UcY9c1BPZYfjYEtw5froRAvqZEo6sn5Tji6VkhcpfMaQ6id9Spf5iNvprRTcpdF5pj7m5Suyu1E8iC4xnb6MkjUnCJureTsmdXfG" 16 | xpubMainNet = "dpubZGWjhGoJRkwao4W8Jsk56RPJNSAHmEtuERoeuugKmGxFhU1zhZJ2MfScJjzccGcs3xLHYZN2V7FjAHfBoiHdcpXtVyUnJQMxZxRENNgTEsM" 17 | xpubSimNet = "spubVVBn1KgTWoDRajAZrymsoTRjP1qQdKTbuUMBBKw2q6vNVrbHXYGPTxDFgcaYYzrTRQ38mvkKt8dbk9pUHppT6WLZ23DroW8V3i3kptjfndx" 18 | childrenTestNet = []string{ 19 | "TskTcbmvjYxduojxjAnTGxLArnGo4EFnoi3", "Tsg1JdVFUw9GVmW9dGo4ZCf3FGWw1FUaqvk", 20 | "TsbVWuTSuERse1yFeZHznF8xAHyyuSS8HkY", "Tso91gcUQdWZKkgLAeC8yWJ7D4AUE2vNiAC", 21 | "TsbZoFnCsHnsKV5xDSBtde8evQgfzrgrg1P", "Tsbxtn8e3T2yr42oV8psnigAJR7M58oKeZ2", 22 | "Tsf7dkLnsKHKzQEvenYGCgNozyDvnhQFtUu", "TsfDVfxHXPFBtNnwjywaa2gB1vwcnZmwHhR", 23 | "Tsft7d5AYU5mUwxDi6cJwr3SR8L2vExYcqe", "TsRhmeoBjYVfHqVjysChmXYcmo1VvX431qP"} 24 | childrenMainNet = []string{ 25 | "Dshz7KtcGoPYbx3sW1Jh93TVi78VDGPb5te", "DsciYUd8QSunzAoqi5YJz7hmbZ7Gg7Y6Dhc", 26 | "DscxtcYoEDwiFmgV3ApdzG1Q7CZZcAhp4e8", "Dsg5sYFGyWj6w23rtSFCTnj1vCXZWh1vJTX", 27 | "DsnQ43inx8EmY8DxafFbNc8JP8vwKS1WfaV", "DsetJeaZRurv52TJkGQi2drYDCtyNS4Q9KR", 28 | "DsmyehuYCaHxUEJ44bLKF8r8AHU7c2Wv27A", "DsoKUmjgC61ZARQ3GVXREemAFVR1scnqnUL", 29 | "DsU31RL79PYHis8pxRLMj83AQaJzDtHE3TK", "DsS14B29Maf6Gq6hitLqLZyEcTZ5mUc5ne5"} 30 | childrenSimNet = []string{ 31 | "SscWmiP9TMGZimomJiqQvnrkGe23h3C3sJb", "SsYn4toZtiSTbngZLxwvSFfUAh6RBpDVHJf", 32 | "SsaQHmC3GTbGJa4Djijh2mxPuAqp94RDTZX", "Ssi4NUey2gLWyfc78wikFbqu8sTXcdAs32A", 33 | "Ssf9mYdScWXpxrYBrttwKBhBpHaZ3iq5c9X", "Sso2bUfGA4sEFto1Ej7ka84jDZFjg7hNc64", 34 | "SsjyuWdpnaMWwzqYymLvEbEVgdpvZKyN9pg", "Ssi3nP3oZ8jD4G1WEgZuFtLmDo52kpGzuz6", 35 | "SsVwq3tCmRBHkDXy5mo2S2FB48wVbqguGx8", "SsqKRGQKCi6mm3YZkTyxSXXJWtCSSyXfPBG"} 36 | ) 37 | 38 | type dcrutilAddressTest struct { 39 | xpub string 40 | net *chaincfg.Params 41 | children []string 42 | } 43 | 44 | var dcrutilAddressTests = []dcrutilAddressTest{ 45 | {xpubTestNet, chaincfg.TestNet3Params(), childrenTestNet}, 46 | {xpubMainNet, chaincfg.MainNetParams(), childrenMainNet}, 47 | {xpubSimNet, chaincfg.SimNetParams(), childrenSimNet}, 48 | } 49 | 50 | func TestDCRUtilAddressFromExtendedKey(t *testing.T) { 51 | for _, test := range dcrutilAddressTests { 52 | key, err := hdkeychain.NewKeyFromString(test.xpub, test.net) 53 | if err != nil { 54 | t.Error(err) 55 | return 56 | } 57 | branchKey, err := key.Child(ExternalBranch) 58 | if err != nil { 59 | t.Error(err) 60 | return 61 | } 62 | for i := 0; i < 10; i++ { 63 | child, err := branchKey.Child(uint32(i)) 64 | if errors.Is(err, hdkeychain.ErrInvalidChild) { 65 | continue 66 | } 67 | if err != nil { 68 | t.Error(err) 69 | return 70 | } 71 | addr, err := DCRUtilAddressFromExtendedKey(child, test.net) 72 | if err != nil { 73 | t.Error(err) 74 | return 75 | } 76 | if addr.Address() != test.children[i] { 77 | t.Errorf("for xpub %v on network %v at index %v expected address %v but got %v", test.xpub, test.net.Name, i, test.children[i], addr.Address()) 78 | return 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /docs/version-history.md: -------------------------------------------------------------------------------- 1 | # Version History 2 | 3 | - 1.1.0 - Architecture change. 4 | * Per-ticket votebits were removed in favor of per-user voting preferences. 5 | A voting page was added and the API upgraded to v2 to support getting and 6 | setting user voting preferences. 7 | * Addresses from the wallet servers which are needed for generating the 1-of-2 8 | multisig ticket address are now derived from the new votingwalletextpub 9 | config option. This removes the need to call getnewaddress on each wallet. 10 | * An experimental daemon (stakepoold) that votes according to user preference 11 | is available for testing on testnet. This daemon is not for use on mainnet 12 | at this time. 13 | - 1.0.0 - Major changes/improvements. 14 | * API is now at v1 status. API Tokens are generated for all users with a 15 | verified email address when upgrading. Tokens are generated for new 16 | users on demand when visiting the Settings page which displays their token. 17 | Authenticated users may use the API to submit a public key address and to 18 | retrieve ticket purchasing information. The voting service's stats are also 19 | available through the API without authentication. 20 | - 0.0.4 - Major changes/improvements. 21 | * config.toml is no longer required as the options in that file have been 22 | migrated to dcrstakepool.conf. 23 | * Automatic syncing of scripts, tickets, and vote bits is now performed at 24 | startup. Syncing of vote bits is a long process and can be skipped with the 25 | SkipVoteBitsSync flag/configuration value. 26 | * Temporary wallet connectivity errors are now handled much more gracefully. 27 | * A preliminary v0.1 API was added. 28 | - 0.0.3 - More expected/basic web application functionality added. 29 | * SMTPHost now defaults to an empty string so a voting service can be used for 30 | development or testing purposes without a configured mail server. The 31 | contents of the emails are sent through the logger so links can still be 32 | followed. 33 | * Upon sign up, users now have an email sent with a validation link. 34 | They will not be able to sign in until they verify. 35 | * New settings page that allows users to change their email address/password. 36 | * Bug fix to HeightRegistered migration for users who signed up but never 37 | submitted an address would not be able to login. 38 | - 0.0.2 - Minor improvements/feature addition 39 | * The importscript RPC is now called with the current block height at the 40 | time of user registration. Previously, importscript triggered a rescan 41 | for transactions from the genesis block. Since the user just registered, 42 | there won't be any transactions present. A new HeightRegistered column 43 | is automatically added to the Users table. A default value of 15346 is 44 | used for existing users who already had a multisigscript generated. 45 | This can be adjusted to a more reasonable value for you pool by running 46 | the following MySQL query: 47 | ```UPDATE Users SET HeightRegistered = NEWVALUE WHERE HeightRegistered = 15346;``` 48 | * Users may now reset their password by specifying an email address and 49 | clicking a link that they will receive via email. You will need to 50 | add a proper configuration for your mail server for it to work properly. 51 | The various SMTP options can be seen in **sample-dcrstakepool.conf**. 52 | * User instructions on the address and ticket pages were updated. 53 | * SpentBy link added to the voted tickets display. 54 | - 0.0.1 - Initial release for mainnet operations 55 | -------------------------------------------------------------------------------- /views/voting.html: -------------------------------------------------------------------------------- 1 | {{define "voting"}} 2 |
    3 |
    4 | 5 |
    6 | {{range .FlashSuccess}} 7 |
    8 |
    9 |
    10 |

    {{.}}

    11 |
    12 |
    13 | {{end}} 14 | 15 | {{range .FlashError}} 16 |
    17 |
    18 |
    19 |

    {{.}}

    20 |
    21 |
    22 | {{end}} 23 |
    24 | 25 |
    26 |
    27 |
    28 |

    Voting Preferences (v{{.VoteVersion}})

    29 |
    30 |
    31 |

    Select the option for your tickets to vote for. On-going agendas can be tracked at voting.decred.org.

    32 |
    33 |
    34 |
    35 | 36 | {{with .Agendas}} 37 |
    38 |
    39 | {{ range $i, $data := . }} 40 |
    41 |
    42 |
    43 |
    44 |

    {{$data.Agenda.Vote.Id}}

    45 |
    46 | {{with $data.Status}} 47 |
    48 | 49 | {{if eq . "upcoming"}} 50 |
    Upcoming
    51 | {{end}} 52 | {{if eq . "in progress"}} 53 |
    In Progress
    54 | {{end}} 55 | {{if eq . "finished"}} 56 |
    Finished
    57 | {{end}} 58 | {{if eq . "failed"}} 59 |
    Failed
    60 | {{end}} 61 | {{if eq . "locked in"}} 62 |
    Locked In
    63 | {{end}} 64 |
    65 | {{end}} 66 |
    67 |

    ID: #{{$data.Agenda.Vote.Id}}

    68 |
    69 |
    70 |

    {{$data.Agenda.Vote.Description}}

    71 |
    72 |
    73 |
    74 |
    75 | 80 |
    81 |
    82 |
    83 |
    84 | {{end}} 85 |
    86 |
    87 | 88 | {{ $.csrfField }} 89 |
    90 |
    91 | {{else}} 92 |
    93 |
    94 |

    No agendas found

    95 |
    96 |
    97 | 98 |
    99 |
    100 | {{end}} 101 |
    102 |
    103 | {{end}} 104 | -------------------------------------------------------------------------------- /docs/release-note-1.1.1.md: -------------------------------------------------------------------------------- 1 | # 1.1.1 Release Notes 2 | 3 | - The handling of tickets considered invalid because they pay too-low-of-a-fee 4 | is now integrated directly into dcrstakepool and stakepoold. 5 | - Users who pass both the adminIPs and the new adminUserIDs checks will see a 6 | new link on the menu to the new administrative add tickets page. 7 | - Tickets are added to the MySQL database and then stakepoold is triggered to 8 | pull an update from the database and reload its config. 9 | - To accommodate changes to the gRPC API, dcrstakepool/stakepoold had their 10 | API versions changed to require/advertize 4.0.0. This requires performing 11 | the upgrade steps outlined below. 12 | - **KNOWN ISSUE** Total tickets count reported by stakepoold may not be totally 13 | accurate until low fee tickets that have been added to the database can be 14 | marked as voted. This will be resolved by future work. 15 | ([#201](https://github.com/decred/dcrstakepool/issues/201)). 16 | 17 | ## Git Tip Upgrade Guide 18 | 19 | 1) Announce maintenance and shut down dcrstakepool. 20 | 2) Upgrade Go to the latest stable version if necessary/possible. 21 | 3) Perform an upgrade of each stakepoold instance one at a time. 22 | - Stop stakepoold. 23 | - Build and restart stakepoold. 24 | 4) Edit dcrstakepool.conf and set adminIPs/adminUserIDs appropriately to include 25 | the administrative staff for whom you wish give the ability to add low fee 26 | tickets for voting. 27 | 5) Upgrade and start dcrstakepool after setting adminUserIDs. 28 | 6) Announce maintenance complete after verifying functionality. 29 | 30 | ## 1.1.1 Release Notes 31 | 32 | - dcrd has a new agenda and the vote version in dcrwallet has been 33 | incremented to v5 on mainnet. 34 | - stakepoold 35 | - The ticket list is now maintained by doing an initial GetTicket RPC call and 36 | then subtracts/adds tickets by processing SpentAndMissed/New ticket 37 | notifications from dcrwallet. This approach is much faster than the old 38 | method of calling StakePoolUserInfo for each user. 39 | - Bug fixes to the above commit and to accommodate changes in dcrwallet. 40 | - Status page 41 | - StatusUnauthorized error is now thrown rather than a generic one when 42 | accessing the page as a non-admin. 43 | - Updated to use new design. 44 | - Synced dcrwallet walletinfo field list. 45 | - Tickets page 46 | - Performance was greatly improved by skipping display of historic tickets. 47 | - Handles users that have only low fee/invalid tickets properly. 48 | - Expired tickets are now separated from missed. 49 | - General markup improvements. 50 | - Removed mention of creating a voting account as it has been deprecated. 51 | - Instructions were further clarified and updated to strongly recommend the 52 | use of Decrediton/Paymetheus. 53 | - Fragments of invalid markup were fixed. 54 | 55 | ## 1.1.1 Upgrade Guide 56 | 57 | 1) Announce maintenance and shut down dcrstakepool. 58 | 2) Perform upgrades on each dcrd+dcrwallet+stakepoold voting cluster one at a 59 | time. 60 | - Stop stakepoold, dcrwallet, and dcrd. 61 | - Upgrade dcrd, dcrwallet to 1.1.0 release binaries or git. If compiling from 62 | source, Go 1.9 is recommended to pick up improvements to the Golang runtime. 63 | - Restart dcrd, dcrwallet. 64 | - Upgrade stakepoold. 65 | - Start stakepoold. 66 | 3) Upgrade and start dcrstakepool. If you are maintaining a fork, note that 67 | you need to update the dcrd/chaincfg dependency to a revision that contains 68 | the new agenda. 69 | 4) dcrstakepool will reset the votebits for all users to 1 when it detects the 70 | new vote version via stakepoold. 71 | 5) Announce maintenance complete after verifying functionality. If possible, 72 | also announce that a new voting agenda is available and users must login 73 | to set their preferences for the new agenda. -------------------------------------------------------------------------------- /sample-nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | error_log /var/log/nginx/stakepool_error.log; 4 | pid /run/nginx.pid; 5 | 6 | events { 7 | worker_connections 1024; 8 | } 9 | 10 | http { 11 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 12 | '$status $body_bytes_sent "$http_referer" ' 13 | '"$http_user_agent" "$http_x_forwarded_for" "$realip_remote_addr"'; 14 | 15 | access_log /var/log/nginx/stakepool_access.log main; 16 | 17 | sendfile on; 18 | tcp_nopush on; 19 | tcp_nodelay on; 20 | keepalive_timeout 65; 21 | types_hash_max_size 2048; 22 | 23 | include /etc/nginx/mime.types; 24 | default_type application/octet-stream; 25 | 26 | # Ensure ngx_http_realip_module is available 27 | set_real_ip_from 127.0.0.1/32; 28 | # If you are behind a NAT router, specify LAN 29 | #set_real_ip_from 10.24.0/16; 30 | real_ip_header X-Forwarded-For; 31 | real_ip_recursive on; 32 | 33 | server { 34 | listen 80 default_server; 35 | server_name _; 36 | 37 | return 308 https://$host$request_uri; 38 | } 39 | 40 | limit_req_zone $binary_remote_addr zone=stakepool:10m rate=8r/s; 41 | 42 | server { 43 | listen 443 http2 ssl default_server; 44 | server_name _; 45 | 46 | gzip on; 47 | gzip_proxied any; 48 | gzip_comp_level 3; 49 | gzip_min_length 512; 50 | gzip_types text/css text/* text/javascript application/x-javascript application/json 51 | application/xml application/atom+xml application/xaml+xml application/javascript 52 | application/x-font-ttf application/font-woff font/collection font/opentype 53 | font/otf font/ttf image/bmp image/svg+xml image/x-icon application/octet-stream; 54 | 55 | #ssl on; # only needed for older nginx where not in listen 56 | ssl_certificate /etc/ssl/www/stakepool.domain.tld.crt; 57 | ssl_certificate_key /etc/ssl/www/stakepool.domain.tld.key; 58 | 59 | ssl_session_cache shared:SSL:20m; 60 | ssl_protocols TLSv1.1 TLSv1.2; # add TLSv1.3 if supported 61 | ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; 62 | ssl_prefer_server_ciphers on; 63 | #ssl_dhparam /etc/nginx/dhparam.pem; # openssl dhparam -out /etc/nginx/dhparam.pem 4096 64 | add_header Strict-Transport-Security max-age=15552001; 65 | 66 | # Serve static resources directly. 67 | location /assets/ { 68 | # The delay parameter requires nginx 1.15.7 or higher. 69 | limit_req zone=stakepool burst=48 delay=36; 70 | # See the zipassets.sh script, which can be used to prepare the 71 | # pre-zipped files used by gzip_static. 72 | gzip_static on; # use .gz files for pre-compressed data 73 | alias /opt/dcrstakepool/public/; # change this to the actual folder on disk 74 | # Set the Cache-Control and Expires headers for the static assets. 75 | expires modified 2d; 76 | } 77 | 78 | # Everything else is proxied to dcrstakepool. 79 | location / { 80 | # apply rate limiting 81 | limit_req zone=stakepool burst=16; 82 | 83 | proxy_set_header Host $host; 84 | proxy_set_header X-Real-IP $realip_remote_addr; 85 | proxy_pass http://127.0.0.1:8000; 86 | proxy_http_version 1.0; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /backend/stakepoold/log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2018 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/decred/dcrstakepool/backend/stakepoold/rpc/server" 13 | "github.com/decred/dcrstakepool/backend/stakepoold/stakepool" 14 | "github.com/decred/dcrstakepool/backend/stakepoold/userdata" 15 | "github.com/decred/dcrstakepool/signal" 16 | "github.com/decred/slog" 17 | "github.com/jrick/logrotate/rotator" 18 | ) 19 | 20 | // logWriter implements an io.Writer that outputs to both standard output and 21 | // the write-end pipe of an initialized log rotator. 22 | type logWriter struct{} 23 | 24 | func (logWriter) Write(p []byte) (n int, err error) { 25 | os.Stdout.Write(p) 26 | return logRotator.Write(p) 27 | } 28 | 29 | // Loggers per subsystem. A single backend logger is created and all subsytem 30 | // loggers created from it will write to the backend. When adding new 31 | // subsystems, add the subsystem logger variable here and to the 32 | // subsystemLoggers map. 33 | // 34 | // Loggers can not be used before the log rotator has been initialized with a 35 | // log file. This must be performed early during application startup by calling 36 | // initLogRotator. 37 | var ( 38 | // backendLog is the logging backend used to create all subsystem loggers. 39 | // The backend must not be used before the log rotator has been initialized, 40 | // or data races and/or nil pointer dereferences will occur. 41 | backendLog = slog.NewBackend(logWriter{}) 42 | 43 | // logRotator is one of the logging outputs. It should be closed on 44 | // application shutdown. 45 | logRotator *rotator.Rotator 46 | 47 | clientLog = backendLog.Logger("RPCC") 48 | dbLog = backendLog.Logger("DB") 49 | grpcLog = backendLog.Logger("GRPC") 50 | log = backendLog.Logger("STPK") 51 | stakepoolLog = backendLog.Logger("CORE") 52 | ) 53 | 54 | // subsystemLoggers maps each subsystem identifier to its associated logger. 55 | var subsystemLoggers = map[string]slog.Logger{ 56 | "RPCC": clientLog, 57 | "DB": dbLog, 58 | "GRPC": grpcLog, 59 | "STPK": log, 60 | "CORE": stakepoolLog, 61 | } 62 | 63 | // Initialize package-global logger variables. 64 | func init() { 65 | userdata.UseLogger(dbLog) 66 | server.UseLogger(grpcLog) 67 | stakepool.UseLogger(stakepoolLog) 68 | signal.UseLogger(log) 69 | } 70 | 71 | // initLogRotator initializes the logging rotater to write logs to logFile and 72 | // create roll files in the same directory. It must be called before the 73 | // package-global log rotater variables are used. 74 | func initLogRotator(logFile string) { 75 | logDir, _ := filepath.Split(logFile) 76 | err := os.MkdirAll(logDir, 0700) 77 | if err != nil { 78 | fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err) 79 | os.Exit(1) 80 | } 81 | r, err := rotator.New(logFile, 10*1024, false, 3) 82 | if err != nil { 83 | fmt.Fprintf(os.Stderr, "failed to create file rotator: %v\n", err) 84 | os.Exit(1) 85 | } 86 | 87 | logRotator = r 88 | } 89 | 90 | // setLogLevel sets the logging level for provided subsystem. Invalid 91 | // subsystems are ignored. Uninitialized subsystems are dynamically created as 92 | // needed. 93 | func setLogLevel(subsystemID string, logLevel string) { 94 | // Ignore invalid subsystems. 95 | logger, ok := subsystemLoggers[subsystemID] 96 | if !ok { 97 | return 98 | } 99 | 100 | // Defaults to info if the log level is invalid. 101 | level, _ := slog.LevelFromString(logLevel) 102 | logger.SetLevel(level) 103 | } 104 | 105 | // setLogLevels sets the log level for all subsystem loggers to the passed 106 | // level. It also dynamically creates the subsystem loggers as needed, so it 107 | // can be used to initialize the logging system. 108 | func setLogLevels(logLevel string) { 109 | // Configure all sub-systems with the new logging level. Dynamically 110 | // create loggers as needed. 111 | for subsystemID := range subsystemLoggers { 112 | setLogLevel(subsystemID, logLevel) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2014 The btcsuite developers 2 | // Copyright (c) 2015-2019 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | // Package version provides a single location to house the version information 7 | // for dcrstakepool and other utilities provided in the same repository. 8 | package version 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "strings" 14 | ) 15 | 16 | const ( 17 | // semanticAlphabet defines the allowed characters for the pre-release 18 | // portion of a semantic version string. 19 | semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" 20 | 21 | // semanticBuildAlphabet defines the allowed characters for the build 22 | // portion of a semantic version string. 23 | semanticBuildAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-." 24 | ) 25 | 26 | // These constants define the application version and follow the semantic 27 | // versioning 2.0.0 spec (http://semver.org/). 28 | const ( 29 | Major uint = 1 30 | Minor uint = 6 31 | Patch uint = 0 32 | ) 33 | 34 | var ( 35 | // PreRelease is defined as a variable so it can be overridden during the 36 | // build process with: 37 | // '-ldflags "-X github.com/decred/dcrstakepool/internal/version.PreRelease=foo"' 38 | // if needed. It MUST only contain characters from semanticAlphabet per 39 | // the semantic versioning spec. 40 | PreRelease = "pre" 41 | 42 | // BuildMetadata is defined as a variable so it can be overridden during the 43 | // build process with: 44 | // '-ldflags "-X github.com/decred/dcrstakepool/internal/version.BuildMetadata=foo"' 45 | // if needed. It MUST only contain characters from semanticBuildAlphabet 46 | // per the semantic versioning spec. 47 | BuildMetadata = "" 48 | ) 49 | 50 | // String returns the application version as a properly formed string per the 51 | // semantic versioning 2.0.0 spec (http://semver.org/). 52 | func String() string { 53 | // Start with the major, minor, and patch versions. 54 | version := fmt.Sprintf("%d.%d.%d", Major, Minor, Patch) 55 | 56 | // Append pre-release version if there is one. The hyphen called for 57 | // by the semantic versioning spec is automatically appended and should 58 | // not be contained in the pre-release string. The pre-release version 59 | // is not appended if it contains invalid characters. 60 | preRelease := NormalizePreRelString(PreRelease) 61 | if preRelease != "" { 62 | version = fmt.Sprintf("%s-%s", version, preRelease) 63 | } 64 | 65 | // Append build metadata if there is any. The plus called for 66 | // by the semantic versioning spec is automatically appended and should 67 | // not be contained in the build metadata string. The build metadata 68 | // string is not appended if it contains invalid characters. 69 | build := NormalizeBuildString(BuildMetadata) 70 | if build != "" { 71 | version = fmt.Sprintf("%s+%s", version, build) 72 | } 73 | 74 | return version 75 | } 76 | 77 | // normalizeSemString returns the passed string stripped of all characters 78 | // which are not valid according to the provided semantic versioning alphabet. 79 | func normalizeSemString(str, alphabet string) string { 80 | var result bytes.Buffer 81 | for _, r := range str { 82 | if strings.ContainsRune(alphabet, r) { 83 | result.WriteRune(r) 84 | } 85 | } 86 | return result.String() 87 | } 88 | 89 | // NormalizePreRelString returns the passed string stripped of all characters 90 | // which are not valid according to the semantic versioning guidelines for 91 | // pre-release strings. In particular they MUST only contain characters in 92 | // semanticAlphabet. 93 | func NormalizePreRelString(str string) string { 94 | return normalizeSemString(str, semanticAlphabet) 95 | } 96 | 97 | // NormalizeBuildString returns the passed string stripped of all characters 98 | // which are not valid according to the semantic versioning guidelines for build 99 | // metadata strings. In particular they MUST only contain characters in 100 | // semanticBuildAlphabet. 101 | func NormalizeBuildString(str string) string { 102 | return normalizeSemString(str, semanticBuildAlphabet) 103 | } 104 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/decred/dcrstakepool/controllers" 13 | "github.com/decred/dcrstakepool/models" 14 | "github.com/decred/dcrstakepool/signal" 15 | "github.com/decred/dcrstakepool/stakepooldclient" 16 | "github.com/decred/dcrstakepool/system" 17 | "github.com/decred/slog" 18 | "github.com/jrick/logrotate/rotator" 19 | ) 20 | 21 | // logWriter implements an io.Writer that outputs to both standard output and 22 | // the write-end pipe of an initialized log rotator. 23 | type logWriter struct{} 24 | 25 | func (logWriter) Write(p []byte) (n int, err error) { 26 | os.Stdout.Write(p) 27 | if logRotator != nil { 28 | return logRotator.Write(p) 29 | } 30 | return 0, nil 31 | } 32 | 33 | // Loggers per subsystem. A single backend logger is created and all subsytem 34 | // loggers created from it will write to the backend. When adding new 35 | // subsystems, add the subsystem logger variable here and to the 36 | // subsystemLoggers map. 37 | // 38 | // Loggers can not be used before the log rotator has been initialized with a 39 | // log file. This must be performed early during application startup by calling 40 | // initLogRotator. 41 | var ( 42 | 43 | // backendLog is the logging backend used to create all subsystem loggers. 44 | // The backend must not be used before the log rotator has been initialized, 45 | // or data races and/or nil pointer dereferences will occur. 46 | backendLog = slog.NewBackend(logWriter{}) 47 | 48 | // logRotator is one of the logging outputs. It should be closed on 49 | // application shutdown. 50 | logRotator *rotator.Rotator 51 | 52 | controllersLog = backendLog.Logger("CNTL") 53 | log = backendLog.Logger("DCRS") 54 | modelsLog = backendLog.Logger("MODL") 55 | stakepooldclientLog = backendLog.Logger("GRPC") 56 | systemLog = backendLog.Logger("SYTM") 57 | ) 58 | 59 | // Initialize package-global logger variables. 60 | func init() { 61 | controllers.UseLogger(controllersLog) 62 | models.UseLogger(modelsLog) 63 | stakepooldclient.UseLogger(stakepooldclientLog) 64 | system.UseLogger(systemLog) 65 | signal.UseLogger(systemLog) 66 | } 67 | 68 | // subsystemLoggers maps each subsystem identifier to its associated logger. 69 | var subsystemLoggers = map[string]slog.Logger{ 70 | "DCRS": log, 71 | "CNTL": controllersLog, 72 | "GRPC": stakepooldclientLog, 73 | "MODL": modelsLog, 74 | "SYTM": systemLog, 75 | } 76 | 77 | // initLogRotator initializes the logging rotater to write logs to logFile and 78 | // create roll files in the same directory. It must be called before the 79 | // package-global log rotater variables are used. 80 | func initLogRotator(logFile string) { 81 | logDir, _ := filepath.Split(logFile) 82 | err := os.MkdirAll(logDir, 0700) 83 | if err != nil { 84 | fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err) 85 | os.Exit(1) 86 | } 87 | r, err := rotator.New(logFile, 10*1024, false, 3) 88 | if err != nil { 89 | fmt.Fprintf(os.Stderr, "failed to create file rotator: %v\n", err) 90 | os.Exit(1) 91 | } 92 | 93 | logRotator = r 94 | } 95 | 96 | // setLogLevel sets the logging level for provided subsystem. Invalid 97 | // subsystems are ignored. Uninitialized subsystems are dynamically created as 98 | // needed. 99 | func setLogLevel(subsystemID string, logLevel string) { 100 | // Ignore invalid subsystems. 101 | logger, ok := subsystemLoggers[subsystemID] 102 | if !ok { 103 | return 104 | } 105 | 106 | // Defaults to info if the log level is invalid. 107 | level, _ := slog.LevelFromString(logLevel) 108 | logger.SetLevel(level) 109 | } 110 | 111 | // setLogLevels sets the log level for all subsystem loggers to the passed 112 | // level. It also dynamically creates the subsystem loggers as needed, so it 113 | // can be used to initialize the logging system. 114 | func setLogLevels(logLevel string) { 115 | // Configure all sub-systems with the new logging level. Dynamically 116 | // create loggers as needed. 117 | for subsystemID := range subsystemLoggers { 118 | setLogLevel(subsystemID, logLevel) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2019 The Decred developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/decred/dcrd/chaincfg/v3" 11 | "github.com/decred/dcrd/hdkeychain/v3" 12 | ) 13 | 14 | const ( 15 | //real pubkeys 16 | testnetXPub1 = "tpubVpFtCRJV1U7fJQYLiDtKTZFnwwaHp6uhcavTjvXBusiY1pDQw5YqT1HGDcQT2wjLQZnL7N66o7atgscq6tMP6fr5ejyqDHD3eQ9C3KQURzu" 17 | testnetXPub2 = "tpubVpFtCRJV1U7fMienqUufobnNxxYACoLDTaAFmpspHy2iguBzoGRbi4btArDijbsNVvMVnciEC7ZHMCr8T19Ln7ECBuAT5UqYW21cKcNxMN6" 18 | mainnetXPub1 = "dpubZGWjhGoJRkwao4W8Jsk56RPJNSAHmEtuERoeuugKmGxFhU1zhZJ2MfScJjzccGcs3xLHYZN2V7FjAHfBoiHdcpXtVyUnJQMxZxRENNgTEsM" 19 | mainnetXPub2 = "dpubZGWjhGoJRkwaqnFhB6a85gjvSSbLuDxAj7ixmFqcu7RZPW7cthZU4jK7u7LZSrr7ooHGHFc1LEn1n9cypSvzKknqZYugKda6PYc5Ze7NhTL" 20 | simnetXPub1 = "spubVVBn1KgTWoDRajAZrymsoTRjP1qQdKTbuUMBBKw2q6vNVrbHXYGPTxDFgcaYYzrTRQ38mvkKt8dbk9pUHppT6WLZ23DroW8V3i3kptjfndx" 21 | simnetXPub2 = "spubVVBn1KgTWoDRefX2cjSRjhBYFahdbTvhMzB1Lia3hCseDjB4tdxFJ3FDPG3NGkBpA6XEjRxw1r9LnU5nRpkvKGkfxfAqqFtc72kaU5Fmn6r" 22 | ) 23 | 24 | type keysIn struct { 25 | coldFeeWallet string 26 | voteWallet string 27 | } 28 | 29 | type keysOut struct { 30 | coldFeeWallet *hdkeychain.ExtendedKey 31 | voteWallet *hdkeychain.ExtendedKey 32 | } 33 | 34 | type keyParse struct { 35 | params *chaincfg.Params 36 | keysIn keysIn 37 | keysOut keysOut 38 | isError bool 39 | } 40 | 41 | //in keys, expected out keys, and error value 42 | var keyTestValues = []keyParse{ 43 | //testnet 44 | {testNet3Params.Params, keysIn{testnetXPub1, testnetXPub2}, keysOut{hd(testnetXPub1, testNet3Params.Params), hd(testnetXPub2, testNet3Params.Params)}, false}, 45 | {testNet3Params.Params, keysIn{testnetXPub1, mainnetXPub2}, keysOut{hd(testnetXPub1, testNet3Params.Params), hd(mainnetXPub2, testNet3Params.Params)}, true}, 46 | {testNet3Params.Params, keysIn{"", mainnetXPub2}, keysOut{hd("", testNet3Params.Params), hd(mainnetXPub2, testNet3Params.Params)}, true}, 47 | //mainnet 48 | {mainNetParams.Params, keysIn{mainnetXPub1, mainnetXPub2}, keysOut{hd(mainnetXPub1, mainNetParams.Params), hd(mainnetXPub2, mainNetParams.Params)}, false}, 49 | {mainNetParams.Params, keysIn{simnetXPub1, mainnetXPub2}, keysOut{hd(simnetXPub1, mainNetParams.Params), hd(mainnetXPub2, mainNetParams.Params)}, true}, 50 | {mainNetParams.Params, keysIn{mainnetXPub1, mainnetXPub2 + "a"}, keysOut{hd(mainnetXPub1, mainNetParams.Params), hd(mainnetXPub2+"a", mainNetParams.Params)}, true}, 51 | //simnnet 52 | {simNetParams.Params, keysIn{simnetXPub1, simnetXPub2}, keysOut{hd(simnetXPub1, simNetParams.Params), hd(simnetXPub2, simNetParams.Params)}, false}, 53 | {simNetParams.Params, keysIn{testnetXPub1, simnetXPub2}, keysOut{hd(testnetXPub1, simNetParams.Params), hd(simnetXPub2, simNetParams.Params)}, true}, 54 | {simNetParams.Params, keysIn{simnetXPub1[:len(simnetXPub1)-1], simnetXPub2}, keysOut{hd(simnetXPub1[:len(simnetXPub1)-1], simNetParams.Params), hd(simnetXPub2, simNetParams.Params)}, true}, 55 | } 56 | 57 | //helper func string to extended key 58 | func hd(s string, params *chaincfg.Params) *hdkeychain.ExtendedKey { 59 | hd, _ := hdkeychain.NewKeyFromString(s, params) 60 | return hd 61 | } 62 | 63 | //an error will produce a nil key struct so use nil string value 64 | func strFromHd(hd *hdkeychain.ExtendedKey) string { 65 | if hd == nil { 66 | return "" 67 | } 68 | return hd.String() 69 | } 70 | 71 | func TestParsePubKeys(t *testing.T) { 72 | //dummy config 73 | var cfg config 74 | for _, test := range keyTestValues { 75 | //parsePubKeys uses these fields 76 | cfg.ColdWalletExtPub = test.keysIn.coldFeeWallet 77 | cfg.VotingWalletExtPub = test.keysIn.voteWallet 78 | //testing func 79 | err := cfg.parsePubKeys(test.params) 80 | //err if expected output key strings and real output key strings don't match or expected error status is different 81 | if strFromHd(test.keysOut.coldFeeWallet) != strFromHd(coldWalletFeeKey) || strFromHd(test.keysOut.voteWallet) != strFromHd(votingWalletVoteKey) || (err != nil) != test.isError { 82 | t.Error("for", test.keysIn, "expected", strFromHd(test.keysOut.coldFeeWallet), strFromHd(test.keysOut.voteWallet), "and is error=", test.isError, "got", strFromHd(coldWalletFeeKey), strFromHd(votingWalletVoteKey), "and is error=", err != nil) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | var stakepoolFinder = function() { 2 | 3 | var container = $("#stakepool-data"); 4 | 5 | if (container.length == 0) { 6 | return; 7 | } 8 | 9 | var isMainnet = container.data("ismainnet"); 10 | 11 | container.html("Loading..."); 12 | tableMarkup = ''; 13 | $.ajax({ 14 | url: "https://api.decred.org/?c=gsd", 15 | dataType: "json", 16 | success: function(data) { 17 | $.each(data, function(poolName, poolData) { 18 | if (isMainnet) { 19 | if (poolData["Network"] === 'testnet') { 20 | return; 21 | } 22 | } else { 23 | if (poolData["Network"] === 'mainnet') { 24 | return; 25 | } 26 | } 27 | var proportion = poolData["ProportionLive"] * 100; 28 | tableMarkup += ''; 29 | tableMarkup += ''; 30 | tableMarkup += ''; 31 | tableMarkup += ''; 32 | tableMarkup += ''; 33 | tableMarkup += ''; 34 | }); 35 | tableMarkup += '
    IDAddressProportion VSP Fees
    ' + poolName + '' + poolData["URL"].replace("https://", "") + '' + proportion.toFixed(2) + '%' + poolData["PoolFees"] + '%
    '; 36 | container.html(tableMarkup); 37 | $('.dtVerticalScroll').DataTable({ 38 | "scrollY": "251px", 39 | "scrollX": true, 40 | "scrollCollapse": true, 41 | "paging": false, 42 | "searching": false, 43 | "info": false 44 | }); 45 | $('.dataTables_length').addClass('bs-select'); 46 | }, 47 | }); 48 | }(); 49 | 50 | //carousel dot tooltip setup 51 | $(document).ready(function () { 52 | $('.carousel-nav__dot').tooltip({ 53 | placement: 'top', 54 | trigger: 'hover', 55 | width: '100px' 56 | }); 57 | }); 58 | 59 | //flickity carousel setup 60 | var $carousel = $('.main-carousel').flickity({ 61 | cellAlign: 'left', 62 | contain: true, 63 | wrapAround: true, 64 | arrowShape: { 65 | x0: 10, 66 | x1: 60, y1: 50, 67 | x2: 60, y2: 40, 68 | x3: 60 69 | }, 70 | prevNextButtons: false, 71 | pageDots: false 72 | }); 73 | 74 | var flkty = $carousel.data('flickity'); 75 | var $cellButtonGroup = $('.carousel-nav'); 76 | //add slide buttons 77 | var total = flkty.slides.length; 78 | 79 | for (i = 0; i < total; i++) { 80 | var title = $('.carousel-cell').eq(i).find('h2').text(); 81 | if (i === 0) { 82 | $cellButtonGroup.append(''); 83 | } else if (i === total - 1) { 84 | $cellButtonGroup.append(''); 85 | } else { 86 | $cellButtonGroup.append(''); 87 | } 88 | } 89 | 90 | $('.carousel-nav').prepend(''); 91 | $('.carousel-nav').append(''); 92 | 93 | var $cellButtons = $cellButtonGroup.find('.carousel-nav__dot'); 94 | 95 | // update selected cellButtons 96 | $carousel.on('select.flickity', function () { 97 | $cellButtons.filter('.is-selected') 98 | .removeClass('is-selected'); 99 | $cellButtons.eq(flkty.selectedIndex) 100 | .addClass('is-selected'); 101 | }); 102 | 103 | // select cell on button click 104 | $cellButtonGroup.on('click', '.carousel-nav__dot', function () { 105 | var index = $(this).index() - 1; 106 | $carousel.flickity('select', index); 107 | }); 108 | 109 | // previous 110 | $('.carousel-nav__previous').on('click', function () { 111 | $carousel.flickity('previous'); 112 | }); 113 | // next 114 | $('.carousel-nav__next').on('click', function () { 115 | $carousel.flickity('next'); 116 | }); 117 | 118 | -------------------------------------------------------------------------------- /views/admin/tickets.html: -------------------------------------------------------------------------------- 1 | {{define "admin/tickets"}} 2 |
    3 |
    4 | 5 | {{range .FlashError}} 6 |
    7 |
    8 |
    9 |
    10 |

    {{.}}

    11 |
    12 |
    13 |
    14 | {{end}} 15 | 16 | {{range .FlashSuccess}} 17 |
    18 |
    19 |
    20 |
    21 |

    {{.}}

    22 |
    23 |
    24 |
    25 | {{end}} 26 | 27 |
    28 |
    29 |

    30 | Ignored Low Fee Tickets 31 | {{if gt (len .IgnoredLowFeeTickets) 1 }} 32 |
    33 | Select all 34 |
    35 | {{end}} 36 |

    37 |
    38 | 39 | {{with .IgnoredLowFeeTickets}} 40 |
    41 |
    42 |
    43 | 44 | 45 | {{ range $tickethash, $msa := .}} 46 | 47 | 49 | 50 | 51 | 57 | 58 | {{end}} 59 | 60 |
    48 |
    {{printf "%.16s" $tickethash}}...
    {{$msa}}
    Block Explorer 52 | 56 |
    61 |
    62 |
    63 | 64 | {{ $.csrfField }} 65 | 66 |
    67 | {{else}} 68 |
    69 |

    Currently there are no ignored low fee tickets.

    70 |
    71 | {{end}} 72 |
    73 | 74 |
    75 |
    76 |

    77 | Added Low Fee Tickets 78 | {{if gt (len .AddedLowFeeTickets) 1 }} 79 |
    80 | Select all 81 |
    82 | {{end}} 83 |

    84 |
    85 | 86 | {{with .AddedLowFeeTickets}} 87 |
    88 |
    89 |
    90 | 91 | 92 | {{ range $tickethash, $msa := .}} 93 | 94 | 96 | 97 | 98 | 104 | 105 | {{end}} 106 | 107 |
    95 |
    {{printf "%.16s" $tickethash}}...
    {{$msa}}
    Block Explorer 99 | 103 |
    108 |
    109 |
    110 | 111 | {{ $.csrfField }} 112 | 113 |
    114 | {{else}} 115 |
    116 |

    Currently there are no added low fee tickets.

    117 |
    118 | {{end}} 119 |
    120 | 121 |
    122 |
    123 | {{end}} 124 | -------------------------------------------------------------------------------- /sample-dcrstakepool.conf: -------------------------------------------------------------------------------- 1 | ; Access to administrative pages like /status and /admintickets 2 | ; are restricted by both IP address and User ID. Only if both filters pass 3 | ; will a user be able to access those functions. 4 | 5 | adminips=127.0.0.1 6 | ; Multiple values can be used and are separated by a comma. 7 | ;adminips=127.0.0.1,192.0.2.1,198.51.100.1 8 | 9 | ; No default in case UserId 1 is a shared account of some sort. 10 | ;adminuserids=1 11 | ; Multiple values can be used and are separated by a comma. 12 | ;adminuserids=1,2,3 13 | 14 | ; Secret string used to encrypt API and to generate CSRF tokens. 15 | ; Can use openssl rand -hex 32 to generate one. 16 | ;apisecret= 17 | 18 | ; baseurl to use when emailing verification links. 19 | ; Make sure to skip using a trailing slash. 20 | ; baseurl=https://host.domain.tld 21 | 22 | ; Disable new user signups. 23 | ;closepool=1 24 | 25 | ; If you want to specify a custom message, do so here. 26 | ;closepoolmsg=The voting service is temporarily closed to new signups. 27 | 28 | ; Database configuration defaults to these, change as needed. 29 | ;dbhost=localhost 30 | ;dbport=3306 31 | ;dbname=stakepool 32 | ;dbuser=stakepool 33 | 34 | ; No default password so you need to specify one. 35 | ;dbpassword= 36 | 37 | ; Stakepoold hosts, will use default wallet RPC port for network 38 | ; if not specified. 39 | ; stakepooldhosts=10.0.0.20,10.0.0.21 40 | 41 | ; stakepoold RPC Cert. Absolute path or relative name in ~/.dcrstakepool 42 | ; stakepooldcerts=stakepoold1.cert,stakepoold2.cert 43 | 44 | ; Specify a Go-style network listener. Default is below. 45 | ;listen=:8000 46 | 47 | ; The HTTP request header containing the actual remote client IP address for 48 | ; accurate logging. The default value is the empty string, indicating to use 49 | ; golang's Request.RealAddr value, which may be incorrect when behind a proxy. 50 | ; See sample-nginx.conf for an example configuration of ngx_http_realip_module. 51 | ;realipheader=X-Real-IP 52 | 53 | ; Support email and link show on the homepage. 54 | ;poolemail=admin@example.com 55 | ;poollink=https://example.com/ 56 | 57 | ; Specified extended public key is used to generate fee payment addresses 58 | ; which are presented to the user. 59 | ; Should match dcrwallet's stakepoolcoldextkey configuration (without :10000). 60 | ;coldwalletextpub=xpub 61 | 62 | ; Fees as a percentage. 7.5 = 7.5%. Precision of 2, 7.99 = 7.99%. 63 | ; Should match dcrwallet's configuration. 64 | ;poolfees=7.5 65 | 66 | ; Mail server to use. Default is an empty string which disables email-based 67 | ; features like email verification of new users, password resets, and email 68 | ; address changes. This mode is intended to primarily be used for testing. 69 | ; For production use, a mail server is required. 70 | ;smtphost=localhost:25 71 | 72 | ; From address to use. 73 | ;smtpfrom=support@host.domain.tld 74 | 75 | ; Can specify username/password if authentication is required. 76 | ;smtpusername= 77 | ;smtppassword= 78 | 79 | ; Skip SMTP TLS cert verification. Will only skip if SMTPCert is empty 80 | ;smtpskipverify= 81 | 82 | ; Path for the smtp certificate file 83 | ;smtpcert= 84 | 85 | ; Connect to the SMTP server using smtps. 86 | ;usesmtps=false 87 | 88 | ; Stay on testnet until everything is well tested. 89 | testnet=1 90 | 91 | ; Specified extended public key is used to generate ticketed addresses 92 | ; which are combined with a user address for 1-of-2 multisig. 93 | ; Must be the voting wallet's masterpubkey for the default account. 94 | ;votingwalletextpub=xpub 95 | 96 | ; Debug logging level. 97 | ; Valid levels are {trace, debug, info, warn, error, critical} 98 | ; You may also specify =,=,... to set 99 | ; log level for individual subsystems. Use dcrstakepool --debuglevel=show to 100 | ; list available subsystems. 101 | ;debuglevel=info 102 | 103 | ; Various HTTP settings are in this section. 104 | 105 | ; Secret string used to encrypt session data. Can use openssl rand -hex 32 106 | ; to generate one. 107 | ;cookiesecret= 108 | 109 | ; Whether to set the secure flag on cookies. If you have SSL/TLS setup then 110 | ; you should change this to true. 111 | ;cookiesecure=true 112 | 113 | ; Path to the root folder/directory which contains CSS/fonts/images/javascript. 114 | ;publicpath=public 115 | 116 | ; Path to the root folder/directory which contains the HTML templates. 117 | ;templatepath=views 118 | 119 | ; Maximum number of voted tickets to show on tickets page. 120 | ;maxvotedtickets=1000 121 | 122 | ; The designated codename for this VSP. Customises the VSP logo in the top toolbar. 123 | ; eg. Alpha, Bravo, etc 124 | designation=YourVSP 125 | 126 | ; Operators own description of their VSP, up two to sentences. 127 | ; Printed on the home screen under the VSP overview 128 | description=Your VSP description 129 | -------------------------------------------------------------------------------- /backend/stakepoold/userdata/userdata.go: -------------------------------------------------------------------------------- 1 | package userdata 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/decred/dcrd/chaincfg/chainhash" 9 | ) 10 | 11 | // DBConfig stores DB login information. 12 | type DBConfig struct { 13 | DBHost string 14 | DBName string 15 | DBPassword string 16 | DBPort string 17 | DBUser string 18 | } 19 | 20 | // UserData stores the current snapshot of the user voting config. 21 | type UserData struct { 22 | sync.RWMutex 23 | DBConfig *DBConfig 24 | UserVotingConfig map[string]UserVotingConfig // [multisigaddr] 25 | } 26 | 27 | // UserVotingConfig contains per-user voting preferences. 28 | type UserVotingConfig struct { 29 | Userid int64 30 | MultiSigAddress string 31 | VoteBits uint16 32 | VoteBitsVersion uint32 33 | } 34 | 35 | // MySQLFetchAddedLowFeeTickets fetches any low fee tickets that were 36 | // manually added by the admin. 37 | func (u *UserData) MySQLFetchAddedLowFeeTickets() (map[chainhash.Hash]string, error) { 38 | var ( 39 | ticketHashString string 40 | ticketAddress string 41 | ) 42 | 43 | tickets := make(map[chainhash.Hash]string) 44 | 45 | db, err := sql.Open("mysql", fmt.Sprint(u.DBConfig.DBUser, ":", u.DBConfig.DBPassword, "@(", u.DBConfig.DBHost, ":", u.DBConfig.DBPort, ")/", u.DBConfig.DBName, "?charset=utf8mb4")) 46 | if err != nil { 47 | log.Errorf("Unable to open db: %v", err) 48 | return tickets, err 49 | } 50 | 51 | // sql.Open just validates its arguments without creating a connection 52 | // Verify that the data source name is valid with Ping: 53 | if err = db.Ping(); err != nil { 54 | log.Errorf("Unable to establish connection to db: %v", err) 55 | db.Close() 56 | return tickets, err 57 | } 58 | 59 | rows, err := db.Query("SELECT TicketHash, TicketAddress FROM LowFeeTicket") 60 | if err != nil { 61 | log.Errorf("Unable to query db: %v", err) 62 | db.Close() 63 | return tickets, err 64 | } 65 | 66 | for rows.Next() { 67 | err := rows.Scan(&ticketHashString, &ticketAddress) 68 | if err != nil { 69 | log.Errorf("Unable to scan row %v", err) 70 | continue 71 | } 72 | ticketHash, err := chainhash.NewHashFromStr(ticketHashString) 73 | if err != nil { 74 | log.Warnf("NewHashFromStr failed for %v: %v", err) 75 | continue 76 | } 77 | tickets[*ticketHash] = ticketAddress 78 | } 79 | if err = rows.Err(); err != nil { 80 | db.Close() 81 | return tickets, err 82 | } 83 | 84 | return tickets, db.Close() 85 | } 86 | 87 | // MySQLFetchUserVotingConfig fetches the voting preferences of all users 88 | // who have completed registration of the pool by submitting an address 89 | // and generating a multisig ticket address. 90 | func (u *UserData) MySQLFetchUserVotingConfig() (map[string]UserVotingConfig, error) { 91 | var ( 92 | userid int64 93 | multiSigAddress string 94 | voteBits int64 95 | voteBitsVersion int64 96 | ) 97 | 98 | userInfo := map[string]UserVotingConfig{} 99 | 100 | db, err := sql.Open("mysql", fmt.Sprint(u.DBConfig.DBUser, ":", u.DBConfig.DBPassword, "@(", u.DBConfig.DBHost, ":", u.DBConfig.DBPort, ")/", u.DBConfig.DBName, "?charset=utf8mb4")) 101 | if err != nil { 102 | log.Errorf("Unable to open db: %v", err) 103 | return userInfo, err 104 | } 105 | 106 | // sql.Open just validates its arguments without creating a connection 107 | // Verify that the data source name is valid with Ping: 108 | if err = db.Ping(); err != nil { 109 | log.Errorf("Unable to establish connection to db: %v", err) 110 | db.Close() 111 | return userInfo, err 112 | } 113 | 114 | rows, err := db.Query("SELECT UserId, MultiSigAddress, VoteBits, VoteBitsVersion FROM Users WHERE MultiSigAddress <> ''") 115 | if err != nil { 116 | log.Errorf("Unable to query db: %v", err) 117 | db.Close() 118 | return userInfo, err 119 | } 120 | 121 | defer rows.Close() 122 | for rows.Next() { 123 | err := rows.Scan(&userid, &multiSigAddress, &voteBits, &voteBitsVersion) 124 | if err != nil { 125 | log.Errorf("Unable to scan row %v", err) 126 | continue 127 | } 128 | userInfo[multiSigAddress] = UserVotingConfig{ 129 | Userid: userid, 130 | MultiSigAddress: multiSigAddress, 131 | VoteBits: uint16(voteBits), 132 | VoteBitsVersion: uint32(voteBitsVersion), 133 | } 134 | } 135 | if err = rows.Err(); err != nil { 136 | db.Close() 137 | return userInfo, err 138 | } 139 | 140 | return userInfo, db.Close() 141 | } 142 | 143 | // DBSetConfig sets the database configuration. 144 | func (u *UserData) DBSetConfig(DBUser string, DBPassword string, DBHost string, DBPort string, DBName string) { 145 | dbconfig := &DBConfig{ 146 | DBHost: DBHost, 147 | DBName: DBName, 148 | DBPassword: DBPassword, 149 | DBPort: DBPort, 150 | DBUser: DBUser, 151 | } 152 | u.Lock() 153 | u.DBConfig = dbconfig 154 | u.Unlock() 155 | } 156 | -------------------------------------------------------------------------------- /public/js/vendor/d3-format.v1.min.js: -------------------------------------------------------------------------------- 1 | // https://d3js.org/d3-format/ v1.4.1 Copyright 2019 Mike Bostock 2 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t=t||self).d3=t.d3||{})}(this,function(t){"use strict";function n(t,n){if((i=(t=n?t.toExponential(n-1):t.toExponential()).indexOf("e"))<0)return null;var i,r=t.slice(0,i);return[r.length>1?r[0]+r.slice(2):r,+t.slice(i+1)]}function i(t){return(t=n(Math.abs(t)))?t[1]:NaN}var r,e=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function o(t){if(!(n=e.exec(t)))throw new Error("invalid format: "+t);var n;return new a({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function a(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function s(t,i){var r=n(t,i);if(!r)return t+"";var e=r[0],o=r[1];return o<0?"0."+new Array(-o).join("0")+e:e.length>o+1?e.slice(0,o+1)+"."+e.slice(o+1):e+new Array(o-e.length+2).join("0")}o.prototype=a.prototype,a.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var u={"%":function(t,n){return(100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return s(100*t,n)},r:s,s:function(t,i){var e=n(t,i);if(!e)return t+"";var o=e[0],a=e[1],s=a-(r=3*Math.max(-8,Math.min(8,Math.floor(a/3))))+1,u=o.length;return s===u?o:s>u?o+new Array(s-u+1).join("0"):s>0?o.slice(0,s)+"."+o.slice(s):"0."+new Array(1-s).join("0")+n(t,Math.max(0,i+s-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}};function c(t){return t}var f,h=Array.prototype.map,l=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function d(t){var n,e,a=void 0===t.grouping||void 0===t.thousands?c:(n=h.call(t.grouping,Number),e=t.thousands+"",function(t,i){for(var r=t.length,o=[],a=0,s=n[0],u=0;r>0&&s>0&&(u+s+1>i&&(s=Math.max(1,i-u)),o.push(t.substring(r-=s,r+s)),!((u+=s+1)>i));)s=n[a=(a+1)%n.length];return o.reverse().join(e)}),s=void 0===t.currency?"":t.currency[0]+"",f=void 0===t.currency?"":t.currency[1]+"",d=void 0===t.decimal?".":t.decimal+"",m=void 0===t.numerals?c:function(t){return function(n){return n.replace(/[0-9]/g,function(n){return t[+n]})}}(h.call(t.numerals,String)),p=void 0===t.percent?"%":t.percent+"",g=void 0===t.minus?"-":t.minus+"",v=void 0===t.nan?"NaN":t.nan+"";function M(t){var n=(t=o(t)).fill,i=t.align,e=t.sign,c=t.symbol,h=t.zero,M=t.width,y=t.comma,x=t.precision,b=t.trim,w=t.type;"n"===w?(y=!0,w="g"):u[w]||(void 0===x&&(x=12),b=!0,w="g"),(h||"0"===n&&"="===i)&&(h=!0,n="0",i="=");var S="$"===c?s:"#"===c&&/[boxX]/.test(w)?"0"+w.toLowerCase():"",j="$"===c?f:/[%p]/.test(w)?p:"",k=u[w],z=/[defgprs%]/.test(w);function A(t){var o,s,u,c=S,f=j;if("c"===w)f=k(t)+f,t="";else{var p=(t=+t)<0;if(t=isNaN(t)?v:k(Math.abs(t),x),b&&(t=function(t){t:for(var n,i=t.length,r=1,e=-1;r0){if(!+t[r])break t;e=0}}return e>0?t.slice(0,e)+t.slice(n+1):t}(t)),p&&0==+t&&(p=!1),c=(p?"("===e?e:g:"-"===e||"("===e?"":e)+c,f=("s"===w?l[8+r/3]:"")+f+(p&&"("===e?")":""),z)for(o=-1,s=t.length;++o(u=t.charCodeAt(o))||u>57){f=(46===u?d+t.slice(o+1):t.slice(o))+f,t=t.slice(0,o);break}}y&&!h&&(t=a(t,1/0));var A=c.length+t.length+f.length,N=A>1)+c+t+f+N.slice(A);break;default:t=N+c+t+f}return m(t)}return x=void 0===x?6:/[gprs]/.test(w)?Math.max(1,Math.min(21,x)):Math.max(0,Math.min(20,x)),A.toString=function(){return t+""},A}return{format:M,formatPrefix:function(t,n){var r=M(((t=o(t)).type="f",t)),e=3*Math.max(-8,Math.min(8,Math.floor(i(n)/3))),a=Math.pow(10,-e),s=l[8+e/3];return function(t){return r(a*t)+s}}}}function m(n){return f=d(n),t.format=f.format,t.formatPrefix=f.formatPrefix,f}m({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"}),t.FormatSpecifier=a,t.formatDefaultLocale=m,t.formatLocale=d,t.formatSpecifier=o,t.precisionFixed=function(t){return Math.max(0,-i(Math.abs(t)))},t.precisionPrefix=function(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(i(n)/3)))-i(Math.abs(t)))},t.precisionRound=function(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,i(n)-i(t))+1},Object.defineProperty(t,"__esModule",{value:!0})}); 3 | -------------------------------------------------------------------------------- /backend/stakepoold/stakepool/client.go: -------------------------------------------------------------------------------- 1 | package stakepool 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "decred.org/dcrwallet/rpc/client/dcrwallet" 9 | "github.com/decred/dcrd/chaincfg/v3" 10 | "github.com/decred/dcrd/rpcclient/v6" 11 | ) 12 | 13 | const ( 14 | // connectionRetryInterval is the amount of time to wait in between 15 | // retries when automatically reconnecting to an RPC server. 16 | connectionRetryInterval = time.Second * 5 17 | // disconnectCheckInterval is the amount of time to wait between 18 | // checks for a disconnection. 19 | disconnectCheckInterval = time.Second * 10 20 | ) 21 | 22 | // Client holds the information related to an rpcclient and handles access to 23 | // that client through a mutex. 24 | // 25 | // It should be noted that this is a temporary fix to the problem that rpcclient 26 | // does not return an error when autoreconnect is turned on but the client is 27 | // disconnected. The permanent solution is to change the behaviour of rpccleint. 28 | // TODO: Remove this file. 29 | type Client struct { 30 | // client is protected by a mutex that must be held for reads/writes. 31 | client *rpcclient.Client 32 | mux sync.RWMutex 33 | 34 | cfg *rpcclient.ConnConfig 35 | ntfnHandlers *rpcclient.NotificationHandlers 36 | stop chan struct{} 37 | 38 | connected chan struct{} 39 | connectedMux sync.Mutex 40 | 41 | params *chaincfg.Params 42 | } 43 | 44 | // Connected returns a receiving copy of the current connected channel. If 45 | // disconnected and the channel is not yet blocking, creates a new channel that 46 | // will be closed on a successful reconnect. 47 | func (c *Client) Connected() <-chan struct{} { 48 | c.connectedMux.Lock() 49 | defer c.connectedMux.Unlock() 50 | if c.client.Disconnected() { 51 | // Start blocking on connected chan if not already. 52 | select { 53 | case <-c.connected: 54 | c.connected = make(chan struct{}) 55 | default: 56 | } 57 | } 58 | return c.connected 59 | } 60 | 61 | // IsConnected checks and returns whethere the client is currently connected. 62 | func (c *Client) IsConnected() bool { 63 | select { 64 | case <-c.Connected(): 65 | return true 66 | default: 67 | return false 68 | } 69 | } 70 | 71 | // RPCClient allows access to the underlying rpcclient by providing a copy of 72 | // its address. 73 | func (c *Client) RPCClient() *dcrwallet.Client { 74 | c.mux.RLock() 75 | defer c.mux.RUnlock() 76 | raw := dcrwallet.RawRequestCaller(c.client) 77 | return dcrwallet.NewClient(raw, c.params) 78 | } 79 | 80 | // NewClient creates a new Client and starts the automatic reconnection handler. 81 | // Returns an error if unable to construct a new rpcclient. 82 | func NewClient(ctx context.Context, wg *sync.WaitGroup, cfg *rpcclient.ConnConfig, ntfnHandlers *rpcclient.NotificationHandlers, params *chaincfg.Params) (*Client, error) { 83 | client, err := rpcclient.New(cfg, ntfnHandlers) 84 | if err != nil { 85 | return nil, err 86 | } 87 | c := &Client{ 88 | client: client, 89 | cfg: cfg, 90 | ntfnHandlers: ntfnHandlers, 91 | connected: make(chan struct{}), 92 | params: params, 93 | } 94 | // A closed connected channel indcates successfully connected. 95 | close(c.connected) 96 | wg.Add(1) 97 | go c.autoReconnect(ctx, wg) 98 | return c, nil 99 | } 100 | 101 | // autoReconnect waits for a disconnect or stop. On disconnect it attempts to 102 | // reconnect to the client every connectionRetryInterval. 103 | // 104 | // This function must be run as a goroutine. 105 | func (c *Client) autoReconnect(ctx context.Context, wg *sync.WaitGroup) { 106 | defer wg.Done() 107 | out: 108 | for { 109 | select { 110 | case <-ctx.Done(): 111 | break out 112 | case <-time.After(disconnectCheckInterval): 113 | if c.IsConnected() { 114 | continue out 115 | } 116 | // Client is disconnected. Try to reconnect. 117 | } 118 | 119 | reconnect: 120 | for { 121 | select { 122 | case <-ctx.Done(): 123 | break out 124 | default: 125 | } 126 | 127 | // Start a new client. 128 | client, err := rpcclient.New(c.cfg, c.ntfnHandlers) 129 | 130 | if err != nil { 131 | log.Warnf("Failed to connect to %s: %v", 132 | c.cfg.Host, err) 133 | 134 | log.Infof("Retrying connection to %s in "+ 135 | "%s", c.cfg.Host, connectionRetryInterval) 136 | time.Sleep(connectionRetryInterval) 137 | continue reconnect 138 | } 139 | 140 | // Properly shutdown old client. 141 | c.mux.Lock() 142 | c.client.Shutdown() 143 | c.client.WaitForShutdown() 144 | // Switch the new client with the old, shutdown one. 145 | c.client = client 146 | c.mux.Unlock() 147 | 148 | // Close the connected channel so that all waiting 149 | // processes can continue. 150 | close(c.connected) 151 | 152 | log.Infof("Reestablished connection to RPC server %s", 153 | c.cfg.Host) 154 | 155 | // Break out of the reconnect loop back to wait for 156 | // disconnect again. 157 | break 158 | } 159 | } 160 | // Stop blocking on connected if blocking, as we will never reconnect again. 161 | if !c.IsConnected() { 162 | close(c.connected) 163 | } 164 | log.Tracef("RPC client reconnect handler done for %s", c.cfg.Host) 165 | } 166 | -------------------------------------------------------------------------------- /system/middleware.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "github.com/decred/dcrstakepool/models" 10 | "github.com/dgrijalva/jwt-go" 11 | "github.com/go-gorp/gorp" 12 | "github.com/gorilla/sessions" 13 | "github.com/zenazn/goji/web" 14 | gojimw "github.com/zenazn/goji/web/middleware" 15 | "github.com/zenazn/goji/web/mutil" 16 | ) 17 | 18 | // ApplyTemplates makes sure templates are stored in the context 19 | func (application *Application) ApplyTemplates(c *web.C, h http.Handler) http.Handler { 20 | fn := func(w http.ResponseWriter, r *http.Request) { 21 | c.Env["Template"] = application.Template 22 | h.ServeHTTP(w, r) 23 | } 24 | return http.HandlerFunc(fn) 25 | } 26 | 27 | // ApplySessions makes sure controllers can have access to session 28 | func (application *Application) ApplySessions(c *web.C, h http.Handler) http.Handler { 29 | fn := func(w http.ResponseWriter, r *http.Request) { 30 | session, err := application.Store.New(r, "session") 31 | if err != nil { 32 | log.Warnf("session load err: %v ", err) 33 | } 34 | c.Env["Session"] = session 35 | h.ServeHTTP(w, r) 36 | } 37 | return http.HandlerFunc(fn) 38 | } 39 | 40 | // ApplyDbMap makes sure controllers can have access to the gorp DbMap. 41 | func (application *Application) ApplyDbMap(c *web.C, h http.Handler) http.Handler { 42 | fn := func(w http.ResponseWriter, r *http.Request) { 43 | c.Env["DbMap"] = application.DbMap 44 | h.ServeHTTP(w, r) 45 | } 46 | return http.HandlerFunc(fn) 47 | } 48 | 49 | // ApplyAPI verifies the header's API token and ensures it belongs to a user. 50 | func (application *Application) ApplyAPI(c *web.C, h http.Handler) http.Handler { 51 | fn := func(w http.ResponseWriter, r *http.Request) { 52 | if strings.HasPrefix(r.URL.Path, "/api") { 53 | authHeader := r.Header.Get("Authorization") 54 | if strings.HasPrefix(authHeader, "Bearer ") { 55 | apitoken := strings.TrimPrefix(authHeader, "Bearer ") 56 | 57 | JWTtoken, err := jwt.Parse(apitoken, func(token *jwt.Token) (interface{}, error) { 58 | // validate signing algorithm 59 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 60 | return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) 61 | } 62 | return []byte(application.APISecret), nil 63 | }) 64 | 65 | if err != nil { 66 | log.Warnf("invalid token %v: %v", apitoken, err) 67 | } else if claims, ok := JWTtoken.Claims.(jwt.MapClaims); ok && JWTtoken.Valid { 68 | dbMap := c.Env["DbMap"].(*gorp.DbMap) 69 | 70 | user, err := models.GetUserByID(dbMap, int64(claims["loggedInAs"].(float64))) 71 | if err != nil { 72 | log.Errorf("unable to map apitoken %v to user id %v", apitoken, claims["loggedInAs"]) 73 | } else { 74 | c.Env["APIUserID"] = user.ID 75 | log.Infof("mapped apitoken %v to user id %v", apitoken, user.ID) 76 | } 77 | } 78 | } 79 | } 80 | h.ServeHTTP(w, r) 81 | } 82 | return http.HandlerFunc(fn) 83 | } 84 | 85 | // ApplyCaptcha verfies whether or not the captcha has been solved. 86 | func (application *Application) ApplyCaptcha(c *web.C, h http.Handler) http.Handler { 87 | fn := func(w http.ResponseWriter, r *http.Request) { 88 | session := c.Env["Session"].(*sessions.Session) 89 | if done, ok := session.Values["CaptchaDone"].(bool); ok { 90 | c.Env["CaptchaDone"] = done 91 | } else { 92 | c.Env["CaptchaDone"] = false 93 | } 94 | h.ServeHTTP(w, r) 95 | } 96 | return http.HandlerFunc(fn) 97 | } 98 | 99 | // ApplyAuth populates a user's info in the header if their userID is found in 100 | // the database. 101 | func (application *Application) ApplyAuth(c *web.C, h http.Handler) http.Handler { 102 | fn := func(w http.ResponseWriter, r *http.Request) { 103 | session := c.Env["Session"].(*sessions.Session) 104 | if userID := session.Values["UserId"]; userID != nil { 105 | dbMap := c.Env["DbMap"].(*gorp.DbMap) 106 | 107 | user, err := dbMap.Get(models.User{}, userID) 108 | if err != nil { 109 | log.Warnf("Auth error: %v", err) 110 | c.Env["User"] = nil 111 | } else { 112 | c.Env["User"] = user 113 | } 114 | } 115 | h.ServeHTTP(w, r) 116 | } 117 | return http.HandlerFunc(fn) 118 | } 119 | 120 | // Logger is a middleware that logs the start and end of each request, along 121 | // with some useful data about what was requested, what the response status was, 122 | // and how long it took to return. This should be used after the RequestID 123 | // middleware. 124 | func Logger(RealIPHeader string) func(c *web.C, h http.Handler) http.Handler { 125 | return func(c *web.C, h http.Handler) http.Handler { 126 | fn := func(w http.ResponseWriter, r *http.Request) { 127 | reqID := gojimw.GetReqID(*c) 128 | 129 | log.Tracef("[%s] Started %s %q, from %s", reqID, r.Method, 130 | r.URL.String(), ClientIP(r, RealIPHeader)) 131 | 132 | lw := mutil.WrapWriter(w) 133 | 134 | t1 := time.Now() 135 | h.ServeHTTP(lw, r) 136 | 137 | if lw.Status() == 0 { 138 | lw.WriteHeader(http.StatusOK) 139 | } 140 | t2 := time.Now() 141 | 142 | log.Tracef("[%s] Returning %03d in %s", reqID, lw.Status(), t2.Sub(t1)) 143 | } 144 | return http.HandlerFunc(fn) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /views/main.html: -------------------------------------------------------------------------------- 1 | {{define "main"}} 2 | {{template "header" .}} 3 | 4 | 81 | 82 | 120 | {{.Content}} 121 | {{template "footer" .}} 122 | {{end}} 123 | 124 | --------------------------------------------------------------------------------