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  }