Compare scripts rather than addresses. (#267)

* Decode fee address with stdaddr.

Compare script and script versions rather than just comparing address strings.

Also move the fee amount check higher, so it is with the rest of the fee validating code.

* Compare voting addr scripts rather than addrs.

Rather than comparing the address strings, ensure both voting script and script version match.
This commit is contained in:
Jamie Holdstock 2021-06-08 21:46:16 +08:00 committed by GitHub
parent dad662b0f0
commit 978b78e745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -5,12 +5,13 @@
package webapi package webapi
import ( import (
"bytes"
"fmt"
"strings" "strings"
"time" "time"
"github.com/decred/dcrd/blockchain/v4" "github.com/decred/dcrd/blockchain/v4"
"github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/txscript/v4/stdaddr" "github.com/decred/dcrd/txscript/v4/stdaddr"
"github.com/decred/vspd/database" "github.com/decred/vspd/database"
"github.com/decred/vspd/rpc" "github.com/decred/vspd/rpc"
@ -18,11 +19,6 @@ import (
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
) )
const (
// Assume the treasury is enabled
isTreasuryEnabled = true
)
// payFee is the handler for "POST /api/v3/payfee". // payFee is the handler for "POST /api/v3/payfee".
func payFee(c *gin.Context) { func payFee(c *gin.Context) {
const funcName = "payFee" const funcName = "payFee"
@ -128,42 +124,47 @@ func payFee(c *gin.Context) {
return return
} }
// Loop through transaction outputs until we find one which pays to the // Decode fee address to get its payment script details.
// expected fee address. Record how much is being paid to the fee address. feeAddr, err := stdaddr.DecodeAddress(ticket.FeeAddress, cfg.NetParams)
var feePaid dcrutil.Amount
const scriptVersion = 0
findAddress:
for _, txOut := range feeTx.TxOut {
if txOut.Version != scriptVersion {
log.Errorf("%s: Fee tx with invalid script version (clientIP=%s, ticketHash=%s): was %d, expected %d",
funcName, c.ClientIP(), ticket.Hash, txOut.Version, scriptVersion)
sendErrorWithMsg("invalid script version", errInvalidFeeTx, c)
return
}
_, addresses, _, err := txscript.ExtractPkScriptAddrs(scriptVersion,
txOut.PkScript, cfg.NetParams, isTreasuryEnabled)
if err != nil { if err != nil {
log.Errorf("%s: Extract PK error (clientIP=%s, ticketHash=%s): %v", log.Errorf("%s: Failed to decode fee address (ticketHash=%s): %v",
funcName, c.ClientIP(), ticket.Hash, err) funcName, ticket.Hash, err)
sendError(errInternalError, c) sendError(errInternalError, c)
return return
} }
for _, addr := range addresses {
if addr.String() == ticket.FeeAddress { wantScriptVer, wantScript := feeAddr.PaymentScript()
// Confirm the provided fee transaction contains an output which pays to the
// expected payment script. Both script and script version should match.
var feePaid dcrutil.Amount
for _, txOut := range feeTx.TxOut {
if txOut.Version == wantScriptVer && bytes.Equal(txOut.PkScript, wantScript) {
feePaid = dcrutil.Amount(txOut.Value) feePaid = dcrutil.Amount(txOut.Value)
break findAddress break
}
} }
} }
// Confirm a fee payment was found.
if feePaid == 0 { if feePaid == 0 {
log.Warnf("%s: Fee tx did not include expected payment (ticketHash=%s, feeAddress=%s, clientIP=%s)", log.Warnf("%s: Fee tx did not include expected payment (ticketHash=%s, feeAddress=%s, clientIP=%s)",
funcName, ticket.Hash, ticket.FeeAddress, c.ClientIP()) funcName, ticket.Hash, ticket.FeeAddress, c.ClientIP())
sendErrorWithMsg("feetx did not include any payments for fee address", errInvalidFeeTx, c) sendErrorWithMsg(
fmt.Sprintf("feetx did not include any payments for fee address %s", ticket.FeeAddress),
errInvalidFeeTx, c)
return return
} }
// Confirm fee payment is equal to or larger than the minimum expected.
minFee := dcrutil.Amount(ticket.FeeAmount)
if feePaid < minFee {
log.Warnf("%s: Fee too small (ticketHash=%s, clientIP=%s): was %s, expected minimum %s",
funcName, ticket.Hash, c.ClientIP(), feePaid, minFee)
sendError(errFeeTooSmall, c)
return
}
// Decode the provided voting WIF to get its voting rights script.
pkHash := stdaddr.Hash160(votingWIF.PubKey()) pkHash := stdaddr.Hash160(votingWIF.PubKey())
wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, cfg.NetParams) wifAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, cfg.NetParams)
if err != nil { if err != nil {
@ -173,7 +174,9 @@ findAddress:
return return
} }
// Decode ticket transaction to get its voting address. wantScriptVer, wantScript = wifAddr.VotingRightsScript()
// Decode ticket transaction to get its voting rights script.
ticketTx, err := decodeTransaction(rawTicket.Hex) ticketTx, err := decodeTransaction(rawTicket.Hex)
if err != nil { if err != nil {
log.Warnf("%s: Failed to decode ticket hex (ticketHash=%s): %v", log.Warnf("%s: Failed to decode ticket hex (ticketHash=%s): %v",
@ -182,36 +185,19 @@ findAddress:
return return
} }
// Get ticket voting address. actualScriptVer := ticketTx.TxOut[0].Version
_, votingAddr, _, err := txscript.ExtractPkScriptAddrs(scriptVersion, ticketTx.TxOut[0].PkScript, cfg.NetParams, isTreasuryEnabled) actualScript := ticketTx.TxOut[0].PkScript
if err != nil {
log.Errorf("%s: ExtractPK error (ticketHash=%s): %v", funcName, ticket.Hash, err)
sendError(errInternalError, c)
return
}
if len(votingAddr) == 0 {
log.Error("%s: No voting address found for ticket (ticketHash=%s)", funcName, ticket.Hash)
sendError(errInternalError, c)
return
}
// Ensure provided private key will allow us to vote this ticket. // Ensure provided voting WIF matches the actual voting address of the
if votingAddr[0].String() != wifAddr.String() { // ticket. Both script and script version should match.
log.Warnf("%s: Voting address does not match provided private key: (ticketHash=%s, votingAddr=%+v, wifAddr=%+v)", if actualScriptVer != wantScriptVer || !bytes.Equal(actualScript, wantScript) {
funcName, ticket.Hash, votingAddr[0], wifAddr) log.Warnf("%s: Voting address does not match provided private key: (ticketHash=%s)",
funcName, ticket.Hash)
sendErrorWithMsg("voting address does not match provided private key", sendErrorWithMsg("voting address does not match provided private key",
errInvalidPrivKey, c) errInvalidPrivKey, c)
return return
} }
minFee := dcrutil.Amount(ticket.FeeAmount)
if feePaid < minFee {
log.Warnf("%s: Fee too small (ticketHash=%s, clientIP=%s): was %s, expected minimum %s",
funcName, ticket.Hash, c.ClientIP(), feePaid, minFee)
sendError(errFeeTooSmall, c)
return
}
// At this point we are satisfied that the request is valid and the fee tx // At this point we are satisfied that the request is valid and the fee tx
// pays sufficient fees to the expected address. Proceed to update the // pays sufficient fees to the expected address. Proceed to update the
// database, and if the ticket is confirmed broadcast the fee transaction. // database, and if the ticket is confirmed broadcast the fee transaction.