Skip to content

Commit

Permalink
core,btc,app: Manage SPV wallet peers
Browse files Browse the repository at this point in the history
This change allows the user to see the peers the BTC SPV wallets are
connected to, and add new peers.
- `client/asset`: Adds a new `PeerManager` interface with three methods:
  `Peers`, `AddPeer` and `RemovePeer`. Wallets that implement this
  interface have the trait `WalletTraitPeerManager`.
- `client/core`: Adds new methods that call the `PeerManager` methods.
- `client/btc`:
  - A `SPVPeerManager` type is added which manages a key value db and a
    neutrino chain service. When the user adds a new peer, it is stored
    in the key value db, and will be automatically connected the next
    time the wallet is opened.
  - The `BTCWallet` interface now has three new functions,  `Peers`,
    `AddPeer` and `RemovePeer`, and the BTC, BCH, and LTC SPV wallets
    each use a `SPVPeerManager` in order to handle these new functions.
  • Loading branch information
martonp committed Nov 3, 2022
1 parent cad0336 commit f2798b0
Show file tree
Hide file tree
Showing 27 changed files with 936 additions and 87 deletions.
72 changes: 51 additions & 21 deletions client/asset/bch/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"sync/atomic"
Expand Down Expand Up @@ -35,6 +36,7 @@ import (
"github.com/gcash/bchd/bchec"
bchchaincfg "github.com/gcash/bchd/chaincfg"
bchchainhash "github.com/gcash/bchd/chaincfg/chainhash"
"github.com/gcash/bchd/peer"
bchtxscript "github.com/gcash/bchd/txscript"
bchwire "github.com/gcash/bchd/wire"
"github.com/gcash/bchlog"
Expand Down Expand Up @@ -83,13 +85,15 @@ type bchSPVWallet struct {
cl *neutrino.ChainService
loader *wallet.Loader
neutrinoDB walletdb.DB

peerManager *btc.SPVPeerManager
}

var _ btc.BTCWallet = (*bchSPVWallet)(nil)

// openSPVWallet creates a bchSPVWallet, but does not Start.
// Satisfies btc.BTCWalletConstructor.
func openSPVWallet(dir string, cfg *btc.WalletConfig, btcParams *chaincfg.Params, log dex.Logger) btc.BTCWallet {
func openSPVWallet(dir string, cfg *btc.WalletConfig, btcParams *chaincfg.Params, log dex.Logger) (btc.BTCWallet, error) {
var bchParams *bchchaincfg.Params
switch btcParams.Name {
case dexbch.MainNetParams.Name:
Expand All @@ -108,7 +112,7 @@ func openSPVWallet(dir string, cfg *btc.WalletConfig, btcParams *chaincfg.Params
allowAutomaticRescan: !cfg.ActivelyUsed,
}
w.birthdayV.Store(cfg.AdjustedBirthday())
return w
return w, nil
}

// createSPVWallet creates a new SPV wallet.
Expand Down Expand Up @@ -197,32 +201,13 @@ func (w *bchSPVWallet) Start() (btc.SPVService, error) {
}
errCloser.Add(w.neutrinoDB.Close)

// Depending on the network, we add some addpeers or a connect peer. On
// regtest, if the peers haven't been explicitly set, add the simnet harness
// alpha node as an additional peer so we don't have to type it in. On
// mainet and testnet4, add a known reliable persistent peer to be used in
// addition to normal DNS seed-based peer discovery.
var addPeers []string
var connectPeers []string
switch w.chainParams.Net {
// case bchwire.MainNet:
// addPeers = []string{"cfilters.ssgen.io"}
case bchwire.TestNet4:
// Add the address for a local bchd testnet4 node.
addPeers = []string{"localhost:28333"}
case bchwire.TestNet, bchwire.SimNet: // plain "wire.TestNet" is regnet!
connectPeers = []string{"localhost:21577"}
}

w.log.Debug("Starting neutrino chain service...")
w.cl, err = neutrino.NewChainService(neutrino.Config{
DataDir: w.dir,
Database: w.neutrinoDB,
ChainParams: *w.chainParams,
// https://github.com/gcash/neutrino/pull/36
PersistToDisk: true, // keep cfilter headers on disk for efficient rescanning
AddPeers: addPeers,
ConnectPeers: connectPeers,
// WARNING: PublishTransaction currently uses the entire duration
// because if an external bug, but even if the bug is resolved, a
// typical inv/getdata round trip is ~4 seconds, so we set this so
Expand Down Expand Up @@ -254,6 +239,22 @@ func (w *bchSPVWallet) Start() (btc.SPVService, error) {
w.ForceRescan()
}

var defaultPeers []string
switch w.chainParams.Net {
// case bchwire.MainNet:
// addPeers = []string{"cfilters.ssgen.io"}
case bchwire.TestNet4:
// Add the address for a local bchd testnet4 node.
defaultPeers = []string{"localhost:28333"}
case bchwire.TestNet, bchwire.SimNet: // plain "wire.TestNet" is regnet!
defaultPeers = []string{"localhost:21577"}
}
peerManager, err := btc.NewSPVPeerManager(&spvService{w.cl}, defaultPeers, w.dir, w.log)
if err != nil {
return nil, fmt.Errorf("failed to create peer manager: %w", err)
}
w.peerManager = peerManager

if err = w.chainClient.Start(); err != nil { // lazily starts connmgr
return nil, fmt.Errorf("couldn't start Neutrino client: %v", err)
}
Expand All @@ -263,6 +264,8 @@ func (w *bchSPVWallet) Start() (btc.SPVService, error) {

errCloser.Success()

w.peerManager.ConnectToInitialWalletPeers()

return &spvService{w.cl}, nil
}

Expand Down Expand Up @@ -746,6 +749,18 @@ func (w *bchSPVWallet) updateDBBirthday(bday time.Time) error {
})
}

func (w *bchSPVWallet) Peers() ([]*asset.WalletPeer, error) {
return w.peerManager.Peers()
}

func (w *bchSPVWallet) AddPeer(addr string) error {
return w.peerManager.AddPeer(addr)
}

func (w *bchSPVWallet) RemovePeer(addr string) error {
return w.peerManager.RemovePeer(addr)
}

// secretSource is used to locate keys and redemption scripts while signing a
// transaction. secretSource satisfies the txauthor.SecretsSource interface.
type secretSource struct {
Expand Down Expand Up @@ -834,6 +849,21 @@ func (s *spvService) Peers() []btc.SPVPeer {
return peers
}

func (s *spvService) AddPeer(addr string) error {
serverPeer := neutrino.NewServerPeer(s.ChainService, true)
peer, err := peer.NewOutboundPeer(neutrino.NewPeerConfig(serverPeer), addr)
if err != nil {
return err
}
conn, err := net.Dial("tcp", peer.Addr())
if err != nil {
return err
}
peer.AssociateConnection(conn)
serverPeer.Peer = peer
return nil
}

func (s *spvService) GetBlockHeight(h *chainhash.Hash) (int32, error) {
return s.ChainService.GetBlockHeight((*bchchainhash.Hash)(h))
}
Expand Down
26 changes: 25 additions & 1 deletion client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ var _ asset.Rescanner = (*ExchangeWalletSPV)(nil)
var _ asset.FeeRater = (*ExchangeWalletFullNode)(nil)
var _ asset.LogFiler = (*ExchangeWalletSPV)(nil)
var _ asset.Recoverer = (*ExchangeWalletSPV)(nil)
var _ asset.PeerManager = (*ExchangeWalletSPV)(nil)
var _ asset.TxFeeEstimator = (*intermediaryWallet)(nil)

// RecoveryCfg is the information that is transferred from the old wallet
Expand Down Expand Up @@ -909,6 +910,26 @@ func (btc *ExchangeWalletSPV) Rescan(_ context.Context) error {
return w.wallet.RescanAsync()
}

// Peers returns a list of peers that the wallet is connected to.
func (btc *ExchangeWalletSPV) Peers() ([]*asset.WalletPeer, error) {
w := btc.node.(*spvWallet)
return w.peers()
}

// AddPeer connects the wallet to a new peer. The peer's address will be
// persisted and connected to each time the wallet is started up.
func (btc *ExchangeWalletSPV) AddPeer(addr string) error {
w := btc.node.(*spvWallet)
return w.addPeer(addr)
}

// RemovePeer will remove a peer that was added by AddPeer. This peer may
// still be connected to by the wallet if it discovers it on it's own.
func (btc *ExchangeWalletSPV) RemovePeer(addr string) error {
w := btc.node.(*spvWallet)
return w.removePeer(addr)
}

// FeeRate satisfies asset.FeeRater.
func (btc *ExchangeWalletFullNode) FeeRate() uint64 {
rate, err := btc.estimateFee(btc.node, 1)
Expand Down Expand Up @@ -1187,7 +1208,10 @@ func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*E
decodeAddr: btc.decodeAddr,
}

spvw.wallet = walletConstructor(spvw.dir, spvw.cfg, spvw.chainParams, spvw.log)
spvw.wallet, err = walletConstructor(spvw.dir, spvw.cfg, spvw.chainParams, spvw.log)
if err != nil {
return nil, err
}

btc.node = spvw

Expand Down
58 changes: 38 additions & 20 deletions client/asset/btc/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"sync/atomic"
Expand Down Expand Up @@ -52,6 +53,8 @@ type btcSPVWallet struct {
// rescanStarting is set while reloading the wallet and dropping
// transactions from the wallet db.
rescanStarting uint32 // atomic

peerManager *SPVPeerManager
}

var _ BTCWallet = (*btcSPVWallet)(nil)
Expand Down Expand Up @@ -113,7 +116,7 @@ func createSPVWallet(privPass []byte, seed []byte, bday time.Time, dataDir strin

// openSPVWallet is the BTCWalletConstructor for Bitcoin.
func openSPVWallet(dir string, cfg *WalletConfig,
chainParams *chaincfg.Params, log dex.Logger) BTCWallet {
chainParams *chaincfg.Params, log dex.Logger) (BTCWallet, error) {

w := &btcSPVWallet{
dir: dir,
Expand All @@ -122,7 +125,7 @@ func openSPVWallet(dir string, cfg *WalletConfig,
allowAutomaticRescan: !cfg.ActivelyUsed,
}
w.birthdayV.Store(cfg.AdjustedBirthday())
return w
return w, nil
}

func (w *btcSPVWallet) Birthday() time.Time {
Expand Down Expand Up @@ -174,40 +177,41 @@ func (w *btcSPVWallet) Start() (SPVService, error) {
}
errCloser.Add(w.neutrinoDB.Close)

// Depending on the network, we add some addpeers or a connect peer. On
// regtest, if the peers haven't been explicitly set, add the simnet harness
// alpha node as an additional peer so we don't have to type it in. On
// mainet and testnet3, add a known reliable persistent peer to be used in
// addition to normal DNS seed-based peer discovery.
var addPeers []string
var connectPeers []string
switch w.chainParams.Net {
case wire.MainNet:
addPeers = []string{"cfilters.ssgen.io"}
case wire.TestNet3:
addPeers = []string{"dex-test.ssgen.io"}
case wire.TestNet, wire.SimNet: // plain "wire.TestNet" is regnet!
connectPeers = []string{"localhost:20575"}
}
w.log.Debug("Starting neutrino chain service...")
w.cl, err = neutrino.NewChainService(neutrino.Config{
DataDir: w.dir,
Database: w.neutrinoDB,
ChainParams: *w.chainParams,
PersistToDisk: true, // keep cfilter headers on disk for efficient rescanning
AddPeers: addPeers,
ConnectPeers: connectPeers,
// AddPeers: addPeers,
// ConnectPeers: connectPeers,
// WARNING: PublishTransaction currently uses the entire duration
// because if an external bug, but even if the resolved, a typical
// inv/getdata round trip is ~4 seconds, so we set this so neutrino does
// not cancel queries too readily.
BroadcastTimeout: 6 * time.Second,
NameResolver: net.LookupIP,
})
if err != nil {
return nil, fmt.Errorf("couldn't create Neutrino ChainService: %v", err)
return nil, fmt.Errorf("couldn't create Neutrino ChainService: %w", err)
}
errCloser.Add(w.cl.Stop)

var defaultPeers []string
switch w.chainParams.Net {
case wire.MainNet:
defaultPeers = []string{"cfilters.ssgen.io:8333"}
case wire.TestNet3:
defaultPeers = []string{"dex-test.ssgen.io:18333"}
case wire.TestNet, wire.SimNet: // plain "wire.TestNet" is regnet!
defaultPeers = []string{"localhost:20575"}
}
peerManager, err := NewSPVPeerManager(&btcChainService{w.cl}, defaultPeers, w.dir, w.log)
if err != nil {
return nil, fmt.Errorf("failed to create peer manager: %w", err)
}
w.peerManager = peerManager

w.chainClient = chain.NewNeutrinoClient(w.chainParams, w.cl)
w.Wallet = btcw

Expand Down Expand Up @@ -238,6 +242,8 @@ func (w *btcSPVWallet) Start() (SPVService, error) {

errCloser.Success()

w.peerManager.ConnectToInitialWalletPeers()

return &btcChainService{w.cl}, nil
}

Expand Down Expand Up @@ -434,6 +440,18 @@ func (w *btcSPVWallet) BlockNotifications(ctx context.Context) <-chan *BlockNoti
return ch
}

func (w *btcSPVWallet) AddPeer(addr string) error {
return w.peerManager.AddPeer(addr)
}

func (w *btcSPVWallet) RemovePeer(addr string) error {
return w.peerManager.RemovePeer(addr)
}

func (w *btcSPVWallet) Peers() ([]*asset.WalletPeer, error) {
return w.peerManager.Peers()
}

// secretSource is used to locate keys and redemption scripts while signing a
// transaction. secretSource satisfies the txauthor.SecretsSource interface.
type secretSource struct {
Expand Down
Loading

0 comments on commit f2798b0

Please sign in to comment.