From cd8ea2e30905b4c354dff0891abe63e04bbb511f Mon Sep 17 00:00:00 2001 From: cjepson Date: Tue, 1 Mar 2016 19:45:33 -0500 Subject: [PATCH] Add the new RPC function existsmempooltxs A new RPC function has been added to the daemon to quickly check for the existence of transactions in the mempool. It handles raw encoded hex containing transaction hashes and outputs a raw encoded hex of bit flags. This makes the output size as small as possible while speeding up the JSON encoding/decoding of the data. --- dcrjson/dcrdextcmds.go | 14 ++++++++ mempool.go | 24 +++++++++++++ rpcserver.go | 80 ++++++++++++++++++++++++++++++++++++++---- rpcserverhelp.go | 12 +++++-- 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/dcrjson/dcrdextcmds.go b/dcrjson/dcrdextcmds.go index cdaa9c15..0877d39f 100644 --- a/dcrjson/dcrdextcmds.go +++ b/dcrjson/dcrdextcmds.go @@ -46,6 +46,19 @@ func NewExistsLiveTicketsCmd(txHashBlob string) *ExistsLiveTicketsCmd { } } +// ExistsMempoolTxsCmd defines the existsmempooltxs JSON-RPC command. +type ExistsMempoolTxsCmd struct { + TxHashBlob string +} + +// NewExistsMempoolTxsCmd returns a new instance which can be used to issue an +// existslivetickets JSON-RPC command. +func NewExistsMempoolTxsCmd(txHashBlob string) *ExistsMempoolTxsCmd { + return &ExistsMempoolTxsCmd{ + TxHashBlob: txHashBlob, + } +} + // GetCoinSupplyCmd defines the getcoinsupply JSON-RPC command. type GetCoinSupplyCmd struct{} @@ -113,6 +126,7 @@ func init() { MustRegisterCmd("existsaddress", (*ExistsAddressCmd)(nil), flags) MustRegisterCmd("existsliveticket", (*ExistsLiveTicketCmd)(nil), flags) MustRegisterCmd("existslivetickets", (*ExistsLiveTicketsCmd)(nil), flags) + MustRegisterCmd("existsmempooltxs", (*ExistsMempoolTxsCmd)(nil), flags) MustRegisterCmd("getcoinsupply", (*GetCoinSupplyCmd)(nil), flags) MustRegisterCmd("getstakedifficulty", (*GetStakeDifficultyCmd)(nil), flags) MustRegisterCmd("missedtickets", (*MissedTicketsCmd)(nil), flags) diff --git a/mempool.go b/mempool.go index 3043091a..7fbf33c1 100644 --- a/mempool.go +++ b/mempool.go @@ -925,6 +925,30 @@ func (mp *txMemPool) HaveTransaction(hash *chainhash.Hash) bool { return mp.haveTransaction(hash) } +// haveTransactions returns whether or not the passed transactions already exist +// in the main pool or in the orphan pool. +// +// This function MUST be called with the mempool lock held (for reads). +func (mp *txMemPool) haveTransactions(hashes []*chainhash.Hash) []bool { + have := make([]bool, len(hashes)) + for i := range hashes { + have[i] = mp.haveTransaction(hashes[i]) + } + return have +} + +// HaveTransactions returns whether or not the passed transactions already exist +// in the main pool or in the orphan pool. +// +// This function is safe for concurrent access. +func (mp *txMemPool) HaveTransactions(hashes []*chainhash.Hash) []bool { + // Protect concurrent access. + mp.RLock() + defer mp.RUnlock() + + return mp.haveTransactions(hashes) +} + // removeTransaction is the internal function which implements the public // RemoveTransaction. See the comment for RemoveTransaction for more details. // diff --git a/rpcserver.go b/rpcserver.go index 3bce070c..ee07f79b 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -157,6 +157,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "existsaddress": handleExistsAddress, "existsliveticket": handleExistsLiveTicket, "existslivetickets": handleExistsLiveTickets, + "existsmempooltxs": handleExistsMempoolTxs, "generate": handleGenerate, "getaddednodeinfo": handleGetAddedNodeInfo, "getbestblock": handleGetBestBlock, @@ -1515,16 +1516,18 @@ func handleExistsLiveTickets(s *rpcServer, cmd interface{}, txHashBlob, err := hex.DecodeString(c.TxHashBlob) if err != nil { return nil, &dcrjson.RPCError{ - Code: dcrjson.ErrRPCDecodeHexString, - Message: "bad transaction hash blob (unparseable)", + Code: dcrjson.ErrRPCDecodeHexString, + Message: fmt.Sprintf("bad ticket hash blob (unparseable): %v", + err.Error()), } } // 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)", + Code: dcrjson.ErrRPCDecodeHexString, + Message: fmt.Sprintf("bad ticket hash blob (bad length): %v", + len(txHashBlob)), } } @@ -1533,6 +1536,13 @@ func handleExistsLiveTickets(s *rpcServer, cmd interface{}, for i := 0; i < hashesLen; i++ { hashes[i], err = chainhash.NewHash( txHashBlob[i*chainhash.HashSize : (i+1)*chainhash.HashSize]) + if err != nil { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCDecodeHexString, + Message: fmt.Sprintf("bad ticket hash: %v", + err.Error()), + } + } } exists, err := s.server.blockManager.ExistsLiveTickets(hashes) @@ -1541,8 +1551,66 @@ func handleExistsLiveTickets(s *rpcServer, cmd interface{}, } if len(exists) != hashesLen { return nil, &dcrjson.RPCError{ - Code: dcrjson.ErrRPCDatabase, - Message: "output of ExistsLiveTickets wrong size", + Code: dcrjson.ErrRPCDatabase, + Message: fmt.Sprintf("output of ExistsLiveTickets wrong size "+ + "(want %v, got %v)", hashesLen, len(exists)), + } + } + + // 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 +} + +// handleExistsMempoolTxs implements the existsmempooltxs command. +func handleExistsMempoolTxs(s *rpcServer, cmd interface{}, + closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*dcrjson.ExistsMempoolTxsCmd) + + txHashBlob, err := hex.DecodeString(c.TxHashBlob) + if err != nil { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCDecodeHexString, + Message: fmt.Sprintf("bad transaction hash blob (unparseable): %v", + err.Error()), + } + } + + // It needs to be an exact number of hashes. + if len(txHashBlob)%32 != 0 { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCDecodeHexString, + Message: fmt.Sprintf("bad transaction hash blob (bad length): %v", + len(txHashBlob)), + } + } + + 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]) + if err != nil { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCDecodeHexString, + Message: fmt.Sprintf("bad transaction hash: %v", + err.Error()), + } + } + } + + exists := s.server.txMemPool.HaveTransactions(hashes) + if len(exists) != hashesLen { + return nil, &dcrjson.RPCError{ + Code: dcrjson.ErrRPCDatabase, + Message: fmt.Sprintf("output of ExistsMempoolTxs wrong size "+ + "(want %v, got %v)", hashesLen, len(exists)), } } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index cb627c35..8d5091da 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -153,9 +153,14 @@ var helpDescsEnUS = map[string]string{ "existsliveticket--result0": "Bool showing if address exists in the live ticket database or not", // ExistsLiveTicketsCmd help. - "existslivetickets--synopsis": "Test for the existance of the provided tickets", + "existslivetickets--synopsis": "Test for the existance of the provided tickets in the live ticket map", "existslivetickets-txhashblob": "Blob containing the hashes to check", - "existslivetickets--result0": "Bool showing if address exists in the live ticket database or not", + "existslivetickets--result0": "Bool blob showing if ticket exists in the live ticket database or not", + + // ExistsMempoolTxsCmd help. + "existsmempooltxs--synopsis": "Test for the existance of the provided txs in the mempool", + "existsmempooltxs-txhashblob": "Blob containing the hashes to check", + "existsmempooltxs--result0": "Bool blob showing if txs exist in the mempool or not", // GenerateCmd help "generate--synopsis": "Generates a set number of blocks (simnet or regtest only) and returns a JSON\n" + @@ -669,7 +674,8 @@ var rpcResultTypes = map[string][]interface{}{ "estimatefee": []interface{}{(*float64)(nil)}, "existsaddress": []interface{}{(*bool)(nil)}, "existsliveticket": []interface{}{(*bool)(nil)}, - "existslivetickets": []interface{}{(*bool)(nil)}, + "existslivetickets": []interface{}{(*string)(nil)}, + "existsmempooltxs": []interface{}{(*string)(nil)}, "getaddednodeinfo": []interface{}{(*[]string)(nil), (*[]dcrjson.GetAddedNodeInfoResult)(nil)}, "getbestblock": []interface{}{(*dcrjson.GetBestBlockResult)(nil)}, "generate": []interface{}{(*[]string)(nil)},