Set vote choices on voting wallets (#43)
This commit is contained in:
parent
bd518d7e24
commit
bb416e8bc9
@ -28,7 +28,7 @@
|
||||
- Request fee address (`POST /feeaddress`)
|
||||
- Pay fee (`POST /payFee`)
|
||||
- Ticket status (`GET /ticketstatus`)
|
||||
- Set voting preferences (`POST /setvotebits`)
|
||||
- Set voting preferences (`POST /setvotechoices`)
|
||||
- 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.
|
||||
|
||||
@ -3,6 +3,7 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
@ -20,7 +21,7 @@ func exampleTicket() Ticket {
|
||||
FeeAddress: "FeeAddress",
|
||||
SDiff: 1,
|
||||
BlockHeight: 2,
|
||||
VoteBits: 3,
|
||||
VoteChoices: map[string]string{"AgendaID": "Choice"},
|
||||
VotingKey: "VotingKey",
|
||||
VSPFee: 0.1,
|
||||
Expiration: 4,
|
||||
@ -38,7 +39,7 @@ func TestDatabase(t *testing.T) {
|
||||
"testGetTicketByHash": testGetTicketByHash,
|
||||
"testInsertFeeAddressVotingKey": testInsertFeeAddressVotingKey,
|
||||
"testUpdateExpireAndFee": testUpdateExpireAndFee,
|
||||
"testUpdateVoteBits": testUpdateVoteBits,
|
||||
"testUpdateVoteChoices": testUpdateVoteChoices,
|
||||
}
|
||||
|
||||
for testName, test := range tests {
|
||||
@ -105,7 +106,7 @@ func testGetTicketByHash(t *testing.T) {
|
||||
retrieved.FeeAddress != ticket.FeeAddress ||
|
||||
retrieved.SDiff != ticket.SDiff ||
|
||||
retrieved.BlockHeight != ticket.BlockHeight ||
|
||||
retrieved.VoteBits != ticket.VoteBits ||
|
||||
!reflect.DeepEqual(retrieved.VoteChoices, ticket.VoteChoices) ||
|
||||
retrieved.VotingKey != ticket.VotingKey ||
|
||||
retrieved.VSPFee != ticket.VSPFee ||
|
||||
retrieved.Expiration != ticket.Expiration {
|
||||
@ -129,10 +130,11 @@ func testInsertFeeAddressVotingKey(t *testing.T) {
|
||||
|
||||
// Update values.
|
||||
newVotingKey := ticket.VotingKey + "2"
|
||||
newVoteBits := ticket.VoteBits + 2
|
||||
err = db.InsertFeeAddressVotingKey(ticket.CommitmentAddress, newVotingKey, newVoteBits)
|
||||
newVoteChoices := ticket.VoteChoices
|
||||
newVoteChoices["AgendaID"] = "Different choice"
|
||||
err = db.InsertFeeAddressVotingKey(ticket.CommitmentAddress, newVotingKey, newVoteChoices)
|
||||
if err != nil {
|
||||
t.Fatalf("error updating votingkey and votebits: %v", err)
|
||||
t.Fatalf("error updating votingkey and votechoices: %v", err)
|
||||
}
|
||||
|
||||
// Retrieve ticket from database.
|
||||
@ -142,7 +144,7 @@ func testInsertFeeAddressVotingKey(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check ticket fields match expected.
|
||||
if newVoteBits != retrieved.VoteBits ||
|
||||
if !reflect.DeepEqual(newVoteChoices, retrieved.VoteChoices) ||
|
||||
newVotingKey != retrieved.VotingKey {
|
||||
t.Fatal("retrieved ticket value didnt match expected")
|
||||
}
|
||||
@ -176,7 +178,7 @@ func testUpdateExpireAndFee(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func testUpdateVoteBits(t *testing.T) {
|
||||
func testUpdateVoteChoices(t *testing.T) {
|
||||
// Insert a ticket into the database.
|
||||
ticket := exampleTicket()
|
||||
err := db.InsertFeeAddress(ticket)
|
||||
@ -184,11 +186,12 @@ func testUpdateVoteBits(t *testing.T) {
|
||||
t.Fatalf("error storing ticket in database: %v", err)
|
||||
}
|
||||
|
||||
// Update ticket with new votebits.
|
||||
newVoteBits := ticket.VoteBits + 1
|
||||
err = db.UpdateVoteBits(ticket.Hash, newVoteBits)
|
||||
// Update ticket with new votechoices.
|
||||
newVoteChoices := ticket.VoteChoices
|
||||
newVoteChoices["AgendaID"] = "Different choice"
|
||||
err = db.UpdateVoteChoices(ticket.Hash, newVoteChoices)
|
||||
if err != nil {
|
||||
t.Fatalf("error updating votebits: %v", err)
|
||||
t.Fatalf("error updating votechoices: %v", err)
|
||||
}
|
||||
|
||||
// Get updated ticket
|
||||
@ -198,7 +201,7 @@ func testUpdateVoteBits(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check ticket fields match expected.
|
||||
if retrieved.VoteBits != newVoteBits {
|
||||
if !reflect.DeepEqual(newVoteChoices, retrieved.VoteChoices) {
|
||||
t.Fatal("retrieved ticket value didnt match expected")
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,16 +9,16 @@ import (
|
||||
)
|
||||
|
||||
type Ticket struct {
|
||||
Hash string `json:"hash"`
|
||||
CommitmentSignature string `json:"commitmentsignature"`
|
||||
CommitmentAddress string `json:"commitmentaddress"`
|
||||
FeeAddress string `json:"feeaddress"`
|
||||
SDiff float64 `json:"sdiff"`
|
||||
BlockHeight int64 `json:"blockheight"`
|
||||
VoteBits uint16 `json:"votebits"`
|
||||
VotingKey string `json:"votingkey"`
|
||||
VSPFee float64 `json:"vspfee"`
|
||||
Expiration int64 `json:"expiration"`
|
||||
Hash string `json:"hash"`
|
||||
CommitmentSignature string `json:"commitmentsignature"`
|
||||
CommitmentAddress string `json:"commitmentaddress"`
|
||||
FeeAddress string `json:"feeaddress"`
|
||||
SDiff float64 `json:"sdiff"`
|
||||
BlockHeight int64 `json:"blockheight"`
|
||||
VoteChoices map[string]string `json:"votechoices"`
|
||||
VotingKey string `json:"votingkey"`
|
||||
VSPFee float64 `json:"vspfee"`
|
||||
Expiration int64 `json:"expiration"`
|
||||
}
|
||||
|
||||
var (
|
||||
@ -43,7 +43,7 @@ func (vdb *VspDatabase) InsertFeeAddress(ticket Ticket) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (vdb *VspDatabase) InsertFeeAddressVotingKey(address, votingKey string, voteBits uint16) error {
|
||||
func (vdb *VspDatabase) InsertFeeAddressVotingKey(address, votingKey string, voteChoices map[string]string) error {
|
||||
return vdb.db.Update(func(tx *bolt.Tx) error {
|
||||
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
||||
c := ticketBkt.Cursor()
|
||||
@ -57,7 +57,7 @@ func (vdb *VspDatabase) InsertFeeAddressVotingKey(address, votingKey string, vot
|
||||
|
||||
if ticket.CommitmentAddress == address {
|
||||
ticket.VotingKey = votingKey
|
||||
ticket.VoteBits = voteBits
|
||||
ticket.VoteChoices = voteChoices
|
||||
ticketBytes, err := json.Marshal(ticket)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -94,7 +94,7 @@ func (vdb *VspDatabase) GetTicketByHash(hash string) (Ticket, error) {
|
||||
return ticket, err
|
||||
}
|
||||
|
||||
func (vdb *VspDatabase) UpdateVoteBits(hash string, voteBits uint16) error {
|
||||
func (vdb *VspDatabase) UpdateVoteChoices(hash string, voteChoices map[string]string) error {
|
||||
return vdb.db.Update(func(tx *bolt.Tx) error {
|
||||
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
||||
key := []byte(hash)
|
||||
@ -109,7 +109,7 @@ func (vdb *VspDatabase) UpdateVoteBits(hash string, voteBits uint16) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
||||
}
|
||||
ticket.VoteBits = voteBits
|
||||
ticket.VoteChoices = voteChoices
|
||||
|
||||
ticketBytes, err = json.Marshal(ticket)
|
||||
if err != nil {
|
||||
|
||||
@ -38,7 +38,7 @@ func feeAddress(c *gin.Context) {
|
||||
// signature - sanity check signature is in base64 encoding
|
||||
signature := feeAddressRequest.Signature
|
||||
if _, err = base64.StdEncoding.DecodeString(signature); err != nil {
|
||||
log.Warnf("Invalid signature from %s", c.ClientIP())
|
||||
log.Warnf("Invalid signature from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
@ -145,7 +145,7 @@ func feeAddress(c *gin.Context) {
|
||||
message := fmt.Sprintf("vsp v3 getfeeaddress %s", msgTx.TxHash())
|
||||
err = dcrutil.VerifyMessage(addr.Address(), signature, message, cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid signature from %s", c.ClientIP())
|
||||
log.Warnf("Invalid signature from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
@ -179,10 +179,9 @@ func feeAddress(c *gin.Context) {
|
||||
FeeAddress: newAddress,
|
||||
SDiff: blockHeader.SBits,
|
||||
BlockHeight: int64(blockHeader.Height),
|
||||
VoteBits: dcrutil.BlockValid,
|
||||
VSPFee: cfg.VSPFee,
|
||||
Expiration: expire,
|
||||
// VotingKey: set during payfee
|
||||
// VotingKey and VoteChoices: set during payfee
|
||||
}
|
||||
|
||||
err = db.InsertFeeAddress(dbTicket)
|
||||
|
||||
@ -1,41 +1,43 @@
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/v3"
|
||||
"github.com/decred/dcrd/dcrutil/v3"
|
||||
)
|
||||
|
||||
// isValidVoteBits checks if voteBits are valid for the most recent agendas.
|
||||
func isValidVoteBits(params *chaincfg.Params, voteBits uint16) bool {
|
||||
|
||||
if !dcrutil.IsFlagSet16(voteBits, dcrutil.BlockValid) {
|
||||
return false
|
||||
}
|
||||
voteBits &= ^uint16(dcrutil.BlockValid)
|
||||
|
||||
// Get the most recent vote version.
|
||||
var voteVersion uint32
|
||||
func currentVoteVersion(params *chaincfg.Params) uint32 {
|
||||
var latestVersion uint32
|
||||
for version := range params.Deployments {
|
||||
if voteVersion < version {
|
||||
voteVersion = version
|
||||
if latestVersion < version {
|
||||
latestVersion = version
|
||||
}
|
||||
}
|
||||
|
||||
var availVoteBits uint16
|
||||
for _, vote := range params.Deployments[voteVersion] {
|
||||
availVoteBits |= vote.Vote.Mask
|
||||
|
||||
isValid := false
|
||||
maskedBits := voteBits & vote.Vote.Mask
|
||||
for _, c := range vote.Vote.Choices {
|
||||
if c.Bits == maskedBits {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isValid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return latestVersion
|
||||
}
|
||||
|
||||
// isValidVoteChoices returns an error if provided vote choices are not valid for
|
||||
// the most recent agendas.
|
||||
func isValidVoteChoices(params *chaincfg.Params, voteVersion uint32, voteChoices map[string]string) error {
|
||||
|
||||
agendaLoop:
|
||||
for agenda, choice := range voteChoices {
|
||||
// Does the agenda exist?
|
||||
for _, v := range params.Deployments[voteVersion] {
|
||||
if v.Vote.Id == agenda {
|
||||
// Agenda exists - does the vote choice exist?
|
||||
for _, c := range v.Vote.Choices {
|
||||
if c.Id == choice {
|
||||
// Valid agenda and choice combo! Check the next one...
|
||||
continue agendaLoop
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("choice %q not found for agenda %q", choice, agenda)
|
||||
}
|
||||
|
||||
}
|
||||
return fmt.Errorf("agenda %q not found for vote version %d", agenda, voteVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -4,31 +4,47 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/v3"
|
||||
"github.com/decred/dcrd/dcrutil/v3"
|
||||
)
|
||||
|
||||
func TestVoteBits(t *testing.T) {
|
||||
func TestIsValidVoteChoices(t *testing.T) {
|
||||
|
||||
// Mainnet vote version 4 contains 2 agendas - sdiffalgorithm and lnsupport.
|
||||
// Both agendas have vote choices yes/no/abstain.
|
||||
voteVersion := uint32(4)
|
||||
params := chaincfg.MainNetParams()
|
||||
|
||||
var tests = []struct {
|
||||
voteBits uint16
|
||||
isValid bool
|
||||
voteChoices map[string]string
|
||||
valid bool
|
||||
}{
|
||||
{0, false},
|
||||
{dcrutil.BlockValid, true},
|
||||
{dcrutil.BlockValid | 0x0002, true},
|
||||
{dcrutil.BlockValid | 0x0003, true},
|
||||
{dcrutil.BlockValid | 0x0004, true},
|
||||
{dcrutil.BlockValid | 0x0005, true},
|
||||
{dcrutil.BlockValid | 0x0006, false},
|
||||
{dcrutil.BlockValid | 0x0007, false},
|
||||
{dcrutil.BlockValid | 0x0008, true},
|
||||
// Empty vote choices are allowed.
|
||||
{map[string]string{}, true},
|
||||
|
||||
// Valid agenda, valid vote choice.
|
||||
{map[string]string{"lnsupport": "yes"}, true},
|
||||
{map[string]string{"sdiffalgorithm": "no", "lnsupport": "yes"}, true},
|
||||
|
||||
// Invalid agenda.
|
||||
{map[string]string{"": "yes"}, false},
|
||||
{map[string]string{"Fake agenda": "yes"}, false},
|
||||
|
||||
// Valid agenda, invalid vote choice.
|
||||
{map[string]string{"lnsupport": "1234"}, false},
|
||||
{map[string]string{"sdiffalgorithm": ""}, false},
|
||||
|
||||
// One valid choice, one invalid choice.
|
||||
{map[string]string{"sdiffalgorithm": "no", "lnsupport": "1234"}, false},
|
||||
{map[string]string{"sdiffalgorithm": "1234", "lnsupport": "no"}, false},
|
||||
|
||||
// One valid agenda, one invalid agenda.
|
||||
{map[string]string{"fake": "abstain", "lnsupport": "no"}, false},
|
||||
{map[string]string{"sdiffalgorithm": "abstain", "": "no"}, false},
|
||||
}
|
||||
|
||||
params := chaincfg.MainNetParams()
|
||||
for _, test := range tests {
|
||||
isValid := isValidVoteBits(params, test.voteBits)
|
||||
if isValid != test.isValid {
|
||||
t.Fatalf("isValidVoteBits failed for votebits '%d': want %v, got %v",
|
||||
test.voteBits, test.isValid, isValid)
|
||||
err := isValidVoteChoices(params, voteVersion, test.voteChoices)
|
||||
if (err == nil) != test.valid {
|
||||
t.Fatalf("isValidVoteChoices failed for votechoices '%v'.", test.voteChoices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,10 +33,11 @@ func payFee(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
voteBits := payFeeRequest.VoteBits
|
||||
if !isValidVoteBits(cfg.NetParams, voteBits) {
|
||||
log.Warnf("Invalid votebits from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid votebits", http.StatusBadRequest, c)
|
||||
voteChoices := payFeeRequest.VoteChoices
|
||||
err = isValidVoteChoices(cfg.NetParams, currentVoteVersion(cfg.NetParams), voteChoices)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid votechoices from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -161,6 +162,16 @@ findAddress:
|
||||
return
|
||||
}
|
||||
|
||||
// Update vote choices on voting wallets.
|
||||
for agenda, choice := range voteChoices {
|
||||
err = walletClient.Call(ctx, "setvotechoice", nil, agenda, choice, ticket.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("setvotechoice failed: %v", err)
|
||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
feeTxBuf := new(bytes.Buffer)
|
||||
feeTxBuf.Grow(feeTx.SerializeSize())
|
||||
err = feeTx.Serialize(feeTxBuf)
|
||||
@ -178,7 +189,7 @@ findAddress:
|
||||
return
|
||||
}
|
||||
|
||||
err = db.InsertFeeAddressVotingKey(voteAddr.Address(), votingWIF.String(), voteBits)
|
||||
err = db.InsertFeeAddressVotingKey(voteAddr.Address(), votingWIF.String(), voteChoices)
|
||||
if err != nil {
|
||||
log.Errorf("InsertFeeAddressVotingKey failed: %v", err)
|
||||
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/chainhash"
|
||||
"github.com/decred/dcrd/dcrutil/v3"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// setVoteBits is the handler for "POST /setvotebits"
|
||||
func setVoteBits(c *gin.Context) {
|
||||
var setVoteBitsRequest SetVoteBitsRequest
|
||||
if err := c.ShouldBindJSON(&setVoteBitsRequest); err != nil {
|
||||
log.Warnf("Bad setvotebits request from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// ticketHash
|
||||
ticketHashStr := setVoteBitsRequest.TicketHash
|
||||
txHash, err := chainhash.NewHashFromStr(ticketHashStr)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid ticket hash from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid ticket hash", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// signature - sanity check signature is in base64 encoding
|
||||
signature := setVoteBitsRequest.Signature
|
||||
if _, err = base64.StdEncoding.DecodeString(signature); err != nil {
|
||||
log.Warnf("Invalid signature from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// votebits
|
||||
voteBits := setVoteBitsRequest.VoteBits
|
||||
if !isValidVoteBits(cfg.NetParams, voteBits) {
|
||||
log.Warnf("Invalid votebits from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid votebits", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
ticket, err := db.GetTicketByHash(txHash.String())
|
||||
if err != nil {
|
||||
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// verify message
|
||||
message := fmt.Sprintf("vsp v3 setvotebits %d %s %d", setVoteBitsRequest.Timestamp, txHash, voteBits)
|
||||
err = dcrutil.VerifyMessage(ticket.CommitmentAddress, signature, message, cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to verify message from %s", c.ClientIP())
|
||||
sendErrorResponse("message did not pass verification", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.UpdateVoteBits(txHash.String(), voteBits)
|
||||
if err != nil {
|
||||
log.Errorf("UpdateVoteBits error: %v", err)
|
||||
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: DB - error if given timestamp is older than any previous requests
|
||||
|
||||
// TODO: DB - store setvotebits receipt in log
|
||||
|
||||
sendJSONResponse(setVoteBitsResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: setVoteBitsRequest,
|
||||
VoteBits: voteBits,
|
||||
}, c)
|
||||
}
|
||||
99
webapi/setvotechoices.go
Normal file
99
webapi/setvotechoices.go
Normal file
@ -0,0 +1,99 @@
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/chainhash"
|
||||
"github.com/decred/dcrd/dcrutil/v3"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// setVoteChoices is the handler for "POST /setvotechoices"
|
||||
func setVoteChoices(c *gin.Context) {
|
||||
var setVoteChoicesRequest SetVoteChoicesRequest
|
||||
if err := c.ShouldBindJSON(&setVoteChoicesRequest); err != nil {
|
||||
log.Warnf("Bad setvotechoices request from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// ticketHash
|
||||
ticketHashStr := setVoteChoicesRequest.TicketHash
|
||||
txHash, err := chainhash.NewHashFromStr(ticketHashStr)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid ticket hash from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid ticket hash", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// signature - sanity check signature is in base64 encoding
|
||||
signature := setVoteChoicesRequest.Signature
|
||||
if _, err = base64.StdEncoding.DecodeString(signature); err != nil {
|
||||
log.Warnf("Invalid signature from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
voteChoices := setVoteChoicesRequest.VoteChoices
|
||||
err = isValidVoteChoices(cfg.NetParams, currentVoteVersion(cfg.NetParams), voteChoices)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid votechoices from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
ticket, err := db.GetTicketByHash(txHash.String())
|
||||
if err != nil {
|
||||
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
||||
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
// verify message
|
||||
message := fmt.Sprintf("vsp v3 setvotechoices %d %s %v", setVoteChoicesRequest.Timestamp, txHash, voteChoices)
|
||||
err = dcrutil.VerifyMessage(ticket.CommitmentAddress, signature, message, cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to verify message from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse("message did not pass verification", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
walletClient, err := walletRPC()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to dial dcrwallet RPC: %v", err)
|
||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// Update vote choices on voting wallets.
|
||||
for agenda, choice := range voteChoices {
|
||||
err = walletClient.Call(ctx, "setvotechoice", nil, agenda, choice, ticket.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("setvotechoice failed: %v", err)
|
||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = db.UpdateVoteChoices(txHash.String(), voteChoices)
|
||||
if err != nil {
|
||||
log.Errorf("UpdateVoteChoices error: %v", err)
|
||||
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: DB - error if given timestamp is older than any previous requests
|
||||
|
||||
// TODO: DB - store setvotechoices receipt in log
|
||||
|
||||
sendJSONResponse(setVoteChoicesResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: setVoteChoicesRequest,
|
||||
VoteChoices: voteChoices,
|
||||
}, c)
|
||||
}
|
||||
@ -32,7 +32,7 @@ func ticketStatus(c *gin.Context) {
|
||||
// signature - sanity check signature is in base64 encoding
|
||||
signature := ticketStatusRequest.Signature
|
||||
if _, err = base64.StdEncoding.DecodeString(signature); err != nil {
|
||||
log.Warnf("Invalid signature from %s", c.ClientIP())
|
||||
log.Warnf("Invalid signature from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
@ -48,15 +48,15 @@ func ticketStatus(c *gin.Context) {
|
||||
message := fmt.Sprintf("vsp v3 ticketstatus %d %s", ticketStatusRequest.Timestamp, ticketHashStr)
|
||||
err = dcrutil.VerifyMessage(ticket.CommitmentAddress, signature, message, cfg.NetParams)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid signature from %s", c.ClientIP())
|
||||
log.Warnf("Invalid signature from %s: %v", c.ClientIP(), err)
|
||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
||||
return
|
||||
}
|
||||
|
||||
sendJSONResponse(ticketStatusResponse{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: ticketStatusRequest,
|
||||
Status: "active", // TODO - active, pending, expired (missed, revoked?)
|
||||
VoteBits: ticket.VoteBits,
|
||||
Timestamp: time.Now().Unix(),
|
||||
Request: ticketStatusRequest,
|
||||
Status: "active", // TODO - active, pending, expired (missed, revoked?)
|
||||
VoteChoices: ticket.VoteChoices,
|
||||
}, c)
|
||||
}
|
||||
|
||||
@ -25,11 +25,11 @@ type feeAddressResponse struct {
|
||||
}
|
||||
|
||||
type PayFeeRequest struct {
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
FeeTx string `json:"feetx" binding:"required"`
|
||||
VotingKey string `json:"votingkey" binding:"required"`
|
||||
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
FeeTx string `json:"feetx" binding:"required"`
|
||||
VotingKey string `json:"votingkey" binding:"required"`
|
||||
VoteChoices map[string]string `json:"votechoices" binding:"required"`
|
||||
}
|
||||
|
||||
type payFeeResponse struct {
|
||||
@ -38,17 +38,17 @@ type payFeeResponse struct {
|
||||
Request PayFeeRequest `json:"request" binding:"required"`
|
||||
}
|
||||
|
||||
type SetVoteBitsRequest struct {
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
Signature string `json:"commitmentsignature" binding:"required"`
|
||||
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||
type SetVoteChoicesRequest struct {
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
TicketHash string `json:"tickethash" binding:"required"`
|
||||
Signature string `json:"commitmentsignature" binding:"required"`
|
||||
VoteChoices map[string]string `json:"votechoices" binding:"required"`
|
||||
}
|
||||
|
||||
type setVoteBitsResponse struct {
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
Request SetVoteBitsRequest `json:"request" binding:"required"`
|
||||
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||
type setVoteChoicesResponse struct {
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
Request SetVoteChoicesRequest `json:"request" binding:"required"`
|
||||
VoteChoices map[string]string `json:"votechoices" binding:"required"`
|
||||
}
|
||||
|
||||
type TicketStatusRequest struct {
|
||||
@ -58,8 +58,8 @@ type TicketStatusRequest struct {
|
||||
}
|
||||
|
||||
type ticketStatusResponse struct {
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
Request TicketStatusRequest `json:"request" binding:"required"`
|
||||
Status string `json:"status" binding:"required"`
|
||||
VoteBits uint16 `json:"votebits" binding:"required"`
|
||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||
Request TicketStatusRequest `json:"request" binding:"required"`
|
||||
Status string `json:"status" binding:"required"`
|
||||
VoteChoices map[string]string `json:"votechoices" binding:"required"`
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ func router(debugMode bool) *gin.Engine {
|
||||
api.POST("/feeaddress", feeAddress)
|
||||
api.GET("/pubkey", pubKey)
|
||||
api.POST("/payfee", payFee)
|
||||
api.POST("/setvotebits", setVoteBits)
|
||||
api.POST("/setvotechoices", setVoteChoices)
|
||||
api.GET("/ticketstatus", ticketStatus)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user