Reject unvotable tickets.
/payfee and /getaddress will now only accept tickets which are immature or live.
This commit is contained in:
parent
1270f77fd6
commit
81a6bf1ea8
@ -98,17 +98,17 @@ func Open(ctx context.Context, shutdownWg *sync.WaitGroup, dbFile string, backup
|
|||||||
|
|
||||||
// Start a ticker to update the backup file at the specified interval.
|
// Start a ticker to update the backup file at the specified interval.
|
||||||
shutdownWg.Add(1)
|
shutdownWg.Add(1)
|
||||||
backupTicker := time.NewTicker(backupInterval)
|
|
||||||
go func() {
|
go func() {
|
||||||
|
ticker := time.NewTicker(backupInterval)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-backupTicker.C:
|
case <-ticker.C:
|
||||||
err := writeBackup(db, dbFile)
|
err := writeBackup(db, dbFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to write database backup: %v", err)
|
log.Errorf("Failed to write database backup: %v", err)
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
backupTicker.Stop()
|
ticker.Stop()
|
||||||
shutdownWg.Done()
|
shutdownWg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ its voting wallets unless both of these calls have succeeded.**
|
|||||||
|
|
||||||
Request fee amount and address for a ticket. The fee amount is only valid until
|
Request fee amount and address for a ticket. The fee amount is only valid until
|
||||||
the expiration time has passed. The fee amount is an absolute value measured in
|
the expiration time has passed. The fee amount is an absolute value measured in
|
||||||
DCR.
|
DCR. Returns an error if the specified ticket is not currently immature or live.
|
||||||
|
|
||||||
This call will return an error if a fee transaction has already been provided
|
This call will return an error if a fee transaction has already been provided
|
||||||
for the specified ticket.
|
for the specified ticket.
|
||||||
@ -82,7 +82,8 @@ for the specified ticket.
|
|||||||
Provide the voting key for the ticket, voting preference, and a signed
|
Provide the voting key for the ticket, voting preference, and a signed
|
||||||
transaction which pays the fee to the specified address. If the fee has expired,
|
transaction which pays the fee to the specified address. If the fee has expired,
|
||||||
this call will return an error and the client will need to request a new fee by
|
this call will return an error and the client will need to request a new fee by
|
||||||
calling `/feeaddress` again.
|
calling `/feeaddress` again. Returns an error if the specified ticket is not
|
||||||
|
currently immature or live.
|
||||||
|
|
||||||
The VSP will not broadcast the fee transaction until the ticket purchase has 6
|
The VSP will not broadcast the fee transaction until the ticket purchase has 6
|
||||||
confirmations. For this reason, it is important that the client ensures the
|
confirmations. For this reason, it is important that the client ensures the
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -15,6 +15,7 @@ require (
|
|||||||
github.com/decred/slog v1.0.0
|
github.com/decred/slog v1.0.0
|
||||||
github.com/gin-gonic/gin v1.6.3
|
github.com/gin-gonic/gin v1.6.3
|
||||||
github.com/jessevdk/go-flags v1.4.0
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
|
github.com/jrick/bitset v1.0.0
|
||||||
github.com/jrick/logrotate v1.0.0
|
github.com/jrick/logrotate v1.0.0
|
||||||
github.com/jrick/wsrpc/v2 v2.3.3
|
github.com/jrick/wsrpc/v2 v2.3.3
|
||||||
go.etcd.io/bbolt v1.3.4
|
go.etcd.io/bbolt v1.3.4
|
||||||
|
|||||||
45
rpc/dcrd.go
45
rpc/dcrd.go
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/decred/dcrd/chaincfg/v3"
|
"github.com/decred/dcrd/chaincfg/v3"
|
||||||
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
|
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
|
||||||
"github.com/decred/dcrd/wire"
|
"github.com/decred/dcrd/wire"
|
||||||
|
"github.com/jrick/bitset"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -135,3 +136,47 @@ func (c *DcrdRPC) GetBestBlockHeader() (*dcrdtypes.GetBlockHeaderVerboseResult,
|
|||||||
}
|
}
|
||||||
return &blockHeader, nil
|
return &blockHeader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *DcrdRPC) ExistsLiveTicket(ticketHash string) (bool, error) {
|
||||||
|
var exists string
|
||||||
|
err := c.Call(c.ctx, "existslivetickets", &exists, []string{ticketHash})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existsBytes := make([]byte, hex.DecodedLen(len(exists)))
|
||||||
|
_, err = hex.Decode(existsBytes, []byte(exists))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitset.Bytes(existsBytes).Get(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanTicketVote checks determines whether a ticket is able to vote at some
|
||||||
|
// point in the future by checking that it is currently either immature or live.
|
||||||
|
func (c *DcrdRPC) CanTicketVote(ticketHash string, netParams *chaincfg.Params) (bool, error) {
|
||||||
|
// Get ticket details.
|
||||||
|
rawTx, err := c.GetRawTransaction(ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tickets which older than (TicketMaturity+TicketExpiry) are too old to vote.
|
||||||
|
if rawTx.Confirmations > int64(uint32(netParams.TicketMaturity)+netParams.TicketExpiry) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ticket is currently immature, it will be able to vote in future.
|
||||||
|
if rawTx.Confirmations <= int64(netParams.TicketMaturity) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ticket is currently live, it will be able to vote in future.
|
||||||
|
live, err := c.ExistsLiveTicket(ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return live, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -81,6 +81,20 @@ func feeAddress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ticketHash := feeAddressRequest.TicketHash
|
||||||
|
|
||||||
|
canVote, err := dcrdClient.CanTicketVote(ticketHash, cfg.NetParams)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("canTicketVote error: %v", err)
|
||||||
|
sendErrorResponse("error validating ticket", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !canVote {
|
||||||
|
log.Warnf("Unvotable ticket %s from %s", ticketHash, c.ClientIP())
|
||||||
|
sendErrorResponse("ticket not eligible to vote", http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// VSP already knows this ticket and has already issued it a fee address.
|
// VSP already knows this ticket and has already issued it a fee address.
|
||||||
if knownTicket {
|
if knownTicket {
|
||||||
|
|
||||||
@ -126,29 +140,6 @@ func feeAddress(c *gin.Context) {
|
|||||||
// Beyond this point we are processing a new ticket which the VSP has not
|
// Beyond this point we are processing a new ticket which the VSP has not
|
||||||
// seen before.
|
// seen before.
|
||||||
|
|
||||||
ticketHash := feeAddressRequest.TicketHash
|
|
||||||
|
|
||||||
// Get transaction details.
|
|
||||||
rawTx, err := dcrdClient.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't accept tickets which are too old.
|
|
||||||
if rawTx.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if ticket is fully confirmed.
|
|
||||||
var confirmed bool
|
|
||||||
if rawTx.Confirmations >= requiredConfs {
|
|
||||||
confirmed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fee, err := getCurrentFee(dcrdClient)
|
fee, err := getCurrentFee(dcrdClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("getCurrentFee error: %v", err)
|
log.Errorf("getCurrentFee error: %v", err)
|
||||||
@ -169,7 +160,6 @@ func feeAddress(c *gin.Context) {
|
|||||||
CommitmentAddress: commitmentAddress,
|
CommitmentAddress: commitmentAddress,
|
||||||
FeeAddressIndex: newAddressIdx,
|
FeeAddressIndex: newAddressIdx,
|
||||||
FeeAddress: newAddress,
|
FeeAddress: newAddress,
|
||||||
Confirmed: confirmed,
|
|
||||||
FeeAmount: fee,
|
FeeAmount: fee,
|
||||||
FeeExpiration: expire,
|
FeeExpiration: expire,
|
||||||
// VotingKey and VoteChoices: set during payfee
|
// VotingKey and VoteChoices: set during payfee
|
||||||
@ -182,8 +172,8 @@ func feeAddress(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Fee address created for new ticket: tktConfirmed=%t, feeAddrIdx=%d, "+
|
log.Debugf("Fee address created for new ticket: feeAddrIdx=%d, feeAddr=%s, "+
|
||||||
"feeAddr=%s, feeAmt=%f, ticketHash=%s", confirmed, newAddressIdx, newAddress, fee, ticketHash)
|
"feeAmt=%f, ticketHash=%s", newAddressIdx, newAddress, fee, ticketHash)
|
||||||
|
|
||||||
sendJSONResponse(feeAddressResponse{
|
sendJSONResponse(feeAddressResponse{
|
||||||
Timestamp: now.Unix(),
|
Timestamp: now.Unix(),
|
||||||
|
|||||||
@ -44,6 +44,18 @@ func payFee(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canVote, err := dcrdClient.CanTicketVote(ticket.Hash, cfg.NetParams)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("canTicketVote error: %v", err)
|
||||||
|
sendErrorResponse("error validating ticket", http.StatusInternalServerError, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !canVote {
|
||||||
|
log.Warnf("Unvotable ticket %s from %s", ticket.Hash, c.ClientIP())
|
||||||
|
sendErrorResponse("ticket not eligible to vote", http.StatusBadRequest, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Respond early if the fee for this ticket is expired.
|
// Respond early if the fee for this ticket is expired.
|
||||||
if ticket.FeeExpired() {
|
if ticket.FeeExpired() {
|
||||||
log.Warnf("Expired payfee request from %s", c.ClientIP())
|
log.Warnf("Expired payfee request from %s", c.ClientIP())
|
||||||
|
|||||||
@ -28,9 +28,6 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// requiredConfs is the number of confirmations required to consider a
|
|
||||||
// ticket purchase or a fee transaction to be final.
|
|
||||||
requiredConfs = 6
|
|
||||||
// TODO: Make this configurable or get it from RPC.
|
// TODO: Make this configurable or get it from RPC.
|
||||||
relayFee = 0.0001
|
relayFee = 0.0001
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user