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