Rework client/server authentication. (#58)
* Rework client/server authentication. - Remove Signature from all requests, and instead expect a signature in HTTP header "VSP-Client-Signature". - Remove CommitmentSignatures from the database. - Use a bool flag to indicate when a ticket is missing from the database rather than an error. This commit introduces a lot of duplication into each of the authenticated HTTP handlers. This should be removed in future work which moves the authentication to a dedicated middleware. * Introduce auth and rpc middleware. This removed the duplication added in the previous commit, and also removes the duplication of RPC client error handling.
This commit is contained in:
parent
1ff55f4b30
commit
ac488464c0
@ -58,8 +58,11 @@ ticket details + fee to a VSP, and the VSP will take the fee and vote in return.
|
|||||||
|
|
||||||
- When dcrvsp is started for the first time, it generates a ed25519 keypair and
|
- When dcrvsp is started for the first time, it generates a ed25519 keypair and
|
||||||
stores it in the database. This key is used to sign all API responses, and the
|
stores it in the database. This key is used to sign all API responses, and the
|
||||||
signature is included in the response header `VSP-Signature`. Error responses
|
signature is included in the response header `VSP-Server-Signature`. Error responses
|
||||||
are not signed.
|
are not signed.
|
||||||
|
- Every client request which references a ticket should include a HTTP header
|
||||||
|
`VSP-Client-Signature`. The value of this header must be a signature of the
|
||||||
|
request body, signed with the commitment address of the referenced ticket.
|
||||||
- An xpub key is provided to dcrvsp via config. The first time dcrvsp starts, it
|
- An xpub key is provided to dcrvsp via config. The first time dcrvsp starts, it
|
||||||
imports this xpub to create a new wallet account. This account is used to
|
imports this xpub to create a new wallet account. This account is used to
|
||||||
derive addresses for fee payments.
|
derive addresses for fee payments.
|
||||||
|
|||||||
@ -17,7 +17,6 @@ func exampleTicket() Ticket {
|
|||||||
return Ticket{
|
return Ticket{
|
||||||
Hash: "Hash",
|
Hash: "Hash",
|
||||||
CommitmentAddress: "Address",
|
CommitmentAddress: "Address",
|
||||||
CommitmentSignature: "CommitmentSignature",
|
|
||||||
FeeAddress: "FeeAddress",
|
FeeAddress: "FeeAddress",
|
||||||
SDiff: 1,
|
SDiff: 1,
|
||||||
BlockHeight: 2,
|
BlockHeight: 2,
|
||||||
@ -95,15 +94,17 @@ func testGetTicketByHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve ticket from database.
|
// Retrieve ticket from database.
|
||||||
retrieved, err := db.GetTicketByHash(ticket.Hash)
|
retrieved, found, err := db.GetTicketByHash(ticket.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
|
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
|
||||||
}
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("expected found==true")
|
||||||
|
}
|
||||||
|
|
||||||
// Check ticket fields match expected.
|
// Check ticket fields match expected.
|
||||||
if retrieved.Hash != ticket.Hash ||
|
if retrieved.Hash != ticket.Hash ||
|
||||||
retrieved.CommitmentAddress != ticket.CommitmentAddress ||
|
retrieved.CommitmentAddress != ticket.CommitmentAddress ||
|
||||||
retrieved.CommitmentSignature != ticket.CommitmentSignature ||
|
|
||||||
retrieved.FeeAddress != ticket.FeeAddress ||
|
retrieved.FeeAddress != ticket.FeeAddress ||
|
||||||
retrieved.SDiff != ticket.SDiff ||
|
retrieved.SDiff != ticket.SDiff ||
|
||||||
retrieved.BlockHeight != ticket.BlockHeight ||
|
retrieved.BlockHeight != ticket.BlockHeight ||
|
||||||
@ -115,10 +116,13 @@ func testGetTicketByHash(t *testing.T) {
|
|||||||
t.Fatal("retrieved ticket value didnt match expected")
|
t.Fatal("retrieved ticket value didnt match expected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error if non-existent ticket requested.
|
// Check found==false when requesting a non-existent ticket.
|
||||||
_, err = db.GetTicketByHash("Not a real ticket hash")
|
_, found, err = db.GetTicketByHash("Not a real ticket hash")
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Fatal("expected an error while retrieving a non-existent ticket")
|
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
t.Fatal("expected found==false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +145,7 @@ func testSetTicketVotingKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve ticket from database.
|
// Retrieve ticket from database.
|
||||||
retrieved, err := db.GetTicketByHash(ticket.Hash)
|
retrieved, _, err := db.GetTicketByHash(ticket.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
|
t.Fatalf("error retrieving ticket by ticket hash: %v", err)
|
||||||
}
|
}
|
||||||
@ -171,7 +175,7 @@ func testUpdateExpireAndFee(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get updated ticket
|
// Get updated ticket
|
||||||
retrieved, err := db.GetTicketByHash(ticket.Hash)
|
retrieved, _, err := db.GetTicketByHash(ticket.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error retrieving updated ticket: %v", err)
|
t.Fatalf("error retrieving updated ticket: %v", err)
|
||||||
}
|
}
|
||||||
@ -199,7 +203,7 @@ func testUpdateVoteChoices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get updated ticket
|
// Get updated ticket
|
||||||
retrieved, err := db.GetTicketByHash(ticket.Hash)
|
retrieved, _, err := db.GetTicketByHash(ticket.Hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error retrieving updated ticket: %v", err)
|
t.Fatalf("error retrieving updated ticket: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
type Ticket struct {
|
type Ticket struct {
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
CommitmentSignature string `json:"commitmentsignature"`
|
|
||||||
CommitmentAddress string `json:"commitmentaddress"`
|
CommitmentAddress string `json:"commitmentaddress"`
|
||||||
FeeAddress string `json:"feeaddress"`
|
FeeAddress string `json:"feeaddress"`
|
||||||
SDiff float64 `json:"sdiff"`
|
SDiff float64 `json:"sdiff"`
|
||||||
@ -82,14 +81,15 @@ func (vdb *VspDatabase) SetTicketVotingKey(ticketHash, votingKey string, voteCho
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, error) {
|
func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, bool, error) {
|
||||||
var ticket Ticket
|
var ticket Ticket
|
||||||
|
var found bool
|
||||||
err := vdb.db.View(func(tx *bolt.Tx) error {
|
err := vdb.db.View(func(tx *bolt.Tx) error {
|
||||||
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
|
||||||
|
|
||||||
ticketBytes := ticketBkt.Get([]byte(ticketHash))
|
ticketBytes := ticketBkt.Get([]byte(ticketHash))
|
||||||
if ticketBytes == nil {
|
if ticketBytes == nil {
|
||||||
return ErrNoTicketFound
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.Unmarshal(ticketBytes, &ticket)
|
err := json.Unmarshal(ticketBytes, &ticket)
|
||||||
@ -97,10 +97,12 @@ func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, error) {
|
|||||||
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
return fmt.Errorf("could not unmarshal ticket: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
found = true
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return ticket, err
|
return ticket, found, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vdb *VspDatabase) UpdateVoteChoices(ticketHash string, voteChoices map[string]string) error {
|
func (vdb *VspDatabase) UpdateVoteChoices(ticketHash string, voteChoices map[string]string) error {
|
||||||
|
|||||||
@ -2,11 +2,15 @@ package rpc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
wallettypes "decred.org/dcrwallet/rpc/jsonrpc/types"
|
wallettypes "decred.org/dcrwallet/rpc/jsonrpc/types"
|
||||||
|
"github.com/decred/dcrd/blockchain/stake/v3"
|
||||||
|
"github.com/decred/dcrd/chaincfg/v3"
|
||||||
"github.com/decred/dcrd/dcrutil/v3"
|
"github.com/decred/dcrd/dcrutil/v3"
|
||||||
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
|
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
|
||||||
|
"github.com/decred/dcrd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -129,3 +133,25 @@ func (c *FeeWalletRPC) GetWalletFee() (dcrutil.Amount, error) {
|
|||||||
|
|
||||||
return amount, nil
|
return amount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *FeeWalletRPC) GetTicketCommitmentAddress(ticketHash string, netParams *chaincfg.Params) (string, error) {
|
||||||
|
resp, err := c.GetRawTransaction(ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgHex, err := hex.DecodeString(resp.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
msgTx := wire.NewMsgTx()
|
||||||
|
if err = msgTx.FromBytes(msgHex); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, netParams)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr.Address(), nil
|
||||||
|
}
|
||||||
|
|||||||
@ -1,66 +1,46 @@
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/decred/dcrd/blockchain/stake/v3"
|
"github.com/decred/dcrd/blockchain/stake/v3"
|
||||||
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
||||||
"github.com/decred/dcrd/dcrutil/v3"
|
|
||||||
"github.com/decred/dcrd/wire"
|
"github.com/decred/dcrd/wire"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
"github.com/jholdstock/dcrvsp/database"
|
"github.com/jholdstock/dcrvsp/database"
|
||||||
"github.com/jholdstock/dcrvsp/rpc"
|
"github.com/jholdstock/dcrvsp/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// feeAddress is the handler for "POST /feeaddress".
|
// feeAddress is the handler for "POST /feeaddress".
|
||||||
func feeAddress(c *gin.Context) {
|
func feeAddress(c *gin.Context) {
|
||||||
|
|
||||||
|
// Get values which have been added to context by middleware.
|
||||||
|
rawRequest := c.MustGet("RawRequest").([]byte)
|
||||||
|
ticket := c.MustGet("Ticket").(database.Ticket)
|
||||||
|
knownTicket := c.MustGet("KnownTicket").(bool)
|
||||||
|
commitmentAddress := c.MustGet("CommitmentAddress").(string)
|
||||||
|
fWalletClient := c.MustGet("FeeWalletClient").(*rpc.FeeWalletRPC)
|
||||||
|
|
||||||
var feeAddressRequest FeeAddressRequest
|
var feeAddressRequest FeeAddressRequest
|
||||||
if err := c.ShouldBindJSON(&feeAddressRequest); err != nil {
|
if err := binding.JSON.BindBody(rawRequest, &feeAddressRequest); err != nil {
|
||||||
log.Warnf("Bad feeaddress request from %s: %v", c.ClientIP(), err)
|
log.Warnf("Bad feeaddress request from %s: %v", c.ClientIP(), err)
|
||||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate TicketHash.
|
// VSP already knows this ticket and has already issued it a fee address.
|
||||||
ticketHashStr := feeAddressRequest.TicketHash
|
if knownTicket {
|
||||||
txHash, err := chainhash.NewHashFromStr(ticketHashStr)
|
// If the expiry period has passed we need to issue a new fee.
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Invalid ticket hash from %s", c.ClientIP())
|
|
||||||
sendErrorResponse("invalid ticket hash", http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate 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: %v", c.ClientIP(), err)
|
|
||||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing response
|
|
||||||
ticket, err := db.GetTicketByHash(ticketHashStr)
|
|
||||||
if err != nil && !errors.Is(err, database.ErrNoTicketFound) {
|
|
||||||
log.Errorf("GetTicketByHash error: %v", err)
|
|
||||||
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
// Ticket already exists
|
|
||||||
if signature == ticket.CommitmentSignature {
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
expire := ticket.FeeExpiration
|
expire := ticket.FeeExpiration
|
||||||
VSPFee := ticket.VSPFee
|
VSPFee := ticket.VSPFee
|
||||||
if ticket.FeeExpired() {
|
if now.After(time.Unix(ticket.FeeExpiration, 0)) {
|
||||||
expire = now.Add(cfg.FeeAddressExpiration).Unix()
|
expire = now.Add(cfg.FeeAddressExpiration).Unix()
|
||||||
VSPFee = cfg.VSPFee
|
VSPFee = cfg.VSPFee
|
||||||
|
|
||||||
err = db.UpdateExpireAndFee(ticketHashStr, expire, VSPFee)
|
err := db.UpdateExpireAndFee(ticket.Hash, expire, VSPFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("UpdateExpireAndFee error: %v", err)
|
log.Errorf("UpdateExpireAndFee error: %v", err)
|
||||||
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
@ -77,28 +57,16 @@ func feeAddress(c *gin.Context) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Warnf("Invalid signature from %s", c.ClientIP())
|
|
||||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fWalletConn, err := feeWalletConnect()
|
// Beyond this point we are processing a new ticket which the VSP has not
|
||||||
if err != nil {
|
// seen before.
|
||||||
log.Errorf("Fee wallet connection error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
fWalletClient, err := rpc.FeeWalletClient(ctx, fWalletConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Fee wallet client error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := fWalletClient.GetRawTransaction(txHash.String())
|
ticketHash := feeAddressRequest.TicketHash
|
||||||
|
|
||||||
|
// Ensure ticket exists and is mined.
|
||||||
|
resp, err := fWalletClient.GetRawTransaction(ticketHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Could not retrieve tx %s for %s: %v", txHash, c.ClientIP(), err)
|
log.Warnf("Could not retrieve tx %s for %s: %v", ticketHash, c.ClientIP(), err)
|
||||||
sendErrorResponse("unknown transaction", http.StatusBadRequest, c)
|
sendErrorResponse("unknown transaction", http.StatusBadRequest, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -137,23 +105,6 @@ func feeAddress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get commitment address
|
|
||||||
addr, err := stake.AddrFromSStxPkScrCommitment(msgTx.TxOut[1].PkScript, cfg.NetParams)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to get commitment address: %v", err)
|
|
||||||
sendErrorResponse("failed to get commitment address", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify message
|
|
||||||
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: %v", c.ClientIP(), err)
|
|
||||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get blockheight and sdiff which is required by
|
// get blockheight and sdiff which is required by
|
||||||
// txrules.StakePoolTicketFee, and store them in the database
|
// txrules.StakePoolTicketFee, and store them in the database
|
||||||
// for processing by payfee
|
// for processing by payfee
|
||||||
@ -176,9 +127,8 @@ func feeAddress(c *gin.Context) {
|
|||||||
expire := now.Add(cfg.FeeAddressExpiration).Unix()
|
expire := now.Add(cfg.FeeAddressExpiration).Unix()
|
||||||
|
|
||||||
dbTicket := database.Ticket{
|
dbTicket := database.Ticket{
|
||||||
Hash: txHash.String(),
|
Hash: ticketHash,
|
||||||
CommitmentSignature: signature,
|
CommitmentAddress: commitmentAddress,
|
||||||
CommitmentAddress: addr.Address(),
|
|
||||||
FeeAddress: newAddress,
|
FeeAddress: newAddress,
|
||||||
SDiff: blockHeader.SBits,
|
SDiff: blockHeader.SBits,
|
||||||
BlockHeight: int64(blockHeader.Height),
|
BlockHeight: int64(blockHeader.Height),
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/decred/dcrd/chaincfg/v3"
|
"github.com/decred/dcrd/chaincfg/v3"
|
||||||
|
"github.com/decred/dcrd/dcrutil/v3"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func currentVoteVersion(params *chaincfg.Params) uint32 {
|
func currentVoteVersion(params *chaincfg.Params) uint32 {
|
||||||
@ -41,3 +44,17 @@ agendaLoop:
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSignature(reqBytes []byte, commitmentAddress string, c *gin.Context) error {
|
||||||
|
// Ensure a signature is provided.
|
||||||
|
signature := c.GetHeader("VSP-Client-Signature")
|
||||||
|
if signature == "" {
|
||||||
|
return errors.New("no VSP-Client-Signature header")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dcrutil.VerifyMessage(commitmentAddress, signature, string(reqBytes), cfg.NetParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
124
webapi/middleware.go
Normal file
124
webapi/middleware.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package webapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/jholdstock/dcrvsp/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ticketHashRequest struct {
|
||||||
|
TicketHash string `json:"tickethash" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// withFeeWalletClient middleware adds a fee wallet client to the request
|
||||||
|
// context for downstream handlers to make use of.
|
||||||
|
func withFeeWalletClient() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
fWalletConn, err := feeWalletConnect()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Fee wallet connection error: %v", err)
|
||||||
|
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fWalletClient, err := rpc.FeeWalletClient(c, fWalletConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Fee wallet client error: %v", err)
|
||||||
|
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("FeeWalletClient", fWalletClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// withVotingWalletClient middleware adds a voting wallet client to the request
|
||||||
|
// context for downstream handlers to make use of.
|
||||||
|
func withVotingWalletClient() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
vWalletConn, err := votingWalletConnect()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Voting wallet connection error: %v", err)
|
||||||
|
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vWalletClient, err := rpc.VotingWalletClient(c, vWalletConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Voting wallet client error: %v", err)
|
||||||
|
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("VotingWalletClient", vWalletClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// vspAuth middleware reads the request body and extracts the ticket hash. The
|
||||||
|
// commitment address for the ticket is retrieved from the database if it is
|
||||||
|
// known, or it is retrieved from the chain if not.
|
||||||
|
// The middleware errors out if the VSP-Client-Signature header of the request
|
||||||
|
// does not contain the request body signed with the commitment address.
|
||||||
|
// Ticket information is added to the request context for downstream handlers to
|
||||||
|
// use.
|
||||||
|
func vspAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Read request bytes.
|
||||||
|
reqBytes, err := c.GetRawData()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Error reading request from %s: %v", c.ClientIP(), err)
|
||||||
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add raw request to context for downstream handlers to use.
|
||||||
|
c.Set("RawRequest", reqBytes)
|
||||||
|
|
||||||
|
// Parse request and ensure there is a ticket hash included.
|
||||||
|
var request ticketHashRequest
|
||||||
|
if err := binding.JSON.BindBody(reqBytes, &request); err != nil {
|
||||||
|
log.Warnf("Bad request from %s: %v", c.ClientIP(), err)
|
||||||
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash := request.TicketHash
|
||||||
|
|
||||||
|
// Check if this ticket already appears in the database.
|
||||||
|
ticket, ticketFound, err := db.GetTicketByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("GetTicketByHash error: %v", err)
|
||||||
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the ticket was found in the database we already know its commitment
|
||||||
|
// address. Otherwise we need to get it from the chain.
|
||||||
|
var commitmentAddress string
|
||||||
|
if ticketFound {
|
||||||
|
commitmentAddress = ticket.CommitmentAddress
|
||||||
|
} else {
|
||||||
|
fWalletClient := c.MustGet("FeeWalletClient").(*rpc.FeeWalletRPC)
|
||||||
|
commitmentAddress, err = fWalletClient.GetTicketCommitmentAddress(hash, cfg.NetParams)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("GetTicketCommitmentAddress error: %v", err)
|
||||||
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate request signature to ensure ticket ownership.
|
||||||
|
err = validateSignature(reqBytes, commitmentAddress, c)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Bad signature from %s: %v", c.ClientIP(), err)
|
||||||
|
sendErrorResponse("bad signature", http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ticket information to context so downstream handlers don't need
|
||||||
|
// to access the db for it.
|
||||||
|
c.Set("Ticket", ticket)
|
||||||
|
c.Set("KnownTicket", ticketFound)
|
||||||
|
c.Set("CommitmentAddress", commitmentAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,32 +12,43 @@ import (
|
|||||||
"github.com/decred/dcrd/txscript/v3"
|
"github.com/decred/dcrd/txscript/v3"
|
||||||
"github.com/decred/dcrd/wire"
|
"github.com/decred/dcrd/wire"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/jholdstock/dcrvsp/database"
|
||||||
"github.com/jholdstock/dcrvsp/rpc"
|
"github.com/jholdstock/dcrvsp/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// payFee is the handler for "POST /payfee".
|
// payFee is the handler for "POST /payfee".
|
||||||
func payFee(c *gin.Context) {
|
func payFee(c *gin.Context) {
|
||||||
var payFeeRequest PayFeeRequest
|
|
||||||
if err := c.ShouldBindJSON(&payFeeRequest); err != nil {
|
|
||||||
log.Warnf("Bad payfee request from %s: %v", c.ClientIP(), err)
|
|
||||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ticket, err := db.GetTicketByHash(payFeeRequest.TicketHash)
|
// Get values which have been added to context by middleware.
|
||||||
if err != nil {
|
rawRequest := c.MustGet("RawRequest").([]byte)
|
||||||
|
ticket := c.MustGet("Ticket").(database.Ticket)
|
||||||
|
knownTicket := c.MustGet("KnownTicket").(bool)
|
||||||
|
fWalletClient := c.MustGet("FeeWalletClient").(*rpc.FeeWalletRPC)
|
||||||
|
vWalletClient := c.MustGet("VotingWalletClient").(*rpc.VotingWalletRPC)
|
||||||
|
|
||||||
|
if !knownTicket {
|
||||||
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
||||||
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fee transaction has already been broadcast for this ticket.
|
var payFeeRequest PayFeeRequest
|
||||||
|
if err := binding.JSON.BindBody(rawRequest, &payFeeRequest); err != nil {
|
||||||
|
log.Warnf("Bad payfee request from %s: %v", c.ClientIP(), err)
|
||||||
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond early if fee transaction has already been broadcast for this
|
||||||
|
// ticket.
|
||||||
if ticket.FeeTxHash != "" {
|
if ticket.FeeTxHash != "" {
|
||||||
sendJSONResponse(payFeeResponse{
|
sendJSONResponse(payFeeResponse{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
TxHash: ticket.FeeTxHash,
|
TxHash: ticket.FeeTxHash,
|
||||||
Request: payFeeRequest,
|
Request: payFeeRequest,
|
||||||
}, c)
|
}, c)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate VotingKey.
|
// Validate VotingKey.
|
||||||
@ -129,21 +140,6 @@ findAddress:
|
|||||||
|
|
||||||
sDiff := dcrutil.Amount(ticket.SDiff)
|
sDiff := dcrutil.Amount(ticket.SDiff)
|
||||||
|
|
||||||
fWalletConn, err := feeWalletConnect()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Fee wallet connection error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
|
|
||||||
fWalletClient, err := rpc.FeeWalletClient(ctx, fWalletConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Fee wallet client error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
relayFee, err := fWalletClient.GetWalletFee()
|
relayFee, err := fWalletClient.GetWalletFee()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("GetWalletFee failed: %v", err)
|
log.Errorf("GetWalletFee failed: %v", err)
|
||||||
@ -187,19 +183,6 @@ findAddress:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vWalletConn, err := votingWalletConnect()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Voting wallet connection error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vWalletClient, err := rpc.VotingWalletClient(ctx, vWalletConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Voting wallet client error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = vWalletClient.AddTransaction(rawTicket.BlockHash, rawTicket.Hex)
|
err = vWalletClient.AddTransaction(rawTicket.BlockHash, rawTicket.Hex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("AddTransaction failed: %v", err)
|
log.Errorf("AddTransaction failed: %v", err)
|
||||||
|
|||||||
@ -1,85 +1,48 @@
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
||||||
"github.com/decred/dcrd/dcrutil/v3"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/jholdstock/dcrvsp/database"
|
||||||
"github.com/jholdstock/dcrvsp/rpc"
|
"github.com/jholdstock/dcrvsp/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setVoteChoices is the handler for "POST /setvotechoices".
|
// setVoteChoices is the handler for "POST /setvotechoices".
|
||||||
func setVoteChoices(c *gin.Context) {
|
func setVoteChoices(c *gin.Context) {
|
||||||
|
|
||||||
|
// Get values which have been added to context by middleware.
|
||||||
|
rawRequest := c.MustGet("RawRequest").([]byte)
|
||||||
|
ticket := c.MustGet("Ticket").(database.Ticket)
|
||||||
|
knownTicket := c.MustGet("KnownTicket").(bool)
|
||||||
|
vWalletClient := c.MustGet("VotingWalletClient").(*rpc.VotingWalletRPC)
|
||||||
|
|
||||||
|
if !knownTicket {
|
||||||
|
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
||||||
|
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var setVoteChoicesRequest SetVoteChoicesRequest
|
var setVoteChoicesRequest SetVoteChoicesRequest
|
||||||
if err := c.ShouldBindJSON(&setVoteChoicesRequest); err != nil {
|
if err := binding.JSON.BindBody(rawRequest, &setVoteChoicesRequest); err != nil {
|
||||||
log.Warnf("Bad setvotechoices request from %s: %v", c.ClientIP(), err)
|
log.Warnf("Bad setvotechoices request from %s: %v", c.ClientIP(), err)
|
||||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate 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
|
voteChoices := setVoteChoicesRequest.VoteChoices
|
||||||
err = isValidVoteChoices(cfg.NetParams, currentVoteVersion(cfg.NetParams), voteChoices)
|
err := isValidVoteChoices(cfg.NetParams, currentVoteVersion(cfg.NetParams), voteChoices)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Invalid votechoices from %s: %v", c.ClientIP(), err)
|
log.Warnf("Invalid votechoices from %s: %v", c.ClientIP(), err)
|
||||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
return
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
vWalletConn, err := votingWalletConnect()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Voting wallet connection error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := c.Request.Context()
|
|
||||||
vWalletClient, err := rpc.VotingWalletClient(ctx, vWalletConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Voting wallet client error: %v", err)
|
|
||||||
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update VoteChoices in the database before updating the wallets. DB is
|
// Update VoteChoices in the database before updating the wallets. DB is
|
||||||
// source of truth and is less likely to error.
|
// source of truth and is less likely to error.
|
||||||
err = db.UpdateVoteChoices(txHash.String(), voteChoices)
|
err = db.UpdateVoteChoices(ticket.Hash, voteChoices)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("UpdateVoteChoices error: %v", err)
|
log.Errorf("UpdateVoteChoices error: %v", err)
|
||||||
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
||||||
|
|||||||
@ -1,62 +1,39 @@
|
|||||||
package webapi
|
package webapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
||||||
"github.com/decred/dcrd/dcrutil/v3"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/jholdstock/dcrvsp/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ticketStatus is the handler for "GET /ticketstatus".
|
// ticketStatus is the handler for "GET /ticketstatus".
|
||||||
func ticketStatus(c *gin.Context) {
|
func ticketStatus(c *gin.Context) {
|
||||||
var ticketStatusRequest TicketStatusRequest
|
|
||||||
if err := c.ShouldBindJSON(&ticketStatusRequest); err != nil {
|
|
||||||
log.Warnf("Bad ticketstatus request from %s: %v", c.ClientIP(), err)
|
|
||||||
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate TicketHash.
|
// Get values which have been added to context by middleware.
|
||||||
ticketHashStr := ticketStatusRequest.TicketHash
|
rawRequest := c.MustGet("RawRequest").([]byte)
|
||||||
_, err := chainhash.NewHashFromStr(ticketHashStr)
|
ticket := c.MustGet("Ticket").(database.Ticket)
|
||||||
if err != nil {
|
knownTicket := c.MustGet("KnownTicket").(bool)
|
||||||
log.Warnf("Invalid ticket hash from %s", c.ClientIP())
|
|
||||||
sendErrorResponse("invalid ticket hash", http.StatusBadRequest, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Signature - sanity check signature is in base64 encoding.
|
if !knownTicket {
|
||||||
signature := ticketStatusRequest.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
|
|
||||||
}
|
|
||||||
|
|
||||||
ticket, err := db.GetTicketByHash(ticketHashStr)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
||||||
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify message
|
var ticketStatusRequest TicketStatusRequest
|
||||||
message := fmt.Sprintf("vsp v3 ticketstatus %d %s", ticketStatusRequest.Timestamp, ticketHashStr)
|
if err := binding.JSON.BindBody(rawRequest, &ticketStatusRequest); err != nil {
|
||||||
err = dcrutil.VerifyMessage(ticket.CommitmentAddress, signature, message, cfg.NetParams)
|
log.Warnf("Bad ticketstatus request from %s: %v", c.ClientIP(), err)
|
||||||
if err != nil {
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
||||||
log.Warnf("Invalid signature from %s: %v", c.ClientIP(), err)
|
|
||||||
sendErrorResponse("invalid signature", http.StatusBadRequest, c)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONResponse(ticketStatusResponse{
|
sendJSONResponse(ticketStatusResponse{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
Request: ticketStatusRequest,
|
Request: ticketStatusRequest,
|
||||||
Status: "active", // TODO - active, pending, expired (missed, revoked?)
|
Status: "active",
|
||||||
VoteChoices: ticket.VoteChoices,
|
VoteChoices: ticket.VoteChoices,
|
||||||
}, c)
|
}, c)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ type feeResponse struct {
|
|||||||
type FeeAddressRequest struct {
|
type FeeAddressRequest struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
TicketHash string `json:"tickethash" binding:"required"`
|
TicketHash string `json:"tickethash" binding:"required"`
|
||||||
Signature string `json:"signature" binding:"required"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type feeAddressResponse struct {
|
type feeAddressResponse struct {
|
||||||
@ -41,7 +40,6 @@ type payFeeResponse struct {
|
|||||||
type SetVoteChoicesRequest struct {
|
type SetVoteChoicesRequest struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
TicketHash string `json:"tickethash" binding:"required"`
|
TicketHash string `json:"tickethash" binding:"required"`
|
||||||
Signature string `json:"commitmentsignature" binding:"required"`
|
|
||||||
VoteChoices map[string]string `json:"votechoices" binding:"required"`
|
VoteChoices map[string]string `json:"votechoices" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +52,6 @@ type setVoteChoicesResponse struct {
|
|||||||
type TicketStatusRequest struct {
|
type TicketStatusRequest struct {
|
||||||
Timestamp int64 `json:"timestamp" binding:"required"`
|
Timestamp int64 `json:"timestamp" binding:"required"`
|
||||||
TicketHash string `json:"tickethash" binding:"required"`
|
TicketHash string `json:"tickethash" binding:"required"`
|
||||||
Signature string `json:"signature" binding:"required"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ticketStatusResponse struct {
|
type ticketStatusResponse struct {
|
||||||
|
|||||||
@ -145,17 +145,25 @@ func router(debugMode bool) *gin.Engine {
|
|||||||
// Serve static web resources
|
// Serve static web resources
|
||||||
router.Static("/public", "webapi/public/")
|
router.Static("/public", "webapi/public/")
|
||||||
|
|
||||||
|
// These routes have no extra middleware. They can be accessed by anybody.
|
||||||
router.GET("/", homepage)
|
router.GET("/", homepage)
|
||||||
|
router.GET("/api/fee", fee)
|
||||||
|
router.GET("/api/pubkey", pubKey)
|
||||||
|
|
||||||
api := router.Group("/api")
|
// These API routes access the fee wallet and they need authentication.
|
||||||
{
|
feeOnly := router.Group("/api").Use(
|
||||||
api.GET("/fee", fee)
|
withFeeWalletClient(), vspAuth(),
|
||||||
api.POST("/feeaddress", feeAddress)
|
)
|
||||||
api.GET("/pubkey", pubKey)
|
feeOnly.POST("/feeaddress", feeAddress)
|
||||||
api.POST("/payfee", payFee)
|
feeOnly.GET("/ticketstatus", ticketStatus)
|
||||||
api.POST("/setvotechoices", setVoteChoices)
|
|
||||||
api.GET("/ticketstatus", ticketStatus)
|
// These API routes access the fee wallet and the voting wallets, and they
|
||||||
}
|
// need authentication.
|
||||||
|
both := router.Group("/api").Use(
|
||||||
|
withFeeWalletClient(), withVotingWalletClient(), vspAuth(),
|
||||||
|
)
|
||||||
|
both.POST("/payfee", payFee)
|
||||||
|
both.POST("/setvotechoices", setVoteChoices)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
@ -188,7 +196,7 @@ func sendJSONResponse(resp interface{}, c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sig := ed25519.Sign(cfg.SignKey, dec)
|
sig := ed25519.Sign(cfg.SignKey, dec)
|
||||||
c.Writer.Header().Set("VSP-Signature", hex.EncodeToString(sig))
|
c.Writer.Header().Set("VSP-Server-Signature", hex.EncodeToString(sig))
|
||||||
|
|
||||||
c.AbortWithStatusJSON(http.StatusOK, resp)
|
c.AbortWithStatusJSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user