decred.org/dcrdex@v1.0.3/client/asset/btc/rpcclient.go (about) 1 // This code is available on the terms of the project LICENSE.md file, 2 // also available online at https://blueoakcouncil.org/license/1.0.0. 3 4 package btc 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/hex" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "strings" 14 "sync" 15 "sync/atomic" 16 "time" 17 18 "decred.org/dcrdex/client/asset" 19 "decred.org/dcrdex/dex" 20 "decred.org/dcrdex/dex/config" 21 dexbtc "decred.org/dcrdex/dex/networks/btc" 22 "github.com/btcsuite/btcd/btcec/v2" 23 "github.com/btcsuite/btcd/btcjson" 24 "github.com/btcsuite/btcd/btcutil" 25 "github.com/btcsuite/btcd/chaincfg" 26 "github.com/btcsuite/btcd/chaincfg/chainhash" 27 "github.com/btcsuite/btcd/wire" 28 "github.com/decred/dcrd/dcrjson/v4" // for dcrjson.RPCError returns from rpcclient 29 ) 30 31 const ( 32 methodGetBalances = "getbalances" 33 methodGetBalance = "getbalance" 34 methodListUnspent = "listunspent" 35 methodLockUnspent = "lockunspent" 36 methodListLockUnspent = "listlockunspent" 37 methodChangeAddress = "getrawchangeaddress" 38 methodNewAddress = "getnewaddress" 39 methodSignTx = "signrawtransactionwithwallet" 40 methodSignTxLegacy = "signrawtransaction" 41 methodUnlock = "walletpassphrase" 42 methodLock = "walletlock" 43 methodPrivKeyForAddress = "dumpprivkey" 44 methodGetTransaction = "gettransaction" 45 methodSendToAddress = "sendtoaddress" 46 methodSetTxFee = "settxfee" 47 methodGetWalletInfo = "getwalletinfo" 48 methodGetAddressInfo = "getaddressinfo" 49 methodListDescriptors = "listdescriptors" 50 methodValidateAddress = "validateaddress" 51 methodEstimateSmartFee = "estimatesmartfee" 52 methodSendRawTransaction = "sendrawtransaction" 53 methodGetTxOut = "gettxout" 54 methodGetBlock = "getblock" 55 methodGetBlockHash = "getblockhash" 56 methodGetBestBlockHash = "getbestblockhash" 57 methodGetRawMempool = "getrawmempool" 58 methodGetRawTransaction = "getrawtransaction" 59 methodGetBlockHeader = "getblockheader" 60 methodGetNetworkInfo = "getnetworkinfo" 61 methodGetBlockchainInfo = "getblockchaininfo" 62 methodFundRawTransaction = "fundrawtransaction" 63 methodListSinceBlock = "listsinceblock" 64 ) 65 66 // IsTxNotFoundErr will return true if the error indicates that the requested 67 // transaction is not known. The error must be dcrjson.RPCError with a numeric 68 // code equal to btcjson.ErrRPCNoTxInfo. WARNING: This is specific to errors 69 // from an RPC to a bitcoind (or clone) using dcrd's rpcclient! 70 func IsTxNotFoundErr(err error) bool { 71 // We are using dcrd's client with Bitcoin Core, so errors will be of type 72 // dcrjson.RPCError, but numeric codes should come from btcjson. 73 const errRPCNoTxInfo = int(btcjson.ErrRPCNoTxInfo) 74 var rpcErr *dcrjson.RPCError 75 return errors.As(err, &rpcErr) && int(rpcErr.Code) == errRPCNoTxInfo 76 } 77 78 // isMethodNotFoundErr will return true if the error indicates that the RPC 79 // method was not found by the RPC server. The error must be dcrjson.RPCError 80 // with a numeric code equal to btcjson.ErrRPCMethodNotFound.Code or a message 81 // containing "method not found". 82 func isMethodNotFoundErr(err error) bool { 83 var errRPCMethodNotFound = int(btcjson.ErrRPCMethodNotFound.Code) 84 var rpcErr *dcrjson.RPCError 85 return errors.As(err, &rpcErr) && 86 (int(rpcErr.Code) == errRPCMethodNotFound || 87 strings.Contains(strings.ToLower(rpcErr.Message), "method not found")) 88 } 89 90 // RawRequester defines decred's rpcclient RawRequest func where all RPC 91 // requests sent through. For testing, it can be satisfied by a stub. 92 type RawRequester interface { 93 RawRequest(context.Context, string, []json.RawMessage) (json.RawMessage, error) 94 } 95 96 // anylist is a list of RPC parameters to be converted to []json.RawMessage and 97 // sent via RawRequest. 98 type anylist []any 99 100 type rpcCore struct { 101 rpcConfig *RPCConfig 102 cloneParams *BTCCloneCFG 103 requesterV atomic.Value // RawRequester 104 segwit bool 105 decodeAddr dexbtc.AddressDecoder 106 stringAddr dexbtc.AddressStringer 107 legacyRawSends bool 108 minNetworkVersion uint64 109 log dex.Logger 110 chainParams *chaincfg.Params 111 omitAddressType bool 112 legacySignTx bool 113 booleanGetBlock bool 114 unlockSpends bool 115 116 deserializeTx func([]byte) (*wire.MsgTx, error) 117 serializeTx func(*wire.MsgTx) ([]byte, error) 118 hashTx func(*wire.MsgTx) *chainhash.Hash 119 numericGetRawTxRPC bool 120 manualMedianTime bool 121 addrFunc func() (btcutil.Address, error) 122 123 deserializeBlock func([]byte) (*wire.MsgBlock, error) 124 legacyValidateAddressRPC bool 125 omitRPCOptionsArg bool 126 privKeyFunc func(addr string) (*btcec.PrivateKey, error) 127 } 128 129 func (c *rpcCore) requester() RawRequester { 130 return c.requesterV.Load().(RawRequester) 131 } 132 133 // rpcClient is a bitcoind JSON RPC client that uses rpcclient.Client's 134 // RawRequest for wallet-related calls. 135 type rpcClient struct { 136 *rpcCore 137 ctx context.Context 138 descriptors bool // set on connect like ctx 139 } 140 141 var _ Wallet = (*rpcClient)(nil) 142 143 // newRPCClient is the constructor for a rpcClient. 144 func newRPCClient(cfg *rpcCore) *rpcClient { 145 return &rpcClient{rpcCore: cfg} 146 } 147 148 // ChainOK is for screening the chain field of the getblockchaininfo result. 149 func ChainOK(net dex.Network, str string) bool { 150 var chainStr string 151 switch net { 152 case dex.Mainnet: 153 chainStr = "main" 154 case dex.Testnet: 155 chainStr = "test" 156 case dex.Regtest: 157 chainStr = "reg" 158 } 159 return strings.Contains(str, chainStr) 160 } 161 162 func (wc *rpcClient) connect(ctx context.Context, _ *sync.WaitGroup) error { 163 wc.ctx = ctx 164 // Check the version. Do it here, so we can also diagnose a bad connection. 165 netVer, codeVer, err := wc.getVersion() 166 if err != nil { 167 return fmt.Errorf("error getting version: %w", err) 168 } 169 if netVer < wc.minNetworkVersion { 170 return fmt.Errorf("reported node version %d is less than minimum %d", netVer, wc.minNetworkVersion) 171 } 172 // TODO: codeVer is actually asset-dependent. Zcash, for example, is at 173 // 170100. So we're just lucking out here, really. 174 if codeVer < minProtocolVersion { 175 return fmt.Errorf("node software out of date. version %d is less than minimum %d", codeVer, minProtocolVersion) 176 } 177 chainInfo, err := wc.getBlockchainInfo() 178 if err != nil { 179 return fmt.Errorf("getblockchaininfo error: %w", err) 180 } 181 if !ChainOK(wc.cloneParams.Network, chainInfo.Chain) { 182 return errors.New("wrong net") 183 } 184 wiRes, err := wc.GetWalletInfo() 185 if err != nil { 186 return fmt.Errorf("getwalletinfo failure: %w", err) 187 } 188 wc.descriptors = wiRes.Descriptors 189 if wc.descriptors { 190 if netVer < minDescriptorVersion { 191 return fmt.Errorf("reported node version %d is less than minimum %d"+ 192 " for descriptor wallets", netVer, minDescriptorVersion) 193 } 194 wc.log.Debug("Using a descriptor wallet.") 195 } 196 return nil 197 } 198 199 // reconfigure attempts to reconfigure the rpcClient for the new settings. Live 200 // reconfiguration is only attempted if the new wallet type is walletTypeRPC. If 201 // the special_activelyUsed flag is set, reconfigure will fail if we can't 202 // validate ownership of the current deposit address. 203 func (wc *rpcClient) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { 204 if cfg.Type != wc.cloneParams.WalletCFG.Type { 205 restartRequired = true 206 return 207 } 208 if wc.ctx == nil || wc.ctx.Err() != nil { 209 return true, nil // not connected, ok to reconfigure, but restart required 210 } 211 212 parsedCfg := new(RPCWalletConfig) 213 if err = config.Unmapify(cfg.Settings, parsedCfg); err != nil { 214 return 215 } 216 217 // Check the RPC configuration. 218 newCfg := &parsedCfg.RPCConfig 219 if err = dexbtc.CheckRPCConfig(&newCfg.RPCConfig, wc.cloneParams.WalletInfo.Name, 220 wc.cloneParams.Network, wc.cloneParams.Ports); err != nil { 221 return 222 } 223 224 // If the RPC configuration has changed, try to update the client. 225 oldCfg := wc.rpcConfig 226 if *newCfg != *oldCfg { 227 cl, err := newRPCConnection(parsedCfg, wc.cloneParams.SingularWallet) 228 if err != nil { 229 return false, fmt.Errorf("error creating RPC client with new credentials: %v", err) 230 } 231 232 // Require restart if the wallet does not own or understand our current 233 // address. We can't use wc.ownsAddress because the rpcClient still has 234 // the old requester stored, so we'll call directly. 235 method := methodGetAddressInfo 236 if wc.legacyValidateAddressRPC { 237 method = methodValidateAddress 238 } 239 ai := new(GetAddressInfoResult) 240 if err := Call(wc.ctx, cl, method, anylist{currentAddress}, ai); err != nil { 241 return false, fmt.Errorf("error getting address info with new RPC client: %w", err) 242 } else if !ai.IsMine { 243 // If the wallet is in active use, check the supplied address. 244 if parsedCfg.ActivelyUsed { // deny reconfigure 245 return false, errors.New("cannot reconfigure to a new RPC wallet during active use") 246 } 247 // Allow reconfigure, but restart to trigger dep address refresh and 248 // full connect checks, which include the getblockchaininfo check. 249 return true, nil 250 } // else same wallet, skip full reconnect 251 252 chainInfo := new(GetBlockchainInfoResult) 253 if err := Call(wc.ctx, cl, methodGetBlockchainInfo, nil, chainInfo); err != nil { 254 return false, fmt.Errorf("%s: %w", methodGetBlockchainInfo, err) 255 } 256 if !ChainOK(wc.cloneParams.Network, chainInfo.Chain) { 257 return false, errors.New("wrong net") 258 } 259 260 wc.requesterV.Store(cl) 261 wc.rpcConfig = newCfg 262 263 // No restart required 264 } 265 return 266 } 267 268 // RawRequest passes the request to the wallet's RawRequester. 269 func (wc *rpcClient) RawRequest(ctx context.Context, method string, params []json.RawMessage) (json.RawMessage, error) { 270 return wc.requester().RawRequest(ctx, method, params) 271 } 272 273 // estimateSmartFee requests the server to estimate a fee level based on the 274 // given parameters. 275 func estimateSmartFee(ctx context.Context, rr RawRequester, confTarget uint64, mode *btcjson.EstimateSmartFeeMode) (*btcjson.EstimateSmartFeeResult, error) { 276 res := new(btcjson.EstimateSmartFeeResult) 277 return res, Call(ctx, rr, methodEstimateSmartFee, anylist{confTarget, mode}, res) 278 } 279 280 // SendRawTransactionLegacy broadcasts the transaction with an additional legacy 281 // boolean `allowhighfees` argument set to false. 282 func (wc *rpcClient) SendRawTransactionLegacy(tx *wire.MsgTx) (*chainhash.Hash, error) { 283 txBytes, err := wc.serializeTx(tx) 284 if err != nil { 285 return nil, err 286 } 287 return wc.callHashGetter(methodSendRawTransaction, anylist{ 288 hex.EncodeToString(txBytes), false}) 289 } 290 291 // SendRawTransaction broadcasts the transaction. 292 func (wc *rpcClient) SendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { 293 b, err := wc.serializeTx(tx) 294 if err != nil { 295 return nil, err 296 } 297 var txid string 298 err = wc.call(methodSendRawTransaction, anylist{hex.EncodeToString(b)}, &txid) 299 if err != nil { 300 return nil, err 301 } 302 return chainhash.NewHashFromStr(txid) 303 } 304 305 // sendRawTransaction sends the MsgTx. 306 func (wc *rpcClient) sendRawTransaction(tx *wire.MsgTx) (txHash *chainhash.Hash, err error) { 307 if wc.legacyRawSends { 308 txHash, err = wc.SendRawTransactionLegacy(tx) 309 } else { 310 txHash, err = wc.SendRawTransaction(tx) 311 } 312 if err != nil { 313 return nil, err 314 } 315 if !wc.unlockSpends { 316 return txHash, nil 317 } 318 319 // TODO: lockUnspent should really just take a []*OutPoint, since it doesn't 320 // need the value. 321 ops := make([]*Output, 0, len(tx.TxIn)) 322 for _, txIn := range tx.TxIn { 323 prevOut := &txIn.PreviousOutPoint 324 ops = append(ops, &Output{Pt: NewOutPoint(&prevOut.Hash, prevOut.Index)}) 325 } 326 if err := wc.lockUnspent(true, ops); err != nil { 327 wc.log.Warnf("error unlocking spent outputs: %v", err) 328 } 329 return txHash, nil 330 } 331 332 // getTxOut returns the transaction output info if it's unspent and 333 // nil, otherwise. 334 func (wc *rpcClient) getTxOut(txHash *chainhash.Hash, index uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) { 335 txOut, err := wc.getTxOutput(txHash, index) 336 if err != nil { 337 return nil, 0, fmt.Errorf("getTxOut error: %w", err) 338 } 339 if txOut == nil { 340 return nil, 0, nil 341 } 342 outputScript, _ := hex.DecodeString(txOut.ScriptPubKey.Hex) 343 // Check equivalence of pkScript and outputScript? 344 return wire.NewTxOut(int64(toSatoshi(txOut.Value)), outputScript), uint32(txOut.Confirmations), nil 345 } 346 347 // getTxOut returns the transaction output info if it's unspent and 348 // nil, otherwise. 349 func (wc *rpcClient) getTxOutput(txHash *chainhash.Hash, index uint32) (*btcjson.GetTxOutResult, error) { 350 // Note that we pass to call pointer to a pointer (&res) so that 351 // json.Unmarshal can nil the pointer if the method returns the JSON null. 352 var res *btcjson.GetTxOutResult 353 return res, wc.call(methodGetTxOut, anylist{txHash.String(), index, true}, 354 &res) 355 } 356 357 func (wc *rpcClient) callHashGetter(method string, args anylist) (*chainhash.Hash, error) { 358 var txid string 359 err := wc.call(method, args, &txid) 360 if err != nil { 361 return nil, err 362 } 363 return chainhash.NewHashFromStr(txid) 364 } 365 366 // getBlock fetches the MsgBlock. 367 func (wc *rpcClient) getBlock(h chainhash.Hash) (*wire.MsgBlock, error) { 368 var blkB dex.Bytes 369 args := anylist{h.String()} 370 if wc.booleanGetBlock { 371 args = append(args, false) 372 } else { 373 args = append(args, 0) 374 } 375 err := wc.call(methodGetBlock, args, &blkB) 376 if err != nil { 377 return nil, err 378 } 379 380 return wc.deserializeBlock(blkB) 381 } 382 383 // getBlockHash returns the hash of the block in the best block chain at the 384 // given height. 385 func (wc *rpcClient) getBlockHash(blockHeight int64) (*chainhash.Hash, error) { 386 return wc.callHashGetter(methodGetBlockHash, anylist{blockHeight}) 387 } 388 389 // getBestBlockHash returns the hash of the best block in the longest block 390 // chain (aka mainchain). 391 func (wc *rpcClient) getBestBlockHash() (*chainhash.Hash, error) { 392 return wc.callHashGetter(methodGetBestBlockHash, nil) 393 } 394 395 // getBestBlockHeight returns the height of the top mainchain block. 396 func (wc *rpcClient) getBestBlockHeader() (*BlockHeader, error) { 397 tipHash, err := wc.getBestBlockHash() 398 if err != nil { 399 return nil, err 400 } 401 hdr, _, err := wc.getBlockHeader(tipHash) 402 return hdr, err 403 } 404 405 // getBestBlockHeight returns the height of the top mainchain block. 406 func (wc *rpcClient) getBestBlockHeight() (int32, error) { 407 header, err := wc.getBestBlockHeader() 408 if err != nil { 409 return -1, err 410 } 411 return int32(header.Height), nil 412 } 413 414 // getChainStamp satisfies chainStamper for manual median time calculations. 415 func (wc *rpcClient) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) { 416 hdr, _, err := wc.getBlockHeader(blockHash) 417 if err != nil { 418 return 419 } 420 prevHash, err = chainhash.NewHashFromStr(hdr.PreviousBlockHash) 421 if err != nil { 422 return 423 } 424 return time.Unix(hdr.Time, 0).UTC(), prevHash, nil 425 } 426 427 // medianTime is the median time for the current best block. 428 func (wc *rpcClient) medianTime() (stamp time.Time, err error) { 429 tipHash, err := wc.getBestBlockHash() 430 if err != nil { 431 return 432 } 433 if wc.manualMedianTime { 434 return CalcMedianTime(func(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) { 435 hdr, _, err := wc.getBlockHeader(blockHash) 436 if err != nil { 437 return 438 } 439 prevHash, err = chainhash.NewHashFromStr(hdr.PreviousBlockHash) 440 if err != nil { 441 return 442 } 443 return time.Unix(hdr.Time, 0), prevHash, nil 444 }, tipHash) 445 } 446 hdr, err := wc.getRPCBlockHeader(tipHash) 447 if err != nil { 448 return 449 } 450 return time.Unix(hdr.MedianTime, 0).UTC(), nil 451 } 452 453 // GetRawMempool returns the hashes of all transactions in the memory pool. 454 func (wc *rpcClient) GetRawMempool() ([]*chainhash.Hash, error) { 455 var mempool []string 456 err := wc.call(methodGetRawMempool, nil, &mempool) 457 if err != nil { 458 return nil, err 459 } 460 461 // Convert received hex hashes to chainhash.Hash 462 hashes := make([]*chainhash.Hash, 0, len(mempool)) 463 for _, h := range mempool { 464 hash, err := chainhash.NewHashFromStr(h) 465 if err != nil { 466 return nil, err 467 } 468 hashes = append(hashes, hash) 469 } 470 return hashes, nil 471 } 472 473 // GetRawTransaction retrieves the MsgTx. 474 func (wc *rpcClient) GetRawTransaction(txHash *chainhash.Hash) (*wire.MsgTx, error) { 475 var txB dex.Bytes 476 args := anylist{txHash.String(), false} 477 if wc.numericGetRawTxRPC { 478 args[1] = 0 479 } 480 err := wc.call(methodGetRawTransaction, args, &txB) 481 if err != nil { 482 return nil, err 483 } 484 485 return wc.deserializeTx(txB) 486 } 487 488 // balances retrieves a wallet's balance details. 489 func (wc *rpcClient) balances() (*GetBalancesResult, error) { 490 var balances GetBalancesResult 491 return &balances, wc.call(methodGetBalances, nil, &balances) 492 } 493 494 // listUnspent retrieves a list of the wallet's UTXOs. 495 func (wc *rpcClient) listUnspent() ([]*ListUnspentResult, error) { 496 unspents := make([]*ListUnspentResult, 0) 497 // TODO: listunspent 0 9999999 []string{}, include_unsafe=false 498 return unspents, wc.call(methodListUnspent, anylist{uint8(0)}, &unspents) 499 } 500 501 // lockUnspent locks and unlocks outputs for spending. An output that is part of 502 // an order, but not yet spent, should be locked until spent or until the order 503 // is canceled or fails. 504 func (wc *rpcClient) lockUnspent(unlock bool, ops []*Output) error { 505 var rpcops []*RPCOutpoint // To clear all, this must be nil->null, not empty slice. 506 for _, op := range ops { 507 rpcops = append(rpcops, &RPCOutpoint{ 508 TxID: op.txHash().String(), 509 Vout: op.vout(), 510 }) 511 } 512 var success bool 513 err := wc.call(methodLockUnspent, anylist{unlock, rpcops}, &success) 514 if err == nil && !success { 515 return fmt.Errorf("lockunspent unsuccessful") 516 } 517 return err 518 } 519 520 // listLockUnspent returns a slice of outpoints for all unspent outputs marked 521 // as locked by a wallet. 522 func (wc *rpcClient) listLockUnspent() ([]*RPCOutpoint, error) { 523 var unspents []*RPCOutpoint 524 err := wc.call(methodListLockUnspent, nil, &unspents) 525 if err != nil { 526 return nil, err 527 } 528 if !wc.unlockSpends { 529 return unspents, nil 530 } 531 // This is quirky wallet software that does not unlock spent outputs, so 532 // we'll verify that each output is actually unspent. 533 var i int // for in-place filter 534 for _, utxo := range unspents { 535 var gtxo *btcjson.GetTxOutResult 536 err = wc.call(methodGetTxOut, anylist{utxo.TxID, utxo.Vout, true}, >xo) 537 if err != nil { 538 wc.log.Warnf("gettxout(%v:%d): %v", utxo.TxID, utxo.Vout, err) 539 continue 540 } 541 if gtxo != nil { 542 unspents[i] = utxo // unspent, keep it 543 i++ 544 continue 545 } 546 // actually spent, unlock 547 var success bool 548 op := []*RPCOutpoint{{ 549 TxID: utxo.TxID, 550 Vout: utxo.Vout, 551 }} 552 err = wc.call(methodLockUnspent, anylist{true, op}, &success) 553 if err != nil || !success { 554 wc.log.Warnf("lockunspent(unlocking %v:%d): success = %v, err = %v", 555 utxo.TxID, utxo.Vout, success, err) 556 continue 557 } 558 wc.log.Debugf("Unlocked spent outpoint %v:%d", utxo.TxID, utxo.Vout) 559 } 560 unspents = unspents[:i] 561 return unspents, nil 562 } 563 564 // changeAddress gets a new internal address from the wallet. The address will 565 // be bech32-encoded (P2WPKH). 566 func (wc *rpcClient) changeAddress() (btcutil.Address, error) { 567 var addrStr string 568 var err error 569 switch { 570 case wc.omitAddressType: 571 err = wc.call(methodChangeAddress, nil, &addrStr) 572 case wc.segwit: 573 err = wc.call(methodChangeAddress, anylist{"bech32"}, &addrStr) 574 default: 575 err = wc.call(methodChangeAddress, anylist{"legacy"}, &addrStr) 576 } 577 if err != nil { 578 return nil, err 579 } 580 return wc.decodeAddr(addrStr, wc.chainParams) 581 } 582 583 func (wc *rpcClient) externalAddress() (btcutil.Address, error) { 584 if wc.segwit { 585 return wc.address("bech32") 586 } 587 return wc.address("legacy") 588 } 589 590 // address is used internally for fetching addresses of various types from the 591 // wallet. 592 func (wc *rpcClient) address(aType string) (btcutil.Address, error) { 593 var addrStr string 594 args := anylist{""} 595 if !wc.omitAddressType { 596 args = append(args, aType) 597 } 598 err := wc.call(methodNewAddress, args, &addrStr) 599 if err != nil { 600 return nil, err 601 } 602 return wc.decodeAddr(addrStr, wc.chainParams) // we should consider returning a string 603 } 604 605 // signTx attempts to have the wallet sign the transaction inputs. 606 func (wc *rpcClient) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { 607 txBytes, err := wc.serializeTx(inTx) 608 if err != nil { 609 return nil, fmt.Errorf("tx serialization error: %w", err) 610 } 611 res := new(SignTxResult) 612 method := methodSignTx 613 if wc.legacySignTx { 614 method = methodSignTxLegacy 615 } 616 617 err = wc.call(method, anylist{hex.EncodeToString(txBytes)}, res) 618 if err != nil { 619 return nil, fmt.Errorf("tx signing error: %w", err) 620 } 621 if !res.Complete { 622 sep := "" 623 errMsg := "" 624 for _, e := range res.Errors { 625 errMsg += e.Error + sep 626 sep = ";" 627 } 628 return nil, fmt.Errorf("signing incomplete. %d signing errors encountered: %s", len(res.Errors), errMsg) 629 } 630 outTx, err := wc.deserializeTx(res.Hex) 631 if err != nil { 632 return nil, fmt.Errorf("error deserializing transaction response: %w", err) 633 } 634 return outTx, nil 635 } 636 637 func (wc *rpcClient) listDescriptors(private bool) (*listDescriptorsResult, error) { 638 descriptors := new(listDescriptorsResult) 639 return descriptors, wc.call(methodListDescriptors, anylist{private}, descriptors) 640 } 641 642 func (wc *rpcClient) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { 643 blockHash, err := wc.getBlockHash(int64(blockHeight)) 644 if err != nil { 645 return nil, fmt.Errorf("getBlockHash error: %w", err) 646 } 647 result := new(struct { 648 Transactions []btcjson.ListTransactionsResult `json:"transactions"` 649 }) 650 err = wc.call(methodListSinceBlock, anylist{blockHash.String()}, result) 651 if err != nil { 652 return nil, fmt.Errorf("listtransactions error: %w", err) 653 } 654 655 txs := make([]*ListTransactionsResult, 0, len(result.Transactions)) 656 for _, tx := range result.Transactions { 657 var blockHeight uint32 658 if tx.BlockHeight != nil { 659 blockHeight = uint32(*tx.BlockHeight) 660 } 661 txs = append(txs, &ListTransactionsResult{ 662 TxID: tx.TxID, 663 BlockHeight: blockHeight, 664 BlockTime: uint64(tx.BlockTime), 665 Fee: tx.Fee, 666 Send: tx.Category == "send", 667 }) 668 } 669 670 return txs, nil 671 } 672 673 // privKeyForAddress retrieves the private key associated with the specified 674 // address. 675 func (wc *rpcClient) privKeyForAddress(addr string) (*btcec.PrivateKey, error) { 676 // Use a specialized client's privKey function 677 if wc.privKeyFunc != nil { 678 return wc.privKeyFunc(addr) 679 } 680 // Descriptor wallets do not have dumpprivkey. 681 if !wc.descriptors { 682 var keyHex string 683 err := wc.call(methodPrivKeyForAddress, anylist{addr}, &keyHex) 684 if err != nil { 685 return nil, err 686 } 687 wif, err := btcutil.DecodeWIF(keyHex) 688 if err != nil { 689 return nil, err 690 } 691 return wif.PrivKey, nil 692 } 693 694 // With descriptor wallets, we have to get the address' descriptor from 695 // getaddressinfo, parse out its key origin (fingerprint of the master 696 // private key followed by derivation path to the address) and the pubkey of 697 // the address itself. Then we get the private key using listdescriptors 698 // private=true, which returns a set of master private keys and derivation 699 // paths, one of which corresponds to the fingerprint and path from 700 // getaddressinfo. When the parent master private key is identified, we 701 // derive the private key for the address. 702 ai := new(GetAddressInfoResult) 703 if err := wc.call(methodGetAddressInfo, anylist{addr}, ai); err != nil { 704 return nil, fmt.Errorf("getaddressinfo RPC failure: %w", err) 705 } 706 wc.log.Tracef("Address %v descriptor: %v", addr, ai.Descriptor) 707 desc, err := dexbtc.ParseDescriptor(ai.Descriptor) 708 if err != nil { 709 return nil, fmt.Errorf("failed to parse descriptor %q: %w", ai.Descriptor, err) 710 } 711 if desc.KeyOrigin == nil { 712 return nil, errors.New("address descriptor has no key origin") 713 } 714 // For addresses from imported private keys that have no derivation path in 715 // the key origin, we inspect private keys of type KeyWIFPriv. For addresses 716 // with a derivation path, we match KeyExtended private keys based on the 717 // master key fingerprint and derivation path. 718 fp, addrPath := desc.KeyOrigin.Fingerprint, desc.KeyOrigin.Steps 719 // Should match: 720 // fp, path = ai.HDMasterFingerprint, ai.HDKeyPath 721 // addrPath, _, err = dexbtc.ParsePath(path) 722 bareKey := len(addrPath) == 0 723 724 if desc.KeyFmt != dexbtc.KeyHexPub { 725 return nil, fmt.Errorf("not a hexadecimal pubkey: %v", desc.Key) 726 } 727 // The key was validated by ParseDescriptor, but check again. 728 addrPubKeyB, err := hex.DecodeString(desc.Key) 729 if err != nil { 730 return nil, fmt.Errorf("address pubkey not hexadecimal: %w", err) 731 } 732 addrPubKey, err := btcec.ParsePubKey(addrPubKeyB) 733 if err != nil { 734 return nil, fmt.Errorf("invalid pubkey for address: %w", err) 735 } 736 addrPubKeyC := addrPubKey.SerializeCompressed() // may or may not equal addrPubKeyB 737 738 // Get the private key descriptors. 739 masterDescs, err := wc.listDescriptors(true) 740 if err != nil { 741 return nil, fmt.Errorf("listdescriptors RPC failure: %w", err) 742 } 743 744 // We're going to decode a number of private keys that we need to zero. 745 var toClear []interface{ Zero() } 746 defer func() { 747 for _, k := range toClear { 748 k.Zero() 749 } 750 }() // surprisingly, much cleaner than making the loop body below into a function 751 deferZero := func(z interface{ Zero() }) { toClear = append(toClear, z) } 752 753 masters: 754 for _, d := range masterDescs.Descriptors { 755 masterDesc, err := dexbtc.ParseDescriptor(d.Descriptor) 756 if err != nil { 757 wc.log.Errorf("Failed to parse descriptor %q: %v", d.Descriptor, err) 758 continue // unexpected, but check the others 759 } 760 if bareKey { // match KeyHexPub -> KeyWIFPriv 761 if masterDesc.KeyFmt != dexbtc.KeyWIFPriv { 762 continue 763 } 764 wif, err := btcutil.DecodeWIF(masterDesc.Key) 765 if err != nil { 766 wc.log.Errorf("Invalid WIF private key: %v", err) 767 continue // ParseDescriptor already validated it, so shouldn't happen 768 } 769 if !bytes.Equal(addrPubKeyC, wif.PrivKey.PubKey().SerializeCompressed()) { 770 continue // not the one 771 } 772 return wif.PrivKey, nil 773 } 774 775 // match KeyHexPub -> [fingerprint/path]KeyExtended 776 if masterDesc.KeyFmt != dexbtc.KeyExtended { 777 continue 778 } 779 // Break the key into its parts and compute the fingerprint of the 780 // master private key. 781 xPriv, fingerprint, pathStr, isRange, err := dexbtc.ParseKeyExtended(masterDesc.Key) 782 if err != nil { 783 wc.log.Debugf("Failed to parse descriptor extended key: %v", err) 784 continue 785 } 786 deferZero(xPriv) 787 if fingerprint != fp { 788 continue 789 } 790 if !xPriv.IsPrivate() { // imported xpub with no private key? 791 wc.log.Debugf("Not an extended private key. Fingerprint: %v", fingerprint) 792 continue 793 } 794 // NOTE: After finding the xprv with the matching fingerprint, we could 795 // skip to checking the private key for a match instead of first 796 // matching the path. Let's just check the path too since fingerprint 797 // collision are possible, and the different address types are allowed 798 // to use descriptors with different fingerprints. 799 if !isRange { 800 continue // imported? 801 } 802 path, _, err := dexbtc.ParsePath(pathStr) 803 if err != nil { 804 wc.log.Debugf("Failed to parse descriptor extended key path %q: %v", pathStr, err) 805 continue 806 } 807 if len(addrPath) != len(path)+1 { // addrPath includes index of self 808 continue 809 } 810 for i := range path { 811 if addrPath[i] != path[i] { 812 continue masters // different path 813 } 814 } 815 816 // NOTE: We could conceivably cache the extended private key for this 817 // address range/branch, but it could be a security risk: 818 // childIdx := addrPath[len(addrPath)-1] 819 // branch, err := dexbtc.DeepChild(xPriv, path) 820 // child, err := branch.Derive(childIdx) 821 child, err := dexbtc.DeepChild(xPriv, addrPath) 822 if err != nil { 823 return nil, fmt.Errorf("address key derivation failed: %v", err) // any point in checking the rest? 824 } 825 deferZero(child) 826 privkey, err := child.ECPrivKey() 827 if err != nil { // only errors if the extended key is not private 828 return nil, err // hdkeychain.ErrNotPrivExtKey 829 } 830 // That's the private key, but do a final check that the pubkey matches 831 // the "pubkey" field of the getaddressinfo response. 832 pubkey := privkey.PubKey().SerializeCompressed() 833 if !bytes.Equal(pubkey, addrPubKeyC) { 834 wc.log.Warnf("Derived wrong pubkey for address %v from matching descriptor %v: %x != %x", 835 addr, d.Descriptor, pubkey, addrPubKey) 836 continue // theoretically could be a fingerprint collision (see KeyOrigin docs) 837 } 838 return privkey, nil 839 } 840 841 return nil, errors.New("no private key found for address") 842 } 843 844 // getWalletTransaction retrieves the JSON-RPC gettransaction result. 845 func (wc *rpcClient) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { 846 tx := new(GetTransactionResult) 847 err := wc.call(methodGetTransaction, anylist{txHash.String()}, tx) 848 if err != nil { 849 if IsTxNotFoundErr(err) { 850 return nil, asset.CoinNotFoundError 851 } 852 return nil, err 853 } 854 return tx, nil 855 } 856 857 // walletUnlock unlocks the wallet. 858 func (wc *rpcClient) walletUnlock(pw []byte) error { 859 // 100000000 comes from bitcoin-cli help walletpassphrase 860 return wc.call(methodUnlock, anylist{string(pw), 100000000}, nil) 861 } 862 863 // walletLock locks the wallet. 864 func (wc *rpcClient) walletLock() error { 865 return wc.call(methodLock, nil, nil) 866 } 867 868 // locked returns the wallet's lock state. 869 func (wc *rpcClient) locked() bool { 870 walletInfo, err := wc.GetWalletInfo() 871 if err != nil { 872 wc.log.Errorf("GetWalletInfo error: %w", err) 873 return false 874 } 875 if walletInfo.UnlockedUntil == nil { 876 // This wallet is not encrypted. 877 return false 878 } 879 880 return time.Unix(*walletInfo.UnlockedUntil, 0).Before(time.Now()) 881 } 882 883 // sendTxFeeEstimator returns the fee required to send tx using the provided 884 // feeRate. 885 func (wc *rpcClient) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (txfee uint64, err error) { 886 txBytes, err := wc.serializeTx(tx) 887 if err != nil { 888 return 0, fmt.Errorf("tx serialization error: %w", err) 889 } 890 args := anylist{hex.EncodeToString(txBytes)} 891 892 // 1e-5 = 1e-8 for satoshis * 1000 for kB. 893 feeRateOption := float64(feeRate) / 1e5 894 options := &btcjson.FundRawTransactionOpts{ 895 FeeRate: &feeRateOption, 896 } 897 if !wc.omitAddressType { 898 if wc.segwit { 899 options.ChangeType = &btcjson.ChangeTypeBech32 900 } else { 901 options.ChangeType = &btcjson.ChangeTypeLegacy 902 } 903 } 904 if subtract { 905 options.SubtractFeeFromOutputs = []int{0} 906 } 907 args = append(args, options) 908 909 var res struct { 910 TxBytes dex.Bytes `json:"hex"` 911 Fees float64 `json:"fee"` 912 } 913 err = wc.call(methodFundRawTransaction, args, &res) 914 if err != nil { 915 wc.log.Debugf("%s fundrawtranasaction error for args %+v: %v \n", wc.cloneParams.WalletInfo.Name, args, err) 916 // This is a work around for ZEC wallet, which does not support options 917 // argument for fundrawtransaction. 918 if wc.omitRPCOptionsArg { 919 var sendAmount uint64 920 for _, txOut := range tx.TxOut { 921 sendAmount += uint64(txOut.Value) 922 } 923 var bal float64 924 // args: "(dummy)" minconf includeWatchonly inZat 925 // Using default inZat = false for compatibility with ZCL. 926 if err := wc.call(methodGetBalance, anylist{"", 0, false}, &bal); err != nil { 927 return 0, err 928 } 929 if subtract && sendAmount <= toSatoshi(bal) { 930 return 0, errors.New("wallet does not support options") 931 } 932 } 933 return 0, fmt.Errorf("error calculating transaction fee: %w", err) 934 } 935 return toSatoshi(res.Fees), nil 936 } 937 938 // GetWalletInfo gets the getwalletinfo RPC result. 939 func (wc *rpcClient) GetWalletInfo() (*GetWalletInfoResult, error) { 940 wi := new(GetWalletInfoResult) 941 return wi, wc.call(methodGetWalletInfo, nil, wi) 942 } 943 944 // fingerprint returns an identifier for this wallet. Only HD wallets will have 945 // an identifier. Descriptor wallets will not. 946 func (wc *rpcClient) fingerprint() (string, error) { 947 walletInfo, err := wc.GetWalletInfo() 948 if err != nil { 949 return "", err 950 } 951 952 if walletInfo.HdSeedID == "" { 953 return "", fmt.Errorf("fingerprint not availble") 954 } 955 956 return walletInfo.HdSeedID, nil 957 } 958 959 // GetAddressInfo gets information about the given address by calling 960 // getaddressinfo RPC command. 961 func (wc *rpcClient) getAddressInfo(addr btcutil.Address, method string) (*GetAddressInfoResult, error) { 962 ai := new(GetAddressInfoResult) 963 addrStr, err := wc.stringAddr(addr, wc.chainParams) 964 if err != nil { 965 return nil, err 966 } 967 return ai, wc.call(method, anylist{addrStr}, ai) 968 } 969 970 // ownsAddress indicates if an address belongs to the wallet. 971 func (wc *rpcClient) ownsAddress(addr btcutil.Address) (bool, error) { 972 method := methodGetAddressInfo 973 if wc.legacyValidateAddressRPC { 974 method = methodValidateAddress 975 } 976 ai, err := wc.getAddressInfo(addr, method) 977 if err != nil { 978 return false, err 979 } 980 return ai.IsMine, nil 981 } 982 983 // syncStatus is information about the blockchain sync status. 984 func (wc *rpcClient) syncStatus() (*asset.SyncStatus, error) { 985 chainInfo, err := wc.getBlockchainInfo() 986 if err != nil { 987 return nil, fmt.Errorf("getblockchaininfo error: %w", err) 988 } 989 synced := !chainInfo.Syncing() 990 return &asset.SyncStatus{ 991 Synced: synced, 992 TargetHeight: uint64(chainInfo.Headers), 993 Blocks: uint64(chainInfo.Blocks), 994 }, nil 995 } 996 997 // swapConfirmations gets the number of confirmations for the specified coin ID 998 // by first checking for a unspent output, and if not found, searching indexed 999 // wallet transactions. 1000 func (wc *rpcClient) swapConfirmations(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (confs uint32, spent bool, err error) { 1001 // Check for an unspent output. 1002 txOut, err := wc.getTxOutput(txHash, vout) 1003 if err == nil && txOut != nil { 1004 return uint32(txOut.Confirmations), false, nil 1005 } 1006 // Check wallet transactions. 1007 tx, err := wc.getWalletTransaction(txHash) 1008 if err != nil { 1009 if IsTxNotFoundErr(err) { 1010 return 0, false, asset.CoinNotFoundError 1011 } 1012 return 0, false, err 1013 } 1014 return uint32(tx.Confirmations), true, nil 1015 } 1016 1017 // getBlockHeader gets the *rpcBlockHeader for the specified block hash. 1018 func (wc *rpcClient) getRPCBlockHeader(blockHash *chainhash.Hash) (*BlockHeader, error) { 1019 blkHeader := new(BlockHeader) 1020 err := wc.call(methodGetBlockHeader, 1021 anylist{blockHash.String(), true}, blkHeader) 1022 if err != nil { 1023 return nil, err 1024 } 1025 1026 return blkHeader, nil 1027 } 1028 1029 // getBlockHeader gets the *blockHeader for the specified block hash. It also 1030 // returns a bool value to indicate whether this block is a part of main chain. 1031 // For orphaned blocks header.Confirmations is negative (typically -1). 1032 func (wc *rpcClient) getBlockHeader(blockHash *chainhash.Hash) (header *BlockHeader, mainchain bool, err error) { 1033 hdr, err := wc.getRPCBlockHeader(blockHash) 1034 if err != nil { 1035 return nil, false, err 1036 } 1037 // RPC wallet must return negative confirmations number for orphaned blocks. 1038 mainchain = hdr.Confirmations >= 0 1039 return hdr, mainchain, nil 1040 } 1041 1042 // getBlockHeight gets the mainchain height for the specified block. Returns 1043 // error for orphaned blocks. 1044 func (wc *rpcClient) getBlockHeight(blockHash *chainhash.Hash) (int32, error) { 1045 hdr, _, err := wc.getBlockHeader(blockHash) 1046 if err != nil { 1047 return -1, err 1048 } 1049 if hdr.Height < 0 { 1050 return -1, fmt.Errorf("block is not a mainchain block") 1051 } 1052 return int32(hdr.Height), nil 1053 } 1054 1055 func (wc *rpcClient) peerCount() (uint32, error) { 1056 var r struct { 1057 Connections uint32 `json:"connections"` 1058 } 1059 err := wc.call(methodGetNetworkInfo, nil, &r) 1060 if err != nil { 1061 return 0, err 1062 } 1063 return r.Connections, nil 1064 } 1065 1066 // getBlockchainInfo sends the getblockchaininfo request and returns the result. 1067 func (wc *rpcClient) getBlockchainInfo() (*GetBlockchainInfoResult, error) { 1068 chainInfo := new(GetBlockchainInfoResult) 1069 err := wc.call(methodGetBlockchainInfo, nil, chainInfo) 1070 if err != nil { 1071 return nil, err 1072 } 1073 return chainInfo, nil 1074 } 1075 1076 // getVersion gets the current BTC network and protocol versions. 1077 func (wc *rpcClient) getVersion() (uint64, uint64, error) { 1078 r := &struct { 1079 Version uint64 `json:"version"` 1080 SubVersion string `json:"subversion"` 1081 ProtocolVersion uint64 `json:"protocolversion"` 1082 }{} 1083 err := wc.call(methodGetNetworkInfo, nil, r) 1084 if err != nil { 1085 return 0, 0, err 1086 } 1087 // TODO: We might consider checking getnetworkinfo's "subversion" field, 1088 // which is something like "/Satoshi:24.0.1/". 1089 wc.log.Debugf("Node at %v reports subversion \"%v\"", wc.rpcConfig.RPCBind, r.SubVersion) 1090 return r.Version, r.ProtocolVersion, nil 1091 } 1092 1093 // findRedemptionsInMempool attempts to find spending info for the specified 1094 // contracts by searching every input of all txs in the mempool. 1095 func (wc *rpcClient) findRedemptionsInMempool(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq) (discovered map[OutPoint]*FindRedemptionResult) { 1096 return FindRedemptionsInMempool(ctx, wc.log, reqs, wc.GetRawMempool, wc.GetRawTransaction, wc.segwit, wc.hashTx, wc.chainParams) 1097 } 1098 1099 func FindRedemptionsInMempool( 1100 ctx context.Context, 1101 log dex.Logger, 1102 reqs map[OutPoint]*FindRedemptionReq, 1103 getMempool func() ([]*chainhash.Hash, error), 1104 getTx func(txHash *chainhash.Hash) (*wire.MsgTx, error), 1105 segwit bool, 1106 hashTx func(*wire.MsgTx) *chainhash.Hash, 1107 chainParams *chaincfg.Params, 1108 1109 ) (discovered map[OutPoint]*FindRedemptionResult) { 1110 contractsCount := len(reqs) 1111 log.Debugf("finding redemptions for %d contracts in mempool", contractsCount) 1112 1113 discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs)) 1114 1115 var totalFound, totalCanceled int 1116 logAbandon := func(reason string) { 1117 // Do not remove the contracts from the findRedemptionQueue 1118 // as they could be subsequently redeemed in some mined tx(s), 1119 // which would be captured when a new tip is reported. 1120 if totalFound+totalCanceled > 0 { 1121 log.Debugf("%d redemptions found, %d canceled out of %d contracts in mempool", 1122 totalFound, totalCanceled, contractsCount) 1123 } 1124 log.Errorf("abandoning mempool redemption search for %d contracts because of %s", 1125 contractsCount-totalFound-totalCanceled, reason) 1126 } 1127 1128 mempoolTxs, err := getMempool() 1129 if err != nil { 1130 logAbandon(fmt.Sprintf("error retrieving transactions: %v", err)) 1131 return 1132 } 1133 1134 for _, txHash := range mempoolTxs { 1135 if ctx.Err() != nil { 1136 return nil 1137 } 1138 tx, err := getTx(txHash) 1139 if err != nil { 1140 logAbandon(fmt.Sprintf("getrawtransaction error for tx hash %v: %v", txHash, err)) 1141 return 1142 } 1143 newlyDiscovered := findRedemptionsInTxWithHasher(ctx, segwit, reqs, tx, chainParams, hashTx) 1144 for outPt, res := range newlyDiscovered { 1145 discovered[outPt] = res 1146 } 1147 1148 } 1149 return 1150 } 1151 1152 // searchBlockForRedemptions attempts to find spending info for the specified 1153 // contracts by searching every input of all txs in the provided block range. 1154 func (wc *rpcClient) searchBlockForRedemptions(ctx context.Context, reqs map[OutPoint]*FindRedemptionReq, blockHash chainhash.Hash) (discovered map[OutPoint]*FindRedemptionResult) { 1155 msgBlock, err := wc.getBlock(blockHash) 1156 if err != nil { 1157 wc.log.Errorf("RPC GetBlock error: %v", err) 1158 return 1159 } 1160 return SearchBlockForRedemptions(ctx, reqs, msgBlock, wc.segwit, wc.hashTx, wc.chainParams) 1161 } 1162 1163 func SearchBlockForRedemptions( 1164 ctx context.Context, 1165 reqs map[OutPoint]*FindRedemptionReq, 1166 msgBlock *wire.MsgBlock, 1167 segwit bool, 1168 hashTx func(*wire.MsgTx) *chainhash.Hash, 1169 chainParams *chaincfg.Params, 1170 ) (discovered map[OutPoint]*FindRedemptionResult) { 1171 1172 discovered = make(map[OutPoint]*FindRedemptionResult, len(reqs)) 1173 1174 for _, msgTx := range msgBlock.Transactions { 1175 newlyDiscovered := findRedemptionsInTxWithHasher(ctx, segwit, reqs, msgTx, chainParams, hashTx) 1176 for outPt, res := range newlyDiscovered { 1177 discovered[outPt] = res 1178 } 1179 } 1180 return 1181 } 1182 1183 // call is used internally to marshal parameters and send requests to the RPC 1184 // server via (*rpcclient.Client).RawRequest. If thing is non-nil, the result 1185 // will be marshaled into thing. 1186 func (wc *rpcClient) call(method string, args anylist, thing any) error { 1187 return Call(wc.ctx, wc.requester(), method, args, thing) 1188 } 1189 1190 func Call(ctx context.Context, r RawRequester, method string, args anylist, thing any) error { 1191 params := make([]json.RawMessage, 0, len(args)) 1192 for i := range args { 1193 p, err := json.Marshal(args[i]) 1194 if err != nil { 1195 return err 1196 } 1197 params = append(params, p) 1198 } 1199 1200 b, err := r.RawRequest(ctx, method, params) 1201 if err != nil { 1202 return fmt.Errorf("rawrequest (%v) error: %w", method, err) 1203 } 1204 if thing != nil { 1205 return json.Unmarshal(b, thing) 1206 } 1207 return nil 1208 }