decred.org/dcrdex@v1.0.5/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 hdrStr, err := ew.chain().BlockHeader(ew.ctx, uint32(height)) 559 if err != nil { 560 return nil, err 561 } 562 563 fullHeader, err := hex.DecodeString(hdrStr) 564 if err != nil { 565 return nil, err 566 } 567 568 hash := chainhash.DoubleHashH(fullHeader) 569 570 return &hash, nil 571 } 572 573 // part of btc.Wallet interface 574 func (ew *electrumWallet) GetBestBlockHash() (*chainhash.Hash, error) { 575 inf, err := ew.wallet.GetInfo(ew.ctx) 576 if err != nil { 577 return nil, err 578 } 579 return ew.GetBlockHash(inf.SyncHeight) 580 } 581 582 // part of btc.Wallet interface 583 func (ew *electrumWallet) GetBestBlockHeight() (int32, error) { 584 inf, err := ew.wallet.GetInfo(ew.ctx) 585 if err != nil { 586 return 0, err 587 } 588 return int32(inf.SyncHeight), nil 589 } 590 591 // part of btc.Wallet interface 592 func (ew *electrumWallet) GetBestBlockHeader() (*BlockHeader, error) { 593 inf, err := ew.wallet.GetInfo(ew.ctx) 594 if err != nil { 595 return nil, err 596 } 597 598 hdr, err := ew.getBlockHeaderByHeight(ew.ctx, inf.SyncHeight) 599 if err != nil { 600 return nil, err 601 } 602 603 header := &BlockHeader{ 604 Hash: hdr.BlockHash().String(), 605 Height: inf.SyncHeight, 606 Confirmations: 1, // it's the head 607 Time: hdr.Timestamp.Unix(), 608 PreviousBlockHash: hdr.PrevBlock.String(), 609 } 610 return header, nil 611 } 612 613 // part of btc.Wallet interface 614 func (ew *electrumWallet) Balances() (*GetBalancesResult, error) { 615 eBal, err := ew.wallet.GetBalance(ew.ctx) 616 if err != nil { 617 return nil, err 618 } 619 // NOTE: Nothing from the Electrum wallet's response indicates trusted vs. 620 // untrusted. To allow unconfirmed coins to be spent, we treat both 621 // confirmed and unconfirmed as trusted. This is like dogecoind's handling 622 // of balance. TODO: listunspent -> checkWalletTx(txid) -> for each 623 // input, checkWalletTx(prevout) and ismine(addr) 624 return &GetBalancesResult{ 625 Mine: Balances{ 626 Trusted: eBal.Confirmed + eBal.Unconfirmed, 627 Immature: eBal.Immature, 628 }, 629 }, nil 630 } 631 632 // part of btc.Wallet interface 633 func (ew *electrumWallet) ListUnspent() ([]*ListUnspentResult, error) { 634 eUnspent, err := ew.wallet.ListUnspent(ew.ctx) 635 if err != nil { 636 return nil, err 637 } 638 chainHeight, err := ew.GetBestBlockHeight() 639 if err != nil { 640 return nil, err 641 } 642 643 // Filter out locked outpoints since listUnspent includes them. 644 lockedOPs := ew.listLockedOutpoints() 645 lockedOPMap := make(map[RPCOutpoint]bool, len(lockedOPs)) 646 for _, pt := range lockedOPs { 647 lockedOPMap[*pt] = true 648 } 649 650 unspents := make([]*ListUnspentResult, 0, len(eUnspent)) 651 for _, utxo := range eUnspent { 652 if lockedOPMap[RPCOutpoint{utxo.PrevOutHash, utxo.PrevOutIdx}] { 653 continue 654 } 655 addr, err := ew.decodeAddr(utxo.Address, ew.chainParams) 656 if err != nil { 657 ew.log.Warnf("Output (%v:%d) with bad address %v found: %v", 658 utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err) 659 continue 660 } 661 pkScript, err := txscript.PayToAddrScript(addr) 662 if err != nil { 663 ew.log.Warnf("Output (%v:%d) with bad address %v found: %v", 664 utxo.PrevOutHash, utxo.PrevOutIdx, utxo.Address, err) 665 continue 666 } 667 val, err := strconv.ParseFloat(utxo.Value, 64) 668 if err != nil { 669 ew.log.Warnf("Output (%v:%d) with bad value %v found: %v", 670 utxo.PrevOutHash, utxo.PrevOutIdx, val, err) 671 continue 672 } 673 var confs uint32 674 if height := int32(utxo.Height); height > 0 { 675 // height is non-zero, so confirmed, but if the RPCs are 676 // inconsistent with respect to height, avoid an underflow or 677 // appearing unconfirmed. 678 if height > chainHeight { 679 confs = 1 680 } else { 681 confs = uint32(chainHeight - height + 1) 682 } 683 } 684 redeemScript, err := hex.DecodeString(utxo.RedeemScript) 685 if err != nil { 686 ew.log.Warnf("Output (%v:%d) with bad redeemscript %v found: %v", 687 utxo.PrevOutHash, utxo.PrevOutIdx, utxo.RedeemScript, err) 688 continue 689 } 690 691 unspents = append(unspents, &ListUnspentResult{ 692 TxID: utxo.PrevOutHash, 693 Vout: utxo.PrevOutIdx, 694 Address: utxo.Address, 695 ScriptPubKey: pkScript, 696 Amount: val, 697 Confirmations: confs, 698 RedeemScript: redeemScript, 699 Spendable: true, // can electrum have unspendable? 700 Solvable: true, 701 // Safe is unknown, leave ptr nil 702 }) 703 } 704 return unspents, nil 705 } 706 707 // part of btc.Wallet interface 708 func (ew *electrumWallet) LockUnspent(unlock bool, ops []*Output) error { 709 eUnspent, err := ew.wallet.ListUnspent(ew.ctx) 710 if err != nil { 711 return err 712 } 713 opMap := make(map[OutPoint]struct{}, len(ops)) 714 for _, op := range ops { 715 opMap[op.Pt] = struct{}{} 716 } 717 // For the ones that appear in listunspent, use (un)freeze_utxo also. 718 unspents: 719 for _, utxo := range eUnspent { 720 for op := range opMap { 721 if op.Vout == utxo.PrevOutIdx && op.TxHash.String() == utxo.PrevOutHash { 722 // FreezeUTXO and UnfreezeUTXO do not error when called 723 // repeatedly for the same UTXO. 724 if unlock { 725 if err = ew.wallet.UnfreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil { 726 ew.log.Warnf("UnfreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err) 727 // Maybe we lost a race somewhere. Keep going. 728 } 729 ew.lockedOutpointsMtx.Lock() 730 delete(ew.lockedOutpoints, op) 731 ew.lockedOutpointsMtx.Unlock() 732 delete(opMap, op) 733 continue unspents 734 } 735 736 if err = ew.wallet.FreezeUTXO(ew.ctx, utxo.PrevOutHash, utxo.PrevOutIdx); err != nil { 737 ew.log.Warnf("FreezeUTXO(%s:%d) failed: %v", utxo.PrevOutHash, utxo.PrevOutIdx, err) 738 } 739 // listunspent returns locked utxos, so we have to track it. 740 ew.lockedOutpointsMtx.Lock() 741 ew.lockedOutpoints[op] = struct{}{} 742 ew.lockedOutpointsMtx.Unlock() 743 delete(opMap, op) 744 continue unspents 745 } 746 } 747 } 748 749 // If not in the listunspent response, fail if trying to lock, otherwise 750 // just remove them from the lockedOutpoints map (unlocking spent UTXOs). 751 if len(opMap) > 0 && !unlock { 752 return fmt.Errorf("failed to lock some utxos") 753 } 754 for op := range opMap { 755 ew.lockedOutpointsMtx.Lock() 756 delete(ew.lockedOutpoints, op) 757 ew.lockedOutpointsMtx.Unlock() 758 } 759 760 return nil 761 } 762 763 func (ew *electrumWallet) listLockedOutpoints() []*RPCOutpoint { 764 ew.lockedOutpointsMtx.RLock() 765 defer ew.lockedOutpointsMtx.RUnlock() 766 locked := make([]*RPCOutpoint, 0, len(ew.lockedOutpoints)) 767 for op := range ew.lockedOutpoints { 768 locked = append(locked, &RPCOutpoint{ 769 TxID: op.TxHash.String(), 770 Vout: op.Vout, 771 }) 772 } 773 return locked 774 } 775 776 // part of btc.Wallet interface 777 func (ew *electrumWallet) ListLockUnspent() ([]*RPCOutpoint, error) { 778 return ew.listLockedOutpoints(), nil 779 } 780 781 // ExternalAddress creates a fresh address beyond the default gap limit, so it 782 // should be used immediately. Part of btc.Wallet interface. 783 func (ew *electrumWallet) ExternalAddress() (btcutil.Address, error) { 784 addr, err := ew.wallet.GetUnusedAddress(ew.ctx) 785 if err != nil { 786 return nil, err 787 } 788 return ew.decodeAddr(addr, ew.chainParams) 789 } 790 791 // ChangeAddress creates a fresh address beyond the default gap limit, so it 792 // should be used immediately. Part of btc.Wallet interface. 793 func (ew *electrumWallet) ChangeAddress() (btcutil.Address, error) { 794 return ew.ExternalAddress() // sadly, cannot request internal addresses 795 } 796 797 // part of btc.Wallet interface 798 func (ew *electrumWallet) SignTx(inTx *wire.MsgTx) (*wire.MsgTx, error) { 799 // If the wallet's signtransaction RPC ever has a problem with the PSBT, we 800 // could attempt to sign the transaction ourselves by pulling the inputs' 801 // private keys and using txscript manually, but this can vary greatly 802 // between assets. 803 804 packet, err := psbt.NewFromUnsignedTx(inTx) 805 if err != nil { 806 return nil, err 807 } 808 psbtB64, err := packet.B64Encode() 809 if err != nil { 810 return nil, err 811 } 812 813 signedB, err := ew.wallet.SignTx(ew.ctx, ew.walletPass(), psbtB64) 814 if err != nil { 815 return nil, err 816 } 817 return msgTxFromBytes(signedB) 818 } 819 820 type hash160er interface { 821 Hash160() *[20]byte 822 } 823 824 type pubKeyer interface { 825 PubKey() *btcec.PublicKey 826 } 827 828 // part of btc.Wallet interface 829 func (ew *electrumWallet) PrivKeyForAddress(addr string) (*btcec.PrivateKey, error) { 830 addrDec, err := ew.decodeAddr(addr, ew.chainParams) 831 if err != nil { 832 return nil, err 833 } 834 wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, ew.walletPass(), addr) 835 if err != nil { 836 return nil, err 837 } 838 wif, err := btcutil.DecodeWIF(wifStr) 839 if err != nil { 840 return nil, err 841 } // wif.PrivKey is the result 842 843 // Sanity check that PrivKey corresponds to the pubkey(hash). 844 var pkh []byte 845 switch addrT := addrDec.(type) { 846 case pubKeyer: // e.g. *btcutil.AddressPubKey: 847 // Get same format as wif.SerializePubKey() 848 var pk []byte 849 if wif.CompressPubKey { 850 pk = addrT.PubKey().SerializeCompressed() 851 } else { 852 pk = addrT.PubKey().SerializeUncompressed() 853 } 854 pkh = btcutil.Hash160(pk) // addrT.ScriptAddress() would require SetFormat(compress/uncompress) 855 case *btcutil.AddressScriptHash, *btcutil.AddressWitnessScriptHash: 856 return wif.PrivKey, nil // assume unknown redeem script references this pubkey 857 case hash160er: // p2pkh and p2wpkh 858 pkh = addrT.Hash160()[:] 859 } 860 wifPKH := btcutil.Hash160(wif.SerializePubKey()) 861 if !bytes.Equal(pkh, wifPKH) { 862 return nil, errors.New("pubkey mismatch") 863 } 864 return wif.PrivKey, nil 865 } 866 867 func (ew *electrumWallet) pass() (pw string, unlocked bool) { 868 ew.pwMtx.RLock() 869 defer ew.pwMtx.RUnlock() 870 return ew.pw, ew.unlocked 871 } 872 873 func (ew *electrumWallet) testPass(pw []byte) error { 874 addr, err := ew.wallet.GetUnusedAddress(ew.ctx) 875 if err != nil { 876 return err 877 } 878 wifStr, err := ew.wallet.GetPrivateKeys(ew.ctx, string(pw), addr) 879 if err != nil { 880 // When providing a password to an unprotected wallet, and other cases, 881 // a cryptic error containing "incorrect padding" is returned. 882 if strings.Contains(strings.ToLower(err.Error()), "incorrect padding") { 883 return errors.New("incorrect password (no password required?)") 884 } 885 return fmt.Errorf("GetPrivateKeys: %v", err) 886 } 887 // That should be enough, but validate the returned keys in case they are 888 // empty or invalid. 889 if _, err = btcutil.DecodeWIF(wifStr); err != nil { 890 return fmt.Errorf("DecodeWIF: %v", err) 891 } 892 return nil 893 } 894 895 // WalletLock locks the wallet. Part of the btc.Wallet interface. 896 func (ew *electrumWallet) WalletLock() error { 897 ew.pwMtx.Lock() 898 defer ew.pwMtx.Unlock() 899 if ew.pw == "" && ew.unlocked { 900 // This is an unprotected wallet (can't actually lock it). But confirm 901 // the password is still empty in case it changed externally. 902 if err := ew.testPass([]byte{}); err == nil { 903 return nil 904 } // must have changed! "lock" it! 905 } 906 ew.pw, ew.unlocked = "", false 907 return nil 908 } 909 910 // Locked indicates if the wallet has been unlocked. Part of the btc.Wallet 911 // interface. 912 func (ew *electrumWallet) Locked() bool { 913 ew.pwMtx.RLock() 914 defer ew.pwMtx.RUnlock() 915 return !ew.unlocked 916 } 917 918 // walletPass returns the wallet passphrase. Since an empty password is valid, 919 // use pass or locked to determine if locked. This is for convenience. 920 func (ew *electrumWallet) walletPass() string { 921 pw, _ := ew.pass() 922 return pw 923 } 924 925 // WalletUnlock attempts to unlock the wallet with the provided password. On 926 // success, the password is stored and may be accessed via pass or walletPass. 927 // Part of the btc.Wallet interface. 928 func (ew *electrumWallet) WalletUnlock(pw []byte) error { 929 if err := ew.testPass(pw); err != nil { 930 return err 931 } 932 ew.pwMtx.Lock() 933 ew.pw, ew.unlocked = string(pw), true 934 ew.pwMtx.Unlock() 935 return nil 936 } 937 938 // part of the btc.Wallet interface 939 func (ew *electrumWallet) PeerCount() (uint32, error) { 940 if ew.chain() == nil { // must work prior to resetChain 941 return 0, nil 942 } 943 944 info, err := ew.wallet.GetInfo(ew.ctx) 945 if err != nil { 946 return 0, err 947 } 948 select { 949 case <-ew.chain().Done(): 950 return 0, errors.New("electrumx server connection down") 951 default: 952 } 953 954 return uint32(info.Connections), nil 955 } 956 957 // part of the btc.Wallet interface 958 func (ew *electrumWallet) OwnsAddress(addr btcutil.Address) (bool, error) { 959 addrStr, err := ew.stringAddr(addr, ew.chainParams) 960 if err != nil { 961 return false, err 962 } 963 valid, mine, err := ew.wallet.CheckAddress(ew.ctx, addrStr) 964 if err != nil { 965 return false, err 966 } 967 if !valid { // maybe electrum doesn't know all encodings that btcutil does 968 return false, nil // an error here may prevent reconfiguring a misconfigured wallet 969 } 970 return mine, nil 971 } 972 973 // part of the btc.Wallet interface 974 func (ew *electrumWallet) SyncStatus() (*asset.SyncStatus, error) { 975 info, err := ew.wallet.GetInfo(ew.ctx) 976 if err != nil { 977 return nil, err 978 } 979 return &asset.SyncStatus{ 980 Synced: info.Connected && info.SyncHeight >= info.ServerHeight, 981 TargetHeight: uint64(info.ServerHeight), 982 Blocks: uint64(info.SyncHeight), 983 }, nil 984 } 985 986 // part of the btc.Wallet interface 987 func (ew *electrumWallet) ListTransactionsSinceBlock(blockHeight int32) ([]*ListTransactionsResult, error) { 988 bestHeight, err := ew.GetBestBlockHeight() 989 if err != nil { 990 return nil, fmt.Errorf("error getting best block: %v", err) 991 } 992 if bestHeight < blockHeight { 993 return nil, nil 994 } 995 var txs []*ListTransactionsResult 996 from := int64(blockHeight) 997 to := int64(blockHeight + 2000) 998 for { 999 if to > int64(bestHeight) { 1000 to = int64(bestHeight) 1001 } 1002 addTxs, err := ew.wallet.OnchainHistory(ew.ctx, from, to) 1003 if err != nil { 1004 return nil, fmt.Errorf("error getting onchain history: %v", err) 1005 } 1006 for _, tx := range addTxs { 1007 ltr := &ListTransactionsResult{ 1008 BlockHeight: uint32(tx.Height), 1009 BlockTime: uint64(tx.Timestamp), // Maybe? 1010 Send: !tx.Incoming, 1011 TxID: tx.TxID, 1012 } 1013 if tx.Fee != nil { 1014 f, err := strconv.ParseFloat(*tx.Fee, 64) 1015 if err != nil { 1016 return nil, fmt.Errorf("error parsing fee: %v", err) 1017 } 1018 ltr.Fee = &f 1019 } 1020 txs = append(txs, ltr) 1021 } 1022 if to == int64(bestHeight) { 1023 break 1024 } 1025 from = to 1026 to += 2000 1027 } 1028 return txs, nil 1029 } 1030 1031 // checkWalletTx will get the bytes and confirmations of a wallet transaction. 1032 // For non-wallet transactions, it is normal to see "Exception: Transaction not 1033 // in wallet" in Electrum's parent console, if launched from a terminal. 1034 // Part of the walletTxChecker interface. 1035 func (ew *electrumWallet) checkWalletTx(txid string) ([]byte, uint32, error) { 1036 // GetWalletTxConfs only works for wallet transactions, while 1037 // wallet.GetRawTransaction will try the wallet DB first, but fall back to 1038 // querying a server, so do GetWalletTxConfs first to prevent that. 1039 confs, err := ew.wallet.GetWalletTxConfs(ew.ctx, txid) 1040 if err != nil { 1041 return nil, 0, err 1042 } 1043 txRaw, err := ew.wallet.GetRawTransaction(ew.ctx, txid) 1044 if err != nil { 1045 return nil, 0, err 1046 } 1047 if confs < 0 { 1048 confs = 0 1049 } 1050 return txRaw, uint32(confs), nil 1051 } 1052 1053 // part of the walletTxChecker interface 1054 func (ew *electrumWallet) GetWalletTransaction(txHash *chainhash.Hash) (*GetTransactionResult, error) { 1055 // Try the wallet first. If it is not a wallet transaction or if it is 1056 // confirmed, fall back to the chain method to get the block info and time 1057 // fields. 1058 txid := txHash.String() 1059 txRaw, confs, err := ew.checkWalletTx(txid) 1060 if err == nil && confs == 0 { 1061 return &GetTransactionResult{ 1062 TxID: txid, 1063 Bytes: txRaw, 1064 // Time/TimeReceived? now? needed? 1065 }, nil 1066 } // else we have to ask a server for the verbose response with block info 1067 1068 txInfo, err := ew.chain().GetTransaction(ew.ctx, txid) 1069 if err != nil { 1070 return nil, err 1071 } 1072 txRaw, err = hex.DecodeString(txInfo.Hex) 1073 if err != nil { 1074 return nil, err 1075 } 1076 return &GetTransactionResult{ 1077 Confirmations: uint64(txInfo.Confirmations), 1078 BlockHash: txInfo.BlockHash, 1079 // BlockIndex unknown 1080 BlockTime: uint64(txInfo.BlockTime), 1081 TxID: txInfo.TxID, // txHash.String() 1082 Time: uint64(txInfo.Time), 1083 TimeReceived: uint64(txInfo.Time), 1084 Bytes: txRaw, 1085 }, nil 1086 } 1087 1088 func (ew *electrumWallet) Fingerprint() (string, error) { 1089 return "", fmt.Errorf("fingerprint not implemented") 1090 } 1091 1092 // part of the walletTxChecker interface 1093 func (ew *electrumWallet) SwapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error) { 1094 // To determine if it is spent, we need the address of the output. 1095 var pkScript []byte 1096 txid := txHash.String() 1097 // Try the wallet first in case this is a wallet transaction (own swap). 1098 txRaw, confs, err := ew.checkWalletTx(txid) 1099 if err == nil { 1100 msgTx, err := msgTxFromBytes(txRaw) 1101 if err != nil { 1102 return 0, false, err 1103 } 1104 if vout >= uint32(len(msgTx.TxOut)) { 1105 return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid) 1106 } 1107 pkScript = msgTx.TxOut[vout].PkScript 1108 } else { 1109 // Fall back to the more expensive server request. 1110 txInfo, err := ew.chain().GetTransaction(ew.ctx, txid) 1111 if err != nil { 1112 return 0, false, err 1113 } 1114 confs = uint32(txInfo.Confirmations) 1115 if txInfo.Confirmations < 1 { 1116 confs = 0 1117 } 1118 if vout >= uint32(len(txInfo.Vout)) { 1119 return 0, false, fmt.Errorf("output %d of tx %v does not exists", vout, txid) 1120 } 1121 txOut := &txInfo.Vout[vout] 1122 pkScript, err = hex.DecodeString(txOut.PkScript.Hex) 1123 if err != nil { 1124 return 0, false, fmt.Errorf("invalid pkScript: %w", err) 1125 } 1126 } 1127 1128 spent, err = ew.outputIsSpent(ew.ctx, txHash, vout, pkScript) 1129 if err != nil { 1130 return 0, false, err 1131 } 1132 return confs, spent, nil 1133 } 1134 1135 // tryRemoveLocalTx attempts to remove a "local" transaction from the Electrum 1136 // wallet. Such a transaction is unbroadcasted. This may be necessary if a 1137 // broadcast of a local txn attempt failed so that the inputs are available for 1138 // other transactions. 1139 func (ew *electrumWallet) tryRemoveLocalTx(ctx context.Context, txid string) { 1140 if err := ew.wallet.RemoveLocalTx(ctx, txid); err != nil { 1141 ew.log.Errorf("Failed to remove local transaction %s: %v", 1142 txid, err) 1143 } 1144 } 1145 1146 func (ew *electrumWallet) outPointAddress(ctx context.Context, txid string, vout uint32) (string, error) { 1147 txRaw, err := ew.wallet.GetRawTransaction(ctx, txid) 1148 if err != nil { 1149 return "", err 1150 } 1151 msgTx, err := msgTxFromBytes(txRaw) 1152 if err != nil { 1153 return "", err 1154 } 1155 if vout >= uint32(len(msgTx.TxOut)) { 1156 return "", fmt.Errorf("output %d of tx %v does not exists", vout, txid) 1157 } 1158 pkScript := msgTx.TxOut[vout].PkScript 1159 _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, ew.chainParams) 1160 if err != nil { 1161 return "", fmt.Errorf("invalid pkScript: %v", err) 1162 } 1163 if len(addrs) != 1 { 1164 return "", fmt.Errorf("invalid pkScript: %d addresses", len(addrs)) 1165 } 1166 addrStr, err := ew.stringAddr(addrs[0], ew.chainParams) 1167 if err != nil { 1168 return "", err 1169 } 1170 return addrStr, nil 1171 } 1172 1173 func (ew *electrumWallet) findOutputSpender(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*wire.MsgTx, uint32, error) { 1174 txid := txHash.String() 1175 addr, err := ew.outPointAddress(ctx, txid, vout) 1176 if err != nil { 1177 return nil, 0, fmt.Errorf("invalid outpoint address: %w", err) 1178 } 1179 // NOTE: Caller should already have determined the output is spent before 1180 // requesting the entire address history. 1181 hist, err := ew.wallet.GetAddressHistory(ctx, addr) 1182 if err != nil { 1183 return nil, 0, fmt.Errorf("unable to get address history: %w", err) 1184 } 1185 1186 sort.Slice(hist, func(i, j int) bool { 1187 return hist[i].Height > hist[j].Height // descending 1188 }) 1189 1190 var outHeight int64 1191 for _, io := range hist { 1192 if io.TxHash == txid { 1193 outHeight = io.Height 1194 continue // same txn 1195 } 1196 if io.Height < outHeight { 1197 break // spender not before the output's txn 1198 } 1199 txRaw, err := ew.wallet.GetRawTransaction(ctx, io.TxHash) 1200 if err != nil { 1201 ew.log.Warnf("Unable to retrieve transaction %v for address %v: %v", 1202 io.TxHash, addr, err) 1203 continue 1204 } 1205 msgTx, err := msgTxFromBytes(txRaw) 1206 if err != nil { 1207 ew.log.Warnf("Unable to decode transaction %v for address %v: %v", 1208 io.TxHash, addr, err) 1209 continue 1210 } 1211 for vin, txIn := range msgTx.TxIn { 1212 prevOut := &txIn.PreviousOutPoint 1213 if vout == prevOut.Index && prevOut.Hash.IsEqual(txHash) { 1214 return msgTx, uint32(vin), nil 1215 } 1216 } 1217 } 1218 1219 return nil, 0, nil // caller should check msgTx (internal method) 1220 } 1221 1222 func (ew *electrumWallet) AddressUsed(addrStr string) (bool, error) { 1223 txs, err := ew.wallet.GetAddressHistory(ew.ctx, addrStr) 1224 if err != nil { 1225 return false, fmt.Errorf("error getting address history: %w", err) 1226 } 1227 return len(txs) > 0, nil 1228 }