diff --git a/btcwallet.go b/btcwallet.go index b3be088..af87b0e 100644 --- a/btcwallet.go +++ b/btcwallet.go @@ -10,12 +10,15 @@ import ( "net/http" _ "net/http/pprof" "os" + "path/filepath" "runtime" "sync" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/rpc/legacyrpc" "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/lightninglabs/neutrino" ) var ( @@ -140,13 +143,50 @@ func walletMain() error { // associated with the server for RPC passthrough and to enable additional // methods. func rpcClientConnectLoop(legacyRPCServer *legacyrpc.Server, loader *wallet.Loader) { - certs := readCAFile() + var certs []byte + if !cfg.UseSPV { + certs = readCAFile() + } for { - chainClient, err := startChainRPC(certs) - if err != nil { - log.Errorf("Unable to open connection to consensus RPC server: %v", err) - continue + var ( + chainClient chain.Interface + err error + ) + + if cfg.UseSPV { + var ( + chainService *neutrino.ChainService + spvdb walletdb.DB + ) + netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params) + spvdb, err = walletdb.Create("bdb", + filepath.Join(netDir, "neutrino.db")) + defer spvdb.Close() + if err != nil { + log.Errorf("Unable to create Neutrino DB: %s", err) + continue + } + chainService, err = neutrino.NewChainService( + neutrino.Config{ + DataDir: netDir, + Database: spvdb, + ChainParams: *activeNet.Params, + ConnectPeers: cfg.ConnectPeers, + AddPeers: cfg.AddPeers, + }) + if err != nil { + log.Errorf("Couldn't create Neutrino ChainService: %s", err) + continue + } + chainService.Start() + chainClient = chain.NewSPVChain(chainService) + } else { + chainClient, err = startChainRPC(certs) + if err != nil { + log.Errorf("Unable to open connection to consensus RPC server: %v", err) + continue + } } // Rather than inlining this logic directly into the loader diff --git a/chain/neutrino.go b/chain/neutrino.go index 79397a8..237f50d 100644 --- a/chain/neutrino.go +++ b/chain/neutrino.go @@ -78,6 +78,7 @@ func (s *SPVChain) WaitForShutdown() { func (s *SPVChain) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) { // TODO(roasbeef): add a block cache? // * which evication strategy? depends on use case + // Should the block cache be INSIDE neutrino instead of in btcwallet? block, err := s.cs.GetBlockFromNetwork(*hash) if err != nil { return nil, err @@ -85,6 +86,18 @@ func (s *SPVChain) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) { return block.MsgBlock(), nil } +// GetBlockHeight gets the height of a block by its hash. It serves as a +// replacement for the use of GetBlockVerboseTxAsync for the wallet package +// since we can't actually return a FutureGetBlockVerboseResult because the +// underlying type is private to btcrpcclient. +func (s *SPVChain) GetBlockHeight(hash *chainhash.Hash) (int32, error) { + _, height, err := s.cs.GetBlockByHash(*hash) + if err != nil { + return 0, err + } + return int32(height), nil +} + // GetBestBlock replicates the RPC client's GetBestBlock command. func (s *SPVChain) GetBestBlock() (*chainhash.Hash, int32, error) { header, height, err := s.cs.LatestBlock() diff --git a/config.go b/config.go index 64d187b..2b9e7ea 100644 --- a/config.go +++ b/config.go @@ -495,60 +495,67 @@ func loadConfig() (*config, []string, error) { return nil, nil, err } - if cfg.RPCConnect == "" { - cfg.RPCConnect = net.JoinHostPort("localhost", activeNet.RPCClientPort) - } - - // Add default port to connect flag if missing. - cfg.RPCConnect, err = cfgutil.NormalizeAddress(cfg.RPCConnect, - activeNet.RPCClientPort) - if err != nil { - fmt.Fprintf(os.Stderr, - "Invalid rpcconnect network address: %v\n", err) - return nil, nil, err - } - localhostListeners := map[string]struct{}{ "localhost": {}, "127.0.0.1": {}, "::1": {}, } - RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect) - if err != nil { - return nil, nil, err - } - if cfg.DisableClientTLS { - if _, ok := localhostListeners[RPCHost]; !ok { - str := "%s: the --noclienttls option may not be used " + - "when connecting RPC to non localhost " + - "addresses: %s" - err := fmt.Errorf(str, funcName, cfg.RPCConnect) - fmt.Fprintln(os.Stderr, err) - fmt.Fprintln(os.Stderr, usageMessage) + + if cfg.UseSPV { + neutrino.MaxPeers = cfg.MaxPeers + neutrino.BanDuration = cfg.BanDuration + neutrino.BanThreshold = cfg.BanThreshold + } else { + if cfg.RPCConnect == "" { + cfg.RPCConnect = net.JoinHostPort("localhost", activeNet.RPCClientPort) + } + + // Add default port to connect flag if missing. + cfg.RPCConnect, err = cfgutil.NormalizeAddress(cfg.RPCConnect, + activeNet.RPCClientPort) + if err != nil { + fmt.Fprintf(os.Stderr, + "Invalid rpcconnect network address: %v\n", err) return nil, nil, err } - } else { - // If CAFile is unset, choose either the copy or local btcd cert. - if !cfg.CAFile.ExplicitlySet() { - cfg.CAFile.Value = filepath.Join(cfg.AppDataDir.Value, defaultCAFilename) - // If the CA copy does not exist, check if we're connecting to - // a local btcd and switch to its RPC cert if it exists. - certExists, err := cfgutil.FileExists(cfg.CAFile.Value) - if err != nil { + RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect) + if err != nil { + return nil, nil, err + } + if cfg.DisableClientTLS { + if _, ok := localhostListeners[RPCHost]; !ok { + str := "%s: the --noclienttls option may not be used " + + "when connecting RPC to non localhost " + + "addresses: %s" + err := fmt.Errorf(str, funcName, cfg.RPCConnect) fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, usageMessage) return nil, nil, err } - if !certExists { - if _, ok := localhostListeners[RPCHost]; ok { - btcdCertExists, err := cfgutil.FileExists( - btcdDefaultCAFile) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return nil, nil, err - } - if btcdCertExists { - cfg.CAFile.Value = btcdDefaultCAFile + } else { + // If CAFile is unset, choose either the copy or local btcd cert. + if !cfg.CAFile.ExplicitlySet() { + cfg.CAFile.Value = filepath.Join(cfg.AppDataDir.Value, defaultCAFilename) + + // If the CA copy does not exist, check if we're connecting to + // a local btcd and switch to its RPC cert if it exists. + certExists, err := cfgutil.FileExists(cfg.CAFile.Value) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return nil, nil, err + } + if !certExists { + if _, ok := localhostListeners[RPCHost]; ok { + btcdCertExists, err := cfgutil.FileExists( + btcdDefaultCAFile) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return nil, nil, err + } + if btcdCertExists { + cfg.CAFile.Value = btcdDefaultCAFile + } } } } diff --git a/glide.lock b/glide.lock index 2a09c6d..48f18e8 100644 --- a/glide.lock +++ b/glide.lock @@ -97,7 +97,7 @@ imports: - name: github.com/kkdai/bstream version: f391b8402d23024e7c0f624b31267a89998fca95 - name: github.com/lightninglabs/neutrino - version: 7306107b67bb4eea6f70bc598d28049ea00ac442 + version: b96493137cf98477d84581f15c3c6ee60e9b9cec repo: git@github.com:lightninglabs/neutrino - name: golang.org/x/crypto version: 0fe963104e9d1877082f8fb38f816fcd97eb1d10 diff --git a/rpc/legacyrpc/server.go b/rpc/legacyrpc/server.go index b43d65d..60ce593 100644 --- a/rpc/legacyrpc/server.go +++ b/rpc/legacyrpc/server.go @@ -258,7 +258,7 @@ func (s *Server) Stop() { // functional bitcoin wallet RPC server. This can be called to enable RPC // passthrough even before a loaded wallet is set, but the wallet's RPC client // is preferred. -func (s *Server) SetChainServer(chainClient *chain.RPCClient) { +func (s *Server) SetChainServer(chainClient chain.Interface) { s.handlerMu.Lock() s.chainClient = chainClient s.handlerMu.Unlock() diff --git a/wallet/wallet.go b/wallet/wallet.go index 4aaa740..adf3331 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -67,7 +67,7 @@ type Wallet struct { Manager *waddrmgr.Manager TxStore *wtxmgr.Store - chainClient *chain.RPCClient + chainClient chain.Interface chainClientLock sync.Mutex chainClientSynced bool chainClientSyncMtx sync.Mutex @@ -139,7 +139,7 @@ func (w *Wallet) Start() { // // This method is unstable and will be removed when all syncing logic is moved // outside of the wallet package. -func (w *Wallet) SynchronizeRPC(chainClient *chain.RPCClient) { +func (w *Wallet) SynchronizeRPC(chainClient chain.Interface) { w.quitMu.Lock() select { case <-w.quit: @@ -1349,7 +1349,16 @@ func (w *Wallet) GetTransactions(startBlock, endBlock *BlockIdentifier, cancel < if chainClient == nil { return nil, errors.New("no chain server client") } - startResp = chainClient.GetBlockVerboseTxAsync(startBlock.hash) + switch client := chainClient.(type) { + case *chain.RPCClient: + startResp = client.GetBlockVerboseTxAsync(startBlock.hash) + case *chain.SPVChain: + var err error + start, err = client.GetBlockHeight(startBlock.hash) + if err != nil { + return nil, err + } + } } } if endBlock != nil { @@ -1359,7 +1368,16 @@ func (w *Wallet) GetTransactions(startBlock, endBlock *BlockIdentifier, cancel < if chainClient == nil { return nil, errors.New("no chain server client") } - endResp = chainClient.GetBlockVerboseTxAsync(endBlock.hash) + switch client := chainClient.(type) { + case *chain.RPCClient: + endResp = client.GetBlockVerboseTxAsync(endBlock.hash) + case *chain.SPVChain: + var err error + end, err = client.GetBlockHeight(endBlock.hash) + if err != nil { + return nil, err + } + } } } if startResp != nil {