decred.org/dcrwallet/v3@v3.1.0/rpc/client/dcrd/calls.go (about) 1 // Copyright (c) 2019 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 // TODO: consistent error wrapping 6 7 package dcrd 8 9 import ( 10 "context" 11 "encoding/hex" 12 "encoding/json" 13 "strings" 14 15 "decred.org/dcrwallet/v3/errors" 16 "github.com/decred/dcrd/chaincfg/chainhash" 17 "github.com/decred/dcrd/dcrutil/v4" 18 "github.com/decred/dcrd/gcs/v4" 19 "github.com/decred/dcrd/txscript/v4/stdaddr" 20 "github.com/decred/dcrd/wire" 21 "github.com/jrick/bitset" 22 "github.com/jrick/wsrpc/v2" 23 "golang.org/x/sync/errgroup" 24 ) 25 26 // Caller provides a client interface to perform JSON-RPC remote procedure calls. 27 type Caller interface { 28 // Call performs the remote procedure call defined by method and 29 // waits for a response or a broken client connection. 30 // Args provides positional parameters for the call. 31 // Res must be a pointer to a struct, slice, or map type to unmarshal 32 // a result (if any), or nil if no result is needed. 33 Call(ctx context.Context, method string, res interface{}, args ...interface{}) error 34 } 35 36 // RPC provides methods for calling dcrd JSON-RPCs without exposing the details 37 // of JSON encoding. 38 type RPC struct { 39 Caller 40 } 41 42 // New creates a new RPC client instance from a caller. 43 func New(caller Caller) *RPC { 44 return &RPC{caller} 45 } 46 47 func hashSliceToStrings(hashes []*chainhash.Hash) []string { 48 s := make([]string, len(hashes)) 49 for i, h := range hashes { 50 s[i] = h.String() 51 } 52 return s 53 } 54 55 func addrSliceToStrings(addrs []stdaddr.Address) []string { 56 s := make([]string, len(addrs)) 57 for i, a := range addrs { 58 s[i] = a.String() 59 } 60 return s 61 } 62 63 // exists serves as a common entry point for all exists* RPCs which take a 64 // single JSON parameter (usually an array) and return a hex-encoded bitset. 65 func exists(ctx context.Context, r *RPC, method string, res *bitset.Bytes, param json.RawMessage) error { 66 var bitsetHex string 67 err := r.Call(ctx, method, &bitsetHex, param) 68 if err != nil { 69 return errors.E(errors.Op(method), err) 70 } 71 decoded, err := hex.DecodeString(bitsetHex) 72 if err != nil { 73 return errors.E(errors.Op(method), errors.Encoding, err) 74 } 75 *res = decoded 76 return nil 77 } 78 79 // ExistsLiveTicket returns whether a ticket identified by its hash is currently 80 // live and not immature. 81 func (r *RPC) ExistsLiveTicket(ctx context.Context, ticket *chainhash.Hash) (bool, error) { 82 const op errors.Op = "dcrd.ExistsLiveTicket" 83 var exists bool 84 err := r.Call(ctx, "existsliveticket", &exists, ticket.String()) 85 if err != nil { 86 return false, errors.E(op, err) 87 } 88 return exists, err 89 } 90 91 // UsedAddresses returns a bitset identifying whether each address has been 92 // publically used on the blockchain. This feature requires the optional dcrd 93 // existsaddress index to be enabled. 94 func (r *RPC) UsedAddresses(ctx context.Context, addrs []stdaddr.Address) (bitset.Bytes, error) { 95 const op errors.Op = "dcrd.UsedAddresses" 96 addrArray, err := json.Marshal(addrSliceToStrings(addrs)) 97 if err != nil { 98 return nil, errors.E(op, err) 99 } 100 var bits bitset.Bytes 101 err = exists(ctx, r, "existsaddresses", &bits, addrArray) 102 if err != nil { 103 return nil, errors.E(op, err) 104 } 105 return bits, nil 106 } 107 108 // ExistsLiveTickets returns a bitset identifying whether each ticket is 109 // currently live. 110 func (r *RPC) ExistsLiveTickets(ctx context.Context, tickets []*chainhash.Hash) (bitset.Bytes, error) { 111 const op errors.Op = "dcrd.ExistsLiveTickets" 112 ticketArray, err := json.Marshal(hashSliceToStrings(tickets)) 113 if err != nil { 114 return nil, errors.E(op, err) 115 } 116 var bits bitset.Bytes 117 err = exists(ctx, r, "existslivetickets", &bits, ticketArray) 118 if err != nil { 119 return nil, errors.E(op, err) 120 } 121 return bits, nil 122 } 123 124 // MempoolCount returns the count of a particular kind of transaction in mempool. 125 // Kind may be one of: 126 // 127 // "all" 128 // "regular" 129 // "tickets" 130 // "votes" 131 // "revocations" 132 func (r *RPC) MempoolCount(ctx context.Context, kind string) (int, error) { 133 const op errors.Op = "dcrd.MempoolCount" 134 // This is rather inefficient, as only the count is needed, not all 135 // matching hashes. 136 var hashStrings []string 137 err := r.Call(ctx, "getrawmempool", &hashStrings, false, kind) 138 if err != nil { 139 return 0, errors.E(op, err) 140 } 141 return len(hashStrings), nil 142 } 143 144 // getRawTransaction retrieve a transaction by hash" 145 func (r *RPC) getRawTransaction(ctx context.Context, hash string) (*wire.MsgTx, error) { 146 tx := new(wire.MsgTx) 147 err := r.Call(ctx, "getrawtransaction", unhex(tx), hash) 148 return tx, err 149 } 150 151 // GetMempoolTSpends retrieves all mempool tspends. 152 func (r *RPC) GetMempoolTSpends(ctx context.Context) ([]*wire.MsgTx, error) { 153 const op errors.Op = "dcrd.GetMempoolTSpends" 154 var hashStrings []string 155 err := r.Call(ctx, "getrawmempool", &hashStrings, false, "tspend") 156 if err != nil { 157 return nil, errors.E(op, err) 158 } 159 160 txs := make([]*wire.MsgTx, 0, len(hashStrings)) 161 for _, h := range hashStrings { 162 tx, err := r.getRawTransaction(ctx, h) 163 if err != nil { 164 return nil, errors.E(op, err) 165 } 166 txs = append(txs, tx) 167 } 168 return txs, nil 169 } 170 171 // PublishTransaction submits the transaction to dcrd mempool for acceptance. 172 // If accepted, the transaction is published to other peers. 173 // The transaction may not be an orphan. 174 func (r *RPC) PublishTransaction(ctx context.Context, tx *wire.MsgTx) error { 175 const op errors.Op = "dcrd.PublishTransaction" 176 return r.publishTransaction(ctx, op, tx) 177 } 178 179 func (r *RPC) publishTransaction(ctx context.Context, op errors.Op, tx *wire.MsgTx) error { 180 var b strings.Builder 181 b.Grow(tx.SerializeSize() * 2) 182 err := tx.Serialize(hex.NewEncoder(&b)) 183 if err != nil { 184 return errors.E(op, errors.Encoding, err) 185 } 186 err = r.Call(ctx, "sendrawtransaction", nil, b.String()) 187 if err != nil { 188 // Duplicate txs are not considered an error 189 var e *wsrpc.Error 190 if errors.As(err, &e) && e.Code == codeDuplicateTx { 191 return nil 192 } 193 return errors.E(op, err) 194 } 195 return nil 196 } 197 198 // PublishTransactions submits each transaction to dcrd mempool for acceptance. 199 // If accepted, the transaction is published to other peers. 200 // Transactions are sent in order and later transactions may spend outputs of 201 // previous transactions. 202 // No transaction may be an orphan. 203 func (r *RPC) PublishTransactions(ctx context.Context, txs ...*wire.MsgTx) error { 204 const op errors.Op = "dcrd.PublishTransactions" 205 206 // sendrawtransaction does not allow orphans, so we can not concurrently 207 // send transactions. All transaction sends are attempted, and the 208 // first non-nil error is returned. 209 var firstErr error 210 for _, tx := range txs { 211 err := r.publishTransaction(ctx, op, tx) 212 if err != nil && firstErr == nil { 213 firstErr = err 214 } 215 } 216 if firstErr != nil { 217 return errors.E(op, firstErr) 218 } 219 return nil 220 } 221 222 // Blocks returns the blocks for each block hash. 223 func (r *RPC) Blocks(ctx context.Context, blockHashes []*chainhash.Hash) ([]*wire.MsgBlock, error) { 224 const op errors.Op = "dcrd.Blocks" 225 226 blocks := make([]*wire.MsgBlock, len(blockHashes)) 227 var g errgroup.Group 228 for i := range blockHashes { 229 i := i 230 g.Go(func() error { 231 blocks[i] = new(wire.MsgBlock) 232 return r.Call(ctx, "getblock", unhex(blocks[i]), blockHashes[i].String(), false) 233 }) 234 } 235 err := g.Wait() 236 if err != nil { 237 return nil, errors.E(op, err) 238 } 239 return blocks, nil 240 } 241 242 // CFilterV2 returns the version 2 committed filter and the data required for 243 // verifying the inclusion proof of the cfilter for a block. 244 func (r *RPC) CFilterV2(ctx context.Context, blockHash *chainhash.Hash) (*gcs.FilterV2, uint32, []chainhash.Hash, error) { 245 const opf = "dcrd.CFilterV2(%v)" 246 247 var res cfilterV2Reply 248 err := r.Call(ctx, "getcfilterv2", &res, blockHash.String()) 249 if err != nil { 250 op := errors.Opf(opf, blockHash) 251 return nil, 0, nil, errors.E(op, err) 252 } 253 254 return res.Filter.Filter, res.ProofIndex, res.proofHashes(), nil 255 } 256 257 // filterProof is an alias to the same anonymous struct as wallet package's 258 // FilterProof struct. 259 type filterProof = struct { 260 Filter *gcs.FilterV2 261 ProofIndex uint32 262 Proof []chainhash.Hash 263 } 264 265 // CFiltersV2 returns the version 2 committed filters for blocks. 266 // If this method errors, a partial result of filter proofs may be returned, 267 // with nil filters if the query errored. 268 func (r *RPC) CFiltersV2(ctx context.Context, blockHashes []*chainhash.Hash) ([]filterProof, error) { 269 const opf = "dcrd.CFiltersV2(%v)" 270 271 filters := make([]filterProof, len(blockHashes)) 272 var g errgroup.Group 273 for i := range blockHashes { 274 i := i 275 g.Go(func() error { 276 var res cfilterV2Reply 277 err := r.Call(ctx, "getcfilterv2", &res, blockHashes[i].String()) 278 if err != nil { 279 op := errors.Opf(opf, blockHashes[i]) 280 err = errors.E(op, err) 281 return err 282 } 283 filters[i] = filterProof{ 284 Filter: res.Filter.Filter, 285 ProofIndex: res.ProofIndex, 286 Proof: res.proofHashes(), 287 } 288 return err 289 }) 290 } 291 err := g.Wait() 292 return filters, err 293 } 294 295 // Headers returns the block headers starting at the fork point between the 296 // client and the dcrd server identified by the client's block locators. 297 func (r *RPC) Headers(ctx context.Context, blockLocators []*chainhash.Hash, hashStop *chainhash.Hash) ([]*wire.BlockHeader, error) { 298 const op errors.Op = "dcrd.Headers" 299 300 res := &struct { 301 Headers *headers `json:"headers"` 302 }{ 303 Headers: new(headers), 304 } 305 err := r.Call(ctx, "getheaders", res, &hashes{blockLocators}, hashStop.String()) 306 if err != nil { 307 return nil, errors.E(op, err) 308 } 309 return res.Headers.Headers, nil 310 } 311 312 // LoadTxFilter loads or reloads the precise server-side transaction filter used 313 // for relevant transaction notifications and rescans. 314 // Addresses and outpoints are added to an existing filter if reload is false. 315 func (r *RPC) LoadTxFilter(ctx context.Context, reload bool, addrs []stdaddr.Address, outpoints []wire.OutPoint) error { 316 const op errors.Op = "dcrd.LoadTxFilter" 317 318 type outpoint struct { 319 Hash string `json:"hash"` 320 Index uint32 `json:"index"` 321 Tree int8 `json:"tree"` 322 } 323 outpointArray := make([]*outpoint, len(outpoints)) 324 for i, o := range outpoints { 325 outpointArray[i] = &outpoint{ 326 Hash: o.Hash.String(), 327 Index: o.Index, 328 Tree: o.Tree, 329 } 330 } 331 332 err := r.Call(ctx, "loadtxfilter", nil, reload, addrSliceToStrings(addrs), outpointArray) 333 if err != nil { 334 return errors.E(op, err) 335 } 336 return nil 337 } 338 339 // Rescan rescans the specified blocks in order, using the loaded transaction 340 // filter to determine which transactions are possibly relevant to the client. 341 // The save function is called for the discovered transactions from each block. 342 func (r *RPC) Rescan(ctx context.Context, blocks []chainhash.Hash, save func(block *chainhash.Hash, txs []*wire.MsgTx) error) error { 343 const op errors.Op = "dcrd.Rescan" 344 345 var res struct { 346 DiscoveredData []struct { 347 Hash string `json:"hash"` 348 Transactions []string `json:"transactions"` 349 } `json:"discovereddata"` 350 } 351 err := r.Call(ctx, "rescan", &res, &hashesContiguous{blocks}) 352 if err != nil { 353 return errors.E(op, err) 354 } 355 for _, d := range res.DiscoveredData { 356 blockHash, err := chainhash.NewHashFromStr(d.Hash) 357 if err != nil { 358 return errors.E(op, errors.Encoding, err) 359 } 360 txs := make([]*wire.MsgTx, 0, len(d.Transactions)) 361 for _, txHex := range d.Transactions { 362 tx := new(wire.MsgTx) 363 err := tx.Deserialize(hex.NewDecoder(strings.NewReader(txHex))) 364 if err != nil { 365 return errors.E(op, errors.Encoding, err) 366 } 367 txs = append(txs, tx) 368 } 369 err = save(blockHash, txs) 370 if err != nil { 371 return err 372 } 373 } 374 return nil 375 } 376 377 // StakeDifficulty returns the stake difficulty (AKA ticket price) of the next 378 // block. 379 func (r *RPC) StakeDifficulty(ctx context.Context) (dcrutil.Amount, error) { 380 const op errors.Op = "dcrd.StakeDifficulty" 381 382 var res struct { 383 Sdiff float64 `json:"nextstakedifficulty"` 384 } 385 err := r.Call(ctx, "getstakedifficulty", &res) 386 if err != nil { 387 return 0, errors.E(op, err) 388 } 389 sdiff, err := dcrutil.NewAmount(res.Sdiff) 390 if err != nil { 391 return 0, errors.E(op, err) 392 } 393 return sdiff, nil 394 }