Add basic HTML page and project license (#15)

This commit is contained in:
Jamie Holdstock 2020-05-15 16:07:26 +01:00 committed by GitHub
parent d8b6517dbd
commit 7c5ab7ebae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 144 additions and 52 deletions

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2020 The Decred developers
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,21 +1,30 @@
# dcrvsp
[![Build Status](https://github.com/jholdstock/dcrvsp/workflows/Build%20and%20Test/badge.svg)](https://github.com/jholdstock/dcrvsp/actions)
[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
[![Go Report Card](https://goreportcard.com/badge/github.com/jholdstock/dcrvsp)](https://goreportcard.com/report/github.com/jholdstock/dcrvsp)
## Design decisions
- [gin-gonic](https://github.com/gin-gonic/gin) webserver
- [bbolt](https://github.com/etcd-io/bbolt) database
- [gin-gonic](https://github.com/gin-gonic/gin) webserver for both front-end and API.
- API uses JSON encoded reqs/resps in HTTP body.
- [bbolt](https://github.com/etcd-io/bbolt) k/v database.
- Tickets are stored in a single bucket, using ticket hash as the key and a
json encoded representation of the ticket as the value.
- [wsrpc](https://github.com/jrick/wsrpc) for dcrwallet comms.
## MVP features
- VSP API "v3" as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574)
and implemented in [dcrstakepool #625](https://github.com/decred/dcrstakepool/pull/625)
- Request fee amount
- Request fee address
- Pay fee
- Set voting preferences
- VSP API as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574)
- Request fee amount (`GET /fee`)
- Request fee address (`POST /feeaddress`)
- Pay fee (`POST /payFee`)
- Ticket status (`POST /ticketstatus`)
- Set voting preferences (TBD)
- A minimal, static, web front-end providing pool stats and basic connection instructions.
- Fees have an expiry period. If the fee is not paid within this period, the
client must request a new fee. This enables the VSP to alter its fee rate.
## Future features
@ -25,3 +34,12 @@ and implemented in [dcrstakepool #625](https://github.com/decred/dcrstakepool/pu
- Accountability for both client and server changes to voting preferences.
- Consistency checking across connected wallets.
- Validate votebits provided in PayFee request are valid per current agendas.
## Issue Tracker
The [integrated github issue tracker](https://github.com/jholdstock/dcrvsp/issues)
is used for this project.
## License
dcrvsp is licensed under the [copyfree](http://copyfree.org) ISC License.

View File

@ -54,7 +54,10 @@ func (vdb *VspDatabase) InsertFeeAddressVotingKey(address, votingKey string, vot
if err != nil {
return err
}
ticketBkt.Put(k, ticketBytes)
err = ticketBkt.Put(k, ticketBytes)
if err != nil {
return err
}
}
}

4
log.go
View File

@ -38,7 +38,7 @@ var (
// application shutdown.
logRotator *rotator.Rotator
vspLog = backendLog.Logger("VSP")
log = backendLog.Logger("VSP")
dbLog = backendLog.Logger(" DB")
)
@ -49,7 +49,7 @@ func init() {
// subsystemLoggers maps each subsystem identifier to its associated logger.
var subsystemLoggers = map[string]slog.Logger{
"VSP": vspLog,
"VSP": log,
" DB": dbLog,
}

33
main.go
View File

@ -1,33 +1,42 @@
package main
import (
"log"
"fmt"
"os"
"github.com/jholdstock/dcrvsp/database"
"github.com/jrick/wsrpc/v2"
)
var cfg *config
var db *database.VspDatabase
var nodeConnection *wsrpc.Client
var (
cfg *config
db *database.VspDatabase
nodeConnection *wsrpc.Client
)
func main() {
var err error
cfg, err := loadConfig()
cfg, err = loadConfig()
if err != nil {
log.Fatalf("config error: %v", err)
// Don't use logger here because it may not be initialised yet.
fmt.Fprintf(os.Stderr, "config error: %v", err)
os.Exit(1)
}
db, err = database.New(cfg.dbPath)
if err != nil {
log.Fatalf("database error: %v", err)
log.Errorf("database error: %v", err)
os.Exit(1)
}
defer db.Close()
// Start HTTP server
log.Printf("Listening on %s", cfg.Listen)
log.Print(newRouter().Run(cfg.Listen))
log.Infof("Listening on %s", cfg.Listen)
// TODO: Make releaseMode properly configurable.
releaseMode := false
err = newRouter(releaseMode).Run(cfg.Listen)
if err != nil {
log.Errorf("web server terminated with error: %v", err)
os.Exit(1)
}
}

View File

@ -9,7 +9,6 @@ import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"time"
@ -28,10 +27,10 @@ const (
defaultFeeAddressExpiration = 24 * time.Hour
)
func sendJSONResponse(resp interface{}, code int, c *gin.Context) {
func sendJSONResponse(resp interface{}, c *gin.Context) {
dec, err := json.Marshal(resp)
if err != nil {
log.Printf("JSON marshal error: %v", err)
log.Errorf("JSON marshal error: %v", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
@ -39,7 +38,7 @@ func sendJSONResponse(resp interface{}, code int, c *gin.Context) {
sig := ed25519.Sign(cfg.signKey, dec)
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
c.Writer.Header().Set("VSP-Signature", hex.EncodeToString(sig))
c.Writer.WriteHeader(code)
c.Writer.WriteHeader(http.StatusOK)
c.Writer.Write(dec)
}
@ -47,14 +46,14 @@ func pubKey(c *gin.Context) {
sendJSONResponse(pubKeyResponse{
Timestamp: time.Now().Unix(),
PubKey: cfg.pubKey,
}, http.StatusOK, c)
}, c)
}
func fee(c *gin.Context) {
sendJSONResponse(feeResponse{
Timestamp: time.Now().Unix(),
Fee: cfg.VSPFee,
}, http.StatusOK, c)
}, c)
}
func feeAddress(c *gin.Context) {
@ -173,7 +172,7 @@ func feeAddress(c *gin.Context) {
Request: feeAddressRequest,
FeeAddress: newAddress,
Expiration: now.Add(defaultFeeAddressExpiration).Unix(),
}, http.StatusOK, c)
}, c)
}
func payFee(c *gin.Context) {
@ -206,7 +205,7 @@ func payFee(c *gin.Context) {
validFeeAddrs, err := db.GetInactiveFeeAddresses()
if err != nil {
log.Fatalf("database error: %v", err)
log.Errorf("database error: %v", err)
c.AbortWithError(http.StatusInternalServerError, errors.New("database error"))
return
}
@ -307,7 +306,7 @@ findAddress:
Timestamp: now.Unix(),
TxHash: resp,
Request: payFeeRequest,
}, http.StatusOK, c)
}, c)
}
// PayFee2 is copied from the stakepoold implementation in #625
@ -403,5 +402,5 @@ func ticketStatus(c *gin.Context) {
Request: ticketStatusRequest,
Status: "active", // TODO - active, pending, expired (missed, revoked?)
VoteBits: voteBits,
}, http.StatusOK, c)
}, c)
}

View File

@ -6,24 +6,20 @@ import (
type netParams struct {
*chaincfg.Params
DcrdRPCServerPort string
WalletRPCServerPort string
}
var mainNetParams = netParams{
Params: chaincfg.MainNetParams(),
DcrdRPCServerPort: "9109",
WalletRPCServerPort: "9111",
WalletRPCServerPort: "9110",
}
var testNet3Params = netParams{
Params: chaincfg.TestNet3Params(),
DcrdRPCServerPort: "19109",
WalletRPCServerPort: "19111",
WalletRPCServerPort: "19110",
}
var simNetParams = netParams{
Params: chaincfg.SimNetParams(),
DcrdRPCServerPort: "19556",
WalletRPCServerPort: "19558",
WalletRPCServerPort: "19557",
}

7
public/css/dcrvsp.css Normal file
View File

@ -0,0 +1,7 @@
body {
background-color: #091440;
color: white;
}
img {
height: 70px;
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1719 522"><defs><style>.a{fill:#fff;}.b{fill:url(#a);}.c{fill:url(#b);}</style><linearGradient id="a" x1="368.44" y1="296.18" x2="512.25" y2="213.16" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#36bdba"/><stop offset="0.93" stop-color="#39d8aa"/></linearGradient><linearGradient id="b" x1="241.1" y1="330.17" x2="359.1" y2="212.18" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#2576f4"/><stop offset="1" stop-color="#3298da"/></linearGradient></defs><title>transparent background - primary - negative - gardient</title><path class="a" d="M995.21,347.55c-20.6,0-36.14-17.29-36.14-40.22v-.41c0-22.7,15.54-39.82,36.14-39.82,12.46,0,22.27,4.95,31.58,16l26.76-20.71c-14-18.47-32.94-27.46-57.94-27.46-41.85,0-73.41,31.13-73.41,72.4v.4c0,19.93,7.44,38.26,21,51.6,13.34,13.15,31.38,20.39,50.82,20.39,26.23,0,45-9.22,60.54-29.84l-26.67-19C1019.26,340.94,1010.21,347.55,995.21,347.55Z"/><path class="a" d="M1168.88,326.77c13-5.52,28.43-17.35,28.43-42.42v-.41c0-13.64-4-24.15-12.16-32.12-9.81-10-25.12-15.08-45.53-15.08h-66.14v140.1h35.93V333.2H1130l29,43.64h41.25l-33.62-49.11Zm-7.71-39.95c0,11.77-9,19.08-23.41,19.08h-28.35V267.33h28.15c15,0,23.61,6.95,23.61,19.08Z"/><polygon class="a" points="1255.58 320.05 1324.74 320.05 1324.74 292.55 1255.58 292.55 1255.58 266.74 1331.95 266.74 1331.95 236.77 1220.05 236.77 1220.05 376.86 1332.98 376.86 1332.98 346.89 1255.58 346.89 1255.58 320.05"/><path class="a" d="M1470.08,256.27c-14.06-12.76-34.33-19.5-58.61-19.5h-53.58V376.86h52.75c24.4,0,44.86-6.95,59.17-20.11,13.72-12.61,21-30,21-50.35V306C1490.78,285.75,1483.62,268.56,1470.08,256.27ZM1453.81,307c0,23.71-16,38.44-41.72,38.44h-18.27V268.17h18.27c25.74,0,41.72,14.73,41.72,38.44Z"/><path class="a" d="M835.15,235.08c-38.43,0-67.4,31.12-67.4,72.38,0,41.78,29.85,72.1,71,72.1,22.33,0,40.11-7.59,54.28-23.19l-14.19-12.59c-12.35,11.23-24.27,16-39.54,16-24.15,0-41.66-15.39-45.69-40.16l-.72-4.4h108c.14-1.74.25-3.51.25-5.32,0-20.76-5.92-39.5-16.68-52.77C872.79,242.72,855.73,235.08,835.15,235.08Zm-42.42,65.68.58-4.29c3.39-25.3,19.71-41.65,41.57-41.65,22.49,0,38.11,16,40.75,41.77l.43,4.17Z"/><path class="a" d="M665.29,234.79c-31.84,0-64.07,24.86-64.07,72.39,0,36,22,72.38,64.07,72.38,19.15,0,34.44-8.45,46.76-25.83L719,344v32.83h25.92V165.91H719V267.85l-6.8-8.76C699.47,242.74,684.14,234.79,665.29,234.79Zm54.22,72.39c0,29-19.86,50.89-46.19,50.89-26.6,0-45.9-21.4-45.9-50.89,0-30,18.87-50.9,45.9-50.9C699.65,256.28,719.51,278.16,719.51,307.18Z"/><path class="b" d="M453.89,320l57,57H454.83l-94-94H419a47,47,0,1,0,0-94H400.07l-47-47h63.81a94,94,0,0,1,94,94C510.88,274.12,494.07,304,453.89,320Z"/><path class="c" d="M289.86,199l-57-57h56.05l94,94H324.76a47,47,0,1,0,0,94h18.91l47,47h-63.8a94,94,0,0,1-94-94C232.87,244.88,249.67,215,289.86,199Z"/></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,22 +1,51 @@
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func newRouter() *gin.Engine {
router := gin.Default()
func newRouter(releaseMode bool) *gin.Engine {
// With release mode enabled, gin will only read template files once and cache them.
// With release mode disabled, templates will be reloaded on the fly.
if releaseMode {
gin.SetMode(gin.ReleaseMode)
}
router := gin.New()
router.LoadHTMLGlob("templates/*")
// Recovery middleware handles any go panics generated while processing web
// requests. Ensures a 500 response is sent to the client rather than
// sending no response at all.
router.Use(gin.Recovery())
if !releaseMode {
// Logger middleware outputs very detailed logging of webserver requests
// to the terminal. Does not get logged to file.
router.Use(gin.Logger())
}
// Serve static web resources
router.Static("/public", "./public/")
router.GET("/", homepage)
api := router.Group("/api")
api.Use()
{
router.GET("/fee", fee)
router.POST("/feeaddress", feeAddress)
router.GET("/pubkey", pubKey)
router.POST("/payfee", payFee)
router.POST("/ticketstatus", ticketStatus)
api.GET("/fee", fee)
api.POST("/feeaddress", feeAddress)
api.GET("/pubkey", pubKey)
api.POST("/payfee", payFee)
api.POST("/ticketstatus", ticketStatus)
}
return router
}
func homepage(c *gin.Context) {
c.HTML(http.StatusOK, "homepage.html", gin.H{
"Message": "Welcome to dcrvsp!",
})
}

View File

@ -27,8 +27,8 @@ golangci-lint run --disable-all --deadline=10m \
--enable=structcheck \
--enable=goimports \
--enable=misspell \
--enable=unparam \
--enable=asciicheck
# --enable=unparam \
# --enable=deadcode \
# --enable=errcheck \
# --enable=unused \

15
templates/homepage.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="/public/css/dcrvsp.css" />
<title>dcrwages</title>
</head>
<body>
<img src="/public/images/decred-logo.svg" />
<h1>{{ .Message }}</h1>
</body>
</html>