Repository: decred/dcrd
Pull request #2657 (2021-06-09 05:30:29)

blockchain: Use scripts in tickets address query.
This modifies the `TicketsWithAddress` query method in `blockchain` to accept the more specific `stdaddr.StakeAddress` since only stake-capable addresses can be used in tickets and modifies the logic to compare the expected voting rights script and associated script version directly instead of extracting and comparing encoded addresses.  It also updates the callers and plumbing in `rpcserver` accordingly as well as adds a test for the new non-stake address error in the RPC handler.

Not only is this approach more efficient, it also removes the reliance on address extraction which in turn removes the need for the flag which specifies whether or not the treasury is active.


Patch:
diff --git a/blockchain/stakeext.go b/blockchain/stakeext.go
index f16df3ca..8603fcc4 100644
--- a/blockchain/stakeext.go
+++ b/blockchain/stakeext.go
@@ -6,9 +6,10 @@
 package blockchain
 
 import (
+	"bytes"
+
 	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/dcrutil/v4"
-	"github.com/decred/dcrd/txscript/v4"
 	"github.com/decred/dcrd/txscript/v4/stdaddr"
 	"github.com/decred/dcrd/wire"
 )
@@ -101,16 +102,14 @@ func (b *BlockChain) MissedTickets() ([]chainhash.Hash, error) {
 // corresponding to the given address.
 //
 // This function is safe for concurrent access.
-func (b *BlockChain) TicketsWithAddress(address stdaddr.Address, isTreasuryEnabled bool) ([]chainhash.Hash, error) {
+func (b *BlockChain) TicketsWithAddress(address stdaddr.StakeAddress) ([]chainhash.Hash, error) {
 	b.chainLock.RLock()
 	sn := b.bestChain.Tip().stakeNode
 	b.chainLock.RUnlock()
 
-	tickets := sn.LiveTickets()
-
-	encodedAddr := address.String()
 	var ticketsWithAddr []chainhash.Hash
-
+	votingRightsScriptVer, votingRightsScript := address.VotingRightsScript()
+	tickets := sn.LiveTickets()
 	for _, hash := range tickets {
 		outpoint := wire.OutPoint{Hash: hash, Index: 0, Tree: wire.TxTreeStake}
 		utxo, err := b.utxoCache.FetchEntry(outpoint)
@@ -118,12 +117,9 @@ func (b *BlockChain) TicketsWithAddress(address stdaddr.Address, isTreasuryEnabl
 			return nil, err
 		}
 
-		_, addrs, _, err := txscript.ExtractPkScriptAddrs(utxo.ScriptVersion(),
-			utxo.PkScript(), b.chainParams, isTreasuryEnabled)
-		if err != nil {
-			return nil, err
-		}
-		if addrs[0].String() == encodedAddr {
+		if utxo.ScriptVersion() == votingRightsScriptVer &&
+			bytes.Equal(utxo.PkScript(), votingRightsScript) {
+
 			ticketsWithAddr = append(ticketsWithAddr, hash)
 		}
 	}
diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go
index de6f71e3..8eb9e00b 100644
--- a/internal/rpcserver/interface.go
+++ b/internal/rpcserver/interface.go
@@ -377,7 +377,7 @@ type Chain interface {
 
 	// TicketsWithAddress returns a slice of ticket hashes that are currently
 	// live corresponding to the given address.
-	TicketsWithAddress(address stdaddr.Address, isTreasuryEnabled bool) ([]chainhash.Hash, error)
+	TicketsWithAddress(address stdaddr.StakeAddress) ([]chainhash.Hash, error)
 
 	// TipGeneration returns the entire generation of blocks stemming from the
 	// parent of the current tip.
diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go
index 05579b69..4cbd1475 100644
--- a/internal/rpcserver/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -5100,15 +5100,14 @@ func handleTicketsForAddress(_ context.Context, s *Server, cmd interface{}) (int
 		return nil, rpcInvalidError("Invalid address: %v", err)
 	}
 
-	// Determine if the treasury rules are active as of the current best tip.
-	chain := s.cfg.Chain
-	prevBlkHash := chain.BestSnapshot().Hash
-	isTreasuryEnabled, err := s.isTreasuryAgendaActive(&prevBlkHash)
-	if err != nil {
-		return nil, err
+	stakeAddr, ok := addr.(stdaddr.StakeAddress)
+	if !ok {
+		return nil, rpcInvalidError("Address is not valid for use in the " +
+			"staking system")
 	}
 
-	tickets, err := chain.TicketsWithAddress(addr, isTreasuryEnabled)
+	chain := s.cfg.Chain
+	tickets, err := chain.TicketsWithAddress(stakeAddr)
 	if err != nil {
 		return nil, rpcInternalError(err.Error(), "could not obtain tickets")
 	}


Test patch:
diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go
index 124fafe5..36de0a11 100644
--- a/blockchain/chain_test.go
+++ b/blockchain/chain_test.go
@@ -127,7 +127,7 @@ func TestBlockchainFunctions(t *testing.T) {
 	}
 
 	a, _ := stdaddr.DecodeAddress("SsbKpMkPnadDcZFFZqRPY8nvdFagrktKuzB", params)
-	hs, err := chain.TicketsWithAddress(a, noTreasury)
+	hs, err := chain.TicketsWithAddress(a.(stdaddr.StakeAddress))
 	if err != nil {
 		t.Errorf("Failed to do TicketsWithAddress: %v", err)
 	}
diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
index 93b035c9..98d3b385 100644
--- a/internal/rpcserver/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -401,7 +401,7 @@ func (c *testRPCChain) TicketPoolValue() (dcrutil.Amount, error) {
 
 // TicketsWithAddress returns a mocked slice of ticket hashes that are currently
 // live corresponding to the given address.
-func (c *testRPCChain) TicketsWithAddress(address stdaddr.Address, isTreasuryEnabled bool) ([]chainhash.Hash, error) {
+func (c *testRPCChain) TicketsWithAddress(address stdaddr.StakeAddress) ([]chainhash.Hash, error) {
 	return c.ticketsWithAddress, c.ticketsWithAddressErr
 }
 
@@ -6607,20 +6607,14 @@ func TestTicketsForAddress(t *testing.T) {
 		wantErr: true,
 		errCode: dcrjson.ErrRPCInvalidParameter,
 	}, {
-		name:    "handleTicketsForAddress: unable to fetch treasury agenda status",
+		name:    "handleTicketsForAddress: non-stake address",
 		handler: handleTicketsForAddress,
 		cmd: &types.TicketsForAddressCmd{
-			Address: addr,
+			// v0 pay-to-pubkey ecdsa.
+			Address: "SkLUJQxtYoVrewN6fwqsU6JQjxLs5a6xfcTsGfUYiLr2AUY6HuLMN",
 		},
-		mockChain: func() *testRPCChain {
-			chain := defaultMockRPCChain()
-			chain.treasuryActive = false
-			chain.treasuryActiveErr =
-				errors.New("unable to fetch treasury agenda status")
-			return chain
-		}(),
 		wantErr: true,
-		errCode: dcrjson.ErrRPCInternal.Code,
+		errCode: dcrjson.ErrRPCInvalidParameter,
 	}, {
 		name:    "handleTicketsForAddress: unable to fetch tickets for address",
 		handler: handleTicketsForAddress,