decred.org/dcrdex@v1.0.3/client/asset/btc/electrum_client.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 "crypto/tls" 10 "crypto/x509" 11 "encoding/hex" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "math/rand" 16 "net" 17 "sort" 18 "strconv" 19 "strings" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 "decred.org/dcrdex/client/asset" 25 "decred.org/dcrdex/client/asset/btc/electrum" 26 "decred.org/dcrdex/dex" 27 "decred.org/dcrdex/dex/config" 28 dexbtc "decred.org/dcrdex/dex/networks/btc" 29 "github.com/btcsuite/btcd/btcec/v2" 30 "github.com/btcsuite/btcd/btcutil" 31 "github.com/btcsuite/btcd/btcutil/psbt" 32 "github.com/btcsuite/btcd/chaincfg" 33 "github.com/btcsuite/btcd/chaincfg/chainhash" 34 "github.com/btcsuite/btcd/txscript" 35 "github.com/btcsuite/btcd/wire" 36 ) 37 38 type electrumWalletClient interface { 39 FeeRate(ctx context.Context, confTarget int64) (int64, error) 40 Broadcast(ctx context.Context, tx []byte) (string, error) 41 AddLocalTx(ctx context.Context, tx []byte) (string, error) 42 RemoveLocalTx(ctx context.Context, txid string) error 43 Commands(ctx context.Context) ([]string, error) 44 GetInfo(ctx context.Context) (*electrum.GetInfoResult, error) 45 GetServers(ctx context.Context) ([]*electrum.GetServersResult, error) 46 GetBalance(ctx context.Context) (*electrum.Balance, error) 47 ListUnspent(ctx context.Context) ([]*electrum.ListUnspentResult, error) 48 FreezeUTXO(ctx context.Context, txid string, out uint32) error 49 UnfreezeUTXO(ctx context.Context, txid string, out uint32) error 50 CreateNewAddress(ctx context.Context) (string, error) 51 GetUnusedAddress(ctx context.Context) (string, error) 52 CheckAddress(ctx context.Context, addr string) (valid, mine bool, err error) 53 SignTx(ctx context.Context, walletPass string, psbtB64 string) ([]byte, error) 54 GetPrivateKeys(ctx context.Context, walletPass, addr string) (string, error) 55 GetWalletTxConfs(ctx context.Context, txid string) (int, error) // shortcut if owned 56 GetRawTransaction(ctx context.Context, txid string) ([]byte, error) // wallet method 57 GetAddressHistory(ctx context.Context, addr string) ([]*electrum.GetAddressHistoryResult, error) 58 GetAddressUnspent(ctx context.Context, addr string) ([]*electrum.GetAddressUnspentResult, error) 59 OnchainHistory(ctx context.Context, from, to int64) ([]electrum.TransactionResult, error) 60 Version(ctx context.Context) (string, error) 61 SetIncludeIgnoreWarnings(include bool) 62 } 63 64 type electrumNetworkClient interface { 65 Done() <-chan struct{} 66 Shutdown() 67 Features(ctx context.Context) (*electrum.ServerFeatures, error) 68 GetTransaction(ctx context.Context, txid string) (*electrum.GetTransactionResult, error) 69 BlockHeader(ctx context.Context, height uint32) (string, error) 70 BlockHeaders(ctx context.Context, startHeight, count uint32) (*electrum.GetBlockHeadersResult, error) 71 } 72 73 type electrumWallet struct { 74 log dex.Logger 75 chainParams *chaincfg.Params 76 decodeAddr dexbtc.AddressDecoder 77 stringAddr dexbtc.AddressStringer 78 rpcCfg *RPCConfig // supports live reconfigure check 79 wallet electrumWalletClient 80 chainV atomic.Value // electrumNetworkClient 81 segwit bool 82 83 // ctx is set on connect, and used in asset.Wallet and btc.Wallet interface 84 // method implementations that have no ctx arg yet (refactoring TODO). 85 ctx context.Context 86 87 lockedOutpointsMtx sync.RWMutex 88 lockedOutpoints map[OutPoint]struct{} 89 90 pwMtx sync.RWMutex 91 pw string 92 unlocked bool 93 } 94 95 func (ew *electrumWallet) chain() electrumNetworkClient { 96 cl, _ := ew.chainV.Load().(electrumNetworkClient) 97 return cl 98 } 99 100 func (ew *electrumWallet) resetChain(cl electrumNetworkClient) { 101 ew.chainV.Store(cl) 102 } 103 104 type electrumWalletConfig struct { 105 params *chaincfg.Params 106 log dex.Logger 107 addrDecoder dexbtc.AddressDecoder 108 addrStringer dexbtc.AddressStringer 109 segwit bool // indicates if segwit addresses are expected from requests 110 rpcCfg *RPCConfig 111 } 112 113 func newElectrumWallet(ew electrumWalletClient, cfg *electrumWalletConfig) *electrumWallet { 114 addrDecoder := cfg.addrDecoder 115 if addrDecoder == nil { 116 addrDecoder = btcutil.DecodeAddress 117 } 118 119 addrStringer := cfg.addrStringer 120 if addrStringer == nil { 121 addrStringer = func(addr btcutil.Address, _ *chaincfg.Params) (string, error) { 122 return addr.String(), nil 123 } 124 } 125 126 return &electrumWallet{ 127 log: cfg.log, 128 chainParams: cfg.params, 129 decodeAddr: addrDecoder, 130 stringAddr: addrStringer, 131 wallet: ew, 132 segwit: cfg.segwit, 133 // TODO: remove this when all interface methods are given a Context. In 134 // the meantime, init with a valid sentry context until connect(). 135 ctx: context.TODO(), 136 // chain is constructed after wallet connects to a server 137 lockedOutpoints: make(map[OutPoint]struct{}), 138 rpcCfg: cfg.rpcCfg, 139 } 140 } 141 142 // BEGIN unimplemented asset.Wallet methods 143 144 func (ew *electrumWallet) RawRequest(context.Context, string, []json.RawMessage) (json.RawMessage, error) { 145 return nil, errors.New("not available") // and not used 146 } 147 148 // END unimplemented methods 149 150 // Prefer the SSL port if set, but allow TCP if that's all it has. 151 func bestAddr(host string, gsr *electrum.GetServersResult) (string, *tls.Config) { 152 if gsr.SSL != 0 { 153 rootCAs, _ := x509.SystemCertPool() 154 tlsConfig := &tls.Config{ 155 InsecureSkipVerify: true, 156 RootCAs: rootCAs, 157 // MinVersion: tls.VersionTLS12, 158 ServerName: host, 159 } 160 port := strconv.FormatUint(uint64(gsr.SSL), 10) 161 return net.JoinHostPort(host, port), tlsConfig 162 } else if gsr.TCP != 0 { 163 port := strconv.FormatUint(uint64(gsr.TCP), 10) 164 return net.JoinHostPort(host, port), nil 165 } 166 return "", nil 167 } 168 169 // Look up the port of the active server via getservers and return a "host:port" 170 // formatted address. A non-nil tls.Config is returned if an SSL port. A empty 171 // host input will pick a random SSL host. 172 func (ew *electrumWallet) connInfo(ctx context.Context, host string) (addr string, tlsConfig *tls.Config, err error) { 173 servers, err := ew.wallet.GetServers(ctx) 174 if err != nil { 175 return "", nil, err 176 } 177 var wsrv *electrum.GetServersResult 178 if host == "" { // pick a random SSL host 179 var sslServers []*electrum.GetServersResult 180 for _, srv := range servers { 181 if srv.SSL != 0 { 182 sslServers = append(sslServers, srv) 183 } 184 } 185 // TODO: allow non-tcp onion hosts 186 if len(sslServers) == 0 { 187 return "", nil, errors.New("no SSL servers") 188 } 189 wsrv = sslServers[rand.Intn(len(sslServers))] 190 } else { 191 for _, srv := range servers { 192 if srv.Host == host { 193 wsrv = srv 194 break 195 } 196 } 197 if wsrv == nil { 198 return "", nil, fmt.Errorf("Electrum wallet server %q not found in getservers result", host) 199 } 200 } 201 addr, tlsConfig = bestAddr(host, wsrv) 202 if addr == "" { 203 return "", nil, fmt.Errorf("no suitable address for host %v", host) 204 } 205 return addr, tlsConfig, nil 206 } 207 208 // part of btc.Wallet interface 209 func (ew *electrumWallet) connect(ctx context.Context, wg *sync.WaitGroup) error { 210 // Helper to get a host:port string and connection options for a host name. 211 connInfo := func(host string) (addr string, srvOpts *electrum.ConnectOpts, err error) { 212 addr, tlsConfig, err := ew.connInfo(ctx, host) 213 if err != nil { 214 return "", nil, fmt.Errorf("no suitable address for host %q: %w", host, err) 215 } 216 srvOpts = &electrum.ConnectOpts{ 217 // TorProxy: TODO 218 TLSConfig: tlsConfig, // may be nil if not ssl host 219 DebugLogger: ew.log.Debugf, 220 } 221 return addr, srvOpts, nil 222 } 223 224 info, err := ew.wallet.GetInfo(ctx) // also initial connectivity test with the external wallet 225 if err != nil { 226 return err 227 } 228 if !info.Connected || info.Server == "" { 229 return errors.New("Electrum wallet has no server connections") 230 } 231 232 // Determine if segwit expectation is met. Request and decode an address, 233 // then compare with the segwit config field. 234 addr, err := ew.wallet.GetUnusedAddress(ctx) 235 if err != nil { 236 return err 237 } 238 address, err := ew.decodeAddr(addr, ew.chainParams) 239 if err != nil { 240 return err 241 } 242 _, segwit := address.(interface { 243 WitnessVersion() byte 244 }) 245 if segwit != ew.segwit { 246 return fmt.Errorf("segwit expectation not met: wanted segwit = %v (old wallet seed?)", ew.segwit) 247 } 248 249 addr, srvOpts, err := connInfo(info.Server) 250 if err != nil { 251 return fmt.Errorf("no suitable address for host %v: %w", info.Server, err) 252 } 253 chain, err := electrum.ConnectServer(ctx, addr, srvOpts) 254 if err != nil { 255 return err // maybe just try a different one if it doesn't allow multiple conns 256 } 257 ew.log.Infof("Now connected to electrum server %v.", addr) 258 ew.resetChain(chain) 259 ew.ctx = ctx // for requests via methods that lack a context arg 260 261 // This wallet may not be "protected", in which case we omit the password 262 // from the requests. Detect this now and flag the wallet as unlocked. 263 _ = ew.walletUnlock([]byte{}) 264 265 // Start a goroutine to keep the chain client alive and on the same 266 // ElectrumX server as the external Electrum wallet if possible. 267 wg.Add(1) 268 go func() { 269 defer wg.Done() 270 defer ew.chain().Shutdown() 271 lastWalletServer := info.Server 272 273 failing := make(map[string]int) 274 const maxFails = 8 275 276 ticker := time.NewTicker(6 * time.Second) // to keep wallet and chain client on same server 277 defer ticker.Stop() 278 279 for { 280 var walletCheck bool 281 select { 282 case <-ew.chain().Done(): 283 ew.log.Warnf("Electrum server connection lost. Reconnecting in 5 seconds...") 284 select { 285 case <-time.After(5 * time.Second): 286 case <-ctx.Done(): 287 return 288 } 289 290 case <-ticker.C: // just checking with wallet for changes 291 walletCheck = true 292 293 case <-ctx.Done(): 294 return 295 } 296 297 info, err := ew.wallet.GetInfo(ctx) 298 if err != nil { 299 ew.log.Errorf("Electrum wallet getinfo failed: %v", err) 300 continue 301 } 302 if walletCheck { // just checking if wallet's server changed 303 if lastWalletServer == info.Server { 304 continue // no change 305 } 306 delete(failing, info.Server) // clean slate now that wallet has just gotten on it 307 ew.log.Infof("Electrum wallet changed server to %v", info.Server) 308 } 309 lastWalletServer = info.Server 310 311 tryAddr := info.Server 312 if fails := failing[tryAddr]; fails > maxFails { 313 ew.log.Warnf("Server %q has failed to connect %d times. Trying a random one...", tryAddr, fails) 314 tryAddr = "" // try a random one instead 315 } 316 317 addr, srvOpts, err := connInfo(tryAddr) 318 if err != nil { 319 failing[tryAddr]++ 320 ew.log.Errorf("No suitable address for host %q: %v", tryAddr, err) 321 continue 322 } 323 324 if walletCheck { 325 ew.chain().Shutdown() 326 } 327 ew.log.Infof("Connecting to new server %v...", addr) 328 chain, err := electrum.ConnectServer(ctx, addr, srvOpts) 329 if err != nil { 330 ew.log.Errorf("Failed to connect to %v: %v", addr, err) 331 failing[tryAddr]++ 332 continue 333 } 334 ew.log.Infof("Chain service now connected to electrum server %v", addr) 335 ew.resetChain(chain) 336 337 if ctx.Err() != nil { // in case shutdown while waiting on ConnectServer 338 return 339 } 340 } 341 }() 342 343 return err 344 } 345 346 func (ew *electrumWallet) reconfigure(cfg *asset.WalletConfig, currentAddress string) (restartRequired bool, err error) { 347 // electrumWallet only handles walletTypeElectrum. 348 if cfg.Type != walletTypeElectrum { 349 restartRequired = true 350 return 351 } 352 353 // Check the RPC configuration. 354 var parsedCfg RPCWalletConfig // {RPCConfig, WalletConfig} - former may not change 355 err = config.Unmapify(cfg.Settings, &parsedCfg) 356 if err != nil { 357 return false, fmt.Errorf("error parsing rpc wallet config: %w", err) 358 } 359 dexbtc.StandardizeRPCConf(&parsedCfg.RPCConfig.RPCConfig, "") 360 361 // Changing RPC settings is not supported without restart. 362 return parsedCfg.RPCConfig != *ew.rpcCfg, nil 363 } 364 365 // part of btc.Wallet interface 366 func (ew *electrumWallet) sendRawTransaction(tx *wire.MsgTx) (*chainhash.Hash, error) { 367 b, err := serializeMsgTx(tx) 368 if err != nil { 369 return nil, err 370 } 371 // Add the transaction to the wallet DB before broadcasting it on the 372 // network, otherwise it is not immediately recorded. This is expected to 373 // error on non-wallet transactions such as counterparty transactions. 374 _, err = ew.wallet.AddLocalTx(ew.ctx, b) 375 if err != nil && !strings.Contains(err.Error(), "unrelated to this wallet") { 376 ew.log.Warnf("Failed to add tx to the wallet DB: %v", err) 377 } 378 txid, err := ew.wallet.Broadcast(ew.ctx, b) 379 if err != nil { 380 ew.tryRemoveLocalTx(ew.ctx, tx.TxHash().String()) 381 return nil, err 382 } 383 hash, err := chainhash.NewHashFromStr(txid) 384 if err != nil { 385 return nil, err // well that sucks, it's already sent 386 } 387 ops := make([]*Output, len(tx.TxIn)) 388 for i, txIn := range tx.TxIn { 389 prevOut := txIn.PreviousOutPoint 390 ops[i] = &Output{Pt: NewOutPoint(&prevOut.Hash, prevOut.Index)} 391 } 392 if err = ew.lockUnspent(true, ops); err != nil { 393 ew.log.Errorf("Failed to unlock spent UTXOs: %v", err) 394 } 395 return hash, nil 396 } 397 398 func (ew *electrumWallet) outputIsSpent(ctx context.Context, txHash *chainhash.Hash, vout uint32, pkScript []byte) (bool, error) { 399 _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ew.chainParams) 400 if err != nil { 401 return false, fmt.Errorf("failed to decode pkScript: %w", err) 402 } 403 if len(addrs) != 1 { 404 return false, fmt.Errorf("pkScript encodes %d addresses, not 1", len(addrs)) 405 } 406 addr, err := ew.stringAddr(addrs[0], ew.chainParams) 407 if err != nil { 408 return false, fmt.Errorf("invalid address encoding: %w", err) 409 } 410 // Now see if the unspent outputs for this address include this outpoint. 411 addrUnspents, err := ew.wallet.GetAddressUnspent(ctx, addr) 412 if err != nil { 413 return false, fmt.Errorf("getaddressunspent: %w", err) 414 } 415 txid := txHash.String() 416 for _, utxo := range addrUnspents { 417 if utxo.TxHash == txid && uint32(utxo.TxPos) == vout { 418 return false, nil // still unspent 419 } 420 } 421 ew.log.Infof("Output %s:%d not found in unspent output list. Searching for spending txn...", 422 txid, vout) 423 // getaddressunspent can sometimes exclude an unspent output if it is new, 424 // so now search for an actual spending txn, which is a more expensive 425 // operation so we only fall back on this. 426 spendTx, _, err := ew.findOutputSpender(ctx, txHash, vout) 427 if err != nil { 428 return false, fmt.Errorf("failure while checking for spending txn: %v", err) 429 } 430 return spendTx != nil, nil 431 } 432 433 // part of btc.Wallet interface 434 func (ew *electrumWallet) getTxOut(txHash *chainhash.Hash, vout uint32, _ []byte, _ time.Time) (*wire.TxOut, uint32, error) { 435 return ew.getTxOutput(ew.ctx, txHash, vout) 436 } 437 438 func (ew *electrumWallet) getTxOutput(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*wire.TxOut, uint32, error) { 439 // In case this is a wallet transaction, try the wallet DB methods first, 440 // then fall back to the more expensive server request. 441 txid := txHash.String() 442 txRaw, confs, err := ew.checkWalletTx(txid) 443 if err != nil { 444 txRes, err := ew.chain().GetTransaction(ctx, txid) 445 if err != nil { 446 return nil, 0, err 447 } 448 confs = uint32(txRes.Confirmations) 449 txRaw, err = hex.DecodeString(txRes.Hex) 450 if err != nil { 451 return nil, 0, err 452 } 453 } 454 455 msgTx, err := msgTxFromBytes(txRaw) 456 if err != nil { 457 return nil, 0, err 458 } 459 if vout >= uint32(len(msgTx.TxOut)) { 460 return nil, 0, fmt.Errorf("output %d of tx %v does not exists", vout, txid) 461 } 462 pkScript := msgTx.TxOut[vout].PkScript 463 amt := msgTx.TxOut[vout].Value 464 465 // Given the pkScript, we can query for unspent outputs to see if this one 466 // is unspent. 467 spent, err := ew.outputIsSpent(ctx, txHash, vout, pkScript) 468 if err != nil { 469 return nil, 0, err 470 } 471 if spent { 472 return nil, 0, nil 473 } 474 475 return wire.NewTxOut(amt, pkScript), confs, nil 476 } 477 478 func (ew *electrumWallet) getBlockHeaderByHeight(ctx context.Context, height int64) (*wire.BlockHeader, error) { 479 hdrStr, err := ew.chain().BlockHeader(ctx, uint32(height)) 480 if err != nil { 481 return nil, err 482 } 483 hdr := &wire.BlockHeader{} 484 err = hdr.Deserialize(hex.NewDecoder(strings.NewReader(hdrStr))) 485 if err != nil { 486 return nil, err 487 } 488 return hdr, nil 489 } 490 491 // part of btc.Wallet interface 492 func (ew *electrumWallet) medianTime() (time.Time, error) { 493 chainHeight, err := ew.getBestBlockHeight() 494 if err != nil { 495 return time.Time{}, err 496 } 497 return ew.calcMedianTime(ew.ctx, int64(chainHeight)) 498 } 499 500 func (ew *electrumWallet) calcMedianTime(ctx context.Context, height int64) (time.Time, error) { 501 startHeight := height - medianTimeBlocks + 1 502 if startHeight < 0 { 503 startHeight = 0 504 } 505 506 // TODO: check a block hash => median time cache 507 508 hdrsRes, err := ew.chain().BlockHeaders(ctx, uint32(startHeight), 509 uint32(height-startHeight+1)) 510 if err != nil { 511 return time.Time{}, err 512 } 513 514 if hdrsRes.Count != medianTimeBlocks { 515 ew.log.Warnf("Failed to retrieve headers for %d blocks since block %v, got %d", 516 medianTimeBlocks, height, hdrsRes.Count) 517 } 518 if hdrsRes.Count == 0 { 519 return time.Time{}, errors.New("no headers retrieved") 520 } 521 522 hdrReader := hex.NewDecoder(strings.NewReader(hdrsRes.HexConcat)) 523 524 timestamps := make([]int64, 0, hdrsRes.Count) 525 for i := int64(0); i < int64(hdrsRes.Count); i++ { 526 hdr := &wire.BlockHeader{} 527 err = hdr.Deserialize(hdrReader) 528 if err != nil { 529 if i > 0 { 530 ew.log.Errorf("Failed to deserialize header for block %d: %v", 531 startHeight+i, err) 532 break // we have at least one time stamp, work with it 533 } 534 return time.Time{}, err 535 } 536 timestamps = append(timestamps, hdr.Timestamp.Unix()) 537 } 538 // Naive way fetching each header separately, if we needed to use 539 // btc.calcMedianTime as a chainStamper: 540 // for i := height; i > height-medianTimeBlocks && i > 0; i-- { 541 // hdr, err := ew.getBlockHeaderByHeight(ctx, height) 542 // if err != nil { 543 // return time.Time{}, err 544 // } 545 // timestamps = append(timestamps, hdr.Timestamp.Unix()) 546 // } 547 548 sort.Slice(timestamps, func(i, j int) bool { 549 return timestamps[i] < timestamps[j] 550 }) 551 552 medianTimestamp := timestamps[len(timestamps)/2] 553 return time.Unix(medianTimestamp, 0), nil 554 } 555 556 // part of btc.Wallet interface 557 func (ew *electrumWallet) getBlockHash(height int64) (*chainhash.Hash, error) { 558 hdr, err := ew.getBlockHeaderByHeight(ew.ctx, height) 559 if err != nil { 560 return nil, err 561 } 562 hash := hdr.BlockHash() 563 return &hash, nil 564 } 565 566 // part of btc.Wallet interface 567 func (ew *electrumWallet) getBestBlockHash() (*chainhash.Hash, error) { 568 inf, err := ew.wallet.GetInfo(ew.ctx) 569 if err != nil { 570 return nil, err 571 } 572 return ew.getBlockHash(inf.SyncHeight) 573 } 574 575 // part of btc.Wallet interface 576 func (ew *electrumWallet) getBestBlockHeight() (int32, error) { 577 inf, err := ew.wallet.GetInfo(ew.ctx) 578 if err != nil { 579 return 0, err 580 } 581 return int32(inf.SyncHeight), nil 582 } 583 584 // part of btc.Wallet interface 585 func (ew *electrumWallet) getBestBlockHeader() (*BlockHeader, error) { 586 inf, err := ew.wallet.GetInfo(ew.ctx) 587 if err != nil { 588 return nil, err 589 } 590 591 hdr, err := ew.getBlockHeaderByHeight(ew.ctx, inf.SyncHeight) 592 if err != nil { 593 return nil, err 594 } 595 596 header := &BlockHeader{ 597 Hash: hdr.BlockHash().String(), 598 Height: inf.SyncHeight, 599 Confirmations: 1, // it's the head 600 Time: hdr.Timestamp.Unix(), 601 PreviousBlockHash: hdr.PrevBlock.String(), 602 } 603 return header, nil 604 } 605 606 // part of btc.Wallet interface 607 func (ew *electrumWallet) balances() (*GetBalancesResult, error) { 608 eBal, err := ew.wallet.GetBalance(ew.ctx) 609 if err != nil { 610 return nil, err 611 } 612 // NOTE: Nothing from the Electrum wallet's response indicates trusted vs. 613 // untrusted. To allow unconfirmed coins to be spent, we treat both 614 // confirmed and unconfirmed as trusted. This is like dogecoind's handling 615 // of balance. TODO: listunspent -> checkWalletTx(txid) -> for each 616 // input, checkWalletTx(prevout) and ismine(addr) 617 return &GetBalancesResult{ 618 Mine: Balances{ 619 Trusted: eBal.Confirmed + eBal.Unconfirmed, 620 Immature: eBal.Immature, 621 }, 622 }, nil 623 } 624 625 // part of btc.Wallet interface 626 func (ew *electrumWallet) listUnspent() ([]*ListUnspentResult, error) { 627 eUnspent, err := ew.wallet.ListUnspent(ew.ctx) 628 if err != nil { 629 return nil, err 630 } 631 chainHeight, err := ew.getBestBlockHeight() 632 if err != nil { 633 return nil, err 634 } 635 636 // Filter out locked outpoints since listUnspent includes them. 637 lockedOPs := ew.listLockedOutpoints() 638 lockedOPMap := make(map[RPCOutpoint]bool, len(lockedOPs)) 639 for _, pt := range lockedOPs { 640 lockedOPMap[*pt] = true 641 } 642 643 unspents := make([]*ListUnspentResult, 0, len(eUnspent)) 644 for _, utxo := range eUnspent { 645 if lockedOPMap[RPCOutpoint{utxo.PrevOutHash, utxo.PrevOutIdx}] { 646 continue 647 } 648 addr, err := ew.decodeAddr(utxo.Address, ew.chainParams) 649 if err != nil { 650 ew.log.Warnf("Output (%v:%d) with bad address %v found: %v", 651 utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err) 652 continue 653 } 654 pkScript, err := txscript.PayToAddrScript(addr) 655 if err != nil { 656 ew.log.Warnf("Output (%v:%d) with bad address %v found: %v", 657 utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err) 658 continue 659 } 660 val, err := strconv.ParseFloat(utxo.Value, 64) 661 if err != nil { 662 ew.log.Warnf("Output (%v:%d) with bad value %v found: %v", 663 utxo.PrevOutHash, utxo.PrevOutIdx, val, err) 664 continue 665 } 666 var confs uint32 667 if height := int32(utxo.Height); height > 0 { 668 // height is non-zero, so confirmed, but if the RPCs are 669 // inconsistent with respect to height, avoid an underflow or 670 // appearing unconfirmed. 671 if height > chainHeight { 672 confs = 1 673 } else { 674 confs = uint32(chainHeight - height + 1) 675 } 676 } 677 redeemScript, err := hex.DecodeString(utxo.RedeemScript) 678 if err != nil { 679 ew.log.Warnf("Output (%v:%d) with bad redeemscript %v found: %v", 680 utxo.PrevOutHash, utxo.PrevOutIdx, utxo.RedeemScript, err) 681 continue 682 } 683 684 unspents = append(unspents, &ListUnspentResult{ 685 TxID: utxo.PrevOutHash, 686 Vout: utxo.PrevOutIdx, 687 Address: utxo.Address, 688 ScriptPubKey: pkScript, 689 Amount: val, 690 Confirmations: confs, 691 RedeemScript: redeemScript, 692 Spendable: true, // can electrum have unspendable? 693 Solvable: true, 694 // Safe is unknown, leave ptr nil 695 }) 696 } 697 return unspents, nil 698 } 699 700 // part of btc.Wallet interface 701 func (ew *electrumWallet) lockUnspent(unlock bool, ops []*Output) error { 702 eUnspent, err := ew.wallet.ListUnspent(ew.ctx) 703 if err != nil { 704 return err 705 } 706 opMap := make(map[OutPoint]struct{}, len(ops)) 707 for _, op := range ops { 708 opMap[op.Pt] = struct{}{} 709 } 710 // For the ones that appear in listunspent, use (un)freeze_utxo also. 711 unspents: 712 for _, utxo := range eUnspent { 713 for op := range opMap { 714 if op.Vout == utxo.PrevOutIdx && op.TxHash.String() == utxo.PrevOutHash { 715 // FreezeUTXO and UnfreezeUTXO do not error when called 716 // repeatedly for the same UTXO. 717 if unlock { 718 if err = ew.wallet.UnfreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil { 719 ew.log.Warnf("UnfreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err) 720 // Maybe we lost a race somewhere. Keep going. 721 } 722 ew.lockedOutpointsMtx.Lock() 723 delete(ew.lockedOutpoints, op) 724 ew.lockedOutpointsMtx.Unlock() 725 delete(opMap, op) 726 continue unspents 727 } 728 729 if err = ew.wallet.FreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil { 730 ew.log.Warnf("FreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err) 731 } 732 // listunspent returns locked utxos, so we have to track it. 733 ew.lockedOutpointsMtx.Lock() 734 ew.lockedOutpoints[op] = struct{}{} 735 ew.lockedOutpointsMtx.Unlock() 736 delete(opMap, op) 737 continue unspents 738 } 739 } 740 } 741 742 // If not in the listunspent response, fail if trying to lock, otherwise 743 // just remove them from the lockedOutpoints map (unlocking spent UTXOs). 744 if len(opMap) > 0 && !unlock { 745 return fmt.Errorf("failed to lock some utxos") 746 } 747 for op := range opMap { 748 ew.lockedOutpointsMtx.Lock() 749 delete(ew.lockedOutpoints, op) 750 ew.lockedOutpointsMtx.Unlock() 751 } 752 753 return nil 754 } 755 756 func (ew *electrumWallet) listLockedOutpoints() []*RPCOutpoint { 757 ew.lockedOutpointsMtx.RLock() 758 defer ew.lockedOutpointsMtx.RUnlock() 759 locked := make([]*RPCOutpoint, 0, len(ew.lockedOutpoints)) 760 for op := range ew.lockedOutpoints { 761 locked = append(locked, &RPCOutpoint{ 762 TxID: op.TxHash.String(), 763 Vout: op.Vout, 764 }) 765 } 766 return locked 767 } 768 769 // part of btc.Wallet interface 770 func (ew *electrumWallet) listLockUnspent() ([]*RPCOutpoint, error) { 771 return ew.listLockedOutpoints(), nil 772 } 773 774 // externalAddress creates a fresh address beyond the default gap limit, so it 775 // should be used immediately. Part of btc.Wallet interface. 776 func (ew *electrumWallet) externalAddress() (btcutil.Address, error) { 777 addr, err := ew.wallet.GetUnusedAddress(ew.ctx) 778 if err != nil { 779 return nil, err 780 } 781 return ew.decodeAddr(addr, ew.chainParams) 782 } 783 784 // changeAddress creates a fresh address beyond the default gap limit, so it 785 // should be used immediately. Part of btc.Wallet interface. 786 func (ew *electrumWallet) changeAddress() (btcutil.Address, error) { 787 return ew.externalAddress() // sadly, cannot request internal addresses 788 } 789 790 // part of btc.Wallet interface 791 func (ew *electrumWallet) signTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { 792 // If the wallet's signtransaction RPC ever has a problem with the PSBT, we 793 // could attempt to sign the transaction ourselves by pulling the inputs' 794 // private keys and using txscript manually, but this can vary greatly 795 // between assets. 796 797 packet, err := psbt.NewFromUnsignedTx(inTx) 798 if err != nil { 799 return nil, err 800 } 801 psbtB64, err := packet.B64Encode() 802 if err != nil { 803 return nil, err 804 } 805 806 signedB, err := ew.wallet.SignTx(ew.ctx, ew.walletPass(), psbtB64) 807 if err != nil { 808 return nil, err 809 } 810 return msgTxFromBytes(signedB) 811 } 812 813 type hash160er interface { 814 Hash160() *[20]byte 815 } 816 817 type pubKeyer interface { 818 PubKey() *btcec.PublicKey 819 } 820 821 // part of btc.Wallet interface 822 func (ew *electrumWallet) privKeyForAddress(addr string) (*btcec.PrivateKey, error) { 823 addrDec, err := ew.decodeAddr(addr, ew.chainParams) 824 if err != nil { 825 return nil, err 826 } 827 wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, ew.walletPass(), addr) 828 if err != nil { 829 return nil, err 830 } 831 wif, err := btcutil.DecodeWIF(wifStr) 832 if err != nil { 833 return nil, err 834 } // wif.PrivKey is the result 835 836 // Sanity check that PrivKey corresponds to the pubkey(hash). 837 var pkh []byte 838 switch addrT := addrDec.(type) { 839 case pubKeyer: // e.g. *btcutil.AddressPubKey: 840 // Get same format as wif.SerializePubKey() 841 var pk []byte 842 if wif.CompressPubKey { 843 pk = addrT.PubKey().SerializeCompressed() 844 } else { 845 pk = addrT.PubKey().SerializeUncompressed() 846 } 847 pkh = btcutil.Hash160(pk) // addrT.ScriptAddress() would require SetFormat(compress/uncompress) 848 case *btcutil.AddressScriptHash, *btcutil.AddressWitnessScriptHash: 849 return wif.PrivKey, nil // assume unknown redeem script references this pubkey 850 case hash160er: // p2pkh and p2wpkh 851 pkh = addrT.Hash160()[:] 852 } 853 wifPKH := btcutil.Hash160(wif.SerializePubKey()) 854 if !bytes.Equal(pkh, wifPKH) { 855 return nil, errors.New("pubkey mismatch") 856 } 857 return wif.PrivKey, nil 858 } 859 860 func (ew *electrumWallet) pass() (pw string, unlocked bool) { 861 ew.pwMtx.RLock() 862 defer ew.pwMtx.RUnlock() 863 return ew.pw, ew.unlocked 864 } 865 866 func (ew *electrumWallet) testPass(pw []byte) error { 867 addr, err := ew.wallet.GetUnusedAddress(ew.ctx) 868 if err != nil { 869 return err 870 } 871 wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, string(pw), addr) 872 if err != nil { 873 // When providing a password to an unprotected wallet, and other cases, 874 // a cryptic error containing "incorrect padding" is returned. 875 if strings.Contains(strings.ToLower(err.Error()), "incorrect padding") { 876 return errors.New("incorrect password (no password required?)") 877 } 878 return fmt.Errorf("GetPrivateKeys: %v", err) 879 } 880 // That should be enough, but validate the returned keys in case they are 881 // empty or invalid. 882 if _, err = btcutil.DecodeWIF(wifStr); err != nil { 883 return fmt.Errorf("DecodeWIF: %v", err) 884 } 885 return nil 886 } 887 888 // walletLock locks the wallet. Part of the btc.Wallet interface. 889 func (ew *electrumWallet) walletLock() error { 890 ew.pwMtx.Lock() 891 defer ew.pwMtx.Unlock() 892 if ew.pw == "" && ew.unlocked { 893 // This is an unprotected wallet (can't actually lock it). But confirm 894 // the password is still empty in case it changed externally. 895 if err := ew.testPass([]byte{}); err == nil { 896 return nil 897 } // must have changed! "lock" it! 898 } 899 ew.pw, ew.unlocked = "", false 900 return nil 901 } 902 903 // locked indicates if the wallet has been unlocked. Part of the btc.Wallet 904 // interface. 905 func (ew *electrumWallet) locked() bool { 906 ew.pwMtx.RLock() 907 defer ew.pwMtx.RUnlock() 908 return !ew.unlocked 909 } 910 911 // walletPass returns the wallet passphrase. Since an empty password is valid, 912 // use pass or locked to determine if locked. This is for convenience. 913 func (ew *electrumWallet) walletPass() string { 914 pw, _ := ew.pass() 915 return pw 916 } 917 918 // walletUnlock attempts to unlock the wallet with the provided password. On 919 // success, the password is stored and may be accessed via pass or walletPass. 920 // Part of the btc.Wallet interface. 921 func (ew *electrumWallet) walletUnlock(pw []byte) error { 922 if err := ew.testPass(pw); err != nil { 923 return err 924 } 925 ew.pwMtx.Lock() 926 ew.pw, ew.unlocked = string(pw), true 927 ew.pwMtx.Unlock() 928 return nil 929 } 930 931 // part of the btc.Wallet interface 932 func (ew *electrumWallet) peerCount() (uint32, error) { 933 if ew.chain() == nil { // must work prior to resetChain 934 return 0, nil 935 } 936 937 info, err := ew.wallet.GetInfo(ew.ctx) 938 if err != nil { 939 return 0, err 940 } 941 select { 942 case <-ew.chain().Done(): 943 return 0, errors.New("electrumx server connection down") 944 default: 945 } 946 947 return uint32(info.Connections), nil 948 } 949 950 // part of the btc.Wallet interface 951 func (ew *electrumWallet) ownsAddress(addr btcutil.Address) (bool, error) { 952 addrStr, err := ew.stringAddr(addr, ew.chainParams) 953 if err != nil { 954 return false, err 955 } 956 valid, mine, err := ew.wallet.CheckAddress(ew.ctx, addrStr) 957 if err != nil { 958 return false, err 959 } 960 if !valid { // maybe electrum doesn't know all encodings that btcutil does 961 return false, nil // an error here may prevent reconfiguring a misconfigured wallet 962 } 963 return mine, nil 964 } 965 966 // part of the btc.Wallet interface 967 func (ew *electrumWallet) syncStatus() (*asset.SyncStatus, error) { 968 info, err := ew.wallet.GetInfo(ew.ctx) 969 if err != nil { 970 return nil, err 971 } 972 return &asset.SyncStatus{ 973 Synced: info.Connected && info.SyncHeight >= info.ServerHeight, 974 TargetHeight: uint64(info.ServerHeight), 975 Blocks: uint64(info.SyncHeight), 976 }, nil 977 } 978 979 // part of the btc.Wallet interface 980 func (ew *electrumWallet) listTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { 981 bestHeight, err := ew.getBestBlockHeight() 982 if err != nil { 983 return nil, fmt.Errorf("error getting best block: %v", err) 984 } 985 if bestHeight < blockHeight { 986 return nil, nil 987 } 988 var txs []*ListTransactionsResult 989 from := int64(blockHeight) 990 to := int64(blockHeight + 2000) 991 for { 992 if to > int64(bestHeight) { 993 to = int64(bestHeight) 994 } 995 addTxs, err := ew.wallet.OnchainHistory(ew.ctx, from, to) 996 if err != nil { 997 return nil, fmt.Errorf("error getting onchain history: %v", err) 998 } 999 for _, tx := range addTxs { 1000 ltr := &ListTransactionsResult{ 1001 BlockHeight: uint32(tx.Height), 1002 BlockTime: uint64(tx.Timestamp), // Maybe? 1003 Send: !tx.Incoming, 1004 TxID: tx.TxID, 1005 } 1006 if tx.Fee != nil { 1007 f, err := strconv.ParseFloat(*tx.Fee, 64) 1008 if err != nil { 1009 return nil, fmt.Errorf("error parsing fee: %v", err) 1010 } 1011 ltr.Fee = &f 1012 } 1013 txs = append(txs, ltr) 1014 } 1015 if to == int64(bestHeight) { 1016 break 1017 } 1018 from = to 1019 to += 2000 1020 } 1021 return txs, nil 1022 } 1023 1024 // checkWalletTx will get the bytes and confirmations of a wallet transaction. 1025 // For non-wallet transactions, it is normal to see "Exception: Transaction not 1026 // in wallet" in Electrum's parent console, if launched from a terminal. 1027 // Part of the walletTxChecker interface. 1028 func (ew *electrumWallet) checkWalletTx(txid string) ([]byte, uint32, error) { 1029 // GetWalletTxConfs only works for wallet transactions, while 1030 // wallet.GetRawTransaction will try the wallet DB first, but fall back to 1031 // querying a server, so do GetWalletTxConfs first to prevent that. 1032 confs, err := ew.wallet.GetWalletTxConfs(ew.ctx, txid) 1033 if err != nil { 1034 return nil, 0, err 1035 } 1036 txRaw, err := ew.wallet.GetRawTransaction(ew.ctx, txid) 1037 if err != nil { 1038 return nil, 0, err 1039 } 1040 if confs < 0 { 1041 confs = 0 1042 } 1043 return txRaw, uint32(confs), nil 1044 } 1045 1046 // part of the walletTxChecker interface 1047 func (ew *electrumWallet) getWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { 1048 // Try the wallet first. If it is not a wallet transaction or if it is 1049 // confirmed, fall back to the chain method to get the block info and time 1050 // fields. 1051 txid := txHash.String() 1052 txRaw, confs, err := ew.checkWalletTx(txid) 1053 if err == nil && confs == 0 { 1054 return &GetTransactionResult{ 1055 TxID: txid, 1056 Bytes: txRaw, 1057 // Time/TimeReceived? now? needed? 1058 }, nil 1059 } // else we have to ask a server for the verbose response with block info 1060 1061 txInfo, err := ew.chain().GetTransaction(ew.ctx, txid) 1062 if err != nil { 1063 return nil, err 1064 } 1065 txRaw, err = hex.DecodeString(txInfo.Hex) 1066 if err != nil { 1067 return nil, err 1068 } 1069 return &GetTransactionResult{ 1070 Confirmations: uint64(txInfo.Confirmations), 1071 BlockHash: txInfo.BlockHash, 1072 // BlockIndex unknown 1073 BlockTime: uint64(txInfo.BlockTime), 1074 TxID: txInfo.TxID, // txHash.String() 1075 Time: uint64(txInfo.Time), 1076 TimeReceived: uint64(txInfo.Time), 1077 Bytes: txRaw, 1078 }, nil 1079 } 1080 1081 func (ew *electrumWallet) fingerprint() (string, error) { 1082 return "", fmt.Errorf("fingerprint not implemented") 1083 } 1084 1085 // part of the walletTxChecker interface 1086 func (ew *electrumWallet) swapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) { 1087 // To determine if it is spent, we need the address of the output. 1088 var pkScript []byte 1089 txid := txHash.String() 1090 // Try the wallet first in case this is a wallet transaction (own swap). 1091 txRaw, confs, err := ew.checkWalletTx(txid) 1092 if err == nil { 1093 msgTx, err := msgTxFromBytes(txRaw) 1094 if err != nil { 1095 return 0, false, err 1096 } 1097 if vout >= uint32(len(msgTx.TxOut)) { 1098 return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid) 1099 } 1100 pkScript = msgTx.TxOut[vout].PkScript 1101 } else { 1102 // Fall back to the more expensive server request. 1103 txInfo, err := ew.chain().GetTransaction(ew.ctx, txid) 1104 if err != nil { 1105 return 0, false, err 1106 } 1107 confs = uint32(txInfo.Confirmations) 1108 if txInfo.Confirmations < 1 { 1109 confs = 0 1110 } 1111 if vout >= uint32(len(txInfo.Vout)) { 1112 return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid) 1113 } 1114 txOut := &txInfo.Vout[vout] 1115 pkScript, err = hex.DecodeString(txOut.PkScript.Hex) 1116 if err != nil { 1117 return 0, false, fmt.Errorf("invalid pkScript: %w", err) 1118 } 1119 } 1120 1121 spent, err = ew.outputIsSpent(ew.ctx, txHash, vout, pkScript) 1122 if err != nil { 1123 return 0, false, err 1124 } 1125 return confs, spent, nil 1126 } 1127 1128 // tryRemoveLocalTx attempts to remove a "local" transaction from the Electrum 1129 // wallet. Such a transaction is unbroadcasted. This may be necessary if a 1130 // broadcast of a local txn attempt failed so that the inputs are available for 1131 // other transactions. 1132 func (ew *electrumWallet) tryRemoveLocalTx(ctx context.Context, txid string) { 1133 if err := ew.wallet.RemoveLocalTx(ctx, txid); err != nil { 1134 ew.log.Errorf("Failed to remove local transaction %s: %v", 1135 txid, err) 1136 } 1137 } 1138 1139 func (ew *electrumWallet) outPointAddress(ctx context.Context, txid string, vout uint32) (string, error) { 1140 txRaw, err := ew.wallet.GetRawTransaction(ctx, txid) 1141 if err != nil { 1142 return "", err 1143 } 1144 msgTx, err := msgTxFromBytes(txRaw) 1145 if err != nil { 1146 return "", err 1147 } 1148 if vout >= uint32(len(msgTx.TxOut)) { 1149 return "", fmt.Errorf("output %d of tx %v does not exists", vout, txid) 1150 } 1151 pkScript := msgTx.TxOut[vout].PkScript 1152 _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ew.chainParams) 1153 if err != nil { 1154 return "", fmt.Errorf("invalid pkScript: %v", err) 1155 } 1156 if len(addrs) != 1 { 1157 return "", fmt.Errorf("invalid pkScript: %d addresses", len(addrs)) 1158 } 1159 addrStr, err := ew.stringAddr(addrs[0], ew.chainParams) 1160 if err != nil { 1161 return "", err 1162 } 1163 return addrStr, nil 1164 } 1165 1166 func (ew *electrumWallet) findOutputSpender(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*wire.MsgTx, uint32, error) { 1167 txid := txHash.String() 1168 addr, err := ew.outPointAddress(ctx, txid, vout) 1169 if err != nil { 1170 return nil, 0, fmt.Errorf("invalid outpoint address: %w", err) 1171 } 1172 // NOTE: Caller should already have determined the output is spent before 1173 // requesting the entire address history. 1174 hist, err := ew.wallet.GetAddressHistory(ctx, addr) 1175 if err != nil { 1176 return nil, 0, fmt.Errorf("unable to get address history: %w", err) 1177 } 1178 1179 sort.Slice(hist, func(i, j int) bool { 1180 return hist[i].Height > hist[j].Height // descending 1181 }) 1182 1183 var outHeight int64 1184 for _, io := range hist { 1185 if io.TxHash == txid { 1186 outHeight = io.Height 1187 continue // same txn 1188 } 1189 if io.Height < outHeight { 1190 break // spender not before the output's txn 1191 } 1192 txRaw, err := ew.wallet.GetRawTransaction(ctx, io.TxHash) 1193 if err != nil { 1194 ew.log.Warnf("Unable to retrieve transaction %v for address %v: %v", 1195 io.TxHash, addr, err) 1196 continue 1197 } 1198 msgTx, err := msgTxFromBytes(txRaw) 1199 if err != nil { 1200 ew.log.Warnf("Unable to decode transaction %v for address %v: %v", 1201 io.TxHash, addr, err) 1202 continue 1203 } 1204 for vin, txIn := range msgTx.TxIn { 1205 prevOut := &txIn.PreviousOutPoint 1206 if vout == prevOut.Index && prevOut.Hash.IsEqual(txHash) { 1207 return msgTx, uint32(vin), nil 1208 } 1209 } 1210 } 1211 1212 return nil, 0, nil // caller should check msgTx (internal method) 1213 }