Add handling of modified getrawmempool RPC call and existslivetickets

getrawmempool has been modified to allow for the selection of specific
transaction types from the mempool. A new RPC call, existslivetickets,
has been added. This call takes a blob of ticket hashes and returns
a blob of bit flags specifying whether or not the tickets exist. This
allows for much faster getstakeinfo calls in the wallet.
This commit is contained in:
cjepson 2016-02-22 21:38:16 -05:00
parent 1652a0db40
commit 18acc1194f
6 changed files with 210 additions and 6 deletions

View File

@ -232,6 +232,23 @@ func (b *BlockChain) CheckLiveTicket(hash *chainhash.Hash) (bool, error) {
return b.tmdb.CheckLiveTicket(*hash)
}
// CheckLiveTickets returns whether or not a slice of tickets exist in the live
// ticket map of the stake database.
//
// This function is NOT safe for concurrent access.
func (b *BlockChain) CheckLiveTickets(hashes []*chainhash.Hash) ([]bool, error) {
var err error
existsSlice := make([]bool, len(hashes))
for i, hash := range hashes {
existsSlice[i], err = b.tmdb.CheckLiveTicket(*hash)
if err != nil {
return nil, err
}
}
return existsSlice, nil
}
// HaveBlock returns whether or not the chain instance has the block represented
// by the passed hash. This includes checking the various places a block can
// be like part of the main chain, on a side chain, or in the orphan pool.

View File

@ -350,6 +350,21 @@ type existsLiveTicketResponse struct {
err error
}
// existsLiveTicketsMsg handles a request for obtaining whether or not a ticket
// from a slice of tickets exists in the live tickets map of the blockchain stake
// database.
type existsLiveTicketsMsg struct {
hashes []*chainhash.Hash
reply chan existsLiveTicketsResponse
}
// existsLiveTicketsResponse is a response to the reply channel of a
// existsLiveTicketsMsg.
type existsLiveTicketsResponse struct {
Exists []bool
err error
}
// getCurrentTemplateMsg handles a request for the current mining block template.
type getCurrentTemplateMsg struct {
reply chan getCurrentTemplateResponse
@ -1985,6 +2000,13 @@ out:
err: err,
}
case existsLiveTicketsMsg:
exists, err := b.blockChain.CheckLiveTickets(msg.hashes)
msg.reply <- existsLiveTicketsResponse{
Exists: exists,
err: err,
}
case getCurrentTemplateMsg:
cur := deepCopyBlockTemplate(b.cachedCurrentTemplate)
msg.reply <- getCurrentTemplateResponse{
@ -2657,6 +2679,15 @@ func (b *blockManager) ExistsLiveTicket(hash *chainhash.Hash) (bool, error) {
return response.Exists, response.err
}
// ExistsLiveTickets returns whether or not tickets in a slice of tickets exist
// in the live tickets database.
func (b *blockManager) ExistsLiveTickets(hashes []*chainhash.Hash) ([]bool, error) {
reply := make(chan existsLiveTicketsResponse)
b.msgChan <- existsLiveTicketsMsg{hashes: hashes, reply: reply}
response := <-reply
return response.Exists, response.err
}
// GetCurrentTemplate gets the current block template for mining.
func (b *blockManager) GetCurrentTemplate() *BlockTemplate {
reply := make(chan getCurrentTemplateResponse)

View File

@ -392,9 +392,31 @@ func NewGetPeerInfoCmd() *GetPeerInfoCmd {
return &GetPeerInfoCmd{}
}
// GetRawMempoolTxTypeCmd defines the type used in the getrawmempool JSON-RPC
// command for the TxType command field.
type GetRawMempoolTxTypeCmd string
const (
// GRMAll indicates any type of transaction should be returned.
GRMAll GetRawMempoolTxTypeCmd = "add"
// GRMRegular indicates only regular transactions should be returned.
GRMRegular GetRawMempoolTxTypeCmd = "regular"
// GRMTickets indicates that only tickets should be returned.
GRMTickets GetRawMempoolTxTypeCmd = "tickets"
// GRMVotes indicates that only votes should be returned.
GRMVotes GetRawMempoolTxTypeCmd = "votes"
// GRMRevocations indicates that only revocations should be returned.
GRMRevocations GetRawMempoolTxTypeCmd = "revocations"
)
// GetRawMempoolCmd defines the getmempool JSON-RPC command.
type GetRawMempoolCmd struct {
Verbose *bool `jsonrpcdefault:"false"`
TxType *string
}
// NewGetRawMempoolCmd returns a new instance which can be used to issue a
@ -402,9 +424,10 @@ type GetRawMempoolCmd struct {
//
// The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value.
func NewGetRawMempoolCmd(verbose *bool) *GetRawMempoolCmd {
func NewGetRawMempoolCmd(verbose *bool, txType *string) *GetRawMempoolCmd {
return &GetRawMempoolCmd{
Verbose: verbose,
TxType: txType,
}
}

View File

@ -452,7 +452,7 @@ func TestChainSvrCmds(t *testing.T) {
return dcrjson.NewCmd("getrawmempool")
},
staticCmd: func() interface{} {
return dcrjson.NewGetRawMempoolCmd(nil)
return dcrjson.NewGetRawMempoolCmd(nil, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"getrawmempool","params":[],"id":1}`,
unmarshalled: &dcrjson.GetRawMempoolCmd{
@ -465,13 +465,27 @@ func TestChainSvrCmds(t *testing.T) {
return dcrjson.NewCmd("getrawmempool", false)
},
staticCmd: func() interface{} {
return dcrjson.NewGetRawMempoolCmd(dcrjson.Bool(false))
return dcrjson.NewGetRawMempoolCmd(dcrjson.Bool(false), nil)
},
marshalled: `{"jsonrpc":"1.0","method":"getrawmempool","params":[false],"id":1}`,
unmarshalled: &dcrjson.GetRawMempoolCmd{
Verbose: dcrjson.Bool(false),
},
},
{
name: "getrawmempool optional 2",
newCmd: func() (interface{}, error) {
return dcrjson.NewCmd("getrawmempool", false, "all")
},
staticCmd: func() interface{} {
return dcrjson.NewGetRawMempoolCmd(dcrjson.Bool(false), dcrjson.String("all"))
},
marshalled: `{"jsonrpc":"1.0","method":"getrawmempool","params":[false,"all"],"id":1}`,
unmarshalled: &dcrjson.GetRawMempoolCmd{
Verbose: dcrjson.Bool(false),
TxType: dcrjson.String("all"),
},
},
{
name: "getrawtransaction",
newCmd: func() (interface{}, error) {

View File

@ -33,6 +33,19 @@ func NewExistsLiveTicketCmd(txHash string) *ExistsLiveTicketCmd {
}
}
// ExistsLiveTicketsCmd defines the existslivetickets JSON-RPC command.
type ExistsLiveTicketsCmd struct {
TxHashBlob string
}
// NewExistsLiveTicketsCmd returns a new instance which can be used to issue an
// existslivetickets JSON-RPC command.
func NewExistsLiveTicketsCmd(txHashBlob string) *ExistsLiveTicketsCmd {
return &ExistsLiveTicketsCmd{
TxHashBlob: txHashBlob,
}
}
// GetCoinSupplyCmd defines the getcoinsupply JSON-RPC command.
type GetCoinSupplyCmd struct{}
@ -99,6 +112,7 @@ func init() {
MustRegisterCmd("existsaddress", (*ExistsAddressCmd)(nil), flags)
MustRegisterCmd("existsliveticket", (*ExistsLiveTicketCmd)(nil), flags)
MustRegisterCmd("existslivetickets", (*ExistsLiveTicketsCmd)(nil), flags)
MustRegisterCmd("getcoinsupply", (*GetCoinSupplyCmd)(nil), flags)
MustRegisterCmd("getstakedifficulty", (*GetStakeDifficultyCmd)(nil), flags)
MustRegisterCmd("missedtickets", (*MissedTicketsCmd)(nil), flags)

View File

@ -31,6 +31,8 @@ import (
"github.com/btcsuite/fastsha256"
"github.com/btcsuite/websocket"
"github.com/decred/bitset"
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg"
@ -40,6 +42,7 @@ import (
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
)
@ -150,6 +153,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"estimatefee": handleEstimateFee,
"existsaddress": handleExistsAddress,
"existsliveticket": handleExistsLiveTicket,
"existslivetickets": handleExistsLiveTickets,
"generate": handleGenerate,
"getaddednodeinfo": handleGetAddedNodeInfo,
"getbestblock": handleGetBestBlock,
@ -1496,6 +1500,56 @@ func handleExistsLiveTicket(s *rpcServer, cmd interface{},
return s.server.blockManager.ExistsLiveTicket(hash)
}
// handleExistsLiveTickets implements the existslivetickets command.
func handleExistsLiveTickets(s *rpcServer, cmd interface{},
closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ExistsLiveTicketsCmd)
txHashBlob, err := hex.DecodeString(c.TxHashBlob)
if err != nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCDecodeHexString,
Message: "bad transaction hash blob (unparseable)",
}
}
// It needs to be an exact number of hashes.
if len(txHashBlob)%32 != 0 {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCDecodeHexString,
Message: "bad transaction hash blob (bad length)",
}
}
hashesLen := len(txHashBlob) / 32
hashes := make([]*chainhash.Hash, hashesLen)
for i := 0; i < hashesLen; i++ {
hashes[i], err = chainhash.NewHash(
txHashBlob[i*chainhash.HashSize : (i+1)*chainhash.HashSize])
}
exists, err := s.server.blockManager.ExistsLiveTickets(hashes)
if err != nil {
return nil, err
}
if len(exists) != hashesLen {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCDatabase,
Message: "output of ExistsLiveTickets wrong size",
}
}
// Convert the slice of bools into a compacted set of bit flags.
set := bitset.NewBytes(hashesLen)
for i := range exists {
if exists[i] {
set.Set(i)
}
}
return hex.EncodeToString([]byte(set)), nil
}
// handleGenerate handles generate commands.
func handleGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
// Respond with an error if there are no addresses to pay the
@ -3179,6 +3233,32 @@ func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{
mp.RLock()
defer mp.RUnlock()
for _, desc := range descs {
// If we're interested in a specific transaction type,
// skip all the transactions which are not this type.
if c.TxType != nil {
switch *c.TxType {
case string(dcrjson.GRMRegular):
if desc.Type != stake.TxTypeRegular {
continue
}
case string(dcrjson.GRMTickets):
if desc.Type != stake.TxTypeSStx {
continue
}
case string(dcrjson.GRMVotes):
if desc.Type != stake.TxTypeSSGen {
continue
}
case string(dcrjson.GRMRevocations):
if desc.Type != stake.TxTypeSSRtx {
continue
}
case string(dcrjson.GRMAll):
fallthrough
default:
}
}
// Calculate the starting and current priority from the
// the tx's inputs. Use zeros if one or more of the
// input transactions can't be found for some reason.
@ -3215,9 +3295,34 @@ func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{
// The response is simply an array of the transaction hashes if the
// verbose flag is not set.
hashStrings := make([]string, len(descs))
for i := range hashStrings {
hashStrings[i] = descs[i].Tx.Sha().String()
descsLen := len(descs)
hashStrings := make([]string, 0, descsLen)
for i := 0; i < descsLen; i++ {
if c.TxType != nil {
switch *c.TxType {
case string(dcrjson.GRMRegular):
if descs[i].Type != stake.TxTypeRegular {
continue
}
case string(dcrjson.GRMTickets):
if descs[i].Type != stake.TxTypeSStx {
continue
}
case string(dcrjson.GRMVotes):
if descs[i].Type != stake.TxTypeSSGen {
continue
}
case string(dcrjson.GRMRevocations):
if descs[i].Type != stake.TxTypeSSRtx {
continue
}
case string(dcrjson.GRMAll):
fallthrough
default:
}
}
hashStrings = append(hashStrings, descs[i].Tx.Sha().String())
}
return hashStrings, nil