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}, >xo) 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 }