decred.org/dcrdex@v1.0.3/client/asset/btc/spv_peer_manager.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  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  
    16  	"decred.org/dcrdex/client/asset"
    17  	"decred.org/dcrdex/dex"
    18  )
    19  
    20  type peerSource uint16
    21  
    22  const (
    23  	added peerSource = iota
    24  	defaultPeer
    25  )
    26  
    27  func (s peerSource) toAssetPeerSource() asset.PeerSource {
    28  	switch s {
    29  	case added:
    30  		return asset.UserAdded
    31  	case defaultPeer:
    32  		return asset.WalletDefault
    33  	}
    34  	return asset.Discovered
    35  }
    36  
    37  type walletPeer struct {
    38  	source       peerSource
    39  	resolvedName string
    40  }
    41  
    42  // PeerManagerChainService are the functions needed for an SPVPeerManager
    43  // to communicate with a chain service.
    44  type PeerManagerChainService interface {
    45  	AddPeer(addr string) error
    46  	RemovePeer(addr string) error
    47  	Peers() []SPVPeer
    48  }
    49  
    50  // SPVPeerManager implements peer management functionality for all bitcoin
    51  // clone SPV wallets.
    52  type SPVPeerManager struct {
    53  	cs PeerManagerChainService
    54  
    55  	peersMtx sync.RWMutex
    56  	peers    map[string]*walletPeer
    57  
    58  	savedPeersFilePath string
    59  
    60  	defaultPeers []string
    61  
    62  	defaultPort string
    63  
    64  	log dex.Logger
    65  }
    66  
    67  // NewSPVPeerManager creates a new SPVPeerManager.
    68  func NewSPVPeerManager(cs PeerManagerChainService, defaultPeers []string, dir string, log dex.Logger, defaultPort string) *SPVPeerManager {
    69  	return &SPVPeerManager{
    70  		cs:                 cs,
    71  		defaultPeers:       defaultPeers,
    72  		peers:              make(map[string]*walletPeer),
    73  		savedPeersFilePath: filepath.Join(dir, "dexc-peers.json"), // peers.json is used by neutrino
    74  		log:                log,
    75  		defaultPort:        defaultPort,
    76  	}
    77  }
    78  
    79  func (s *SPVPeerManager) connectedPeers() map[string]struct{} {
    80  	peers := s.cs.Peers()
    81  	connectedPeers := make(map[string]struct{}, len(peers))
    82  	for _, peer := range peers {
    83  		connectedPeers[peer.Addr()] = struct{}{}
    84  	}
    85  	return connectedPeers
    86  }
    87  
    88  // Peers returns the list of peers that the wallet is connected to. It also
    89  // returns the peers that the user added that the wallet may not currently
    90  // be connected to.
    91  func (s *SPVPeerManager) Peers() ([]*asset.WalletPeer, error) {
    92  	s.peersMtx.RLock()
    93  	defer s.peersMtx.RUnlock()
    94  
    95  	connectedPeers := s.connectedPeers()
    96  
    97  	walletPeers := make([]*asset.WalletPeer, 0, len(connectedPeers))
    98  
    99  	for originalAddr, peer := range s.peers {
   100  		_, connected := connectedPeers[peer.resolvedName]
   101  		delete(connectedPeers, peer.resolvedName)
   102  		walletPeers = append(walletPeers, &asset.WalletPeer{
   103  			Addr:      originalAddr,
   104  			Connected: connected,
   105  			Source:    peer.source.toAssetPeerSource(),
   106  		})
   107  	}
   108  
   109  	for peer := range connectedPeers {
   110  		walletPeers = append(walletPeers, &asset.WalletPeer{
   111  			Addr:      peer,
   112  			Connected: true,
   113  			Source:    asset.Discovered,
   114  		})
   115  	}
   116  
   117  	return walletPeers, nil
   118  }
   119  
   120  // resolveAddress resolves an address to ip:port. This is needed because neutrino
   121  // internally resolves the address, and when neutrino is called to return its list
   122  // of peers, it will return the resolved addresses. Therefore, we call neutrino
   123  // with the resolved address, then we keep track of the mapping of address to
   124  // resolved address in order to be able to display the address the user provided
   125  // back to the user.
   126  func (s *SPVPeerManager) resolveAddress(addr string) (string, error) {
   127  	host, strPort, err := net.SplitHostPort(addr)
   128  	if err != nil {
   129  		switch err.(type) {
   130  		case *net.AddrError:
   131  			host = addr
   132  			strPort = s.defaultPort
   133  		default:
   134  			return "", err
   135  		}
   136  	}
   137  
   138  	// Tor addresses cannot be resolved to an IP, so just return onionAddr
   139  	// instead.
   140  	if strings.HasSuffix(host, ".onion") {
   141  		return addr, nil
   142  	}
   143  
   144  	ips, err := net.LookupIP(host)
   145  	if err != nil {
   146  		return "", err
   147  	}
   148  	if len(ips) == 0 {
   149  		return "", fmt.Errorf("no addresses found for %s", host)
   150  	}
   151  
   152  	var ip string
   153  	if host == "localhost" && len(ips) > 1 {
   154  		ip = "127.0.0.1"
   155  	} else {
   156  		ip = ips[0].String()
   157  	}
   158  
   159  	return net.JoinHostPort(ip, strPort), nil
   160  }
   161  
   162  // peerWithResolvedAddress checks to see if there is a peer with a resolved
   163  // address in s.peers, and if so, returns the address that was user to add
   164  // the peer.
   165  func (s *SPVPeerManager) peerWithResolvedAddr(resolvedAddr string) (string, bool) {
   166  	for originalAddr, peer := range s.peers {
   167  		if peer.resolvedName == resolvedAddr {
   168  			return originalAddr, true
   169  		}
   170  	}
   171  	return "", false
   172  }
   173  
   174  // loadSavedPeersFromFile returns the contents of dexc-peers.json.
   175  func (s *SPVPeerManager) loadSavedPeersFromFile() (map[string]peerSource, error) {
   176  	content, err := os.ReadFile(s.savedPeersFilePath)
   177  	if errors.Is(err, os.ErrNotExist) {
   178  		return make(map[string]peerSource), nil
   179  	}
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	peers := make(map[string]peerSource)
   185  	err = json.Unmarshal(content, &peers)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	return peers, nil
   191  }
   192  
   193  // writeSavedPeersToFile replaces the contents of dexc-peers.json.
   194  func (s *SPVPeerManager) writeSavedPeersToFile(peers map[string]peerSource) error {
   195  	content, err := json.Marshal(peers)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	return os.WriteFile(s.savedPeersFilePath, content, 0644)
   200  }
   201  
   202  func (s *SPVPeerManager) addPeer(addr string, source peerSource, initialLoad bool) error {
   203  	s.peersMtx.Lock()
   204  	defer s.peersMtx.Unlock()
   205  
   206  	resolvedAddr, err := s.resolveAddress(addr)
   207  	if err != nil {
   208  		if initialLoad {
   209  			// If this is the initial load, we still want to add peers that are
   210  			// not able to be connected to the peers map, in order to display them
   211  			// to the user. If a user previously added a peer that originally connected
   212  			// but now the address cannot be resolved to an IP, it should be displayed
   213  			// that the wallet was unable to connect to that peer.
   214  			s.peers[addr] = &walletPeer{source: source}
   215  		}
   216  		return fmt.Errorf("failed to resolve address: %v", err)
   217  	}
   218  
   219  	if duplicatePeer, found := s.peerWithResolvedAddr(resolvedAddr); found {
   220  		return fmt.Errorf("%s and %s resolve to the same node", duplicatePeer, addr)
   221  	}
   222  
   223  	s.peers[addr] = &walletPeer{source: source, resolvedName: resolvedAddr}
   224  
   225  	if !initialLoad {
   226  		savedPeers, err := s.loadSavedPeersFromFile()
   227  		if err != nil {
   228  			s.log.Errorf("failed to load saved peers from file")
   229  		} else {
   230  			savedPeers[addr] = source
   231  			err = s.writeSavedPeersToFile(savedPeers)
   232  			if err != nil {
   233  				s.log.Errorf("failed to add peer to saved peers file: %v")
   234  			}
   235  		}
   236  	}
   237  
   238  	connectedPeers := s.connectedPeers()
   239  	_, connected := connectedPeers[resolvedAddr]
   240  	if !connected {
   241  		return s.cs.AddPeer(resolvedAddr)
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  // AddPeer connects to a new peer and stores it in the db.
   248  func (s *SPVPeerManager) AddPeer(addr string) error {
   249  	return s.addPeer(addr, added, false)
   250  }
   251  
   252  // RemovePeer disconnects from a peer added by the user and removes it from
   253  // the db.
   254  func (s *SPVPeerManager) RemovePeer(addr string) error {
   255  	s.peersMtx.Lock()
   256  	defer s.peersMtx.Unlock()
   257  
   258  	peer, found := s.peers[addr]
   259  	if !found {
   260  		return fmt.Errorf("peer not found: %v", addr)
   261  	}
   262  
   263  	savedPeers, err := s.loadSavedPeersFromFile()
   264  	if err != nil {
   265  		return err
   266  	}
   267  	delete(savedPeers, addr)
   268  	err = s.writeSavedPeersToFile(savedPeers)
   269  	if err != nil {
   270  		s.log.Errorf("failed to delete peer from saved peers file: %v")
   271  	} else {
   272  		delete(s.peers, addr)
   273  	}
   274  
   275  	connectedPeers := s.connectedPeers()
   276  	_, connected := connectedPeers[peer.resolvedName]
   277  	if connected {
   278  		return s.cs.RemovePeer(peer.resolvedName)
   279  	}
   280  
   281  	return nil
   282  }
   283  
   284  // ConnectToInitialWalletPeers connects to the default peers and the peers
   285  // that were added by the user and persisted in the db.
   286  func (s *SPVPeerManager) ConnectToInitialWalletPeers() {
   287  	for _, peer := range s.defaultPeers {
   288  		err := s.addPeer(peer, defaultPeer, true)
   289  		if err != nil {
   290  			s.log.Errorf("failed to add default peer %s: %v", peer, err)
   291  		}
   292  	}
   293  
   294  	savedPeers, err := s.loadSavedPeersFromFile()
   295  	if err != nil {
   296  		s.log.Errorf("failed to load saved peers from file: v", err)
   297  		return
   298  	}
   299  
   300  	for addr := range savedPeers {
   301  		err := s.addPeer(addr, added, true)
   302  		if err != nil {
   303  			s.log.Errorf("failed to add peer %s: %v", addr, err)
   304  		}
   305  	}
   306  }