Add missing db stubs and begin db implementation
This commit is contained in:
parent
1c72b8f1e5
commit
f919d4d8fc
29
README.md
29
README.md
@ -1,8 +1,25 @@
|
||||
# dcrvsp
|
||||
|
||||
- `main.go` initialises the program with hard-coded config.
|
||||
- `responses.go` contains the API response types copied from #625.
|
||||
- `router.go` contains the webserver init and config.
|
||||
- `methods.go` contains an implementation of payfee, copied from #625.
|
||||
- `database.go` contains stubbed database methods.
|
||||
- `wallet.go` contains stubbed dcrwallet calls.
|
||||
## Design decisions
|
||||
|
||||
- [gin-gonic](https://github.com/gin-gonic/gin) webserver
|
||||
- [bbolt](https://github.com/etcd-io/bbolt) database
|
||||
|
||||
## 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
|
||||
- A minimal, static, web front-end providing pool stats and basic connection instructions.
|
||||
|
||||
## Future features
|
||||
|
||||
- Write database backups to disk periodically.
|
||||
- Backup over http.
|
||||
- Status check API call as described in [dcrstakepool #628](https://github.com/decred/dcrstakepool/issues/628).
|
||||
- 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.
|
||||
|
||||
27
database.go
27
database.go
@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
type Database struct {
|
||||
}
|
||||
|
||||
type Fees struct {
|
||||
TicketHash string
|
||||
CommitmentSignature string
|
||||
FeeAddress string
|
||||
Address string
|
||||
SDiff int64
|
||||
BlockHeight int64
|
||||
VoteBits uint16
|
||||
VotingKey string
|
||||
}
|
||||
|
||||
func (db *Database) GetInactiveFeeAddresses() ([]string, error) {
|
||||
return []string{""}, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetFeesByFeeAddress(feeAddr string) (Fees, error) {
|
||||
return Fees{}, nil
|
||||
}
|
||||
|
||||
func (db *Database) InsertFeeAddressVotingKey(address, votingKey string, voteBits uint16) error {
|
||||
return nil
|
||||
}
|
||||
64
database/database.go
Normal file
64
database/database.go
Normal file
@ -0,0 +1,64 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// VspDatabase wraps an instance of bbolt DB and provides VSP specific
|
||||
// convenience functions.
|
||||
type VspDatabase struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
var (
|
||||
// vspBkt is the main parent bucket of the VSP. All values and other buckets
|
||||
// are nested within it.
|
||||
vspBkt = []byte("vspbkt")
|
||||
feesBkt = []byte("feesbkt")
|
||||
versionK = []byte("version")
|
||||
backupFile = "backup.kv"
|
||||
version = 1
|
||||
)
|
||||
|
||||
// New initialises and returns a database connection. If no database file is
|
||||
// found at the provided path, a new one will be created. Returns an open
|
||||
// database connection which should be closed after use.
|
||||
func New(dbFile string) (*VspDatabase, error) {
|
||||
db, err := bolt.Open(dbFile, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to open db file: %v", err)
|
||||
}
|
||||
|
||||
err = createBuckets(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &VspDatabase{db: db}, nil
|
||||
}
|
||||
|
||||
// createBuckets creates all storage buckets of the VSP.
|
||||
func createBuckets(db *bolt.DB) error {
|
||||
return db.Update(func(tx *bolt.Tx) error {
|
||||
pbkt := tx.Bucket(feesBkt)
|
||||
if pbkt == nil {
|
||||
pbkt, err := tx.CreateBucket(feesBkt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s bucket: %v", string(feesBkt), err)
|
||||
}
|
||||
|
||||
vbytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(vbytes, uint32(version))
|
||||
err = pbkt.Put(versionK, vbytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
32
database/fees.go
Normal file
32
database/fees.go
Normal file
@ -0,0 +1,32 @@
|
||||
package database
|
||||
|
||||
type Fees struct {
|
||||
TicketHash string
|
||||
CommitmentSignature string
|
||||
FeeAddress string
|
||||
Address string
|
||||
SDiff int64
|
||||
BlockHeight int64
|
||||
VoteBits uint16
|
||||
VotingKey string
|
||||
}
|
||||
|
||||
func (db *VspDatabase) InsertFeeAddressVotingKey(address, votingKey string, voteBits uint16) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *VspDatabase) InsertFeeAddress() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *VspDatabase) GetInactiveFeeAddresses() ([]string, error) {
|
||||
return []string{""}, nil
|
||||
}
|
||||
|
||||
func (db *VspDatabase) GetFeesByFeeAddress(feeAddr string) (Fees, error) {
|
||||
return Fees{}, nil
|
||||
}
|
||||
|
||||
func (db *VspDatabase) GetFeeAddressByTicketHash() (Fees, error) {
|
||||
return Fees{}, nil
|
||||
}
|
||||
9
go.mod
9
go.mod
@ -1,15 +1,16 @@
|
||||
module dcrvsp
|
||||
module github.com/jholdstock/dcrvsp
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
decred.org/dcrwallet v1.2.3-0.20200507155221-397dd551e317
|
||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.2
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200311044114-143c1884e4c8
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200511175520-d08cb3f72b3b
|
||||
github.com/decred/dcrd/dcrec v1.0.0
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200311044114-143c1884e4c8
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200511175520-d08cb3f72b3b
|
||||
github.com/decred/dcrd/rpcclient v1.1.0
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200421213827-b60c60ffe98b
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200511175520-d08cb3f72b3b
|
||||
github.com/decred/dcrd/wire v1.3.0
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
)
|
||||
|
||||
7
go.sum
7
go.sum
@ -44,6 +44,8 @@ github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8=
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200311044114-143c1884e4c8 h1:UweAZ771bCDNxumIVk7b0y2EJ8JUqeWhXbPpdXKsClc=
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8=
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200511175520-d08cb3f72b3b h1:L6qM+5ISaaYSnNMAFMouu/FGcIN0tP42rJUdtAJqKgY=
|
||||
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200511175520-d08cb3f72b3b/go.mod h1:OHbKBa6UZZOXCU1Y8f9Ta3O+GShto7nB1O0nuEutKq4=
|
||||
github.com/decred/dcrd/connmgr/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:mvIMJsrOEngogmVrq+tdbPIZchHVgGnVBZeNwj1cW6E=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
@ -82,6 +84,8 @@ github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:4
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:jFxEd2LWDLvrWlrIiyx9ZGTQjvoFHZ0OVfBdyIX7jSw=
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200311044114-143c1884e4c8 h1:l7N4vMUp1k8Ugs+ol3WLRKB3Xij0MOf5hC2bY/W6bS4=
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:/CDBC1SOXKrmihavgXviaTr6eVZSAWKQqEbRmacDxgg=
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200511175520-d08cb3f72b3b h1:94fhkXS9ObD1P0SxOxk1TAFVhdFBpVeQeUWl0nYb6X8=
|
||||
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200511175520-d08cb3f72b3b/go.mod h1:85NtF/fmqL2UDf0/gLhTHG/m/0HQHwG+erQKkwWW27A=
|
||||
github.com/decred/dcrd/gcs v1.0.1 h1:MpJXLskT41+JDaD3RLdlSlF2vlu1sxPpZgiRI7FVTWw=
|
||||
github.com/decred/dcrd/gcs v1.0.1/go.mod h1:YwutGzusSdJM79CJtxCo9t7WRCvnkLtWSD19TPo1i9g=
|
||||
github.com/decred/dcrd/gcs/v2 v2.0.0/go.mod h1:3XjKcrtvB+r2ezhIsyNCLk6dRnXRJVyYmsd1P3SkU3o=
|
||||
@ -97,6 +101,8 @@ github.com/decred/dcrd/txscript/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:KsDS7McU1yFaCYR9LCIwk6YnE15YN3wJUDxhKdFqlsc=
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200421213827-b60c60ffe98b h1:HqIl8oViATdNtoYDFbHzGhHe4q9irezWXF16fhKy8/4=
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200421213827-b60c60ffe98b/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0=
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200511175520-d08cb3f72b3b h1:PbEqUN+q0hg/TJdi2IQ0Y/5Qc8GNFrnioXeBXm060n8=
|
||||
github.com/decred/dcrd/txscript/v3 v3.0.0-20200511175520-d08cb3f72b3b/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0=
|
||||
github.com/decred/dcrd/wire v1.1.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM=
|
||||
github.com/decred/dcrd/wire v1.2.0/go.mod h1:/JKOsLInOJu6InN+/zH5AyCq3YDIOW/EqcffvU8fJHM=
|
||||
github.com/decred/dcrd/wire v1.3.0 h1:X76I2/a8esUmxXmFpJpAvXEi014IA4twgwcOBeIS8lE=
|
||||
@ -153,6 +159,7 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
||||
47
main.go
47
main.go
@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
@ -10,39 +12,46 @@ import (
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/v3"
|
||||
"github.com/decred/dcrd/rpcclient"
|
||||
"github.com/jholdstock/dcrvsp/database"
|
||||
)
|
||||
|
||||
const listen = ":3000"
|
||||
|
||||
// Config vars
|
||||
var (
|
||||
type Config struct {
|
||||
signKey ed25519.PrivateKey
|
||||
pubKey ed25519.PublicKey
|
||||
poolFees float64
|
||||
netParams *chaincfg.Params
|
||||
)
|
||||
dbFile string
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
|
||||
// Database with stubbed methods
|
||||
var db Database
|
||||
var db *database.VspDatabase
|
||||
|
||||
// RPC clients
|
||||
var nodeConnection *rpcclient.Client
|
||||
var walletConnection *WalletClient
|
||||
|
||||
func initConfig() {
|
||||
seedPath := filepath.Join("dcrvsp", "sign.seed")
|
||||
func initConfig() (*Config, error) {
|
||||
homePath := "~/.dcrvsp"
|
||||
|
||||
seedPath := filepath.Join(homePath, "sign.seed")
|
||||
seed, err := ioutil.ReadFile(seedPath)
|
||||
var signKey ed25519.PrivateKey
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Fatal("seedPath does not exist")
|
||||
return nil, errors.New("seedPath does not exist")
|
||||
}
|
||||
|
||||
_, signKey, err = ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to generate signing key: %v", err)
|
||||
return nil, fmt.Errorf("failed to generate signing key: %v", err)
|
||||
}
|
||||
err = ioutil.WriteFile(seedPath, signKey.Seed(), 0400)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to save signing key: %v", err)
|
||||
return nil, fmt.Errorf("failed to save signing key: %v", err)
|
||||
}
|
||||
} else {
|
||||
signKey = ed25519.NewKeyFromSeed(seed)
|
||||
@ -50,15 +59,29 @@ func initConfig() {
|
||||
|
||||
pubKey, ok := signKey.Public().(ed25519.PublicKey)
|
||||
if !ok {
|
||||
log.Fatalf("failed to cast signing key: %T", pubKey)
|
||||
return nil, fmt.Errorf("failed to cast signing key: %T", pubKey)
|
||||
}
|
||||
|
||||
netParams = chaincfg.TestNet3Params()
|
||||
return &Config{
|
||||
netParams: chaincfg.TestNet3Params(),
|
||||
dbFile: filepath.Join(homePath, "database.db"),
|
||||
pubKey: pubKey,
|
||||
poolFees: 0.1,
|
||||
signKey: signKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
initConfig()
|
||||
cfg, err := initConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("config error: %v", err)
|
||||
}
|
||||
|
||||
db, err = database.New(cfg.dbFile)
|
||||
if err != nil {
|
||||
log.Fatalf("database error: %v", err)
|
||||
}
|
||||
|
||||
// Start HTTP server
|
||||
log.Printf("Listening on %s", listen)
|
||||
|
||||
12
methods.go
12
methods.go
@ -28,7 +28,7 @@ func sendJSONResponse(resp interface{}, code int, c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
sig := ed25519.Sign(signKey, dec)
|
||||
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.Write(dec)
|
||||
@ -41,7 +41,7 @@ func payFee(c *gin.Context) {
|
||||
// voteBits - voting preferences in little endian
|
||||
|
||||
votingKey := c.Param("votingKey")
|
||||
votingWIF, err := dcrutil.DecodeWIF(votingKey, netParams.PrivateKeyID)
|
||||
votingWIF, err := dcrutil.DecodeWIF(votingKey, cfg.netParams.PrivateKeyID)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
@ -84,7 +84,7 @@ func payFee(c *gin.Context) {
|
||||
findAddress:
|
||||
for _, txOut := range feeTx.TxOut {
|
||||
_, addresses, _, err := txscript.ExtractPkScriptAddrs(scriptVersion,
|
||||
txOut.PkScript, netParams)
|
||||
txOut.PkScript, cfg.netParams)
|
||||
if err != nil {
|
||||
fmt.Printf("Extract: %v", err)
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
@ -113,13 +113,13 @@ findAddress:
|
||||
c.AbortWithError(http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
voteAddr, err := dcrutil.DecodeAddress(feeEntry.Address, netParams)
|
||||
voteAddr, err := dcrutil.DecodeAddress(feeEntry.Address, cfg.netParams)
|
||||
if err != nil {
|
||||
fmt.Errorf("PayFee: DecodeAddress: %v", err)
|
||||
c.AbortWithError(http.StatusInternalServerError, errors.New("database error"))
|
||||
return
|
||||
}
|
||||
_, err = dcrutil.NewAddressPubKeyHash(dcrutil.Hash160(votingWIF.PubKey()), netParams,
|
||||
_, err = dcrutil.NewAddressPubKeyHash(dcrutil.Hash160(votingWIF.PubKey()), cfg.netParams,
|
||||
dcrec.STEcdsaSecp256k1)
|
||||
if err != nil {
|
||||
fmt.Errorf("PayFee: NewAddressPubKeyHash: %v", err)
|
||||
@ -137,7 +137,7 @@ findAddress:
|
||||
return
|
||||
}
|
||||
|
||||
minFee := txrules.StakePoolTicketFee(sDiff, relayFee, int32(feeEntry.BlockHeight), poolFees, netParams)
|
||||
minFee := txrules.StakePoolTicketFee(sDiff, relayFee, int32(feeEntry.BlockHeight), cfg.poolFees, cfg.netParams)
|
||||
if feeAmount < minFee {
|
||||
fmt.Printf("too cheap: %v %v", feeAmount, minFee)
|
||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("dont get cheap on me, dodgson (sent:%v required:%v)", feeAmount, minFee))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user