decred.org/dcrdex@v1.0.3/client/asset/zec/transparent_rpc.go (about)

     1  package zec
     2  
     3  import (
     4  	"encoding/hex"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"decred.org/dcrdex/client/asset"
     9  	"decred.org/dcrdex/client/asset/btc"
    10  	"decred.org/dcrdex/dex"
    11  	dexzec "decred.org/dcrdex/dex/networks/zec"
    12  	"github.com/btcsuite/btcd/btcjson"
    13  	"github.com/btcsuite/btcd/btcutil"
    14  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    15  	"github.com/btcsuite/btcd/wire"
    16  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
    17  	"github.com/decred/dcrd/rpcclient/v8"
    18  )
    19  
    20  func listUnspent(c rpcCaller) (res []*btc.ListUnspentResult, err error) {
    21  	const minConf = 0
    22  	return res, c.CallRPC("listunspent", []any{minConf}, &res)
    23  }
    24  
    25  func lockUnspent(c rpcCaller, unlock bool, ops []*btc.Output) error {
    26  	var rpcops []*btc.RPCOutpoint // To clear all, this must be nil->null, not empty slice.
    27  	for _, op := range ops {
    28  		rpcops = append(rpcops, &btc.RPCOutpoint{
    29  			TxID: op.Pt.TxHash.String(),
    30  			Vout: op.Pt.Vout,
    31  		})
    32  	}
    33  	var success bool
    34  	err := c.CallRPC("lockunspent", []any{unlock, rpcops}, &success)
    35  	if err == nil && !success {
    36  		return fmt.Errorf("lockunspent unsuccessful")
    37  	}
    38  	return err
    39  }
    40  
    41  type zTx struct {
    42  	*dexzec.Tx
    43  	blockHash *chainhash.Hash
    44  }
    45  
    46  type GetTransactionResult struct {
    47  	Confirmations int64  `json:"confirmations"`
    48  	BlockHash     string `json:"blockhash"`
    49  	// BlockIndex    int64  `json:"blockindex"` // unused, consider commenting
    50  	BlockTime    uint64    `json:"blocktime"`
    51  	TxID         string    `json:"txid"`
    52  	Time         uint64    `json:"time"`
    53  	TimeReceived uint64    `json:"timereceived"`
    54  	Bytes        dex.Bytes `json:"hex"`
    55  }
    56  
    57  func getTransaction(c rpcCaller, txHash *chainhash.Hash) (*zTx, error) {
    58  	var tx GetTransactionResult
    59  	if err := c.CallRPC("gettransaction", []any{txHash.String()}, &tx); err != nil {
    60  		return nil, err
    61  	}
    62  	dexzecTx, err := dexzec.DeserializeTx(tx.Bytes)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("invalid block hash for transaction: %v", err)
    69  	}
    70  	zt := &zTx{
    71  		Tx:        dexzecTx,
    72  		blockHash: blockHash,
    73  	}
    74  	return zt, nil
    75  }
    76  
    77  func getRawTransaction(c rpcCaller, txHash *chainhash.Hash) ([]byte, error) {
    78  	var txB dex.Bytes
    79  	return txB, c.CallRPC("getrawtransaction", []any{txHash.String()}, &txB)
    80  }
    81  
    82  func signTxByRPC(c rpcCaller, inTx *dexzec.Tx) (*dexzec.Tx, error) {
    83  	txBytes, err := inTx.Bytes()
    84  	if err != nil {
    85  		return nil, fmt.Errorf("tx serialization error: %w", err)
    86  	}
    87  	res := new(btc.SignTxResult)
    88  
    89  	err = c.CallRPC("signrawtransaction", []any{hex.EncodeToString(txBytes)}, res)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("tx signing error: %w", err)
    92  	}
    93  	if !res.Complete {
    94  		sep := ""
    95  		errMsg := ""
    96  		for _, e := range res.Errors {
    97  			errMsg += e.Error + sep
    98  			sep = ";"
    99  		}
   100  		return nil, fmt.Errorf("signing incomplete. %d signing errors encountered: %s", len(res.Errors), errMsg)
   101  	}
   102  	outTx, err := dexzec.DeserializeTx(res.Hex)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("error deserializing transaction response: %w", err)
   105  	}
   106  	return outTx, nil
   107  }
   108  
   109  func callHashGetter(c rpcCaller, method string, args []any) (*chainhash.Hash, error) {
   110  	var txid string
   111  	err := c.CallRPC(method, args, &txid)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return chainhash.NewHashFromStr(txid)
   116  }
   117  
   118  func sendRawTransaction(c rpcCaller, tx *dexzec.Tx) (*chainhash.Hash, error) {
   119  	txB, err := tx.Bytes()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	return callHashGetter(c, "sendrawtransaction", []any{hex.EncodeToString(txB), false})
   124  }
   125  
   126  func dumpPrivKey(c rpcCaller, addr string) (*secp256k1.PrivateKey, error) {
   127  	var keyHex string
   128  	err := c.CallRPC("dumpprivkey", []any{addr}, &keyHex)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	wif, err := btcutil.DecodeWIF(keyHex)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	return wif.PrivKey, nil
   137  }
   138  
   139  func listLockUnspent(c rpcCaller, log dex.Logger) ([]*btc.RPCOutpoint, error) {
   140  	var unspents []*btc.RPCOutpoint
   141  	err := c.CallRPC("listlockunspent", nil, &unspents)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	// This is quirky wallet software that does not unlock spent outputs, so
   146  	// we'll verify that each output is actually unspent.
   147  	var i int // for in-place filter
   148  	for _, utxo := range unspents {
   149  		var gtxo *btcjson.GetTxOutResult
   150  		err = c.CallRPC("gettxout", []any{utxo.TxID, utxo.Vout, true}, &gtxo)
   151  		if err != nil {
   152  			log.Warnf("gettxout(%v:%d): %v", utxo.TxID, utxo.Vout, err)
   153  			continue
   154  		}
   155  		if gtxo != nil {
   156  			unspents[i] = utxo // unspent, keep it
   157  			i++
   158  			continue
   159  		}
   160  		// actually spent, unlock
   161  		var success bool
   162  		op := []*btc.RPCOutpoint{{
   163  			TxID: utxo.TxID,
   164  			Vout: utxo.Vout,
   165  		}}
   166  
   167  		err = c.CallRPC("lockunspent", []any{true, op}, &success)
   168  		if err != nil || !success {
   169  			log.Warnf("lockunspent(unlocking %v:%d): success = %v, err = %v",
   170  				utxo.TxID, utxo.Vout, success, err)
   171  			continue
   172  		}
   173  		log.Debugf("Unlocked spent outpoint %v:%d", utxo.TxID, utxo.Vout)
   174  	}
   175  	unspents = unspents[:i]
   176  	return unspents, nil
   177  }
   178  
   179  func getTxOut(c rpcCaller, txHash *chainhash.Hash, index uint32) (*wire.TxOut, uint32, error) {
   180  	// Note that we pass to call pointer to a pointer (&res) so that
   181  	// json.Unmarshal can nil the pointer if the method returns the JSON null.
   182  	var res *btcjson.GetTxOutResult
   183  	if err := c.CallRPC("gettxout", []any{txHash.String(), index, true}, &res); err != nil {
   184  		return nil, 0, err
   185  	}
   186  	if res == nil {
   187  		return nil, 0, nil
   188  	}
   189  	outputScript, err := hex.DecodeString(res.ScriptPubKey.Hex)
   190  	if err != nil {
   191  		return nil, 0, err
   192  	}
   193  	return wire.NewTxOut(int64(toZats(res.Value)), outputScript), uint32(res.Confirmations), nil
   194  }
   195  
   196  func getVersion(c rpcCaller) (uint64, uint64, error) {
   197  	r := &struct {
   198  		Version         uint64 `json:"version"`
   199  		SubVersion      string `json:"subversion"`
   200  		ProtocolVersion uint64 `json:"protocolversion"`
   201  	}{}
   202  	err := c.CallRPC("getnetworkinfo", nil, r)
   203  	if err != nil {
   204  		return 0, 0, err
   205  	}
   206  	return r.Version, r.ProtocolVersion, nil
   207  }
   208  
   209  func getBlockchainInfo(c rpcCaller) (*btc.GetBlockchainInfoResult, error) {
   210  	chainInfo := new(btc.GetBlockchainInfoResult)
   211  	err := c.CallRPC("getblockchaininfo", nil, chainInfo)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	return chainInfo, nil
   216  }
   217  
   218  func getBestBlockHeader(c rpcCaller) (*btc.BlockHeader, error) {
   219  	tipHash, err := getBestBlockHash(c)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	hdr, _, err := getVerboseBlockHeader(c, tipHash)
   224  	return hdr, err
   225  }
   226  
   227  func getBestBlockHash(c rpcCaller) (*chainhash.Hash, error) {
   228  	return callHashGetter(c, "getbestblockhash", nil)
   229  }
   230  
   231  func getVerboseBlockHeader(c rpcCaller, blockHash *chainhash.Hash) (header *btc.BlockHeader, mainchain bool, err error) {
   232  	hdr, err := getRPCBlockHeader(c, blockHash)
   233  	if err != nil {
   234  		return nil, false, err
   235  	}
   236  	// RPC wallet must return negative confirmations number for orphaned blocks.
   237  	mainchain = hdr.Confirmations >= 0
   238  	return hdr, mainchain, nil
   239  }
   240  
   241  func getBlockHeader(c rpcCaller, blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
   242  	var b dex.Bytes
   243  	err := c.CallRPC("getblockheader", []any{blockHash.String(), false}, &b)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	return dexzec.DeserializeBlockHeader(b)
   248  }
   249  
   250  func getRPCBlockHeader(c rpcCaller, blockHash *chainhash.Hash) (*btc.BlockHeader, error) {
   251  	blkHeader := new(btc.BlockHeader)
   252  	err := c.CallRPC("getblockheader", []any{blockHash.String(), true}, blkHeader)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	return blkHeader, nil
   257  }
   258  
   259  func getWalletTransaction(c rpcCaller, txHash *chainhash.Hash) (*GetTransactionResult, error) {
   260  	var tx GetTransactionResult
   261  	err := c.CallRPC("gettransaction", []any{txHash.String()}, &tx)
   262  	if err != nil {
   263  		if btc.IsTxNotFoundErr(err) {
   264  			return nil, asset.CoinNotFoundError
   265  		}
   266  		return nil, err
   267  	}
   268  	return &tx, nil
   269  }
   270  
   271  func getBalance(c rpcCaller) (bal uint64, err error) {
   272  	return bal, c.CallRPC("getbalance", []any{"", 0 /* minConf */, false /* includeWatchOnly */, true /* inZats */}, &bal)
   273  }
   274  
   275  type networkInfo struct {
   276  	Connections uint32 `json:"connections"`
   277  }
   278  
   279  func peerCount(c rpcCaller) (uint32, error) {
   280  	var r networkInfo
   281  	err := c.CallRPC("getnetworkinfo", nil, &r)
   282  	if err != nil {
   283  		return 0, codedError(errGetNetInfo, err)
   284  	}
   285  	return r.Connections, nil
   286  }
   287  
   288  func getBlockHeight(c rpcCaller, blockHash *chainhash.Hash) (int32, error) {
   289  	hdr, _, err := getVerboseBlockHeader(c, blockHash)
   290  	if err != nil {
   291  		return -1, err
   292  	}
   293  	if hdr.Height < 0 {
   294  		return -1, fmt.Errorf("block is not a mainchain block")
   295  	}
   296  	return int32(hdr.Height), nil
   297  }
   298  
   299  func getBlock(c rpcCaller, h chainhash.Hash) (*dexzec.Block, error) {
   300  	var blkB dex.Bytes
   301  	err := c.CallRPC("getblock", []any{h.String(), int64(0)}, &blkB)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	return dexzec.DeserializeBlock(blkB)
   307  }
   308  
   309  // getBestBlockHeight returns the height of the top mainchain block.
   310  func getBestBlockHeight(c rpcCaller) (int32, error) {
   311  	header, err := getBestBlockHeader(c)
   312  	if err != nil {
   313  		return -1, err
   314  	}
   315  	return int32(header.Height), nil
   316  }
   317  
   318  func getBlockHash(c rpcCaller, blockHeight int64) (*chainhash.Hash, error) {
   319  	return callHashGetter(c, "getblockhash", []any{blockHeight})
   320  }
   321  
   322  func getRawMempool(c rpcCaller) ([]*chainhash.Hash, error) {
   323  	var mempool []string
   324  	err := c.CallRPC("getrawmempool", nil, &mempool)
   325  	if err != nil {
   326  		return nil, translateRPCCancelErr(err)
   327  	}
   328  
   329  	// Convert received hex hashes to chainhash.Hash
   330  	hashes := make([]*chainhash.Hash, 0, len(mempool))
   331  	for _, h := range mempool {
   332  		hash, err := chainhash.NewHashFromStr(h)
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  		hashes = append(hashes, hash)
   337  	}
   338  	return hashes, nil
   339  }
   340  
   341  func getZecTransaction(c rpcCaller, txHash *chainhash.Hash) (*dexzec.Tx, error) {
   342  	txB, err := getRawTransaction(c, txHash)
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  
   347  	return dexzec.DeserializeTx(txB)
   348  }
   349  
   350  func translateRPCCancelErr(err error) error {
   351  	if err == nil {
   352  		return nil
   353  	}
   354  	if errors.Is(err, rpcclient.ErrRequestCanceled) {
   355  		err = asset.ErrRequestTimeout
   356  	}
   357  	return err
   358  }
   359  
   360  func getTxOutput(c rpcCaller, txHash *chainhash.Hash, index uint32) (*btcjson.GetTxOutResult, error) {
   361  	// Note that we pass to call pointer to a pointer (&res) so that
   362  	// json.Unmarshal can nil the pointer if the method returns the JSON null.
   363  	var res *btcjson.GetTxOutResult
   364  	return res, c.CallRPC("gettxout", []any{txHash.String(), index, true}, &res)
   365  }
   366  
   367  func syncStatus(c rpcCaller) (*asset.SyncStatus, error) {
   368  	chainInfo, err := getBlockchainInfo(c)
   369  	if err != nil {
   370  		return nil, newError(errGetChainInfo, "getblockchaininfo error: %w", err)
   371  	}
   372  	return &asset.SyncStatus{
   373  		Synced:       chainInfo.Blocks > 0 && !chainInfo.Syncing(),
   374  		TargetHeight: uint64(chainInfo.Headers),
   375  		Blocks:       uint64(chainInfo.Blocks),
   376  	}, nil
   377  }
   378  
   379  type listSinceBlockRes struct {
   380  	Transactions []btcjson.ListTransactionsResult `json:"transactions"`
   381  }
   382  
   383  func listSinceBlock(c rpcCaller, txHash *chainhash.Hash) ([]btcjson.ListTransactionsResult, error) {
   384  	var res listSinceBlockRes
   385  	if err := c.CallRPC("listsinceblock", []any{txHash.String()}, &res); err != nil {
   386  		return nil, err
   387  	}
   388  	return res.Transactions, nil
   389  }
   390  
   391  type walletInfoRes struct {
   392  	WalletVersion              int     `json:"walletversion"`
   393  	Balance                    float64 `json:"balance"`
   394  	UnconfirmedBalance         float64 `json:"unconfirmed_balance"`
   395  	ImmatureBalance            float64 `json:"immature_balance"`
   396  	ShieldedBalance            string  `json:"shielded_balance"`
   397  	ShieldedUnconfirmedBalance string  `json:"shielded_unconfirmed_balance"`
   398  	TxCount                    int     `json:"txcount"`
   399  	KeypoolOldest              int     `json:"keypoololdest"`
   400  	KeypoolSize                int     `json:"keypoolsize"`
   401  	PayTxFee                   float64 `json:"paytxfee"`
   402  	MnemonicSeedfp             string  `json:"mnemonic_seedfp"`
   403  	LegacySeedfp               string  `json:"legacy_seedfp,omitempty"`
   404  }
   405  
   406  func walletInfo(c rpcCaller) (*walletInfoRes, error) {
   407  	var res walletInfoRes
   408  	if err := c.CallRPC("getwalletinfo", nil, &res); err != nil {
   409  		return nil, err
   410  	}
   411  	return &res, nil
   412  }