Skip to content

Commit

Permalink
client: monitor wallet peers
Browse files Browse the repository at this point in the history
  • Loading branch information
chappjc committed Feb 11, 2022
1 parent 1298846 commit 0ca5b52
Show file tree
Hide file tree
Showing 33 changed files with 385 additions and 69 deletions.
54 changes: 47 additions & 7 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ import (
const (
version = 0

// Use RawRequest to get the verbose block header for a blockhash.
methodGetBlockHeader = "getblockheader"
// Use RawRequest to get the verbose block with verbose txs, as the btcd
// rpcclient.Client's GetBlockVerboseTx appears to be busted.
methodGetNetworkInfo = "getnetworkinfo"
methodGetBlockchainInfo = "getblockchaininfo"
// BipID is the BIP-0044 asset ID.
BipID = 0

Expand Down Expand Up @@ -81,6 +75,7 @@ const (
var (
// blockTicker is the delay between calls to check for new blocks.
blockTicker = time.Second
peerCountTicker = 5 * time.Second
walletBlockAllowance = time.Second * 10
conventionalConversionFactor = float64(dexbtc.UnitInfo.Conventional.ConversionFactor)
rpcOpts = []*asset.ConfigOption{
Expand Down Expand Up @@ -513,6 +508,8 @@ type ExchangeWallet struct {
log dex.Logger
symbol string
tipChange func(error)
lastPeerCount uint32
peersChange func(uint32)
minNetworkVersion uint64
fallbackFeeRate uint64
feeRateLimit uint64
Expand Down Expand Up @@ -727,6 +724,7 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*ExchangeW
chainParams: cfg.ChainParams,
log: cfg.Logger,
tipChange: cfg.WalletCFG.TipChange,
peersChange: cfg.WalletCFG.PeersChange,
fundingCoins: make(map[outPoint]*utxo),
findRedemptionQueue: make(map[outPoint]*findRedemptionReq),
minNetworkVersion: cfg.MinNetworkVersion,
Expand Down Expand Up @@ -823,6 +821,11 @@ func (btc *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error)
btc.watchBlocks(ctx)
btc.shutdown()
}()
wg.Add(1)
go func() {
defer wg.Done()
btc.monitorPeers(ctx)
}()
return &wg, nil
}

Expand Down Expand Up @@ -865,7 +868,14 @@ func (btc *ExchangeWallet) SyncStatus() (bool, float32, error) {
}
return false, progress, nil
}
return true, 1, nil

// It looks like we are ready based on syncStatus, but that may just be
// comparing wallet height to known chain height. Now check peers.
numPeers, err := btc.node.peerCount()
if err != nil {
return false, 0, err
}
return numPeers > 0, 1, nil
}

// OwnsAddress indicates if an address belongs to the wallet.
Expand Down Expand Up @@ -2692,6 +2702,36 @@ func (btc *ExchangeWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes)
return uint32(tx.Confirmations), nil
}

func (btc *ExchangeWallet) checkPeers() {
numPeers, err := btc.node.peerCount()
if err != nil {
prevPeer := atomic.SwapUint32(&btc.lastPeerCount, 0)
if prevPeer != 0 {
btc.log.Errorf("Failed to get peer count: %v", err)
btc.peersChange(0)
}
return
}
prevPeer := atomic.SwapUint32(&btc.lastPeerCount, numPeers)
if prevPeer != numPeers {
btc.peersChange(numPeers)
}
}

func (btc *ExchangeWallet) monitorPeers(ctx context.Context) {
ticker := time.NewTicker(peerCountTicker)
defer ticker.Stop()
for {
btc.checkPeers()

select {
case <-ticker.C:
case <-ctx.Done():
return
}
}
}

// watchBlocks pings for new blocks and runs the tipChange callback function
// when the block changes.
func (btc *ExchangeWallet) watchBlocks(ctx context.Context) {
Expand Down
7 changes: 7 additions & 0 deletions client/asset/btc/btc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ type tRawRequester struct {
func (c *tRawRequester) RawRequest(_ context.Context, method string, params []json.RawMessage) (json.RawMessage, error) {
switch method {
// TODO: handle methodGetBlockHash and add actual tests to cover it.
case methodGetNetworkInfo:
return json.Marshal(&btcjson.GetNetworkInfoResult{
Connections: 1,
})
case methodEstimateSmartFee:
if c.testData.estFeeErr != nil {
return nil, c.testData.estFeeErr
Expand Down Expand Up @@ -580,6 +584,9 @@ func tNewWallet(segwit bool, walletType string) (*ExchangeWallet, *testData, fun
default:
}
},
PeersChange: func(num uint32) {
fmt.Println("peer count: ", num)
},
}
walletCtx, shutdown := context.WithCancel(tCtx)
cfg := &BTCCloneCFG{
Expand Down
3 changes: 3 additions & 0 deletions client/asset/btc/livetest/livetest.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func tBackend(ctx context.Context, t *testing.T, cfg *Config, node, name string,
TipChange: func(err error) {
blkFunc(reportName, err)
},
PeersChange: func(num uint32) {
fmt.Println("peer count: ", num)
},
}

w, err := cfg.NewWallet(walletCfg, logger, dex.Regtest)
Expand Down
14 changes: 14 additions & 0 deletions client/asset/btc/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ const (
methodGetBestBlockHash = "getbestblockhash"
methodGetRawMempool = "getrawmempool"
methodGetRawTransaction = "getrawtransaction"
methodGetBlockHeader = "getblockheader"
methodGetNetworkInfo = "getnetworkinfo"
methodGetBlockchainInfo = "getblockchaininfo"
)

// RawRequester defines decred's rpcclient RawRequest func where all RPC
Expand Down Expand Up @@ -532,6 +535,17 @@ func (wc *rpcClient) getBlockHeight(blockHash *chainhash.Hash) (int32, error) {
return int32(hdr.Height), nil
}

func (wc *rpcClient) peerCount() (uint32, error) {
var r struct {
Connections uint32 `json:"connections"`
}
err := wc.call(methodGetNetworkInfo, nil, &r)
if err != nil {
return 0, err
}
return r.Connections, nil
}

// getBlockchainInfo sends the getblockchaininfo request and returns the result.
func (wc *rpcClient) getBlockchainInfo() (*getBlockchainInfoResult, error) {
chainInfo := new(getBlockchainInfoResult)
Expand Down
4 changes: 4 additions & 0 deletions client/asset/btc/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ func (w *spvWallet) getChainHeight() (int32, error) {
return blk.Height, err
}

func (w *spvWallet) peerCount() (uint32, error) {
return uint32(len(w.cl.Peers())), nil
}

// syncHeight is the best known sync height among peers.
func (w *spvWallet) syncHeight() int32 {
var maxHeight int32
Expand Down
1 change: 1 addition & 0 deletions client/asset/btc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Wallet interface {
sendToAddress(address string, value, feeRate uint64, subtract bool) (*chainhash.Hash, error)
locked() bool
syncStatus() (*syncStatus, error)
peerCount() (uint32, error)
swapConfirmations(txHash *chainhash.Hash, vout uint32, contract []byte, startTime time.Time) (confs uint32, spent bool, err error)
getBlockHeader(blockHash *chainhash.Hash) (*blockHeader, error)
ownsAddress(addr btcutil.Address) (bool, error)
Expand Down
42 changes: 42 additions & 0 deletions client/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"decred.org/dcrdex/client/asset"
Expand Down Expand Up @@ -69,6 +70,7 @@ const (
var (
// blockTicker is the delay between calls to check for new blocks.
blockTicker = time.Second
peerCountTicker = 5 * time.Second
conventionalConversionFactor = float64(dexdcr.UnitInfo.Conventional.ConversionFactor)
configOpts = []*asset.ConfigOption{
{
Expand Down Expand Up @@ -386,6 +388,8 @@ type ExchangeWallet struct {
log dex.Logger
acct string
tipChange func(error)
lastPeerCount uint32
peersChange func(uint32)
fallbackFeeRate uint64
feeRateLimit uint64
redeemConfTarget uint64
Expand Down Expand Up @@ -501,6 +505,7 @@ func unconnectedWallet(cfg *asset.WalletConfig, dcrCfg *Config, chainParams *cha
chainParams: chainParams,
acct: dcrCfg.Account,
tipChange: cfg.TipChange,
peersChange: cfg.PeersChange,
fundingCoins: make(map[outPoint]*fundingCoin),
findRedemptionQueue: make(map[outPoint]*findRedemptionReq),
externalTxCache: make(map[chainhash.Hash]*externalTx),
Expand Down Expand Up @@ -582,6 +587,11 @@ func (dcr *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error)
}
dcr.shutdown()
}()
wg.Add(1)
go func() {
defer wg.Done()
dcr.monitorPeers(ctx)
}()
return &wg, nil
}

Expand Down Expand Up @@ -3066,6 +3076,38 @@ func (dcr *ExchangeWallet) getKeys(addr stdaddr.Address) (*secp256k1.PrivateKey,
return priv, priv.PubKey(), nil
}

func (dcr *ExchangeWallet) checkPeers() {
ctx, cancel := context.WithTimeout(dcr.ctx, 2*time.Second)
defer cancel()
numPeers, err := dcr.wallet.PeerCount(ctx)
if err != nil { // e.g. dcrd passthrough fail in non-SPV mode
prevPeer := atomic.SwapUint32(&dcr.lastPeerCount, 0)
if prevPeer != 0 {
dcr.log.Errorf("Failed to get peer count: %v", err)
dcr.peersChange(0)
}
return
}
prevPeer := atomic.SwapUint32(&dcr.lastPeerCount, numPeers)
if prevPeer != numPeers {
dcr.peersChange(numPeers)
}
}

func (dcr *ExchangeWallet) monitorPeers(ctx context.Context) {
ticker := time.NewTicker(peerCountTicker)
defer ticker.Stop()
for {
dcr.checkPeers()

select {
case <-ticker.C:
case <-ctx.Done():
return
}
}
}

// monitorBlocks pings for new blocks and runs the tipChange callback function
// when the block changes. New blocks are also scanned for potential contract
// redeems.
Expand Down
9 changes: 8 additions & 1 deletion client/asset/dcr/dcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ func newTxOutResult(script []byte, value uint64, confs int64) *chainjson.GetTxOu
func tNewWallet() (*ExchangeWallet, *tRPCClient, func(), error) {
client := newTRPCClient()
walletCfg := &asset.WalletConfig{
TipChange: func(error) {},
TipChange: func(error) {},
PeersChange: func(uint32) {},
}
walletCtx, shutdown := context.WithCancel(tCtx)
wallet, err := unconnectedWallet(walletCfg, &Config{Account: "default"}, tChainParams, tLogger)
Expand Down Expand Up @@ -530,6 +531,12 @@ func (c *tRPCClient) RawRequest(_ context.Context, method string, params []json.
}

switch method {
case methodGetPeerInfo:
return json.Marshal([]*walletjson.GetPeerInfoResult{
{
Addr: "127.0.0.1",
},
})
case methodGetCFilterV2:
if len(params) != 1 {
return nil, fmt.Errorf("getcfilterv2 requires 1 param, got %d", len(params))
Expand Down
21 changes: 20 additions & 1 deletion client/asset/dcr/rpcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
methodListLockUnspent = "listlockunspent"
methodSignRawTransaction = "signrawtransaction"
methodSyncStatus = "syncstatus"
methodGetPeerInfo = "getpeerinfo"
)

// rpcWallet implements Wallet functionality using an rpc client to communicate
Expand Down Expand Up @@ -480,6 +481,7 @@ func (w *rpcWallet) GetNewAddressGapPolicy(ctx context.Context, account string,
// SignRawTransaction signs the provided transaction using rpc RawRequest.
// Part of the Wallet interface.
func (w *rpcWallet) SignRawTransaction(ctx context.Context, txHex string) (*walletjson.SignRawTransactionResult, error) {
// w.rpcClient.SignRawTransaction exists
var res walletjson.SignRawTransactionResult
err := w.rpcClientRawRequest(ctx, methodSignRawTransaction, anylist{txHex}, &res)
return &res, err
Expand Down Expand Up @@ -618,7 +620,24 @@ func (w *rpcWallet) SyncStatus(ctx context.Context) (bool, float32, error) {
return false, 0, fmt.Errorf("rawrequest error: %w", err)
}
ready := syncStatus.Synced && !syncStatus.InitialBlockDownload
return ready, syncStatus.HeadersFetchProgress, nil
if !ready {
return false, syncStatus.HeadersFetchProgress, nil
}
// It looks like we are ready based on syncstatus, but that may just be
// comparing wallet height to known chain height. Now check peers.
numPeers, err := w.PeerCount(ctx)
if err != nil {
return false, 0, err
}
return numPeers > 0, syncStatus.HeadersFetchProgress, nil
}

// PeerCount returns the number of network peers to which the wallet or its
// backing node are connected.
func (w *rpcWallet) PeerCount(ctx context.Context) (uint32, error) {
var peerInfo []*walletjson.GetPeerInfoResult
err := w.rpcClientRawRequest(ctx, methodGetPeerInfo, nil, &peerInfo)
return uint32(len(peerInfo)), err
}

// AddressPrivKey fetches the privkey for the specified address.
Expand Down
3 changes: 3 additions & 0 deletions client/asset/dcr/simnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ func tBackend(t *testing.T, name string, blkFunc func(string, error)) (*Exchange
TipChange: func(err error) {
blkFunc(name, err)
},
PeersChange: func(num uint32) {
t.Log("peer count: ", num)
},
}
var backend asset.Wallet
backend, err = NewWallet(walletCfg, tLogger, dex.Simnet)
Expand Down
3 changes: 3 additions & 0 deletions client/asset/dcr/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ type Wallet interface {
UnlockAccount(ctx context.Context, account, passphrase string) error
// SyncStatus returns the wallet's sync status.
SyncStatus(ctx context.Context) (bool, float32, error)
// PeerCount returns the number of network peers to which the wallet or its
// backing node are connected.
PeerCount(ctx context.Context) (uint32, error)
// AddressPrivKey fetches the privkey for the specified address.
AddressPrivKey(ctx context.Context, address stdaddr.Address) (*dcrutil.WIF, error)
}
Expand Down
Loading

0 comments on commit 0ca5b52

Please sign in to comment.