Skip to content

Commit

Permalink
core,btc,app: Manage SPV wallet peers
Browse files Browse the repository at this point in the history
  • Loading branch information
martonp committed Oct 30, 2022
1 parent 5550e84 commit 5fe8ba3
Show file tree
Hide file tree
Showing 27 changed files with 929 additions and 78 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 @@ -840,6 +840,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 @@ -895,6 +896,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 @@ -1173,7 +1194,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 5fe8ba3

Please sign in to comment.