From 18acc1194faeb111a34934f9d030aa77bfe1e12c Mon Sep 17 00:00:00 2001 From: cjepson Date: Mon, 22 Feb 2016 21:38:16 -0500 Subject: [PATCH] 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. --- blockchain/chain.go | 17 ++++++ blockmanager.go | 31 ++++++++++ dcrjson/chainsvrcmds.go | 25 +++++++- dcrjson/chainsvrcmds_test.go | 18 +++++- dcrjson/dcrdextcmds.go | 14 +++++ rpcserver.go | 111 ++++++++++++++++++++++++++++++++++- 6 files changed, 210 insertions(+), 6 deletions(-) diff --git a/blockchain/chain.go b/blockchain/chain.go index 73b82594..313a91fb 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -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. diff --git a/blockmanager.go b/blockmanager.go index 68e531b9..ead9a534 100644 --- a/blockmanager.go +++ b/blockmanager.go @@ -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) diff --git a/dcrjson/chainsvrcmds.go b/dcrjson/chainsvrcmds.go index af188b51..c746f42e 100644 --- a/dcrjson/chainsvrcmds.go +++ b/dcrjson/chainsvrcmds.go @@ -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, } } diff --git a/dcrjson/chainsvrcmds_test.go b/dcrjson/chainsvrcmds_test.go index f5b68765..101725e4 100644 --- a/dcrjson/chainsvrcmds_test.go +++ b/dcrjson/chainsvrcmds_test.go @@ -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) { diff --git a/dcrjson/dcrdextcmds.go b/dcrjson/dcrdextcmds.go index 3691879c..cdaa9c15 100644 --- a/dcrjson/dcrdextcmds.go +++ b/dcrjson/dcrdextcmds.go @@ -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) diff --git a/rpcserver.go b/rpcserver.go index feeb7153..8c96a973 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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