* 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.
155 lines
4.7 KiB
Go
155 lines
4.7 KiB
Go
package webapi
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain/stake/v3"
|
|
"github.com/decred/dcrd/wire"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gin-gonic/gin/binding"
|
|
"github.com/jholdstock/dcrvsp/database"
|
|
"github.com/jholdstock/dcrvsp/rpc"
|
|
)
|
|
|
|
// feeAddress is the handler for "POST /feeaddress".
|
|
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
|
|
if err := binding.JSON.BindBody(rawRequest, &feeAddressRequest); err != nil {
|
|
log.Warnf("Bad feeaddress request from %s: %v", c.ClientIP(), err)
|
|
sendErrorResponse(err.Error(), http.StatusBadRequest, c)
|
|
return
|
|
}
|
|
|
|
// VSP already knows this ticket and has already issued it a fee address.
|
|
if knownTicket {
|
|
// If the expiry period has passed we need to issue a new fee.
|
|
now := time.Now()
|
|
expire := ticket.FeeExpiration
|
|
VSPFee := ticket.VSPFee
|
|
if now.After(time.Unix(ticket.FeeExpiration, 0)) {
|
|
expire = now.Add(cfg.FeeAddressExpiration).Unix()
|
|
VSPFee = cfg.VSPFee
|
|
|
|
err := db.UpdateExpireAndFee(ticket.Hash, expire, VSPFee)
|
|
if err != nil {
|
|
log.Errorf("UpdateExpireAndFee error: %v", err)
|
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
|
return
|
|
}
|
|
}
|
|
sendJSONResponse(feeAddressResponse{
|
|
Timestamp: now.Unix(),
|
|
Request: feeAddressRequest,
|
|
FeeAddress: ticket.FeeAddress,
|
|
Fee: VSPFee,
|
|
Expiration: expire,
|
|
}, c)
|
|
|
|
return
|
|
}
|
|
|
|
// Beyond this point we are processing a new ticket which the VSP has not
|
|
// seen before.
|
|
|
|
ticketHash := feeAddressRequest.TicketHash
|
|
|
|
// Ensure ticket exists and is mined.
|
|
resp, err := fWalletClient.GetRawTransaction(ticketHash)
|
|
if err != nil {
|
|
log.Warnf("Could not retrieve tx %s for %s: %v", ticketHash, c.ClientIP(), err)
|
|
sendErrorResponse("unknown transaction", http.StatusBadRequest, c)
|
|
return
|
|
}
|
|
if resp.Confirmations < 2 || resp.BlockHeight < 0 {
|
|
log.Warnf("Not enough confs for tx from %s", c.ClientIP())
|
|
sendErrorResponse("transaction does not have minimum confirmations", http.StatusBadRequest, c)
|
|
return
|
|
}
|
|
if resp.Confirmations > int64(uint32(cfg.NetParams.TicketMaturity)+cfg.NetParams.TicketExpiry) {
|
|
log.Warnf("Too old tx from %s", c.ClientIP())
|
|
sendErrorResponse("transaction too old", http.StatusBadRequest, c)
|
|
return
|
|
}
|
|
|
|
msgHex, err := hex.DecodeString(resp.Hex)
|
|
if err != nil {
|
|
log.Errorf("Failed to decode tx: %v", err)
|
|
sendErrorResponse("unable to decode transaction", http.StatusInternalServerError, c)
|
|
return
|
|
}
|
|
|
|
msgTx := wire.NewMsgTx()
|
|
if err = msgTx.FromBytes(msgHex); err != nil {
|
|
log.Errorf("Failed to deserialize tx: %v", err)
|
|
sendErrorResponse("failed to deserialize transaction", http.StatusInternalServerError, c)
|
|
return
|
|
}
|
|
if !stake.IsSStx(msgTx) {
|
|
log.Warnf("Non-ticket tx from %s", c.ClientIP())
|
|
sendErrorResponse("transaction is not a ticket", http.StatusBadRequest, c)
|
|
return
|
|
}
|
|
if len(msgTx.TxOut) != 3 {
|
|
log.Warnf("Invalid ticket from %s", c.ClientIP())
|
|
sendErrorResponse("invalid ticket", http.StatusBadRequest, c)
|
|
return
|
|
}
|
|
|
|
// get blockheight and sdiff which is required by
|
|
// txrules.StakePoolTicketFee, and store them in the database
|
|
// for processing by payfee
|
|
blockHeader, err := fWalletClient.GetBlockHeader(resp.BlockHash)
|
|
if err != nil {
|
|
log.Errorf("GetBlockHeader error: %v", err)
|
|
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
|
|
return
|
|
}
|
|
|
|
// TODO: Generate this within dcrvsp without an RPC call?
|
|
newAddress, err := fWalletClient.GetNewAddress(cfg.FeeAccountName)
|
|
if err != nil {
|
|
log.Errorf("GetNewAddress error: %v", err)
|
|
sendErrorResponse("unable to generate fee address", http.StatusInternalServerError, c)
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
expire := now.Add(cfg.FeeAddressExpiration).Unix()
|
|
|
|
dbTicket := database.Ticket{
|
|
Hash: ticketHash,
|
|
CommitmentAddress: commitmentAddress,
|
|
FeeAddress: newAddress,
|
|
SDiff: blockHeader.SBits,
|
|
BlockHeight: int64(blockHeader.Height),
|
|
VSPFee: cfg.VSPFee,
|
|
FeeExpiration: expire,
|
|
// VotingKey and VoteChoices: set during payfee
|
|
}
|
|
|
|
err = db.InsertTicket(dbTicket)
|
|
if err != nil {
|
|
log.Errorf("InsertTicket error: %v", err)
|
|
sendErrorResponse("database error", http.StatusInternalServerError, c)
|
|
return
|
|
}
|
|
|
|
sendJSONResponse(feeAddressResponse{
|
|
Timestamp: now.Unix(),
|
|
Request: feeAddressRequest,
|
|
FeeAddress: newAddress,
|
|
Fee: cfg.VSPFee,
|
|
Expiration: expire,
|
|
}, c)
|
|
}
|