From 921480c4a35866f192e4372fdd35184afd79af5f Mon Sep 17 00:00:00 2001 From: jholdstock Date: Sat, 18 Apr 2026 12:23:43 +0800 Subject: [PATCH] Enhance fee transaction sanity checks. Confirm the existence of all of the inputs to fee transactions provided by clients. If any of the inputs do not exist, or are not known by the local dcrd instance, an error is returned to the client and the ticket is not registered with the VSP. --- internal/webapi/payfee.go | 25 ++++++++++++++++++++++++- rpc/dcrd.go | 16 +++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/internal/webapi/payfee.go b/internal/webapi/payfee.go index 77547fd7..9eedd1d4 100644 --- a/internal/webapi/payfee.go +++ b/internal/webapi/payfee.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2024 The Decred developers +// Copyright (c) 2021-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -143,6 +143,29 @@ func (w *WebAPI) payFee(c *gin.Context) { return } + // Confirm all inputs of the fee transaction exist. + for _, input := range feeTx.TxIn { + prevOut := input.PreviousOutPoint + txOut, err := dcrdClient.GetTxOut(prevOut.Hash, prevOut.Index, prevOut.Tree) + if err != nil { + w.log.Errorf("%s: dcrd.GetTxOut for fee tx input failed "+ + "(ticketHash=%s, output=%s:%d:%d, clientIP=%s): %v", + funcName, ticket.Hash, prevOut.Hash, prevOut.Index, prevOut.Tree, + c.ClientIP(), err) + w.sendError(types.ErrInternalError, c) + return + } + if txOut == nil { + w.log.Warnf("%s: Fee tx contains non-existent input "+ + "(ticketHash=%s, output=%s:%d:%d, clientIP=%s)", + funcName, ticket.Hash, prevOut.Hash, prevOut.Index, prevOut.Tree, + c.ClientIP()) + w.sendErrorWithMsg("fee tx includes non-existent input", + types.ErrInvalidFeeTx, c) + return + } + } + // Decode fee address to get its payment script details. feeAddr, err := stdaddr.DecodeAddress(ticket.FeeAddress, w.cfg.Network) if err != nil { diff --git a/rpc/dcrd.go b/rpc/dcrd.go index c40bc33a..75bc2edd 100644 --- a/rpc/dcrd.go +++ b/rpc/dcrd.go @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2025 The Decred developers +// Copyright (c) 2021-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -306,6 +306,20 @@ func (c *DcrdRPC) GetBlockHash(height int64) (string, error) { return resp, nil } +// GetTxOut returns the transaction output info if it's unspent and nil +// otherwise. +func (c *DcrdRPC) GetTxOut(hash chainhash.Hash, index uint32, + tree int8) (*dcrdtypes.GetTxOutResult, error) { + var resp *dcrdtypes.GetTxOutResult + const includeMempool = true + err := c.Call(context.TODO(), "gettxout", &resp, hash.String(), index, tree, + includeMempool) + if err != nil { + return nil, err + } + return resp, nil +} + // GetCFilterV2 retrieves the GCS filter for the provided block header, // optionally verifies the inclusion proof, then returns the filter along with // its key.