From d6a6dff6516da34e9653ffcae4fc0ef63903a379 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 4 Aug 2022 14:44:51 +1000 Subject: [PATCH 01/28] btc client --- btcclient/bitcoind_client.go | 1344 +++++++++++++++++++++ btcclient/bitcoind_conn.go | 437 +++++++ btcclient/bitcoind_events.go | 705 +++++++++++ btcclient/bitcoind_events_test.go | 491 ++++++++ btcclient/block_filterer.go | 217 ++++ btcclient/block_filterer_test.go | 324 +++++ btcclient/client.go | 510 ++++++++ btcclient/interface.go | 125 ++ btcclient/log.go | 34 + btcclient/pruned_block_dispatcher.go | 666 ++++++++++ btcclient/pruned_block_dispatcher_test.go | 659 ++++++++++ btcclient/queue.go | 88 ++ btcclient/utils_test.go | 248 ++++ go.mod | 49 + go.sum | 272 +++++ types/cfgutil/amount.go | 43 + types/cfgutil/explicitflags.go | 36 + types/cfgutil/file.go | 19 + types/cfgutil/normalization.go | 50 + types/rpchelp/genrpcserverhelp.go | 95 ++ types/rpchelp/helpdescs_en_US.go | 414 +++++++ types/rpchelp/methods.go | 81 ++ types/zero/array.go | 17 + types/zero/benchmark_test.go | 87 ++ types/zero/doc.go | 3 + types/zero/slice.go | 34 + types/zero/zero_test.go | 147 +++ 27 files changed, 7195 insertions(+) create mode 100644 btcclient/bitcoind_client.go create mode 100644 btcclient/bitcoind_conn.go create mode 100644 btcclient/bitcoind_events.go create mode 100644 btcclient/bitcoind_events_test.go create mode 100644 btcclient/block_filterer.go create mode 100644 btcclient/block_filterer_test.go create mode 100644 btcclient/client.go create mode 100644 btcclient/interface.go create mode 100644 btcclient/log.go create mode 100644 btcclient/pruned_block_dispatcher.go create mode 100644 btcclient/pruned_block_dispatcher_test.go create mode 100644 btcclient/queue.go create mode 100644 btcclient/utils_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 types/cfgutil/amount.go create mode 100644 types/cfgutil/explicitflags.go create mode 100644 types/cfgutil/file.go create mode 100644 types/cfgutil/normalization.go create mode 100644 types/rpchelp/genrpcserverhelp.go create mode 100644 types/rpchelp/helpdescs_en_US.go create mode 100644 types/rpchelp/methods.go create mode 100644 types/zero/array.go create mode 100644 types/zero/benchmark_test.go create mode 100644 types/zero/doc.go create mode 100644 types/zero/slice.go create mode 100644 types/zero/zero_test.go diff --git a/btcclient/bitcoind_client.go b/btcclient/bitcoind_client.go new file mode 100644 index 00000000..890903e9 --- /dev/null +++ b/btcclient/bitcoind_client.go @@ -0,0 +1,1344 @@ +package btcclient + +import ( + "container/list" + "encoding/hex" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wtxmgr" +) + +var ( + // ErrBitcoindClientShuttingDown is an error returned when we attempt + // to receive a notification for a specific item and the bitcoind client + // is in the middle of shutting down. + ErrBitcoindClientShuttingDown = errors.New("client is shutting down") +) + +var _ Interface = &BitcoindClient{} + +// BitcoindClient represents a persistent client connection to a bitcoind server +// for information regarding the current best block chain. +type BitcoindClient struct { + // notifyBlocks signals whether the client is sending block + // notifications to the caller. This must be used atomically. + notifyBlocks uint32 + + started int32 // To be used atomically. + stopped int32 // To be used atomically. + + // birthday is the earliest time for which we should begin scanning the + // chain. + birthday time.Time + + // id is the unique ID of this client assigned by the backing bitcoind + // connection. + id uint64 + + // chainConn is the backing client to our rescan client that contains + // the RPC and ZMQ connections to a bitcoind node. + chainConn *BitcoindConn + + // bestBlock keeps track of the tip of the current best chain. + bestBlockMtx sync.RWMutex + bestBlock waddrmgr.BlockStamp + + // rescanUpdate is a channel will be sent items that we should match + // transactions against while processing a chain rescan to determine if + // they are relevant to the client. + rescanUpdate chan interface{} + + // watchedAddresses, watchedOutPoints, and watchedTxs are the set of + // items we should match transactions against while processing a chain + // rescan to determine if they are relevant to the client. + watchMtx sync.RWMutex + watchedAddresses map[string]struct{} + watchedOutPoints map[wire.OutPoint]struct{} + watchedTxs map[chainhash.Hash]struct{} + + // mempool keeps track of all relevant transactions that have yet to be + // confirmed. This is used to shortcut the filtering process of a + // transaction when a new confirmed transaction notification is + // received. + // + // NOTE: This requires the watchMtx to be held. + mempool map[chainhash.Hash]struct{} + + // expiredMempool keeps track of a set of confirmed transactions along + // with the height at which they were included in a block. These + // transactions will then be removed from the mempool after a period of + // 288 blocks. This is done to ensure the transactions are safe from a + // reorg in the chain. + // + // NOTE: This requires the watchMtx to be held. + expiredMempool map[int32]map[chainhash.Hash]struct{} + + // notificationQueue is a concurrent unbounded queue that handles + // dispatching notifications to the subscriber of this client. + // + // TODO: Rather than leaving this as an unbounded queue for all types of + // notifications, try dropping ones where a later enqueued notification + // can fully invalidate one waiting to be processed. For example, + // BlockConnected notifications for greater block heights can remove the + // need to process earlier notifications still waiting to be processed. + notificationQueue *ConcurrentQueue + + // txNtfns is a channel through which transaction events will be + // retrieved from the backing bitcoind connection, either via ZMQ or + // polling RPC. + txNtfns chan *wire.MsgTx + + // blockNtfns is a channel through block events will be retrieved from + // the backing bitcoind connection, either via ZMQ or polling RPC. + blockNtfns chan *wire.MsgBlock + + quit chan struct{} + wg sync.WaitGroup +} + +// A compile-time check to ensure that BitcoindClient satisfies the +// chain.Interface interface. +var _ Interface = (*BitcoindClient)(nil) + +// BackEnd returns the name of the driver. +func (c *BitcoindClient) BackEnd() string { + return "bitcoind" +} + +// GetBestBlock returns the highest block known to bitcoind. +func (c *BitcoindClient) GetBestBlock() (*chainhash.Hash, int32, error) { + bcinfo, err := c.chainConn.client.GetBlockChainInfo() + if err != nil { + return nil, 0, err + } + + hash, err := chainhash.NewHashFromStr(bcinfo.BestBlockHash) + if err != nil { + return nil, 0, err + } + + return hash, bcinfo.Blocks, nil +} + +// GetBlockHeight returns the height for the hash, if known, or returns an +// error. +func (c *BitcoindClient) GetBlockHeight(hash *chainhash.Hash) (int32, error) { + header, err := c.chainConn.client.GetBlockHeaderVerbose(hash) + if err != nil { + return 0, err + } + + return header.Height, nil +} + +// GetBlock returns a block from the hash. +func (c *BitcoindClient) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) { + return c.chainConn.GetBlock(hash) +} + +// GetBlockVerbose returns a verbose block from the hash. +func (c *BitcoindClient) GetBlockVerbose( + hash *chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) { + + return c.chainConn.client.GetBlockVerbose(hash) +} + +// GetBlockHash returns a block hash from the height. +func (c *BitcoindClient) GetBlockHash(height int64) (*chainhash.Hash, error) { + return c.chainConn.client.GetBlockHash(height) +} + +// GetBlockHeader returns a block header from the hash. +func (c *BitcoindClient) GetBlockHeader( + hash *chainhash.Hash) (*wire.BlockHeader, error) { + + return c.chainConn.client.GetBlockHeader(hash) +} + +// GetBlockHeaderVerbose returns a block header from the hash. +func (c *BitcoindClient) GetBlockHeaderVerbose( + hash *chainhash.Hash) (*btcjson.GetBlockHeaderVerboseResult, error) { + + return c.chainConn.client.GetBlockHeaderVerbose(hash) +} + +// IsCurrent returns whether the chain backend considers its view of the network +// as "current". +func (c *BitcoindClient) IsCurrent() bool { + bestHash, _, err := c.GetBestBlock() + if err != nil { + return false + } + bestHeader, err := c.GetBlockHeader(bestHash) + if err != nil { + return false + } + return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta)) +} + +// GetRawTransactionVerbose returns a transaction from the tx hash. +func (c *BitcoindClient) GetRawTransactionVerbose( + hash *chainhash.Hash) (*btcjson.TxRawResult, error) { + + return c.chainConn.client.GetRawTransactionVerbose(hash) +} + +// GetTxOut returns a txout from the outpoint info provided. +func (c *BitcoindClient) GetTxOut(txHash *chainhash.Hash, index uint32, + mempool bool) (*btcjson.GetTxOutResult, error) { + + return c.chainConn.client.GetTxOut(txHash, index, mempool) +} + +// SendRawTransaction sends a raw transaction via bitcoind. +func (c *BitcoindClient) SendRawTransaction(tx *wire.MsgTx, + allowHighFees bool) (*chainhash.Hash, error) { + + return c.chainConn.client.SendRawTransaction(tx, allowHighFees) +} + +// Notifications returns a channel to retrieve notifications from. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) Notifications() <-chan interface{} { + return c.notificationQueue.ChanOut() +} + +// NotifyReceived allows the chain backend to notify the caller whenever a +// transaction pays to any of the given addresses. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) NotifyReceived(addrs []btcutil.Address) error { + _ = c.NotifyBlocks() + + select { + case c.rescanUpdate <- addrs: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + return nil +} + +// NotifySpent allows the chain backend to notify the caller whenever a +// transaction spends any of the given outpoints. +func (c *BitcoindClient) NotifySpent(outPoints []*wire.OutPoint) error { + _ = c.NotifyBlocks() + + select { + case c.rescanUpdate <- outPoints: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + return nil +} + +// NotifyTx allows the chain backend to notify the caller whenever any of the +// given transactions confirm within the chain. +func (c *BitcoindClient) NotifyTx(txids []chainhash.Hash) error { + _ = c.NotifyBlocks() + + select { + case c.rescanUpdate <- txids: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + return nil +} + +// NotifyBlocks allows the chain backend to notify the caller whenever a block +// is connected or disconnected. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) NotifyBlocks() error { + // We'll guard the goroutine being spawned below by the notifyBlocks + // variable we'll use atomically. We'll make sure to reset it in case of + // a failure before spawning the goroutine so that it can be retried. + if !atomic.CompareAndSwapUint32(&c.notifyBlocks, 0, 1) { + return nil + } + + // Re-evaluate our known best block since it's possible that blocks have + // occurred between now and when the client was created. This ensures we + // don't detect a new notified block as a potential reorg. + bestHash, bestHeight, err := c.GetBestBlock() + if err != nil { + atomic.StoreUint32(&c.notifyBlocks, 0) + return fmt.Errorf("unable to retrieve best block: %v", err) + } + bestHeader, err := c.GetBlockHeaderVerbose(bestHash) + if err != nil { + atomic.StoreUint32(&c.notifyBlocks, 0) + return fmt.Errorf("unable to retrieve header for best block: "+ + "%v", err) + } + + c.bestBlockMtx.Lock() + c.bestBlock.Hash = *bestHash + c.bestBlock.Height = bestHeight + c.bestBlock.Timestamp = time.Unix(bestHeader.Time, 0) + c.bestBlockMtx.Unlock() + + // Include the client in the set of rescan clients of the backing + // bitcoind connection in order to receive ZMQ event notifications for + // new blocks and transactions. + c.chainConn.AddClient(c) + + c.wg.Add(1) + go c.ntfnHandler() + + return nil +} + +// shouldNotifyBlocks determines whether the client should send block +// notifications to the caller. +func (c *BitcoindClient) shouldNotifyBlocks() bool { + return atomic.LoadUint32(&c.notifyBlocks) == 1 +} + +// LoadTxFilter uses the given filters to what we should match transactions +// against to determine if they are relevant to the client. The reset argument +// is used to reset the current filters. +// +// The current filters supported are of the following types: +// []btcutil.Address +// []wire.OutPoint +// []*wire.OutPoint +// map[wire.OutPoint]btcutil.Address +// []chainhash.Hash +// []*chainhash.Hash +func (c *BitcoindClient) LoadTxFilter(reset bool, filters ...interface{}) error { + if reset { + select { + case c.rescanUpdate <- struct{}{}: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + } + + updateFilter := func(filter interface{}) error { + select { + case c.rescanUpdate <- filter: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + return nil + } + + // In order to make this operation atomic, we'll iterate through the + // filters twice: the first to ensure there aren't any unsupported + // filter types, and the second to actually update our filters. + for _, filter := range filters { + switch filter := filter.(type) { + case []btcutil.Address, []wire.OutPoint, []*wire.OutPoint, + map[wire.OutPoint]btcutil.Address, []chainhash.Hash, + []*chainhash.Hash: + + // Proceed to check the next filter type. + default: + return fmt.Errorf("unsupported filter type %T", filter) + } + } + + for _, filter := range filters { + if err := updateFilter(filter); err != nil { + return err + } + } + + return nil +} + +// RescanBlocks rescans any blocks passed, returning only the blocks that +// matched as []btcjson.BlockDetails. +func (c *BitcoindClient) RescanBlocks( + blockHashes []chainhash.Hash) ([]btcjson.RescannedBlock, error) { + + rescannedBlocks := make([]btcjson.RescannedBlock, 0, len(blockHashes)) + for _, hash := range blockHashes { + hash := hash + + header, err := c.GetBlockHeaderVerbose(&hash) + if err != nil { + log.Warnf("Unable to get header %s from bitcoind: %s", + hash, err) + continue + } + + // Prevent fetching the block completely if we know we shouldn't + // filter it. + if !c.shouldFilterBlock(time.Unix(header.Time, 0)) { + continue + } + + block, err := c.GetBlock(&hash) + if err != nil { + log.Warnf("Unable to get block %s from bitcoind: %s", + hash, err) + continue + } + + relevantTxs := c.filterBlock(block, header.Height, false) + if len(relevantTxs) > 0 { + rescannedBlock := btcjson.RescannedBlock{ + Hash: hash.String(), + } + for _, tx := range relevantTxs { + rescannedBlock.Transactions = append( + rescannedBlock.Transactions, + hex.EncodeToString(tx.SerializedTx), + ) + } + + rescannedBlocks = append(rescannedBlocks, rescannedBlock) + } + } + + return rescannedBlocks, nil +} + +// Rescan rescans from the block with the given hash until the current block, +// after adding the passed addresses and outpoints to the client's watch list. +func (c *BitcoindClient) Rescan(blockHash *chainhash.Hash, + addresses []btcutil.Address, outPoints map[wire.OutPoint]btcutil.Address) error { + + // A block hash is required to use as the starting point of the rescan. + if blockHash == nil { + return errors.New("rescan requires a starting block hash") + } + + // We'll then update our filters with the given outpoints and addresses. + select { + case c.rescanUpdate <- addresses: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + select { + case c.rescanUpdate <- outPoints: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + // Once the filters have been updated, we can begin the rescan. + select { + case c.rescanUpdate <- *blockHash: + case <-c.quit: + return ErrBitcoindClientShuttingDown + } + + return nil +} + +// Start initializes the bitcoind rescan client using the backing bitcoind +// connection and starts all goroutines necessary in order to process rescans +// and ZMQ notifications. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) Start() error { + if !atomic.CompareAndSwapInt32(&c.started, 0, 1) { + return nil + } + + // Start the notification queue and immediately dispatch a + // ClientConnected notification to the caller. This is needed as some of + // the callers will require this notification before proceeding. + c.notificationQueue.Start() + c.notificationQueue.ChanIn() <- ClientConnected{} + + // Retrieve the best block of the chain. + bestHash, bestHeight, err := c.GetBestBlock() + if err != nil { + return fmt.Errorf("unable to retrieve best block: %v", err) + } + bestHeader, err := c.GetBlockHeaderVerbose(bestHash) + if err != nil { + return fmt.Errorf("unable to retrieve header for best block: "+ + "%v", err) + } + + c.bestBlockMtx.Lock() + c.bestBlock = waddrmgr.BlockStamp{ + Hash: *bestHash, + Height: bestHeight, + Timestamp: time.Unix(bestHeader.Time, 0), + } + c.bestBlockMtx.Unlock() + + c.wg.Add(1) + go c.rescanHandler() + + return nil +} + +// Stop stops the bitcoind rescan client from processing rescans and ZMQ +// notifications. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) Stop() { + if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { + return + } + + close(c.quit) + + // Remove this client's reference from the bitcoind connection to + // prevent sending notifications to it after it's been stopped. + c.chainConn.RemoveClient(c.id) + + c.notificationQueue.Stop() +} + +// WaitForShutdown blocks until the client has finished disconnecting and all +// handlers have exited. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) WaitForShutdown() { + c.wg.Wait() +} + +// rescanHandler handles the logic needed for the caller to trigger a chain +// rescan. +// +// NOTE: This must be called as a goroutine. +func (c *BitcoindClient) rescanHandler() { + defer c.wg.Done() + + for { + select { + case update := <-c.rescanUpdate: + switch update := update.(type) { + + // We're clearing the filters. + case struct{}: + c.watchMtx.Lock() + c.watchedOutPoints = make(map[wire.OutPoint]struct{}) + c.watchedAddresses = make(map[string]struct{}) + c.watchedTxs = make(map[chainhash.Hash]struct{}) + c.watchMtx.Unlock() + + // We're adding the addresses to our filter. + case []btcutil.Address: + c.watchMtx.Lock() + for _, addr := range update { + c.watchedAddresses[addr.String()] = struct{}{} + } + c.watchMtx.Unlock() + + // We're adding the outpoints to our filter. + case []wire.OutPoint: + c.watchMtx.Lock() + for _, op := range update { + c.watchedOutPoints[op] = struct{}{} + } + c.watchMtx.Unlock() + case []*wire.OutPoint: + c.watchMtx.Lock() + for _, op := range update { + c.watchedOutPoints[*op] = struct{}{} + } + c.watchMtx.Unlock() + + // We're adding the outpoints that map to the scripts + // that we should scan for to our filter. + case map[wire.OutPoint]btcutil.Address: + c.watchMtx.Lock() + for op := range update { + c.watchedOutPoints[op] = struct{}{} + } + c.watchMtx.Unlock() + + // We're adding the transactions to our filter. + case []chainhash.Hash: + c.watchMtx.Lock() + for _, txid := range update { + c.watchedTxs[txid] = struct{}{} + } + c.watchMtx.Unlock() + case []*chainhash.Hash: + c.watchMtx.Lock() + for _, txid := range update { + c.watchedTxs[*txid] = struct{}{} + } + c.watchMtx.Unlock() + + // We're starting a rescan from the hash. + case chainhash.Hash: + if err := c.rescan(update); err != nil { + log.Errorf("Unable to complete chain "+ + "rescan: %v", err) + } + default: + log.Warnf("Received unexpected filter type %T", + update) + } + case <-c.quit: + return + } + } +} + +// ntfnHandler handles the logic to retrieve ZMQ notifications from the backing +// bitcoind connection. +// +// NOTE: This must be called as a goroutine. +func (c *BitcoindClient) ntfnHandler() { + defer c.wg.Done() + + for { + select { + case tx := <-c.txNtfns: + _, _, err := c.filterTx(tx, nil, true) + if err != nil { + log.Errorf("Unable to filter transaction %v: %v", + tx.TxHash(), err) + } + + case newBlock := <-c.blockNtfns: + // If the new block's previous hash matches the best + // hash known to us, then the new block is the next + // successor, so we'll update our best block to reflect + // this and determine if this new block matches any of + // our existing filters. + c.bestBlockMtx.RLock() + bestBlock := c.bestBlock + c.bestBlockMtx.RUnlock() + if newBlock.Header.PrevBlock == bestBlock.Hash { + newBlockHeight := bestBlock.Height + 1 + _ = c.filterBlock(newBlock, newBlockHeight, true) + + // With the block successfully filtered, we'll + // make it our new best block. + bestBlock.Hash = newBlock.BlockHash() + bestBlock.Height = newBlockHeight + bestBlock.Timestamp = newBlock.Header.Timestamp + + c.bestBlockMtx.Lock() + c.bestBlock = bestBlock + c.bestBlockMtx.Unlock() + + continue + } + + // Otherwise, we've encountered a reorg. + if err := c.reorg(bestBlock, newBlock); err != nil { + log.Errorf("Unable to process chain reorg: %v", + err) + } + case <-c.quit: + return + } + } +} + +// SetBirthday sets the birthday of the bitcoind rescan client. +// +// NOTE: This should be done before the client has been started in order for it +// to properly carry its duties. +func (c *BitcoindClient) SetBirthday(t time.Time) { + c.birthday = t +} + +// BlockStamp returns the latest block notified by the client, or an error +// if the client has been shut down. +func (c *BitcoindClient) BlockStamp() (*waddrmgr.BlockStamp, error) { + c.bestBlockMtx.RLock() + bestBlock := c.bestBlock + c.bestBlockMtx.RUnlock() + + return &bestBlock, nil +} + +// onBlockConnected is a callback that's executed whenever a new block has been +// detected. This will queue a BlockConnected notification to the caller. +func (c *BitcoindClient) onBlockConnected(hash *chainhash.Hash, height int32, + timestamp time.Time) { + + if c.shouldNotifyBlocks() { + select { + case c.notificationQueue.ChanIn() <- BlockConnected{ + Block: wtxmgr.Block{ + Hash: *hash, + Height: height, + }, + Time: timestamp, + }: + case <-c.quit: + } + } +} + +// onFilteredBlockConnected is an alternative callback that's executed whenever +// a new block has been detected. It serves the same purpose as +// onBlockConnected, but it also includes a list of the relevant transactions +// found within the block being connected. This will queue a +// FilteredBlockConnected notification to the caller. +func (c *BitcoindClient) onFilteredBlockConnected(height int32, + header *wire.BlockHeader, relevantTxs []*wtxmgr.TxRecord) { + + if c.shouldNotifyBlocks() { + select { + case c.notificationQueue.ChanIn() <- FilteredBlockConnected{ + Block: &wtxmgr.BlockMeta{ + Block: wtxmgr.Block{ + Hash: header.BlockHash(), + Height: height, + }, + Time: header.Timestamp, + }, + RelevantTxs: relevantTxs, + }: + case <-c.quit: + } + } +} + +// onBlockDisconnected is a callback that's executed whenever a block has been +// disconnected. This will queue a BlockDisconnected notification to the caller +// with the details of the block being disconnected. +func (c *BitcoindClient) onBlockDisconnected(hash *chainhash.Hash, height int32, + timestamp time.Time) { + + if c.shouldNotifyBlocks() { + select { + case c.notificationQueue.ChanIn() <- BlockDisconnected{ + Block: wtxmgr.Block{ + Hash: *hash, + Height: height, + }, + Time: timestamp, + }: + case <-c.quit: + } + } +} + +// onRelevantTx is a callback that's executed whenever a transaction is relevant +// to the caller. This means that the transaction matched a specific item in the +// client's different filters. This will queue a RelevantTx notification to the +// caller. +func (c *BitcoindClient) onRelevantTx(tx *wtxmgr.TxRecord, + blockDetails *btcjson.BlockDetails) { + + block, err := parseBlock(blockDetails) + if err != nil { + log.Errorf("Unable to send onRelevantTx notification, failed "+ + "parse block: %v", err) + return + } + + select { + case c.notificationQueue.ChanIn() <- RelevantTx{ + TxRecord: tx, + Block: block, + }: + case <-c.quit: + } +} + +// onRescanProgress is a callback that's executed whenever a rescan is in +// progress. This will queue a RescanProgress notification to the caller with +// the current rescan progress details. +func (c *BitcoindClient) onRescanProgress(hash *chainhash.Hash, height int32, + timestamp time.Time) { + + select { + case c.notificationQueue.ChanIn() <- &RescanProgress{ + Hash: hash, + Height: height, + Time: timestamp, + }: + case <-c.quit: + } +} + +// onRescanFinished is a callback that's executed whenever a rescan has +// finished. This will queue a RescanFinished notification to the caller with +// the details of the last block in the range of the rescan. +func (c *BitcoindClient) onRescanFinished(hash *chainhash.Hash, height int32, + timestamp time.Time) { + + select { + case c.notificationQueue.ChanIn() <- &RescanFinished{ + Hash: hash, + Height: height, + Time: timestamp, + }: + case <-c.quit: + } +} + +// reorg processes a reorganization during chain synchronization. This is +// separate from a rescan's handling of a reorg. This will rewind back until it +// finds a common ancestor and notify all the new blocks since then. +func (c *BitcoindClient) reorg(currentBlock waddrmgr.BlockStamp, + reorgBlock *wire.MsgBlock) error { + + // Retrieve the best known height based on the block which caused the + // reorg. This way, we can preserve the chain of blocks we need to + // retrieve. + bestHash := reorgBlock.BlockHash() + bestHeight, err := c.GetBlockHeight(&bestHash) + if err != nil { + return fmt.Errorf("unable to get block height for %v: %v", + bestHash, err) + } + + log.Debugf("Possible reorg at block: height=%v, hash=%v", bestHeight, + bestHash) + + if bestHeight < currentBlock.Height { + log.Debugf("Detected multiple reorgs: best_height=%v below "+ + "current_height=%v", bestHeight, currentBlock.Height) + return nil + } + + // We'll now keep track of all the blocks known to the *chain*, starting + // from the best block known to us until the best block in the chain. + // This will let us fast-forward despite any future reorgs. + blocksToNotify := list.New() + blocksToNotify.PushFront(reorgBlock) + previousBlock := reorgBlock.Header.PrevBlock + for i := bestHeight - 1; i >= currentBlock.Height; i-- { + block, err := c.GetBlock(&previousBlock) + if err != nil { + return fmt.Errorf("unable to get block %v: %v", + previousBlock, err) + } + blocksToNotify.PushFront(block) + previousBlock = block.Header.PrevBlock + } + + // Rewind back to the last common ancestor block using the previous + // block hash from each header to avoid any race conditions. If we + // encounter more reorgs, they'll be queued and we'll repeat the cycle. + // + // We'll start by retrieving the header to the best block known to us. + currentHeader, err := c.GetBlockHeader(¤tBlock.Hash) + if err != nil { + return fmt.Errorf("unable to get block header for %v: %v", + currentBlock.Hash, err) + } + + // Then, we'll walk backwards in the chain until we find our common + // ancestor. + for previousBlock != currentHeader.PrevBlock { + // Since the previous hashes don't match, the current block has + // been reorged out of the chain, so we should send a + // BlockDisconnected notification for it. + log.Debugf("Disconnecting block: height=%v, hash=%v", + currentBlock.Height, currentBlock.Hash) + + c.onBlockDisconnected( + ¤tBlock.Hash, currentBlock.Height, + currentBlock.Timestamp, + ) + + // Our current block should now reflect the previous one to + // continue the common ancestor search. + prevBlock := ¤tHeader.PrevBlock + currentHeader, err = c.GetBlockHeader(prevBlock) + if err != nil { + return fmt.Errorf("unable to get block header for %v: %v", + prevBlock, err) + } + + currentBlock.Height-- + currentBlock.Hash = currentHeader.PrevBlock + currentBlock.Timestamp = currentHeader.Timestamp + + // Store the correct block in our list in order to notify it + // once we've found our common ancestor. + block, err := c.GetBlock(&previousBlock) + if err != nil { + return fmt.Errorf("unable to get block %v: %v", + previousBlock, err) + } + blocksToNotify.PushFront(block) + previousBlock = block.Header.PrevBlock + } + + // Disconnect the last block from the old chain. Since the previous + // block remains the same between the old and new chains, the tip will + // now be the last common ancestor. + log.Debugf("Disconnecting block: height=%v, hash=%v", + currentBlock.Height, currentBlock.Hash) + + c.onBlockDisconnected( + ¤tBlock.Hash, currentBlock.Height, currentHeader.Timestamp, + ) + + currentBlock.Height-- + + // Now we fast-forward to the new block, notifying along the way. + for blocksToNotify.Front() != nil { + nextBlock := blocksToNotify.Front().Value.(*wire.MsgBlock) + nextHeight := currentBlock.Height + 1 + nextHash := nextBlock.BlockHash() + nextHeader, err := c.GetBlockHeader(&nextHash) + if err != nil { + return fmt.Errorf("unable to get block header for %v: %v", + nextHash, err) + } + + _ = c.filterBlock(nextBlock, nextHeight, true) + + currentBlock.Height = nextHeight + currentBlock.Hash = nextHash + currentBlock.Timestamp = nextHeader.Timestamp + + blocksToNotify.Remove(blocksToNotify.Front()) + } + + c.bestBlockMtx.Lock() + c.bestBlock = currentBlock + c.bestBlockMtx.Unlock() + + return nil +} + +// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any +// addresses of interest. Each block will be fetched and filtered sequentially, +// returning a FilterBlocksReponse for the first block containing a matching +// address. If no matches are found in the range of blocks requested, the +// returned response will be nil. +// +// NOTE: This is part of the chain.Interface interface. +func (c *BitcoindClient) FilterBlocks( + req *FilterBlocksRequest) (*FilterBlocksResponse, error) { + + blockFilterer := NewBlockFilterer(c.chainConn.cfg.ChainParams, req) + + // Iterate over the requested blocks, fetching each from the rpc client. + // Each block will scanned using the reverse addresses indexes generated + // above, breaking out early if any addresses are found. + for i, block := range req.Blocks { + // TODO(conner): add prefetching, since we already know we'll be + // fetching *every* block + rawBlock, err := c.GetBlock(&block.Hash) + if err != nil { + return nil, err + } + + if !blockFilterer.FilterBlock(rawBlock) { + continue + } + + // If any external or internal addresses were detected in this + // block, we return them to the caller so that the rescan + // windows can widened with subsequent addresses. The + // `BatchIndex` is returned so that the caller can compute the + // *next* block from which to begin again. + resp := &FilterBlocksResponse{ + BatchIndex: uint32(i), + BlockMeta: block, + FoundExternalAddrs: blockFilterer.FoundExternal, + FoundInternalAddrs: blockFilterer.FoundInternal, + FoundOutPoints: blockFilterer.FoundOutPoints, + RelevantTxns: blockFilterer.RelevantTxns, + } + + return resp, nil + } + + // No addresses were found for this range. + return nil, nil +} + +// rescan performs a rescan of the chain using a bitcoind backend, from the +// specified hash to the best known hash, while watching out for reorgs that +// happen during the rescan. It uses the addresses and outputs being tracked by +// the client in the watch list. This is called only within a queue processing +// loop. +func (c *BitcoindClient) rescan(start chainhash.Hash) error { + // We start by getting the best already processed block. We only use + // the height, as the hash can change during a reorganization, which we + // catch by testing connectivity from known blocks to the previous + // block. + bestHash, bestHeight, err := c.GetBestBlock() + if err != nil { + return err + } + bestHeader, err := c.GetBlockHeaderVerbose(bestHash) + if err != nil { + return err + } + bestBlock := waddrmgr.BlockStamp{ + Hash: *bestHash, + Height: bestHeight, + Timestamp: time.Unix(bestHeader.Time, 0), + } + + // Create a list of headers sorted in forward order. We'll use this in + // the event that we need to backtrack due to a chain reorg. + headers := list.New() + previousHeader, err := c.GetBlockHeaderVerbose(&start) + if err != nil { + return err + } + previousHash, err := chainhash.NewHashFromStr(previousHeader.Hash) + if err != nil { + return err + } + headers.PushBack(previousHeader) + + // Cycle through all of the blocks known to bitcoind, being mindful of + // reorgs. + for i := previousHeader.Height + 1; i <= bestBlock.Height; i++ { + hash, err := c.GetBlockHash(int64(i)) + if err != nil { + return err + } + + // If the previous header is before the wallet birthday, fetch + // the current header and construct a dummy block, rather than + // fetching the whole block itself. This speeds things up as we + // no longer have to fetch the whole block when we know it won't + // match any of our filters. + var block *wire.MsgBlock + afterBirthday := previousHeader.Time >= c.birthday.Unix() + if !afterBirthday { + header, err := c.GetBlockHeader(hash) + if err != nil { + return err + } + block = &wire.MsgBlock{ + Header: *header, + } + + afterBirthday = c.birthday.Before(header.Timestamp) + if afterBirthday { + c.onRescanProgress( + previousHash, i, + block.Header.Timestamp, + ) + } + } + + if afterBirthday { + block, err = c.GetBlock(hash) + if err != nil { + return err + } + } + + for block.Header.PrevBlock.String() != previousHeader.Hash { + // If we're in this for loop, it looks like we've been + // reorganized. We now walk backwards to the common + // ancestor between the best chain and the known chain. + // + // First, we signal a disconnected block to rewind the + // rescan state. + c.onBlockDisconnected( + previousHash, previousHeader.Height, + time.Unix(previousHeader.Time, 0), + ) + + // Get the previous block of the best chain. + hash, err := c.GetBlockHash(int64(i - 1)) + if err != nil { + return err + } + block, err = c.GetBlock(hash) + if err != nil { + return err + } + + // Then, we'll the get the header of this previous + // block. + if headers.Back() != nil { + // If it's already in the headers list, we can + // just get it from there and remove the + // current hash. + headers.Remove(headers.Back()) + if headers.Back() != nil { + previousHeader = headers.Back(). + Value.(*btcjson.GetBlockHeaderVerboseResult) + previousHash, err = chainhash.NewHashFromStr( + previousHeader.Hash, + ) + if err != nil { + return err + } + } + } else { + // Otherwise, we get it from bitcoind. + previousHash, err = chainhash.NewHashFromStr( + previousHeader.PreviousHash, + ) + if err != nil { + return err + } + previousHeader, err = c.GetBlockHeaderVerbose( + previousHash, + ) + if err != nil { + return err + } + } + } + + // Now that we've ensured we haven't come across a reorg, we'll + // add the current block header to our list of headers. + blockHash := block.BlockHash() + previousHash = &blockHash + previousHeader = &btcjson.GetBlockHeaderVerboseResult{ + Hash: blockHash.String(), + Height: i, + PreviousHash: block.Header.PrevBlock.String(), + Time: block.Header.Timestamp.Unix(), + } + headers.PushBack(previousHeader) + + // Notify the block and any of its relevant transacations. + _ = c.filterBlock(block, i, true) + + if i%10000 == 0 { + c.onRescanProgress( + previousHash, i, block.Header.Timestamp, + ) + } + + // If we've reached the previously best known block, check to + // make sure the underlying node hasn't synchronized additional + // blocks. If it has, update the best known block and continue + // to rescan to that point. + if i == bestBlock.Height { + bestHash, bestHeight, err = c.GetBestBlock() + if err != nil { + return err + } + bestHeader, err = c.GetBlockHeaderVerbose(bestHash) + if err != nil { + return err + } + + bestBlock.Hash = *bestHash + bestBlock.Height = bestHeight + bestBlock.Timestamp = time.Unix(bestHeader.Time, 0) + } + } + + c.onRescanFinished(bestHash, bestHeight, time.Unix(bestHeader.Time, 0)) + + return nil +} + +// shouldFilterBlock determines whether we should filter a block based on its +// timestamp or our watch list. +func (c *BitcoindClient) shouldFilterBlock(blockTimestamp time.Time) bool { + c.watchMtx.RLock() + hasEmptyFilter := len(c.watchedAddresses) == 0 && + len(c.watchedOutPoints) == 0 && len(c.watchedTxs) == 0 + c.watchMtx.RUnlock() + + return !(blockTimestamp.Before(c.birthday) || hasEmptyFilter) +} + +// filterBlock filters a block for watched outpoints and addresses, and returns +// any matching transactions, sending notifications along the way. +func (c *BitcoindClient) filterBlock(block *wire.MsgBlock, height int32, + notify bool) []*wtxmgr.TxRecord { + + // If this block happened before the client's birthday or we have + // nothing to filter for, then we'll skip it entirely. + blockHash := block.BlockHash() + if !c.shouldFilterBlock(block.Header.Timestamp) { + if notify { + c.onFilteredBlockConnected(height, &block.Header, nil) + c.onBlockConnected( + &blockHash, height, block.Header.Timestamp, + ) + } + return nil + } + + if c.shouldNotifyBlocks() { + log.Debugf("Filtering block %d (%s) with %d transactions", + height, block.BlockHash(), len(block.Transactions)) + } + + // Create a block details template to use for all of the confirmed + // transactions found within this block. + blockDetails := &btcjson.BlockDetails{ + Hash: blockHash.String(), + Height: height, + Time: block.Header.Timestamp.Unix(), + } + + // Now, we'll through all of the transactions in the block keeping track + // of any relevant to the caller. + var relevantTxs []*wtxmgr.TxRecord + confirmedTxs := make(map[chainhash.Hash]struct{}) + for i, tx := range block.Transactions { + // Update the index in the block details with the index of this + // transaction. + blockDetails.Index = i + isRelevant, rec, err := c.filterTx(tx, blockDetails, notify) + if err != nil { + log.Warnf("Unable to filter transaction %v: %v", + tx.TxHash(), err) + continue + } + + if isRelevant { + relevantTxs = append(relevantTxs, rec) + confirmedTxs[tx.TxHash()] = struct{}{} + } + } + + // Update the expiration map by setting the block's confirmed + // transactions and deleting any in the mempool that were confirmed + // over 288 blocks ago. + c.watchMtx.Lock() + c.expiredMempool[height] = confirmedTxs + if oldBlock, ok := c.expiredMempool[height-288]; ok { + for txHash := range oldBlock { + delete(c.mempool, txHash) + } + delete(c.expiredMempool, height-288) + } + c.watchMtx.Unlock() + + if notify { + c.onFilteredBlockConnected(height, &block.Header, relevantTxs) + c.onBlockConnected(&blockHash, height, block.Header.Timestamp) + } + + return relevantTxs +} + +// filterTx determines whether a transaction is relevant to the client by +// inspecting the client's different filters. +func (c *BitcoindClient) filterTx(tx *wire.MsgTx, + blockDetails *btcjson.BlockDetails, + notify bool) (bool, *wtxmgr.TxRecord, error) { + + txDetails := btcutil.NewTx(tx) + if blockDetails != nil { + txDetails.SetIndex(blockDetails.Index) + } + + rec, err := wtxmgr.NewTxRecordFromMsgTx(txDetails.MsgTx(), time.Now()) + if err != nil { + log.Errorf("Cannot create transaction record for relevant "+ + "tx: %v", err) + return false, nil, err + } + if blockDetails != nil { + rec.Received = time.Unix(blockDetails.Time, 0) + } + + // We'll begin the filtering process by holding the lock to ensure we + // match exactly against what's currently in the filters. + c.watchMtx.Lock() + defer c.watchMtx.Unlock() + + // If we've already seen this transaction and it's now been confirmed, + // then we'll shortcut the filter process by immediately sending a + // notification to the caller that the filter matches. + if _, ok := c.mempool[tx.TxHash()]; ok { + if notify && blockDetails != nil { + c.onRelevantTx(rec, blockDetails) + } + return true, rec, nil + } + + // Otherwise, this is a new transaction we have yet to see. We'll need + // to determine if this transaction is somehow relevant to the caller. + var isRelevant bool + + // We'll start by checking all inputs and determining whether it spends + // an existing outpoint or a pkScript encoded as an address in our watch + // list. + for _, txIn := range tx.TxIn { + // If it matches an outpoint in our watch list, we can exit our + // loop early. + if _, ok := c.watchedOutPoints[txIn.PreviousOutPoint]; ok { + isRelevant = true + break + } + + // Otherwise, we'll check whether it matches a pkScript in our + // watch list encoded as an address. To do so, we'll re-derive + // the pkScript of the output the input is attempting to spend. + pkScript, err := txscript.ComputePkScript( + txIn.SignatureScript, txIn.Witness, + ) + if err != nil { + // Non-standard outputs can be safely skipped. + continue + } + addr, err := pkScript.Address(c.chainConn.cfg.ChainParams) + if err != nil { + // Non-standard outputs can be safely skipped. + continue + } + if _, ok := c.watchedAddresses[addr.String()]; ok { + isRelevant = true + break + } + } + + // We'll also cycle through its outputs to determine if it pays to + // any of the currently watched addresses. If an output matches, we'll + // add it to our watch list. + for i, txOut := range tx.TxOut { + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + txOut.PkScript, c.chainConn.cfg.ChainParams, + ) + if err != nil { + // Non-standard outputs can be safely skipped. + continue + } + + for _, addr := range addrs { + if _, ok := c.watchedAddresses[addr.String()]; ok { + isRelevant = true + op := wire.OutPoint{ + Hash: tx.TxHash(), + Index: uint32(i), + } + c.watchedOutPoints[op] = struct{}{} + } + } + } + + // If the transaction didn't pay to any of our watched addresses, we'll + // check if we're currently watching for the hash of this transaction. + if !isRelevant { + if _, ok := c.watchedTxs[tx.TxHash()]; ok { + isRelevant = true + } + } + + // If the transaction is not relevant to us, we can simply exit. + if !isRelevant { + return false, rec, nil + } + + // Otherwise, the transaction matched our filters, so we should dispatch + // a notification for it. If it's still unconfirmed, we'll include it in + // our mempool so that it can also be notified as part of + // FilteredBlockConnected once it confirms. + if blockDetails == nil { + c.mempool[tx.TxHash()] = struct{}{} + } + + c.onRelevantTx(rec, blockDetails) + + return true, rec, nil +} diff --git a/btcclient/bitcoind_conn.go b/btcclient/bitcoind_conn.go new file mode 100644 index 00000000..a06d20ff --- /dev/null +++ b/btcclient/bitcoind_conn.go @@ -0,0 +1,437 @@ +package btcclient + +import ( + "fmt" + "net" + "sync" + "sync/atomic" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/ticker" +) + +const ( + // rawBlockZMQCommand is the command used to receive raw block + // notifications from bitcoind through ZMQ. + rawBlockZMQCommand = "rawblock" + + // rawTxZMQCommand is the command used to receive raw transaction + // notifications from bitcoind through ZMQ. + rawTxZMQCommand = "rawtx" + + // maxRawBlockSize is the maximum size in bytes for a raw block received + // from bitcoind through ZMQ. + maxRawBlockSize = 4e6 + + // maxRawTxSize is the maximum size in bytes for a raw transaction + // received from bitcoind through ZMQ. + maxRawTxSize = maxRawBlockSize + + // seqNumLen is the length of the sequence number of a message sent from + // bitcoind through ZMQ. + seqNumLen = 4 + + // errBlockPrunedStr is the error message returned by bitcoind upon + // calling GetBlock on a pruned block. + errBlockPrunedStr = "Block not available (pruned data)" +) + +// BitcoindConfig contains all of the parameters required to establish a +// connection to a bitcoind's RPC. +type BitcoindConfig struct { + // ChainParams are the chain parameters the bitcoind server is running + // on. + ChainParams *chaincfg.Params + + // Host is the IP address and port of the bitcoind's RPC server. + Host string + + // User is the username to use to authenticate to bitcoind's RPC server. + User string + + // Pass is the passphrase to use to authenticate to bitcoind's RPC + // server. + Pass string + + // ZMQConfig holds the configuration settings required for setting up + // zmq connections to bitcoind. + ZMQConfig *ZMQConfig + + // PollingConfig holds the configuration settings required for using + // RPC polling for block and transaction notifications instead of the + // ZMQ interface. + PollingConfig *PollingConfig + + // Dialer is a closure we'll use to dial Bitcoin peers. If the chain + // backend is running over Tor, this must support dialing peers over Tor + // as well. + Dialer Dialer + + // PrunedModeMaxPeers is the maximum number of peers we'll attempt to + // retrieve pruned blocks from. + // + // NOTE: This only applies for pruned bitcoind nodes. + PrunedModeMaxPeers int +} + +// BitcoindConn represents a persistent client connection to a bitcoind node +// that listens for events read from a ZMQ connection. +type BitcoindConn struct { + started int32 // To be used atomically. + stopped int32 // To be used atomically. + + // rescanClientCounter is an atomic counter that assigns a unique ID to + // each new bitcoind rescan client using the current bitcoind + // connection. + rescanClientCounter uint64 + + cfg BitcoindConfig + + // client is the RPC client to the bitcoind node. + client *rpcclient.Client + + // prunedBlockDispatcher handles all of the pruned block requests. + // + // NOTE: This is nil when the bitcoind node is not pruned. + prunedBlockDispatcher *PrunedBlockDispatcher + + // events handles the block and transaction events that are received or + // retrieved from bitcoind. + events BitcoindEvents + + // rescanClients is the set of active bitcoind rescan clients to which + // ZMQ event notifications will be sent to. + rescanClientsMtx sync.Mutex + rescanClients map[uint64]*BitcoindClient + + quit chan struct{} + wg sync.WaitGroup +} + +// Dialer represents a way to dial Bitcoin peers. If the chain backend is +// running over Tor, this must support dialing peers over Tor as well. +type Dialer = func(string) (net.Conn, error) + +// NewBitcoindConn creates a client connection to the node described by the host +// string. The ZMQ connections are established immediately to ensure liveness. +// If the remote node does not operate on the same bitcoin network as described +// by the passed chain parameters, the connection will be disconnected. +func NewBitcoindConn(cfg *BitcoindConfig) (*BitcoindConn, error) { + clientCfg := &rpcclient.ConnConfig{ + Host: cfg.Host, + User: cfg.User, + Pass: cfg.Pass, + DisableAutoReconnect: false, + DisableConnectOnNew: true, + DisableTLS: true, + HTTPPostMode: true, + } + client, err := rpcclient.New(clientCfg, nil) + if err != nil { + return nil, err + } + + // Verify that the node is running on the expected network. + net, err := getCurrentNet(client) + if err != nil { + return nil, err + } + if net != cfg.ChainParams.Net { + return nil, fmt.Errorf("expected network %v, got %v", + cfg.ChainParams.Net, net) + } + + // Check if the node is pruned, as we'll need to perform additional + // operations if so. + chainInfo, err := client.GetBlockChainInfo() + if err != nil { + return nil, fmt.Errorf("unable to determine if bitcoind is "+ + "pruned: %v", err) + } + + // Only initialize the PrunedBlockDispatcher when the connected bitcoind + // node is pruned. + var prunedBlockDispatcher *PrunedBlockDispatcher + if chainInfo.Pruned { + prunedBlockDispatcher, err = NewPrunedBlockDispatcher( + &PrunedBlockDispatcherConfig{ + ChainParams: cfg.ChainParams, + NumTargetPeers: cfg.PrunedModeMaxPeers, + Dial: cfg.Dialer, + GetPeers: client.GetPeerInfo, + GetNodeAddresses: client.GetNodeAddresses, + PeerReadyTimeout: defaultPeerReadyTimeout, + RefreshPeersTicker: ticker.New(defaultRefreshPeersInterval), + MaxRequestInvs: wire.MaxInvPerMsg, + }, + ) + if err != nil { + return nil, err + } + } + + bc := &BitcoindConn{ + cfg: *cfg, + client: client, + prunedBlockDispatcher: prunedBlockDispatcher, + rescanClients: make(map[uint64]*BitcoindClient), + quit: make(chan struct{}), + } + + bc.events, err = NewBitcoindEventSubscriber(cfg, client) + if err != nil { + return nil, err + } + + return bc, nil +} + +// Start attempts to establish a RPC and ZMQ connection to a bitcoind node. If +// successful, a goroutine is spawned to read events from the ZMQ connection. +// It's possible for this function to fail due to a limited number of connection +// attempts. This is done to prevent waiting forever on the connection to be +// established in the case that the node is down. +func (c *BitcoindConn) Start() error { + if !atomic.CompareAndSwapInt32(&c.started, 0, 1) { + return nil + } + + // If we're connected to a pruned backend, we'll need to also start our + // pruned block dispatcher to handle pruned block requests. + if c.prunedBlockDispatcher != nil { + log.Debug("Detected pruned bitcoind backend") + if err := c.prunedBlockDispatcher.Start(); err != nil { + return err + } + } + + c.wg.Add(2) + go c.sendBlockToClients() + go c.sendTxToClients() + + return c.events.Start() +} + +// Stop terminates the RPC and ZMQ connection to a bitcoind node and removes any +// active rescan clients. +func (c *BitcoindConn) Stop() { + if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { + return + } + + for _, client := range c.rescanClients { + client.Stop() + } + + close(c.quit) + c.client.Shutdown() + + if err := c.events.Stop(); err != nil { + log.Errorf("error shutting down bitcoind events: %w", err) + } + + if c.prunedBlockDispatcher != nil { + c.prunedBlockDispatcher.Stop() + } + + c.client.WaitForShutdown() + c.wg.Wait() +} + +// sendBlockToClients is used to notify all rescan clients of a new block. It +// MUST be run in a goroutine. +func (c *BitcoindConn) sendBlockToClients() { + defer c.wg.Done() + + // sendBlock is a helper function that sends the given block to each + // of the rescan clients + sendBlock := func(block *wire.MsgBlock) { + c.rescanClientsMtx.Lock() + defer c.rescanClientsMtx.Unlock() + + for _, client := range c.rescanClients { + select { + case client.blockNtfns <- block: + case <-client.quit: + case <-c.quit: + return + } + } + } + + var block *wire.MsgBlock + for { + select { + case block = <-c.events.BlockNotifications(): + case <-c.quit: + return + } + + sendBlock(block) + } +} + +// sendTxToClients is used to notify all rescan clients of a new transaction. +// It MUST be run as a goroutine. +func (c *BitcoindConn) sendTxToClients() { + defer c.wg.Done() + + sendTx := func(tx *wire.MsgTx) { + c.rescanClientsMtx.Lock() + defer c.rescanClientsMtx.Unlock() + + for _, client := range c.rescanClients { + select { + case client.txNtfns <- tx: + case <-client.quit: + case <-c.quit: + return + } + } + } + + var tx *wire.MsgTx + for { + select { + case tx = <-c.events.TxNotifications(): + case <-c.quit: + return + } + + sendTx(tx) + } +} + +// getCurrentNet returns the network on which the bitcoind node is running. +func getCurrentNet(client *rpcclient.Client) (wire.BitcoinNet, error) { + hash, err := client.GetBlockHash(0) + if err != nil { + return 0, err + } + + switch *hash { + case *chaincfg.TestNet3Params.GenesisHash: + return chaincfg.TestNet3Params.Net, nil + case *chaincfg.RegressionNetParams.GenesisHash: + return chaincfg.RegressionNetParams.Net, nil + case *chaincfg.SigNetParams.GenesisHash: + return chaincfg.SigNetParams.Net, nil + case *chaincfg.MainNetParams.GenesisHash: + return chaincfg.MainNetParams.Net, nil + default: + return 0, fmt.Errorf("unknown network with genesis hash %v", hash) + } +} + +// NewBitcoindClient returns a bitcoind client using the current bitcoind +// connection. This allows us to share the same connection using multiple +// clients. +func (c *BitcoindConn) NewBitcoindClient() *BitcoindClient { + return &BitcoindClient{ + quit: make(chan struct{}), + + id: atomic.AddUint64(&c.rescanClientCounter, 1), + + chainConn: c, + + rescanUpdate: make(chan interface{}), + watchedAddresses: make(map[string]struct{}), + watchedOutPoints: make(map[wire.OutPoint]struct{}), + watchedTxs: make(map[chainhash.Hash]struct{}), + + notificationQueue: NewConcurrentQueue(20), + txNtfns: make(chan *wire.MsgTx, 1000), + blockNtfns: make(chan *wire.MsgBlock, 100), + + mempool: make(map[chainhash.Hash]struct{}), + expiredMempool: make(map[int32]map[chainhash.Hash]struct{}), + } +} + +// AddClient adds a client to the set of active rescan clients of the current +// chain connection. This allows the connection to include the specified client +// in its notification delivery. +// +// NOTE: This function is safe for concurrent access. +func (c *BitcoindConn) AddClient(client *BitcoindClient) { + c.rescanClientsMtx.Lock() + defer c.rescanClientsMtx.Unlock() + + c.rescanClients[client.id] = client +} + +// RemoveClient removes the client with the given ID from the set of active +// rescan clients. Once removed, the client will no longer receive block and +// transaction notifications from the chain connection. +// +// NOTE: This function is safe for concurrent access. +func (c *BitcoindConn) RemoveClient(id uint64) { + c.rescanClientsMtx.Lock() + defer c.rescanClientsMtx.Unlock() + + delete(c.rescanClients, id) +} + +// isBlockPrunedErr determines if the error returned by the GetBlock RPC +// corresponds to the requested block being pruned. +func isBlockPrunedErr(err error) bool { + rpcErr, ok := err.(*btcjson.RPCError) + return ok && rpcErr.Code == btcjson.ErrRPCMisc && + rpcErr.Message == errBlockPrunedStr +} + +// GetBlock returns a raw block from the server given its hash. If the server +// has already pruned the block, it will be retrieved from one of its peers. +func (c *BitcoindConn) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) { + block, err := c.client.GetBlock(hash) + // Got the block from the backend successfully, return it. + if err == nil { + return block, nil + } + + // We failed getting the block from the backend for whatever reason. If + // it wasn't due to the block being pruned, return the error + // immediately. + if !isBlockPrunedErr(err) || c.prunedBlockDispatcher == nil { + return nil, err + } + + // Now that we know the block has been pruned for sure, request it from + // our backend peers. + blockChan, errChan := c.prunedBlockDispatcher.Query( + []*chainhash.Hash{hash}, + ) + + for { + select { + case block := <-blockChan: + return block, nil + + case err := <-errChan: + if err != nil { + return nil, err + } + + // errChan fired before blockChan with a nil error, wait + // for the block now. + + case <-c.quit: + return nil, ErrBitcoindClientShuttingDown + } + } +} + +// isASCII is a helper method that checks whether all bytes in `data` would be +// printable ASCII characters if interpreted as a string. +func isASCII(s string) bool { + for _, c := range s { + if c < 32 || c > 126 { + return false + } + } + return true +} diff --git a/btcclient/bitcoind_events.go b/btcclient/bitcoind_events.go new file mode 100644 index 00000000..945257b7 --- /dev/null +++ b/btcclient/bitcoind_events.go @@ -0,0 +1,705 @@ +package btcclient + +import ( + "bytes" + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/gozmq" +) + +const ( + // defaultBlockPollInterval is the default interval used for querying + // for new blocks. + defaultBlockPollInterval = time.Second * 10 + + // defaultTxPollInterval is the default interval used for querying + // for new mempool transactions. + defaultTxPollInterval = time.Second * 10 +) + +// BitcoindEvents is the interface that must be satisfied by any type that +// serves bitcoind block and transactions events. +type BitcoindEvents interface { + // TxNotifications will return a channel which will deliver new + // transactions. + TxNotifications() <-chan *wire.MsgTx + + // BlockNotifications will return a channel which will deliver new + // blocks. + BlockNotifications() <-chan *wire.MsgBlock + + // Start will kick off any goroutines required for operation. + Start() error + + // Stop will clean up any resources and goroutines. + Stop() error +} + +// NewBitcoindEventSubscriber initialises a new BitcoinEvents object impl +// depending on the config passed. +func NewBitcoindEventSubscriber(cfg *BitcoindConfig, + client *rpcclient.Client) (BitcoindEvents, error) { + + if cfg.PollingConfig != nil && cfg.ZMQConfig != nil { + return nil, fmt.Errorf("either PollingConfig or ZMQConfig " + + "should be specified, not both") + } + + if cfg.PollingConfig != nil { + if client == nil { + return nil, fmt.Errorf("rpc client must be given " + + "if rpc polling is to be used for event " + + "subscriptions") + } + + pollingEvents := newBitcoindRPCPollingEvents( + cfg.PollingConfig, client, + ) + + return pollingEvents, nil + } + + if cfg.ZMQConfig == nil { + return nil, fmt.Errorf("ZMQConfig must be specified if " + + "rpcpolling is disabled") + } + + return newBitcoindZMQEvents(cfg.ZMQConfig) +} + +// ZMQConfig holds all the config values needed to set up a ZMQ connection to +// bitcoind. +type ZMQConfig struct { + // ZMQBlockHost is the IP address and port of the bitcoind's rawblock + // listener. + ZMQBlockHost string + + // ZMQTxHost is the IP address and port of the bitcoind's rawtx + // listener. + ZMQTxHost string + + // ZMQReadDeadline represents the read deadline we'll apply when reading + // ZMQ messages from either subscription. + ZMQReadDeadline time.Duration +} + +// bitcoindZMQEvents delivers block and transaction notifications that it gets +// from ZMQ connections to bitcoind. +type bitcoindZMQEvents struct { + cfg *ZMQConfig + + // blockConn is the ZMQ connection we'll use to read raw block events. + blockConn *gozmq.Conn + + // txConn is the ZMQ connection we'll use to read raw transaction + // events. + txConn *gozmq.Conn + + // blockNtfns is a channel to which any new blocks will be sent. + blockNtfns chan *wire.MsgBlock + + // txNtfns is a channel to which any new transactions will be sent. + txNtfns chan *wire.MsgTx + + wg sync.WaitGroup + quit chan struct{} +} + +// newBitcoindZMQEvents initialises the necessary zmq connections to bitcoind. +func newBitcoindZMQEvents(cfg *ZMQConfig) (*bitcoindZMQEvents, error) { + // Establish two different ZMQ connections to bitcoind to retrieve block + // and transaction event notifications. We'll use two as a separation of + // concern to ensure one type of event isn't dropped from the connection + // queue due to another type of event filling it up. + zmqBlockConn, err := gozmq.Subscribe( + cfg.ZMQBlockHost, []string{rawBlockZMQCommand}, + cfg.ZMQReadDeadline, + ) + if err != nil { + return nil, fmt.Errorf("unable to subscribe for zmq block "+ + "events: %v", err) + } + + zmqTxConn, err := gozmq.Subscribe( + cfg.ZMQTxHost, []string{rawTxZMQCommand}, cfg.ZMQReadDeadline, + ) + if err != nil { + // Ensure that the block zmq connection is closed in the case + // that it succeeded but the tx zmq connection failed. + if err := zmqBlockConn.Close(); err != nil { + log.Errorf("could not close zmq block conn: %v", err) + } + + return nil, fmt.Errorf("unable to subscribe for zmq tx "+ + "events: %v", err) + } + + return &bitcoindZMQEvents{ + cfg: cfg, + blockConn: zmqBlockConn, + txConn: zmqTxConn, + blockNtfns: make(chan *wire.MsgBlock), + txNtfns: make(chan *wire.MsgTx), + quit: make(chan struct{}), + }, nil +} + +// Start spins off the bitcoindZMQEvent goroutines. +func (b *bitcoindZMQEvents) Start() error { + b.wg.Add(2) + go b.blockEventHandler() + go b.txEventHandler() + return nil +} + +// Stop cleans up any of the resources and goroutines held by bitcoindZMQEvents. +func (b *bitcoindZMQEvents) Stop() error { + var returnErr error + if err := b.txConn.Close(); err != nil { + returnErr = err + } + + if err := b.blockConn.Close(); err != nil { + returnErr = err + } + + close(b.quit) + b.wg.Wait() + return returnErr +} + +// TxNotifications returns a channel which will deliver new transactions. +func (b *bitcoindZMQEvents) TxNotifications() <-chan *wire.MsgTx { + return b.txNtfns +} + +// BlockNotifications returns a channel which will deliver new blocks. +func (b *bitcoindZMQEvents) BlockNotifications() <-chan *wire.MsgBlock { + return b.blockNtfns +} + +// blockEventHandler reads raw blocks events from the ZMQ block socket and +// forwards them along to the current rescan clients. +// +// NOTE: This must be run as a goroutine. +func (b *bitcoindZMQEvents) blockEventHandler() { + defer b.wg.Done() + + log.Info("Started listening for bitcoind block notifications via ZMQ "+ + "on", b.blockConn.RemoteAddr()) + + // Set up the buffers we expect our messages to consume. ZMQ + // messages from bitcoind include three parts: the command, the + // data, and the sequence number. + // + // We'll allocate a fixed data slice that we'll reuse when reading + // blocks from bitcoind through ZMQ. There's no need to recycle this + // slice (zero out) after using it, as further reads will overwrite the + // slice and we'll only be deserializing the bytes needed. + var ( + command [len(rawBlockZMQCommand)]byte + seqNum [seqNumLen]byte + data = make([]byte, maxRawBlockSize) + ) + + for { + // Before attempting to read from the ZMQ socket, we'll make + // sure to check if we've been requested to shut down. + select { + case <-b.quit: + return + default: + } + + // Poll an event from the ZMQ socket. + var ( + bufs = [][]byte{command[:], data, seqNum[:]} + err error + ) + bufs, err = b.blockConn.Receive(bufs) + if err != nil { + // EOF should only be returned if the connection was + // explicitly closed, so we can exit at this point. + if err == io.EOF { + return + } + + // It's possible that the connection to the socket + // continuously times out, so we'll prevent logging this + // error to prevent spamming the logs. + netErr, ok := err.(net.Error) + if ok && netErr.Timeout() { + log.Trace("Re-establishing timed out ZMQ " + + "block connection") + continue + } + + log.Errorf("Unable to receive ZMQ %v message: %v", + rawBlockZMQCommand, err) + continue + } + + // We have an event! We'll now ensure it is a block event, + // deserialize it, and report it to the different rescan + // clients. + eventType := string(bufs[0]) + switch eventType { + case rawBlockZMQCommand: + block := &wire.MsgBlock{} + r := bytes.NewReader(bufs[1]) + if err := block.Deserialize(r); err != nil { + log.Errorf("Unable to deserialize block: %v", + err) + continue + } + + select { + case b.blockNtfns <- block: + case <-b.quit: + return + } + + default: + // It's possible that the message wasn't fully read if + // bitcoind shuts down, which will produce an unreadable + // event type. To prevent from logging it, we'll make + // sure it conforms to the ASCII standard. + if eventType == "" || !isASCII(eventType) { + continue + } + + log.Warnf("Received unexpected event type from %v "+ + "subscription: %v", rawBlockZMQCommand, + eventType) + } + } +} + +// txEventHandler reads raw blocks events from the ZMQ block socket and forwards +// them along to the current rescan clients. +// +// NOTE: This must be run as a goroutine. +func (b *bitcoindZMQEvents) txEventHandler() { + defer b.wg.Done() + + log.Info("Started listening for bitcoind transaction notifications "+ + "via ZMQ on", b.txConn.RemoteAddr()) + + // Set up the buffers we expect our messages to consume. ZMQ + // messages from bitcoind include three parts: the command, the + // data, and the sequence number. + // + // We'll allocate a fixed data slice that we'll reuse when reading + // transactions from bitcoind through ZMQ. There's no need to recycle + // this slice (zero out) after using it, as further reads will overwrite + // the slice and we'll only be deserializing the bytes needed. + var ( + command [len(rawTxZMQCommand)]byte + seqNum [seqNumLen]byte + data = make([]byte, maxRawTxSize) + ) + + for { + // Before attempting to read from the ZMQ socket, we'll make + // sure to check if we've been requested to shut down. + select { + case <-b.quit: + return + default: + } + + // Poll an event from the ZMQ socket. + var ( + bufs = [][]byte{command[:], data, seqNum[:]} + err error + ) + bufs, err = b.txConn.Receive(bufs) + if err != nil { + // EOF should only be returned if the connection was + // explicitly closed, so we can exit at this point. + if err == io.EOF { + return + } + + // It's possible that the connection to the socket + // continuously times out, so we'll prevent logging this + // error to prevent spamming the logs. + netErr, ok := err.(net.Error) + if ok && netErr.Timeout() { + log.Trace("Re-establishing timed out ZMQ " + + "transaction connection") + continue + } + + log.Errorf("Unable to receive ZMQ %v message: %v", + rawTxZMQCommand, err) + continue + } + + // We have an event! We'll now ensure it is a transaction event, + // deserialize it, and report it to the different rescan + // clients. + eventType := string(bufs[0]) + switch eventType { + case rawTxZMQCommand: + tx := &wire.MsgTx{} + r := bytes.NewReader(bufs[1]) + if err := tx.Deserialize(r); err != nil { + log.Errorf("Unable to deserialize "+ + "transaction: %v", err) + continue + } + + select { + case b.txNtfns <- tx: + case <-b.quit: + return + } + + default: + // It's possible that the message wasn't fully read if + // bitcoind shuts down, which will produce an unreadable + // event type. To prevent from logging it, we'll make + // sure it conforms to the ASCII standard. + if eventType == "" || !isASCII(eventType) { + continue + } + + log.Warnf("Received unexpected event type from %v "+ + "subscription: %v", rawTxZMQCommand, eventType) + } + } +} + +// PollingConfig holds all the config options used for setting up +// bitcoindRPCPollingEvents. +type PollingConfig struct { + // BlockPollingInterval is the interval that will be used to poll + // bitcoind for new blocks. + BlockPollingInterval time.Duration + + // TxPollingInterval is the interval that will be used to poll bitcoind + // for new transactions. + TxPollingInterval time.Duration +} + +// bitcoindRPCPollingEvents delivers block and transaction notifications that +// it gets by polling bitcoind's rpc interface at regular intervals. +type bitcoindRPCPollingEvents struct { + cfg *PollingConfig + + client *rpcclient.Client + + // mempool holds all the transactions that we currently see as being in + // the mempool. This is used so that we know which transactions we have + // already sent notifications for. + mempool *mempool + + // blockNtfns is a channel to which any new blocks will be sent. + blockNtfns chan *wire.MsgBlock + + // txNtfns is a channel to which any new transactions will be sent. + txNtfns chan *wire.MsgTx + + wg sync.WaitGroup + quit chan struct{} +} + +// newBitcoindRPCPollingEvents instantiates a new bitcoindRPCPollingEvents +// object. +func newBitcoindRPCPollingEvents(cfg *PollingConfig, + client *rpcclient.Client) *bitcoindRPCPollingEvents { + + if cfg.BlockPollingInterval == 0 { + cfg.BlockPollingInterval = defaultBlockPollInterval + } + + if cfg.TxPollingInterval == 0 { + cfg.TxPollingInterval = defaultTxPollInterval + } + + return &bitcoindRPCPollingEvents{ + cfg: cfg, + client: client, + txNtfns: make(chan *wire.MsgTx), + blockNtfns: make(chan *wire.MsgBlock), + mempool: newMempool(), + quit: make(chan struct{}), + } +} + +// Start kicks off all the bitcoindRPCPollingEvents goroutines. +func (b *bitcoindRPCPollingEvents) Start() error { + info, err := b.client.GetBlockChainInfo() + if err != nil { + return err + } + + b.wg.Add(2) + go b.blockEventHandlerRPC(info.Blocks) + go b.txEventHandlerRPC() + return nil +} + +// Stop cleans up all the bitcoindRPCPollingEvents resources and goroutines. +func (b *bitcoindRPCPollingEvents) Stop() error { + close(b.quit) + b.wg.Wait() + return nil +} + +// TxNotifications returns a channel which will deliver new transactions. +func (b *bitcoindRPCPollingEvents) TxNotifications() <-chan *wire.MsgTx { + return b.txNtfns +} + +// BlockNotifications returns a channel which will deliver new blocks. +func (b *bitcoindRPCPollingEvents) BlockNotifications() <-chan *wire.MsgBlock { + return b.blockNtfns +} + +// blockEventHandlerRPC is a goroutine that uses the rpc client to check if we +// have a new block every so often. +func (b *bitcoindRPCPollingEvents) blockEventHandlerRPC(startHeight int32) { + defer b.wg.Done() + + ticker := time.NewTicker(b.cfg.BlockPollingInterval) + defer ticker.Stop() + + height := startHeight + log.Infof("Started polling for new bitcoind blocks via RPC at "+ + "height %d", height) + + for { + select { + case <-ticker.C: + // At every interval, we poll to see if there's a block + // with a height that exceeds the height that we + // previously recorded. + info, err := b.client.GetBlockChainInfo() + if err != nil { + log.Errorf("Unable to retrieve best block: "+ + "%v", err) + continue + } + + // If the block isn't new, we continue and wait for the + // next interval tick. In order to replicate the + // behaviour of the zmq block subscription, we only do + // a height based check here. We only deliver + // notifications if the new block has a height above the + // one we previously saw. The caller is left to + // determine if there has been a reorg. + if info.Blocks <= height { + continue + } + + // Since we do a height based check, we send + // notifications for each block with a height between + // the last height we recorded and the new height. + for i := height + 1; i <= info.Blocks; i++ { + newHash, err := b.client.GetBlockHash(int64(i)) + if err != nil { + log.Errorf("Unable to retrieve "+ + "block hash: %v", err) + continue + } + + newBlock, err := b.client.GetBlock(newHash) + if err != nil { + log.Errorf("Unable to retrieve "+ + "block: %v", err) + continue + } + + // notify the client of the new block. + select { + case b.blockNtfns <- newBlock: + case <-b.quit: + return + } + + // From our local mempool map, let's remove each + // of the transactions that are confirmed in + // this new block, since they are no longer in + // the mempool. + b.mempool.clean(newBlock.Transactions) + + height++ + } + + case <-b.quit: + return + } + } +} + +// txEventHandlerRPC is a goroutine that uses the RPC client to check the +// mempool for new transactions. +func (b *bitcoindRPCPollingEvents) txEventHandlerRPC() { + defer b.wg.Done() + + log.Info("Started polling for new bitcoind transactions via RPC.") + ticker := time.NewTicker(b.cfg.TxPollingInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // After each ticker interval, we poll the mempool to + // check for transactions we haven't seen yet. + txs, err := b.client.GetRawMempool() + if err != nil { + log.Errorf("Unable to retrieve mempool txs: "+ + "%v", err) + continue + } + + // Set all mempool txs to false. + b.mempool.unmarkAll() + + // We'll scan through the most recent txs in the + // mempool to see whether there are new txs that we + // need to send to the client. + for _, txHash := range txs { + // If the transaction is already in our local + // mempool, then we have already sent it to the + // client. + if b.mempool.contains(*txHash) { + // Mark the tx as true so that we know + // not to remove it from our internal + // mempool. + b.mempool.mark(*txHash) + continue + } + + // Grab full mempool transaction from hash. + tx, err := b.client.GetRawTransaction(txHash) + if err != nil { + log.Errorf("unable to fetch "+ + "transaction %s from "+ + "mempool: %v", txHash, err) + continue + } + + // Add the transaction to our local mempool. + // Note that we only do this after fetching + // the full raw transaction from bitcoind. + // We do this so that if that call happens to + // initially fail, then we will retry it on the + // next interval since it is still not in our + // local mempool. + b.mempool.add(*txHash) + + select { + case b.txNtfns <- tx.MsgTx(): + case <-b.quit: + return + } + } + + // Now, we clear our internal mempool of any unmarked + // transactions. These are all the transactions that + // we still have in the mempool but that were not + // returned in the latest GetRawMempool query. + b.mempool.deleteUnmarked() + + case <-b.quit: + return + } + } +} + +// mempool represents our view of the mempool and helps to keep track of which +// mempool transactions we already know about. The boolean in the txs map is +// used to indicate if we should remove the tx from our local mempool due to +// the chain backend's mempool no longer containing it. +type mempool struct { + sync.RWMutex + txs map[chainhash.Hash]bool +} + +// newMempool creates a new mempool object. +func newMempool() *mempool { + return &mempool{ + txs: make(map[chainhash.Hash]bool), + } +} + +// clean removes any of the given transactions from the mempool if they are +// found there. +func (m *mempool) clean(txs []*wire.MsgTx) { + m.Lock() + defer m.Unlock() + + for _, tx := range txs { + // If the transaction is in our mempool map, we need to delete + // it. + delete(m.txs, tx.TxHash()) + } +} + +// contains returns true if the given transaction hash is already in our +// mempool. +func (m *mempool) contains(hash chainhash.Hash) bool { + m.RLock() + defer m.RUnlock() + + _, ok := m.txs[hash] + return ok +} + +// add inserts the given hash into our mempool and marks it to indicate that it +// should not be deleted. +func (m *mempool) add(hash chainhash.Hash) { + m.Lock() + defer m.Unlock() + + m.txs[hash] = true +} + +// unmarkAll un-marks all the transactions in the mempool. This should be done +// just before we re-evaluate the contents of our local mempool comared to the +// chain backend's mempool. +func (m *mempool) unmarkAll() { + m.Lock() + defer m.Unlock() + + for hash := range m.txs { + m.txs[hash] = false + } +} + +// mark marks the transaction of the given hash to indicate that it is still +// present in the chain backend's mempool. +func (m *mempool) mark(hash chainhash.Hash) { + m.Lock() + defer m.Unlock() + + if _, ok := m.txs[hash]; !ok { + return + } + + m.txs[hash] = true +} + +// deleteUnmarked removes all the unmarked transactions from our local mempool. +func (m *mempool) deleteUnmarked() { + m.Lock() + defer m.Unlock() + + for hash, marked := range m.txs { + if marked { + continue + } + + delete(m.txs, hash) + } +} diff --git a/btcclient/bitcoind_events_test.go b/btcclient/bitcoind_events_test.go new file mode 100644 index 00000000..c959f2fd --- /dev/null +++ b/btcclient/bitcoind_events_test.go @@ -0,0 +1,491 @@ +package btcclient + +import ( + "fmt" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/integration/rpctest" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +// TestBitcoindEvents ensures that the BitcoindClient correctly delivers tx and +// block notifications for both the case where a ZMQ subscription is used and +// for the case where RPC polling is used. +func TestBitcoindEvents(t *testing.T) { + tests := []struct { + name string + rpcPolling bool + }{ + { + name: "Events via ZMQ subscriptions", + rpcPolling: false, + }, + { + name: "Events via RPC Polling", + rpcPolling: true, + }, + } + + // Set up 2 btcd miners. + miner1, miner2 := setupMiners(t) + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + // Set up a bitcoind node and connect it to miner 1. + btcClient := setupBitcoind( + t, miner1.P2PAddress(), test.rpcPolling, + ) + + // Test that the correct block `Connect` and + // `Disconnect` notifications are received during a + // re-org. + testReorg(t, miner1, miner2, btcClient) + + // Test that the expected block and transaction + // notifications are received. + testNotifications(t, miner1, btcClient) + }) + } +} + +// TestMempool tests that each method of the mempool struct works as expected. +func TestMempool(t *testing.T) { + m := newMempool() + + // Create a transaction. + tx1 := &wire.MsgTx{LockTime: 1} + + // Check that mempool doesn't have the tx yet. + require.False(t, m.contains(tx1.TxHash())) + + // Now add the tx. + m.add(tx1.TxHash()) + + // Mempool should now contain the tx. + require.True(t, m.contains(tx1.TxHash())) + + // Add another tx to the mempool. + tx2 := &wire.MsgTx{LockTime: 2} + m.add(tx2.TxHash()) + require.True(t, m.contains(tx2.TxHash())) + + // Clean the mempool of tx1 (this simulates a block being confirmed + // with tx1 in the block). + m.clean([]*wire.MsgTx{tx1}) + + // Ensure that tx1 is no longer in the mempool but that tx2 still is. + require.False(t, m.contains(tx1.TxHash())) + require.True(t, m.contains(tx2.TxHash())) + + // Lastly, we test that only marked transactions are deleted from the + // mempool. + + // Let's first re-add tx1 so that we have more txs to work with. + m.add(tx1.TxHash()) + + // Now, we unmark all the transactions. + m.unmarkAll() + + // Add tx3. This should automatically mark tx3. + tx3 := &wire.MsgTx{LockTime: 3} + m.add(tx3.TxHash()) + + // Let us now manually mark tx2. + m.mark(tx2.TxHash()) + + // Now we delete all unmarked txs. This should leave only tx2 and tx3 + // in the mempool. + m.deleteUnmarked() + + require.False(t, m.contains(tx1.TxHash())) + require.True(t, m.contains(tx2.TxHash())) + require.True(t, m.contains(tx3.TxHash())) +} + +// testNotifications tests that the correct notifications are received for +// blocks and transactions in the simple non-reorg case. +func testNotifications(t *testing.T, miner *rpctest.Harness, + client *BitcoindClient) { + + script, _, err := randPubKeyHashScript() + require.NoError(t, err) + + tx, err := miner.CreateTransaction( + []*wire.TxOut{{Value: 1000, PkScript: script}}, 5, false, + ) + require.NoError(t, err) + + hash := tx.TxHash() + + err = client.NotifyTx([]chainhash.Hash{hash}) + require.NoError(t, err) + + _, err = client.SendRawTransaction(tx, true) + require.NoError(t, err) + + ntfns := client.Notifications() + + miner.Client.Generate(1) + + // First, we expect to get a RelevantTx notification. + select { + case ntfn := <-ntfns: + tx, ok := ntfn.(RelevantTx) + if !ok { + t.Fatalf("Expected a notification of type "+ + "RelevantTx, got %T", ntfn) + } + + require.True(t, tx.TxRecord.Hash.IsEqual(&hash)) + + case <-time.After(time.Second): + t.Fatalf("timed out waiting for RelevantTx notification") + } + + // Then, we expect to get a FilteredBlockConnected notification. + select { + case ntfn := <-ntfns: + _, ok := ntfn.(FilteredBlockConnected) + if !ok { + t.Fatalf("Expected a notification of type "+ + "FilteredBlockConnected, got %T", ntfn) + } + + case <-time.After(time.Second): + t.Fatalf("timed out waiting for FilteredBlockConnected " + + "notification") + } + + // Lastly, we expect to get a BlockConnected notification. + select { + case ntfn := <-ntfns: + _, ok := ntfn.(BlockConnected) + if !ok { + t.Fatalf("Expected a notification of type "+ + "BlockConnected, got %T", ntfn) + } + + case <-time.After(time.Second): + t.Fatalf("timed out waiting for BlockConnected notification") + } +} + +// testReorg tests that the given BitcoindClient correctly responds to a chain +// re-org. +func testReorg(t *testing.T, miner1, miner2 *rpctest.Harness, + client *BitcoindClient) { + + miner1Hash, commonHeight, err := miner1.Client.GetBestBlock() + require.NoError(t, err) + + miner2Hash, miner2Height, err := miner2.Client.GetBestBlock() + require.NoError(t, err) + + require.Equal(t, commonHeight, miner2Height) + require.Equal(t, miner1Hash, miner2Hash) + + // Let miner2 generate a few blocks and ensure that our bitcoind client + // is notified of this block. + hashes, err := miner2.Client.Generate(5) + require.NoError(t, err) + require.Len(t, hashes, 5) + + ntfns := client.Notifications() + + for i := 0; i < 5; i++ { + commonHeight++ + ntfnHash := waitForBlockNtfn(t, ntfns, commonHeight, true) + require.True(t, ntfnHash.IsEqual(hashes[i])) + } + + // Now disconnect the two miners. + err = miner1.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove) + require.NoError(t, err) + + // Generate 5 blocks on miner2. + _, err = miner2.Client.Generate(5) + require.NoError(t, err) + + // Since the miners have been disconnected, we expect not to get any + // notifications from our client since our client is connected to + // miner1. + select { + case ntfn := <-ntfns: + t.Fatalf("received a notification of type %T but expected, "+ + "none", ntfn) + + case <-time.After(time.Millisecond * 500): + } + + // Now generate 3 blocks on miner1. Note that to force our client to + // experience a re-org, miner1 must generate fewer blocks here than + // miner2 so that when they reconnect, miner1 does a re-org to switch + // to the longer chain. + _, err = miner1.Client.Generate(3) + require.NoError(t, err) + + // Read the notifications for the new blocks + for i := 0; i < 3; i++ { + _ = waitForBlockNtfn(t, ntfns, commonHeight+int32(i+1), true) + } + + // Ensure that the two miners have different ideas of what the best + // block is. + hash1, height1, err := miner1.Client.GetBestBlock() + require.NoError(t, err) + require.Equal(t, commonHeight+3, height1) + + hash2, height2, err := miner2.Client.GetBestBlock() + require.NoError(t, err) + require.Equal(t, commonHeight+5, height2) + + require.False(t, hash1.IsEqual(hash2)) + + // Reconnect the miners. This should result in miner1 reorging to match + // miner2. Since our client is connected to a node connected to miner1, + // we should get the expected disconnected and connected notifications. + err = rpctest.ConnectNode(miner1, miner2) + require.NoError(t, err) + + err = rpctest.JoinNodes( + []*rpctest.Harness{miner1, miner2}, rpctest.Blocks, + ) + require.NoError(t, err) + + // Check that the miners are now on the same page. + hash1, height1, err = miner1.Client.GetBestBlock() + require.NoError(t, err) + + hash2, height2, err = miner2.Client.GetBestBlock() + require.NoError(t, err) + + require.Equal(t, commonHeight+5, height2) + require.Equal(t, commonHeight+5, height1) + require.True(t, hash1.IsEqual(hash2)) + + // We expect our client to get 3 BlockDisconnected notifications first + // signaling the unwinding of its top 3 blocks. + for i := 0; i < 3; i++ { + _ = waitForBlockNtfn(t, ntfns, commonHeight+int32(3-i), false) + } + + // Now we expect 5 BlockConnected notifications. + for i := 0; i < 5; i++ { + _ = waitForBlockNtfn(t, ntfns, commonHeight+int32(i+1), true) + } +} + +// waitForBlockNtfn waits on the passed channel for a BlockConnected or +// BlockDisconnected notification for a block of the expectedHeight. It returns +// hash of the notification if received. If the expected notification is not +// received within 2 seconds, the test is failed. Use the `connected` parameter +// to set whether a Connected or Disconnected notification is expected. +func waitForBlockNtfn(t *testing.T, ntfns <-chan interface{}, + expectedHeight int32, connected bool) chainhash.Hash { + + timer := time.NewTimer(2 * time.Second) + for { + select { + case nftn := <-ntfns: + switch ntfnType := nftn.(type) { + case BlockConnected: + if !connected { + fmt.Println("???") + continue + } + + if ntfnType.Height < expectedHeight { + continue + } else if ntfnType.Height != expectedHeight { + t.Fatalf("expected notification for "+ + "height %d, got height %d", + expectedHeight, ntfnType.Height) + } + + return ntfnType.Hash + + case BlockDisconnected: + if connected { + continue + } + + if ntfnType.Height > expectedHeight { + continue + } else if ntfnType.Height != expectedHeight { + t.Fatalf("expected notification for "+ + "height %d, got height %d", + expectedHeight, ntfnType.Height) + } + + return ntfnType.Hash + + default: + } + + case <-timer.C: + t.Fatalf("timed out waiting for block notification") + } + } +} + +// setUpMiners sets up two miners that can be used for a re-org test. +func setupMiners(t *testing.T) (*rpctest.Harness, *rpctest.Harness) { + trickle := fmt.Sprintf("--trickleinterval=%v", 10*time.Millisecond) + args := []string{trickle} + + miner1, err := rpctest.New( + &chaincfg.RegressionNetParams, nil, args, "", + ) + require.NoError(t, err) + + t.Cleanup(func() { + miner1.TearDown() + }) + + require.NoError(t, miner1.SetUp(true, 1)) + + miner2, err := rpctest.New( + &chaincfg.RegressionNetParams, nil, args, "", + ) + require.NoError(t, err) + + t.Cleanup(func() { + miner2.TearDown() + }) + + require.NoError(t, miner2.SetUp(false, 0)) + + // Connect the miners. + require.NoError(t, rpctest.ConnectNode(miner1, miner2)) + + err = rpctest.JoinNodes( + []*rpctest.Harness{miner1, miner2}, rpctest.Blocks, + ) + require.NoError(t, err) + + return miner1, miner2 +} + +// setupBitcoind starts up a bitcoind node with either a zmq connection or +// rpc polling connection and returns a client wrapper of this connection. +func setupBitcoind(t *testing.T, minerAddr string, + rpcPolling bool) *BitcoindClient { + t.Skip("TODO: this test requires the bitcoind binary, skip for now") + + // Start a bitcoind instance and connect it to miner1. + tempBitcoindDir, err := ioutil.TempDir("", "bitcoind") + require.NoError(t, err) + + zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket" + zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket" + t.Cleanup(func() { + os.RemoveAll(tempBitcoindDir) + }) + + rpcPort := rand.Int()%(65536-1024) + 1024 + bitcoind := exec.Command( + "bitcoind", + "-datadir="+tempBitcoindDir, + "-regtest", + "-connect="+minerAddr, + "-txindex", + "-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+ + "d$507c670e800a95284294edb5773b05544b"+ + "220110063096c221be9933c82d38e1", + fmt.Sprintf("-rpcport=%d", rpcPort), + "-disablewallet", + "-zmqpubrawblock="+zmqBlockHost, + "-zmqpubrawtx="+zmqTxHost, + ) + require.NoError(t, bitcoind.Start()) + + t.Cleanup(func() { + bitcoind.Process.Kill() + bitcoind.Wait() + }) + + // Wait for the bitcoind instance to start up. + time.Sleep(time.Second) + + host := fmt.Sprintf("127.0.0.1:%d", rpcPort) + cfg := &BitcoindConfig{ + ChainParams: &chaincfg.RegressionNetParams, + Host: host, + User: "weks", + Pass: "weks", + // Fields only required for pruned nodes, not + // needed for these tests. + Dialer: nil, + PrunedModeMaxPeers: 0, + } + + if rpcPolling { + cfg.PollingConfig = &PollingConfig{ + BlockPollingInterval: time.Millisecond * 100, + TxPollingInterval: time.Millisecond * 100, + } + } else { + cfg.ZMQConfig = &ZMQConfig{ + ZMQBlockHost: zmqBlockHost, + ZMQTxHost: zmqTxHost, + ZMQReadDeadline: 5 * time.Second, + } + } + + chainConn, err := NewBitcoindConn(cfg) + require.NoError(t, err) + require.NoError(t, chainConn.Start()) + + t.Cleanup(func() { + chainConn.Stop() + }) + + // Create a bitcoind client. + btcClient := chainConn.NewBitcoindClient() + require.NoError(t, btcClient.Start()) + + t.Cleanup(func() { + btcClient.Stop() + }) + + require.NoError(t, btcClient.NotifyBlocks()) + + return btcClient +} + +// randPubKeyHashScript generates a P2PKH script that pays to the public key of +// a randomly-generated private key. +func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) { + privKey, err := btcec.NewPrivateKey() + if err != nil { + return nil, nil, err + } + + pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) + addrScript, err := btcutil.NewAddressPubKeyHash( + pubKeyHash, &chaincfg.RegressionNetParams, + ) + if err != nil { + return nil, nil, err + } + + pkScript, err := txscript.PayToAddrScript(addrScript) + if err != nil { + return nil, nil, err + } + + return pkScript, privKey, nil +} diff --git a/btcclient/block_filterer.go b/btcclient/block_filterer.go new file mode 100644 index 00000000..e6fb348f --- /dev/null +++ b/btcclient/block_filterer.go @@ -0,0 +1,217 @@ +package btcclient + +import ( + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" +) + +// BlockFilterer is used to iteratively scan blocks for a set of addresses of +// interest. This is done by constructing reverse indexes mapping the +// addresses to a ScopedIndex, which permits the reconstruction of the exact +// child deriviation paths that reported matches. +// +// Once initialized, a BlockFilterer can be used to scan any number of blocks +// until a invocation of `FilterBlock` returns true. This allows the reverse +// indexes to be resused in the event that the set of addresses does not need to +// be altered. After a match is reported, a new BlockFilterer should be +// initialized with the updated set of addresses that include any new keys that +// are now within our look-ahead. +// +// We track internal and external addresses separately in order to conserve the +// amount of space occupied in memory. Specifically, the account and branch +// combined contribute only 1-bit of information when using the default scopes +// used by the wallet. Thus we can avoid storing an additional 64-bits per +// address of interest by not storing the full derivation paths, and instead +// opting to allow the caller to contextually infer the account (DefaultAccount) +// and branch (Internal or External). +type BlockFilterer struct { + // Params specifies the chain params of the current network. + Params *chaincfg.Params + + // ExReverseFilter holds a reverse index mapping an external address to + // the scoped index from which it was derived. + ExReverseFilter map[string]waddrmgr.ScopedIndex + + // InReverseFilter holds a reverse index mapping an internal address to + // the scoped index from which it was derived. + InReverseFilter map[string]waddrmgr.ScopedIndex + + // WathcedOutPoints is a global set of outpoints being tracked by the + // wallet. This allows the block filterer to check for spends from an + // outpoint we own. + WatchedOutPoints map[wire.OutPoint]btcutil.Address + + // FoundExternal is a two-layer map recording the scope and index of + // external addresses found in a single block. + FoundExternal map[waddrmgr.KeyScope]map[uint32]struct{} + + // FoundInternal is a two-layer map recording the scope and index of + // internal addresses found in a single block. + FoundInternal map[waddrmgr.KeyScope]map[uint32]struct{} + + // FoundOutPoints is a set of outpoints found in a single block whose + // address belongs to the wallet. + FoundOutPoints map[wire.OutPoint]btcutil.Address + + // RelevantTxns records the transactions found in a particular block + // that contained matches from an address in either ExReverseFilter or + // InReverseFilter. + RelevantTxns []*wire.MsgTx +} + +// NewBlockFilterer constructs the reverse indexes for the current set of +// external and internal addresses that we are searching for, and is used to +// scan successive blocks for addresses of interest. A particular block filter +// can be reused until the first call from `FitlerBlock` returns true. +func NewBlockFilterer(params *chaincfg.Params, + req *FilterBlocksRequest) *BlockFilterer { + + // Construct a reverse index by address string for the requested + // external addresses. + nExAddrs := len(req.ExternalAddrs) + exReverseFilter := make(map[string]waddrmgr.ScopedIndex, nExAddrs) + for scopedIndex, addr := range req.ExternalAddrs { + exReverseFilter[addr.EncodeAddress()] = scopedIndex + } + + // Construct a reverse index by address string for the requested + // internal addresses. + nInAddrs := len(req.InternalAddrs) + inReverseFilter := make(map[string]waddrmgr.ScopedIndex, nInAddrs) + for scopedIndex, addr := range req.InternalAddrs { + inReverseFilter[addr.EncodeAddress()] = scopedIndex + } + + foundExternal := make(map[waddrmgr.KeyScope]map[uint32]struct{}) + foundInternal := make(map[waddrmgr.KeyScope]map[uint32]struct{}) + foundOutPoints := make(map[wire.OutPoint]btcutil.Address) + + return &BlockFilterer{ + Params: params, + ExReverseFilter: exReverseFilter, + InReverseFilter: inReverseFilter, + WatchedOutPoints: req.WatchedOutPoints, + FoundExternal: foundExternal, + FoundInternal: foundInternal, + FoundOutPoints: foundOutPoints, + } +} + +// FilterBlock parses all txns in the provided block, searching for any that +// contain addresses of interest in either the external or internal reverse +// filters. This method return true iff the block contains a non-zero number of +// addresses of interest, or a transaction in the block spends from outpoints +// controlled by the wallet. +func (bf *BlockFilterer) FilterBlock(block *wire.MsgBlock) bool { + var hasRelevantTxns bool + for _, tx := range block.Transactions { + if bf.FilterTx(tx) { + bf.RelevantTxns = append(bf.RelevantTxns, tx) + hasRelevantTxns = true + } + } + + return hasRelevantTxns +} + +// FilterTx scans all txouts in the provided txn, testing to see if any found +// addresses match those contained within the external or internal reverse +// indexes. This method returns true iff the txn contains a non-zero number of +// addresses of interest, or the transaction spends from an outpoint that +// belongs to the wallet. +func (bf *BlockFilterer) FilterTx(tx *wire.MsgTx) bool { + var isRelevant bool + + // First, check the inputs to this transaction to see if they spend any + // inputs belonging to the wallet. In addition to checking + // WatchedOutPoints, we also check FoundOutPoints, in case a txn spends + // from an outpoint created in the same block. + for _, in := range tx.TxIn { + if _, ok := bf.WatchedOutPoints[in.PreviousOutPoint]; ok { + isRelevant = true + } + if _, ok := bf.FoundOutPoints[in.PreviousOutPoint]; ok { + isRelevant = true + } + } + + // Now, parse all of the outputs created by this transactions, and see + // if they contain any addresses known the wallet using our reverse + // indexes for both external and internal addresses. If a new output is + // found, we will add the outpoint to our set of FoundOutPoints. + for i, out := range tx.TxOut { + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + out.PkScript, bf.Params, + ) + if err != nil { + log.Warnf("Could not parse output script in %s:%d: %v", + tx.TxHash(), i, err) + continue + } + + if !bf.FilterOutputAddrs(addrs) { + continue + } + + // If we've reached this point, then the output contains an + // address of interest. + isRelevant = true + + // Record the outpoint that containing the address in our set of + // found outpoints, so that the caller can update its global + // set of watched outpoints. + outPoint := wire.OutPoint{ + Hash: *btcutil.NewTx(tx).Hash(), + Index: uint32(i), + } + + bf.FoundOutPoints[outPoint] = addrs[0] + } + + return isRelevant +} + +// FilterOutputAddrs tests the set of addresses against the block filterer's +// external and internal reverse address indexes. If any are found, they are +// added to set of external and internal found addresses, respectively. This +// method returns true iff a non-zero number of the provided addresses are of +// interest. +func (bf *BlockFilterer) FilterOutputAddrs(addrs []btcutil.Address) bool { + var isRelevant bool + for _, addr := range addrs { + addrStr := addr.EncodeAddress() + if scopedIndex, ok := bf.ExReverseFilter[addrStr]; ok { + bf.foundExternal(scopedIndex) + isRelevant = true + } + if scopedIndex, ok := bf.InReverseFilter[addrStr]; ok { + bf.foundInternal(scopedIndex) + isRelevant = true + } + } + + return isRelevant +} + +// foundExternal marks the scoped index as found within the block filterer's +// FoundExternal map. If this the first index found for a particular scope, the +// scope's second layer map will be initialized before marking the index. +func (bf *BlockFilterer) foundExternal(scopedIndex waddrmgr.ScopedIndex) { + if _, ok := bf.FoundExternal[scopedIndex.Scope]; !ok { + bf.FoundExternal[scopedIndex.Scope] = make(map[uint32]struct{}) + } + bf.FoundExternal[scopedIndex.Scope][scopedIndex.Index] = struct{}{} +} + +// foundInternal marks the scoped index as found within the block filterer's +// FoundInternal map. If this the first index found for a particular scope, the +// scope's second layer map will be initialized before marking the index. +func (bf *BlockFilterer) foundInternal(scopedIndex waddrmgr.ScopedIndex) { + if _, ok := bf.FoundInternal[scopedIndex.Scope]; !ok { + bf.FoundInternal[scopedIndex.Scope] = make(map[uint32]struct{}) + } + bf.FoundInternal[scopedIndex.Scope][scopedIndex.Index] = struct{}{} +} diff --git a/btcclient/block_filterer_test.go b/btcclient/block_filterer_test.go new file mode 100644 index 00000000..3d0702c6 --- /dev/null +++ b/btcclient/block_filterer_test.go @@ -0,0 +1,324 @@ +package btcclient_test + +import ( + "reflect" + "testing" + "time" + + "github.com/babylonchain/vigilante/btcclient" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +var Block100000 = wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: chainhash.Hash([32]byte{ // Make go vet happy. + 0x50, 0x12, 0x01, 0x19, 0x17, 0x2a, 0x61, 0x04, + 0x21, 0xa6, 0xc3, 0x01, 0x1d, 0xd3, 0x30, 0xd9, + 0xdf, 0x07, 0xb6, 0x36, 0x16, 0xc2, 0xcc, 0x1f, + 0x1c, 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + }), // 000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250 + MerkleRoot: chainhash.Hash([32]byte{ // Make go vet happy. + 0x66, 0x57, 0xa9, 0x25, 0x2a, 0xac, 0xd5, 0xc0, + 0xb2, 0x94, 0x09, 0x96, 0xec, 0xff, 0x95, 0x22, + 0x28, 0xc3, 0x06, 0x7c, 0xc3, 0x8d, 0x48, 0x85, + 0xef, 0xb5, 0xa4, 0xac, 0x42, 0x47, 0xe9, 0xf3, + }), // f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766 + Timestamp: time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC + Bits: 0x1b04864c, // 453281356 + Nonce: 0x10572b0f, // 274148111 + }, + Transactions: []*wire.MsgTx{ + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{ + 0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02, + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x12a05f200, // 5000000000 + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, + 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, + 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, + 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, + 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, + 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, + 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, + 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, + 0x84, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, + 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, + 0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, + 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87, + }), // 87a157f3fd88ac7907c05fc55e271dc4acdc5605d187d646604ca8c0e9382e03 + Index: 0, + }, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, + 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, + 0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, + 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, + 0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, + 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, + 0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c, + 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, + 0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, + 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, + 0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, + 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, + 0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, + 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, + 0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b, + 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, + 0xd3, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0x2123e300, // 556000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, + 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, + 0xf7, 0xf5, 0x8b, 0x32, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + { + Value: 0x108e20f00, // 4444000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, + 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, + 0x52, 0xde, 0x3d, 0x7c, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, + 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, + 0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, + 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf, + }), // cf4e2978d0611ce46592e02d7e7daf8627a316ab69759a9f3df109a7f2bf3ec3 + Index: 1, + }, + SignatureScript: []byte{ + 0x47, // OP_DATA_71 + 0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, + 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, + 0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, + 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, + 0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, + 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, + 0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c, + 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, + 0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01, + 0x41, // OP_DATA_65 + 0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, + 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, + 0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, + 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, + 0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, + 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, + 0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95, + 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, + 0x0f, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0xf4240, // 1000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, + 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, + 0xad, 0xbe, 0x7e, 0x10, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + { + Value: 0x11d260c0, // 299000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, + 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, + 0xb3, 0x40, 0x9c, 0xd9, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + { + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash([32]byte{ // Make go vet happy. + 0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73, + 0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac, + 0x3b, 0x24, 0x0c, 0x84, 0xb9, 0x17, 0xa3, 0x90, + 0x9b, 0xa1, 0xc4, 0x3d, 0xed, 0x5f, 0x51, 0xf4, + }), // f4515fed3dc4a19b90a317b9840c243bac26114cf637522373a7d486b372600b + Index: 0, + }, + SignatureScript: []byte{ + 0x49, // OP_DATA_73 + 0x30, 0x46, 0x02, 0x21, 0x00, 0xbb, 0x1a, 0xd2, + 0x6d, 0xf9, 0x30, 0xa5, 0x1c, 0xce, 0x11, 0x0c, + 0xf4, 0x4f, 0x7a, 0x48, 0xc3, 0xc5, 0x61, 0xfd, + 0x97, 0x75, 0x00, 0xb1, 0xae, 0x5d, 0x6b, 0x6f, + 0xd1, 0x3d, 0x0b, 0x3f, 0x4a, 0x02, 0x21, 0x00, + 0xc5, 0xb4, 0x29, 0x51, 0xac, 0xed, 0xff, 0x14, + 0xab, 0xba, 0x27, 0x36, 0xfd, 0x57, 0x4b, 0xdb, + 0x46, 0x5f, 0x3e, 0x6f, 0x8d, 0xa1, 0x2e, 0x2c, + 0x53, 0x03, 0x95, 0x4a, 0xca, 0x7f, 0x78, 0xf3, + 0x01, // 73-byte signature + 0x41, // OP_DATA_65 + 0x04, 0xa7, 0x13, 0x5b, 0xfe, 0x82, 0x4c, 0x97, + 0xec, 0xc0, 0x1e, 0xc7, 0xd7, 0xe3, 0x36, 0x18, + 0x5c, 0x81, 0xe2, 0xaa, 0x2c, 0x41, 0xab, 0x17, + 0x54, 0x07, 0xc0, 0x94, 0x84, 0xce, 0x96, 0x94, + 0xb4, 0x49, 0x53, 0xfc, 0xb7, 0x51, 0x20, 0x65, + 0x64, 0xa9, 0xc2, 0x4d, 0xd0, 0x94, 0xd4, 0x2f, + 0xdb, 0xfd, 0xd5, 0xaa, 0xd3, 0xe0, 0x63, 0xce, + 0x6a, 0xf4, 0xcf, 0xaa, 0xea, 0x4e, 0xa1, 0x4f, + 0xbb, // 65-byte pubkey + }, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 0xf4240, // 1000000 + PkScript: []byte{ + 0x76, // OP_DUP + 0xa9, // OP_HASH160 + 0x14, // OP_DATA_20 + 0x39, 0xaa, 0x3d, 0x56, 0x9e, 0x06, 0xa1, 0xd7, + 0x92, 0x6d, 0xc4, 0xbe, 0x11, 0x93, 0xc9, 0x9b, + 0xf2, 0xeb, 0x9e, 0xe0, + 0x88, // OP_EQUALVERIFY + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 0, + }, + }, +} + +// TestBlockFiltererOneInOneOut tests the correctness of the BlockFilterer in +// finding outpoints that spend from a "watched outpoint", even if they do not +// send to an address controlled by the wallet. +func TestBlockFiltererOneInOneOut(t *testing.T) { + // Watch for spend from prev in in first and last tx, both of which are + // single input/single output. + firstTx := Block100000.Transactions[1] + lastTx := Block100000.Transactions[3] + + // Add each of their single previous outpoints to the set of watched + // outpoints to filter for. + watchedOutPoints := make(map[wire.OutPoint]btcutil.Address) + watchedOutPoints[firstTx.TxIn[0].PreviousOutPoint] = &btcutil.AddressWitnessPubKeyHash{} + watchedOutPoints[lastTx.TxIn[0].PreviousOutPoint] = &btcutil.AddressWitnessPubKeyHash{} + + // Construct a filter request, watching only for the outpoints above, + // and construct a block filterer. + req := &btcclient.FilterBlocksRequest{ + WatchedOutPoints: watchedOutPoints, + } + blockFilterer := btcclient.NewBlockFilterer(&chaincfg.SimNetParams, req) + + // Filter block 100000, which should find matches for the watched + // outpoints. + match := blockFilterer.FilterBlock(&Block100000) + if !match { + t.Fatalf("failed to find matches when filtering for " + + "1-in-1-out txns") + } + + // We should find exactly two relevant transactions added to the block + // filterer, then we check that both the first and last txns are found + // in that list. + assertNumRelevantTxns(t, blockFilterer, 2) + assertRelevantTxnsContains(t, blockFilterer, firstTx) + assertRelevantTxnsContains(t, blockFilterer, lastTx) +} + +// assertNumRelevantTxns checks that the set of relevant txns found in a block +// filterer is of a specific size. +func assertNumRelevantTxns(t *testing.T, bf *btcclient.BlockFilterer, size int) { + count := len(bf.RelevantTxns) + if count != size { + t.Fatalf("unexpected number of relevant txns: "+ + "want %v, got %v", size, count) + } +} + +// assertRelevantTxnsContains checks that the wantTx is found in the block +// filterers set of relevant txns. +func assertRelevantTxnsContains(t *testing.T, bf *btcclient.BlockFilterer, wantTx *wire.MsgTx) { + for _, relevantTx := range bf.RelevantTxns { + if reflect.DeepEqual(relevantTx, wantTx) { + return + } + } + + t.Fatalf("unable to find tx: %x in %d relevant txns", wantTx.TxHash(), + len(bf.RelevantTxns)) +} diff --git a/btcclient/client.go b/btcclient/client.go new file mode 100644 index 00000000..cba3d337 --- /dev/null +++ b/btcclient/client.go @@ -0,0 +1,510 @@ +// Copyright (c) 2013-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcclient + +import ( + "errors" + "sync" + "time" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/gcs" + "github.com/btcsuite/btcd/btcutil/gcs/builder" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wtxmgr" +) + +var _ Interface = &Client{} + +// Client represents a persistent client connection to a bitcoin RPC server +// for information regarding the current best block chain. +type Client struct { + *rpcclient.Client + connConfig *rpcclient.ConnConfig // Work around unexported field + chainParams *chaincfg.Params + reconnectAttempts int + + enqueueNotification chan interface{} + dequeueNotification chan interface{} + currentBlock chan *waddrmgr.BlockStamp + + quit chan struct{} + wg sync.WaitGroup + started bool + quitMtx sync.Mutex +} + +// NewClient creates a client connection to the server described by the +// connect string. If disableTLS is false, the remote RPC certificate must be +// provided in the certs slice. The connection is not established immediately, +// but must be done using the Start method. If the remote server does not +// operate on the same bitcoin network as described by the passed chain +// parameters, the connection will be disconnected. +func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, + disableTLS bool, reconnectAttempts int) (*Client, error) { + + if reconnectAttempts < 0 { + return nil, errors.New("reconnectAttempts must be positive") + } + + client := &Client{ + connConfig: &rpcclient.ConnConfig{ + Host: connect, + Endpoint: "ws", + User: user, + Pass: pass, + Certificates: certs, + DisableAutoReconnect: false, + DisableConnectOnNew: true, + DisableTLS: disableTLS, + }, + chainParams: chainParams, + reconnectAttempts: reconnectAttempts, + enqueueNotification: make(chan interface{}), + dequeueNotification: make(chan interface{}), + currentBlock: make(chan *waddrmgr.BlockStamp), + quit: make(chan struct{}), + } + ntfnCallbacks := &rpcclient.NotificationHandlers{ + OnClientConnected: client.onClientConnect, + OnBlockConnected: client.onBlockConnected, + OnBlockDisconnected: client.onBlockDisconnected, + OnRecvTx: client.onRecvTx, + OnRedeemingTx: client.onRedeemingTx, + OnRescanFinished: client.onRescanFinished, + OnRescanProgress: client.onRescanProgress, + } + rpcClient, err := rpcclient.New(client.connConfig, ntfnCallbacks) + if err != nil { + return nil, err + } + client.Client = rpcClient + return client, nil +} + +// BackEnd returns the name of the driver. +func (c *Client) BackEnd() string { + return "btcd" +} + +// Start attempts to establish a client connection with the remote server. +// If successful, handler goroutines are started to process notifications +// sent by the server. After a limited number of connection attempts, this +// function gives up, and therefore will not block forever waiting for the +// connection to be established to a server that may not exist. +func (c *Client) Start() error { + err := c.Connect(c.reconnectAttempts) + if err != nil { + return err + } + + // Verify that the server is running on the expected network. + net, err := c.GetCurrentNet() + if err != nil { + c.Disconnect() + return err + } + if net != c.chainParams.Net { + c.Disconnect() + return errors.New("mismatched networks") + } + + c.quitMtx.Lock() + c.started = true + c.quitMtx.Unlock() + + c.wg.Add(1) + go c.handler() + return nil +} + +// Stop disconnects the client and signals the shutdown of all goroutines +// started by Start. +func (c *Client) Stop() { + c.quitMtx.Lock() + select { + case <-c.quit: + default: + close(c.quit) + c.Client.Shutdown() + + if !c.started { + close(c.dequeueNotification) + } + } + c.quitMtx.Unlock() +} + +// IsCurrent returns whether the chain backend considers its view of the network +// as "current". +func (c *Client) IsCurrent() bool { + bestHash, _, err := c.GetBestBlock() + if err != nil { + return false + } + bestHeader, err := c.GetBlockHeader(bestHash) + if err != nil { + return false + } + return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta)) +} + +// Rescan wraps the normal Rescan command with an additional parameter that +// allows us to map an outpoint to the address in the chain that it pays to. +// This is useful when using BIP 158 filters as they include the prev pkScript +// rather than the full outpoint. +func (c *Client) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address, + outPoints map[wire.OutPoint]btcutil.Address) error { + + flatOutpoints := make([]*wire.OutPoint, 0, len(outPoints)) + for ops := range outPoints { + ops := ops + + flatOutpoints = append(flatOutpoints, &ops) + } + + return c.Client.Rescan(startHash, addrs, flatOutpoints) // nolint:staticcheck +} + +// WaitForShutdown blocks until both the client has finished disconnecting +// and all handlers have exited. +func (c *Client) WaitForShutdown() { + c.Client.WaitForShutdown() + c.wg.Wait() +} + +// Notifications returns a channel of parsed notifications sent by the remote +// bitcoin RPC server. This channel must be continually read or the process +// may abort for running out memory, as unread notifications are queued for +// later reads. +func (c *Client) Notifications() <-chan interface{} { + return c.dequeueNotification +} + +// BlockStamp returns the latest block notified by the client, or an error +// if the client has been shut down. +func (c *Client) BlockStamp() (*waddrmgr.BlockStamp, error) { + select { + case bs := <-c.currentBlock: + return bs, nil + case <-c.quit: + return nil, errors.New("disconnected") + } +} + +// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any +// addresses of interest. For each requested block, the corresponding compact +// filter will first be checked for matches, skipping those that do not report +// anything. If the filter returns a positive match, the full block will be +// fetched and filtered. This method returns a FilterBlocksResponse for the first +// block containing a matching address. If no matches are found in the range of +// blocks requested, the returned response will be nil. +func (c *Client) FilterBlocks( + req *FilterBlocksRequest) (*FilterBlocksResponse, error) { + + blockFilterer := NewBlockFilterer(c.chainParams, req) + + // Construct the watchlist using the addresses and outpoints contained + // in the filter blocks request. + watchList, err := buildFilterBlocksWatchList(req) + if err != nil { + return nil, err + } + + // Iterate over the requested blocks, fetching the compact filter for + // each one, and matching it against the watchlist generated above. If + // the filter returns a positive match, the full block is then requested + // and scanned for addresses using the block filterer. + for i, blk := range req.Blocks { + rawFilter, err := c.GetCFilter(&blk.Hash, wire.GCSFilterRegular) + if err != nil { + return nil, err + } + + // Ensure the filter is large enough to be deserialized. + if len(rawFilter.Data) < 4 { + continue + } + + filter, err := gcs.FromNBytes( + builder.DefaultP, builder.DefaultM, rawFilter.Data, + ) + if err != nil { + return nil, err + } + + // Skip any empty filters. + if filter.N() == 0 { + continue + } + + key := builder.DeriveKey(&blk.Hash) + matched, err := filter.MatchAny(key, watchList) + if err != nil { + return nil, err + } else if !matched { + continue + } + + log.Infof("Fetching block height=%d hash=%v", + blk.Height, blk.Hash) + + rawBlock, err := c.GetBlock(&blk.Hash) + if err != nil { + return nil, err + } + + if !blockFilterer.FilterBlock(rawBlock) { + continue + } + + // If any external or internal addresses were detected in this + // block, we return them to the caller so that the rescan + // windows can widened with subsequent addresses. The + // `BatchIndex` is returned so that the caller can compute the + // *next* block from which to begin again. + resp := &FilterBlocksResponse{ + BatchIndex: uint32(i), + BlockMeta: blk, + FoundExternalAddrs: blockFilterer.FoundExternal, + FoundInternalAddrs: blockFilterer.FoundInternal, + FoundOutPoints: blockFilterer.FoundOutPoints, + RelevantTxns: blockFilterer.RelevantTxns, + } + + return resp, nil + } + + // No addresses were found for this range. + return nil, nil +} + +// parseBlock parses a btcws definition of the block a tx is mined it to the +// Block structure of the wtxmgr package, and the block index. This is done +// here since rpcclient doesn't parse this nicely for us. +func parseBlock(block *btcjson.BlockDetails) (*wtxmgr.BlockMeta, error) { + if block == nil { + return nil, nil + } + blkHash, err := chainhash.NewHashFromStr(block.Hash) + if err != nil { + return nil, err + } + blk := &wtxmgr.BlockMeta{ + Block: wtxmgr.Block{ + Height: block.Height, + Hash: *blkHash, + }, + Time: time.Unix(block.Time, 0), + } + return blk, nil +} + +func (c *Client) onClientConnect() { + select { + case c.enqueueNotification <- ClientConnected{}: + case <-c.quit: + } +} + +func (c *Client) onBlockConnected(hash *chainhash.Hash, height int32, time time.Time) { + select { + case c.enqueueNotification <- BlockConnected{ + Block: wtxmgr.Block{ + Hash: *hash, + Height: height, + }, + Time: time, + }: + case <-c.quit: + } +} + +func (c *Client) onBlockDisconnected(hash *chainhash.Hash, height int32, time time.Time) { + select { + case c.enqueueNotification <- BlockDisconnected{ + Block: wtxmgr.Block{ + Hash: *hash, + Height: height, + }, + Time: time, + }: + case <-c.quit: + } +} + +func (c *Client) onRecvTx(tx *btcutil.Tx, block *btcjson.BlockDetails) { + blk, err := parseBlock(block) + if err != nil { + // Log and drop improper notification. + log.Errorf("recvtx notification bad block: %v", err) + return + } + + rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(), time.Now()) + if err != nil { + log.Errorf("Cannot create transaction record for relevant "+ + "tx: %v", err) + return + } + select { + case c.enqueueNotification <- RelevantTx{rec, blk}: + case <-c.quit: + } +} + +func (c *Client) onRedeemingTx(tx *btcutil.Tx, block *btcjson.BlockDetails) { + // Handled exactly like recvtx notifications. + c.onRecvTx(tx, block) +} + +func (c *Client) onRescanProgress(hash *chainhash.Hash, height int32, blkTime time.Time) { + select { + case c.enqueueNotification <- &RescanProgress{hash, height, blkTime}: + case <-c.quit: + } +} + +func (c *Client) onRescanFinished(hash *chainhash.Hash, height int32, blkTime time.Time) { + select { + case c.enqueueNotification <- &RescanFinished{hash, height, blkTime}: + case <-c.quit: + } + +} + +// handler maintains a queue of notifications and the current state (best +// block) of the chain. +func (c *Client) handler() { + hash, height, err := c.GetBestBlock() + if err != nil { + log.Errorf("Failed to receive best block from chain server: %v", err) + c.Stop() + c.wg.Done() + return + } + + bs := &waddrmgr.BlockStamp{Hash: *hash, Height: height} + + // TODO: Rather than leaving this as an unbounded queue for all types of + // notifications, try dropping ones where a later enqueued notification + // can fully invalidate one waiting to be processed. For example, + // blockconnected notifications for greater block heights can remove the + // need to process earlier blockconnected notifications still waiting + // here. + + var notifications []interface{} + enqueue := c.enqueueNotification + var dequeue chan interface{} + var next interface{} +out: + for { + select { + case n, ok := <-enqueue: + if !ok { + // If no notifications are queued for handling, + // the queue is finished. + if len(notifications) == 0 { + break out + } + // nil channel so no more reads can occur. + enqueue = nil + continue + } + if len(notifications) == 0 { + next = n + dequeue = c.dequeueNotification + } + notifications = append(notifications, n) + + case dequeue <- next: + if n, ok := next.(BlockConnected); ok { + bs = &waddrmgr.BlockStamp{ + Height: n.Height, + Hash: n.Hash, + } + } + + notifications[0] = nil + notifications = notifications[1:] + if len(notifications) != 0 { + next = notifications[0] + } else { + // If no more notifications can be enqueued, the + // queue is finished. + if enqueue == nil { + break out + } + dequeue = nil + } + + case c.currentBlock <- bs: + + case <-c.quit: + break out + } + } + + c.Stop() + close(c.dequeueNotification) + c.wg.Done() +} + +// POSTClient creates the equivalent HTTP POST rpcclient.Client. +func (c *Client) POSTClient() (*rpcclient.Client, error) { + configCopy := *c.connConfig + configCopy.HTTPPostMode = true + return rpcclient.New(&configCopy, nil) +} + +// buildFilterBlocksWatchList constructs a watchlist used for matching against a +// cfilter from a FilterBlocksRequest. The watchlist will be populated with all +// external addresses, internal addresses, and outpoints contained in the +// request. +func buildFilterBlocksWatchList(req *FilterBlocksRequest) ([][]byte, error) { + // Construct a watch list containing the script addresses of all + // internal and external addresses that were requested, in addition to + // the set of outpoints currently being watched. + watchListSize := len(req.ExternalAddrs) + + len(req.InternalAddrs) + + len(req.WatchedOutPoints) + + watchList := make([][]byte, 0, watchListSize) + + for _, addr := range req.ExternalAddrs { + p2shAddr, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, err + } + + watchList = append(watchList, p2shAddr) + } + + for _, addr := range req.InternalAddrs { + p2shAddr, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, err + } + + watchList = append(watchList, p2shAddr) + } + + for _, addr := range req.WatchedOutPoints { + addr, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, err + } + + watchList = append(watchList, addr) + } + + return watchList, nil +} diff --git a/btcclient/interface.go b/btcclient/interface.go new file mode 100644 index 00000000..16180bae --- /dev/null +++ b/btcclient/interface.go @@ -0,0 +1,125 @@ +package btcclient + +import ( + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wtxmgr" +) + +// isCurrentDelta is the delta duration we'll use from the present time to +// determine if a backend is considered "current", i.e. synced to the tip of +// the chain. +const isCurrentDelta = 2 * time.Hour + +// BackEnds returns a list of the available back ends. +// TODO: Refactor each into a driver and use dynamic registration. +func BackEnds() []string { + return []string{ + "bitcoind", + "btcd", + "neutrino", + "bitcoind-rpc-polling", + } +} + +// Interface allows more than one backing blockchain source, such as a +// btcd RPC chain server, or an SPV library, as long as we write a driver for +// it. +type Interface interface { + Start() error + Stop() + WaitForShutdown() + GetBestBlock() (*chainhash.Hash, int32, error) + GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) + GetBlockHash(int64) (*chainhash.Hash, error) + GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error) + IsCurrent() bool + FilterBlocks(*FilterBlocksRequest) (*FilterBlocksResponse, error) + BlockStamp() (*waddrmgr.BlockStamp, error) + SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash, error) + Rescan(*chainhash.Hash, []btcutil.Address, map[wire.OutPoint]btcutil.Address) error + NotifyReceived([]btcutil.Address) error + NotifyBlocks() error + Notifications() <-chan interface{} + BackEnd() string +} + +// Notification types. These are defined here and processed from from reading +// a notificationChan to avoid handling these notifications directly in +// rpcclient callbacks, which isn't very Go-like and doesn't allow +// blocking client calls. +type ( + // ClientConnected is a notification for when a client connection is + // opened or reestablished to the chain server. + ClientConnected struct{} + + // BlockConnected is a notification for a newly-attached block to the + // best chain. + BlockConnected wtxmgr.BlockMeta + + // FilteredBlockConnected is an alternate notification that contains + // both block and relevant transaction information in one struct, which + // allows atomic updates. + FilteredBlockConnected struct { + Block *wtxmgr.BlockMeta + RelevantTxs []*wtxmgr.TxRecord + } + + // FilterBlocksRequest specifies a range of blocks and the set of + // internal and external addresses of interest, indexed by corresponding + // scoped-index of the child address. A global set of watched outpoints + // is also included to monitor for spends. + FilterBlocksRequest struct { + Blocks []wtxmgr.BlockMeta + ExternalAddrs map[waddrmgr.ScopedIndex]btcutil.Address + InternalAddrs map[waddrmgr.ScopedIndex]btcutil.Address + WatchedOutPoints map[wire.OutPoint]btcutil.Address + } + + // FilterBlocksResponse reports the set of all internal and external + // addresses found in response to a FilterBlockRequest, any outpoints + // found that correspond to those addresses, as well as the relevant + // transactions that can modify the wallet's balance. The index of the + // block within the FilterBlocksRequest is returned, such that the + // caller can reinitiate a request for the subsequent block after + // updating the addresses of interest. + FilterBlocksResponse struct { + BatchIndex uint32 + BlockMeta wtxmgr.BlockMeta + FoundExternalAddrs map[waddrmgr.KeyScope]map[uint32]struct{} + FoundInternalAddrs map[waddrmgr.KeyScope]map[uint32]struct{} + FoundOutPoints map[wire.OutPoint]btcutil.Address + RelevantTxns []*wire.MsgTx + } + + // BlockDisconnected is a notifcation that the block described by the + // BlockStamp was reorganized out of the best chain. + BlockDisconnected wtxmgr.BlockMeta + + // RelevantTx is a notification for a transaction which spends wallet + // inputs or pays to a watched address. + RelevantTx struct { + TxRecord *wtxmgr.TxRecord + Block *wtxmgr.BlockMeta // nil if unmined + } + + // RescanProgress is a notification describing the current status + // of an in-progress rescan. + RescanProgress struct { + Hash *chainhash.Hash + Height int32 + Time time.Time + } + + // RescanFinished is a notification that a previous rescan request + // has finished. + RescanFinished struct { + Hash *chainhash.Hash + Height int32 + Time time.Time + } +) diff --git a/btcclient/log.go b/btcclient/log.go new file mode 100644 index 00000000..6f9101ae --- /dev/null +++ b/btcclient/log.go @@ -0,0 +1,34 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcclient + +import ( + "github.com/btcsuite/btclog" + "github.com/lightninglabs/neutrino/query" +) + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + DisableLog() +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until either UseLogger or SetLogWriter are called. +func DisableLog() { + log = btclog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger + query.UseLogger(logger) +} diff --git a/btcclient/pruned_block_dispatcher.go b/btcclient/pruned_block_dispatcher.go new file mode 100644 index 00000000..8a1e7114 --- /dev/null +++ b/btcclient/pruned_block_dispatcher.go @@ -0,0 +1,666 @@ +package btcclient + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "math/rand" + "net" + "sync" + "time" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/peer" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/neutrino/query" + "github.com/lightningnetwork/lnd/ticker" +) + +const ( + // defaultRefreshPeersInterval represents the default polling interval + // at which we attempt to refresh the set of known peers. + defaultRefreshPeersInterval = 30 * time.Second + + // defaultPeerReadyTimeout is the default amount of time we'll wait for + // a query peer to be ready to receive incoming block requests. Peers + // cannot respond to requests until the version exchange is completed + // upon connection establishment. + defaultPeerReadyTimeout = 15 * time.Second + + // requiredServices are the requires services we require any candidate + // peers to signal such that we can retrieve pruned blocks from them. + requiredServices = wire.SFNodeNetwork | wire.SFNodeWitness + + // prunedNodeService is the service bit signaled by pruned nodes on the + // network. Note that this service bit can also be signaled by full + // nodes, except that they also signal wire.SFNodeNetwork, where as + // pruned nodes don't. + prunedNodeService wire.ServiceFlag = 1 << 10 +) + +// queryPeer represents a Bitcoin network peer that we'll query for blocks. +// The ready channel serves as a signal for us to know when we can be sending +// queries to the peer. Any messages received from the peer are sent through the +// msgsRecvd channel. +type queryPeer struct { + *peer.Peer + ready chan struct{} + msgsRecvd chan wire.Message + quit chan struct{} +} + +// signalUponDisconnect closes the peer's quit chan to signal it has +// disconnected. +func (p *queryPeer) signalUponDisconnect(f func()) { + go func() { + p.WaitForDisconnect() + close(p.quit) + f() + }() +} + +// SubscribeRecvMsg adds a OnRead subscription to the peer. All bitcoin messages +// received from this peer will be sent on the returned channel. A closure is +// also returned, that should be called to cancel the subscription. +// +// NOTE: This method exists to satisfy the query.Peer interface. +func (p *queryPeer) SubscribeRecvMsg() (<-chan wire.Message, func()) { + return p.msgsRecvd, func() {} +} + +// OnDisconnect returns a channel that will be closed once the peer disconnects. +// +// NOTE: This method exists to satisfy the query.Peer interface. +func (p *queryPeer) OnDisconnect() <-chan struct{} { + return p.quit +} + +// PrunedBlockDispatcherConfig encompasses all of the dependencies required by +// the PrunedBlockDispatcher to carry out its duties. +type PrunedBlockDispatcherConfig struct { + // ChainParams represents the parameters of the current active chain. + ChainParams *chaincfg.Params + + // NumTargetPeer represents the target number of peers we should + // maintain connections with. This exists to prevent establishing + // connections to all of the bitcoind's peers, which would be + // unnecessary and ineffecient. + NumTargetPeers int + + // Dial establishes connections to Bitcoin peers. This must support + // dialing peers running over Tor if the backend also supports it. + Dial func(string) (net.Conn, error) + + // GetPeers retrieves the active set of peers known to the backend node. + GetPeers func() ([]btcjson.GetPeerInfoResult, error) + + // GetNodeAddresses returns random reachable addresses known to the + // backend node. An optional number of addresses to return can be + // provided, otherwise 8 are returned by default. + GetNodeAddresses func(*int32) ([]btcjson.GetNodeAddressesResult, error) + + // PeerReadyTimeout is the amount of time we'll wait for a query peer to + // be ready to receive incoming block requests. Peers cannot respond to + // requests until the version exchange is completed upon connection + // establishment. + PeerReadyTimeout time.Duration + + // RefreshPeersTicker is the polling ticker that signals us when we + // should attempt to refresh the set of known peers. + RefreshPeersTicker ticker.Ticker + + // AllowSelfPeerConns is only used to allow the tests to bypass the peer + // self connection detecting and disconnect logic since they + // intentionally do so for testing purposes. + AllowSelfPeerConns bool + + // MaxRequestInvs dictates how many invs we should fit in a single + // getdata request to a peer. This only exists to facilitate the testing + // of a request spanning multiple getdata messages. + MaxRequestInvs int +} + +// PrunedBlockDispatcher enables a chain client to request blocks that the +// server has already pruned. This is done by connecting to the server's full +// node peers and querying them directly. Ideally, this is a capability +// supported by the server, though this is not yet possible with bitcoind. +type PrunedBlockDispatcher struct { + cfg PrunedBlockDispatcherConfig + + // workManager handles satisfying all of our incoming pruned block + // requests. + workManager *query.WorkManager + + // blocksQueried represents the set of pruned blocks we've been + // requested to query. Each block maps to a list of clients waiting to + // be notified once the block is received. + // + // NOTE: The blockMtx lock must always be held when accessing this + // field. + blocksQueried map[chainhash.Hash][]chan *wire.MsgBlock + blockMtx sync.Mutex + + // currentPeers represents the set of peers we're currently connected + // to. Each peer found here will have a worker spawned within the + // workManager to handle our queries. + // + // NOTE: The peerMtx lock must always be held when accessing this + // field. + currentPeers map[string]*peer.Peer + + // bannedPeers represents the set of peers who have sent us an invalid + // reply corresponding to a query. Peers within this set should not be + // dialed. + // + // NOTE: The peerMtx lock must always be held when accessing this + // field. + bannedPeers map[string]struct{} + peerMtx sync.Mutex + + // peersConnected is the channel through which we'll send new peers + // we've established connections to. + peersConnected chan query.Peer + + // timeSource provides a mechanism to add several time samples which are + // used to determine a median time which is then used as an offset to + // the local clock when validating blocks received from peers. + timeSource blockchain.MedianTimeSource + + quit chan struct{} + wg sync.WaitGroup +} + +// NewPrunedBlockDispatcher initializes a new PrunedBlockDispatcher instance +// backed by the given config. +func NewPrunedBlockDispatcher(cfg *PrunedBlockDispatcherConfig) ( + *PrunedBlockDispatcher, error) { + + if cfg.NumTargetPeers < 1 { + return nil, errors.New("config option NumTargetPeer must be >= 1") + } + if cfg.MaxRequestInvs > wire.MaxInvPerMsg { + return nil, fmt.Errorf("config option MaxRequestInvs must be "+ + "<= %v", wire.MaxInvPerMsg) + } + + peersConnected := make(chan query.Peer) + return &PrunedBlockDispatcher{ + cfg: *cfg, + workManager: query.New(&query.Config{ + ConnectedPeers: func() (<-chan query.Peer, func(), error) { + return peersConnected, func() {}, nil + }, + NewWorker: query.NewWorker, + Ranking: query.NewPeerRanking(), + }), + blocksQueried: make(map[chainhash.Hash][]chan *wire.MsgBlock), + currentPeers: make(map[string]*peer.Peer), + bannedPeers: make(map[string]struct{}), + peersConnected: peersConnected, + timeSource: blockchain.NewMedianTime(), + quit: make(chan struct{}), + }, nil +} + +// Start allows the PrunedBlockDispatcher to begin handling incoming block +// requests. +func (d *PrunedBlockDispatcher) Start() error { + log.Tracef("Starting pruned block dispatcher") + + if err := d.workManager.Start(); err != nil { + return err + } + + d.wg.Add(1) + go d.pollPeers() + + return nil +} + +// Stop stops the PrunedBlockDispatcher from accepting any more incoming block +// requests. +func (d *PrunedBlockDispatcher) Stop() { + log.Tracef("Stopping pruned block dispatcher") + + close(d.quit) + d.wg.Wait() + + _ = d.workManager.Stop() +} + +// pollPeers continuously polls the backend node for new peers to establish +// connections to. +func (d *PrunedBlockDispatcher) pollPeers() { + defer d.wg.Done() + + if err := d.connectToPeers(); err != nil { + log.Warnf("Unable to establish peer connections: %v", err) + } + + d.cfg.RefreshPeersTicker.Resume() + defer d.cfg.RefreshPeersTicker.Stop() + + for { + select { + case <-d.cfg.RefreshPeersTicker.Ticks(): + // Quickly determine if we need any more peer + // connections. If we don't, we'll wait for our next + // tick. + d.peerMtx.Lock() + peersNeeded := d.cfg.NumTargetPeers - len(d.currentPeers) + d.peerMtx.Unlock() + if peersNeeded <= 0 { + continue + } + + // If we do, attempt to establish connections until + // we've reached our target number. + if err := d.connectToPeers(); err != nil { + log.Warnf("Failed to establish peer "+ + "connections: %v", err) + continue + } + + case <-d.quit: + return + } + } +} + +// connectToPeers attempts to establish new peer connections until the target +// number is reached. Once a connection is successfully established, the peer is +// sent through the peersConnected channel to notify the internal workManager. +func (d *PrunedBlockDispatcher) connectToPeers() error { + // Refresh the list of peers our backend is currently connected to, and + // filter out any that do not meet our requirements. + peers, err := d.cfg.GetPeers() + if err != nil { + return err + } + addrs, err := filterPeers(peers) + if err != nil { + return err + } + rand.Shuffle(len(addrs), func(i, j int) { + addrs[i], addrs[j] = addrs[j], addrs[i] + }) + + for _, addr := range addrs { + needMore, err := d.connectToPeer(addr) + if err != nil { + log.Debugf("Failed connecting to peer %v: %v", addr, err) + continue + } + if !needMore { + return nil + } + } + + // We still need more addresses so we'll also invoke the + // `getnodeaddresses` RPC to receive random reachable addresses. We'll + // also filter out any that do not meet our requirements. The nil + // argument will return a default number of addresses, which is + // currently 8. We don't care how many addresses are returned as long as + // 1 is returned, since this will be polled regularly if needed. + nodeAddrs, err := d.cfg.GetNodeAddresses(nil) + if err != nil { + return err + } + addrs = filterNodeAddrs(nodeAddrs) + for _, addr := range addrs { + if _, err := d.connectToPeer(addr); err != nil { + log.Debugf("Failed connecting to peer %v: %v", addr, err) + } + } + + return nil +} + +// connectToPeer attempts to establish a connection to the given peer and waits +// up to PeerReadyTimeout for the version exchange to complete so that we can +// begin sending it our queries. +func (d *PrunedBlockDispatcher) connectToPeer(addr string) (bool, error) { + // Prevent connections to peers we've already connected to or we've + // banned. + d.peerMtx.Lock() + _, isBanned := d.bannedPeers[addr] + _, isConnected := d.currentPeers[addr] + d.peerMtx.Unlock() + if isBanned || isConnected { + return true, nil + } + + peer, err := d.newQueryPeer(addr) + if err != nil { + return true, fmt.Errorf("unable to configure query peer %v: "+ + "%v", addr, err) + } + + // Establish the connection and wait for the protocol negotiation to + // complete. + conn, err := d.cfg.Dial(addr) + if err != nil { + return true, err + } + peer.AssociateConnection(conn) + + select { + case <-peer.ready: + case <-time.After(d.cfg.PeerReadyTimeout): + peer.Disconnect() + return true, errors.New("timed out waiting for protocol negotiation") + case <-d.quit: + return false, errors.New("shutting down") + } + + // Remove the peer once it has disconnected. + peer.signalUponDisconnect(func() { + d.peerMtx.Lock() + delete(d.currentPeers, peer.Addr()) + d.peerMtx.Unlock() + }) + + d.peerMtx.Lock() + d.currentPeers[addr] = peer.Peer + numPeers := len(d.currentPeers) + d.peerMtx.Unlock() + + // Notify the new peer connection to our workManager. + select { + case d.peersConnected <- peer: + case <-d.quit: + return false, errors.New("shutting down") + } + + // Request more peer connections if we haven't reached our target number + // with the new peer. + return numPeers < d.cfg.NumTargetPeers, nil +} + +// filterPeers filters out any peers which cannot handle arbitrary witness block +// requests, i.e., any peer which is not considered a segwit-enabled +// "full-node". +func filterPeers(peers []btcjson.GetPeerInfoResult) ([]string, error) { + var eligible []string // nolint:prealloc + for _, peer := range peers { + rawServices, err := hex.DecodeString(peer.Services) + if err != nil { + return nil, err + } + services := wire.ServiceFlag(binary.BigEndian.Uint64(rawServices)) + if !satisfiesRequiredServices(services) { + continue + } + eligible = append(eligible, peer.Addr) + } + return eligible, nil +} + +// filterNodeAddrs filters out any peers which cannot handle arbitrary witness +// block requests, i.e., any peer which is not considered a segwit-enabled +// "full-node". +func filterNodeAddrs(nodeAddrs []btcjson.GetNodeAddressesResult) []string { + var eligible []string // nolint:prealloc + for _, nodeAddr := range nodeAddrs { + services := wire.ServiceFlag(nodeAddr.Services) + if !satisfiesRequiredServices(services) { + continue + } + eligible = append(eligible, nodeAddr.Address) + } + return eligible +} + +// satisfiesRequiredServices determines whether the services signaled by a peer +// satisfy our requirements for retrieving pruned blocks from them. We need the +// full chain, and witness data as well. Note that we ignore the limited +// (pruned bit) as nodes can have the full data and set that as well. Pure +// pruned nodes won't set the network bit. +func satisfiesRequiredServices(services wire.ServiceFlag) bool { + return services&requiredServices == requiredServices +} + +// newQueryPeer creates a new peer instance configured to relay any received +// messages to the internal workManager. +func (d *PrunedBlockDispatcher) newQueryPeer(addr string) (*queryPeer, error) { + ready := make(chan struct{}) + msgsRecvd := make(chan wire.Message) + + cfg := &peer.Config{ + ChainParams: d.cfg.ChainParams, + // We're not interested in transactions, so disable their relay. + DisableRelayTx: true, + Listeners: peer.MessageListeners{ + // Add the remote peer time as a sample for creating an + // offset against the local clock to keep the network + // time in sync. + OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) *wire.MsgReject { + d.timeSource.AddTimeSample(p.Addr(), msg.Timestamp) + return nil + }, + // Register a callback to signal us when we can start + // querying the peer for blocks. + OnVerAck: func(*peer.Peer, *wire.MsgVerAck) { + close(ready) + }, + // Register a callback to signal us whenever the peer + // has sent us a block message. + OnRead: func(p *peer.Peer, _ int, msg wire.Message, err error) { + if err != nil { + return + } + + var block *wire.MsgBlock + switch msg := msg.(type) { + case *wire.MsgBlock: + block = msg + case *wire.MsgVersion, *wire.MsgVerAck, + *wire.MsgPing, *wire.MsgPong: + return + default: + log.Debugf("Received unexpected message "+ + "%T from peer %v", msg, p.Addr()) + return + } + + select { + case msgsRecvd <- block: + case <-d.quit: + } + }, + }, + AllowSelfConns: true, + } + p, err := peer.NewOutboundPeer(cfg, addr) + if err != nil { + return nil, err + } + + return &queryPeer{ + Peer: p, + ready: ready, + msgsRecvd: msgsRecvd, + quit: make(chan struct{}), + }, nil +} + +// banPeer bans a peer by disconnecting them and ensuring we don't reconnect. +func (d *PrunedBlockDispatcher) banPeer(peer string) { + d.peerMtx.Lock() + defer d.peerMtx.Unlock() + + d.bannedPeers[peer] = struct{}{} + if p, ok := d.currentPeers[peer]; ok { + p.Disconnect() + } +} + +// Query submits a request to query the information of the given blocks. +func (d *PrunedBlockDispatcher) Query(blocks []*chainhash.Hash, + opts ...query.QueryOption) (<-chan *wire.MsgBlock, <-chan error) { + + reqs, blockChan, err := d.newRequest(blocks) + if err != nil { + errChan := make(chan error, 1) + errChan <- err + return nil, errChan + } + + var errChan chan error + if len(reqs) > 0 { + errChan = d.workManager.Query(reqs, opts...) + } + return blockChan, errChan +} + +// newRequest construct a new query request for the given blocks to submit to +// the internal workManager. A channel is also returned through which the +// requested blocks are sent through. +func (d *PrunedBlockDispatcher) newRequest(blocks []*chainhash.Hash) ( + []*query.Request, <-chan *wire.MsgBlock, error) { + + // Make sure the channel is buffered enough to handle all blocks. + blockChan := make(chan *wire.MsgBlock, len(blocks)) + + d.blockMtx.Lock() + defer d.blockMtx.Unlock() + + // Each GetData message can only include up to MaxRequestInvs invs, + // and each block consumes a single inv. + var ( + reqs []*query.Request + getData *wire.MsgGetData + ) + for i, block := range blocks { + if getData == nil { + getData = wire.NewMsgGetData() + } + + if _, ok := d.blocksQueried[*block]; !ok { + log.Debugf("Queuing new block %v for request", *block) + inv := wire.NewInvVect(wire.InvTypeBlock, block) + if err := getData.AddInvVect(inv); err != nil { + return nil, nil, err + } + } else { + log.Debugf("Received new request for pending query of "+ + "block %v", *block) + } + + d.blocksQueried[*block] = append( + d.blocksQueried[*block], blockChan, + ) + + // If we have any invs to request, or we've reached the maximum + // allowed, queue the getdata message as is, and proceed to the + // next if any. + if (len(getData.InvList) > 0 && i == len(blocks)-1) || + len(getData.InvList) == d.cfg.MaxRequestInvs { + + reqs = append(reqs, &query.Request{ + Req: getData, + HandleResp: d.handleResp, + }) + getData = nil + } + } + + return reqs, blockChan, nil +} + +// handleResp is a response handler that will be called for every message +// received from the peer that the request was made to. It should validate the +// response against the request made, and return a Progress indicating whether +// the request was answered by this particular response. +// +// NOTE: Since the worker's job queue will be stalled while this method is +// running, it should not be doing any expensive operations. It should validate +// the response and immediately return the progress. The response should be +// handed off to another goroutine for processing. +func (d *PrunedBlockDispatcher) handleResp(req, resp wire.Message, + peer string) query.Progress { + + // We only expect MsgBlock as replies. + block, ok := resp.(*wire.MsgBlock) + if !ok { + return query.Progress{ + Progressed: false, + Finished: false, + } + } + + // We only serve MsgGetData requests. + getData, ok := req.(*wire.MsgGetData) + if !ok { + return query.Progress{ + Progressed: false, + Finished: false, + } + } + + // Check that we've actually queried for this block and validate it. + blockHash := block.BlockHash() + d.blockMtx.Lock() + blockChans, ok := d.blocksQueried[blockHash] + if !ok { + d.blockMtx.Unlock() + return query.Progress{ + Progressed: false, + Finished: false, + } + } + + err := blockchain.CheckBlockSanity( + btcutil.NewBlock(block), d.cfg.ChainParams.PowLimit, + d.timeSource, + ) + if err != nil { + d.blockMtx.Unlock() + + log.Warnf("Received invalid block %v from peer %v: %v", + blockHash, peer, err) + d.banPeer(peer) + + return query.Progress{ + Progressed: false, + Finished: false, + } + } + + // Once validated, we can safely remove it. + delete(d.blocksQueried, blockHash) + + // Check whether we have any other pending blocks we've yet to receive. + // If we do, we'll mark the response as progressing our query, but not + // completing it yet. + progress := query.Progress{Progressed: true, Finished: true} + for _, inv := range getData.InvList { + if _, ok := d.blocksQueried[inv.Hash]; ok { + progress.Finished = false + break + } + } + d.blockMtx.Unlock() + + // Launch a goroutine to notify all clients of the block as we don't + // want to potentially block our workManager. + d.wg.Add(1) + go func() { + defer d.wg.Done() + + for _, blockChan := range blockChans { + select { + case blockChan <- block: + case <-d.quit: + return + } + } + }() + + return progress +} diff --git a/btcclient/pruned_block_dispatcher_test.go b/btcclient/pruned_block_dispatcher_test.go new file mode 100644 index 00000000..0b5104af --- /dev/null +++ b/btcclient/pruned_block_dispatcher_test.go @@ -0,0 +1,659 @@ +package btcclient + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/peer" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/ticker" + "github.com/stretchr/testify/require" +) + +var ( + addrCounter int32 // Increased atomically. + + chainParams = chaincfg.RegressionNetParams +) + +func nextAddr() string { + port := atomic.AddInt32(&addrCounter, 1) + return fmt.Sprintf("10.0.0.1:%d", port) +} + +// prunedBlockDispatcherHarness is a harness used to facilitate the testing of the +// PrunedBlockDispatcher. +type prunedBlockDispatcherHarness struct { + t *testing.T + + dispatcher *PrunedBlockDispatcher + + hashes []*chainhash.Hash + blocks map[chainhash.Hash]*wire.MsgBlock + + peerMtx sync.Mutex + peers map[string]*peer.Peer + fallbackAddrs map[string]*peer.Peer + localConns map[string]net.Conn // Connections to peers. + remoteConns map[string]net.Conn // Connections from peers. + + dialedPeer chan string + queriedPeer chan struct{} + blocksQueried map[chainhash.Hash]int + + shouldReply uint32 // 0 == true, 1 == false, 2 == invalid reply +} + +// newNetworkBlockTestHarness initializes a new PrunedBlockDispatcher test harness +// backed by a custom chain and peers. +func newNetworkBlockTestHarness(t *testing.T, numBlocks, + numPeers, numWorkers uint32) *prunedBlockDispatcherHarness { + + h := &prunedBlockDispatcherHarness{ + t: t, + dispatcher: &PrunedBlockDispatcher{}, + peers: make(map[string]*peer.Peer, numPeers), + fallbackAddrs: make(map[string]*peer.Peer, numPeers), + localConns: make(map[string]net.Conn, numPeers), + remoteConns: make(map[string]net.Conn, numPeers), + dialedPeer: make(chan string), + queriedPeer: make(chan struct{}), + blocksQueried: make(map[chainhash.Hash]int), + shouldReply: 0, + } + + h.hashes, h.blocks = genBlockChain(numBlocks) + for i := uint32(0); i < numPeers; i++ { + h.addPeer(false) + } + + dial := func(addr string) (net.Conn, error) { + go func() { + h.dialedPeer <- addr + }() + + h.peerMtx.Lock() + defer h.peerMtx.Unlock() + + localConn, ok := h.localConns[addr] + if !ok { + return nil, fmt.Errorf("local conn %v not found", addr) + } + remoteConn, ok := h.remoteConns[addr] + if !ok { + return nil, fmt.Errorf("remote conn %v not found", addr) + } + + if p, ok := h.peers[addr]; ok { + p.AssociateConnection(remoteConn) + } + if p, ok := h.fallbackAddrs[addr]; ok { + p.AssociateConnection(remoteConn) + } + return localConn, nil + } + + var err error + h.dispatcher, err = NewPrunedBlockDispatcher(&PrunedBlockDispatcherConfig{ + ChainParams: &chainParams, + NumTargetPeers: int(numWorkers), + Dial: dial, + GetPeers: func() ([]btcjson.GetPeerInfoResult, error) { + h.peerMtx.Lock() + defer h.peerMtx.Unlock() + + res := make([]btcjson.GetPeerInfoResult, 0, len(h.peers)) + for addr, peer := range h.peers { + var rawServices [8]byte + binary.BigEndian.PutUint64( + rawServices[:], uint64(peer.Services()), + ) + + res = append(res, btcjson.GetPeerInfoResult{ + Addr: addr, + Services: hex.EncodeToString(rawServices[:]), + }) + } + + return res, nil + }, + GetNodeAddresses: func(*int32) ([]btcjson.GetNodeAddressesResult, error) { + h.peerMtx.Lock() + defer h.peerMtx.Unlock() + + res := make( + []btcjson.GetNodeAddressesResult, 0, + len(h.fallbackAddrs), + ) + for addr, peer := range h.fallbackAddrs { + res = append(res, btcjson.GetNodeAddressesResult{ + Services: uint64(peer.Services()), + Address: addr, + }) + } + return res, nil + }, + PeerReadyTimeout: time.Hour, + RefreshPeersTicker: ticker.NewForce(time.Hour), + AllowSelfPeerConns: true, + MaxRequestInvs: wire.MaxInvPerMsg, + }) + require.NoError(t, err) + + return h +} + +// start starts the PrunedBlockDispatcher and asserts that connections are made +// to all available peers. +func (h *prunedBlockDispatcherHarness) start() { + h.t.Helper() + + err := h.dispatcher.Start() + require.NoError(h.t, err) + + h.peerMtx.Lock() + numPeers := len(h.peers) + h.peerMtx.Unlock() + + for i := 0; i < numPeers; i++ { + h.assertPeerDialed() + } +} + +// stop stops the PrunedBlockDispatcher and asserts that all internal fields of +// the harness have been properly consumed. +func (h *prunedBlockDispatcherHarness) stop() { + h.dispatcher.Stop() + + select { + case <-h.dialedPeer: + h.t.Fatal("did not consume all dialedPeer signals") + default: + } + + select { + case <-h.queriedPeer: + h.t.Fatal("did not consume all queriedPeer signals") + default: + } + + require.Empty(h.t, h.blocksQueried) +} + +// addPeer adds a new random peer available for use by the +// PrunedBlockDispatcher. +func (h *prunedBlockDispatcherHarness) addPeer(fallback bool) string { + addr := nextAddr() + + h.peerMtx.Lock() + defer h.peerMtx.Unlock() + + h.resetPeer(addr, fallback) + return addr +} + +// resetPeer resets the internal peer connection state allowing the +// PrunedBlockDispatcher to establish a mock connection to it. +func (h *prunedBlockDispatcherHarness) resetPeer(addr string, fallback bool) { + if fallback { + h.fallbackAddrs[addr] = h.newPeer() + } else { + h.peers[addr] = h.newPeer() + } + + inConn, outConn, err := setupConnPair() + if err != nil { + h.t.Fatalf("failed to setup conn pair: %v", err) + } + + h.localConns[addr] = outConn + h.remoteConns[addr] = inConn +} + +// newPeer returns a new properly configured peer.Peer instance that will be +// used by the PrunedBlockDispatcher. +func (h *prunedBlockDispatcherHarness) newPeer() *peer.Peer { + return peer.NewInboundPeer(&peer.Config{ + ChainParams: &chainParams, + DisableRelayTx: true, + Listeners: peer.MessageListeners{ + OnGetData: func(p *peer.Peer, msg *wire.MsgGetData) { + go func() { + h.queriedPeer <- struct{}{} + }() + + for _, inv := range msg.InvList { + // Invs should always be for blocks. + require.Equal(h.t, wire.InvTypeBlock, inv.Type) + + // Invs should always be for known blocks. + block, ok := h.blocks[inv.Hash] + require.True(h.t, ok) + + switch atomic.LoadUint32(&h.shouldReply) { + // Don't reply if requested. + case 1: + continue + // Make the block invalid and send it. + case 2: + block = produceInvalidBlock(block) + } + + go p.QueueMessage(block, nil) + } + }, + }, + Services: wire.SFNodeNetwork | wire.SFNodeWitness, + AllowSelfConns: true, + }) +} + +// query requests the given blocks from the PrunedBlockDispatcher. +func (h *prunedBlockDispatcherHarness) query(blocks []*chainhash.Hash) ( + <-chan *wire.MsgBlock, <-chan error) { + + h.t.Helper() + + blockChan, errChan := h.dispatcher.Query(blocks) + select { + case err := <-errChan: + require.NoError(h.t, err) + default: + } + + for _, block := range blocks { + h.blocksQueried[*block]++ + } + + return blockChan, errChan +} + +// disablePeerReplies prevents the query peer from replying. +func (h *prunedBlockDispatcherHarness) disablePeerReplies() { + atomic.StoreUint32(&h.shouldReply, 1) +} + +// enablePeerReplies allows the query peer to reply. +func (h *prunedBlockDispatcherHarness) enablePeerReplies() { + atomic.StoreUint32(&h.shouldReply, 0) +} + +// enableInvalidPeerReplies +func (h *prunedBlockDispatcherHarness) enableInvalidPeerReplies() { + atomic.StoreUint32(&h.shouldReply, 2) +} + +// refreshPeers forces the RefreshPeersTicker to fire. +func (h *prunedBlockDispatcherHarness) refreshPeers() { + h.t.Helper() + + h.dispatcher.cfg.RefreshPeersTicker.(*ticker.Force).Force <- time.Now() +} + +// disconnectPeer simulates a peer disconnecting from the PrunedBlockDispatcher. +func (h *prunedBlockDispatcherHarness) disconnectPeer(addr string, fallback bool) { + h.t.Helper() + + h.peerMtx.Lock() + defer h.peerMtx.Unlock() + + require.Contains(h.t, h.peers, addr) + + // Obtain the current number of peers before disconnecting such that we + // can block until the peer has been fully disconnected. + h.dispatcher.peerMtx.Lock() + numPeers := len(h.dispatcher.currentPeers) + h.dispatcher.peerMtx.Unlock() + + h.peers[addr].Disconnect() + + require.Eventually(h.t, func() bool { + h.dispatcher.peerMtx.Lock() + defer h.dispatcher.peerMtx.Unlock() + return len(h.dispatcher.currentPeers) == numPeers-1 + }, time.Second, 200*time.Millisecond) + + // Reset the peer connection state to allow connections to them again. + h.resetPeer(addr, fallback) +} + +// assertPeerDialed asserts that a connection was made to the given peer. +func (h *prunedBlockDispatcherHarness) assertPeerDialed() { + h.t.Helper() + + select { + case <-h.dialedPeer: + case <-time.After(5 * time.Second): + h.t.Fatalf("expected peer to be dialed") + } +} + +// assertPeerDialedWithAddr asserts that a connection was made to the given peer. +func (h *prunedBlockDispatcherHarness) assertPeerDialedWithAddr(addr string) { + h.t.Helper() + + select { + case dialedAddr := <-h.dialedPeer: + require.Equal(h.t, addr, dialedAddr) + case <-time.After(5 * time.Second): + h.t.Fatalf("expected peer to be dialed") + } +} + +// assertPeerQueried asserts that query was sent to the given peer. +func (h *prunedBlockDispatcherHarness) assertPeerQueried() { + h.t.Helper() + + select { + case <-h.queriedPeer: + case <-time.After(5 * time.Second): + h.t.Fatalf("expected a peer to be queried") + } +} + +// assertPeerReplied asserts that the query peer replies with a block the +// PrunedBlockDispatcher queried for. +func (h *prunedBlockDispatcherHarness) assertPeerReplied( + blockChan <-chan *wire.MsgBlock, errChan <-chan error, + expectCompletionSignal bool) { + + h.t.Helper() + + select { + case block := <-blockChan: + blockHash := block.BlockHash() + _, ok := h.blocksQueried[blockHash] + require.True(h.t, ok) + + expBlock, ok := h.blocks[blockHash] + require.True(h.t, ok) + require.Equal(h.t, expBlock, block) + + // Decrement how many clients queried the same block. Once we + // have none left, remove it from the map. + h.blocksQueried[blockHash]-- + if h.blocksQueried[blockHash] == 0 { + delete(h.blocksQueried, blockHash) + } + + case <-time.After(5 * time.Second): + select { + case err := <-errChan: + h.t.Fatalf("received unexpected error send: %v", err) + default: + } + h.t.Fatal("expected reply from peer") + } + + // If we should expect a nil error to be sent by the internal + // workManager to signal completion of the request, wait for it now. + if expectCompletionSignal { + select { + case err := <-errChan: + require.NoError(h.t, err) + case <-time.After(5 * time.Second): + h.t.Fatal("expected nil err to signal completion") + } + } +} + +// assertNoPeerDialed asserts that the PrunedBlockDispatcher hasn't established +// a new peer connection. +func (h *prunedBlockDispatcherHarness) assertNoPeerDialed() { + h.t.Helper() + + select { + case peer := <-h.dialedPeer: + h.t.Fatalf("unexpected connection established with peer %v", peer) + case <-time.After(2 * time.Second): + } +} + +// assertNoReply asserts that the peer hasn't replied to a query. +func (h *prunedBlockDispatcherHarness) assertNoReply( + blockChan <-chan *wire.MsgBlock, errChan <-chan error) { + + h.t.Helper() + + select { + case block := <-blockChan: + h.t.Fatalf("received unexpected block %v", block.BlockHash()) + case err := <-errChan: + h.t.Fatalf("received unexpected error send: %v", err) + case <-time.After(2 * time.Second): + } +} + +// TestPrunedBlockDispatcherQuerySameBlock tests that client requests for the +// same block result in only fetching the block once while pending. +func TestPrunedBlockDispatcherQuerySameBlock(t *testing.T) { + t.Parallel() + + const numBlocks = 1 + const numPeers = 5 + const numRequests = numBlocks * numPeers + + h := newNetworkBlockTestHarness(t, numBlocks, numPeers, numPeers) + h.start() + defer h.stop() + + // Queue all the block requests one by one. + blockChans := make([]<-chan *wire.MsgBlock, 0, numRequests) + errChans := make([]<-chan error, 0, numRequests) + for i := 0; i < numRequests; i++ { + blockChan, errChan := h.query(h.hashes) + blockChans = append(blockChans, blockChan) + errChans = append(errChans, errChan) + } + + // We should only see one query. + h.assertPeerQueried() + for i := 0; i < numRequests; i++ { + h.assertPeerReplied(blockChans[i], errChans[i], i == 0) + } +} + +// TestPrunedBlockDispatcherMultipleGetData tests that a client requesting blocks +// that span across multiple queries works as intended. +func TestPrunedBlockDispatcherMultipleGetData(t *testing.T) { + t.Parallel() + + const maxRequestInvs = 5 + const numBlocks = (maxRequestInvs * 5) + 1 + + h := newNetworkBlockTestHarness(t, numBlocks, 1, 1) + h.dispatcher.cfg.MaxRequestInvs = maxRequestInvs + h.start() + defer h.stop() + + // Request all blocks. + blockChan, errChan := h.query(h.hashes) + + // Since we have more blocks than can fit in a single GetData message, + // we should expect multiple queries. For each query, we should expect + // wire.MaxInvPerMsg replies until we've received all of them. + blocksRecvd := 0 + numMsgs := (numBlocks / maxRequestInvs) + if numBlocks%wire.MaxInvPerMsg > 0 { + numMsgs++ + } + for i := 0; i < numMsgs; i++ { + h.assertPeerQueried() + for j := 0; j < maxRequestInvs; j++ { + expectCompletionSignal := blocksRecvd == numBlocks-1 + h.assertPeerReplied( + blockChan, errChan, expectCompletionSignal, + ) + + blocksRecvd++ + if blocksRecvd == numBlocks { + break + } + } + } +} + +// TestPrunedBlockDispatcherMultipleQueryPeers tests that client requests are +// distributed across multiple query peers. +func TestPrunedBlockDispatcherMultipleQueryPeers(t *testing.T) { + t.Parallel() + + const numBlocks = 10 + const numPeers = numBlocks / 2 + + h := newNetworkBlockTestHarness(t, numBlocks, numPeers, numPeers) + h.start() + defer h.stop() + + // Queue all the block requests one by one. + blockChans := make([]<-chan *wire.MsgBlock, 0, numBlocks) + errChans := make([]<-chan error, 0, numBlocks) + for i := 0; i < numBlocks; i++ { + blockChan, errChan := h.query(h.hashes[i : i+1]) + blockChans = append(blockChans, blockChan) + errChans = append(errChans, errChan) + } + + // We should see one query per block. + for i := 0; i < numBlocks; i++ { + h.assertPeerQueried() + h.assertPeerReplied(blockChans[i], errChans[i], true) + } +} + +// TestPrunedBlockDispatcherPeerPoller ensures that the peer poller can detect +// when more connections are required to satisfy a request. +func TestPrunedBlockDispatcherPeerPoller(t *testing.T) { + t.Parallel() + + // Initialize our harness as usual, but don't create any peers yet. + h := newNetworkBlockTestHarness(t, 1, 0, 2) + h.start() + defer h.stop() + + // We shouldn't see any peers dialed since we don't have any. + h.assertNoPeerDialed() + + // We'll then query for a block. + blockChan, errChan := h.query(h.hashes) + + // Refresh our peers. This would dial some peers, but we don't have any + // yet. + h.refreshPeers() + h.assertNoPeerDialed() + + // Add a new peer and force a refresh. We should see the peer be dialed. + // We'll disable replies for now, as we'll want to test the disconnect + // case. + h.disablePeerReplies() + peer := h.addPeer(false) + h.refreshPeers() + h.assertPeerDialedWithAddr(peer) + h.assertPeerQueried() + + // Disconnect our peer and re-enable replies. + h.disconnectPeer(peer, false) + h.enablePeerReplies() + h.assertNoReply(blockChan, errChan) + + // Force a refresh once again. Since the peer has disconnected, a new + // connection should be made and the peer should be queried again. + h.refreshPeers() + h.assertPeerDialed() + h.assertPeerQueried() + + // Add a fallback addresses and force refresh our peers again. We can + // afford to have one more query peer, so a connection should be made. + fallbackPeer := h.addPeer(true) + h.refreshPeers() + h.assertPeerDialedWithAddr(fallbackPeer) + + // Now that we know we've connected to the peer, we should be able to + // receive their response. + h.assertPeerReplied(blockChan, errChan, true) +} + +// TestPrunedBlockDispatcherInvalidBlock ensures that validation is performed on +// blocks received from peers, and that any peers which have sent an invalid +// block are banned and not connected to. +func TestPrunedBlockDispatcherInvalidBlock(t *testing.T) { + t.Parallel() + + h := newNetworkBlockTestHarness(t, 1, 1, 1) + h.start() + defer h.stop() + + // We'll start the test by signaling our peer to send an invalid block. + h.enableInvalidPeerReplies() + + // We'll then query for a block. We shouldn't see a response as the + // block should have failed validation. + blockChan, errChan := h.query(h.hashes) + h.assertPeerQueried() + h.assertNoReply(blockChan, errChan) + + // Since the peer sent us an invalid block, they should have been + // disconnected and banned. Refreshing our peers shouldn't result in a + // new connection attempt because we don't have any other peers + // available. + h.refreshPeers() + h.assertNoPeerDialed() + + // Signal to our peers to send valid replies and add a new peer. + h.enablePeerReplies() + _ = h.addPeer(false) + + // Force a refresh, which should cause our new peer to be dialed and + // queried. We expect them to send a valid block and fulfill our + // request. + h.refreshPeers() + h.assertPeerDialed() + h.assertPeerQueried() + h.assertPeerReplied(blockChan, errChan, true) +} + +func TestSatisfiesRequiredServices(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + services wire.ServiceFlag + ok bool + }{ + { + name: "full node, segwit", + services: wire.SFNodeWitness | wire.SFNodeNetwork, + ok: true, + }, + { + name: "full node segwit, signals limited", + services: wire.SFNodeWitness | wire.SFNodeNetwork | prunedNodeService, + ok: true, + }, + { + name: "full node, no segwit", + services: wire.SFNodeNetwork, + ok: false, + }, + { + name: "segwit, pure pruned", + services: wire.SFNodeWitness | prunedNodeService, + ok: false, + }, + } + for _, testCase := range testCases { + ok := satisfiesRequiredServices(testCase.services) + require.Equal( + t, testCase.ok, ok, fmt.Sprintf("test case: %v", testCase.name), + ) + } +} diff --git a/btcclient/queue.go b/btcclient/queue.go new file mode 100644 index 00000000..5d15c157 --- /dev/null +++ b/btcclient/queue.go @@ -0,0 +1,88 @@ +package btcclient + +import ( + "container/list" +) + +// ConcurrentQueue is a concurrent-safe FIFO queue with unbounded capacity. +// Clients interact with the queue by pushing items into the in channel and +// popping items from the out channel. There is a goroutine that manages moving +// items from the in channel to the out channel in the correct order that must +// be started by calling Start(). +type ConcurrentQueue struct { + chanIn chan interface{} + chanOut chan interface{} + quit chan struct{} + overflow *list.List +} + +// NewConcurrentQueue constructs a ConcurrentQueue. The bufferSize parameter is +// the capacity of the output channel. When the size of the queue is below this +// threshold, pushes do not incur the overhead of the less efficient overflow +// structure. +func NewConcurrentQueue(bufferSize int) *ConcurrentQueue { + return &ConcurrentQueue{ + chanIn: make(chan interface{}), + chanOut: make(chan interface{}, bufferSize), + quit: make(chan struct{}), + overflow: list.New(), + } +} + +// ChanIn returns a channel that can be used to push new items into the queue. +func (cq *ConcurrentQueue) ChanIn() chan<- interface{} { + return cq.chanIn +} + +// ChanOut returns a channel that can be used to pop items from the queue. +func (cq *ConcurrentQueue) ChanOut() <-chan interface{} { + return cq.chanOut +} + +// Start begins a goroutine that manages moving items from the in channel to +// the out channel. The queue tries to move items directly to the out channel +// minimize overhead, but if the out channel is full it pushes items to an +// overflow queue. This must be called before using the queue. +func (cq *ConcurrentQueue) Start() { + go func() { + for { + nextElement := cq.overflow.Front() + if nextElement == nil { + // The overflow queue is empty, so incoming + // items can be pushed directly to the output + // channel. However, if output channel is full, + // we'll push to the overflow list instead. + select { + case item := <-cq.chanIn: + select { + case cq.chanOut <- item: + case <-cq.quit: + return + default: + cq.overflow.PushBack(item) + } + case <-cq.quit: + return + } + } else { + // The overflow queue is not empty, so any new + // items get pushed to the back to preserve + // order. + select { + case item := <-cq.chanIn: + cq.overflow.PushBack(item) + case cq.chanOut <- nextElement.Value: + cq.overflow.Remove(nextElement) + case <-cq.quit: + return + } + } + } + }() +} + +// Stop ends the goroutine that moves items from the in channel to the out +// channel. +func (cq *ConcurrentQueue) Stop() { + close(cq.quit) +} diff --git a/btcclient/utils_test.go b/btcclient/utils_test.go new file mode 100644 index 00000000..10244aa6 --- /dev/null +++ b/btcclient/utils_test.go @@ -0,0 +1,248 @@ +package btcclient + +import ( + "errors" + "fmt" + "math" + "net" + "runtime" + "sync" + "time" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// setupConnPair initiates a tcp connection between two peers. +func setupConnPair() (net.Conn, net.Conn, error) { + // listenFunc is a function closure that listens for a tcp connection. + // The tcp connection will be the one the inbound peer uses. + listenFunc := func(l *net.TCPListener, errChan chan error, + listenChan chan struct{}, connChan chan net.Conn) { + + listenChan <- struct{}{} + + conn, err := l.Accept() + if err != nil { + errChan <- err + return + } + + connChan <- conn + } + + // dialFunc is a function closure that initiates the tcp connection. + // This tcp connection will be the one the outbound peer uses. + dialFunc := func(addr *net.TCPAddr) (net.Conn, error) { + conn, err := net.Dial("tcp", addr.String()) + if err != nil { + return nil, err + } + + return conn, nil + } + + listenAddr := "localhost:0" + + addr, err := net.ResolveTCPAddr("tcp", listenAddr) + if err != nil { + return nil, nil, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return nil, nil, err + } + + errChan := make(chan error, 1) + listenChan := make(chan struct{}, 1) + connChan := make(chan net.Conn, 1) + + go listenFunc(l, errChan, listenChan, connChan) + <-listenChan + + outConn, err := dialFunc(l.Addr().(*net.TCPAddr)) + if err != nil { + return nil, nil, err + } + + select { + case err = <-errChan: + return nil, nil, err + case inConn := <-connChan: + return inConn, outConn, nil + case <-time.After(time.Second * 5): + return nil, nil, errors.New("failed to create connection") + } +} + +// calcMerkleRoot creates a merkle tree from the slice of transactions and +// returns the root of the tree. +// +// This function was copied from: +// https://github.com/btcsuite/btcd/blob/36a96f6a0025b6aeaebe4106821c2d46ee4be8d4/blockchain/fullblocktests/generate.go#L303 +func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { + if len(txns) == 0 { + return chainhash.Hash{} + } + + utilTxns := make([]*btcutil.Tx, 0, len(txns)) + for _, tx := range txns { + utilTxns = append(utilTxns, btcutil.NewTx(tx)) + } + merkles := blockchain.BuildMerkleTreeStore(utilTxns, false) + return *merkles[len(merkles)-1] +} + +// solveBlock attempts to find a nonce which makes the passed block header hash +// to a value less than the target difficulty. When a successful solution is +// found true is returned and the nonce field of the passed header is updated +// with the solution. False is returned if no solution exists. +// +// This function was copied from: +// https://github.com/btcsuite/btcd/blob/36a96f6a0025b6aeaebe4106821c2d46ee4be8d4/blockchain/fullblocktests/generate.go#L324 +func solveBlock(header *wire.BlockHeader) bool { + // sbResult is used by the solver goroutines to send results. + type sbResult struct { + found bool + nonce uint32 + } + + // Make sure all spawned goroutines finish executing before returning. + var wg sync.WaitGroup + defer func() { + wg.Wait() + }() + + // solver accepts a block header and a nonce range to test. It is + // intended to be run as a goroutine. + targetDifficulty := blockchain.CompactToBig(header.Bits) + quit := make(chan bool) + results := make(chan sbResult) + solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) { + defer wg.Done() + + // We need to modify the nonce field of the header, so make sure + // we work with a copy of the original header. + for i := startNonce; i >= startNonce && i <= stopNonce; i++ { + select { + case <-quit: + return + default: + hdr.Nonce = i + hash := hdr.BlockHash() + if blockchain.HashToBig(&hash).Cmp( + targetDifficulty) <= 0 { + + select { + case results <- sbResult{true, i}: + case <-quit: + } + + return + } + } + } + + select { + case results <- sbResult{false, 0}: + case <-quit: + } + } + + startNonce := uint32(1) + stopNonce := uint32(math.MaxUint32) + numCores := uint32(runtime.NumCPU()) + noncesPerCore := (stopNonce - startNonce) / numCores + wg.Add(int(numCores)) + for i := uint32(0); i < numCores; i++ { + rangeStart := startNonce + (noncesPerCore * i) + rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1 + if i == numCores-1 { + rangeStop = stopNonce + } + go solver(*header, rangeStart, rangeStop) + } + for i := uint32(0); i < numCores; i++ { + result := <-results + if result.found { + close(quit) + header.Nonce = result.nonce + return true + } + } + + return false +} + +// genBlockChain generates a test chain with the given number of blocks. +func genBlockChain(numBlocks uint32) ([]*chainhash.Hash, map[chainhash.Hash]*wire.MsgBlock) { + prevHash := chainParams.GenesisHash + prevHeader := &chainParams.GenesisBlock.Header + + hashes := make([]*chainhash.Hash, numBlocks) + blocks := make(map[chainhash.Hash]*wire.MsgBlock, numBlocks) + + // Each block contains three transactions, including the coinbase + // transaction. Each non-coinbase transaction spends outputs from + // the previous block. We also need to produce blocks that succeed + // validation through blockchain.CheckBlockSanity. + script := []byte{0x01, 0x01} + createTx := func(prevOut wire.OutPoint) *wire.MsgTx { + return &wire.MsgTx{ + TxIn: []*wire.TxIn{{ + PreviousOutPoint: prevOut, + SignatureScript: script, + }}, + TxOut: []*wire.TxOut{{PkScript: script}}, + } + } + for i := uint32(0); i < numBlocks; i++ { + txs := []*wire.MsgTx{ + createTx(wire.OutPoint{Index: wire.MaxPrevOutIndex}), + createTx(wire.OutPoint{Hash: *prevHash, Index: 0}), + createTx(wire.OutPoint{Hash: *prevHash, Index: 1}), + } + header := &wire.BlockHeader{ + Version: 1, + PrevBlock: *prevHash, + MerkleRoot: calcMerkleRoot(txs), + Timestamp: prevHeader.Timestamp.Add(10 * time.Minute), + Bits: chainParams.PowLimitBits, + Nonce: 0, + } + if !solveBlock(header) { + panic(fmt.Sprintf("could not solve block at idx %v", i)) + } + block := &wire.MsgBlock{ + Header: *header, + Transactions: txs, + } + + blockHash := block.BlockHash() + hashes[i] = &blockHash + blocks[blockHash] = block + + prevHash = &blockHash + prevHeader = header + } + + return hashes, blocks +} + +// producesInvalidBlock produces a copy of the block that duplicates the last +// transaction. When the block has an odd number of transactions, this results +// in the invalid block maintaining the same hash as the valid block. +func produceInvalidBlock(block *wire.MsgBlock) *wire.MsgBlock { + numTxs := len(block.Transactions) + lastTx := block.Transactions[numTxs-1] + blockCopy := &wire.MsgBlock{ + Header: block.Header, + Transactions: make([]*wire.MsgTx, numTxs), + } + copy(blockCopy.Transactions, block.Transactions) + blockCopy.AddTransaction(lastTx) + return blockCopy +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..095486cd --- /dev/null +++ b/go.mod @@ -0,0 +1,49 @@ +module github.com/babylonchain/vigilante + +go 1.18 + +require ( + github.com/btcsuite/btcd v0.23.1 + github.com/btcsuite/btcd/btcec/v2 v2.1.3 + github.com/btcsuite/btcd/btcutil v1.1.1 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/btcsuite/btcwallet v0.15.1 + github.com/btcsuite/btcwallet/wtxmgr v1.5.0 + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf + github.com/lightninglabs/neutrino v0.14.2 + github.com/lightningnetwork/lnd/ticker v1.1.0 + github.com/stretchr/testify v1.8.0 +) + +require ( + github.com/aead/siphash v1.0.1 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.4 // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 // indirect + github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect + github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect + github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/decred/dcrd/lru v1.0.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/jessevdk/go-flags v1.4.0 // indirect + github.com/jrick/logrotate v1.0.0 // indirect + github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect + github.com/lightningnetwork/lnd/clock v1.0.1 // indirect + github.com/lightningnetwork/lnd/queue v1.0.1 // indirect + github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect + google.golang.org/grpc v1.48.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..620fcf62 --- /dev/null +++ b/go.sum @@ -0,0 +1,272 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.1 h1:IB8cVQcC2X5mHbnfirLG5IZnkWYNTPlLZVrxUYSotbE= +github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY= +github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= +github.com/btcsuite/btcd/btcutil/psbt v1.1.4 h1:Edx4AfBn+YPam2KP5AobDitulGp4r1Oibm8oruzkMdI= +github.com/btcsuite/btcd/btcutil/psbt v1.1.4/go.mod h1:9AyU6EQVJ9Iw9zPyNT1lcdHd6cnEZdno5wLu5FY74os= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet v0.15.1 h1:SKfh/l2Bgz9sJwHZvfiVbZ8Pl3N/8fFcWWXzsAPz9GU= +github.com/btcsuite/btcwallet v0.15.1/go.mod h1:7OFsQ8ypiRwmr67hE0z98uXgJgXGAihE79jCib9x6ag= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 h1:M2yr5UlULvpqtxUqpMxTME/pA92Z9cpqeyvAFk9lAg0= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3/go.mod h1:T2xSiKGpUkSLCh68aF+FMXmKK9mFqNdHl9VaqOr+JjU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= +github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= +github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= +github.com/btcsuite/btcwallet/wtxmgr v1.5.0 h1:WO0KyN4l6H3JWnlFxfGR7r3gDnlGT7W2cL8vl6av4SU= +github.com/btcsuite/btcwallet/wtxmgr v1.5.0/go.mod h1:TQVDhFxseiGtZwEPvLgtfyxuNUDsIdaJdshvWzR0HJ4= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= +github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/cgCewJcCo= +github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc= +github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= +github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= +github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= +github.com/lightningnetwork/lnd/tlv v1.0.2 h1:LG7H3Uw/mHYGnEeHRPg+STavAH+UsFvuBflD0PzcYFQ= +github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs= +go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= +golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/types/cfgutil/amount.go b/types/cfgutil/amount.go new file mode 100644 index 00000000..daf3c94f --- /dev/null +++ b/types/cfgutil/amount.go @@ -0,0 +1,43 @@ +// Copyright (c) 2015-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package cfgutil + +import ( + "strconv" + "strings" + + "github.com/btcsuite/btcd/btcutil" +) + +// AmountFlag embeds a btcutil.Amount and implements the flags.Marshaler and +// Unmarshaler interfaces so it can be used as a config struct field. +type AmountFlag struct { + btcutil.Amount +} + +// NewAmountFlag creates an AmountFlag with a default btcutil.Amount. +func NewAmountFlag(defaultValue btcutil.Amount) *AmountFlag { + return &AmountFlag{defaultValue} +} + +// MarshalFlag satisfies the flags.Marshaler interface. +func (a *AmountFlag) MarshalFlag() (string, error) { + return a.Amount.String(), nil +} + +// UnmarshalFlag satisfies the flags.Unmarshaler interface. +func (a *AmountFlag) UnmarshalFlag(value string) error { + value = strings.TrimSuffix(value, " BTC") + valueF64, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + amount, err := btcutil.NewAmount(valueF64) + if err != nil { + return err + } + a.Amount = amount + return nil +} diff --git a/types/cfgutil/explicitflags.go b/types/cfgutil/explicitflags.go new file mode 100644 index 00000000..331e6099 --- /dev/null +++ b/types/cfgutil/explicitflags.go @@ -0,0 +1,36 @@ +// Copyright (c) 2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package cfgutil + +// ExplicitString is a string value implementing the flags.Marshaler and +// flags.Unmarshaler interfaces so it may be used as a config struct field. It +// records whether the value was explicitly set by the flags package. This is +// useful when behavior must be modified depending on whether a flag was set by +// the user or left as a default. Without recording this, it would be +// impossible to determine whether flag with a default value was unmodified or +// explicitly set to the default. +type ExplicitString struct { + Value string + explicitlySet bool +} + +// NewExplicitString creates a string flag with the provided default value. +func NewExplicitString(defaultValue string) *ExplicitString { + return &ExplicitString{Value: defaultValue, explicitlySet: false} +} + +// ExplicitlySet returns whether the flag was explicitly set through the +// flags.Unmarshaler interface. +func (e *ExplicitString) ExplicitlySet() bool { return e.explicitlySet } + +// MarshalFlag implements the flags.Marshaler interface. +func (e *ExplicitString) MarshalFlag() (string, error) { return e.Value, nil } + +// UnmarshalFlag implements the flags.Unmarshaler interface. +func (e *ExplicitString) UnmarshalFlag(value string) error { + e.Value = value + e.explicitlySet = true + return nil +} diff --git a/types/cfgutil/file.go b/types/cfgutil/file.go new file mode 100644 index 00000000..02495642 --- /dev/null +++ b/types/cfgutil/file.go @@ -0,0 +1,19 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package cfgutil + +import "os" + +// FileExists reports whether the named file or directory exists. +func FileExists(filePath string) (bool, error) { + _, err := os.Stat(filePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} diff --git a/types/cfgutil/normalization.go b/types/cfgutil/normalization.go new file mode 100644 index 00000000..dc67a21e --- /dev/null +++ b/types/cfgutil/normalization.go @@ -0,0 +1,50 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package cfgutil + +import "net" + +// NormalizeAddress returns the normalized form of the address, adding a default +// port if necessary. An error is returned if the address, even without a port, +// is not valid. +func NormalizeAddress(addr string, defaultPort string) (hostport string, err error) { + // If the first SplitHostPort errors because of a missing port and not + // for an invalid host, add the port. If the second SplitHostPort + // fails, then a port is not missing and the original error should be + // returned. + host, port, origErr := net.SplitHostPort(addr) + if origErr == nil { + return net.JoinHostPort(host, port), nil + } + addr = net.JoinHostPort(addr, defaultPort) + _, _, err = net.SplitHostPort(addr) + if err != nil { + return "", origErr + } + return addr, nil +} + +// NormalizeAddresses returns a new slice with all the passed peer addresses +// normalized with the given default port, and all duplicates removed. +func NormalizeAddresses(addrs []string, defaultPort string) ([]string, error) { + var ( + normalized = make([]string, 0, len(addrs)) + seenSet = make(map[string]struct{}) + ) + + for _, addr := range addrs { + normalizedAddr, err := NormalizeAddress(addr, defaultPort) + if err != nil { + return nil, err + } + _, seen := seenSet[normalizedAddr] + if !seen { + normalized = append(normalized, normalizedAddr) + seenSet[normalizedAddr] = struct{}{} + } + } + + return normalized, nil +} diff --git a/types/rpchelp/genrpcserverhelp.go b/types/rpchelp/genrpcserverhelp.go new file mode 100644 index 00000000..9a9c24bb --- /dev/null +++ b/types/rpchelp/genrpcserverhelp.go @@ -0,0 +1,95 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +//go:build generate +// +build generate + +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcwallet/internal/rpchelp" +) + +var outputFile = func() *os.File { + fi, err := os.Create("rpcserverhelp.go") + if err != nil { + log.Fatal(err) + } + return fi +}() + +func writefln(format string, args ...interface{}) { + _, err := fmt.Fprintf(outputFile, format, args...) + if err != nil { + log.Fatal(err) + } + _, err = outputFile.Write([]byte{'\n'}) + if err != nil { + log.Fatal(err) + } +} + +func writeLocaleHelp(locale, goLocale string, descs map[string]string) { + funcName := "helpDescs" + goLocale + writefln("func %s() map[string]string {", funcName) + writefln("return map[string]string{") + for i := range rpchelp.Methods { + m := &rpchelp.Methods[i] + helpText, err := btcjson.GenerateHelp(m.Method, descs, m.ResultTypes...) + if err != nil { + log.Fatal(err) + } + writefln("%q: %q,", m.Method, helpText) + } + writefln("}") + writefln("}") +} + +func writeLocales() { + writefln("var localeHelpDescs = map[string]func() map[string]string{") + for _, h := range rpchelp.HelpDescs { + writefln("%q: helpDescs%s,", h.Locale, h.GoLocale) + } + writefln("}") +} + +func writeUsage() { + usageStrs := make([]string, len(rpchelp.Methods)) + var err error + for i := range rpchelp.Methods { + usageStrs[i], err = btcjson.MethodUsageText(rpchelp.Methods[i].Method) + if err != nil { + log.Fatal(err) + } + } + usages := strings.Join(usageStrs, "\n") + writefln("var requestUsages = %q", usages) +} + +func main() { + defer outputFile.Close() + + packageName := "main" + if len(os.Args) > 1 { + packageName = os.Args[1] + } + + writefln("// AUTOGENERATED by internal/rpchelp/genrpcserverhelp.go; do not edit.") + writefln("") + writefln("package %s", packageName) + writefln("") + for _, h := range rpchelp.HelpDescs { + writeLocaleHelp(h.Locale, h.GoLocale, h.Descs) + writefln("") + } + writeLocales() + writefln("") + writeUsage() +} diff --git a/types/rpchelp/helpdescs_en_US.go b/types/rpchelp/helpdescs_en_US.go new file mode 100644 index 00000000..0eab5816 --- /dev/null +++ b/types/rpchelp/helpdescs_en_US.go @@ -0,0 +1,414 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +//go:build !generate +// +build !generate + +package rpchelp + +var helpDescsEnUS = map[string]string{ + // AddMultisigAddressCmd help. + "addmultisigaddress--synopsis": "Generates and imports a multisig address and redeeming script to the 'imported' account.", + "addmultisigaddress-account": "DEPRECATED -- Unused (all imported addresses belong to the imported account)", + "addmultisigaddress-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address", + "addmultisigaddress-nrequired": "The number of signatures required to redeem outputs paid to this address", + "addmultisigaddress--result0": "The imported pay-to-script-hash address", + + // CreateMultisigCmd help. + "createmultisig--synopsis": "Generate a multisig address and redeem script.", + "createmultisig-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address", + "createmultisig-nrequired": "The number of signatures required to redeem outputs paid to this address", + + // CreateMultisigResult help. + "createmultisigresult-address": "The generated pay-to-script-hash address", + "createmultisigresult-redeemScript": "The script required to redeem outputs paid to the multisig address", + + // DumpPrivKeyCmd help. + "dumpprivkey--synopsis": "Returns the private key in WIF encoding that controls some wallet address.", + "dumpprivkey-address": "The address to return a private key for", + "dumpprivkey--result0": "The WIF-encoded private key", + + // GetAccountCmd help. + "getaccount--synopsis": "DEPRECATED -- Lookup the account name that some wallet address belongs to.", + "getaccount-address": "The address to query the account for", + "getaccount--result0": "The name of the account that 'address' belongs to", + + // GetAccountAddressCmd help. + "getaccountaddress--synopsis": "DEPRECATED -- Returns the most recent external payment address for an account that has not been seen publicly.\n" + + "A new address is generated for the account if the most recently generated address has been seen on the blockchain or in mempool.", + "getaccountaddress-account": "The account of the returned address", + "getaccountaddress--result0": "The unused address for 'account'", + + // GetAddressesByAccountCmd help. + "getaddressesbyaccount--synopsis": "DEPRECATED -- Returns all addresses strings controlled by a single account.", + "getaddressesbyaccount-account": "Account name to fetch addresses for", + "getaddressesbyaccount--result0": "All addresses controlled by 'account'", + + // GetBalanceCmd help. + "getbalance--synopsis": "Calculates and returns the balance of one or all accounts.", + "getbalance-minconf": "Minimum number of block confirmations required before an unspent output's value is included in the balance", + "getbalance-account": "DEPRECATED -- The account name to query the balance for, or \"*\" to consider all accounts (default=\"*\")", + "getbalance--condition0": "account != \"*\"", + "getbalance--condition1": "account = \"*\"", + "getbalance--result0": "The balance of 'account' valued in bitcoin", + "getbalance--result1": "The balance of all accounts valued in bitcoin", + + // GetBestBlockHashCmd help. + "getbestblockhash--synopsis": "Returns the hash of the newest block in the best chain that wallet has finished syncing with.", + "getbestblockhash--result0": "The hash of the most recent synced-to block", + + // GetBlockCountCmd help. + "getblockcount--synopsis": "Returns the blockchain height of the newest block in the best chain that wallet has finished syncing with.", + "getblockcount--result0": "The blockchain height of the most recent synced-to block", + + // GetInfoCmd help. + "getinfo--synopsis": "Returns a JSON object containing various state info.", + + // InfoWalletResult help. + "infowalletresult-version": "The version of the server", + "infowalletresult-protocolversion": "The latest supported protocol version", + "infowalletresult-blocks": "The number of blocks processed", + "infowalletresult-timeoffset": "The time offset", + "infowalletresult-connections": "The number of connected peers", + "infowalletresult-proxy": "The proxy used by the server", + "infowalletresult-difficulty": "The current target difficulty", + "infowalletresult-testnet": "Whether or not server is using testnet", + "infowalletresult-relayfee": "The minimum relay fee for non-free transactions in BTC/KB", + "infowalletresult-errors": "Any current errors", + "infowalletresult-paytxfee": "The increment used each time more fee is required for an authored transaction", + "infowalletresult-balance": "The balance of all accounts calculated with one block confirmation", + "infowalletresult-walletversion": "The version of the address manager database", + "infowalletresult-unlocked_until": "Unset", + "infowalletresult-keypoolsize": "Unset", + "infowalletresult-keypoololdest": "Unset", + + // GetNewAddressCmd help. + "getnewaddress--synopsis": "Generates and returns a new payment address.", + "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to (default=\"default\")", + "getnewaddress-addresstype": "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")", + "getnewaddress--result0": "The payment address", + + // GetRawChangeAddressCmd help. + "getrawchangeaddress--synopsis": "Generates and returns a new internal payment address for use as a change address in raw transactions.", + "getrawchangeaddress-account": "Account name the new internal address will belong to (default=\"default\")", + "getrawchangeaddress-addresstype": "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")", + "getrawchangeaddress--result0": "The internal payment address", + + // GetReceivedByAccountCmd help. + "getreceivedbyaccount--synopsis": "DEPRECATED -- Returns the total amount received by addresses of some account, including spent outputs.", + "getreceivedbyaccount-account": "Account name to query total received amount for", + "getreceivedbyaccount-minconf": "Minimum number of block confirmations required before an output's value is included in the total", + "getreceivedbyaccount--result0": "The total received amount valued in bitcoin", + + // GetReceivedByAddressCmd help. + "getreceivedbyaddress--synopsis": "Returns the total amount received by a single address, including spent outputs.", + "getreceivedbyaddress-address": "Payment address which received outputs to include in total", + "getreceivedbyaddress-minconf": "Minimum number of block confirmations required before an output's value is included in the total", + "getreceivedbyaddress--result0": "The total received amount valued in bitcoin", + + // GetTransactionCmd help. + "gettransaction--synopsis": "Returns a JSON object with details regarding a transaction relevant to this wallet.", + "gettransaction-txid": "Hash of the transaction to query", + "gettransaction-includewatchonly": "Also consider transactions involving watched addresses", + + // HelpCmd help. + "help--synopsis": "Returns a list of all commands or help for a specified command.", + "help-command": "The command to retrieve help for", + "help--condition0": "no command provided", + "help--condition1": "command specified", + "help--result0": "List of commands", + "help--result1": "Help for specified command", + + // GetTransactionResult help. + "gettransactionresult-amount": "The total amount this transaction credits to the wallet, valued in bitcoin", + "gettransactionresult-fee": "The total input value minus the total output value, or 0 if 'txid' is not a sent transaction", + "gettransactionresult-confirmations": "The number of block confirmations of the transaction", + "gettransactionresult-blockhash": "The hash of the block this transaction is mined in, or the empty string if unmined", + "gettransactionresult-blockindex": "Unset", + "gettransactionresult-blocktime": "The Unix time of the block header this transaction is mined in, or 0 if unmined", + "gettransactionresult-txid": "The transaction hash", + "gettransactionresult-walletconflicts": "Unset", + "gettransactionresult-time": "The earliest Unix time this transaction was known to exist", + "gettransactionresult-timereceived": "The earliest Unix time this transaction was known to exist", + "gettransactionresult-details": "Additional details for each recorded wallet credit and debit", + "gettransactionresult-hex": "The transaction encoded as a hexadecimal string", + + // GetTransactionDetailsResult help. + "gettransactiondetailsresult-account": "DEPRECATED -- Unset", + "gettransactiondetailsresult-address": "The address an output was paid to, or the empty string if the output is nonstandard or this detail is regarding a transaction input", + "gettransactiondetailsresult-category": `The kind of detail: "send" for sent transactions, "immature" for immature coinbase outputs, "generate" for mature coinbase outputs, or "recv" for all other received outputs`, + "gettransactiondetailsresult-amount": "The amount of a received output", + "gettransactiondetailsresult-fee": "The included fee for a sent transaction", + "gettransactiondetailsresult-vout": "The transaction output index", + "gettransactiondetailsresult-involveswatchonly": "Unset", + + // ImportPrivKeyCmd help. + "importprivkey--synopsis": "Imports a WIF-encoded private key to the 'imported' account.", + "importprivkey-privkey": "The WIF-encoded private key", + "importprivkey-label": "Unused (must be unset or 'imported')", + "importprivkey-rescan": "Rescan the blockchain (since the genesis block) for outputs controlled by the imported key", + + // KeypoolRefillCmd help. + "keypoolrefill--synopsis": "DEPRECATED -- This request does nothing since no keypool is maintained.", + "keypoolrefill-newsize": "Unused", + + // ListAccountsCmd help. + "listaccounts--synopsis": "DEPRECATED -- Returns a JSON object of all accounts and their balances.", + "listaccounts-minconf": "Minimum number of block confirmations required before an unspent output's value is included in the balance", + "listaccounts--result0--desc": "JSON object with account names as keys and bitcoin amounts as values", + "listaccounts--result0--key": "The account name", + "listaccounts--result0--value": "The account balance valued in bitcoin", + + // ListLockUnspentCmd help. + "listlockunspent--synopsis": "Returns a JSON array of outpoints marked as locked (with lockunspent) for this wallet session.", + + // TransactionInput help. + "transactioninput-txid": "The transaction hash of the referenced output", + "transactioninput-vout": "The output index of the referenced output", + + // ListReceivedByAccountCmd help. + "listreceivedbyaccount--synopsis": "DEPRECATED -- Returns a JSON array of objects listing all accounts and the total amount received by each account.", + "listreceivedbyaccount-minconf": "Minimum number of block confirmations required before a transaction is considered", + "listreceivedbyaccount-includeempty": "Unused", + "listreceivedbyaccount-includewatchonly": "Unused", + + // ListReceivedByAccountResult help. + "listreceivedbyaccountresult-account": "The name of the account", + "listreceivedbyaccountresult-amount": "Total amount received by payment addresses of the account valued in bitcoin", + "listreceivedbyaccountresult-confirmations": "Number of block confirmations of the most recent transaction relevant to the account", + + // ListReceivedByAddressCmd help. + "listreceivedbyaddress--synopsis": "Returns a JSON array of objects listing wallet payment addresses and their total received amounts.", + "listreceivedbyaddress-minconf": "Minimum number of block confirmations required before a transaction is considered", + "listreceivedbyaddress-includeempty": "Unused", + "listreceivedbyaddress-includewatchonly": "Unused", + + // ListReceivedByAddressResult help. + "listreceivedbyaddressresult-account": "DEPRECATED -- Unset", + "listreceivedbyaddressresult-address": "The payment address", + "listreceivedbyaddressresult-amount": "Total amount received by the payment address valued in bitcoin", + "listreceivedbyaddressresult-confirmations": "Number of block confirmations of the most recent transaction relevant to the address", + "listreceivedbyaddressresult-txids": "Transaction hashes of all transactions involving this address", + "listreceivedbyaddressresult-involvesWatchonly": "Unset", + + // ListSinceBlockCmd help. + "listsinceblock--synopsis": "Returns a JSON array of objects listing details of all wallet transactions after some block.", + "listsinceblock-blockhash": "Hash of the parent block of the first block to consider transactions from, or unset to list all transactions", + "listsinceblock-targetconfirmations": "Minimum number of block confirmations of the last block in the result object. Must be 1 or greater. Note: The transactions array in the result object is not affected by this parameter", + "listsinceblock-includewatchonly": "Unused", + "listsinceblock--condition0": "blockhash specified", + "listsinceblock--condition1": "no blockhash specified", + "listsinceblock--result0": "Lists all transactions, including unmined transactions, since the specified block", + "listsinceblock--result1": "Lists all transactions since the genesis block", + + // ListSinceBlockResult help. + "listsinceblockresult-transactions": "JSON array of objects containing verbose details of the each transaction", + "listsinceblockresult-lastblock": "Hash of the latest-synced block to be used in later calls to listsinceblock", + + // ListTransactionsResult help. + "listtransactionsresult-account": "DEPRECATED -- Unset", + "listtransactionsresult-address": "Payment address for a transaction output", + "listtransactionsresult-category": `The kind of transaction: "send" for sent transactions, "immature" for immature coinbase outputs, "generate" for mature coinbase outputs, or "recv" for all other received outputs. Note: A single output may be included multiple times under different categories`, + "listtransactionsresult-amount": "The value of the transaction output valued in bitcoin", + "listtransactionsresult-fee": "The total input value minus the total output value for sent transactions", + "listtransactionsresult-confirmations": "The number of block confirmations of the transaction", + "listtransactionsresult-generated": "Whether the transaction output is a coinbase output", + "listtransactionsresult-blockhash": "The hash of the block this transaction is mined in, or the empty string if unmined", + "listtransactionsresult-blockheight": "The block height containing the transaction.", + "listtransactionsresult-blockindex": "Unset", + "listtransactionsresult-blocktime": "The Unix time of the block header this transaction is mined in, or 0 if unmined", + "listtransactionsresult-label": "A comment for the address/transaction, if any", + "listtransactionsresult-txid": "The hash of the transaction", + "listtransactionsresult-vout": "The transaction output index", + "listtransactionsresult-walletconflicts": "Unset", + "listtransactionsresult-time": "The earliest Unix time this transaction was known to exist", + "listtransactionsresult-timereceived": "The earliest Unix time this transaction was known to exist", + "listtransactionsresult-involveswatchonly": "Unset", + "listtransactionsresult-comment": "Unset", + "listtransactionsresult-otheraccount": "Unset", + "listtransactionsresult-trusted": "Unset", + "listtransactionsresult-bip125-replaceable": "Unset", + "listtransactionsresult-abandoned": "Unset", + + // ListTransactionsCmd help. + "listtransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions.", + "listtransactions-account": "DEPRECATED -- Unused (must be unset or \"*\")", + "listtransactions-count": "Maximum number of transactions to create results from", + "listtransactions-from": "Number of transactions to skip before results are created", + "listtransactions-includewatchonly": "Unused", + + // ListUnspentCmd help. + "listunspent--synopsis": "Returns a JSON array of objects representing unlocked unspent outputs controlled by wallet keys.", + "listunspent-minconf": "Minimum number of block confirmations required before a transaction output is considered", + "listunspent-maxconf": "Maximum number of block confirmations required before a transaction output is excluded", + "listunspent-addresses": "If set, limits the returned details to unspent outputs received by any of these payment addresses", + + // ListUnspentResult help. + "listunspentresult-txid": "The transaction hash of the referenced output", + "listunspentresult-vout": "The output index of the referenced output", + "listunspentresult-address": "The payment address that received the output", + "listunspentresult-account": "The account associated with the receiving payment address", + "listunspentresult-scriptPubKey": "The output script encoded as a hexadecimal string", + "listunspentresult-redeemScript": "Unset", + "listunspentresult-amount": "The amount of the output valued in bitcoin", + "listunspentresult-confirmations": "The number of block confirmations of the transaction", + "listunspentresult-spendable": "Whether the output is entirely controlled by wallet keys/scripts (false for partially controlled multisig outputs or outputs to watch-only addresses)", + + // LockUnspentCmd help. + "lockunspent--synopsis": "Locks or unlocks an unspent output.\n" + + "Locked outputs are not chosen for transaction inputs of authored transactions and are not included in 'listunspent' results.\n" + + "Locked outputs are volatile and are not saved across wallet restarts.\n" + + "If unlock is true and no transaction outputs are specified, all locked outputs are marked unlocked.", + "lockunspent-unlock": "True to unlock outputs, false to lock", + "lockunspent-transactions": "Transaction outputs to lock or unlock", + "lockunspent--result0": "The boolean 'true'", + + // SendFromCmd help. + "sendfrom--synopsis": "DEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + + "A change output is automatically included to send extra output value back to the original account.", + "sendfrom-fromaccount": "Account to pick unspent outputs from", + "sendfrom-toaddress": "Address to pay", + "sendfrom-amount": "Amount to send to the payment address valued in bitcoin", + "sendfrom-minconf": "Minimum number of block confirmations required before a transaction output is eligible to be spent", + "sendfrom-comment": "Unused", + "sendfrom-commentto": "Unused", + "sendfrom--result0": "The transaction hash of the sent transaction", + + // SendManyCmd help. + "sendmany--synopsis": "Authors, signs, and sends a transaction that outputs to many payment addresses.\n" + + "A change output is automatically included to send extra output value back to the original account.", + "sendmany-fromaccount": "DEPRECATED -- Account to pick unspent outputs from", + "sendmany-amounts": "Pairs of payment addresses and the output amount to pay each", + "sendmany-amounts--desc": "JSON object using payment addresses as keys and output amounts valued in bitcoin to send to each address", + "sendmany-amounts--key": "Address to pay", + "sendmany-amounts--value": "Amount to send to the payment address valued in bitcoin", + "sendmany-minconf": "Minimum number of block confirmations required before a transaction output is eligible to be spent", + "sendmany-comment": "Unused", + "sendmany--result0": "The transaction hash of the sent transaction", + + // SendToAddressCmd help. + "sendtoaddress--synopsis": "Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + + "Unlike sendfrom, outputs are always chosen from the default account.\n" + + "A change output is automatically included to send extra output value back to the original account.", + "sendtoaddress-address": "Address to pay", + "sendtoaddress-amount": "Amount to send to the payment address valued in bitcoin", + "sendtoaddress-comment": "Unused", + "sendtoaddress-commentto": "Unused", + "sendtoaddress--result0": "The transaction hash of the sent transaction", + + // SetTxFeeCmd help. + "settxfee--synopsis": "Modify the increment used each time more fee is required for an authored transaction.", + "settxfee-amount": "The new fee increment valued in bitcoin", + "settxfee--result0": "The boolean 'true'", + + // SignMessageCmd help. + "signmessage--synopsis": "Signs a message using the private key of a payment address.", + "signmessage-address": "Payment address of private key used to sign the message with", + "signmessage-message": "Message to sign", + "signmessage--result0": "The signed message encoded as a base64 string", + + // SignRawTransactionCmd help. + "signrawtransaction--synopsis": "Signs transaction inputs using private keys from this wallet and request.\n" + + "The valid flags options are ALL, NONE, SINGLE, ALL|ANYONECANPAY, NONE|ANYONECANPAY, and SINGLE|ANYONECANPAY.", + "signrawtransaction-rawtx": "Unsigned or partially unsigned transaction to sign encoded as a hexadecimal string", + "signrawtransaction-inputs": "Additional data regarding inputs that this wallet may not be tracking", + "signrawtransaction-privkeys": "Additional WIF-encoded private keys to use when creating signatures", + "signrawtransaction-flags": "Sighash flags", + + // SignRawTransactionResult help. + "signrawtransactionresult-hex": "The resulting transaction encoded as a hexadecimal string", + "signrawtransactionresult-complete": "Whether all input signatures have been created", + "signrawtransactionresult-errors": "Script verification errors (if exists)", + + // SignRawTransactionError help. + "signrawtransactionerror-error": "Verification or signing error related to the input", + "signrawtransactionerror-sequence": "Script sequence number", + "signrawtransactionerror-scriptSig": "The hex-encoded signature script", + "signrawtransactionerror-txid": "The transaction hash of the referenced previous output", + "signrawtransactionerror-vout": "The output index of the referenced previous output", + + // ValidateAddressCmd help. + "validateaddress--synopsis": "Verify that an address is valid.\n" + + "Extra details are returned if the address is controlled by this wallet.\n" + + "The following fields are valid only when the address is controlled by this wallet (ismine=true): isscript, pubkey, iscompressed, account, addresses, hex, script, and sigsrequired.\n" + + "The following fields are only valid when address has an associated public key: pubkey, iscompressed.\n" + + "The following fields are only valid when address is a pay-to-script-hash address: addresses, hex, and script.\n" + + "If the address is a multisig address controlled by this wallet, the multisig fields will be left unset if the wallet is locked since the redeem script cannot be decrypted.", + "validateaddress-address": "Address to validate", + + // ValidateAddressWalletResult help. + "validateaddresswalletresult-isvalid": "Whether or not the address is valid", + "validateaddresswalletresult-address": "The payment address (only when isvalid is true)", + "validateaddresswalletresult-ismine": "Whether this address is controlled by the wallet (only when isvalid is true)", + "validateaddresswalletresult-iswatchonly": "Unset", + "validateaddresswalletresult-isscript": "Whether the payment address is a pay-to-script-hash address (only when isvalid is true)", + "validateaddresswalletresult-pubkey": "The associated public key of the payment address, if any (only when isvalid is true)", + "validateaddresswalletresult-iscompressed": "Whether the address was created by hashing a compressed public key, if any (only when isvalid is true)", + "validateaddresswalletresult-account": "The account this payment address belongs to (only when isvalid is true)", + "validateaddresswalletresult-addresses": "All associated payment addresses of the script if address is a multisig address (only when isvalid is true)", + "validateaddresswalletresult-hex": "The redeem script ", + "validateaddresswalletresult-script": "The class of redeem script for a multisig address", + "validateaddresswalletresult-sigsrequired": "The number of required signatures to redeem outputs to the multisig address", + + // VerifyMessageCmd help. + "verifymessage--synopsis": "Verify a message was signed with the associated private key of some address.", + "verifymessage-address": "Address used to sign message", + "verifymessage-signature": "The signature to verify", + "verifymessage-message": "The message to verify", + "verifymessage--result0": "Whether the message was signed with the private key of 'address'", + + // WalletLockCmd help. + "walletlock--synopsis": "Lock the wallet.", + + // WalletPassphraseCmd help. + "walletpassphrase--synopsis": "Unlock the wallet.", + "walletpassphrase-passphrase": "The wallet passphrase", + "walletpassphrase-timeout": "The number of seconds to wait before the wallet automatically locks", + + // WalletPassphraseChangeCmd help. + "walletpassphrasechange--synopsis": "Change the wallet passphrase.", + "walletpassphrasechange-oldpassphrase": "The old wallet passphrase", + "walletpassphrasechange-newpassphrase": "The new wallet passphrase", + + // CreateNewAccountCmd help. + "createnewaccount--synopsis": "Creates a new account.\n" + + "The wallet must be unlocked for this request to succeed.", + "createnewaccount-account": "Name of the new account", + + // ExportWatchingWalletCmd help. + "exportwatchingwallet--synopsis": "Creates and returns a duplicate of the wallet database without any private keys to be used as a watching-only wallet.", + "exportwatchingwallet-account": "Unused (must be unset or \"*\")", + "exportwatchingwallet-download": "Unused", + "exportwatchingwallet--result0": "The watching-only database encoded as a base64 string", + + // GetBestBlockCmd help. + "getbestblock--synopsis": "Returns the hash and height of the newest block in the best chain that wallet has finished syncing with.", + + // GetBestBlockResult help. + "getbestblockresult-hash": "The hash of the block", + "getbestblockresult-height": "The blockchain height of the block", + + // GetUnconfirmedBalanceCmd help. + "getunconfirmedbalance--synopsis": "Calculates the unspent output value of all unmined transaction outputs for an account.", + "getunconfirmedbalance-account": "The account to query the unconfirmed balance for (default=\"default\")", + "getunconfirmedbalance--result0": "Total amount of all unmined unspent outputs of the account valued in bitcoin.", + + // ListAddressTransactionsCmd help. + "listaddresstransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions pertaining some addresses.", + "listaddresstransactions-addresses": "Addresses to filter transaction results by", + "listaddresstransactions-account": "Unused (must be unset or \"*\")", + + // ListAllTransactionsCmd help. + "listalltransactions--synopsis": "Returns a JSON array of objects in the same format as 'listtransactions' without limiting the number of returned objects.", + "listalltransactions-account": "Unused (must be unset or \"*\")", + + // RenameAccountCmd help. + "renameaccount--synopsis": "Renames an account.", + "renameaccount-oldaccount": "The old account name to rename", + "renameaccount-newaccount": "The new name for the account", + + // WalletIsLockedCmd help. + "walletislocked--synopsis": "Returns whether or not the wallet is locked.", + "walletislocked--result0": "Whether the wallet is locked", +} diff --git a/types/rpchelp/methods.go b/types/rpchelp/methods.go new file mode 100644 index 00000000..ce037f72 --- /dev/null +++ b/types/rpchelp/methods.go @@ -0,0 +1,81 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +//go:build !generate +// +build !generate + +package rpchelp + +import "github.com/btcsuite/btcd/btcjson" + +// Common return types. +var ( + returnsBool = []interface{}{(*bool)(nil)} + returnsNumber = []interface{}{(*float64)(nil)} + returnsString = []interface{}{(*string)(nil)} + returnsStringArray = []interface{}{(*[]string)(nil)} + returnsLTRArray = []interface{}{(*[]btcjson.ListTransactionsResult)(nil)} +) + +// Methods contains all methods and result types that help is generated for, +// for every locale. +var Methods = []struct { + Method string + ResultTypes []interface{} +}{ + {"addmultisigaddress", returnsString}, + {"createmultisig", []interface{}{(*btcjson.CreateMultiSigResult)(nil)}}, + {"dumpprivkey", returnsString}, + {"getaccount", returnsString}, + {"getaccountaddress", returnsString}, + {"getaddressesbyaccount", returnsStringArray}, + {"getbalance", append(returnsNumber, returnsNumber[0])}, + {"getbestblockhash", returnsString}, + {"getblockcount", returnsNumber}, + {"getinfo", []interface{}{(*btcjson.InfoWalletResult)(nil)}}, + {"getnewaddress", returnsString}, + {"getrawchangeaddress", returnsString}, + {"getreceivedbyaccount", returnsNumber}, + {"getreceivedbyaddress", returnsNumber}, + {"gettransaction", []interface{}{(*btcjson.GetTransactionResult)(nil)}}, + {"help", append(returnsString, returnsString[0])}, + {"importprivkey", nil}, + {"keypoolrefill", nil}, + {"listaccounts", []interface{}{(*map[string]float64)(nil)}}, + {"listlockunspent", []interface{}{(*[]btcjson.TransactionInput)(nil)}}, + {"listreceivedbyaccount", []interface{}{(*[]btcjson.ListReceivedByAccountResult)(nil)}}, + {"listreceivedbyaddress", []interface{}{(*[]btcjson.ListReceivedByAddressResult)(nil)}}, + {"listsinceblock", []interface{}{(*btcjson.ListSinceBlockResult)(nil)}}, + {"listtransactions", returnsLTRArray}, + {"listunspent", []interface{}{(*btcjson.ListUnspentResult)(nil)}}, + {"lockunspent", returnsBool}, + {"sendfrom", returnsString}, + {"sendmany", returnsString}, + {"sendtoaddress", returnsString}, + {"settxfee", returnsBool}, + {"signmessage", returnsString}, + {"signrawtransaction", []interface{}{(*btcjson.SignRawTransactionResult)(nil)}}, + {"validateaddress", []interface{}{(*btcjson.ValidateAddressWalletResult)(nil)}}, + {"verifymessage", returnsBool}, + {"walletlock", nil}, + {"walletpassphrase", nil}, + {"walletpassphrasechange", nil}, + {"createnewaccount", nil}, + {"exportwatchingwallet", returnsString}, + {"getbestblock", []interface{}{(*btcjson.GetBestBlockResult)(nil)}}, + {"getunconfirmedbalance", returnsNumber}, + {"listaddresstransactions", returnsLTRArray}, + {"listalltransactions", returnsLTRArray}, + {"renameaccount", nil}, + {"walletislocked", returnsBool}, +} + +// HelpDescs contains the locale-specific help strings along with the locale. +var HelpDescs = []struct { + Locale string // Actual locale, e.g. en_US + GoLocale string // Locale used in Go names, e.g. EnUS + Descs map[string]string +}{ + {"en_US", "EnUS", helpDescsEnUS}, // helpdescs_en_US.go +} diff --git a/types/zero/array.go b/types/zero/array.go new file mode 100644 index 00000000..79077aca --- /dev/null +++ b/types/zero/array.go @@ -0,0 +1,17 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package zero + +// Bytea32 clears the 32-byte array by filling it with the zero value. +// This is used to explicitly clear private key material from memory. +func Bytea32(b *[32]byte) { + *b = [32]byte{} +} + +// Bytea64 clears the 64-byte array by filling it with the zero value. +// This is used to explicitly clear sensitive material from memory. +func Bytea64(b *[64]byte) { + *b = [64]byte{} +} diff --git a/types/zero/benchmark_test.go b/types/zero/benchmark_test.go new file mode 100644 index 00000000..37d1b95b --- /dev/null +++ b/types/zero/benchmark_test.go @@ -0,0 +1,87 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package zero_test + +import ( + "testing" + + "github.com/babylonchain/vigilante/types/zero" +) + +var ( + bytes32 = make([]byte, 32) // typical key size + bytes64 = make([]byte, 64) // passphrase hash size + bytea32 = new([32]byte) + bytea64 = new([64]byte) +) + +// xor is the "slow" byte zeroing implementation which this package +// originally replaced. If this function benchmarks faster than the +// functions exported by this package in a future Go version (perhaps +// by calling runtime.memclr), replace the "optimized" versions with +// this. +func xor(b []byte) { + for i := range b { + b[i] ^= b[i] + } +} + +// zrange is an alternative zero implementation that, while currently +// slower than the functions provided by this package, may be faster +// in a future Go release. Switch to this or the xor implementation +// if they ever become faster. +func zrange(b []byte) { + for i := range b { + b[i] = 0 + } +} + +func BenchmarkXor32(b *testing.B) { + for i := 0; i < b.N; i++ { + xor(bytes32) + } +} + +func BenchmarkXor64(b *testing.B) { + for i := 0; i < b.N; i++ { + xor(bytes64) + } +} + +func BenchmarkRange32(b *testing.B) { + for i := 0; i < b.N; i++ { + zrange(bytes32) + } +} + +func BenchmarkRange64(b *testing.B) { + for i := 0; i < b.N; i++ { + zrange(bytes64) + } +} + +func BenchmarkBytes32(b *testing.B) { + for i := 0; i < b.N; i++ { + zero.Bytes(bytes32) + } +} + +func BenchmarkBytes64(b *testing.B) { + for i := 0; i < b.N; i++ { + zero.Bytes(bytes64) + } +} + +func BenchmarkBytea32(b *testing.B) { + for i := 0; i < b.N; i++ { + zero.Bytea32(bytea32) + } +} + +func BenchmarkBytea64(b *testing.B) { + for i := 0; i < b.N; i++ { + zero.Bytea64(bytea64) + } +} diff --git a/types/zero/doc.go b/types/zero/doc.go new file mode 100644 index 00000000..457b8e3b --- /dev/null +++ b/types/zero/doc.go @@ -0,0 +1,3 @@ +// Package zero contains functions to clear data from byte slices and +// multi-precision integers. +package zero diff --git a/types/zero/slice.go b/types/zero/slice.go new file mode 100644 index 00000000..70edfc89 --- /dev/null +++ b/types/zero/slice.go @@ -0,0 +1,34 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// This file implements range-based zeroing, which as of Go 1.5 is +// optimized using a Duff's device. + +package zero + +import "math/big" + +// Bytes sets all bytes in the passed slice to zero. This is used to +// explicitly clear private key material from memory. +// +// In general, prefer to use the fixed-sized zeroing functions (Bytea*) +// when zeroing bytes as they are much more efficient than the variable +// sized zeroing func Bytes. +func Bytes(b []byte) { + for i := range b { + b[i] = 0 + } +} + +// BigInt sets all bytes in the passed big int to zero and then sets the +// value to 0. This differs from simply setting the value in that it +// specifically clears the underlying bytes whereas simply setting the value +// does not. This is mostly useful to forcefully clear private keys. +func BigInt(x *big.Int) { + b := x.Bits() + for i := range b { + b[i] = 0 + } + x.SetInt64(0) +} diff --git a/types/zero/zero_test.go b/types/zero/zero_test.go new file mode 100644 index 00000000..e1217cdb --- /dev/null +++ b/types/zero/zero_test.go @@ -0,0 +1,147 @@ +// Copyright (c) 2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package zero_test + +import ( + "fmt" + "math/big" + "strings" + "testing" + + "github.com/babylonchain/vigilante/types/zero" +) + +func makeOneBytes(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = 1 + } + return b +} + +func checkZeroBytes(b []byte) error { + for i, v := range b { + if v != 0 { + return fmt.Errorf("b[%d] = %d", i, v) + } + } + return nil +} + +func TestBytes(t *testing.T) { + tests := []int{ + 0, + 31, + 32, + 33, + 127, + 128, + 129, + 255, + 256, + 256, + 257, + 383, + 384, + 385, + 511, + 512, + 513, + } + + for i, n := range tests { + b := makeOneBytes(n) + zero.Bytes(b) + err := checkZeroBytes(b) + if err != nil { + t.Errorf("Test %d (n=%d) failed: %v", i, n, err) + continue + } + } +} + +func checkZeroWords(b []big.Word) error { + for i, v := range b { + if v != 0 { + return fmt.Errorf("b[%d] = %d", i, v) + } + } + return nil +} + +var bigZero = new(big.Int) + +func TestBigInt(t *testing.T) { + tests := []string{ + // 16 0xFFFFFFFF 32-bit uintptrs + strings.Repeat("FFFFFFFF", 16), + + // 17 32-bit uintptrs, minimum value which enters loop on 32-bit + "01" + strings.Repeat("00000000", 16), + + // 32 0xFFFFFFFF 32-bit uintptrs, maximum value which enters loop exactly once on 32-bit + strings.Repeat("FFFFFFFF", 32), + + // 33 32-bit uintptrs, minimum value which enters loop twice on 32-bit + "01" + strings.Repeat("00000000", 32), + + // 16 0xFFFFFFFFFFFFFFFF 64-bit uintptrs + strings.Repeat("FFFFFFFFFFFFFFFF", 16), + + // 17 64-bit uintptrs, minimum value which enters loop on 64-bit + "01" + strings.Repeat("0000000000000000", 16), + + // 32 0xFFFFFFFFFFFFFFFF 64-bit uintptrs, maximum value which enters loop exactly once on 64-bit + strings.Repeat("FFFFFFFFFFFFFFFF", 32), + + // 33 64-bit uintptrs, minimum value which enters loop twice on 64-bit + "01" + strings.Repeat("0000000000000000", 32), + } + + for i, s := range tests { + v, ok := new(big.Int).SetString(s, 16) + if !ok { + t.Errorf("Test %d includes invalid hex number %s", i, s) + continue + } + + zero.BigInt(v) + err := checkZeroWords(v.Bits()) + if err != nil { + t.Errorf("Test %d (s=%s) failed: %v", i, s, err) + continue + } + if v.Cmp(bigZero) != 0 { + t.Errorf("Test %d (s=%s) zeroed big.Int represents non-zero number %v", i, s, v) + continue + } + } +} + +func TestBytea32(t *testing.T) { + const sz = 32 + var b [sz]byte + copy(b[:], makeOneBytes(sz)) + + zero.Bytea32(&b) + + err := checkZeroBytes(b[:]) + if err != nil { + t.Error(err) + } +} + +func TestBytea64(t *testing.T) { + const sz = 64 + var b [sz]byte + copy(b[:], makeOneBytes(sz)) + + zero.Bytea64(&b) + + err := checkZeroBytes(b[:]) + if err != nil { + t.Error(err) + } +} From 4d9c96e4bc00adf91db51a3faf1531771d353c36 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 4 Aug 2022 15:34:27 +1000 Subject: [PATCH 02/28] rpc server --- btcclient/README.md | 3 + go.mod | 14 +- go.sum | 13 +- rpcserver/README.md | 3 + rpcserver/api.proto | 18 +++ rpcserver/api/api.pb.go | 333 ++++++++++++++++++++++++++++++++++++++++ rpcserver/log.go | 81 ++++++++++ rpcserver/regen.sh | 3 + rpcserver/server.go | 48 ++++++ 9 files changed, 496 insertions(+), 20 deletions(-) create mode 100644 btcclient/README.md create mode 100644 rpcserver/README.md create mode 100644 rpcserver/api.proto create mode 100644 rpcserver/api/api.pb.go create mode 100644 rpcserver/log.go create mode 100755 rpcserver/regen.sh create mode 100644 rpcserver/server.go diff --git a/btcclient/README.md b/btcclient/README.md new file mode 100644 index 00000000..ab779f0b --- /dev/null +++ b/btcclient/README.md @@ -0,0 +1,3 @@ +# btcclient + +This package implements a Bitcoin client. The code is adapted from https://github.com/btcsuite/btcwallet/tree/master/chain. \ No newline at end of file diff --git a/go.mod b/go.mod index 095486cd..287d218b 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,13 @@ require ( github.com/lightninglabs/neutrino v0.14.2 github.com/lightningnetwork/lnd/ticker v1.1.0 github.com/stretchr/testify v1.8.0 + golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b + google.golang.org/grpc v1.48.0 + google.golang.org/protobuf v1.28.1 ) require ( github.com/aead/siphash v1.0.1 // indirect - github.com/btcsuite/btcd/btcutil/psbt v1.1.4 // indirect - github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 // indirect - github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect - github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect @@ -30,20 +29,13 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/lru v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/jessevdk/go-flags v1.4.0 // indirect - github.com/jrick/logrotate v1.0.0 // indirect github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect github.com/lightningnetwork/lnd/clock v1.0.1 // indirect - github.com/lightningnetwork/lnd/queue v1.0.1 // indirect github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect - google.golang.org/grpc v1.48.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 620fcf62..a774ca0a 100644 --- a/go.sum +++ b/go.sum @@ -21,7 +21,6 @@ github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9Ur github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= -github.com/btcsuite/btcd/btcutil/psbt v1.1.4 h1:Edx4AfBn+YPam2KP5AobDitulGp4r1Oibm8oruzkMdI= github.com/btcsuite/btcd/btcutil/psbt v1.1.4/go.mod h1:9AyU6EQVJ9Iw9zPyNT1lcdHd6cnEZdno5wLu5FY74os= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -31,11 +30,8 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcwallet v0.15.1 h1:SKfh/l2Bgz9sJwHZvfiVbZ8Pl3N/8fFcWWXzsAPz9GU= github.com/btcsuite/btcwallet v0.15.1/go.mod h1:7OFsQ8ypiRwmr67hE0z98uXgJgXGAihE79jCib9x6ag= -github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 h1:M2yr5UlULvpqtxUqpMxTME/pA92Z9cpqeyvAFk9lAg0= github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3/go.mod h1:T2xSiKGpUkSLCh68aF+FMXmKK9mFqNdHl9VaqOr+JjU= -github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= -github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= @@ -104,14 +100,13 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= @@ -128,7 +123,6 @@ github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/c github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc= github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= -github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= @@ -205,7 +199,6 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -222,6 +215,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -252,8 +246,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/rpcserver/README.md b/rpcserver/README.md new file mode 100644 index 00000000..434e19ce --- /dev/null +++ b/rpcserver/README.md @@ -0,0 +1,3 @@ +# rpcserver + +This package implements a GRPC server. The code is adapted from https://github.com/btcsuite/btcwallet/tree/master/rpc. \ No newline at end of file diff --git a/rpcserver/api.proto b/rpcserver/api.proto new file mode 100644 index 00000000..cb28584d --- /dev/null +++ b/rpcserver/api.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package rpc; +option go_package = "./"; + +service VigilanteService { + rpc Version (VersionRequest) returns (VersionResponse); +} + +message VersionRequest { +} +message VersionResponse { + string version_string = 1; + uint32 major = 2; + uint32 minor = 3; + uint32 patch = 4; + string prerelease = 5; + string build_metadata = 6; +} \ No newline at end of file diff --git a/rpcserver/api/api.pb.go b/rpcserver/api/api.pb.go new file mode 100644 index 00000000..b7a8951c --- /dev/null +++ b/rpcserver/api/api.pb.go @@ -0,0 +1,333 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.0 +// protoc v3.21.2 +// source: api.proto + +package __ + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type VersionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *VersionRequest) Reset() { + *x = VersionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VersionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VersionRequest) ProtoMessage() {} + +func (x *VersionRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VersionRequest.ProtoReflect.Descriptor instead. +func (*VersionRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +type VersionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + VersionString string `protobuf:"bytes,1,opt,name=version_string,json=versionString,proto3" json:"version_string,omitempty"` + Major uint32 `protobuf:"varint,2,opt,name=major,proto3" json:"major,omitempty"` + Minor uint32 `protobuf:"varint,3,opt,name=minor,proto3" json:"minor,omitempty"` + Patch uint32 `protobuf:"varint,4,opt,name=patch,proto3" json:"patch,omitempty"` + Prerelease string `protobuf:"bytes,5,opt,name=prerelease,proto3" json:"prerelease,omitempty"` + BuildMetadata string `protobuf:"bytes,6,opt,name=build_metadata,json=buildMetadata,proto3" json:"build_metadata,omitempty"` +} + +func (x *VersionResponse) Reset() { + *x = VersionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VersionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VersionResponse) ProtoMessage() {} + +func (x *VersionResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. +func (*VersionResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +func (x *VersionResponse) GetVersionString() string { + if x != nil { + return x.VersionString + } + return "" +} + +func (x *VersionResponse) GetMajor() uint32 { + if x != nil { + return x.Major + } + return 0 +} + +func (x *VersionResponse) GetMinor() uint32 { + if x != nil { + return x.Minor + } + return 0 +} + +func (x *VersionResponse) GetPatch() uint32 { + if x != nil { + return x.Patch + } + return 0 +} + +func (x *VersionResponse) GetPrerelease() string { + if x != nil { + return x.Prerelease + } + return "" +} + +func (x *VersionResponse) GetBuildMetadata() string { + if x != nil { + return x.BuildMetadata + } + return "" +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x72, 0x70, 0x63, + 0x22, 0x10, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, + 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, + 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x1e, 0x0a, 0x0a, 0x70, 0x72, 0x65, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x65, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, + 0x25, 0x0a, 0x0e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x32, 0x48, 0x0a, 0x10, 0x56, 0x69, 0x67, 0x69, 0x6c, 0x61, + 0x6e, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x70, 0x63, + 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x04, 0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_api_proto_goTypes = []interface{}{ + (*VersionRequest)(nil), // 0: rpc.VersionRequest + (*VersionResponse)(nil), // 1: rpc.VersionResponse +} +var file_api_proto_depIdxs = []int32{ + 0, // 0: rpc.VigilanteService.Version:input_type -> rpc.VersionRequest + 1, // 1: rpc.VigilanteService.Version:output_type -> rpc.VersionResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VersionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// VigilanteServiceClient is the client API for VigilanteService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type VigilanteServiceClient interface { + Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error) +} + +type vigilanteServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewVigilanteServiceClient(cc grpc.ClientConnInterface) VigilanteServiceClient { + return &vigilanteServiceClient{cc} +} + +func (c *vigilanteServiceClient) Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error) { + out := new(VersionResponse) + err := c.cc.Invoke(ctx, "/rpc.VigilanteService/Version", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// VigilanteServiceServer is the server API for VigilanteService service. +type VigilanteServiceServer interface { + Version(context.Context, *VersionRequest) (*VersionResponse, error) +} + +// UnimplementedVigilanteServiceServer can be embedded to have forward compatible implementations. +type UnimplementedVigilanteServiceServer struct { +} + +func (*UnimplementedVigilanteServiceServer) Version(context.Context, *VersionRequest) (*VersionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Version not implemented") +} + +func RegisterVigilanteServiceServer(s *grpc.Server, srv VigilanteServiceServer) { + s.RegisterService(&_VigilanteService_serviceDesc, srv) +} + +func _VigilanteService_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VersionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(VigilanteServiceServer).Version(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/rpc.VigilanteService/Version", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(VigilanteServiceServer).Version(ctx, req.(*VersionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _VigilanteService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "rpc.VigilanteService", + HandlerType: (*VigilanteServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Version", + Handler: _VigilanteService_Version_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/rpcserver/log.go b/rpcserver/log.go new file mode 100644 index 00000000..36e35662 --- /dev/null +++ b/rpcserver/log.go @@ -0,0 +1,81 @@ +// Copyright (c) 2015-2016 The btcsuite developers +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package rpcserver + +import ( + "os" + "strings" + + "google.golang.org/grpc/grpclog" + + "github.com/btcsuite/btclog" +) + +// UseLogger sets the logger to use for the gRPC server. +func UseLogger(l btclog.Logger) { + grpclog.SetLogger(logger{l}) // nolint:staticcheck +} + +// logger uses a btclog.Logger to implement the grpclog.Logger interface. +type logger struct { + btclog.Logger +} + +// stripGrpcPrefix removes the package prefix for all logs made to the grpc +// logger, since these are already included as the btclog subsystem name. +func stripGrpcPrefix(logstr string) string { + return strings.TrimPrefix(logstr, "grpc: ") +} + +// stripGrpcPrefixArgs removes the package prefix from the first argument, if it +// exists and is a string, returning the same arg slice after reassigning the +// first arg. +func stripGrpcPrefixArgs(args ...interface{}) []interface{} { + if len(args) == 0 { + return args + } + firstArgStr, ok := args[0].(string) + if ok { + args[0] = stripGrpcPrefix(firstArgStr) + } + return args +} + +func (l logger) Fatal(args ...interface{}) { + l.Critical(stripGrpcPrefixArgs(args)...) + os.Exit(1) +} + +func (l logger) Fatalf(format string, args ...interface{}) { + l.Criticalf(stripGrpcPrefix(format), args...) + os.Exit(1) +} + +func (l logger) Fatalln(args ...interface{}) { + l.Critical(stripGrpcPrefixArgs(args)...) + os.Exit(1) +} + +func (l logger) Print(args ...interface{}) { + l.Info(stripGrpcPrefixArgs(args)...) +} + +func (l logger) Printf(format string, args ...interface{}) { + l.Infof(stripGrpcPrefix(format), args...) +} + +func (l logger) Println(args ...interface{}) { + l.Info(stripGrpcPrefixArgs(args)...) +} diff --git a/rpcserver/regen.sh b/rpcserver/regen.sh new file mode 100755 index 00000000..3dc6149f --- /dev/null +++ b/rpcserver/regen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +protoc -I. --go_out=plugins=grpc:api api.proto diff --git a/rpcserver/server.go b/rpcserver/server.go new file mode 100644 index 00000000..42c3769f --- /dev/null +++ b/rpcserver/server.go @@ -0,0 +1,48 @@ +// Copyright (c) 2015-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +// Package rpcserver implements the RPC API and is used by the main package to +// start gRPC services. +// +// Full documentation of the API implemented by this package is maintained in a +// language-agnostic document: +// +// https://github.com/btcsuite/btcwallet/blob/master/rpc/documentation/api.md +// +// Any API changes must be performed according to the steps listed here: +// +// https://github.com/btcsuite/btcwallet/blob/master/rpc/documentation/serverchanges.md +package rpcserver + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" + + pb "github.com/babylonchain/vigilante/rpcserver/api" +) + +// Public API version constants +const ( + verString = "2.0.1" + verMajor = 2 + verMinor = 0 + verPatch = 1 +) + +type server struct{} + +// StartVigilanteService creates an implementation of the VigilanteService and +// registers it with the gRPC server. +func StartVigilanteService(gs *grpc.Server) { + pb.RegisterVigilanteServiceServer(gs, &server{}) +} + +func (*server) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) { + return &pb.VersionResponse{ + VersionString: verString, + Major: verMajor, + Minor: verMinor, + Patch: verPatch, + }, nil +} From 0ee86e3c991c60d8cc3ede5538edc467ce6dc7c3 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 4 Aug 2022 16:07:28 +1000 Subject: [PATCH 03/28] vigilantes --- netparams/babylon.go | 3 + netparams/bitcoin.go | 62 +++++ types/cfgutil/amount.go | 43 ---- types/cfgutil/explicitflags.go | 36 --- types/cfgutil/file.go | 19 -- types/cfgutil/normalization.go | 50 ---- types/rpchelp/genrpcserverhelp.go | 95 ------- types/rpchelp/helpdescs_en_US.go | 414 ------------------------------ types/rpchelp/methods.go | 81 ------ types/zero/array.go | 17 -- types/zero/benchmark_test.go | 87 ------- types/zero/doc.go | 3 - types/zero/slice.go | 34 --- types/zero/zero_test.go | 147 ----------- vigilante/README.md | 3 + vigilante/reporter.go | 157 +++++++++++ vigilante/submitter.go | 158 ++++++++++++ 17 files changed, 383 insertions(+), 1026 deletions(-) create mode 100644 netparams/babylon.go create mode 100644 netparams/bitcoin.go delete mode 100644 types/cfgutil/amount.go delete mode 100644 types/cfgutil/explicitflags.go delete mode 100644 types/cfgutil/file.go delete mode 100644 types/cfgutil/normalization.go delete mode 100644 types/rpchelp/genrpcserverhelp.go delete mode 100644 types/rpchelp/helpdescs_en_US.go delete mode 100644 types/rpchelp/methods.go delete mode 100644 types/zero/array.go delete mode 100644 types/zero/benchmark_test.go delete mode 100644 types/zero/doc.go delete mode 100644 types/zero/slice.go delete mode 100644 types/zero/zero_test.go create mode 100644 vigilante/README.md create mode 100644 vigilante/reporter.go create mode 100644 vigilante/submitter.go diff --git a/netparams/babylon.go b/netparams/babylon.go new file mode 100644 index 00000000..23f6e0ba --- /dev/null +++ b/netparams/babylon.go @@ -0,0 +1,3 @@ +package netparams + +// TODO: add Babylon parameters here diff --git a/netparams/bitcoin.go b/netparams/bitcoin.go new file mode 100644 index 00000000..d8300a0e --- /dev/null +++ b/netparams/bitcoin.go @@ -0,0 +1,62 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package netparams + +import ( + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" +) + +// BTCParams is used to group parameters for various networks such as the main +// network and test networks. +type BTCParams struct { + *chaincfg.Params + RPCClientPort string + RPCServerPort string +} + +// MainNetParams contains parameters specific running btcwallet and +// btcd on the main network (wire.MainNet). +var MainNetParams = BTCParams{ + Params: &chaincfg.MainNetParams, + RPCClientPort: "8334", + RPCServerPort: "8332", +} + +// TestNet3Params contains parameters specific running btcwallet and +// btcd on the test network (version 3) (wire.TestNet3). +var TestNet3Params = BTCParams{ + Params: &chaincfg.TestNet3Params, + RPCClientPort: "18334", + RPCServerPort: "18332", +} + +// SimNetParams contains parameters specific to the simulation test network +// (wire.SimNet). +var SimNetParams = BTCParams{ + Params: &chaincfg.SimNetParams, + RPCClientPort: "18556", + RPCServerPort: "18554", +} + +// SigNetParams contains parameters specific to the signet test network +// (wire.SigNet). +var SigNetParams = BTCParams{ + Params: &chaincfg.SigNetParams, + RPCClientPort: "38334", + RPCServerPort: "38332", +} + +// SigNetWire is a helper function that either returns the given chain +// parameter's net value if the parameter represents a signet network or 0 if +// it's not. This is necessary because there can be custom signet networks that +// have a different net value. +func SigNetWire(params *chaincfg.Params) wire.BitcoinNet { + if params.Name == chaincfg.SigNetParams.Name { + return params.Net + } + + return 0 +} diff --git a/types/cfgutil/amount.go b/types/cfgutil/amount.go deleted file mode 100644 index daf3c94f..00000000 --- a/types/cfgutil/amount.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2015-2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package cfgutil - -import ( - "strconv" - "strings" - - "github.com/btcsuite/btcd/btcutil" -) - -// AmountFlag embeds a btcutil.Amount and implements the flags.Marshaler and -// Unmarshaler interfaces so it can be used as a config struct field. -type AmountFlag struct { - btcutil.Amount -} - -// NewAmountFlag creates an AmountFlag with a default btcutil.Amount. -func NewAmountFlag(defaultValue btcutil.Amount) *AmountFlag { - return &AmountFlag{defaultValue} -} - -// MarshalFlag satisfies the flags.Marshaler interface. -func (a *AmountFlag) MarshalFlag() (string, error) { - return a.Amount.String(), nil -} - -// UnmarshalFlag satisfies the flags.Unmarshaler interface. -func (a *AmountFlag) UnmarshalFlag(value string) error { - value = strings.TrimSuffix(value, " BTC") - valueF64, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } - amount, err := btcutil.NewAmount(valueF64) - if err != nil { - return err - } - a.Amount = amount - return nil -} diff --git a/types/cfgutil/explicitflags.go b/types/cfgutil/explicitflags.go deleted file mode 100644 index 331e6099..00000000 --- a/types/cfgutil/explicitflags.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2016 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package cfgutil - -// ExplicitString is a string value implementing the flags.Marshaler and -// flags.Unmarshaler interfaces so it may be used as a config struct field. It -// records whether the value was explicitly set by the flags package. This is -// useful when behavior must be modified depending on whether a flag was set by -// the user or left as a default. Without recording this, it would be -// impossible to determine whether flag with a default value was unmodified or -// explicitly set to the default. -type ExplicitString struct { - Value string - explicitlySet bool -} - -// NewExplicitString creates a string flag with the provided default value. -func NewExplicitString(defaultValue string) *ExplicitString { - return &ExplicitString{Value: defaultValue, explicitlySet: false} -} - -// ExplicitlySet returns whether the flag was explicitly set through the -// flags.Unmarshaler interface. -func (e *ExplicitString) ExplicitlySet() bool { return e.explicitlySet } - -// MarshalFlag implements the flags.Marshaler interface. -func (e *ExplicitString) MarshalFlag() (string, error) { return e.Value, nil } - -// UnmarshalFlag implements the flags.Unmarshaler interface. -func (e *ExplicitString) UnmarshalFlag(value string) error { - e.Value = value - e.explicitlySet = true - return nil -} diff --git a/types/cfgutil/file.go b/types/cfgutil/file.go deleted file mode 100644 index 02495642..00000000 --- a/types/cfgutil/file.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package cfgutil - -import "os" - -// FileExists reports whether the named file or directory exists. -func FileExists(filePath string) (bool, error) { - _, err := os.Stat(filePath) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - return true, nil -} diff --git a/types/cfgutil/normalization.go b/types/cfgutil/normalization.go deleted file mode 100644 index dc67a21e..00000000 --- a/types/cfgutil/normalization.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package cfgutil - -import "net" - -// NormalizeAddress returns the normalized form of the address, adding a default -// port if necessary. An error is returned if the address, even without a port, -// is not valid. -func NormalizeAddress(addr string, defaultPort string) (hostport string, err error) { - // If the first SplitHostPort errors because of a missing port and not - // for an invalid host, add the port. If the second SplitHostPort - // fails, then a port is not missing and the original error should be - // returned. - host, port, origErr := net.SplitHostPort(addr) - if origErr == nil { - return net.JoinHostPort(host, port), nil - } - addr = net.JoinHostPort(addr, defaultPort) - _, _, err = net.SplitHostPort(addr) - if err != nil { - return "", origErr - } - return addr, nil -} - -// NormalizeAddresses returns a new slice with all the passed peer addresses -// normalized with the given default port, and all duplicates removed. -func NormalizeAddresses(addrs []string, defaultPort string) ([]string, error) { - var ( - normalized = make([]string, 0, len(addrs)) - seenSet = make(map[string]struct{}) - ) - - for _, addr := range addrs { - normalizedAddr, err := NormalizeAddress(addr, defaultPort) - if err != nil { - return nil, err - } - _, seen := seenSet[normalizedAddr] - if !seen { - normalized = append(normalized, normalizedAddr) - seenSet[normalizedAddr] = struct{}{} - } - } - - return normalized, nil -} diff --git a/types/rpchelp/genrpcserverhelp.go b/types/rpchelp/genrpcserverhelp.go deleted file mode 100644 index 9a9c24bb..00000000 --- a/types/rpchelp/genrpcserverhelp.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -//go:build generate -// +build generate - -package main - -import ( - "fmt" - "log" - "os" - "strings" - - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcwallet/internal/rpchelp" -) - -var outputFile = func() *os.File { - fi, err := os.Create("rpcserverhelp.go") - if err != nil { - log.Fatal(err) - } - return fi -}() - -func writefln(format string, args ...interface{}) { - _, err := fmt.Fprintf(outputFile, format, args...) - if err != nil { - log.Fatal(err) - } - _, err = outputFile.Write([]byte{'\n'}) - if err != nil { - log.Fatal(err) - } -} - -func writeLocaleHelp(locale, goLocale string, descs map[string]string) { - funcName := "helpDescs" + goLocale - writefln("func %s() map[string]string {", funcName) - writefln("return map[string]string{") - for i := range rpchelp.Methods { - m := &rpchelp.Methods[i] - helpText, err := btcjson.GenerateHelp(m.Method, descs, m.ResultTypes...) - if err != nil { - log.Fatal(err) - } - writefln("%q: %q,", m.Method, helpText) - } - writefln("}") - writefln("}") -} - -func writeLocales() { - writefln("var localeHelpDescs = map[string]func() map[string]string{") - for _, h := range rpchelp.HelpDescs { - writefln("%q: helpDescs%s,", h.Locale, h.GoLocale) - } - writefln("}") -} - -func writeUsage() { - usageStrs := make([]string, len(rpchelp.Methods)) - var err error - for i := range rpchelp.Methods { - usageStrs[i], err = btcjson.MethodUsageText(rpchelp.Methods[i].Method) - if err != nil { - log.Fatal(err) - } - } - usages := strings.Join(usageStrs, "\n") - writefln("var requestUsages = %q", usages) -} - -func main() { - defer outputFile.Close() - - packageName := "main" - if len(os.Args) > 1 { - packageName = os.Args[1] - } - - writefln("// AUTOGENERATED by internal/rpchelp/genrpcserverhelp.go; do not edit.") - writefln("") - writefln("package %s", packageName) - writefln("") - for _, h := range rpchelp.HelpDescs { - writeLocaleHelp(h.Locale, h.GoLocale, h.Descs) - writefln("") - } - writeLocales() - writefln("") - writeUsage() -} diff --git a/types/rpchelp/helpdescs_en_US.go b/types/rpchelp/helpdescs_en_US.go deleted file mode 100644 index 0eab5816..00000000 --- a/types/rpchelp/helpdescs_en_US.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -//go:build !generate -// +build !generate - -package rpchelp - -var helpDescsEnUS = map[string]string{ - // AddMultisigAddressCmd help. - "addmultisigaddress--synopsis": "Generates and imports a multisig address and redeeming script to the 'imported' account.", - "addmultisigaddress-account": "DEPRECATED -- Unused (all imported addresses belong to the imported account)", - "addmultisigaddress-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address", - "addmultisigaddress-nrequired": "The number of signatures required to redeem outputs paid to this address", - "addmultisigaddress--result0": "The imported pay-to-script-hash address", - - // CreateMultisigCmd help. - "createmultisig--synopsis": "Generate a multisig address and redeem script.", - "createmultisig-keys": "Pubkeys and/or pay-to-pubkey-hash addresses to partially control the multisig address", - "createmultisig-nrequired": "The number of signatures required to redeem outputs paid to this address", - - // CreateMultisigResult help. - "createmultisigresult-address": "The generated pay-to-script-hash address", - "createmultisigresult-redeemScript": "The script required to redeem outputs paid to the multisig address", - - // DumpPrivKeyCmd help. - "dumpprivkey--synopsis": "Returns the private key in WIF encoding that controls some wallet address.", - "dumpprivkey-address": "The address to return a private key for", - "dumpprivkey--result0": "The WIF-encoded private key", - - // GetAccountCmd help. - "getaccount--synopsis": "DEPRECATED -- Lookup the account name that some wallet address belongs to.", - "getaccount-address": "The address to query the account for", - "getaccount--result0": "The name of the account that 'address' belongs to", - - // GetAccountAddressCmd help. - "getaccountaddress--synopsis": "DEPRECATED -- Returns the most recent external payment address for an account that has not been seen publicly.\n" + - "A new address is generated for the account if the most recently generated address has been seen on the blockchain or in mempool.", - "getaccountaddress-account": "The account of the returned address", - "getaccountaddress--result0": "The unused address for 'account'", - - // GetAddressesByAccountCmd help. - "getaddressesbyaccount--synopsis": "DEPRECATED -- Returns all addresses strings controlled by a single account.", - "getaddressesbyaccount-account": "Account name to fetch addresses for", - "getaddressesbyaccount--result0": "All addresses controlled by 'account'", - - // GetBalanceCmd help. - "getbalance--synopsis": "Calculates and returns the balance of one or all accounts.", - "getbalance-minconf": "Minimum number of block confirmations required before an unspent output's value is included in the balance", - "getbalance-account": "DEPRECATED -- The account name to query the balance for, or \"*\" to consider all accounts (default=\"*\")", - "getbalance--condition0": "account != \"*\"", - "getbalance--condition1": "account = \"*\"", - "getbalance--result0": "The balance of 'account' valued in bitcoin", - "getbalance--result1": "The balance of all accounts valued in bitcoin", - - // GetBestBlockHashCmd help. - "getbestblockhash--synopsis": "Returns the hash of the newest block in the best chain that wallet has finished syncing with.", - "getbestblockhash--result0": "The hash of the most recent synced-to block", - - // GetBlockCountCmd help. - "getblockcount--synopsis": "Returns the blockchain height of the newest block in the best chain that wallet has finished syncing with.", - "getblockcount--result0": "The blockchain height of the most recent synced-to block", - - // GetInfoCmd help. - "getinfo--synopsis": "Returns a JSON object containing various state info.", - - // InfoWalletResult help. - "infowalletresult-version": "The version of the server", - "infowalletresult-protocolversion": "The latest supported protocol version", - "infowalletresult-blocks": "The number of blocks processed", - "infowalletresult-timeoffset": "The time offset", - "infowalletresult-connections": "The number of connected peers", - "infowalletresult-proxy": "The proxy used by the server", - "infowalletresult-difficulty": "The current target difficulty", - "infowalletresult-testnet": "Whether or not server is using testnet", - "infowalletresult-relayfee": "The minimum relay fee for non-free transactions in BTC/KB", - "infowalletresult-errors": "Any current errors", - "infowalletresult-paytxfee": "The increment used each time more fee is required for an authored transaction", - "infowalletresult-balance": "The balance of all accounts calculated with one block confirmation", - "infowalletresult-walletversion": "The version of the address manager database", - "infowalletresult-unlocked_until": "Unset", - "infowalletresult-keypoolsize": "Unset", - "infowalletresult-keypoololdest": "Unset", - - // GetNewAddressCmd help. - "getnewaddress--synopsis": "Generates and returns a new payment address.", - "getnewaddress-account": "DEPRECATED -- Account name the new address will belong to (default=\"default\")", - "getnewaddress-addresstype": "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")", - "getnewaddress--result0": "The payment address", - - // GetRawChangeAddressCmd help. - "getrawchangeaddress--synopsis": "Generates and returns a new internal payment address for use as a change address in raw transactions.", - "getrawchangeaddress-account": "Account name the new internal address will belong to (default=\"default\")", - "getrawchangeaddress-addresstype": "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".(default=\"legacy\")", - "getrawchangeaddress--result0": "The internal payment address", - - // GetReceivedByAccountCmd help. - "getreceivedbyaccount--synopsis": "DEPRECATED -- Returns the total amount received by addresses of some account, including spent outputs.", - "getreceivedbyaccount-account": "Account name to query total received amount for", - "getreceivedbyaccount-minconf": "Minimum number of block confirmations required before an output's value is included in the total", - "getreceivedbyaccount--result0": "The total received amount valued in bitcoin", - - // GetReceivedByAddressCmd help. - "getreceivedbyaddress--synopsis": "Returns the total amount received by a single address, including spent outputs.", - "getreceivedbyaddress-address": "Payment address which received outputs to include in total", - "getreceivedbyaddress-minconf": "Minimum number of block confirmations required before an output's value is included in the total", - "getreceivedbyaddress--result0": "The total received amount valued in bitcoin", - - // GetTransactionCmd help. - "gettransaction--synopsis": "Returns a JSON object with details regarding a transaction relevant to this wallet.", - "gettransaction-txid": "Hash of the transaction to query", - "gettransaction-includewatchonly": "Also consider transactions involving watched addresses", - - // HelpCmd help. - "help--synopsis": "Returns a list of all commands or help for a specified command.", - "help-command": "The command to retrieve help for", - "help--condition0": "no command provided", - "help--condition1": "command specified", - "help--result0": "List of commands", - "help--result1": "Help for specified command", - - // GetTransactionResult help. - "gettransactionresult-amount": "The total amount this transaction credits to the wallet, valued in bitcoin", - "gettransactionresult-fee": "The total input value minus the total output value, or 0 if 'txid' is not a sent transaction", - "gettransactionresult-confirmations": "The number of block confirmations of the transaction", - "gettransactionresult-blockhash": "The hash of the block this transaction is mined in, or the empty string if unmined", - "gettransactionresult-blockindex": "Unset", - "gettransactionresult-blocktime": "The Unix time of the block header this transaction is mined in, or 0 if unmined", - "gettransactionresult-txid": "The transaction hash", - "gettransactionresult-walletconflicts": "Unset", - "gettransactionresult-time": "The earliest Unix time this transaction was known to exist", - "gettransactionresult-timereceived": "The earliest Unix time this transaction was known to exist", - "gettransactionresult-details": "Additional details for each recorded wallet credit and debit", - "gettransactionresult-hex": "The transaction encoded as a hexadecimal string", - - // GetTransactionDetailsResult help. - "gettransactiondetailsresult-account": "DEPRECATED -- Unset", - "gettransactiondetailsresult-address": "The address an output was paid to, or the empty string if the output is nonstandard or this detail is regarding a transaction input", - "gettransactiondetailsresult-category": `The kind of detail: "send" for sent transactions, "immature" for immature coinbase outputs, "generate" for mature coinbase outputs, or "recv" for all other received outputs`, - "gettransactiondetailsresult-amount": "The amount of a received output", - "gettransactiondetailsresult-fee": "The included fee for a sent transaction", - "gettransactiondetailsresult-vout": "The transaction output index", - "gettransactiondetailsresult-involveswatchonly": "Unset", - - // ImportPrivKeyCmd help. - "importprivkey--synopsis": "Imports a WIF-encoded private key to the 'imported' account.", - "importprivkey-privkey": "The WIF-encoded private key", - "importprivkey-label": "Unused (must be unset or 'imported')", - "importprivkey-rescan": "Rescan the blockchain (since the genesis block) for outputs controlled by the imported key", - - // KeypoolRefillCmd help. - "keypoolrefill--synopsis": "DEPRECATED -- This request does nothing since no keypool is maintained.", - "keypoolrefill-newsize": "Unused", - - // ListAccountsCmd help. - "listaccounts--synopsis": "DEPRECATED -- Returns a JSON object of all accounts and their balances.", - "listaccounts-minconf": "Minimum number of block confirmations required before an unspent output's value is included in the balance", - "listaccounts--result0--desc": "JSON object with account names as keys and bitcoin amounts as values", - "listaccounts--result0--key": "The account name", - "listaccounts--result0--value": "The account balance valued in bitcoin", - - // ListLockUnspentCmd help. - "listlockunspent--synopsis": "Returns a JSON array of outpoints marked as locked (with lockunspent) for this wallet session.", - - // TransactionInput help. - "transactioninput-txid": "The transaction hash of the referenced output", - "transactioninput-vout": "The output index of the referenced output", - - // ListReceivedByAccountCmd help. - "listreceivedbyaccount--synopsis": "DEPRECATED -- Returns a JSON array of objects listing all accounts and the total amount received by each account.", - "listreceivedbyaccount-minconf": "Minimum number of block confirmations required before a transaction is considered", - "listreceivedbyaccount-includeempty": "Unused", - "listreceivedbyaccount-includewatchonly": "Unused", - - // ListReceivedByAccountResult help. - "listreceivedbyaccountresult-account": "The name of the account", - "listreceivedbyaccountresult-amount": "Total amount received by payment addresses of the account valued in bitcoin", - "listreceivedbyaccountresult-confirmations": "Number of block confirmations of the most recent transaction relevant to the account", - - // ListReceivedByAddressCmd help. - "listreceivedbyaddress--synopsis": "Returns a JSON array of objects listing wallet payment addresses and their total received amounts.", - "listreceivedbyaddress-minconf": "Minimum number of block confirmations required before a transaction is considered", - "listreceivedbyaddress-includeempty": "Unused", - "listreceivedbyaddress-includewatchonly": "Unused", - - // ListReceivedByAddressResult help. - "listreceivedbyaddressresult-account": "DEPRECATED -- Unset", - "listreceivedbyaddressresult-address": "The payment address", - "listreceivedbyaddressresult-amount": "Total amount received by the payment address valued in bitcoin", - "listreceivedbyaddressresult-confirmations": "Number of block confirmations of the most recent transaction relevant to the address", - "listreceivedbyaddressresult-txids": "Transaction hashes of all transactions involving this address", - "listreceivedbyaddressresult-involvesWatchonly": "Unset", - - // ListSinceBlockCmd help. - "listsinceblock--synopsis": "Returns a JSON array of objects listing details of all wallet transactions after some block.", - "listsinceblock-blockhash": "Hash of the parent block of the first block to consider transactions from, or unset to list all transactions", - "listsinceblock-targetconfirmations": "Minimum number of block confirmations of the last block in the result object. Must be 1 or greater. Note: The transactions array in the result object is not affected by this parameter", - "listsinceblock-includewatchonly": "Unused", - "listsinceblock--condition0": "blockhash specified", - "listsinceblock--condition1": "no blockhash specified", - "listsinceblock--result0": "Lists all transactions, including unmined transactions, since the specified block", - "listsinceblock--result1": "Lists all transactions since the genesis block", - - // ListSinceBlockResult help. - "listsinceblockresult-transactions": "JSON array of objects containing verbose details of the each transaction", - "listsinceblockresult-lastblock": "Hash of the latest-synced block to be used in later calls to listsinceblock", - - // ListTransactionsResult help. - "listtransactionsresult-account": "DEPRECATED -- Unset", - "listtransactionsresult-address": "Payment address for a transaction output", - "listtransactionsresult-category": `The kind of transaction: "send" for sent transactions, "immature" for immature coinbase outputs, "generate" for mature coinbase outputs, or "recv" for all other received outputs. Note: A single output may be included multiple times under different categories`, - "listtransactionsresult-amount": "The value of the transaction output valued in bitcoin", - "listtransactionsresult-fee": "The total input value minus the total output value for sent transactions", - "listtransactionsresult-confirmations": "The number of block confirmations of the transaction", - "listtransactionsresult-generated": "Whether the transaction output is a coinbase output", - "listtransactionsresult-blockhash": "The hash of the block this transaction is mined in, or the empty string if unmined", - "listtransactionsresult-blockheight": "The block height containing the transaction.", - "listtransactionsresult-blockindex": "Unset", - "listtransactionsresult-blocktime": "The Unix time of the block header this transaction is mined in, or 0 if unmined", - "listtransactionsresult-label": "A comment for the address/transaction, if any", - "listtransactionsresult-txid": "The hash of the transaction", - "listtransactionsresult-vout": "The transaction output index", - "listtransactionsresult-walletconflicts": "Unset", - "listtransactionsresult-time": "The earliest Unix time this transaction was known to exist", - "listtransactionsresult-timereceived": "The earliest Unix time this transaction was known to exist", - "listtransactionsresult-involveswatchonly": "Unset", - "listtransactionsresult-comment": "Unset", - "listtransactionsresult-otheraccount": "Unset", - "listtransactionsresult-trusted": "Unset", - "listtransactionsresult-bip125-replaceable": "Unset", - "listtransactionsresult-abandoned": "Unset", - - // ListTransactionsCmd help. - "listtransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions.", - "listtransactions-account": "DEPRECATED -- Unused (must be unset or \"*\")", - "listtransactions-count": "Maximum number of transactions to create results from", - "listtransactions-from": "Number of transactions to skip before results are created", - "listtransactions-includewatchonly": "Unused", - - // ListUnspentCmd help. - "listunspent--synopsis": "Returns a JSON array of objects representing unlocked unspent outputs controlled by wallet keys.", - "listunspent-minconf": "Minimum number of block confirmations required before a transaction output is considered", - "listunspent-maxconf": "Maximum number of block confirmations required before a transaction output is excluded", - "listunspent-addresses": "If set, limits the returned details to unspent outputs received by any of these payment addresses", - - // ListUnspentResult help. - "listunspentresult-txid": "The transaction hash of the referenced output", - "listunspentresult-vout": "The output index of the referenced output", - "listunspentresult-address": "The payment address that received the output", - "listunspentresult-account": "The account associated with the receiving payment address", - "listunspentresult-scriptPubKey": "The output script encoded as a hexadecimal string", - "listunspentresult-redeemScript": "Unset", - "listunspentresult-amount": "The amount of the output valued in bitcoin", - "listunspentresult-confirmations": "The number of block confirmations of the transaction", - "listunspentresult-spendable": "Whether the output is entirely controlled by wallet keys/scripts (false for partially controlled multisig outputs or outputs to watch-only addresses)", - - // LockUnspentCmd help. - "lockunspent--synopsis": "Locks or unlocks an unspent output.\n" + - "Locked outputs are not chosen for transaction inputs of authored transactions and are not included in 'listunspent' results.\n" + - "Locked outputs are volatile and are not saved across wallet restarts.\n" + - "If unlock is true and no transaction outputs are specified, all locked outputs are marked unlocked.", - "lockunspent-unlock": "True to unlock outputs, false to lock", - "lockunspent-transactions": "Transaction outputs to lock or unlock", - "lockunspent--result0": "The boolean 'true'", - - // SendFromCmd help. - "sendfrom--synopsis": "DEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + - "A change output is automatically included to send extra output value back to the original account.", - "sendfrom-fromaccount": "Account to pick unspent outputs from", - "sendfrom-toaddress": "Address to pay", - "sendfrom-amount": "Amount to send to the payment address valued in bitcoin", - "sendfrom-minconf": "Minimum number of block confirmations required before a transaction output is eligible to be spent", - "sendfrom-comment": "Unused", - "sendfrom-commentto": "Unused", - "sendfrom--result0": "The transaction hash of the sent transaction", - - // SendManyCmd help. - "sendmany--synopsis": "Authors, signs, and sends a transaction that outputs to many payment addresses.\n" + - "A change output is automatically included to send extra output value back to the original account.", - "sendmany-fromaccount": "DEPRECATED -- Account to pick unspent outputs from", - "sendmany-amounts": "Pairs of payment addresses and the output amount to pay each", - "sendmany-amounts--desc": "JSON object using payment addresses as keys and output amounts valued in bitcoin to send to each address", - "sendmany-amounts--key": "Address to pay", - "sendmany-amounts--value": "Amount to send to the payment address valued in bitcoin", - "sendmany-minconf": "Minimum number of block confirmations required before a transaction output is eligible to be spent", - "sendmany-comment": "Unused", - "sendmany--result0": "The transaction hash of the sent transaction", - - // SendToAddressCmd help. - "sendtoaddress--synopsis": "Authors, signs, and sends a transaction that outputs some amount to a payment address.\n" + - "Unlike sendfrom, outputs are always chosen from the default account.\n" + - "A change output is automatically included to send extra output value back to the original account.", - "sendtoaddress-address": "Address to pay", - "sendtoaddress-amount": "Amount to send to the payment address valued in bitcoin", - "sendtoaddress-comment": "Unused", - "sendtoaddress-commentto": "Unused", - "sendtoaddress--result0": "The transaction hash of the sent transaction", - - // SetTxFeeCmd help. - "settxfee--synopsis": "Modify the increment used each time more fee is required for an authored transaction.", - "settxfee-amount": "The new fee increment valued in bitcoin", - "settxfee--result0": "The boolean 'true'", - - // SignMessageCmd help. - "signmessage--synopsis": "Signs a message using the private key of a payment address.", - "signmessage-address": "Payment address of private key used to sign the message with", - "signmessage-message": "Message to sign", - "signmessage--result0": "The signed message encoded as a base64 string", - - // SignRawTransactionCmd help. - "signrawtransaction--synopsis": "Signs transaction inputs using private keys from this wallet and request.\n" + - "The valid flags options are ALL, NONE, SINGLE, ALL|ANYONECANPAY, NONE|ANYONECANPAY, and SINGLE|ANYONECANPAY.", - "signrawtransaction-rawtx": "Unsigned or partially unsigned transaction to sign encoded as a hexadecimal string", - "signrawtransaction-inputs": "Additional data regarding inputs that this wallet may not be tracking", - "signrawtransaction-privkeys": "Additional WIF-encoded private keys to use when creating signatures", - "signrawtransaction-flags": "Sighash flags", - - // SignRawTransactionResult help. - "signrawtransactionresult-hex": "The resulting transaction encoded as a hexadecimal string", - "signrawtransactionresult-complete": "Whether all input signatures have been created", - "signrawtransactionresult-errors": "Script verification errors (if exists)", - - // SignRawTransactionError help. - "signrawtransactionerror-error": "Verification or signing error related to the input", - "signrawtransactionerror-sequence": "Script sequence number", - "signrawtransactionerror-scriptSig": "The hex-encoded signature script", - "signrawtransactionerror-txid": "The transaction hash of the referenced previous output", - "signrawtransactionerror-vout": "The output index of the referenced previous output", - - // ValidateAddressCmd help. - "validateaddress--synopsis": "Verify that an address is valid.\n" + - "Extra details are returned if the address is controlled by this wallet.\n" + - "The following fields are valid only when the address is controlled by this wallet (ismine=true): isscript, pubkey, iscompressed, account, addresses, hex, script, and sigsrequired.\n" + - "The following fields are only valid when address has an associated public key: pubkey, iscompressed.\n" + - "The following fields are only valid when address is a pay-to-script-hash address: addresses, hex, and script.\n" + - "If the address is a multisig address controlled by this wallet, the multisig fields will be left unset if the wallet is locked since the redeem script cannot be decrypted.", - "validateaddress-address": "Address to validate", - - // ValidateAddressWalletResult help. - "validateaddresswalletresult-isvalid": "Whether or not the address is valid", - "validateaddresswalletresult-address": "The payment address (only when isvalid is true)", - "validateaddresswalletresult-ismine": "Whether this address is controlled by the wallet (only when isvalid is true)", - "validateaddresswalletresult-iswatchonly": "Unset", - "validateaddresswalletresult-isscript": "Whether the payment address is a pay-to-script-hash address (only when isvalid is true)", - "validateaddresswalletresult-pubkey": "The associated public key of the payment address, if any (only when isvalid is true)", - "validateaddresswalletresult-iscompressed": "Whether the address was created by hashing a compressed public key, if any (only when isvalid is true)", - "validateaddresswalletresult-account": "The account this payment address belongs to (only when isvalid is true)", - "validateaddresswalletresult-addresses": "All associated payment addresses of the script if address is a multisig address (only when isvalid is true)", - "validateaddresswalletresult-hex": "The redeem script ", - "validateaddresswalletresult-script": "The class of redeem script for a multisig address", - "validateaddresswalletresult-sigsrequired": "The number of required signatures to redeem outputs to the multisig address", - - // VerifyMessageCmd help. - "verifymessage--synopsis": "Verify a message was signed with the associated private key of some address.", - "verifymessage-address": "Address used to sign message", - "verifymessage-signature": "The signature to verify", - "verifymessage-message": "The message to verify", - "verifymessage--result0": "Whether the message was signed with the private key of 'address'", - - // WalletLockCmd help. - "walletlock--synopsis": "Lock the wallet.", - - // WalletPassphraseCmd help. - "walletpassphrase--synopsis": "Unlock the wallet.", - "walletpassphrase-passphrase": "The wallet passphrase", - "walletpassphrase-timeout": "The number of seconds to wait before the wallet automatically locks", - - // WalletPassphraseChangeCmd help. - "walletpassphrasechange--synopsis": "Change the wallet passphrase.", - "walletpassphrasechange-oldpassphrase": "The old wallet passphrase", - "walletpassphrasechange-newpassphrase": "The new wallet passphrase", - - // CreateNewAccountCmd help. - "createnewaccount--synopsis": "Creates a new account.\n" + - "The wallet must be unlocked for this request to succeed.", - "createnewaccount-account": "Name of the new account", - - // ExportWatchingWalletCmd help. - "exportwatchingwallet--synopsis": "Creates and returns a duplicate of the wallet database without any private keys to be used as a watching-only wallet.", - "exportwatchingwallet-account": "Unused (must be unset or \"*\")", - "exportwatchingwallet-download": "Unused", - "exportwatchingwallet--result0": "The watching-only database encoded as a base64 string", - - // GetBestBlockCmd help. - "getbestblock--synopsis": "Returns the hash and height of the newest block in the best chain that wallet has finished syncing with.", - - // GetBestBlockResult help. - "getbestblockresult-hash": "The hash of the block", - "getbestblockresult-height": "The blockchain height of the block", - - // GetUnconfirmedBalanceCmd help. - "getunconfirmedbalance--synopsis": "Calculates the unspent output value of all unmined transaction outputs for an account.", - "getunconfirmedbalance-account": "The account to query the unconfirmed balance for (default=\"default\")", - "getunconfirmedbalance--result0": "Total amount of all unmined unspent outputs of the account valued in bitcoin.", - - // ListAddressTransactionsCmd help. - "listaddresstransactions--synopsis": "Returns a JSON array of objects containing verbose details for wallet transactions pertaining some addresses.", - "listaddresstransactions-addresses": "Addresses to filter transaction results by", - "listaddresstransactions-account": "Unused (must be unset or \"*\")", - - // ListAllTransactionsCmd help. - "listalltransactions--synopsis": "Returns a JSON array of objects in the same format as 'listtransactions' without limiting the number of returned objects.", - "listalltransactions-account": "Unused (must be unset or \"*\")", - - // RenameAccountCmd help. - "renameaccount--synopsis": "Renames an account.", - "renameaccount-oldaccount": "The old account name to rename", - "renameaccount-newaccount": "The new name for the account", - - // WalletIsLockedCmd help. - "walletislocked--synopsis": "Returns whether or not the wallet is locked.", - "walletislocked--result0": "Whether the wallet is locked", -} diff --git a/types/rpchelp/methods.go b/types/rpchelp/methods.go deleted file mode 100644 index ce037f72..00000000 --- a/types/rpchelp/methods.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -//go:build !generate -// +build !generate - -package rpchelp - -import "github.com/btcsuite/btcd/btcjson" - -// Common return types. -var ( - returnsBool = []interface{}{(*bool)(nil)} - returnsNumber = []interface{}{(*float64)(nil)} - returnsString = []interface{}{(*string)(nil)} - returnsStringArray = []interface{}{(*[]string)(nil)} - returnsLTRArray = []interface{}{(*[]btcjson.ListTransactionsResult)(nil)} -) - -// Methods contains all methods and result types that help is generated for, -// for every locale. -var Methods = []struct { - Method string - ResultTypes []interface{} -}{ - {"addmultisigaddress", returnsString}, - {"createmultisig", []interface{}{(*btcjson.CreateMultiSigResult)(nil)}}, - {"dumpprivkey", returnsString}, - {"getaccount", returnsString}, - {"getaccountaddress", returnsString}, - {"getaddressesbyaccount", returnsStringArray}, - {"getbalance", append(returnsNumber, returnsNumber[0])}, - {"getbestblockhash", returnsString}, - {"getblockcount", returnsNumber}, - {"getinfo", []interface{}{(*btcjson.InfoWalletResult)(nil)}}, - {"getnewaddress", returnsString}, - {"getrawchangeaddress", returnsString}, - {"getreceivedbyaccount", returnsNumber}, - {"getreceivedbyaddress", returnsNumber}, - {"gettransaction", []interface{}{(*btcjson.GetTransactionResult)(nil)}}, - {"help", append(returnsString, returnsString[0])}, - {"importprivkey", nil}, - {"keypoolrefill", nil}, - {"listaccounts", []interface{}{(*map[string]float64)(nil)}}, - {"listlockunspent", []interface{}{(*[]btcjson.TransactionInput)(nil)}}, - {"listreceivedbyaccount", []interface{}{(*[]btcjson.ListReceivedByAccountResult)(nil)}}, - {"listreceivedbyaddress", []interface{}{(*[]btcjson.ListReceivedByAddressResult)(nil)}}, - {"listsinceblock", []interface{}{(*btcjson.ListSinceBlockResult)(nil)}}, - {"listtransactions", returnsLTRArray}, - {"listunspent", []interface{}{(*btcjson.ListUnspentResult)(nil)}}, - {"lockunspent", returnsBool}, - {"sendfrom", returnsString}, - {"sendmany", returnsString}, - {"sendtoaddress", returnsString}, - {"settxfee", returnsBool}, - {"signmessage", returnsString}, - {"signrawtransaction", []interface{}{(*btcjson.SignRawTransactionResult)(nil)}}, - {"validateaddress", []interface{}{(*btcjson.ValidateAddressWalletResult)(nil)}}, - {"verifymessage", returnsBool}, - {"walletlock", nil}, - {"walletpassphrase", nil}, - {"walletpassphrasechange", nil}, - {"createnewaccount", nil}, - {"exportwatchingwallet", returnsString}, - {"getbestblock", []interface{}{(*btcjson.GetBestBlockResult)(nil)}}, - {"getunconfirmedbalance", returnsNumber}, - {"listaddresstransactions", returnsLTRArray}, - {"listalltransactions", returnsLTRArray}, - {"renameaccount", nil}, - {"walletislocked", returnsBool}, -} - -// HelpDescs contains the locale-specific help strings along with the locale. -var HelpDescs = []struct { - Locale string // Actual locale, e.g. en_US - GoLocale string // Locale used in Go names, e.g. EnUS - Descs map[string]string -}{ - {"en_US", "EnUS", helpDescsEnUS}, // helpdescs_en_US.go -} diff --git a/types/zero/array.go b/types/zero/array.go deleted file mode 100644 index 79077aca..00000000 --- a/types/zero/array.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package zero - -// Bytea32 clears the 32-byte array by filling it with the zero value. -// This is used to explicitly clear private key material from memory. -func Bytea32(b *[32]byte) { - *b = [32]byte{} -} - -// Bytea64 clears the 64-byte array by filling it with the zero value. -// This is used to explicitly clear sensitive material from memory. -func Bytea64(b *[64]byte) { - *b = [64]byte{} -} diff --git a/types/zero/benchmark_test.go b/types/zero/benchmark_test.go deleted file mode 100644 index 37d1b95b..00000000 --- a/types/zero/benchmark_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package zero_test - -import ( - "testing" - - "github.com/babylonchain/vigilante/types/zero" -) - -var ( - bytes32 = make([]byte, 32) // typical key size - bytes64 = make([]byte, 64) // passphrase hash size - bytea32 = new([32]byte) - bytea64 = new([64]byte) -) - -// xor is the "slow" byte zeroing implementation which this package -// originally replaced. If this function benchmarks faster than the -// functions exported by this package in a future Go version (perhaps -// by calling runtime.memclr), replace the "optimized" versions with -// this. -func xor(b []byte) { - for i := range b { - b[i] ^= b[i] - } -} - -// zrange is an alternative zero implementation that, while currently -// slower than the functions provided by this package, may be faster -// in a future Go release. Switch to this or the xor implementation -// if they ever become faster. -func zrange(b []byte) { - for i := range b { - b[i] = 0 - } -} - -func BenchmarkXor32(b *testing.B) { - for i := 0; i < b.N; i++ { - xor(bytes32) - } -} - -func BenchmarkXor64(b *testing.B) { - for i := 0; i < b.N; i++ { - xor(bytes64) - } -} - -func BenchmarkRange32(b *testing.B) { - for i := 0; i < b.N; i++ { - zrange(bytes32) - } -} - -func BenchmarkRange64(b *testing.B) { - for i := 0; i < b.N; i++ { - zrange(bytes64) - } -} - -func BenchmarkBytes32(b *testing.B) { - for i := 0; i < b.N; i++ { - zero.Bytes(bytes32) - } -} - -func BenchmarkBytes64(b *testing.B) { - for i := 0; i < b.N; i++ { - zero.Bytes(bytes64) - } -} - -func BenchmarkBytea32(b *testing.B) { - for i := 0; i < b.N; i++ { - zero.Bytea32(bytea32) - } -} - -func BenchmarkBytea64(b *testing.B) { - for i := 0; i < b.N; i++ { - zero.Bytea64(bytea64) - } -} diff --git a/types/zero/doc.go b/types/zero/doc.go deleted file mode 100644 index 457b8e3b..00000000 --- a/types/zero/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package zero contains functions to clear data from byte slices and -// multi-precision integers. -package zero diff --git a/types/zero/slice.go b/types/zero/slice.go deleted file mode 100644 index 70edfc89..00000000 --- a/types/zero/slice.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -// This file implements range-based zeroing, which as of Go 1.5 is -// optimized using a Duff's device. - -package zero - -import "math/big" - -// Bytes sets all bytes in the passed slice to zero. This is used to -// explicitly clear private key material from memory. -// -// In general, prefer to use the fixed-sized zeroing functions (Bytea*) -// when zeroing bytes as they are much more efficient than the variable -// sized zeroing func Bytes. -func Bytes(b []byte) { - for i := range b { - b[i] = 0 - } -} - -// BigInt sets all bytes in the passed big int to zero and then sets the -// value to 0. This differs from simply setting the value in that it -// specifically clears the underlying bytes whereas simply setting the value -// does not. This is mostly useful to forcefully clear private keys. -func BigInt(x *big.Int) { - b := x.Bits() - for i := range b { - b[i] = 0 - } - x.SetInt64(0) -} diff --git a/types/zero/zero_test.go b/types/zero/zero_test.go deleted file mode 100644 index e1217cdb..00000000 --- a/types/zero/zero_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) 2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package zero_test - -import ( - "fmt" - "math/big" - "strings" - "testing" - - "github.com/babylonchain/vigilante/types/zero" -) - -func makeOneBytes(n int) []byte { - b := make([]byte, n) - for i := range b { - b[i] = 1 - } - return b -} - -func checkZeroBytes(b []byte) error { - for i, v := range b { - if v != 0 { - return fmt.Errorf("b[%d] = %d", i, v) - } - } - return nil -} - -func TestBytes(t *testing.T) { - tests := []int{ - 0, - 31, - 32, - 33, - 127, - 128, - 129, - 255, - 256, - 256, - 257, - 383, - 384, - 385, - 511, - 512, - 513, - } - - for i, n := range tests { - b := makeOneBytes(n) - zero.Bytes(b) - err := checkZeroBytes(b) - if err != nil { - t.Errorf("Test %d (n=%d) failed: %v", i, n, err) - continue - } - } -} - -func checkZeroWords(b []big.Word) error { - for i, v := range b { - if v != 0 { - return fmt.Errorf("b[%d] = %d", i, v) - } - } - return nil -} - -var bigZero = new(big.Int) - -func TestBigInt(t *testing.T) { - tests := []string{ - // 16 0xFFFFFFFF 32-bit uintptrs - strings.Repeat("FFFFFFFF", 16), - - // 17 32-bit uintptrs, minimum value which enters loop on 32-bit - "01" + strings.Repeat("00000000", 16), - - // 32 0xFFFFFFFF 32-bit uintptrs, maximum value which enters loop exactly once on 32-bit - strings.Repeat("FFFFFFFF", 32), - - // 33 32-bit uintptrs, minimum value which enters loop twice on 32-bit - "01" + strings.Repeat("00000000", 32), - - // 16 0xFFFFFFFFFFFFFFFF 64-bit uintptrs - strings.Repeat("FFFFFFFFFFFFFFFF", 16), - - // 17 64-bit uintptrs, minimum value which enters loop on 64-bit - "01" + strings.Repeat("0000000000000000", 16), - - // 32 0xFFFFFFFFFFFFFFFF 64-bit uintptrs, maximum value which enters loop exactly once on 64-bit - strings.Repeat("FFFFFFFFFFFFFFFF", 32), - - // 33 64-bit uintptrs, minimum value which enters loop twice on 64-bit - "01" + strings.Repeat("0000000000000000", 32), - } - - for i, s := range tests { - v, ok := new(big.Int).SetString(s, 16) - if !ok { - t.Errorf("Test %d includes invalid hex number %s", i, s) - continue - } - - zero.BigInt(v) - err := checkZeroWords(v.Bits()) - if err != nil { - t.Errorf("Test %d (s=%s) failed: %v", i, s, err) - continue - } - if v.Cmp(bigZero) != 0 { - t.Errorf("Test %d (s=%s) zeroed big.Int represents non-zero number %v", i, s, v) - continue - } - } -} - -func TestBytea32(t *testing.T) { - const sz = 32 - var b [sz]byte - copy(b[:], makeOneBytes(sz)) - - zero.Bytea32(&b) - - err := checkZeroBytes(b[:]) - if err != nil { - t.Error(err) - } -} - -func TestBytea64(t *testing.T) { - const sz = 64 - var b [sz]byte - copy(b[:], makeOneBytes(sz)) - - zero.Bytea64(&b) - - err := checkZeroBytes(b[:]) - if err != nil { - t.Error(err) - } -} diff --git a/vigilante/README.md b/vigilante/README.md new file mode 100644 index 00000000..ef6a2af5 --- /dev/null +++ b/vigilante/README.md @@ -0,0 +1,3 @@ +# vigilante + +This package implements the vigilantes, including the vigilant submitter and the vigilant reporter. The code is adapted from https://github.com/btcsuite/btcwallet/tree/master/wallet. \ No newline at end of file diff --git a/vigilante/reporter.go b/vigilante/reporter.go new file mode 100644 index 00000000..3b94d943 --- /dev/null +++ b/vigilante/reporter.go @@ -0,0 +1,157 @@ +package vigilante + +import ( + "errors" + "sync" + + "github.com/babylonchain/vigilante/btcclient" + "github.com/babylonchain/vigilante/netparams" +) + +type Reporter struct { + btcClient btcclient.Interface + btcClientLock sync.Mutex + // TODO: add Babylon client + + btcParams *netparams.BTCParams + // TODO: add Babylon parameters + wg sync.WaitGroup + + started bool + quit chan struct{} + quitMu sync.Mutex +} + +// Start starts the goroutines necessary to manage a vigilante. +func (r *Reporter) Start() { + r.quitMu.Lock() + select { + case <-r.quit: + // Restart the vigilante goroutines after shutdown finishes. + r.WaitForShutdown() + r.quit = make(chan struct{}) + default: + // Ignore when the vigilante is still running. + if r.started { + r.quitMu.Unlock() + return + } + r.started = true + } + r.quitMu.Unlock() + + // r.wg.Add(2) + // go r.txCreator() + // go r.walletLocker() +} + +// SynchronizeRPC associates the vigilante with the consensus RPC client, +// synchronizes the vigilante with the latest changes to the blockchain, and +// continuously updates the vigilante through RPC notifications. +// +// This method is unstable and will be removed when all syncing logic is moved +// outside of the vigilante package. +func (r *Reporter) SynchronizeRPC(btcClient btcclient.Interface) { + r.quitMu.Lock() + select { + case <-r.quit: + r.quitMu.Unlock() + return + default: + } + r.quitMu.Unlock() + + // TODO: Ignoring the new client when one is already set breaks callers + // who are replacing the client, perhaps after a disconnect. + r.btcClientLock.Lock() + if r.btcClient != nil { + r.btcClientLock.Unlock() + return + } + r.btcClient = btcClient + r.btcClientLock.Unlock() + + // TODO: It would be preferable to either run these goroutines + // separately from the vigilante (use vigilante mutator functions to + // make changes from the RPC client) and not have to stop and + // restart them each time the client disconnects and reconnets. + // r.wg.Add(4) + // go r.handleChainNotifications() + // go r.rescanBatchHandler() + // go r.rescanProgressHandler() + // go r.rescanRPCHandler() +} + +// requirebtcClient marks that a vigilante method can only be completed when the +// consensus RPC server is set. This function and all functions that call it +// are unstable and will need to be moved when the syncing code is moved out of +// the vigilante. +func (r *Reporter) requireGetBtcClient() (btcclient.Interface, error) { + r.btcClientLock.Lock() + btcClient := r.btcClient + r.btcClientLock.Unlock() + if btcClient == nil { + return nil, errors.New("blockchain RPC is inactive") + } + return btcClient, nil +} + +// btcClient returns the optional consensus RPC client associated with the +// vigilante. +// +// This function is unstable and will be removed once sync logic is moved out of +// the vigilante. +func (r *Reporter) getBtcClient() btcclient.Interface { + r.btcClientLock.Lock() + btcClient := r.btcClient + r.btcClientLock.Unlock() + return btcClient +} + +// quitChan atomically reads the quit channel. +func (r *Reporter) quitChan() <-chan struct{} { + r.quitMu.Lock() + c := r.quit + r.quitMu.Unlock() + return c +} + +// Stop signals all vigilante goroutines to shutdown. +func (r *Reporter) Stop() { + r.quitMu.Lock() + quit := r.quit + r.quitMu.Unlock() + + select { + case <-quit: + default: + close(quit) + r.btcClientLock.Lock() + if r.btcClient != nil { + r.btcClient.Stop() + r.btcClient = nil + } + r.btcClientLock.Unlock() + } +} + +// ShuttingDown returns whether the vigilante is currently in the process of +// shutting down or not. +func (r *Reporter) ShuttingDown() bool { + select { + case <-r.quitChan(): + return true + default: + return false + } +} + +// WaitForShutdown blocks until all vigilante goroutines have finished executing. +func (r *Reporter) WaitForShutdown() { + r.btcClientLock.Lock() + if r.btcClient != nil { + r.btcClient.WaitForShutdown() + } + r.btcClientLock.Unlock() + r.wg.Wait() +} diff --git a/vigilante/submitter.go b/vigilante/submitter.go new file mode 100644 index 00000000..bb405453 --- /dev/null +++ b/vigilante/submitter.go @@ -0,0 +1,158 @@ +package vigilante + +import ( + "errors" + "sync" + + "github.com/babylonchain/vigilante/btcclient" + "github.com/babylonchain/vigilante/netparams" +) + +type Submitter struct { + btcClient btcclient.Interface + btcClientLock sync.Mutex + // TODO: add Babylon client + // TODO: add wallet client + + btcParams *netparams.BTCParams + // TODO: add Babylon parameters + wg sync.WaitGroup + + started bool + quit chan struct{} + quitMu sync.Mutex +} + +// Start starts the goroutines necessary to manage a vigilante. +func (s *Submitter) Start() { + s.quitMu.Lock() + select { + case <-s.quit: + // Restart the vigilante goroutines after shutdown finishes. + s.WaitForShutdown() + s.quit = make(chan struct{}) + default: + // Ignore when the vigilante is still running. + if s.started { + s.quitMu.Unlock() + return + } + s.started = true + } + s.quitMu.Unlock() + + // s.wg.Add(2) + // go s.txCreator() + // go s.walletLocker() +} + +// SynchronizeRPC associates the vigilante with the consensus RPC client, +// synchronizes the vigilante with the latest changes to the blockchain, and +// continuously updates the vigilante through RPC notifications. +// +// This method is unstable and will be removed when all syncing logic is moved +// outside of the vigilante package. +func (s *Submitter) SynchronizeRPC(btcClient btcclient.Interface) { + s.quitMu.Lock() + select { + case <-s.quit: + s.quitMu.Unlock() + return + default: + } + s.quitMu.Unlock() + + // TODO: Ignoring the new client when one is already set breaks callers + // who are replacing the client, perhaps after a disconnect. + s.btcClientLock.Lock() + if s.btcClient != nil { + s.btcClientLock.Unlock() + return + } + s.btcClient = btcClient + s.btcClientLock.Unlock() + + // TODO: It would be preferable to either run these goroutines + // separately from the vigilante (use vigilante mutator functions to + // make changes from the RPC client) and not have to stop and + // restart them each time the client disconnects and reconnets. + // s.wg.Add(4) + // go s.handleChainNotifications() + // go s.rescanBatchHandler() + // go s.rescanProgressHandler() + // go s.rescanRPCHandler() +} + +// requirebtcClient marks that a vigilante method can only be completed when the +// consensus RPC server is set. This function and all functions that call it +// are unstable and will need to be moved when the syncing code is moved out of +// the vigilante. +func (s *Submitter) requireGetBtcClient() (btcclient.Interface, error) { + s.btcClientLock.Lock() + btcClient := s.btcClient + s.btcClientLock.Unlock() + if btcClient == nil { + return nil, errors.New("blockchain RPC is inactive") + } + return btcClient, nil +} + +// btcClient returns the optional consensus RPC client associated with the +// vigilante. +// +// This function is unstable and will be removed once sync logic is moved out of +// the vigilante. +func (s *Submitter) getBtcClient() btcclient.Interface { + s.btcClientLock.Lock() + btcClient := s.btcClient + s.btcClientLock.Unlock() + return btcClient +} + +// quitChan atomically reads the quit channel. +func (s *Submitter) quitChan() <-chan struct{} { + s.quitMu.Lock() + c := s.quit + s.quitMu.Unlock() + return c +} + +// Stop signals all vigilante goroutines to shutdown. +func (s *Submitter) Stop() { + s.quitMu.Lock() + quit := s.quit + s.quitMu.Unlock() + + select { + case <-quit: + default: + close(quit) + s.btcClientLock.Lock() + if s.btcClient != nil { + s.btcClient.Stop() + s.btcClient = nil + } + s.btcClientLock.Unlock() + } +} + +// ShuttingDown returns whether the vigilante is currently in the process of +// shutting down or not. +func (s *Submitter) ShuttingDown() bool { + select { + case <-s.quitChan(): + return true + default: + return false + } +} + +// WaitForShutdown blocks until all vigilante goroutines have finished executing. +func (s *Submitter) WaitForShutdown() { + s.btcClientLock.Lock() + if s.btcClient != nil { + s.btcClient.WaitForShutdown() + } + s.btcClientLock.Unlock() + s.wg.Wait() +} From 19c0a6cc6fc3761f6245d35a2105a46346472c87 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 4 Aug 2022 16:45:07 +1000 Subject: [PATCH 04/28] hello world vigilantes --- .gitignore | 2 ++ cmd/main.go | 28 ++++++++++++++++++++++++++++ cmd/reporter/reporter.go | 21 +++++++++++++++++++++ cmd/submitter/submitter.go | 21 +++++++++++++++++++++ go.mod | 3 +++ go.sum | 9 +++++++++ 6 files changed, 84 insertions(+) create mode 100644 cmd/main.go create mode 100644 cmd/reporter/reporter.go create mode 100644 cmd/submitter/submitter.go diff --git a/.gitignore b/.gitignore index 66fd13c9..ae90729c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +main \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 00000000..fa10e844 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/babylonchain/vigilante/cmd/reporter" + "github.com/babylonchain/vigilante/cmd/submitter" +) + +func main() { + rootCmd := &cobra.Command{ + Use: "vigilante", + Short: "Babylon vigilante", + } + rootCmd.AddCommand(reporter.GetCmd(), submitter.GetCmd()) + + if err := rootCmd.Execute(); err != nil { + switch e := err.(type) { + // TODO: dedicated error codes for vigilantes + default: + fmt.Print(e.Error()) + os.Exit(1) + } + } +} diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go new file mode 100644 index 00000000..a8ecda49 --- /dev/null +++ b/cmd/reporter/reporter.go @@ -0,0 +1,21 @@ +package reporter + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// GetCmd returns the cli query commands for this module +func GetCmd() *cobra.Command { + // Group epoching queries under a subcommand + cmd := &cobra.Command{ + Use: "reporter", + Short: "Vigilant reporter", + Run: func(cmd *cobra.Command, args []string) { + fmt.Print("Hello world!") + }, + } + + return cmd +} diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go new file mode 100644 index 00000000..c487a8e2 --- /dev/null +++ b/cmd/submitter/submitter.go @@ -0,0 +1,21 @@ +package submitter + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// GetCmd returns the cli query commands for this module +func GetCmd() *cobra.Command { + // Group epoching queries under a subcommand + cmd := &cobra.Command{ + Use: "submitter", + Short: "Vigilant submitter", + Run: func(cmd *cobra.Command, args []string) { + fmt.Print("Hello world!") + }, + } + + return cmd +} diff --git a/go.mod b/go.mod index 287d218b..c6a8261d 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf github.com/lightninglabs/neutrino v0.14.2 github.com/lightningnetwork/lnd/ticker v1.1.0 + github.com/spf13/cobra v1.5.0 github.com/stretchr/testify v1.8.0 golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b google.golang.org/grpc v1.48.0 @@ -29,10 +30,12 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/lru v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect github.com/lightningnetwork/lnd/clock v1.0.1 // indirect github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index a774ca0a..e0a6ec55 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -105,6 +106,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -142,6 +145,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -259,6 +267,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f9f8ac69eac655e4ced8b12d2aa30e3b37b45486 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 00:30:18 +1000 Subject: [PATCH 05/28] some refactor --- btcclient/client.go | 4 ++-- rpcserver/log.go | 2 ++ rpcserver/server.go | 42 +++++++++++++++++++----------------------- rpcserver/service.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 rpcserver/service.go diff --git a/btcclient/client.go b/btcclient/client.go index cba3d337..ad7645f2 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -42,13 +42,13 @@ type Client struct { quitMtx sync.Mutex } -// NewClient creates a client connection to the server described by the +// New creates a client connection to the server described by the // connect string. If disableTLS is false, the remote RPC certificate must be // provided in the certs slice. The connection is not established immediately, // but must be done using the Start method. If the remote server does not // operate on the same bitcoin network as described by the passed chain // parameters, the connection will be disconnected. -func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, +func New(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, disableTLS bool, reconnectAttempts int) (*Client, error) { if reconnectAttempts < 0 { diff --git a/rpcserver/log.go b/rpcserver/log.go index 36e35662..fb371d6d 100644 --- a/rpcserver/log.go +++ b/rpcserver/log.go @@ -23,6 +23,8 @@ import ( "github.com/btcsuite/btclog" ) +var log logger + // UseLogger sets the logger to use for the gRPC server. func UseLogger(l btclog.Logger) { grpclog.SetLogger(logger{l}) // nolint:staticcheck diff --git a/rpcserver/server.go b/rpcserver/server.go index 42c3769f..5d5af91c 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -16,33 +16,29 @@ package rpcserver import ( - "golang.org/x/net/context" - "google.golang.org/grpc" + "net" - pb "github.com/babylonchain/vigilante/rpcserver/api" + "google.golang.org/grpc" ) -// Public API version constants -const ( - verString = "2.0.1" - verMajor = 2 - verMinor = 0 - verPatch = 1 -) +func New() (*grpc.Server, error) { + // TODO: TLS and other server opts + server := grpc.NewServer() + StartVigilanteService(server) -type server struct{} + // endpoint + // TODO: config for ip:port + lis, err := net.Listen("tcp", "localhost:8080") + if err != nil { + log.Fatalf("failed to listen: %v", err) + } -// StartVigilanteService creates an implementation of the VigilanteService and -// registers it with the gRPC server. -func StartVigilanteService(gs *grpc.Server) { - pb.RegisterVigilanteServiceServer(gs, &server{}) -} + // start the server listenting to this endpoint + go func() { + if err := server.Serve(lis); err != nil { + log.Errorf("serve RPC server: %v", err) + } + }() -func (*server) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) { - return &pb.VersionResponse{ - VersionString: verString, - Major: verMajor, - Minor: verMinor, - Patch: verPatch, - }, nil + return server, nil } diff --git a/rpcserver/service.go b/rpcserver/service.go new file mode 100644 index 00000000..cc0db54d --- /dev/null +++ b/rpcserver/service.go @@ -0,0 +1,33 @@ +package rpcserver + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" + + pb "github.com/babylonchain/vigilante/rpcserver/api" +) + +// Public API version constants +const ( + verString = "2.0.1" + verMajor = 2 + verMinor = 0 + verPatch = 1 +) + +type service struct{} + +// StartVigilanteService creates an implementation of the VigilanteService and +// registers it with the gRPC server. +func StartVigilanteService(gs *grpc.Server) { + pb.RegisterVigilanteServiceServer(gs, &service{}) +} + +func (s *service) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) { + return &pb.VersionResponse{ + VersionString: verString, + Major: verMajor, + Minor: verMinor, + Patch: verPatch, + }, nil +} From f80141022ed4c34b65eb78ef8ed5a2602c7e214e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 01:19:43 +1000 Subject: [PATCH 06/28] some init stuff --- cmd/reporter/reporter.go | 28 ++- cmd/signal.go | 84 +++++++++ cmd/signalsigterm.go | 17 ++ config/config.go | 78 ++++++++ go.mod | 17 +- go.sum | 375 ++++++++++++++++++++++++++++++++++++++- vigilante/reporter.go | 8 + vigilante/submitter.go | 8 + 8 files changed, 604 insertions(+), 11 deletions(-) create mode 100644 cmd/signal.go create mode 100644 cmd/signalsigterm.go create mode 100644 config/config.go diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index a8ecda49..3f90a55e 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -1,8 +1,11 @@ package reporter import ( - "fmt" - + "github.com/babylonchain/vigilante/btcclient" + "github.com/babylonchain/vigilante/netparams" + "github.com/babylonchain/vigilante/rpcserver" + "github.com/babylonchain/vigilante/vigilante" + "github.com/btcsuite/btcd/chaincfg" "github.com/spf13/cobra" ) @@ -13,7 +16,26 @@ func GetCmd() *cobra.Command { Use: "reporter", Short: "Vigilant reporter", Run: func(cmd *cobra.Command, args []string) { - fmt.Print("Hello world!") + // creat stuff + // TODO: specify btc params in cmd / config file + btcParams := netparams.SimNetParams + // TODO: specify btc params in cmd / config file + btcClient, err := btcclient.New(&chaincfg.SimNetParams, "localhost:18554", "user", "pass", []byte{}, false, 5) + if err != nil { + panic(err) + } + reporter := vigilante.NewReporter(btcClient, &btcParams) + + // start stuff + if err := btcClient.Start(); err != nil { + panic(err) + } + reporter.Start() + if _, err = rpcserver.New(); err != nil { + panic(err) + } + + // TODO: SIGINT handling stuff }, } diff --git a/cmd/signal.go b/cmd/signal.go new file mode 100644 index 00000000..a8b140c3 --- /dev/null +++ b/cmd/signal.go @@ -0,0 +1,84 @@ +// Copyright (c) 2013-2014 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "os/signal" +) + +// interruptChannel is used to receive SIGINT (Ctrl+C) signals. +var interruptChannel chan os.Signal + +// addHandlerChannel is used to add an interrupt handler to the list of handlers +// to be invoked on SIGINT (Ctrl+C) signals. +var addHandlerChannel = make(chan func()) + +// interruptHandlersDone is closed after all interrupt handlers run the first +// time an interrupt is signaled. +var interruptHandlersDone = make(chan struct{}) + +var simulateInterruptChannel = make(chan struct{}, 1) + +// signals defines the signals that are handled to do a clean shutdown. +// Conditional compilation is used to also include SIGTERM on Unix. +var signals = []os.Signal{os.Interrupt} + +// simulateInterrupt requests invoking the clean termination process by an +// internal component instead of a SIGINT. +func simulateInterrupt() { + select { + case simulateInterruptChannel <- struct{}{}: + default: + } +} + +// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the +// interruptChannel and invokes the registered interruptCallbacks accordingly. +// It also listens for callback registration. It must be run as a goroutine. +func mainInterruptHandler() { + // interruptCallbacks is a list of callbacks to invoke when a + // SIGINT (Ctrl+C) is received. + var interruptCallbacks []func() + invokeCallbacks := func() { + // run handlers in LIFO order. + for i := range interruptCallbacks { + idx := len(interruptCallbacks) - 1 - i + interruptCallbacks[idx]() + } + close(interruptHandlersDone) + } + + for { + select { + case sig := <-interruptChannel: + fmt.Printf("Received signal (%s). Shutting down...", sig) + invokeCallbacks() + return + case <-simulateInterruptChannel: + fmt.Printf("Received shutdown request. Shutting down...") + invokeCallbacks() + return + + case handler := <-addHandlerChannel: + interruptCallbacks = append(interruptCallbacks, handler) + } + } +} + +// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is +// received. +func addInterruptHandler(handler func()) { + // Create the channel and start the main interrupt handler which invokes + // all other callbacks and exits if not already done. + if interruptChannel == nil { + interruptChannel = make(chan os.Signal, 1) + signal.Notify(interruptChannel, signals...) + go mainInterruptHandler() + } + + addHandlerChannel <- handler +} diff --git a/cmd/signalsigterm.go b/cmd/signalsigterm.go new file mode 100644 index 00000000..36cf2786 --- /dev/null +++ b/cmd/signalsigterm.go @@ -0,0 +1,17 @@ +// Copyright (c) 2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package main + +import ( + "os" + "syscall" +) + +func init() { + signals = []os.Signal{os.Interrupt, syscall.SIGTERM} +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..dcff8c08 --- /dev/null +++ b/config/config.go @@ -0,0 +1,78 @@ +package config + +import ( + "github.com/spf13/viper" +) + +const ( + // DefaultGRPCAddress defines the default address to bind the gRPC server to. + DefaultGRPCAddress = "0.0.0.0:8080" + + // DefaultGRPCWebAddress defines the default address to bind the gRPC-web server to. + DefaultGRPCWebAddress = "0.0.0.0:8081" +) + +// BaseConfig defines the server's basic configuration +type BaseConfig struct { + Placeholder string `mapstructure:"placeholder"` +} + +// APIConfig defines the API listener configuration. +type APIConfig struct { + Placeholder string `mapstructure:"placeholder"` +} + +// GRPCConfig defines configuration for the gRPC server. +type GRPCConfig struct { + Placeholder string `mapstructure:"placeholder"` +} + +// GRPCWebConfig defines configuration for the gRPC-web server. +type GRPCWebConfig struct { + Placeholder string `mapstructure:"placeholder"` +} + +// Config defines the server's top level configuration +type Config struct { + BaseConfig `mapstructure:",squash"` + + API APIConfig `mapstructure:"api"` + GRPC GRPCConfig `mapstructure:"grpc"` + GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` +} + +// DefaultConfig returns server's default configuration. +func DefaultConfig() *Config { + return &Config{ + BaseConfig: BaseConfig{ + Placeholder: "baseconfig", + }, + API: APIConfig{ + Placeholder: "apiconfig", + }, + GRPC: GRPCConfig{ + Placeholder: "grpcconfig", + }, + GRPCWeb: GRPCWebConfig{ + Placeholder: "grpcwebconfig", + }, + } +} + +// GetConfig returns a fully parsed Config object. +func GetConfig(v *viper.Viper) Config { + return Config{ + BaseConfig: BaseConfig{ + Placeholder: v.GetString("placeholder"), + }, + API: APIConfig{ + Placeholder: v.GetString("placeholder"), + }, + GRPC: GRPCConfig{ + Placeholder: v.GetString("placeholder"), + }, + GRPCWeb: GRPCWebConfig{ + Placeholder: v.GetString("placeholder"), + }, + } +} diff --git a/go.mod b/go.mod index c6a8261d..2ec982fa 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/lightninglabs/neutrino v0.14.2 github.com/lightningnetwork/lnd/ticker v1.1.0 github.com/spf13/cobra v1.5.0 + github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b google.golang.org/grpc v1.48.0 @@ -29,16 +30,28 @@ require ( github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/decred/dcrd/lru v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect github.com/lightningnetwork/lnd/clock v1.0.1 // indirect github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + github.com/subosito/gotenv v1.3.0 // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect + google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect + gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e0a6ec55..46c0b2da 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,43 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -49,8 +86,12 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -70,18 +111,37 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -95,31 +155,63 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/cgCewJcCo= @@ -132,6 +224,10 @@ github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= github.com/lightningnetwork/lnd/tlv v1.0.2 h1:LG7H3Uw/mHYGnEeHRPg+STavAH+UsFvuBflD0PzcYFQ= github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -141,37 +237,102 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= +github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -182,65 +343,253 @@ golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -251,26 +600,40 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/vigilante/reporter.go b/vigilante/reporter.go index 3b94d943..5f8cd94c 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -22,6 +22,14 @@ type Reporter struct { quitMu sync.Mutex } +func NewReporter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) *Reporter { + return &Reporter{ + btcClient: btcClient, + btcParams: btcParams, + quit: make(chan struct{}), + } +} + // Start starts the goroutines necessary to manage a vigilante. func (r *Reporter) Start() { r.quitMu.Lock() diff --git a/vigilante/submitter.go b/vigilante/submitter.go index bb405453..ba170e0f 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -23,6 +23,14 @@ type Submitter struct { quitMu sync.Mutex } +func NewSubmitter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) *Submitter { + return &Submitter{ + btcClient: btcClient, + btcParams: btcParams, + quit: make(chan struct{}), + } +} + // Start starts the goroutines necessary to manage a vigilante. func (s *Submitter) Start() { s.quitMu.Lock() From 4d83f34eccb7b7127a97038ce21d6d1c8eb0dc33 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 12:18:59 +1000 Subject: [PATCH 07/28] log and main programs --- btcclient/bitcoind_client.go | 1 + btcclient/bitcoind_conn.go | 1 + btcclient/bitcoind_events.go | 1 + btcclient/block_filterer.go | 1 + btcclient/client.go | 1 + btcclient/log.go | 34 ------------ btcclient/pruned_block_dispatcher.go | 1 + cmd/main.go | 2 + cmd/reporter/reporter.go | 52 ++++++++++++----- cmd/submitter/submitter.go | 50 ++++++++++++++++- cmd/utils/btcclient.go | 57 +++++++++++++++++++ cmd/{ => utils}/signal.go | 19 ++++--- cmd/{ => utils}/signalsigterm.go | 2 +- config/config.go | 30 +++++++--- go.mod | 3 +- go.sum | 3 + netparams/bitcoin.go | 15 +++++ rpcserver/log.go | 83 ---------------------------- rpcserver/server.go | 1 + vigilante/reporter.go | 4 +- vigilante/submitter.go | 4 +- 21 files changed, 211 insertions(+), 154 deletions(-) delete mode 100644 btcclient/log.go create mode 100644 cmd/utils/btcclient.go rename cmd/{ => utils}/signal.go (83%) rename cmd/{ => utils}/signalsigterm.go (96%) delete mode 100644 rpcserver/log.go diff --git a/btcclient/bitcoind_client.go b/btcclient/bitcoind_client.go index 890903e9..4cdff30f 100644 --- a/btcclient/bitcoind_client.go +++ b/btcclient/bitcoind_client.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wtxmgr" + log "github.com/sirupsen/logrus" ) var ( diff --git a/btcclient/bitcoind_conn.go b/btcclient/bitcoind_conn.go index a06d20ff..6b5f0bff 100644 --- a/btcclient/bitcoind_conn.go +++ b/btcclient/bitcoind_conn.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/ticker" + log "github.com/sirupsen/logrus" ) const ( diff --git a/btcclient/bitcoind_events.go b/btcclient/bitcoind_events.go index 945257b7..f3751580 100644 --- a/btcclient/bitcoind_events.go +++ b/btcclient/bitcoind_events.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/gozmq" + log "github.com/sirupsen/logrus" ) const ( diff --git a/btcclient/block_filterer.go b/btcclient/block_filterer.go index e6fb348f..154ae114 100644 --- a/btcclient/block_filterer.go +++ b/btcclient/block_filterer.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" + log "github.com/sirupsen/logrus" ) // BlockFilterer is used to iteratively scan blocks for a set of addresses of diff --git a/btcclient/client.go b/btcclient/client.go index ad7645f2..0c5dd2b1 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -20,6 +20,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wtxmgr" + log "github.com/sirupsen/logrus" ) var _ Interface = &Client{} diff --git a/btcclient/log.go b/btcclient/log.go deleted file mode 100644 index 6f9101ae..00000000 --- a/btcclient/log.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2013-2014 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package btcclient - -import ( - "github.com/btcsuite/btclog" - "github.com/lightninglabs/neutrino/query" -) - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log btclog.Logger - -// The default amount of logging is none. -func init() { - DisableLog() -} - -// DisableLog disables all library log output. Logging output is disabled -// by default until either UseLogger or SetLogWriter are called. -func DisableLog() { - log = btclog.Disabled -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using btclog. -func UseLogger(logger btclog.Logger) { - log = logger - query.UseLogger(logger) -} diff --git a/btcclient/pruned_block_dispatcher.go b/btcclient/pruned_block_dispatcher.go index 8a1e7114..f0291792 100644 --- a/btcclient/pruned_block_dispatcher.go +++ b/btcclient/pruned_block_dispatcher.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino/query" "github.com/lightningnetwork/lnd/ticker" + log "github.com/sirupsen/logrus" ) const ( diff --git a/cmd/main.go b/cmd/main.go index fa10e844..8cfc5d74 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,6 +10,8 @@ import ( "github.com/babylonchain/vigilante/cmd/submitter" ) +// TODO: init log + func main() { rootCmd := &cobra.Command{ Use: "vigilante", diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index 3f90a55e..e2df8cf3 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -1,11 +1,13 @@ package reporter import ( - "github.com/babylonchain/vigilante/btcclient" + "fmt" + + "github.com/babylonchain/vigilante/cmd/utils" + "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" - "github.com/btcsuite/btcd/chaincfg" "github.com/spf13/cobra" ) @@ -16,26 +18,50 @@ func GetCmd() *cobra.Command { Use: "reporter", Short: "Vigilant reporter", Run: func(cmd *cobra.Command, args []string) { - // creat stuff - // TODO: specify btc params in cmd / config file - btcParams := netparams.SimNetParams - // TODO: specify btc params in cmd / config file - btcClient, err := btcclient.New(&chaincfg.SimNetParams, "localhost:18554", "user", "pass", []byte{}, false, 5) + // load config + // TODO: read config from a file + cfg := config.DefaultConfig() + btcParams := netparams.GetParams(cfg.BTC.NetParams) + + // create BTC client + btcClient, err := utils.NewBTCClient(cfg) if err != nil { panic(err) } - reporter := vigilante.NewReporter(btcClient, &btcParams) - - // start stuff - if err := btcClient.Start(); err != nil { + // create RPC client + reporter, err := vigilante.NewReporter(btcClient, &btcParams) + if err != nil { panic(err) } + + // keep trying BTC client + utils.BTCClientConnectLoop(cfg, btcClient) + // start reporter and sync reporter.Start() - if _, err = rpcserver.New(); err != nil { + reporter.SynchronizeRPC(btcClient) + // start RPC server + server, err := rpcserver.New() + if err != nil { panic(err) } - // TODO: SIGINT handling stuff + // SIGINT handling stuff + utils.AddInterruptHandler(func() { + // TODO: Does this need to wait for the grpc server to finish up any requests? + fmt.Println("Stopping RPC server...") + server.Stop() + fmt.Println("RPC server shutdown") + }) + utils.AddInterruptHandler(func() { + fmt.Println("Stopping BTC client...") + if !btcClient.Disconnected() { + btcClient.Stop() + } + fmt.Println("BTC client shutdown") + }) + + <-utils.InterruptHandlersDone + fmt.Println("Shutdown complete") }, } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index c487a8e2..2710f4df 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -3,6 +3,11 @@ package submitter import ( "fmt" + "github.com/babylonchain/vigilante/cmd/utils" + "github.com/babylonchain/vigilante/config" + "github.com/babylonchain/vigilante/netparams" + "github.com/babylonchain/vigilante/rpcserver" + "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) @@ -13,7 +18,50 @@ func GetCmd() *cobra.Command { Use: "submitter", Short: "Vigilant submitter", Run: func(cmd *cobra.Command, args []string) { - fmt.Print("Hello world!") + // load config + // TODO: read config from a file + cfg := config.DefaultConfig() + btcParams := netparams.GetParams(cfg.BTC.NetParams) + + // create BTC client + btcClient, err := utils.NewBTCClient(cfg) + if err != nil { + panic(err) + } + // create RPC client + submitter, err := vigilante.NewSubmitter(btcClient, &btcParams) + if err != nil { + panic(err) + } + + // keep trying BTC client + utils.BTCClientConnectLoop(cfg, btcClient) + // start submitter and sync + submitter.Start() + submitter.SynchronizeRPC(btcClient) + // start RPC server + server, err := rpcserver.New() + if err != nil { + panic(err) + } + + // SIGINT handling stuff + utils.AddInterruptHandler(func() { + // TODO: Does this need to wait for the grpc server to finish up any requests? + fmt.Println("Stopping RPC server...") + server.Stop() + fmt.Println("RPC server shutdown") + }) + utils.AddInterruptHandler(func() { + fmt.Println("Stopping BTC client...") + if !btcClient.Disconnected() { + btcClient.Stop() + } + fmt.Println("BTC client shutdown") + }) + + <-utils.InterruptHandlersDone + fmt.Println("Shutdown complete") }, } diff --git a/cmd/utils/btcclient.go b/cmd/utils/btcclient.go new file mode 100644 index 00000000..1530aef0 --- /dev/null +++ b/cmd/utils/btcclient.go @@ -0,0 +1,57 @@ +package utils + +import ( + "io/ioutil" + "time" + + "github.com/babylonchain/vigilante/btcclient" + "github.com/babylonchain/vigilante/config" + "github.com/babylonchain/vigilante/netparams" + log "github.com/sirupsen/logrus" +) + +func NewBTCClient(cfg *config.Config) (*btcclient.Client, error) { + certs := readCAFile(cfg) + params := netparams.GetParams(cfg.BTC.NetParams) + // TODO: parameterise the reconnect attempts? + client, err := btcclient.New(params.Params, cfg.BTC.Endpoint, cfg.BTC.Username, cfg.BTC.Password, certs, cfg.BTC.DisableClientTLS, 5) + if err != nil { + return nil, err + } + return client, nil +} + +func BTCClientConnectLoop(cfg *config.Config, client *btcclient.Client) { + ticker := time.NewTicker(5 * time.Second) + + go func() { + for range ticker.C { + log.Infof("Attempting RPC client connection to %v", cfg.BTC.Endpoint) + if err := client.Start(); err != nil { + log.Errorf("Unable to open connection to consensus RPC server: %v", err) + continue + } + client.WaitForShutdown() + break + } + }() +} + +func readCAFile(cfg *config.Config) []byte { + // Read certificate file if TLS is not disabled. + var certs []byte + if !cfg.BTC.DisableClientTLS { + var err error + certs, err = ioutil.ReadFile(cfg.BTC.CAFile) + if err != nil { + log.Errorf("Cannot open CA file: %v", err) + // If there's an error reading the CA file, continue + // with nil certs and without the client connection. + certs = nil + } + } else { + log.Infof("Chain server RPC TLS is disabled") + } + + return certs +} diff --git a/cmd/signal.go b/cmd/utils/signal.go similarity index 83% rename from cmd/signal.go rename to cmd/utils/signal.go index a8b140c3..e15d7db2 100644 --- a/cmd/signal.go +++ b/cmd/utils/signal.go @@ -2,12 +2,13 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package main +package utils import ( - "fmt" "os" "os/signal" + + log "github.com/sirupsen/logrus" ) // interruptChannel is used to receive SIGINT (Ctrl+C) signals. @@ -17,9 +18,9 @@ var interruptChannel chan os.Signal // to be invoked on SIGINT (Ctrl+C) signals. var addHandlerChannel = make(chan func()) -// interruptHandlersDone is closed after all interrupt handlers run the first +// InterruptHandlersDone is closed after all interrupt handlers run the first // time an interrupt is signaled. -var interruptHandlersDone = make(chan struct{}) +var InterruptHandlersDone = make(chan struct{}) var simulateInterruptChannel = make(chan struct{}, 1) @@ -49,17 +50,17 @@ func mainInterruptHandler() { idx := len(interruptCallbacks) - 1 - i interruptCallbacks[idx]() } - close(interruptHandlersDone) + close(InterruptHandlersDone) } for { select { case sig := <-interruptChannel: - fmt.Printf("Received signal (%s). Shutting down...", sig) + log.Infof("Received signal (%s). Shutting down...", sig) invokeCallbacks() return case <-simulateInterruptChannel: - fmt.Printf("Received shutdown request. Shutting down...") + log.Infof("Received shutdown request. Shutting down...") invokeCallbacks() return @@ -69,9 +70,9 @@ func mainInterruptHandler() { } } -// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is +// AddInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) is // received. -func addInterruptHandler(handler func()) { +func AddInterruptHandler(handler func()) { // Create the channel and start the main interrupt handler which invokes // all other callbacks and exits if not already done. if interruptChannel == nil { diff --git a/cmd/signalsigterm.go b/cmd/utils/signalsigterm.go similarity index 96% rename from cmd/signalsigterm.go rename to cmd/utils/signalsigterm.go index 36cf2786..02d4e2af 100644 --- a/cmd/signalsigterm.go +++ b/cmd/utils/signalsigterm.go @@ -5,7 +5,7 @@ //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris -package main +package utils import ( "os" diff --git a/config/config.go b/config/config.go index dcff8c08..ab63925a 100644 --- a/config/config.go +++ b/config/config.go @@ -17,9 +17,14 @@ type BaseConfig struct { Placeholder string `mapstructure:"placeholder"` } -// APIConfig defines the API listener configuration. -type APIConfig struct { - Placeholder string `mapstructure:"placeholder"` +// BTCConfig defines the server's basic configuration +type BTCConfig struct { + DisableClientTLS bool `mapstructure:"noclienttls"` + CAFile string `mapstructure:"cafile"` + Endpoint string `mapstructure:"endpoint"` + NetParams string `mapstructure:"netparams"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` } // GRPCConfig defines configuration for the gRPC server. @@ -36,7 +41,7 @@ type GRPCWebConfig struct { type Config struct { BaseConfig `mapstructure:",squash"` - API APIConfig `mapstructure:"api"` + BTC BTCConfig `mapstructure:"btc"` GRPC GRPCConfig `mapstructure:"grpc"` GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` } @@ -47,8 +52,12 @@ func DefaultConfig() *Config { BaseConfig: BaseConfig{ Placeholder: "baseconfig", }, - API: APIConfig{ - Placeholder: "apiconfig", + BTC: BTCConfig{ + DisableClientTLS: true, + Endpoint: "localhost:18554", + NetParams: "simnet", + Username: "user", + Password: "pass", }, GRPC: GRPCConfig{ Placeholder: "grpcconfig", @@ -65,8 +74,13 @@ func GetConfig(v *viper.Viper) Config { BaseConfig: BaseConfig{ Placeholder: v.GetString("placeholder"), }, - API: APIConfig{ - Placeholder: v.GetString("placeholder"), + BTC: BTCConfig{ + DisableClientTLS: v.GetBool("noclienttls"), + CAFile: v.GetString("cafile"), + Endpoint: v.GetString("endpoint"), + NetParams: v.GetString("netparams"), + Username: v.GetString("username"), + Password: v.GetString("password"), }, GRPC: GRPCConfig{ Placeholder: v.GetString("placeholder"), diff --git a/go.mod b/go.mod index 2ec982fa..52cb8880 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,12 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.1.3 github.com/btcsuite/btcd/btcutil v1.1.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 - github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcwallet v0.15.1 github.com/btcsuite/btcwallet/wtxmgr v1.5.0 github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf github.com/lightninglabs/neutrino v0.14.2 github.com/lightningnetwork/lnd/ticker v1.1.0 + github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 github.com/spf13/viper v1.12.0 github.com/stretchr/testify v1.8.0 @@ -23,6 +23,7 @@ require ( require ( github.com/aead/siphash v1.0.1 // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect diff --git a/go.sum b/go.sum index 46c0b2da..c5b31c2e 100644 --- a/go.sum +++ b/go.sum @@ -250,6 +250,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= @@ -436,6 +438,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/netparams/bitcoin.go b/netparams/bitcoin.go index d8300a0e..b7554a21 100644 --- a/netparams/bitcoin.go +++ b/netparams/bitcoin.go @@ -60,3 +60,18 @@ func SigNetWire(params *chaincfg.Params) wire.BitcoinNet { return 0 } + +func GetParams(net string) BTCParams { + switch net { + case "mainnet": + return MainNetParams + case "testnet": + return TestNet3Params + case "signet": + return SigNetParams + case "simnet": + return SimNetParams + default: + return SimNetParams + } +} diff --git a/rpcserver/log.go b/rpcserver/log.go deleted file mode 100644 index fb371d6d..00000000 --- a/rpcserver/log.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2015-2016 The btcsuite developers -// -// Permission to use, copy, modify, and distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -package rpcserver - -import ( - "os" - "strings" - - "google.golang.org/grpc/grpclog" - - "github.com/btcsuite/btclog" -) - -var log logger - -// UseLogger sets the logger to use for the gRPC server. -func UseLogger(l btclog.Logger) { - grpclog.SetLogger(logger{l}) // nolint:staticcheck -} - -// logger uses a btclog.Logger to implement the grpclog.Logger interface. -type logger struct { - btclog.Logger -} - -// stripGrpcPrefix removes the package prefix for all logs made to the grpc -// logger, since these are already included as the btclog subsystem name. -func stripGrpcPrefix(logstr string) string { - return strings.TrimPrefix(logstr, "grpc: ") -} - -// stripGrpcPrefixArgs removes the package prefix from the first argument, if it -// exists and is a string, returning the same arg slice after reassigning the -// first arg. -func stripGrpcPrefixArgs(args ...interface{}) []interface{} { - if len(args) == 0 { - return args - } - firstArgStr, ok := args[0].(string) - if ok { - args[0] = stripGrpcPrefix(firstArgStr) - } - return args -} - -func (l logger) Fatal(args ...interface{}) { - l.Critical(stripGrpcPrefixArgs(args)...) - os.Exit(1) -} - -func (l logger) Fatalf(format string, args ...interface{}) { - l.Criticalf(stripGrpcPrefix(format), args...) - os.Exit(1) -} - -func (l logger) Fatalln(args ...interface{}) { - l.Critical(stripGrpcPrefixArgs(args)...) - os.Exit(1) -} - -func (l logger) Print(args ...interface{}) { - l.Info(stripGrpcPrefixArgs(args)...) -} - -func (l logger) Printf(format string, args ...interface{}) { - l.Infof(stripGrpcPrefix(format), args...) -} - -func (l logger) Println(args ...interface{}) { - l.Info(stripGrpcPrefixArgs(args)...) -} diff --git a/rpcserver/server.go b/rpcserver/server.go index 5d5af91c..a50edade 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -18,6 +18,7 @@ package rpcserver import ( "net" + log "github.com/sirupsen/logrus" "google.golang.org/grpc" ) diff --git a/vigilante/reporter.go b/vigilante/reporter.go index 5f8cd94c..1f5e55cb 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -22,12 +22,12 @@ type Reporter struct { quitMu sync.Mutex } -func NewReporter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) *Reporter { +func NewReporter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Reporter, error) { return &Reporter{ btcClient: btcClient, btcParams: btcParams, quit: make(chan struct{}), - } + }, nil } // Start starts the goroutines necessary to manage a vigilante. diff --git a/vigilante/submitter.go b/vigilante/submitter.go index ba170e0f..0aadd9a7 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -23,12 +23,12 @@ type Submitter struct { quitMu sync.Mutex } -func NewSubmitter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) *Submitter { +func NewSubmitter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Submitter, error) { return &Submitter{ btcClient: btcClient, btcParams: btcParams, quit: make(chan struct{}), - } + }, nil } // Start starts the goroutines necessary to manage a vigilante. From a3212a525004679c02c1ec20c471aaa274158778 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 15:05:58 +1000 Subject: [PATCH 08/28] rpc server working --- btcclient/interface.go | 1 - cmd/reporter/reporter.go | 7 +-- cmd/submitter/submitter.go | 7 +-- cmd/utils/btcclient.go | 17 +++---- cmd/utils/rpcserver.go | 11 +++++ config/config.go | 34 ++++++++++++-- rpcserver/README.md | 8 +++- rpcserver/server.go | 41 +++++++++++------ rpcserver/tls.go | 91 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 177 insertions(+), 40 deletions(-) create mode 100644 cmd/utils/rpcserver.go create mode 100644 rpcserver/tls.go diff --git a/btcclient/interface.go b/btcclient/interface.go index 16180bae..bc530dd9 100644 --- a/btcclient/interface.go +++ b/btcclient/interface.go @@ -21,7 +21,6 @@ func BackEnds() []string { return []string{ "bitcoind", "btcd", - "neutrino", "bitcoind-rpc-polling", } } diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index e2df8cf3..ea3b0b0a 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -6,7 +6,6 @@ import ( "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" - "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) @@ -40,7 +39,7 @@ func GetCmd() *cobra.Command { reporter.Start() reporter.SynchronizeRPC(btcClient) // start RPC server - server, err := rpcserver.New() + server, err := utils.NewRPCServer(cfg) if err != nil { panic(err) } @@ -54,9 +53,7 @@ func GetCmd() *cobra.Command { }) utils.AddInterruptHandler(func() { fmt.Println("Stopping BTC client...") - if !btcClient.Disconnected() { - btcClient.Stop() - } + btcClient.Stop() fmt.Println("BTC client shutdown") }) diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 2710f4df..0194514e 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -6,7 +6,6 @@ import ( "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" - "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) @@ -40,7 +39,7 @@ func GetCmd() *cobra.Command { submitter.Start() submitter.SynchronizeRPC(btcClient) // start RPC server - server, err := rpcserver.New() + server, err := utils.NewRPCServer(cfg) if err != nil { panic(err) } @@ -54,9 +53,7 @@ func GetCmd() *cobra.Command { }) utils.AddInterruptHandler(func() { fmt.Println("Stopping BTC client...") - if !btcClient.Disconnected() { - btcClient.Stop() - } + btcClient.Stop() fmt.Println("BTC client shutdown") }) diff --git a/cmd/utils/btcclient.go b/cmd/utils/btcclient.go index 1530aef0..545ba5b6 100644 --- a/cmd/utils/btcclient.go +++ b/cmd/utils/btcclient.go @@ -2,7 +2,6 @@ package utils import ( "io/ioutil" - "time" "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" @@ -14,7 +13,7 @@ func NewBTCClient(cfg *config.Config) (*btcclient.Client, error) { certs := readCAFile(cfg) params := netparams.GetParams(cfg.BTC.NetParams) // TODO: parameterise the reconnect attempts? - client, err := btcclient.New(params.Params, cfg.BTC.Endpoint, cfg.BTC.Username, cfg.BTC.Password, certs, cfg.BTC.DisableClientTLS, 5) + client, err := btcclient.New(params.Params, cfg.BTC.Endpoint, cfg.BTC.Username, cfg.BTC.Password, certs, cfg.BTC.DisableClientTLS, 3) if err != nil { return nil, err } @@ -22,18 +21,12 @@ func NewBTCClient(cfg *config.Config) (*btcclient.Client, error) { } func BTCClientConnectLoop(cfg *config.Config, client *btcclient.Client) { - ticker := time.NewTicker(5 * time.Second) - go func() { - for range ticker.C { - log.Infof("Attempting RPC client connection to %v", cfg.BTC.Endpoint) - if err := client.Start(); err != nil { - log.Errorf("Unable to open connection to consensus RPC server: %v", err) - continue - } - client.WaitForShutdown() - break + log.Infof("Attempting RPC client connection to %v", cfg.BTC.Endpoint) + if err := client.Start(); err != nil { + log.Errorf("Unable to open connection to consensus RPC server: %v", err) } + client.WaitForShutdown() }() } diff --git a/cmd/utils/rpcserver.go b/cmd/utils/rpcserver.go new file mode 100644 index 00000000..b0b1c3c8 --- /dev/null +++ b/cmd/utils/rpcserver.go @@ -0,0 +1,11 @@ +package utils + +import ( + "github.com/babylonchain/vigilante/config" + "github.com/babylonchain/vigilante/rpcserver" + "google.golang.org/grpc" +) + +func NewRPCServer(cfg *config.Config) (*grpc.Server, error) { + return rpcserver.New(cfg.GRPC.OneTimeTLSKey, cfg.GRPC.RPCKeyFile, cfg.GRPC.RPCCertFile, cfg.GRPC.Endpoints) +} diff --git a/config/config.go b/config/config.go index ab63925a..4be263e2 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,9 @@ package config import ( + "path/filepath" + + "github.com/btcsuite/btcd/btcutil" "github.com/spf13/viper" ) @@ -10,6 +13,22 @@ const ( // DefaultGRPCWebAddress defines the default address to bind the gRPC-web server to. DefaultGRPCWebAddress = "0.0.0.0:8081" + + defaultConfigFilename = "vigilante.conf" + defaultLogLevel = "info" + defaultLogDirname = "logs" + defaultLogFilename = "vigilante.log" + defaultRPCMaxClients = 10 + defaultRPCMaxWebsockets = 25 +) + +var ( + btcdDefaultCAFile = filepath.Join(btcutil.AppDataDir("btcd", false), "rpc.cert") + defaultAppDataDir = btcutil.AppDataDir("babylon-vigilante", false) + defaultConfigFile = filepath.Join(defaultAppDataDir, defaultConfigFilename) + defaultRPCKeyFile = filepath.Join(defaultAppDataDir, "rpc.key") + defaultRPCCertFile = filepath.Join(defaultAppDataDir, "rpc.cert") + defaultLogDir = filepath.Join(defaultAppDataDir, defaultLogDirname) ) // BaseConfig defines the server's basic configuration @@ -29,7 +48,10 @@ type BTCConfig struct { // GRPCConfig defines configuration for the gRPC server. type GRPCConfig struct { - Placeholder string `mapstructure:"placeholder"` + OneTimeTLSKey bool `mapstructure:"onetimetlskey"` + RPCKeyFile string `mapstructure:"rpckey"` + RPCCertFile string `mapstructure:"rpccert"` + Endpoints []string `mapstructure:"endpoints"` } // GRPCWebConfig defines configuration for the gRPC-web server. @@ -60,7 +82,10 @@ func DefaultConfig() *Config { Password: "pass", }, GRPC: GRPCConfig{ - Placeholder: "grpcconfig", + OneTimeTLSKey: true, + RPCKeyFile: defaultRPCKeyFile, + RPCCertFile: defaultRPCCertFile, + Endpoints: []string{"localhost:8080"}, }, GRPCWeb: GRPCWebConfig{ Placeholder: "grpcwebconfig", @@ -83,7 +108,10 @@ func GetConfig(v *viper.Viper) Config { Password: v.GetString("password"), }, GRPC: GRPCConfig{ - Placeholder: v.GetString("placeholder"), + OneTimeTLSKey: v.GetBool("onetimetlskey"), + RPCKeyFile: v.GetString("rpckey"), + RPCCertFile: v.GetString("rpccert"), + Endpoints: v.GetStringSlice("endpoints"), }, GRPCWeb: GRPCWebConfig{ Placeholder: v.GetString("placeholder"), diff --git a/rpcserver/README.md b/rpcserver/README.md index 434e19ce..1683d4d5 100644 --- a/rpcserver/README.md +++ b/rpcserver/README.md @@ -1,3 +1,9 @@ # rpcserver -This package implements a GRPC server. The code is adapted from https://github.com/btcsuite/btcwallet/tree/master/rpc. \ No newline at end of file +This package implements a GRPC server. The code is adapted from https://github.com/btcsuite/btcwallet/tree/master/rpc. + +To test: + +```bash +$ grpcurl --insecure localhost:8080 rpc.VigilanteService/Version +``` \ No newline at end of file diff --git a/rpcserver/server.go b/rpcserver/server.go index a50edade..2f0eea13 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -20,26 +20,41 @@ import ( log "github.com/sirupsen/logrus" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/reflection" ) -func New() (*grpc.Server, error) { - // TODO: TLS and other server opts - server := grpc.NewServer() - StartVigilanteService(server) +func New(oneTimeTLSKey bool, RPCKeyFile string, RPCCertFile string, endpoints []string) (*grpc.Server, error) { - // endpoint - // TODO: config for ip:port - lis, err := net.Listen("tcp", "localhost:8080") + // TODO: TLS and other server opts + keyPair, err := openRPCKeyPair(oneTimeTLSKey, RPCKeyFile, RPCCertFile) if err != nil { - log.Fatalf("failed to listen: %v", err) + return nil, err } + creds := credentials.NewServerTLSFromCert(&keyPair) + + server := grpc.NewServer(grpc.Creds(creds)) + reflection.Register(server) + StartVigilanteService(server) - // start the server listenting to this endpoint - go func() { - if err := server.Serve(lis); err != nil { - log.Errorf("serve RPC server: %v", err) + // create listeners for endpoints + listeners := []net.Listener{} + for _, endpoint := range endpoints { + lis, err := net.Listen("tcp", endpoint) + if err != nil { + log.Fatalf("failed to listen: %v", err) } - }() + listeners = append(listeners, lis) + } + + // start the server with listeners, each in a goroutine + for _, lis := range listeners { + go func(l net.Listener) { + if err := server.Serve(l); err != nil { + log.Errorf("serve RPC server: %v", err) + } + }(lis) + } return server, nil } diff --git a/rpcserver/tls.go b/rpcserver/tls.go new file mode 100644 index 00000000..f49ae9e2 --- /dev/null +++ b/rpcserver/tls.go @@ -0,0 +1,91 @@ +package rpcserver + +import ( + "crypto/tls" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/btcsuite/btcd/btcutil" + log "github.com/sirupsen/logrus" +) + +// openRPCKeyPair creates or loads the RPC TLS keypair specified by the +// application config. This function respects the cfg.OneTimeTLSKey setting. +func openRPCKeyPair(oneTimeTLSKey bool, RPCKeyFile string, RPCCertFile string) (tls.Certificate, error) { + // Check for existence of the TLS key file. If one time TLS keys are + // enabled but a key already exists, this function should error since + // it's possible that a persistent certificate was copied to a remote + // machine. Otherwise, generate a new keypair when the key is missing. + // When generating new persistent keys, overwriting an existing cert is + // acceptable if the previous execution used a one time TLS key. + // Otherwise, both the cert and key should be read from disk. If the + // cert is missing, the read error will occur in LoadX509KeyPair. + _, e := os.Stat(RPCKeyFile) + keyExists := !os.IsNotExist(e) + switch { + case oneTimeTLSKey && keyExists: + err := fmt.Errorf("one time TLS keys are enabled, but TLS key "+ + "`%s` already exists", RPCKeyFile) + return tls.Certificate{}, err + case oneTimeTLSKey: + return generateRPCKeyPair(RPCKeyFile, RPCCertFile, false) + case !keyExists: + return generateRPCKeyPair(RPCKeyFile, RPCCertFile, true) + default: + return tls.LoadX509KeyPair(RPCCertFile, RPCKeyFile) + } +} + +// generateRPCKeyPair generates a new RPC TLS keypair and writes the cert and +// possibly also the key in PEM format to the paths specified by the config. If +// successful, the new keypair is returned. +func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (tls.Certificate, error) { + log.Infof("Generating TLS certificates...") + + // Create directories for cert and key files if they do not yet exist. + certDir, _ := filepath.Split(RPCCertFile) + keyDir, _ := filepath.Split(RPCKeyFile) + err := os.MkdirAll(certDir, 0700) + if err != nil { + return tls.Certificate{}, err + } + err = os.MkdirAll(keyDir, 0700) + if err != nil { + return tls.Certificate{}, err + } + + // Generate cert pair. + org := "Babylon vigilante autogenerated cert" + validUntil := time.Now().Add(time.Hour * 24 * 365 * 10) + cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil) + if err != nil { + return tls.Certificate{}, err + } + keyPair, err := tls.X509KeyPair(cert, key) + if err != nil { + return tls.Certificate{}, err + } + + // Write cert and (potentially) the key files. + err = ioutil.WriteFile(RPCCertFile, cert, 0600) + if err != nil { + return tls.Certificate{}, err + } + if writeKey { + err = ioutil.WriteFile(RPCKeyFile, key, 0600) + if err != nil { + rmErr := os.Remove(RPCCertFile) + if rmErr != nil { + log.Warnf("Cannot remove written certificates: %v", + rmErr) + } + return tls.Certificate{}, err + } + } + + log.Info("Done generating TLS certificates") + return keyPair, nil +} From d11265396768e94d2be5d6798966a829ec4f3485 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 15:11:13 +1000 Subject: [PATCH 09/28] cleanup config --- cmd/reporter/reporter.go | 7 ++++--- cmd/submitter/submitter.go | 7 ++++--- cmd/utils/btcclient.go | 16 ++++++++-------- cmd/utils/rpcserver.go | 11 ----------- rpcserver/server.go | 7 ++++--- 5 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 cmd/utils/rpcserver.go diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index ea3b0b0a..b35cc9aa 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -6,6 +6,7 @@ import ( "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" + "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) @@ -23,7 +24,7 @@ func GetCmd() *cobra.Command { btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client - btcClient, err := utils.NewBTCClient(cfg) + btcClient, err := utils.NewBTCClient(&cfg.BTC) if err != nil { panic(err) } @@ -34,12 +35,12 @@ func GetCmd() *cobra.Command { } // keep trying BTC client - utils.BTCClientConnectLoop(cfg, btcClient) + utils.BTCClientConnectLoop(&cfg.BTC, btcClient) // start reporter and sync reporter.Start() reporter.SynchronizeRPC(btcClient) // start RPC server - server, err := utils.NewRPCServer(cfg) + server, err := rpcserver.New(&cfg.GRPC) if err != nil { panic(err) } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 0194514e..4f0228ea 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -6,6 +6,7 @@ import ( "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" + "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) @@ -23,7 +24,7 @@ func GetCmd() *cobra.Command { btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client - btcClient, err := utils.NewBTCClient(cfg) + btcClient, err := utils.NewBTCClient(&cfg.BTC) if err != nil { panic(err) } @@ -34,12 +35,12 @@ func GetCmd() *cobra.Command { } // keep trying BTC client - utils.BTCClientConnectLoop(cfg, btcClient) + utils.BTCClientConnectLoop(&cfg.BTC, btcClient) // start submitter and sync submitter.Start() submitter.SynchronizeRPC(btcClient) // start RPC server - server, err := utils.NewRPCServer(cfg) + server, err := rpcserver.New(&cfg.GRPC) if err != nil { panic(err) } diff --git a/cmd/utils/btcclient.go b/cmd/utils/btcclient.go index 545ba5b6..8d5d9237 100644 --- a/cmd/utils/btcclient.go +++ b/cmd/utils/btcclient.go @@ -9,20 +9,20 @@ import ( log "github.com/sirupsen/logrus" ) -func NewBTCClient(cfg *config.Config) (*btcclient.Client, error) { +func NewBTCClient(cfg *config.BTCConfig) (*btcclient.Client, error) { certs := readCAFile(cfg) - params := netparams.GetParams(cfg.BTC.NetParams) + params := netparams.GetParams(cfg.NetParams) // TODO: parameterise the reconnect attempts? - client, err := btcclient.New(params.Params, cfg.BTC.Endpoint, cfg.BTC.Username, cfg.BTC.Password, certs, cfg.BTC.DisableClientTLS, 3) + client, err := btcclient.New(params.Params, cfg.Endpoint, cfg.Username, cfg.Password, certs, cfg.DisableClientTLS, 3) if err != nil { return nil, err } return client, nil } -func BTCClientConnectLoop(cfg *config.Config, client *btcclient.Client) { +func BTCClientConnectLoop(cfg *config.BTCConfig, client *btcclient.Client) { go func() { - log.Infof("Attempting RPC client connection to %v", cfg.BTC.Endpoint) + log.Infof("Attempting RPC client connection to %v", cfg.Endpoint) if err := client.Start(); err != nil { log.Errorf("Unable to open connection to consensus RPC server: %v", err) } @@ -30,12 +30,12 @@ func BTCClientConnectLoop(cfg *config.Config, client *btcclient.Client) { }() } -func readCAFile(cfg *config.Config) []byte { +func readCAFile(cfg *config.BTCConfig) []byte { // Read certificate file if TLS is not disabled. var certs []byte - if !cfg.BTC.DisableClientTLS { + if !cfg.DisableClientTLS { var err error - certs, err = ioutil.ReadFile(cfg.BTC.CAFile) + certs, err = ioutil.ReadFile(cfg.CAFile) if err != nil { log.Errorf("Cannot open CA file: %v", err) // If there's an error reading the CA file, continue diff --git a/cmd/utils/rpcserver.go b/cmd/utils/rpcserver.go deleted file mode 100644 index b0b1c3c8..00000000 --- a/cmd/utils/rpcserver.go +++ /dev/null @@ -1,11 +0,0 @@ -package utils - -import ( - "github.com/babylonchain/vigilante/config" - "github.com/babylonchain/vigilante/rpcserver" - "google.golang.org/grpc" -) - -func NewRPCServer(cfg *config.Config) (*grpc.Server, error) { - return rpcserver.New(cfg.GRPC.OneTimeTLSKey, cfg.GRPC.RPCKeyFile, cfg.GRPC.RPCCertFile, cfg.GRPC.Endpoints) -} diff --git a/rpcserver/server.go b/rpcserver/server.go index 2f0eea13..4134d622 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -18,16 +18,17 @@ package rpcserver import ( "net" + "github.com/babylonchain/vigilante/config" log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" ) -func New(oneTimeTLSKey bool, RPCKeyFile string, RPCCertFile string, endpoints []string) (*grpc.Server, error) { +func New(cfg *config.GRPCConfig) (*grpc.Server, error) { // TODO: TLS and other server opts - keyPair, err := openRPCKeyPair(oneTimeTLSKey, RPCKeyFile, RPCCertFile) + keyPair, err := openRPCKeyPair(cfg.OneTimeTLSKey, cfg.RPCKeyFile, cfg.RPCCertFile) if err != nil { return nil, err } @@ -39,7 +40,7 @@ func New(oneTimeTLSKey bool, RPCKeyFile string, RPCCertFile string, endpoints [] // create listeners for endpoints listeners := []net.Listener{} - for _, endpoint := range endpoints { + for _, endpoint := range cfg.Endpoints { lis, err := net.Listen("tcp", endpoint) if err != nil { log.Fatalf("failed to listen: %v", err) From bf3b4931dcf2742370435c8aef636a884403d690 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 15:18:52 +1000 Subject: [PATCH 10/28] config for vigilantes --- cmd/reporter/reporter.go | 2 +- cmd/submitter/submitter.go | 2 +- config/config.go | 30 +++++++++++++++++++++++++++--- vigilante/reporter.go | 3 ++- vigilante/submitter.go | 3 ++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index b35cc9aa..77d89df4 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -29,7 +29,7 @@ func GetCmd() *cobra.Command { panic(err) } // create RPC client - reporter, err := vigilante.NewReporter(btcClient, &btcParams) + reporter, err := vigilante.NewReporter(&cfg.Reporter, btcClient, &btcParams) if err != nil { panic(err) } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 4f0228ea..b0c6e682 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -29,7 +29,7 @@ func GetCmd() *cobra.Command { panic(err) } // create RPC client - submitter, err := vigilante.NewSubmitter(btcClient, &btcParams) + submitter, err := vigilante.NewSubmitter(&cfg.Submitter, btcClient, &btcParams) if err != nil { panic(err) } diff --git a/config/config.go b/config/config.go index 4be263e2..a5b36c46 100644 --- a/config/config.go +++ b/config/config.go @@ -59,13 +59,25 @@ type GRPCWebConfig struct { Placeholder string `mapstructure:"placeholder"` } +// SubmitterConfig defines configuration for the gRPC-web server. +type SubmitterConfig struct { + Placeholder string `mapstructure:"placeholder"` +} + +// ReporterConfig defines configuration for the gRPC-web server. +type ReporterConfig struct { + Placeholder string `mapstructure:"placeholder"` +} + // Config defines the server's top level configuration type Config struct { BaseConfig `mapstructure:",squash"` - BTC BTCConfig `mapstructure:"btc"` - GRPC GRPCConfig `mapstructure:"grpc"` - GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` + BTC BTCConfig `mapstructure:"btc"` + GRPC GRPCConfig `mapstructure:"grpc"` + GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` + Submitter SubmitterConfig `mapstructure:"submitter"` + Reporter ReporterConfig `mapstructure:"reporter"` } // DefaultConfig returns server's default configuration. @@ -90,6 +102,12 @@ func DefaultConfig() *Config { GRPCWeb: GRPCWebConfig{ Placeholder: "grpcwebconfig", }, + Submitter: SubmitterConfig{ + Placeholder: "submitterconfig", + }, + Reporter: ReporterConfig{ + Placeholder: "reporterconfig", + }, } } @@ -116,5 +134,11 @@ func GetConfig(v *viper.Viper) Config { GRPCWeb: GRPCWebConfig{ Placeholder: v.GetString("placeholder"), }, + Submitter: SubmitterConfig{ + Placeholder: v.GetString("placeholder"), + }, + Reporter: ReporterConfig{ + Placeholder: v.GetString("placeholder"), + }, } } diff --git a/vigilante/reporter.go b/vigilante/reporter.go index 1f5e55cb..924d45d3 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/babylonchain/vigilante/btcclient" + "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" ) @@ -22,7 +23,7 @@ type Reporter struct { quitMu sync.Mutex } -func NewReporter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Reporter, error) { +func NewReporter(cfg *config.ReporterConfig, btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Reporter, error) { return &Reporter{ btcClient: btcClient, btcParams: btcParams, diff --git a/vigilante/submitter.go b/vigilante/submitter.go index 0aadd9a7..8dd2980f 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/babylonchain/vigilante/btcclient" + "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" ) @@ -23,7 +24,7 @@ type Submitter struct { quitMu sync.Mutex } -func NewSubmitter(btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Submitter, error) { +func NewSubmitter(cfg *config.SubmitterConfig, btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Submitter, error) { return &Submitter{ btcClient: btcClient, btcParams: btcParams, From 8d21f3794464e47bc005c25b68816560022cd0ed Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 15:29:22 +1000 Subject: [PATCH 11/28] tls for btc client --- btcclient/client.go | 33 +++++++++++++++++-------- btcclient/tls.go | 27 ++++++++++++++++++++ cmd/reporter/reporter.go | 5 ++-- cmd/submitter/submitter.go | 5 ++-- cmd/utils/btcclient.go | 50 -------------------------------------- config/config.go | 38 ++++++++++++++++------------- 6 files changed, 77 insertions(+), 81 deletions(-) create mode 100644 btcclient/tls.go delete mode 100644 cmd/utils/btcclient.go diff --git a/btcclient/client.go b/btcclient/client.go index 0c5dd2b1..6f94244d 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -9,6 +9,8 @@ import ( "sync" "time" + "github.com/babylonchain/vigilante/config" + "github.com/babylonchain/vigilante/netparams" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/gcs" @@ -49,26 +51,27 @@ type Client struct { // but must be done using the Start method. If the remote server does not // operate on the same bitcoin network as described by the passed chain // parameters, the connection will be disconnected. -func New(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, - disableTLS bool, reconnectAttempts int) (*Client, error) { - - if reconnectAttempts < 0 { +func New(cfg *config.BTCConfig) (*Client, error) { + if cfg.ReconnectAttempts < 0 { return nil, errors.New("reconnectAttempts must be positive") } + certs := readCAFile(cfg) + params := netparams.GetParams(cfg.NetParams) + client := &Client{ connConfig: &rpcclient.ConnConfig{ - Host: connect, + Host: cfg.Endpoint, Endpoint: "ws", - User: user, - Pass: pass, + User: cfg.Username, + Pass: cfg.Password, Certificates: certs, DisableAutoReconnect: false, DisableConnectOnNew: true, - DisableTLS: disableTLS, + DisableTLS: cfg.DisableClientTLS, }, - chainParams: chainParams, - reconnectAttempts: reconnectAttempts, + chainParams: params.Params, + reconnectAttempts: cfg.ReconnectAttempts, enqueueNotification: make(chan interface{}), dequeueNotification: make(chan interface{}), currentBlock: make(chan *waddrmgr.BlockStamp), @@ -91,6 +94,16 @@ func New(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, return client, nil } +func (c *Client) ConnectLoop(cfg *config.BTCConfig) { + go func() { + log.Infof("Attempting RPC client connection to %v", cfg.Endpoint) + if err := c.Start(); err != nil { + log.Errorf("Unable to open connection to consensus RPC server: %v", err) + } + c.WaitForShutdown() + }() +} + // BackEnd returns the name of the driver. func (c *Client) BackEnd() string { return "btcd" diff --git a/btcclient/tls.go b/btcclient/tls.go new file mode 100644 index 00000000..44cd4c8e --- /dev/null +++ b/btcclient/tls.go @@ -0,0 +1,27 @@ +package btcclient + +import ( + "io/ioutil" + + "github.com/babylonchain/vigilante/config" + log "github.com/sirupsen/logrus" +) + +func readCAFile(cfg *config.BTCConfig) []byte { + // Read certificate file if TLS is not disabled. + var certs []byte + if !cfg.DisableClientTLS { + var err error + certs, err = ioutil.ReadFile(cfg.CAFile) + if err != nil { + log.Errorf("Cannot open CA file: %v", err) + // If there's an error reading the CA file, continue + // with nil certs and without the client connection. + certs = nil + } + } else { + log.Infof("Chain server RPC TLS is disabled") + } + + return certs +} diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index 77d89df4..27f60d0d 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -3,6 +3,7 @@ package reporter import ( "fmt" + "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" @@ -24,7 +25,7 @@ func GetCmd() *cobra.Command { btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client - btcClient, err := utils.NewBTCClient(&cfg.BTC) + btcClient, err := btcclient.New(&cfg.BTC) if err != nil { panic(err) } @@ -35,7 +36,7 @@ func GetCmd() *cobra.Command { } // keep trying BTC client - utils.BTCClientConnectLoop(&cfg.BTC, btcClient) + btcClient.ConnectLoop(&cfg.BTC) // start reporter and sync reporter.Start() reporter.SynchronizeRPC(btcClient) diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index b0c6e682..3a8eccfa 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -3,6 +3,7 @@ package submitter import ( "fmt" + "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" @@ -24,7 +25,7 @@ func GetCmd() *cobra.Command { btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client - btcClient, err := utils.NewBTCClient(&cfg.BTC) + btcClient, err := btcclient.New(&cfg.BTC) if err != nil { panic(err) } @@ -35,7 +36,7 @@ func GetCmd() *cobra.Command { } // keep trying BTC client - utils.BTCClientConnectLoop(&cfg.BTC, btcClient) + btcClient.ConnectLoop(&cfg.BTC) // start submitter and sync submitter.Start() submitter.SynchronizeRPC(btcClient) diff --git a/cmd/utils/btcclient.go b/cmd/utils/btcclient.go deleted file mode 100644 index 8d5d9237..00000000 --- a/cmd/utils/btcclient.go +++ /dev/null @@ -1,50 +0,0 @@ -package utils - -import ( - "io/ioutil" - - "github.com/babylonchain/vigilante/btcclient" - "github.com/babylonchain/vigilante/config" - "github.com/babylonchain/vigilante/netparams" - log "github.com/sirupsen/logrus" -) - -func NewBTCClient(cfg *config.BTCConfig) (*btcclient.Client, error) { - certs := readCAFile(cfg) - params := netparams.GetParams(cfg.NetParams) - // TODO: parameterise the reconnect attempts? - client, err := btcclient.New(params.Params, cfg.Endpoint, cfg.Username, cfg.Password, certs, cfg.DisableClientTLS, 3) - if err != nil { - return nil, err - } - return client, nil -} - -func BTCClientConnectLoop(cfg *config.BTCConfig, client *btcclient.Client) { - go func() { - log.Infof("Attempting RPC client connection to %v", cfg.Endpoint) - if err := client.Start(); err != nil { - log.Errorf("Unable to open connection to consensus RPC server: %v", err) - } - client.WaitForShutdown() - }() -} - -func readCAFile(cfg *config.BTCConfig) []byte { - // Read certificate file if TLS is not disabled. - var certs []byte - if !cfg.DisableClientTLS { - var err error - certs, err = ioutil.ReadFile(cfg.CAFile) - if err != nil { - log.Errorf("Cannot open CA file: %v", err) - // If there's an error reading the CA file, continue - // with nil certs and without the client connection. - certs = nil - } - } else { - log.Infof("Chain server RPC TLS is disabled") - } - - return certs -} diff --git a/config/config.go b/config/config.go index a5b36c46..320f6bf5 100644 --- a/config/config.go +++ b/config/config.go @@ -38,12 +38,13 @@ type BaseConfig struct { // BTCConfig defines the server's basic configuration type BTCConfig struct { - DisableClientTLS bool `mapstructure:"noclienttls"` - CAFile string `mapstructure:"cafile"` - Endpoint string `mapstructure:"endpoint"` - NetParams string `mapstructure:"netparams"` - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` + DisableClientTLS bool `mapstructure:"noclienttls"` + CAFile string `mapstructure:"cafile"` + Endpoint string `mapstructure:"endpoint"` + NetParams string `mapstructure:"netparams"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + ReconnectAttempts int `mapstructure:"reconnect"` } // GRPCConfig defines configuration for the gRPC server. @@ -87,11 +88,13 @@ func DefaultConfig() *Config { Placeholder: "baseconfig", }, BTC: BTCConfig{ - DisableClientTLS: true, - Endpoint: "localhost:18554", - NetParams: "simnet", - Username: "user", - Password: "pass", + DisableClientTLS: false, + CAFile: btcdDefaultCAFile, + Endpoint: "localhost:18554", + NetParams: "simnet", + Username: "user", + Password: "pass", + ReconnectAttempts: 3, }, GRPC: GRPCConfig{ OneTimeTLSKey: true, @@ -118,12 +121,13 @@ func GetConfig(v *viper.Viper) Config { Placeholder: v.GetString("placeholder"), }, BTC: BTCConfig{ - DisableClientTLS: v.GetBool("noclienttls"), - CAFile: v.GetString("cafile"), - Endpoint: v.GetString("endpoint"), - NetParams: v.GetString("netparams"), - Username: v.GetString("username"), - Password: v.GetString("password"), + DisableClientTLS: v.GetBool("noclienttls"), + CAFile: v.GetString("cafile"), + Endpoint: v.GetString("endpoint"), + NetParams: v.GetString("netparams"), + Username: v.GetString("username"), + Password: v.GetString("password"), + ReconnectAttempts: v.GetInt("reconnect"), }, GRPC: GRPCConfig{ OneTimeTLSKey: v.GetBool("onetimetlskey"), From 91f849e608e4097baa5d113d1729659436993ff4 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 15:43:33 +1000 Subject: [PATCH 12/28] cleanup --- btcclient/client.go | 5 +++-- rpcserver/server.go | 4 ++-- rpcserver/tls.go | 4 ++-- vigilante/reporter.go | 4 ++++ vigilante/submitter.go | 4 ++++ 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/btcclient/client.go b/btcclient/client.go index 6f94244d..a1395c30 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -96,10 +96,11 @@ func New(cfg *config.BTCConfig) (*Client, error) { func (c *Client) ConnectLoop(cfg *config.BTCConfig) { go func() { - log.Infof("Attempting RPC client connection to %v", cfg.Endpoint) + log.Infof("Start connecting to the BTC node %v", cfg.Endpoint) if err := c.Start(); err != nil { - log.Errorf("Unable to open connection to consensus RPC server: %v", err) + log.Errorf("Unable to connect to the BTC node: %v", err) } + log.Infof("Successfully connected to the BTC node") c.WaitForShutdown() }() } diff --git a/rpcserver/server.go b/rpcserver/server.go index 4134d622..5c4f4493 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -26,8 +26,6 @@ import ( ) func New(cfg *config.GRPCConfig) (*grpc.Server, error) { - - // TODO: TLS and other server opts keyPair, err := openRPCKeyPair(cfg.OneTimeTLSKey, cfg.RPCKeyFile, cfg.RPCCertFile) if err != nil { return nil, err @@ -57,5 +55,7 @@ func New(cfg *config.GRPCConfig) (*grpc.Server, error) { }(lis) } + log.Infof("Successfully started the GRPC server") + return server, nil } diff --git a/rpcserver/tls.go b/rpcserver/tls.go index f49ae9e2..96ee6dbe 100644 --- a/rpcserver/tls.go +++ b/rpcserver/tls.go @@ -43,7 +43,7 @@ func openRPCKeyPair(oneTimeTLSKey bool, RPCKeyFile string, RPCCertFile string) ( // possibly also the key in PEM format to the paths specified by the config. If // successful, the new keypair is returned. func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (tls.Certificate, error) { - log.Infof("Generating TLS certificates...") + log.Infof("Generating TLS certificates for the RPC server...") // Create directories for cert and key files if they do not yet exist. certDir, _ := filepath.Split(RPCCertFile) @@ -86,6 +86,6 @@ func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (t } } - log.Info("Done generating TLS certificates") + log.Infof("Successsfully generates TLS certificates for the RPC server") return keyPair, nil } diff --git a/vigilante/reporter.go b/vigilante/reporter.go index 924d45d3..673e9239 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -7,6 +7,7 @@ import ( "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" + log "github.com/sirupsen/logrus" ) type Reporter struct { @@ -49,6 +50,8 @@ func (r *Reporter) Start() { } r.quitMu.Unlock() + log.Infof("Successfully created the vigilant reporter") + // r.wg.Add(2) // go r.txCreator() // go r.walletLocker() @@ -80,6 +83,7 @@ func (r *Reporter) SynchronizeRPC(btcClient btcclient.Interface) { r.btcClient = btcClient r.btcClientLock.Unlock() + // TODO: add internal logic of reporter // TODO: It would be preferable to either run these goroutines // separately from the vigilante (use vigilante mutator functions to // make changes from the RPC client) and not have to stop and diff --git a/vigilante/submitter.go b/vigilante/submitter.go index 8dd2980f..8e9dc05d 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -7,6 +7,7 @@ import ( "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" + log "github.com/sirupsen/logrus" ) type Submitter struct { @@ -50,6 +51,8 @@ func (s *Submitter) Start() { } s.quitMu.Unlock() + log.Infof("Successfully created the vigilant submitter") + // s.wg.Add(2) // go s.txCreator() // go s.walletLocker() @@ -81,6 +84,7 @@ func (s *Submitter) SynchronizeRPC(btcClient btcclient.Interface) { s.btcClient = btcClient s.btcClientLock.Unlock() + // TODO: add internal logic of submitter // TODO: It would be preferable to either run these goroutines // separately from the vigilante (use vigilante mutator functions to // make changes from the RPC client) and not have to stop and From 263a6cda3e5c799f1ca6cdb1f334fa84f9dbdcf7 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 5 Aug 2022 16:08:19 +1000 Subject: [PATCH 13/28] typo --- rpcserver/tls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcserver/tls.go b/rpcserver/tls.go index 96ee6dbe..61f06ae7 100644 --- a/rpcserver/tls.go +++ b/rpcserver/tls.go @@ -86,6 +86,6 @@ func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (t } } - log.Infof("Successsfully generates TLS certificates for the RPC server") + log.Infof("Successfully generates TLS certificates for the RPC server") return keyPair, nil } From cb786211f00e5b238deb00ddc13169320b6d50fd Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 8 Aug 2022 16:04:54 +1000 Subject: [PATCH 14/28] read config file --- .gitignore | 1 + cmd/reporter/reporter.go | 7 +++-- cmd/submitter/submitter.go | 7 +++-- config/config.go | 56 ++++++++++++++++---------------------- rpcserver/tls.go | 2 +- 5 files changed, 35 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index ae90729c..fc9c43b0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ +.vscode/ main \ No newline at end of file diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index 27f60d0d..dadff7b9 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -20,8 +20,11 @@ func GetCmd() *cobra.Command { Short: "Vigilant reporter", Run: func(cmd *cobra.Command, args []string) { // load config - // TODO: read config from a file - cfg := config.DefaultConfig() + // TODO: CLI agruments on customised config file + cfg, err := config.New() + if err != nil { + panic(err) + } btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 3a8eccfa..5725f524 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -20,8 +20,11 @@ func GetCmd() *cobra.Command { Short: "Vigilant submitter", Run: func(cmd *cobra.Command, args []string) { // load config - // TODO: read config from a file - cfg := config.DefaultConfig() + // TODO: CLI agruments on customised config file + cfg, err := config.New() + if err != nil { + panic(err) + } btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client diff --git a/config/config.go b/config/config.go index 320f6bf5..a3f1dd9e 100644 --- a/config/config.go +++ b/config/config.go @@ -1,9 +1,12 @@ package config import ( + "errors" + "os" "path/filepath" "github.com/btcsuite/btcd/btcutil" + log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -14,7 +17,7 @@ const ( // DefaultGRPCWebAddress defines the default address to bind the gRPC-web server to. DefaultGRPCWebAddress = "0.0.0.0:8081" - defaultConfigFilename = "vigilante.conf" + defaultConfigFilename = "vigilante.yaml" defaultLogLevel = "info" defaultLogDirname = "logs" defaultLogFilename = "vigilante.log" @@ -72,8 +75,7 @@ type ReporterConfig struct { // Config defines the server's top level configuration type Config struct { - BaseConfig `mapstructure:",squash"` - + Base BaseConfig `mapstructure:"base"` BTC BTCConfig `mapstructure:"btc"` GRPC GRPCConfig `mapstructure:"grpc"` GRPCWeb GRPCWebConfig `mapstructure:"grpc-web"` @@ -84,7 +86,7 @@ type Config struct { // DefaultConfig returns server's default configuration. func DefaultConfig() *Config { return &Config{ - BaseConfig: BaseConfig{ + Base: BaseConfig{ Placeholder: "baseconfig", }, BTC: BTCConfig{ @@ -115,34 +117,22 @@ func DefaultConfig() *Config { } // GetConfig returns a fully parsed Config object. -func GetConfig(v *viper.Viper) Config { - return Config{ - BaseConfig: BaseConfig{ - Placeholder: v.GetString("placeholder"), - }, - BTC: BTCConfig{ - DisableClientTLS: v.GetBool("noclienttls"), - CAFile: v.GetString("cafile"), - Endpoint: v.GetString("endpoint"), - NetParams: v.GetString("netparams"), - Username: v.GetString("username"), - Password: v.GetString("password"), - ReconnectAttempts: v.GetInt("reconnect"), - }, - GRPC: GRPCConfig{ - OneTimeTLSKey: v.GetBool("onetimetlskey"), - RPCKeyFile: v.GetString("rpckey"), - RPCCertFile: v.GetString("rpccert"), - Endpoints: v.GetStringSlice("endpoints"), - }, - GRPCWeb: GRPCWebConfig{ - Placeholder: v.GetString("placeholder"), - }, - Submitter: SubmitterConfig{ - Placeholder: v.GetString("placeholder"), - }, - Reporter: ReporterConfig{ - Placeholder: v.GetString("placeholder"), - }, +// TODO: read non-default config file from CLI +func New() (Config, error) { + if _, err := os.Stat(defaultConfigFile); err == nil { // read config from default config file + viper.SetConfigFile(defaultConfigFile) + if err := viper.ReadInConfig(); err != nil { + return Config{}, err + } + log.Infof("successfully loaded config file at %s", defaultConfigFile) + var cfg Config + err = viper.Unmarshal(&cfg) + return cfg, err + } else if errors.Is(err, os.ErrNotExist) { // default config file does not exist, use the default config + log.Infof("no config file found at %s, using the default config", defaultConfigFile) + cfg := DefaultConfig() + return *cfg, nil + } else { // other errors + return Config{}, err } } diff --git a/rpcserver/tls.go b/rpcserver/tls.go index 61f06ae7..96ccdee1 100644 --- a/rpcserver/tls.go +++ b/rpcserver/tls.go @@ -86,6 +86,6 @@ func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (t } } - log.Infof("Successfully generates TLS certificates for the RPC server") + log.Infof("Successfully generated TLS certificates for the RPC server") return keyPair, nil } From 1c373f2c188cb370920b95df7a655f9b163caa7e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 8 Aug 2022 18:04:04 +1000 Subject: [PATCH 15/28] refactor --- cmd/reporter/reporter.go | 90 +++++++++++++++++++------------------- cmd/submitter/submitter.go | 90 +++++++++++++++++++------------------- config/config.go | 34 +++++++++++++- 3 files changed, 124 insertions(+), 90 deletions(-) diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index dadff7b9..01bba043 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -18,54 +18,56 @@ func GetCmd() *cobra.Command { cmd := &cobra.Command{ Use: "reporter", Short: "Vigilant reporter", - Run: func(cmd *cobra.Command, args []string) { - // load config - // TODO: CLI agruments on customised config file - cfg, err := config.New() - if err != nil { - panic(err) - } - btcParams := netparams.GetParams(cfg.BTC.NetParams) + Run: cmdFunc, + } + addFlags(cmd) + return cmd +} - // create BTC client - btcClient, err := btcclient.New(&cfg.BTC) - if err != nil { - panic(err) - } - // create RPC client - reporter, err := vigilante.NewReporter(&cfg.Reporter, btcClient, &btcParams) - if err != nil { - panic(err) - } +func addFlags(cmd *cobra.Command) { + // TODO: CLI agruments on customised config file +} - // keep trying BTC client - btcClient.ConnectLoop(&cfg.BTC) - // start reporter and sync - reporter.Start() - reporter.SynchronizeRPC(btcClient) - // start RPC server - server, err := rpcserver.New(&cfg.GRPC) - if err != nil { - panic(err) - } +func cmdFunc(cmd *cobra.Command, args []string) { + // get the config singleton + cfg := config.Cfg + btcParams := netparams.GetParams(cfg.BTC.NetParams) - // SIGINT handling stuff - utils.AddInterruptHandler(func() { - // TODO: Does this need to wait for the grpc server to finish up any requests? - fmt.Println("Stopping RPC server...") - server.Stop() - fmt.Println("RPC server shutdown") - }) - utils.AddInterruptHandler(func() { - fmt.Println("Stopping BTC client...") - btcClient.Stop() - fmt.Println("BTC client shutdown") - }) + // create BTC client + btcClient, err := btcclient.New(&cfg.BTC) + if err != nil { + panic(err) + } + // create RPC client + reporter, err := vigilante.NewReporter(&cfg.Reporter, btcClient, &btcParams) + if err != nil { + panic(err) + } - <-utils.InterruptHandlersDone - fmt.Println("Shutdown complete") - }, + // keep trying BTC client + btcClient.ConnectLoop(&cfg.BTC) + // start reporter and sync + reporter.Start() + reporter.SynchronizeRPC(btcClient) + // start RPC server + server, err := rpcserver.New(&cfg.GRPC) + if err != nil { + panic(err) } - return cmd + // SIGINT handling stuff + utils.AddInterruptHandler(func() { + // TODO: Does this need to wait for the grpc server to finish up any requests? + fmt.Println("Stopping RPC server...") + server.Stop() + fmt.Println("RPC server shutdown") + }) + utils.AddInterruptHandler(func() { + fmt.Println("Stopping BTC client...") + btcClient.Stop() + fmt.Println("BTC client shutdown") + }) + + <-utils.InterruptHandlersDone + fmt.Println("Shutdown complete") } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 5725f524..31fd4519 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -18,54 +18,56 @@ func GetCmd() *cobra.Command { cmd := &cobra.Command{ Use: "submitter", Short: "Vigilant submitter", - Run: func(cmd *cobra.Command, args []string) { - // load config - // TODO: CLI agruments on customised config file - cfg, err := config.New() - if err != nil { - panic(err) - } - btcParams := netparams.GetParams(cfg.BTC.NetParams) + Run: cmdFunc, + } + addFlags(cmd) + return cmd +} - // create BTC client - btcClient, err := btcclient.New(&cfg.BTC) - if err != nil { - panic(err) - } - // create RPC client - submitter, err := vigilante.NewSubmitter(&cfg.Submitter, btcClient, &btcParams) - if err != nil { - panic(err) - } +func addFlags(cmd *cobra.Command) { + // TODO: CLI agruments on customised config file +} - // keep trying BTC client - btcClient.ConnectLoop(&cfg.BTC) - // start submitter and sync - submitter.Start() - submitter.SynchronizeRPC(btcClient) - // start RPC server - server, err := rpcserver.New(&cfg.GRPC) - if err != nil { - panic(err) - } +func cmdFunc(cmd *cobra.Command, args []string) { + // get the config singleton + cfg := config.Cfg + btcParams := netparams.GetParams(cfg.BTC.NetParams) - // SIGINT handling stuff - utils.AddInterruptHandler(func() { - // TODO: Does this need to wait for the grpc server to finish up any requests? - fmt.Println("Stopping RPC server...") - server.Stop() - fmt.Println("RPC server shutdown") - }) - utils.AddInterruptHandler(func() { - fmt.Println("Stopping BTC client...") - btcClient.Stop() - fmt.Println("BTC client shutdown") - }) + // create BTC client + btcClient, err := btcclient.New(&cfg.BTC) + if err != nil { + panic(err) + } + // create RPC client + submitter, err := vigilante.NewSubmitter(&cfg.Submitter, btcClient, &btcParams) + if err != nil { + panic(err) + } - <-utils.InterruptHandlersDone - fmt.Println("Shutdown complete") - }, + // keep trying BTC client + btcClient.ConnectLoop(&cfg.BTC) + // start submitter and sync + submitter.Start() + submitter.SynchronizeRPC(btcClient) + // start RPC server + server, err := rpcserver.New(&cfg.GRPC) + if err != nil { + panic(err) } - return cmd + // SIGINT handling stuff + utils.AddInterruptHandler(func() { + // TODO: Does this need to wait for the grpc server to finish up any requests? + fmt.Println("Stopping RPC server...") + server.Stop() + fmt.Println("RPC server shutdown") + }) + utils.AddInterruptHandler(func() { + fmt.Println("Stopping BTC client...") + btcClient.Stop() + fmt.Println("BTC client shutdown") + }) + + <-utils.InterruptHandlersDone + fmt.Println("Shutdown complete") } diff --git a/config/config.go b/config/config.go index a3f1dd9e..a5c90944 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "errors" + "fmt" "os" "path/filepath" @@ -34,6 +35,16 @@ var ( defaultLogDir = filepath.Join(defaultAppDataDir, defaultLogDirname) ) +var Cfg Config + +func init() { + var err error + Cfg, err = New() + if err != nil { + panic(err) + } +} + // BaseConfig defines the server's basic configuration type BaseConfig struct { Placeholder string `mapstructure:"placeholder"` @@ -116,8 +127,9 @@ func DefaultConfig() *Config { } } -// GetConfig returns a fully parsed Config object. -// TODO: read non-default config file from CLI +// New returns a fully parsed Config object, from either +// - the config file in the default directory, or +// - the default config object (if the config file in the default directory does not exist) func New() (Config, error) { if _, err := os.Stat(defaultConfigFile); err == nil { // read config from default config file viper.SetConfigFile(defaultConfigFile) @@ -136,3 +148,21 @@ func New() (Config, error) { return Config{}, err } } + +// NewFromFile returns a fully parsed Config object from a given file directory +func NewFromFile(configFile string) (Config, error) { + if _, err := os.Stat(configFile); err == nil { // the given file exists, parse it + viper.SetConfigFile(configFile) + if err := viper.ReadInConfig(); err != nil { + return Config{}, err + } + log.Infof("successfully loaded config file at %s", configFile) + var cfg Config + err = viper.Unmarshal(&cfg) + return cfg, err + } else if errors.Is(err, os.ErrNotExist) { // the given config file does not exist, return error + return Config{}, fmt.Errorf("no config file found at %s", configFile) + } else { // other errors + return Config{}, err + } +} From fb17b8bd7db102a1939c67fb3f59b62f35d9f08e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 9 Aug 2022 10:01:35 +1000 Subject: [PATCH 16/28] CLI flag for config file, and sample config --- cmd/reporter/reporter.go | 19 ++++++++++++++++--- cmd/submitter/submitter.go | 19 ++++++++++++++++--- config/config.go | 10 ---------- sample-vigilante.yaml | 22 ++++++++++++++++++++++ 4 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 sample-vigilante.yaml diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index 01bba043..bc96aee4 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -12,6 +12,10 @@ import ( "github.com/spf13/cobra" ) +var ( + cfgFile string +) + // GetCmd returns the cli query commands for this module func GetCmd() *cobra.Command { // Group epoching queries under a subcommand @@ -25,12 +29,21 @@ func GetCmd() *cobra.Command { } func addFlags(cmd *cobra.Command) { - // TODO: CLI agruments on customised config file + cmd.Flags().StringVar(&cfgFile, "config", "", "config file") } func cmdFunc(cmd *cobra.Command, args []string) { - // get the config singleton - cfg := config.Cfg + // get the config from the given file, the default file, or generate a default config + var err error + var cfg config.Config + if len(cfgFile) != 0 { + cfg, err = config.NewFromFile(cfgFile) + } else { + cfg, err = config.New() + } + if err != nil { + panic(err) + } btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 31fd4519..60588ff8 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -12,6 +12,10 @@ import ( "github.com/spf13/cobra" ) +var ( + cfgFile string +) + // GetCmd returns the cli query commands for this module func GetCmd() *cobra.Command { // Group epoching queries under a subcommand @@ -25,12 +29,21 @@ func GetCmd() *cobra.Command { } func addFlags(cmd *cobra.Command) { - // TODO: CLI agruments on customised config file + cmd.Flags().StringVar(&cfgFile, "config", "", "config file") } func cmdFunc(cmd *cobra.Command, args []string) { - // get the config singleton - cfg := config.Cfg + // get the config from the given file, the default file, or generate a default config + var err error + var cfg config.Config + if len(cfgFile) != 0 { + cfg, err = config.NewFromFile(cfgFile) + } else { + cfg, err = config.New() + } + if err != nil { + panic(err) + } btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client diff --git a/config/config.go b/config/config.go index a5c90944..a66b0893 100644 --- a/config/config.go +++ b/config/config.go @@ -35,16 +35,6 @@ var ( defaultLogDir = filepath.Join(defaultAppDataDir, defaultLogDirname) ) -var Cfg Config - -func init() { - var err error - Cfg, err = New() - if err != nil { - panic(err) - } -} - // BaseConfig defines the server's basic configuration type BaseConfig struct { Placeholder string `mapstructure:"placeholder"` diff --git a/sample-vigilante.yaml b/sample-vigilante.yaml new file mode 100644 index 00000000..e36c4b97 --- /dev/null +++ b/sample-vigilante.yaml @@ -0,0 +1,22 @@ +base: + placeholder: baseconfig +btc: + cafile: /Users/rhan0013/Library/Application Support/Btcd/rpc.cert + endpoint: localhost:18554 + netparams: simnet + noclienttls: false + password: pass + reconnect: 3 + username: user +grpc: + endpoints: + - localhost:8080 + onetimetlskey: true + rpccert: /Users/rhan0013/Library/Application Support/Babylon-vigilante/rpc.cert + rpckey: /Users/rhan0013/Library/Application Support/Babylon-vigilante/rpc.key +grpc-web: + placeholder: grpcwebconfig +reporter: + placeholder: reporterconfig +submitter: + placeholder: submitterconfig From 5fc85d61ea86ccb3ce288a45dfad8393b1ff3423 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 9 Aug 2022 11:58:49 +1000 Subject: [PATCH 17/28] log for submodules --- btcclient/bitcoind_client.go | 1 - btcclient/bitcoind_conn.go | 1 - btcclient/bitcoind_events.go | 1 - btcclient/block_filterer.go | 1 - btcclient/client.go | 3 +-- btcclient/log.go | 7 +++++++ btcclient/pruned_block_dispatcher.go | 1 - btcclient/tls.go | 1 - cmd/reporter/reporter.go | 13 ++++++------- cmd/submitter/submitter.go | 13 ++++++------- cmd/utils/signal.go | 6 +++--- config/config.go | 1 - config/log.go | 7 +++++++ log/logger.go | 16 ++++++++++++++++ rpcserver/log.go | 7 +++++++ rpcserver/server.go | 1 - rpcserver/tls.go | 3 +-- vigilante/log.go | 7 +++++++ vigilante/reporter.go | 1 - vigilante/submitter.go | 1 - 20 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 btcclient/log.go create mode 100644 config/log.go create mode 100644 log/logger.go create mode 100644 rpcserver/log.go create mode 100644 vigilante/log.go diff --git a/btcclient/bitcoind_client.go b/btcclient/bitcoind_client.go index 4cdff30f..890903e9 100644 --- a/btcclient/bitcoind_client.go +++ b/btcclient/bitcoind_client.go @@ -16,7 +16,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wtxmgr" - log "github.com/sirupsen/logrus" ) var ( diff --git a/btcclient/bitcoind_conn.go b/btcclient/bitcoind_conn.go index 6b5f0bff..a06d20ff 100644 --- a/btcclient/bitcoind_conn.go +++ b/btcclient/bitcoind_conn.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/ticker" - log "github.com/sirupsen/logrus" ) const ( diff --git a/btcclient/bitcoind_events.go b/btcclient/bitcoind_events.go index f3751580..945257b7 100644 --- a/btcclient/bitcoind_events.go +++ b/btcclient/bitcoind_events.go @@ -12,7 +12,6 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/gozmq" - log "github.com/sirupsen/logrus" ) const ( diff --git a/btcclient/block_filterer.go b/btcclient/block_filterer.go index 154ae114..e6fb348f 100644 --- a/btcclient/block_filterer.go +++ b/btcclient/block_filterer.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" - log "github.com/sirupsen/logrus" ) // BlockFilterer is used to iteratively scan blocks for a set of addresses of diff --git a/btcclient/client.go b/btcclient/client.go index a1395c30..8e633f79 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -22,7 +22,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wtxmgr" - log "github.com/sirupsen/logrus" ) var _ Interface = &Client{} @@ -100,7 +99,7 @@ func (c *Client) ConnectLoop(cfg *config.BTCConfig) { if err := c.Start(); err != nil { log.Errorf("Unable to connect to the BTC node: %v", err) } - log.Infof("Successfully connected to the BTC node") + log.Info("Successfully connected to the BTC node") c.WaitForShutdown() }() } diff --git a/btcclient/log.go b/btcclient/log.go new file mode 100644 index 00000000..c9bba19e --- /dev/null +++ b/btcclient/log.go @@ -0,0 +1,7 @@ +package btcclient + +import ( + vlog "github.com/babylonchain/vigilante/log" +) + +var log = vlog.Logger.WithField("module", "btcclient") diff --git a/btcclient/pruned_block_dispatcher.go b/btcclient/pruned_block_dispatcher.go index f0291792..8a1e7114 100644 --- a/btcclient/pruned_block_dispatcher.go +++ b/btcclient/pruned_block_dispatcher.go @@ -19,7 +19,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/neutrino/query" "github.com/lightningnetwork/lnd/ticker" - log "github.com/sirupsen/logrus" ) const ( diff --git a/btcclient/tls.go b/btcclient/tls.go index 44cd4c8e..176fc503 100644 --- a/btcclient/tls.go +++ b/btcclient/tls.go @@ -4,7 +4,6 @@ import ( "io/ioutil" "github.com/babylonchain/vigilante/config" - log "github.com/sirupsen/logrus" ) func readCAFile(cfg *config.BTCConfig) []byte { diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index bc96aee4..d268a563 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -1,11 +1,10 @@ package reporter import ( - "fmt" - "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" + vlog "github.com/babylonchain/vigilante/log" "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" @@ -71,16 +70,16 @@ func cmdFunc(cmd *cobra.Command, args []string) { // SIGINT handling stuff utils.AddInterruptHandler(func() { // TODO: Does this need to wait for the grpc server to finish up any requests? - fmt.Println("Stopping RPC server...") + vlog.Logger.WithField("module", "cmd").Info("Stopping RPC server...") server.Stop() - fmt.Println("RPC server shutdown") + vlog.Logger.WithField("module", "cmd").Info("RPC server shutdown") }) utils.AddInterruptHandler(func() { - fmt.Println("Stopping BTC client...") + vlog.Logger.WithField("module", "cmd").Info("Stopping BTC client...") btcClient.Stop() - fmt.Println("BTC client shutdown") + vlog.Logger.WithField("module", "cmd").Info("BTC client shutdown") }) <-utils.InterruptHandlersDone - fmt.Println("Shutdown complete") + vlog.Logger.WithField("module", "cmd").Info("Shutdown complete") } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 60588ff8..83dbcc9c 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -1,11 +1,10 @@ package submitter import ( - "fmt" - "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" + vlog "github.com/babylonchain/vigilante/log" "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" @@ -71,16 +70,16 @@ func cmdFunc(cmd *cobra.Command, args []string) { // SIGINT handling stuff utils.AddInterruptHandler(func() { // TODO: Does this need to wait for the grpc server to finish up any requests? - fmt.Println("Stopping RPC server...") + vlog.Logger.WithField("module", "cmd").Info("Stopping RPC server...") server.Stop() - fmt.Println("RPC server shutdown") + vlog.Logger.WithField("module", "cmd").Info("RPC server shutdown") }) utils.AddInterruptHandler(func() { - fmt.Println("Stopping BTC client...") + vlog.Logger.WithField("module", "cmd").Info("Stopping BTC client...") btcClient.Stop() - fmt.Println("BTC client shutdown") + vlog.Logger.WithField("module", "cmd").Info("BTC client shutdown") }) <-utils.InterruptHandlersDone - fmt.Println("Shutdown complete") + vlog.Logger.WithField("module", "cmd").Info("Shutdown complete") } diff --git a/cmd/utils/signal.go b/cmd/utils/signal.go index e15d7db2..3eb7bc57 100644 --- a/cmd/utils/signal.go +++ b/cmd/utils/signal.go @@ -8,7 +8,7 @@ import ( "os" "os/signal" - log "github.com/sirupsen/logrus" + vlog "github.com/babylonchain/vigilante/log" ) // interruptChannel is used to receive SIGINT (Ctrl+C) signals. @@ -56,11 +56,11 @@ func mainInterruptHandler() { for { select { case sig := <-interruptChannel: - log.Infof("Received signal (%s). Shutting down...", sig) + vlog.Logger.WithField("module", "cmd").Infof("Received signal (%s). Shutting down...", sig) invokeCallbacks() return case <-simulateInterruptChannel: - log.Infof("Received shutdown request. Shutting down...") + vlog.Logger.WithField("module", "cmd").Infof("Received shutdown request. Shutting down...") invokeCallbacks() return diff --git a/config/config.go b/config/config.go index a66b0893..d0c5b5ea 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,6 @@ import ( "path/filepath" "github.com/btcsuite/btcd/btcutil" - log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) diff --git a/config/log.go b/config/log.go new file mode 100644 index 00000000..bf2e706d --- /dev/null +++ b/config/log.go @@ -0,0 +1,7 @@ +package config + +import ( + vlog "github.com/babylonchain/vigilante/log" +) + +var log = vlog.Logger.WithField("module", "btcclient") diff --git a/log/logger.go b/log/logger.go new file mode 100644 index 00000000..0a3ba486 --- /dev/null +++ b/log/logger.go @@ -0,0 +1,16 @@ +package log + +import ( + "os" + + "github.com/sirupsen/logrus" +) + +var Logger = &logrus.Logger{ + Out: os.Stderr, + Level: logrus.DebugLevel, + Formatter: &logrus.TextFormatter{ + DisableColors: false, + FullTimestamp: true, + }, +} diff --git a/rpcserver/log.go b/rpcserver/log.go new file mode 100644 index 00000000..0b8d9c96 --- /dev/null +++ b/rpcserver/log.go @@ -0,0 +1,7 @@ +package rpcserver + +import ( + vlog "github.com/babylonchain/vigilante/log" +) + +var log = vlog.Logger.WithField("module", "rpcserver") diff --git a/rpcserver/server.go b/rpcserver/server.go index 5c4f4493..b139b12d 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -19,7 +19,6 @@ import ( "net" "github.com/babylonchain/vigilante/config" - log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" diff --git a/rpcserver/tls.go b/rpcserver/tls.go index 96ccdee1..84c4ece1 100644 --- a/rpcserver/tls.go +++ b/rpcserver/tls.go @@ -9,7 +9,6 @@ import ( "time" "github.com/btcsuite/btcd/btcutil" - log "github.com/sirupsen/logrus" ) // openRPCKeyPair creates or loads the RPC TLS keypair specified by the @@ -86,6 +85,6 @@ func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (t } } - log.Infof("Successfully generated TLS certificates for the RPC server") + log.Info("Successfully generated TLS certificates for the RPC server") return keyPair, nil } diff --git a/vigilante/log.go b/vigilante/log.go new file mode 100644 index 00000000..4052f5ab --- /dev/null +++ b/vigilante/log.go @@ -0,0 +1,7 @@ +package vigilante + +import ( + vlog "github.com/babylonchain/vigilante/log" +) + +var log = vlog.Logger.WithField("module", "vigilante") diff --git a/vigilante/reporter.go b/vigilante/reporter.go index 673e9239..cbe3345b 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -7,7 +7,6 @@ import ( "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" - log "github.com/sirupsen/logrus" ) type Reporter struct { diff --git a/vigilante/submitter.go b/vigilante/submitter.go index 8e9dc05d..1680c0bd 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -7,7 +7,6 @@ import ( "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" - log "github.com/sirupsen/logrus" ) type Submitter struct { From df9e71fb9b4cfd0a84d6cd5866a298a1ca78f99a Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Tue, 9 Aug 2022 12:01:07 +1000 Subject: [PATCH 18/28] typo fix --- config/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/log.go b/config/log.go index bf2e706d..ebde4c94 100644 --- a/config/log.go +++ b/config/log.go @@ -4,4 +4,4 @@ import ( vlog "github.com/babylonchain/vigilante/log" ) -var log = vlog.Logger.WithField("module", "btcclient") +var log = vlog.Logger.WithField("module", "config") From cafe6ddb4816ee4cc351409b4fdeb3f3c878354e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 10:44:25 +1000 Subject: [PATCH 19/28] use btcwallet as dependency --- btcclient/bitcoind_client.go | 1344 --------------------- btcclient/bitcoind_conn.go | 437 ------- btcclient/bitcoind_events.go | 705 ----------- btcclient/bitcoind_events_test.go | 491 -------- btcclient/block_filterer.go | 217 ---- btcclient/block_filterer_test.go | 324 ----- btcclient/client.go | 482 +------- btcclient/interface.go | 124 -- btcclient/pruned_block_dispatcher.go | 666 ---------- btcclient/pruned_block_dispatcher_test.go | 659 ---------- btcclient/queue.go | 88 -- btcclient/utils_test.go | 248 ---- go.mod | 16 +- go.sum | 4 + vigilante/reporter.go | 10 +- vigilante/submitter.go | 10 +- 16 files changed, 28 insertions(+), 5797 deletions(-) delete mode 100644 btcclient/bitcoind_client.go delete mode 100644 btcclient/bitcoind_conn.go delete mode 100644 btcclient/bitcoind_events.go delete mode 100644 btcclient/bitcoind_events_test.go delete mode 100644 btcclient/block_filterer.go delete mode 100644 btcclient/block_filterer_test.go delete mode 100644 btcclient/interface.go delete mode 100644 btcclient/pruned_block_dispatcher.go delete mode 100644 btcclient/pruned_block_dispatcher_test.go delete mode 100644 btcclient/queue.go delete mode 100644 btcclient/utils_test.go diff --git a/btcclient/bitcoind_client.go b/btcclient/bitcoind_client.go deleted file mode 100644 index 890903e9..00000000 --- a/btcclient/bitcoind_client.go +++ /dev/null @@ -1,1344 +0,0 @@ -package btcclient - -import ( - "container/list" - "encoding/hex" - "errors" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcwallet/waddrmgr" - "github.com/btcsuite/btcwallet/wtxmgr" -) - -var ( - // ErrBitcoindClientShuttingDown is an error returned when we attempt - // to receive a notification for a specific item and the bitcoind client - // is in the middle of shutting down. - ErrBitcoindClientShuttingDown = errors.New("client is shutting down") -) - -var _ Interface = &BitcoindClient{} - -// BitcoindClient represents a persistent client connection to a bitcoind server -// for information regarding the current best block chain. -type BitcoindClient struct { - // notifyBlocks signals whether the client is sending block - // notifications to the caller. This must be used atomically. - notifyBlocks uint32 - - started int32 // To be used atomically. - stopped int32 // To be used atomically. - - // birthday is the earliest time for which we should begin scanning the - // chain. - birthday time.Time - - // id is the unique ID of this client assigned by the backing bitcoind - // connection. - id uint64 - - // chainConn is the backing client to our rescan client that contains - // the RPC and ZMQ connections to a bitcoind node. - chainConn *BitcoindConn - - // bestBlock keeps track of the tip of the current best chain. - bestBlockMtx sync.RWMutex - bestBlock waddrmgr.BlockStamp - - // rescanUpdate is a channel will be sent items that we should match - // transactions against while processing a chain rescan to determine if - // they are relevant to the client. - rescanUpdate chan interface{} - - // watchedAddresses, watchedOutPoints, and watchedTxs are the set of - // items we should match transactions against while processing a chain - // rescan to determine if they are relevant to the client. - watchMtx sync.RWMutex - watchedAddresses map[string]struct{} - watchedOutPoints map[wire.OutPoint]struct{} - watchedTxs map[chainhash.Hash]struct{} - - // mempool keeps track of all relevant transactions that have yet to be - // confirmed. This is used to shortcut the filtering process of a - // transaction when a new confirmed transaction notification is - // received. - // - // NOTE: This requires the watchMtx to be held. - mempool map[chainhash.Hash]struct{} - - // expiredMempool keeps track of a set of confirmed transactions along - // with the height at which they were included in a block. These - // transactions will then be removed from the mempool after a period of - // 288 blocks. This is done to ensure the transactions are safe from a - // reorg in the chain. - // - // NOTE: This requires the watchMtx to be held. - expiredMempool map[int32]map[chainhash.Hash]struct{} - - // notificationQueue is a concurrent unbounded queue that handles - // dispatching notifications to the subscriber of this client. - // - // TODO: Rather than leaving this as an unbounded queue for all types of - // notifications, try dropping ones where a later enqueued notification - // can fully invalidate one waiting to be processed. For example, - // BlockConnected notifications for greater block heights can remove the - // need to process earlier notifications still waiting to be processed. - notificationQueue *ConcurrentQueue - - // txNtfns is a channel through which transaction events will be - // retrieved from the backing bitcoind connection, either via ZMQ or - // polling RPC. - txNtfns chan *wire.MsgTx - - // blockNtfns is a channel through block events will be retrieved from - // the backing bitcoind connection, either via ZMQ or polling RPC. - blockNtfns chan *wire.MsgBlock - - quit chan struct{} - wg sync.WaitGroup -} - -// A compile-time check to ensure that BitcoindClient satisfies the -// chain.Interface interface. -var _ Interface = (*BitcoindClient)(nil) - -// BackEnd returns the name of the driver. -func (c *BitcoindClient) BackEnd() string { - return "bitcoind" -} - -// GetBestBlock returns the highest block known to bitcoind. -func (c *BitcoindClient) GetBestBlock() (*chainhash.Hash, int32, error) { - bcinfo, err := c.chainConn.client.GetBlockChainInfo() - if err != nil { - return nil, 0, err - } - - hash, err := chainhash.NewHashFromStr(bcinfo.BestBlockHash) - if err != nil { - return nil, 0, err - } - - return hash, bcinfo.Blocks, nil -} - -// GetBlockHeight returns the height for the hash, if known, or returns an -// error. -func (c *BitcoindClient) GetBlockHeight(hash *chainhash.Hash) (int32, error) { - header, err := c.chainConn.client.GetBlockHeaderVerbose(hash) - if err != nil { - return 0, err - } - - return header.Height, nil -} - -// GetBlock returns a block from the hash. -func (c *BitcoindClient) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) { - return c.chainConn.GetBlock(hash) -} - -// GetBlockVerbose returns a verbose block from the hash. -func (c *BitcoindClient) GetBlockVerbose( - hash *chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) { - - return c.chainConn.client.GetBlockVerbose(hash) -} - -// GetBlockHash returns a block hash from the height. -func (c *BitcoindClient) GetBlockHash(height int64) (*chainhash.Hash, error) { - return c.chainConn.client.GetBlockHash(height) -} - -// GetBlockHeader returns a block header from the hash. -func (c *BitcoindClient) GetBlockHeader( - hash *chainhash.Hash) (*wire.BlockHeader, error) { - - return c.chainConn.client.GetBlockHeader(hash) -} - -// GetBlockHeaderVerbose returns a block header from the hash. -func (c *BitcoindClient) GetBlockHeaderVerbose( - hash *chainhash.Hash) (*btcjson.GetBlockHeaderVerboseResult, error) { - - return c.chainConn.client.GetBlockHeaderVerbose(hash) -} - -// IsCurrent returns whether the chain backend considers its view of the network -// as "current". -func (c *BitcoindClient) IsCurrent() bool { - bestHash, _, err := c.GetBestBlock() - if err != nil { - return false - } - bestHeader, err := c.GetBlockHeader(bestHash) - if err != nil { - return false - } - return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta)) -} - -// GetRawTransactionVerbose returns a transaction from the tx hash. -func (c *BitcoindClient) GetRawTransactionVerbose( - hash *chainhash.Hash) (*btcjson.TxRawResult, error) { - - return c.chainConn.client.GetRawTransactionVerbose(hash) -} - -// GetTxOut returns a txout from the outpoint info provided. -func (c *BitcoindClient) GetTxOut(txHash *chainhash.Hash, index uint32, - mempool bool) (*btcjson.GetTxOutResult, error) { - - return c.chainConn.client.GetTxOut(txHash, index, mempool) -} - -// SendRawTransaction sends a raw transaction via bitcoind. -func (c *BitcoindClient) SendRawTransaction(tx *wire.MsgTx, - allowHighFees bool) (*chainhash.Hash, error) { - - return c.chainConn.client.SendRawTransaction(tx, allowHighFees) -} - -// Notifications returns a channel to retrieve notifications from. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) Notifications() <-chan interface{} { - return c.notificationQueue.ChanOut() -} - -// NotifyReceived allows the chain backend to notify the caller whenever a -// transaction pays to any of the given addresses. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) NotifyReceived(addrs []btcutil.Address) error { - _ = c.NotifyBlocks() - - select { - case c.rescanUpdate <- addrs: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - return nil -} - -// NotifySpent allows the chain backend to notify the caller whenever a -// transaction spends any of the given outpoints. -func (c *BitcoindClient) NotifySpent(outPoints []*wire.OutPoint) error { - _ = c.NotifyBlocks() - - select { - case c.rescanUpdate <- outPoints: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - return nil -} - -// NotifyTx allows the chain backend to notify the caller whenever any of the -// given transactions confirm within the chain. -func (c *BitcoindClient) NotifyTx(txids []chainhash.Hash) error { - _ = c.NotifyBlocks() - - select { - case c.rescanUpdate <- txids: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - return nil -} - -// NotifyBlocks allows the chain backend to notify the caller whenever a block -// is connected or disconnected. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) NotifyBlocks() error { - // We'll guard the goroutine being spawned below by the notifyBlocks - // variable we'll use atomically. We'll make sure to reset it in case of - // a failure before spawning the goroutine so that it can be retried. - if !atomic.CompareAndSwapUint32(&c.notifyBlocks, 0, 1) { - return nil - } - - // Re-evaluate our known best block since it's possible that blocks have - // occurred between now and when the client was created. This ensures we - // don't detect a new notified block as a potential reorg. - bestHash, bestHeight, err := c.GetBestBlock() - if err != nil { - atomic.StoreUint32(&c.notifyBlocks, 0) - return fmt.Errorf("unable to retrieve best block: %v", err) - } - bestHeader, err := c.GetBlockHeaderVerbose(bestHash) - if err != nil { - atomic.StoreUint32(&c.notifyBlocks, 0) - return fmt.Errorf("unable to retrieve header for best block: "+ - "%v", err) - } - - c.bestBlockMtx.Lock() - c.bestBlock.Hash = *bestHash - c.bestBlock.Height = bestHeight - c.bestBlock.Timestamp = time.Unix(bestHeader.Time, 0) - c.bestBlockMtx.Unlock() - - // Include the client in the set of rescan clients of the backing - // bitcoind connection in order to receive ZMQ event notifications for - // new blocks and transactions. - c.chainConn.AddClient(c) - - c.wg.Add(1) - go c.ntfnHandler() - - return nil -} - -// shouldNotifyBlocks determines whether the client should send block -// notifications to the caller. -func (c *BitcoindClient) shouldNotifyBlocks() bool { - return atomic.LoadUint32(&c.notifyBlocks) == 1 -} - -// LoadTxFilter uses the given filters to what we should match transactions -// against to determine if they are relevant to the client. The reset argument -// is used to reset the current filters. -// -// The current filters supported are of the following types: -// []btcutil.Address -// []wire.OutPoint -// []*wire.OutPoint -// map[wire.OutPoint]btcutil.Address -// []chainhash.Hash -// []*chainhash.Hash -func (c *BitcoindClient) LoadTxFilter(reset bool, filters ...interface{}) error { - if reset { - select { - case c.rescanUpdate <- struct{}{}: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - } - - updateFilter := func(filter interface{}) error { - select { - case c.rescanUpdate <- filter: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - return nil - } - - // In order to make this operation atomic, we'll iterate through the - // filters twice: the first to ensure there aren't any unsupported - // filter types, and the second to actually update our filters. - for _, filter := range filters { - switch filter := filter.(type) { - case []btcutil.Address, []wire.OutPoint, []*wire.OutPoint, - map[wire.OutPoint]btcutil.Address, []chainhash.Hash, - []*chainhash.Hash: - - // Proceed to check the next filter type. - default: - return fmt.Errorf("unsupported filter type %T", filter) - } - } - - for _, filter := range filters { - if err := updateFilter(filter); err != nil { - return err - } - } - - return nil -} - -// RescanBlocks rescans any blocks passed, returning only the blocks that -// matched as []btcjson.BlockDetails. -func (c *BitcoindClient) RescanBlocks( - blockHashes []chainhash.Hash) ([]btcjson.RescannedBlock, error) { - - rescannedBlocks := make([]btcjson.RescannedBlock, 0, len(blockHashes)) - for _, hash := range blockHashes { - hash := hash - - header, err := c.GetBlockHeaderVerbose(&hash) - if err != nil { - log.Warnf("Unable to get header %s from bitcoind: %s", - hash, err) - continue - } - - // Prevent fetching the block completely if we know we shouldn't - // filter it. - if !c.shouldFilterBlock(time.Unix(header.Time, 0)) { - continue - } - - block, err := c.GetBlock(&hash) - if err != nil { - log.Warnf("Unable to get block %s from bitcoind: %s", - hash, err) - continue - } - - relevantTxs := c.filterBlock(block, header.Height, false) - if len(relevantTxs) > 0 { - rescannedBlock := btcjson.RescannedBlock{ - Hash: hash.String(), - } - for _, tx := range relevantTxs { - rescannedBlock.Transactions = append( - rescannedBlock.Transactions, - hex.EncodeToString(tx.SerializedTx), - ) - } - - rescannedBlocks = append(rescannedBlocks, rescannedBlock) - } - } - - return rescannedBlocks, nil -} - -// Rescan rescans from the block with the given hash until the current block, -// after adding the passed addresses and outpoints to the client's watch list. -func (c *BitcoindClient) Rescan(blockHash *chainhash.Hash, - addresses []btcutil.Address, outPoints map[wire.OutPoint]btcutil.Address) error { - - // A block hash is required to use as the starting point of the rescan. - if blockHash == nil { - return errors.New("rescan requires a starting block hash") - } - - // We'll then update our filters with the given outpoints and addresses. - select { - case c.rescanUpdate <- addresses: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - select { - case c.rescanUpdate <- outPoints: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - // Once the filters have been updated, we can begin the rescan. - select { - case c.rescanUpdate <- *blockHash: - case <-c.quit: - return ErrBitcoindClientShuttingDown - } - - return nil -} - -// Start initializes the bitcoind rescan client using the backing bitcoind -// connection and starts all goroutines necessary in order to process rescans -// and ZMQ notifications. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) Start() error { - if !atomic.CompareAndSwapInt32(&c.started, 0, 1) { - return nil - } - - // Start the notification queue and immediately dispatch a - // ClientConnected notification to the caller. This is needed as some of - // the callers will require this notification before proceeding. - c.notificationQueue.Start() - c.notificationQueue.ChanIn() <- ClientConnected{} - - // Retrieve the best block of the chain. - bestHash, bestHeight, err := c.GetBestBlock() - if err != nil { - return fmt.Errorf("unable to retrieve best block: %v", err) - } - bestHeader, err := c.GetBlockHeaderVerbose(bestHash) - if err != nil { - return fmt.Errorf("unable to retrieve header for best block: "+ - "%v", err) - } - - c.bestBlockMtx.Lock() - c.bestBlock = waddrmgr.BlockStamp{ - Hash: *bestHash, - Height: bestHeight, - Timestamp: time.Unix(bestHeader.Time, 0), - } - c.bestBlockMtx.Unlock() - - c.wg.Add(1) - go c.rescanHandler() - - return nil -} - -// Stop stops the bitcoind rescan client from processing rescans and ZMQ -// notifications. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) Stop() { - if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { - return - } - - close(c.quit) - - // Remove this client's reference from the bitcoind connection to - // prevent sending notifications to it after it's been stopped. - c.chainConn.RemoveClient(c.id) - - c.notificationQueue.Stop() -} - -// WaitForShutdown blocks until the client has finished disconnecting and all -// handlers have exited. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) WaitForShutdown() { - c.wg.Wait() -} - -// rescanHandler handles the logic needed for the caller to trigger a chain -// rescan. -// -// NOTE: This must be called as a goroutine. -func (c *BitcoindClient) rescanHandler() { - defer c.wg.Done() - - for { - select { - case update := <-c.rescanUpdate: - switch update := update.(type) { - - // We're clearing the filters. - case struct{}: - c.watchMtx.Lock() - c.watchedOutPoints = make(map[wire.OutPoint]struct{}) - c.watchedAddresses = make(map[string]struct{}) - c.watchedTxs = make(map[chainhash.Hash]struct{}) - c.watchMtx.Unlock() - - // We're adding the addresses to our filter. - case []btcutil.Address: - c.watchMtx.Lock() - for _, addr := range update { - c.watchedAddresses[addr.String()] = struct{}{} - } - c.watchMtx.Unlock() - - // We're adding the outpoints to our filter. - case []wire.OutPoint: - c.watchMtx.Lock() - for _, op := range update { - c.watchedOutPoints[op] = struct{}{} - } - c.watchMtx.Unlock() - case []*wire.OutPoint: - c.watchMtx.Lock() - for _, op := range update { - c.watchedOutPoints[*op] = struct{}{} - } - c.watchMtx.Unlock() - - // We're adding the outpoints that map to the scripts - // that we should scan for to our filter. - case map[wire.OutPoint]btcutil.Address: - c.watchMtx.Lock() - for op := range update { - c.watchedOutPoints[op] = struct{}{} - } - c.watchMtx.Unlock() - - // We're adding the transactions to our filter. - case []chainhash.Hash: - c.watchMtx.Lock() - for _, txid := range update { - c.watchedTxs[txid] = struct{}{} - } - c.watchMtx.Unlock() - case []*chainhash.Hash: - c.watchMtx.Lock() - for _, txid := range update { - c.watchedTxs[*txid] = struct{}{} - } - c.watchMtx.Unlock() - - // We're starting a rescan from the hash. - case chainhash.Hash: - if err := c.rescan(update); err != nil { - log.Errorf("Unable to complete chain "+ - "rescan: %v", err) - } - default: - log.Warnf("Received unexpected filter type %T", - update) - } - case <-c.quit: - return - } - } -} - -// ntfnHandler handles the logic to retrieve ZMQ notifications from the backing -// bitcoind connection. -// -// NOTE: This must be called as a goroutine. -func (c *BitcoindClient) ntfnHandler() { - defer c.wg.Done() - - for { - select { - case tx := <-c.txNtfns: - _, _, err := c.filterTx(tx, nil, true) - if err != nil { - log.Errorf("Unable to filter transaction %v: %v", - tx.TxHash(), err) - } - - case newBlock := <-c.blockNtfns: - // If the new block's previous hash matches the best - // hash known to us, then the new block is the next - // successor, so we'll update our best block to reflect - // this and determine if this new block matches any of - // our existing filters. - c.bestBlockMtx.RLock() - bestBlock := c.bestBlock - c.bestBlockMtx.RUnlock() - if newBlock.Header.PrevBlock == bestBlock.Hash { - newBlockHeight := bestBlock.Height + 1 - _ = c.filterBlock(newBlock, newBlockHeight, true) - - // With the block successfully filtered, we'll - // make it our new best block. - bestBlock.Hash = newBlock.BlockHash() - bestBlock.Height = newBlockHeight - bestBlock.Timestamp = newBlock.Header.Timestamp - - c.bestBlockMtx.Lock() - c.bestBlock = bestBlock - c.bestBlockMtx.Unlock() - - continue - } - - // Otherwise, we've encountered a reorg. - if err := c.reorg(bestBlock, newBlock); err != nil { - log.Errorf("Unable to process chain reorg: %v", - err) - } - case <-c.quit: - return - } - } -} - -// SetBirthday sets the birthday of the bitcoind rescan client. -// -// NOTE: This should be done before the client has been started in order for it -// to properly carry its duties. -func (c *BitcoindClient) SetBirthday(t time.Time) { - c.birthday = t -} - -// BlockStamp returns the latest block notified by the client, or an error -// if the client has been shut down. -func (c *BitcoindClient) BlockStamp() (*waddrmgr.BlockStamp, error) { - c.bestBlockMtx.RLock() - bestBlock := c.bestBlock - c.bestBlockMtx.RUnlock() - - return &bestBlock, nil -} - -// onBlockConnected is a callback that's executed whenever a new block has been -// detected. This will queue a BlockConnected notification to the caller. -func (c *BitcoindClient) onBlockConnected(hash *chainhash.Hash, height int32, - timestamp time.Time) { - - if c.shouldNotifyBlocks() { - select { - case c.notificationQueue.ChanIn() <- BlockConnected{ - Block: wtxmgr.Block{ - Hash: *hash, - Height: height, - }, - Time: timestamp, - }: - case <-c.quit: - } - } -} - -// onFilteredBlockConnected is an alternative callback that's executed whenever -// a new block has been detected. It serves the same purpose as -// onBlockConnected, but it also includes a list of the relevant transactions -// found within the block being connected. This will queue a -// FilteredBlockConnected notification to the caller. -func (c *BitcoindClient) onFilteredBlockConnected(height int32, - header *wire.BlockHeader, relevantTxs []*wtxmgr.TxRecord) { - - if c.shouldNotifyBlocks() { - select { - case c.notificationQueue.ChanIn() <- FilteredBlockConnected{ - Block: &wtxmgr.BlockMeta{ - Block: wtxmgr.Block{ - Hash: header.BlockHash(), - Height: height, - }, - Time: header.Timestamp, - }, - RelevantTxs: relevantTxs, - }: - case <-c.quit: - } - } -} - -// onBlockDisconnected is a callback that's executed whenever a block has been -// disconnected. This will queue a BlockDisconnected notification to the caller -// with the details of the block being disconnected. -func (c *BitcoindClient) onBlockDisconnected(hash *chainhash.Hash, height int32, - timestamp time.Time) { - - if c.shouldNotifyBlocks() { - select { - case c.notificationQueue.ChanIn() <- BlockDisconnected{ - Block: wtxmgr.Block{ - Hash: *hash, - Height: height, - }, - Time: timestamp, - }: - case <-c.quit: - } - } -} - -// onRelevantTx is a callback that's executed whenever a transaction is relevant -// to the caller. This means that the transaction matched a specific item in the -// client's different filters. This will queue a RelevantTx notification to the -// caller. -func (c *BitcoindClient) onRelevantTx(tx *wtxmgr.TxRecord, - blockDetails *btcjson.BlockDetails) { - - block, err := parseBlock(blockDetails) - if err != nil { - log.Errorf("Unable to send onRelevantTx notification, failed "+ - "parse block: %v", err) - return - } - - select { - case c.notificationQueue.ChanIn() <- RelevantTx{ - TxRecord: tx, - Block: block, - }: - case <-c.quit: - } -} - -// onRescanProgress is a callback that's executed whenever a rescan is in -// progress. This will queue a RescanProgress notification to the caller with -// the current rescan progress details. -func (c *BitcoindClient) onRescanProgress(hash *chainhash.Hash, height int32, - timestamp time.Time) { - - select { - case c.notificationQueue.ChanIn() <- &RescanProgress{ - Hash: hash, - Height: height, - Time: timestamp, - }: - case <-c.quit: - } -} - -// onRescanFinished is a callback that's executed whenever a rescan has -// finished. This will queue a RescanFinished notification to the caller with -// the details of the last block in the range of the rescan. -func (c *BitcoindClient) onRescanFinished(hash *chainhash.Hash, height int32, - timestamp time.Time) { - - select { - case c.notificationQueue.ChanIn() <- &RescanFinished{ - Hash: hash, - Height: height, - Time: timestamp, - }: - case <-c.quit: - } -} - -// reorg processes a reorganization during chain synchronization. This is -// separate from a rescan's handling of a reorg. This will rewind back until it -// finds a common ancestor and notify all the new blocks since then. -func (c *BitcoindClient) reorg(currentBlock waddrmgr.BlockStamp, - reorgBlock *wire.MsgBlock) error { - - // Retrieve the best known height based on the block which caused the - // reorg. This way, we can preserve the chain of blocks we need to - // retrieve. - bestHash := reorgBlock.BlockHash() - bestHeight, err := c.GetBlockHeight(&bestHash) - if err != nil { - return fmt.Errorf("unable to get block height for %v: %v", - bestHash, err) - } - - log.Debugf("Possible reorg at block: height=%v, hash=%v", bestHeight, - bestHash) - - if bestHeight < currentBlock.Height { - log.Debugf("Detected multiple reorgs: best_height=%v below "+ - "current_height=%v", bestHeight, currentBlock.Height) - return nil - } - - // We'll now keep track of all the blocks known to the *chain*, starting - // from the best block known to us until the best block in the chain. - // This will let us fast-forward despite any future reorgs. - blocksToNotify := list.New() - blocksToNotify.PushFront(reorgBlock) - previousBlock := reorgBlock.Header.PrevBlock - for i := bestHeight - 1; i >= currentBlock.Height; i-- { - block, err := c.GetBlock(&previousBlock) - if err != nil { - return fmt.Errorf("unable to get block %v: %v", - previousBlock, err) - } - blocksToNotify.PushFront(block) - previousBlock = block.Header.PrevBlock - } - - // Rewind back to the last common ancestor block using the previous - // block hash from each header to avoid any race conditions. If we - // encounter more reorgs, they'll be queued and we'll repeat the cycle. - // - // We'll start by retrieving the header to the best block known to us. - currentHeader, err := c.GetBlockHeader(¤tBlock.Hash) - if err != nil { - return fmt.Errorf("unable to get block header for %v: %v", - currentBlock.Hash, err) - } - - // Then, we'll walk backwards in the chain until we find our common - // ancestor. - for previousBlock != currentHeader.PrevBlock { - // Since the previous hashes don't match, the current block has - // been reorged out of the chain, so we should send a - // BlockDisconnected notification for it. - log.Debugf("Disconnecting block: height=%v, hash=%v", - currentBlock.Height, currentBlock.Hash) - - c.onBlockDisconnected( - ¤tBlock.Hash, currentBlock.Height, - currentBlock.Timestamp, - ) - - // Our current block should now reflect the previous one to - // continue the common ancestor search. - prevBlock := ¤tHeader.PrevBlock - currentHeader, err = c.GetBlockHeader(prevBlock) - if err != nil { - return fmt.Errorf("unable to get block header for %v: %v", - prevBlock, err) - } - - currentBlock.Height-- - currentBlock.Hash = currentHeader.PrevBlock - currentBlock.Timestamp = currentHeader.Timestamp - - // Store the correct block in our list in order to notify it - // once we've found our common ancestor. - block, err := c.GetBlock(&previousBlock) - if err != nil { - return fmt.Errorf("unable to get block %v: %v", - previousBlock, err) - } - blocksToNotify.PushFront(block) - previousBlock = block.Header.PrevBlock - } - - // Disconnect the last block from the old chain. Since the previous - // block remains the same between the old and new chains, the tip will - // now be the last common ancestor. - log.Debugf("Disconnecting block: height=%v, hash=%v", - currentBlock.Height, currentBlock.Hash) - - c.onBlockDisconnected( - ¤tBlock.Hash, currentBlock.Height, currentHeader.Timestamp, - ) - - currentBlock.Height-- - - // Now we fast-forward to the new block, notifying along the way. - for blocksToNotify.Front() != nil { - nextBlock := blocksToNotify.Front().Value.(*wire.MsgBlock) - nextHeight := currentBlock.Height + 1 - nextHash := nextBlock.BlockHash() - nextHeader, err := c.GetBlockHeader(&nextHash) - if err != nil { - return fmt.Errorf("unable to get block header for %v: %v", - nextHash, err) - } - - _ = c.filterBlock(nextBlock, nextHeight, true) - - currentBlock.Height = nextHeight - currentBlock.Hash = nextHash - currentBlock.Timestamp = nextHeader.Timestamp - - blocksToNotify.Remove(blocksToNotify.Front()) - } - - c.bestBlockMtx.Lock() - c.bestBlock = currentBlock - c.bestBlockMtx.Unlock() - - return nil -} - -// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any -// addresses of interest. Each block will be fetched and filtered sequentially, -// returning a FilterBlocksReponse for the first block containing a matching -// address. If no matches are found in the range of blocks requested, the -// returned response will be nil. -// -// NOTE: This is part of the chain.Interface interface. -func (c *BitcoindClient) FilterBlocks( - req *FilterBlocksRequest) (*FilterBlocksResponse, error) { - - blockFilterer := NewBlockFilterer(c.chainConn.cfg.ChainParams, req) - - // Iterate over the requested blocks, fetching each from the rpc client. - // Each block will scanned using the reverse addresses indexes generated - // above, breaking out early if any addresses are found. - for i, block := range req.Blocks { - // TODO(conner): add prefetching, since we already know we'll be - // fetching *every* block - rawBlock, err := c.GetBlock(&block.Hash) - if err != nil { - return nil, err - } - - if !blockFilterer.FilterBlock(rawBlock) { - continue - } - - // If any external or internal addresses were detected in this - // block, we return them to the caller so that the rescan - // windows can widened with subsequent addresses. The - // `BatchIndex` is returned so that the caller can compute the - // *next* block from which to begin again. - resp := &FilterBlocksResponse{ - BatchIndex: uint32(i), - BlockMeta: block, - FoundExternalAddrs: blockFilterer.FoundExternal, - FoundInternalAddrs: blockFilterer.FoundInternal, - FoundOutPoints: blockFilterer.FoundOutPoints, - RelevantTxns: blockFilterer.RelevantTxns, - } - - return resp, nil - } - - // No addresses were found for this range. - return nil, nil -} - -// rescan performs a rescan of the chain using a bitcoind backend, from the -// specified hash to the best known hash, while watching out for reorgs that -// happen during the rescan. It uses the addresses and outputs being tracked by -// the client in the watch list. This is called only within a queue processing -// loop. -func (c *BitcoindClient) rescan(start chainhash.Hash) error { - // We start by getting the best already processed block. We only use - // the height, as the hash can change during a reorganization, which we - // catch by testing connectivity from known blocks to the previous - // block. - bestHash, bestHeight, err := c.GetBestBlock() - if err != nil { - return err - } - bestHeader, err := c.GetBlockHeaderVerbose(bestHash) - if err != nil { - return err - } - bestBlock := waddrmgr.BlockStamp{ - Hash: *bestHash, - Height: bestHeight, - Timestamp: time.Unix(bestHeader.Time, 0), - } - - // Create a list of headers sorted in forward order. We'll use this in - // the event that we need to backtrack due to a chain reorg. - headers := list.New() - previousHeader, err := c.GetBlockHeaderVerbose(&start) - if err != nil { - return err - } - previousHash, err := chainhash.NewHashFromStr(previousHeader.Hash) - if err != nil { - return err - } - headers.PushBack(previousHeader) - - // Cycle through all of the blocks known to bitcoind, being mindful of - // reorgs. - for i := previousHeader.Height + 1; i <= bestBlock.Height; i++ { - hash, err := c.GetBlockHash(int64(i)) - if err != nil { - return err - } - - // If the previous header is before the wallet birthday, fetch - // the current header and construct a dummy block, rather than - // fetching the whole block itself. This speeds things up as we - // no longer have to fetch the whole block when we know it won't - // match any of our filters. - var block *wire.MsgBlock - afterBirthday := previousHeader.Time >= c.birthday.Unix() - if !afterBirthday { - header, err := c.GetBlockHeader(hash) - if err != nil { - return err - } - block = &wire.MsgBlock{ - Header: *header, - } - - afterBirthday = c.birthday.Before(header.Timestamp) - if afterBirthday { - c.onRescanProgress( - previousHash, i, - block.Header.Timestamp, - ) - } - } - - if afterBirthday { - block, err = c.GetBlock(hash) - if err != nil { - return err - } - } - - for block.Header.PrevBlock.String() != previousHeader.Hash { - // If we're in this for loop, it looks like we've been - // reorganized. We now walk backwards to the common - // ancestor between the best chain and the known chain. - // - // First, we signal a disconnected block to rewind the - // rescan state. - c.onBlockDisconnected( - previousHash, previousHeader.Height, - time.Unix(previousHeader.Time, 0), - ) - - // Get the previous block of the best chain. - hash, err := c.GetBlockHash(int64(i - 1)) - if err != nil { - return err - } - block, err = c.GetBlock(hash) - if err != nil { - return err - } - - // Then, we'll the get the header of this previous - // block. - if headers.Back() != nil { - // If it's already in the headers list, we can - // just get it from there and remove the - // current hash. - headers.Remove(headers.Back()) - if headers.Back() != nil { - previousHeader = headers.Back(). - Value.(*btcjson.GetBlockHeaderVerboseResult) - previousHash, err = chainhash.NewHashFromStr( - previousHeader.Hash, - ) - if err != nil { - return err - } - } - } else { - // Otherwise, we get it from bitcoind. - previousHash, err = chainhash.NewHashFromStr( - previousHeader.PreviousHash, - ) - if err != nil { - return err - } - previousHeader, err = c.GetBlockHeaderVerbose( - previousHash, - ) - if err != nil { - return err - } - } - } - - // Now that we've ensured we haven't come across a reorg, we'll - // add the current block header to our list of headers. - blockHash := block.BlockHash() - previousHash = &blockHash - previousHeader = &btcjson.GetBlockHeaderVerboseResult{ - Hash: blockHash.String(), - Height: i, - PreviousHash: block.Header.PrevBlock.String(), - Time: block.Header.Timestamp.Unix(), - } - headers.PushBack(previousHeader) - - // Notify the block and any of its relevant transacations. - _ = c.filterBlock(block, i, true) - - if i%10000 == 0 { - c.onRescanProgress( - previousHash, i, block.Header.Timestamp, - ) - } - - // If we've reached the previously best known block, check to - // make sure the underlying node hasn't synchronized additional - // blocks. If it has, update the best known block and continue - // to rescan to that point. - if i == bestBlock.Height { - bestHash, bestHeight, err = c.GetBestBlock() - if err != nil { - return err - } - bestHeader, err = c.GetBlockHeaderVerbose(bestHash) - if err != nil { - return err - } - - bestBlock.Hash = *bestHash - bestBlock.Height = bestHeight - bestBlock.Timestamp = time.Unix(bestHeader.Time, 0) - } - } - - c.onRescanFinished(bestHash, bestHeight, time.Unix(bestHeader.Time, 0)) - - return nil -} - -// shouldFilterBlock determines whether we should filter a block based on its -// timestamp or our watch list. -func (c *BitcoindClient) shouldFilterBlock(blockTimestamp time.Time) bool { - c.watchMtx.RLock() - hasEmptyFilter := len(c.watchedAddresses) == 0 && - len(c.watchedOutPoints) == 0 && len(c.watchedTxs) == 0 - c.watchMtx.RUnlock() - - return !(blockTimestamp.Before(c.birthday) || hasEmptyFilter) -} - -// filterBlock filters a block for watched outpoints and addresses, and returns -// any matching transactions, sending notifications along the way. -func (c *BitcoindClient) filterBlock(block *wire.MsgBlock, height int32, - notify bool) []*wtxmgr.TxRecord { - - // If this block happened before the client's birthday or we have - // nothing to filter for, then we'll skip it entirely. - blockHash := block.BlockHash() - if !c.shouldFilterBlock(block.Header.Timestamp) { - if notify { - c.onFilteredBlockConnected(height, &block.Header, nil) - c.onBlockConnected( - &blockHash, height, block.Header.Timestamp, - ) - } - return nil - } - - if c.shouldNotifyBlocks() { - log.Debugf("Filtering block %d (%s) with %d transactions", - height, block.BlockHash(), len(block.Transactions)) - } - - // Create a block details template to use for all of the confirmed - // transactions found within this block. - blockDetails := &btcjson.BlockDetails{ - Hash: blockHash.String(), - Height: height, - Time: block.Header.Timestamp.Unix(), - } - - // Now, we'll through all of the transactions in the block keeping track - // of any relevant to the caller. - var relevantTxs []*wtxmgr.TxRecord - confirmedTxs := make(map[chainhash.Hash]struct{}) - for i, tx := range block.Transactions { - // Update the index in the block details with the index of this - // transaction. - blockDetails.Index = i - isRelevant, rec, err := c.filterTx(tx, blockDetails, notify) - if err != nil { - log.Warnf("Unable to filter transaction %v: %v", - tx.TxHash(), err) - continue - } - - if isRelevant { - relevantTxs = append(relevantTxs, rec) - confirmedTxs[tx.TxHash()] = struct{}{} - } - } - - // Update the expiration map by setting the block's confirmed - // transactions and deleting any in the mempool that were confirmed - // over 288 blocks ago. - c.watchMtx.Lock() - c.expiredMempool[height] = confirmedTxs - if oldBlock, ok := c.expiredMempool[height-288]; ok { - for txHash := range oldBlock { - delete(c.mempool, txHash) - } - delete(c.expiredMempool, height-288) - } - c.watchMtx.Unlock() - - if notify { - c.onFilteredBlockConnected(height, &block.Header, relevantTxs) - c.onBlockConnected(&blockHash, height, block.Header.Timestamp) - } - - return relevantTxs -} - -// filterTx determines whether a transaction is relevant to the client by -// inspecting the client's different filters. -func (c *BitcoindClient) filterTx(tx *wire.MsgTx, - blockDetails *btcjson.BlockDetails, - notify bool) (bool, *wtxmgr.TxRecord, error) { - - txDetails := btcutil.NewTx(tx) - if blockDetails != nil { - txDetails.SetIndex(blockDetails.Index) - } - - rec, err := wtxmgr.NewTxRecordFromMsgTx(txDetails.MsgTx(), time.Now()) - if err != nil { - log.Errorf("Cannot create transaction record for relevant "+ - "tx: %v", err) - return false, nil, err - } - if blockDetails != nil { - rec.Received = time.Unix(blockDetails.Time, 0) - } - - // We'll begin the filtering process by holding the lock to ensure we - // match exactly against what's currently in the filters. - c.watchMtx.Lock() - defer c.watchMtx.Unlock() - - // If we've already seen this transaction and it's now been confirmed, - // then we'll shortcut the filter process by immediately sending a - // notification to the caller that the filter matches. - if _, ok := c.mempool[tx.TxHash()]; ok { - if notify && blockDetails != nil { - c.onRelevantTx(rec, blockDetails) - } - return true, rec, nil - } - - // Otherwise, this is a new transaction we have yet to see. We'll need - // to determine if this transaction is somehow relevant to the caller. - var isRelevant bool - - // We'll start by checking all inputs and determining whether it spends - // an existing outpoint or a pkScript encoded as an address in our watch - // list. - for _, txIn := range tx.TxIn { - // If it matches an outpoint in our watch list, we can exit our - // loop early. - if _, ok := c.watchedOutPoints[txIn.PreviousOutPoint]; ok { - isRelevant = true - break - } - - // Otherwise, we'll check whether it matches a pkScript in our - // watch list encoded as an address. To do so, we'll re-derive - // the pkScript of the output the input is attempting to spend. - pkScript, err := txscript.ComputePkScript( - txIn.SignatureScript, txIn.Witness, - ) - if err != nil { - // Non-standard outputs can be safely skipped. - continue - } - addr, err := pkScript.Address(c.chainConn.cfg.ChainParams) - if err != nil { - // Non-standard outputs can be safely skipped. - continue - } - if _, ok := c.watchedAddresses[addr.String()]; ok { - isRelevant = true - break - } - } - - // We'll also cycle through its outputs to determine if it pays to - // any of the currently watched addresses. If an output matches, we'll - // add it to our watch list. - for i, txOut := range tx.TxOut { - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - txOut.PkScript, c.chainConn.cfg.ChainParams, - ) - if err != nil { - // Non-standard outputs can be safely skipped. - continue - } - - for _, addr := range addrs { - if _, ok := c.watchedAddresses[addr.String()]; ok { - isRelevant = true - op := wire.OutPoint{ - Hash: tx.TxHash(), - Index: uint32(i), - } - c.watchedOutPoints[op] = struct{}{} - } - } - } - - // If the transaction didn't pay to any of our watched addresses, we'll - // check if we're currently watching for the hash of this transaction. - if !isRelevant { - if _, ok := c.watchedTxs[tx.TxHash()]; ok { - isRelevant = true - } - } - - // If the transaction is not relevant to us, we can simply exit. - if !isRelevant { - return false, rec, nil - } - - // Otherwise, the transaction matched our filters, so we should dispatch - // a notification for it. If it's still unconfirmed, we'll include it in - // our mempool so that it can also be notified as part of - // FilteredBlockConnected once it confirms. - if blockDetails == nil { - c.mempool[tx.TxHash()] = struct{}{} - } - - c.onRelevantTx(rec, blockDetails) - - return true, rec, nil -} diff --git a/btcclient/bitcoind_conn.go b/btcclient/bitcoind_conn.go deleted file mode 100644 index a06d20ff..00000000 --- a/btcclient/bitcoind_conn.go +++ /dev/null @@ -1,437 +0,0 @@ -package btcclient - -import ( - "fmt" - "net" - "sync" - "sync/atomic" - - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/ticker" -) - -const ( - // rawBlockZMQCommand is the command used to receive raw block - // notifications from bitcoind through ZMQ. - rawBlockZMQCommand = "rawblock" - - // rawTxZMQCommand is the command used to receive raw transaction - // notifications from bitcoind through ZMQ. - rawTxZMQCommand = "rawtx" - - // maxRawBlockSize is the maximum size in bytes for a raw block received - // from bitcoind through ZMQ. - maxRawBlockSize = 4e6 - - // maxRawTxSize is the maximum size in bytes for a raw transaction - // received from bitcoind through ZMQ. - maxRawTxSize = maxRawBlockSize - - // seqNumLen is the length of the sequence number of a message sent from - // bitcoind through ZMQ. - seqNumLen = 4 - - // errBlockPrunedStr is the error message returned by bitcoind upon - // calling GetBlock on a pruned block. - errBlockPrunedStr = "Block not available (pruned data)" -) - -// BitcoindConfig contains all of the parameters required to establish a -// connection to a bitcoind's RPC. -type BitcoindConfig struct { - // ChainParams are the chain parameters the bitcoind server is running - // on. - ChainParams *chaincfg.Params - - // Host is the IP address and port of the bitcoind's RPC server. - Host string - - // User is the username to use to authenticate to bitcoind's RPC server. - User string - - // Pass is the passphrase to use to authenticate to bitcoind's RPC - // server. - Pass string - - // ZMQConfig holds the configuration settings required for setting up - // zmq connections to bitcoind. - ZMQConfig *ZMQConfig - - // PollingConfig holds the configuration settings required for using - // RPC polling for block and transaction notifications instead of the - // ZMQ interface. - PollingConfig *PollingConfig - - // Dialer is a closure we'll use to dial Bitcoin peers. If the chain - // backend is running over Tor, this must support dialing peers over Tor - // as well. - Dialer Dialer - - // PrunedModeMaxPeers is the maximum number of peers we'll attempt to - // retrieve pruned blocks from. - // - // NOTE: This only applies for pruned bitcoind nodes. - PrunedModeMaxPeers int -} - -// BitcoindConn represents a persistent client connection to a bitcoind node -// that listens for events read from a ZMQ connection. -type BitcoindConn struct { - started int32 // To be used atomically. - stopped int32 // To be used atomically. - - // rescanClientCounter is an atomic counter that assigns a unique ID to - // each new bitcoind rescan client using the current bitcoind - // connection. - rescanClientCounter uint64 - - cfg BitcoindConfig - - // client is the RPC client to the bitcoind node. - client *rpcclient.Client - - // prunedBlockDispatcher handles all of the pruned block requests. - // - // NOTE: This is nil when the bitcoind node is not pruned. - prunedBlockDispatcher *PrunedBlockDispatcher - - // events handles the block and transaction events that are received or - // retrieved from bitcoind. - events BitcoindEvents - - // rescanClients is the set of active bitcoind rescan clients to which - // ZMQ event notifications will be sent to. - rescanClientsMtx sync.Mutex - rescanClients map[uint64]*BitcoindClient - - quit chan struct{} - wg sync.WaitGroup -} - -// Dialer represents a way to dial Bitcoin peers. If the chain backend is -// running over Tor, this must support dialing peers over Tor as well. -type Dialer = func(string) (net.Conn, error) - -// NewBitcoindConn creates a client connection to the node described by the host -// string. The ZMQ connections are established immediately to ensure liveness. -// If the remote node does not operate on the same bitcoin network as described -// by the passed chain parameters, the connection will be disconnected. -func NewBitcoindConn(cfg *BitcoindConfig) (*BitcoindConn, error) { - clientCfg := &rpcclient.ConnConfig{ - Host: cfg.Host, - User: cfg.User, - Pass: cfg.Pass, - DisableAutoReconnect: false, - DisableConnectOnNew: true, - DisableTLS: true, - HTTPPostMode: true, - } - client, err := rpcclient.New(clientCfg, nil) - if err != nil { - return nil, err - } - - // Verify that the node is running on the expected network. - net, err := getCurrentNet(client) - if err != nil { - return nil, err - } - if net != cfg.ChainParams.Net { - return nil, fmt.Errorf("expected network %v, got %v", - cfg.ChainParams.Net, net) - } - - // Check if the node is pruned, as we'll need to perform additional - // operations if so. - chainInfo, err := client.GetBlockChainInfo() - if err != nil { - return nil, fmt.Errorf("unable to determine if bitcoind is "+ - "pruned: %v", err) - } - - // Only initialize the PrunedBlockDispatcher when the connected bitcoind - // node is pruned. - var prunedBlockDispatcher *PrunedBlockDispatcher - if chainInfo.Pruned { - prunedBlockDispatcher, err = NewPrunedBlockDispatcher( - &PrunedBlockDispatcherConfig{ - ChainParams: cfg.ChainParams, - NumTargetPeers: cfg.PrunedModeMaxPeers, - Dial: cfg.Dialer, - GetPeers: client.GetPeerInfo, - GetNodeAddresses: client.GetNodeAddresses, - PeerReadyTimeout: defaultPeerReadyTimeout, - RefreshPeersTicker: ticker.New(defaultRefreshPeersInterval), - MaxRequestInvs: wire.MaxInvPerMsg, - }, - ) - if err != nil { - return nil, err - } - } - - bc := &BitcoindConn{ - cfg: *cfg, - client: client, - prunedBlockDispatcher: prunedBlockDispatcher, - rescanClients: make(map[uint64]*BitcoindClient), - quit: make(chan struct{}), - } - - bc.events, err = NewBitcoindEventSubscriber(cfg, client) - if err != nil { - return nil, err - } - - return bc, nil -} - -// Start attempts to establish a RPC and ZMQ connection to a bitcoind node. If -// successful, a goroutine is spawned to read events from the ZMQ connection. -// It's possible for this function to fail due to a limited number of connection -// attempts. This is done to prevent waiting forever on the connection to be -// established in the case that the node is down. -func (c *BitcoindConn) Start() error { - if !atomic.CompareAndSwapInt32(&c.started, 0, 1) { - return nil - } - - // If we're connected to a pruned backend, we'll need to also start our - // pruned block dispatcher to handle pruned block requests. - if c.prunedBlockDispatcher != nil { - log.Debug("Detected pruned bitcoind backend") - if err := c.prunedBlockDispatcher.Start(); err != nil { - return err - } - } - - c.wg.Add(2) - go c.sendBlockToClients() - go c.sendTxToClients() - - return c.events.Start() -} - -// Stop terminates the RPC and ZMQ connection to a bitcoind node and removes any -// active rescan clients. -func (c *BitcoindConn) Stop() { - if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) { - return - } - - for _, client := range c.rescanClients { - client.Stop() - } - - close(c.quit) - c.client.Shutdown() - - if err := c.events.Stop(); err != nil { - log.Errorf("error shutting down bitcoind events: %w", err) - } - - if c.prunedBlockDispatcher != nil { - c.prunedBlockDispatcher.Stop() - } - - c.client.WaitForShutdown() - c.wg.Wait() -} - -// sendBlockToClients is used to notify all rescan clients of a new block. It -// MUST be run in a goroutine. -func (c *BitcoindConn) sendBlockToClients() { - defer c.wg.Done() - - // sendBlock is a helper function that sends the given block to each - // of the rescan clients - sendBlock := func(block *wire.MsgBlock) { - c.rescanClientsMtx.Lock() - defer c.rescanClientsMtx.Unlock() - - for _, client := range c.rescanClients { - select { - case client.blockNtfns <- block: - case <-client.quit: - case <-c.quit: - return - } - } - } - - var block *wire.MsgBlock - for { - select { - case block = <-c.events.BlockNotifications(): - case <-c.quit: - return - } - - sendBlock(block) - } -} - -// sendTxToClients is used to notify all rescan clients of a new transaction. -// It MUST be run as a goroutine. -func (c *BitcoindConn) sendTxToClients() { - defer c.wg.Done() - - sendTx := func(tx *wire.MsgTx) { - c.rescanClientsMtx.Lock() - defer c.rescanClientsMtx.Unlock() - - for _, client := range c.rescanClients { - select { - case client.txNtfns <- tx: - case <-client.quit: - case <-c.quit: - return - } - } - } - - var tx *wire.MsgTx - for { - select { - case tx = <-c.events.TxNotifications(): - case <-c.quit: - return - } - - sendTx(tx) - } -} - -// getCurrentNet returns the network on which the bitcoind node is running. -func getCurrentNet(client *rpcclient.Client) (wire.BitcoinNet, error) { - hash, err := client.GetBlockHash(0) - if err != nil { - return 0, err - } - - switch *hash { - case *chaincfg.TestNet3Params.GenesisHash: - return chaincfg.TestNet3Params.Net, nil - case *chaincfg.RegressionNetParams.GenesisHash: - return chaincfg.RegressionNetParams.Net, nil - case *chaincfg.SigNetParams.GenesisHash: - return chaincfg.SigNetParams.Net, nil - case *chaincfg.MainNetParams.GenesisHash: - return chaincfg.MainNetParams.Net, nil - default: - return 0, fmt.Errorf("unknown network with genesis hash %v", hash) - } -} - -// NewBitcoindClient returns a bitcoind client using the current bitcoind -// connection. This allows us to share the same connection using multiple -// clients. -func (c *BitcoindConn) NewBitcoindClient() *BitcoindClient { - return &BitcoindClient{ - quit: make(chan struct{}), - - id: atomic.AddUint64(&c.rescanClientCounter, 1), - - chainConn: c, - - rescanUpdate: make(chan interface{}), - watchedAddresses: make(map[string]struct{}), - watchedOutPoints: make(map[wire.OutPoint]struct{}), - watchedTxs: make(map[chainhash.Hash]struct{}), - - notificationQueue: NewConcurrentQueue(20), - txNtfns: make(chan *wire.MsgTx, 1000), - blockNtfns: make(chan *wire.MsgBlock, 100), - - mempool: make(map[chainhash.Hash]struct{}), - expiredMempool: make(map[int32]map[chainhash.Hash]struct{}), - } -} - -// AddClient adds a client to the set of active rescan clients of the current -// chain connection. This allows the connection to include the specified client -// in its notification delivery. -// -// NOTE: This function is safe for concurrent access. -func (c *BitcoindConn) AddClient(client *BitcoindClient) { - c.rescanClientsMtx.Lock() - defer c.rescanClientsMtx.Unlock() - - c.rescanClients[client.id] = client -} - -// RemoveClient removes the client with the given ID from the set of active -// rescan clients. Once removed, the client will no longer receive block and -// transaction notifications from the chain connection. -// -// NOTE: This function is safe for concurrent access. -func (c *BitcoindConn) RemoveClient(id uint64) { - c.rescanClientsMtx.Lock() - defer c.rescanClientsMtx.Unlock() - - delete(c.rescanClients, id) -} - -// isBlockPrunedErr determines if the error returned by the GetBlock RPC -// corresponds to the requested block being pruned. -func isBlockPrunedErr(err error) bool { - rpcErr, ok := err.(*btcjson.RPCError) - return ok && rpcErr.Code == btcjson.ErrRPCMisc && - rpcErr.Message == errBlockPrunedStr -} - -// GetBlock returns a raw block from the server given its hash. If the server -// has already pruned the block, it will be retrieved from one of its peers. -func (c *BitcoindConn) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) { - block, err := c.client.GetBlock(hash) - // Got the block from the backend successfully, return it. - if err == nil { - return block, nil - } - - // We failed getting the block from the backend for whatever reason. If - // it wasn't due to the block being pruned, return the error - // immediately. - if !isBlockPrunedErr(err) || c.prunedBlockDispatcher == nil { - return nil, err - } - - // Now that we know the block has been pruned for sure, request it from - // our backend peers. - blockChan, errChan := c.prunedBlockDispatcher.Query( - []*chainhash.Hash{hash}, - ) - - for { - select { - case block := <-blockChan: - return block, nil - - case err := <-errChan: - if err != nil { - return nil, err - } - - // errChan fired before blockChan with a nil error, wait - // for the block now. - - case <-c.quit: - return nil, ErrBitcoindClientShuttingDown - } - } -} - -// isASCII is a helper method that checks whether all bytes in `data` would be -// printable ASCII characters if interpreted as a string. -func isASCII(s string) bool { - for _, c := range s { - if c < 32 || c > 126 { - return false - } - } - return true -} diff --git a/btcclient/bitcoind_events.go b/btcclient/bitcoind_events.go deleted file mode 100644 index 945257b7..00000000 --- a/btcclient/bitcoind_events.go +++ /dev/null @@ -1,705 +0,0 @@ -package btcclient - -import ( - "bytes" - "fmt" - "io" - "net" - "sync" - "time" - - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" - "github.com/lightninglabs/gozmq" -) - -const ( - // defaultBlockPollInterval is the default interval used for querying - // for new blocks. - defaultBlockPollInterval = time.Second * 10 - - // defaultTxPollInterval is the default interval used for querying - // for new mempool transactions. - defaultTxPollInterval = time.Second * 10 -) - -// BitcoindEvents is the interface that must be satisfied by any type that -// serves bitcoind block and transactions events. -type BitcoindEvents interface { - // TxNotifications will return a channel which will deliver new - // transactions. - TxNotifications() <-chan *wire.MsgTx - - // BlockNotifications will return a channel which will deliver new - // blocks. - BlockNotifications() <-chan *wire.MsgBlock - - // Start will kick off any goroutines required for operation. - Start() error - - // Stop will clean up any resources and goroutines. - Stop() error -} - -// NewBitcoindEventSubscriber initialises a new BitcoinEvents object impl -// depending on the config passed. -func NewBitcoindEventSubscriber(cfg *BitcoindConfig, - client *rpcclient.Client) (BitcoindEvents, error) { - - if cfg.PollingConfig != nil && cfg.ZMQConfig != nil { - return nil, fmt.Errorf("either PollingConfig or ZMQConfig " + - "should be specified, not both") - } - - if cfg.PollingConfig != nil { - if client == nil { - return nil, fmt.Errorf("rpc client must be given " + - "if rpc polling is to be used for event " + - "subscriptions") - } - - pollingEvents := newBitcoindRPCPollingEvents( - cfg.PollingConfig, client, - ) - - return pollingEvents, nil - } - - if cfg.ZMQConfig == nil { - return nil, fmt.Errorf("ZMQConfig must be specified if " + - "rpcpolling is disabled") - } - - return newBitcoindZMQEvents(cfg.ZMQConfig) -} - -// ZMQConfig holds all the config values needed to set up a ZMQ connection to -// bitcoind. -type ZMQConfig struct { - // ZMQBlockHost is the IP address and port of the bitcoind's rawblock - // listener. - ZMQBlockHost string - - // ZMQTxHost is the IP address and port of the bitcoind's rawtx - // listener. - ZMQTxHost string - - // ZMQReadDeadline represents the read deadline we'll apply when reading - // ZMQ messages from either subscription. - ZMQReadDeadline time.Duration -} - -// bitcoindZMQEvents delivers block and transaction notifications that it gets -// from ZMQ connections to bitcoind. -type bitcoindZMQEvents struct { - cfg *ZMQConfig - - // blockConn is the ZMQ connection we'll use to read raw block events. - blockConn *gozmq.Conn - - // txConn is the ZMQ connection we'll use to read raw transaction - // events. - txConn *gozmq.Conn - - // blockNtfns is a channel to which any new blocks will be sent. - blockNtfns chan *wire.MsgBlock - - // txNtfns is a channel to which any new transactions will be sent. - txNtfns chan *wire.MsgTx - - wg sync.WaitGroup - quit chan struct{} -} - -// newBitcoindZMQEvents initialises the necessary zmq connections to bitcoind. -func newBitcoindZMQEvents(cfg *ZMQConfig) (*bitcoindZMQEvents, error) { - // Establish two different ZMQ connections to bitcoind to retrieve block - // and transaction event notifications. We'll use two as a separation of - // concern to ensure one type of event isn't dropped from the connection - // queue due to another type of event filling it up. - zmqBlockConn, err := gozmq.Subscribe( - cfg.ZMQBlockHost, []string{rawBlockZMQCommand}, - cfg.ZMQReadDeadline, - ) - if err != nil { - return nil, fmt.Errorf("unable to subscribe for zmq block "+ - "events: %v", err) - } - - zmqTxConn, err := gozmq.Subscribe( - cfg.ZMQTxHost, []string{rawTxZMQCommand}, cfg.ZMQReadDeadline, - ) - if err != nil { - // Ensure that the block zmq connection is closed in the case - // that it succeeded but the tx zmq connection failed. - if err := zmqBlockConn.Close(); err != nil { - log.Errorf("could not close zmq block conn: %v", err) - } - - return nil, fmt.Errorf("unable to subscribe for zmq tx "+ - "events: %v", err) - } - - return &bitcoindZMQEvents{ - cfg: cfg, - blockConn: zmqBlockConn, - txConn: zmqTxConn, - blockNtfns: make(chan *wire.MsgBlock), - txNtfns: make(chan *wire.MsgTx), - quit: make(chan struct{}), - }, nil -} - -// Start spins off the bitcoindZMQEvent goroutines. -func (b *bitcoindZMQEvents) Start() error { - b.wg.Add(2) - go b.blockEventHandler() - go b.txEventHandler() - return nil -} - -// Stop cleans up any of the resources and goroutines held by bitcoindZMQEvents. -func (b *bitcoindZMQEvents) Stop() error { - var returnErr error - if err := b.txConn.Close(); err != nil { - returnErr = err - } - - if err := b.blockConn.Close(); err != nil { - returnErr = err - } - - close(b.quit) - b.wg.Wait() - return returnErr -} - -// TxNotifications returns a channel which will deliver new transactions. -func (b *bitcoindZMQEvents) TxNotifications() <-chan *wire.MsgTx { - return b.txNtfns -} - -// BlockNotifications returns a channel which will deliver new blocks. -func (b *bitcoindZMQEvents) BlockNotifications() <-chan *wire.MsgBlock { - return b.blockNtfns -} - -// blockEventHandler reads raw blocks events from the ZMQ block socket and -// forwards them along to the current rescan clients. -// -// NOTE: This must be run as a goroutine. -func (b *bitcoindZMQEvents) blockEventHandler() { - defer b.wg.Done() - - log.Info("Started listening for bitcoind block notifications via ZMQ "+ - "on", b.blockConn.RemoteAddr()) - - // Set up the buffers we expect our messages to consume. ZMQ - // messages from bitcoind include three parts: the command, the - // data, and the sequence number. - // - // We'll allocate a fixed data slice that we'll reuse when reading - // blocks from bitcoind through ZMQ. There's no need to recycle this - // slice (zero out) after using it, as further reads will overwrite the - // slice and we'll only be deserializing the bytes needed. - var ( - command [len(rawBlockZMQCommand)]byte - seqNum [seqNumLen]byte - data = make([]byte, maxRawBlockSize) - ) - - for { - // Before attempting to read from the ZMQ socket, we'll make - // sure to check if we've been requested to shut down. - select { - case <-b.quit: - return - default: - } - - // Poll an event from the ZMQ socket. - var ( - bufs = [][]byte{command[:], data, seqNum[:]} - err error - ) - bufs, err = b.blockConn.Receive(bufs) - if err != nil { - // EOF should only be returned if the connection was - // explicitly closed, so we can exit at this point. - if err == io.EOF { - return - } - - // It's possible that the connection to the socket - // continuously times out, so we'll prevent logging this - // error to prevent spamming the logs. - netErr, ok := err.(net.Error) - if ok && netErr.Timeout() { - log.Trace("Re-establishing timed out ZMQ " + - "block connection") - continue - } - - log.Errorf("Unable to receive ZMQ %v message: %v", - rawBlockZMQCommand, err) - continue - } - - // We have an event! We'll now ensure it is a block event, - // deserialize it, and report it to the different rescan - // clients. - eventType := string(bufs[0]) - switch eventType { - case rawBlockZMQCommand: - block := &wire.MsgBlock{} - r := bytes.NewReader(bufs[1]) - if err := block.Deserialize(r); err != nil { - log.Errorf("Unable to deserialize block: %v", - err) - continue - } - - select { - case b.blockNtfns <- block: - case <-b.quit: - return - } - - default: - // It's possible that the message wasn't fully read if - // bitcoind shuts down, which will produce an unreadable - // event type. To prevent from logging it, we'll make - // sure it conforms to the ASCII standard. - if eventType == "" || !isASCII(eventType) { - continue - } - - log.Warnf("Received unexpected event type from %v "+ - "subscription: %v", rawBlockZMQCommand, - eventType) - } - } -} - -// txEventHandler reads raw blocks events from the ZMQ block socket and forwards -// them along to the current rescan clients. -// -// NOTE: This must be run as a goroutine. -func (b *bitcoindZMQEvents) txEventHandler() { - defer b.wg.Done() - - log.Info("Started listening for bitcoind transaction notifications "+ - "via ZMQ on", b.txConn.RemoteAddr()) - - // Set up the buffers we expect our messages to consume. ZMQ - // messages from bitcoind include three parts: the command, the - // data, and the sequence number. - // - // We'll allocate a fixed data slice that we'll reuse when reading - // transactions from bitcoind through ZMQ. There's no need to recycle - // this slice (zero out) after using it, as further reads will overwrite - // the slice and we'll only be deserializing the bytes needed. - var ( - command [len(rawTxZMQCommand)]byte - seqNum [seqNumLen]byte - data = make([]byte, maxRawTxSize) - ) - - for { - // Before attempting to read from the ZMQ socket, we'll make - // sure to check if we've been requested to shut down. - select { - case <-b.quit: - return - default: - } - - // Poll an event from the ZMQ socket. - var ( - bufs = [][]byte{command[:], data, seqNum[:]} - err error - ) - bufs, err = b.txConn.Receive(bufs) - if err != nil { - // EOF should only be returned if the connection was - // explicitly closed, so we can exit at this point. - if err == io.EOF { - return - } - - // It's possible that the connection to the socket - // continuously times out, so we'll prevent logging this - // error to prevent spamming the logs. - netErr, ok := err.(net.Error) - if ok && netErr.Timeout() { - log.Trace("Re-establishing timed out ZMQ " + - "transaction connection") - continue - } - - log.Errorf("Unable to receive ZMQ %v message: %v", - rawTxZMQCommand, err) - continue - } - - // We have an event! We'll now ensure it is a transaction event, - // deserialize it, and report it to the different rescan - // clients. - eventType := string(bufs[0]) - switch eventType { - case rawTxZMQCommand: - tx := &wire.MsgTx{} - r := bytes.NewReader(bufs[1]) - if err := tx.Deserialize(r); err != nil { - log.Errorf("Unable to deserialize "+ - "transaction: %v", err) - continue - } - - select { - case b.txNtfns <- tx: - case <-b.quit: - return - } - - default: - // It's possible that the message wasn't fully read if - // bitcoind shuts down, which will produce an unreadable - // event type. To prevent from logging it, we'll make - // sure it conforms to the ASCII standard. - if eventType == "" || !isASCII(eventType) { - continue - } - - log.Warnf("Received unexpected event type from %v "+ - "subscription: %v", rawTxZMQCommand, eventType) - } - } -} - -// PollingConfig holds all the config options used for setting up -// bitcoindRPCPollingEvents. -type PollingConfig struct { - // BlockPollingInterval is the interval that will be used to poll - // bitcoind for new blocks. - BlockPollingInterval time.Duration - - // TxPollingInterval is the interval that will be used to poll bitcoind - // for new transactions. - TxPollingInterval time.Duration -} - -// bitcoindRPCPollingEvents delivers block and transaction notifications that -// it gets by polling bitcoind's rpc interface at regular intervals. -type bitcoindRPCPollingEvents struct { - cfg *PollingConfig - - client *rpcclient.Client - - // mempool holds all the transactions that we currently see as being in - // the mempool. This is used so that we know which transactions we have - // already sent notifications for. - mempool *mempool - - // blockNtfns is a channel to which any new blocks will be sent. - blockNtfns chan *wire.MsgBlock - - // txNtfns is a channel to which any new transactions will be sent. - txNtfns chan *wire.MsgTx - - wg sync.WaitGroup - quit chan struct{} -} - -// newBitcoindRPCPollingEvents instantiates a new bitcoindRPCPollingEvents -// object. -func newBitcoindRPCPollingEvents(cfg *PollingConfig, - client *rpcclient.Client) *bitcoindRPCPollingEvents { - - if cfg.BlockPollingInterval == 0 { - cfg.BlockPollingInterval = defaultBlockPollInterval - } - - if cfg.TxPollingInterval == 0 { - cfg.TxPollingInterval = defaultTxPollInterval - } - - return &bitcoindRPCPollingEvents{ - cfg: cfg, - client: client, - txNtfns: make(chan *wire.MsgTx), - blockNtfns: make(chan *wire.MsgBlock), - mempool: newMempool(), - quit: make(chan struct{}), - } -} - -// Start kicks off all the bitcoindRPCPollingEvents goroutines. -func (b *bitcoindRPCPollingEvents) Start() error { - info, err := b.client.GetBlockChainInfo() - if err != nil { - return err - } - - b.wg.Add(2) - go b.blockEventHandlerRPC(info.Blocks) - go b.txEventHandlerRPC() - return nil -} - -// Stop cleans up all the bitcoindRPCPollingEvents resources and goroutines. -func (b *bitcoindRPCPollingEvents) Stop() error { - close(b.quit) - b.wg.Wait() - return nil -} - -// TxNotifications returns a channel which will deliver new transactions. -func (b *bitcoindRPCPollingEvents) TxNotifications() <-chan *wire.MsgTx { - return b.txNtfns -} - -// BlockNotifications returns a channel which will deliver new blocks. -func (b *bitcoindRPCPollingEvents) BlockNotifications() <-chan *wire.MsgBlock { - return b.blockNtfns -} - -// blockEventHandlerRPC is a goroutine that uses the rpc client to check if we -// have a new block every so often. -func (b *bitcoindRPCPollingEvents) blockEventHandlerRPC(startHeight int32) { - defer b.wg.Done() - - ticker := time.NewTicker(b.cfg.BlockPollingInterval) - defer ticker.Stop() - - height := startHeight - log.Infof("Started polling for new bitcoind blocks via RPC at "+ - "height %d", height) - - for { - select { - case <-ticker.C: - // At every interval, we poll to see if there's a block - // with a height that exceeds the height that we - // previously recorded. - info, err := b.client.GetBlockChainInfo() - if err != nil { - log.Errorf("Unable to retrieve best block: "+ - "%v", err) - continue - } - - // If the block isn't new, we continue and wait for the - // next interval tick. In order to replicate the - // behaviour of the zmq block subscription, we only do - // a height based check here. We only deliver - // notifications if the new block has a height above the - // one we previously saw. The caller is left to - // determine if there has been a reorg. - if info.Blocks <= height { - continue - } - - // Since we do a height based check, we send - // notifications for each block with a height between - // the last height we recorded and the new height. - for i := height + 1; i <= info.Blocks; i++ { - newHash, err := b.client.GetBlockHash(int64(i)) - if err != nil { - log.Errorf("Unable to retrieve "+ - "block hash: %v", err) - continue - } - - newBlock, err := b.client.GetBlock(newHash) - if err != nil { - log.Errorf("Unable to retrieve "+ - "block: %v", err) - continue - } - - // notify the client of the new block. - select { - case b.blockNtfns <- newBlock: - case <-b.quit: - return - } - - // From our local mempool map, let's remove each - // of the transactions that are confirmed in - // this new block, since they are no longer in - // the mempool. - b.mempool.clean(newBlock.Transactions) - - height++ - } - - case <-b.quit: - return - } - } -} - -// txEventHandlerRPC is a goroutine that uses the RPC client to check the -// mempool for new transactions. -func (b *bitcoindRPCPollingEvents) txEventHandlerRPC() { - defer b.wg.Done() - - log.Info("Started polling for new bitcoind transactions via RPC.") - ticker := time.NewTicker(b.cfg.TxPollingInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // After each ticker interval, we poll the mempool to - // check for transactions we haven't seen yet. - txs, err := b.client.GetRawMempool() - if err != nil { - log.Errorf("Unable to retrieve mempool txs: "+ - "%v", err) - continue - } - - // Set all mempool txs to false. - b.mempool.unmarkAll() - - // We'll scan through the most recent txs in the - // mempool to see whether there are new txs that we - // need to send to the client. - for _, txHash := range txs { - // If the transaction is already in our local - // mempool, then we have already sent it to the - // client. - if b.mempool.contains(*txHash) { - // Mark the tx as true so that we know - // not to remove it from our internal - // mempool. - b.mempool.mark(*txHash) - continue - } - - // Grab full mempool transaction from hash. - tx, err := b.client.GetRawTransaction(txHash) - if err != nil { - log.Errorf("unable to fetch "+ - "transaction %s from "+ - "mempool: %v", txHash, err) - continue - } - - // Add the transaction to our local mempool. - // Note that we only do this after fetching - // the full raw transaction from bitcoind. - // We do this so that if that call happens to - // initially fail, then we will retry it on the - // next interval since it is still not in our - // local mempool. - b.mempool.add(*txHash) - - select { - case b.txNtfns <- tx.MsgTx(): - case <-b.quit: - return - } - } - - // Now, we clear our internal mempool of any unmarked - // transactions. These are all the transactions that - // we still have in the mempool but that were not - // returned in the latest GetRawMempool query. - b.mempool.deleteUnmarked() - - case <-b.quit: - return - } - } -} - -// mempool represents our view of the mempool and helps to keep track of which -// mempool transactions we already know about. The boolean in the txs map is -// used to indicate if we should remove the tx from our local mempool due to -// the chain backend's mempool no longer containing it. -type mempool struct { - sync.RWMutex - txs map[chainhash.Hash]bool -} - -// newMempool creates a new mempool object. -func newMempool() *mempool { - return &mempool{ - txs: make(map[chainhash.Hash]bool), - } -} - -// clean removes any of the given transactions from the mempool if they are -// found there. -func (m *mempool) clean(txs []*wire.MsgTx) { - m.Lock() - defer m.Unlock() - - for _, tx := range txs { - // If the transaction is in our mempool map, we need to delete - // it. - delete(m.txs, tx.TxHash()) - } -} - -// contains returns true if the given transaction hash is already in our -// mempool. -func (m *mempool) contains(hash chainhash.Hash) bool { - m.RLock() - defer m.RUnlock() - - _, ok := m.txs[hash] - return ok -} - -// add inserts the given hash into our mempool and marks it to indicate that it -// should not be deleted. -func (m *mempool) add(hash chainhash.Hash) { - m.Lock() - defer m.Unlock() - - m.txs[hash] = true -} - -// unmarkAll un-marks all the transactions in the mempool. This should be done -// just before we re-evaluate the contents of our local mempool comared to the -// chain backend's mempool. -func (m *mempool) unmarkAll() { - m.Lock() - defer m.Unlock() - - for hash := range m.txs { - m.txs[hash] = false - } -} - -// mark marks the transaction of the given hash to indicate that it is still -// present in the chain backend's mempool. -func (m *mempool) mark(hash chainhash.Hash) { - m.Lock() - defer m.Unlock() - - if _, ok := m.txs[hash]; !ok { - return - } - - m.txs[hash] = true -} - -// deleteUnmarked removes all the unmarked transactions from our local mempool. -func (m *mempool) deleteUnmarked() { - m.Lock() - defer m.Unlock() - - for hash, marked := range m.txs { - if marked { - continue - } - - delete(m.txs, hash) - } -} diff --git a/btcclient/bitcoind_events_test.go b/btcclient/bitcoind_events_test.go deleted file mode 100644 index c959f2fd..00000000 --- a/btcclient/bitcoind_events_test.go +++ /dev/null @@ -1,491 +0,0 @@ -package btcclient - -import ( - "fmt" - "io/ioutil" - "math/rand" - "os" - "os/exec" - "testing" - "time" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/stretchr/testify/require" -) - -// TestBitcoindEvents ensures that the BitcoindClient correctly delivers tx and -// block notifications for both the case where a ZMQ subscription is used and -// for the case where RPC polling is used. -func TestBitcoindEvents(t *testing.T) { - tests := []struct { - name string - rpcPolling bool - }{ - { - name: "Events via ZMQ subscriptions", - rpcPolling: false, - }, - { - name: "Events via RPC Polling", - rpcPolling: true, - }, - } - - // Set up 2 btcd miners. - miner1, miner2 := setupMiners(t) - - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - // Set up a bitcoind node and connect it to miner 1. - btcClient := setupBitcoind( - t, miner1.P2PAddress(), test.rpcPolling, - ) - - // Test that the correct block `Connect` and - // `Disconnect` notifications are received during a - // re-org. - testReorg(t, miner1, miner2, btcClient) - - // Test that the expected block and transaction - // notifications are received. - testNotifications(t, miner1, btcClient) - }) - } -} - -// TestMempool tests that each method of the mempool struct works as expected. -func TestMempool(t *testing.T) { - m := newMempool() - - // Create a transaction. - tx1 := &wire.MsgTx{LockTime: 1} - - // Check that mempool doesn't have the tx yet. - require.False(t, m.contains(tx1.TxHash())) - - // Now add the tx. - m.add(tx1.TxHash()) - - // Mempool should now contain the tx. - require.True(t, m.contains(tx1.TxHash())) - - // Add another tx to the mempool. - tx2 := &wire.MsgTx{LockTime: 2} - m.add(tx2.TxHash()) - require.True(t, m.contains(tx2.TxHash())) - - // Clean the mempool of tx1 (this simulates a block being confirmed - // with tx1 in the block). - m.clean([]*wire.MsgTx{tx1}) - - // Ensure that tx1 is no longer in the mempool but that tx2 still is. - require.False(t, m.contains(tx1.TxHash())) - require.True(t, m.contains(tx2.TxHash())) - - // Lastly, we test that only marked transactions are deleted from the - // mempool. - - // Let's first re-add tx1 so that we have more txs to work with. - m.add(tx1.TxHash()) - - // Now, we unmark all the transactions. - m.unmarkAll() - - // Add tx3. This should automatically mark tx3. - tx3 := &wire.MsgTx{LockTime: 3} - m.add(tx3.TxHash()) - - // Let us now manually mark tx2. - m.mark(tx2.TxHash()) - - // Now we delete all unmarked txs. This should leave only tx2 and tx3 - // in the mempool. - m.deleteUnmarked() - - require.False(t, m.contains(tx1.TxHash())) - require.True(t, m.contains(tx2.TxHash())) - require.True(t, m.contains(tx3.TxHash())) -} - -// testNotifications tests that the correct notifications are received for -// blocks and transactions in the simple non-reorg case. -func testNotifications(t *testing.T, miner *rpctest.Harness, - client *BitcoindClient) { - - script, _, err := randPubKeyHashScript() - require.NoError(t, err) - - tx, err := miner.CreateTransaction( - []*wire.TxOut{{Value: 1000, PkScript: script}}, 5, false, - ) - require.NoError(t, err) - - hash := tx.TxHash() - - err = client.NotifyTx([]chainhash.Hash{hash}) - require.NoError(t, err) - - _, err = client.SendRawTransaction(tx, true) - require.NoError(t, err) - - ntfns := client.Notifications() - - miner.Client.Generate(1) - - // First, we expect to get a RelevantTx notification. - select { - case ntfn := <-ntfns: - tx, ok := ntfn.(RelevantTx) - if !ok { - t.Fatalf("Expected a notification of type "+ - "RelevantTx, got %T", ntfn) - } - - require.True(t, tx.TxRecord.Hash.IsEqual(&hash)) - - case <-time.After(time.Second): - t.Fatalf("timed out waiting for RelevantTx notification") - } - - // Then, we expect to get a FilteredBlockConnected notification. - select { - case ntfn := <-ntfns: - _, ok := ntfn.(FilteredBlockConnected) - if !ok { - t.Fatalf("Expected a notification of type "+ - "FilteredBlockConnected, got %T", ntfn) - } - - case <-time.After(time.Second): - t.Fatalf("timed out waiting for FilteredBlockConnected " + - "notification") - } - - // Lastly, we expect to get a BlockConnected notification. - select { - case ntfn := <-ntfns: - _, ok := ntfn.(BlockConnected) - if !ok { - t.Fatalf("Expected a notification of type "+ - "BlockConnected, got %T", ntfn) - } - - case <-time.After(time.Second): - t.Fatalf("timed out waiting for BlockConnected notification") - } -} - -// testReorg tests that the given BitcoindClient correctly responds to a chain -// re-org. -func testReorg(t *testing.T, miner1, miner2 *rpctest.Harness, - client *BitcoindClient) { - - miner1Hash, commonHeight, err := miner1.Client.GetBestBlock() - require.NoError(t, err) - - miner2Hash, miner2Height, err := miner2.Client.GetBestBlock() - require.NoError(t, err) - - require.Equal(t, commonHeight, miner2Height) - require.Equal(t, miner1Hash, miner2Hash) - - // Let miner2 generate a few blocks and ensure that our bitcoind client - // is notified of this block. - hashes, err := miner2.Client.Generate(5) - require.NoError(t, err) - require.Len(t, hashes, 5) - - ntfns := client.Notifications() - - for i := 0; i < 5; i++ { - commonHeight++ - ntfnHash := waitForBlockNtfn(t, ntfns, commonHeight, true) - require.True(t, ntfnHash.IsEqual(hashes[i])) - } - - // Now disconnect the two miners. - err = miner1.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove) - require.NoError(t, err) - - // Generate 5 blocks on miner2. - _, err = miner2.Client.Generate(5) - require.NoError(t, err) - - // Since the miners have been disconnected, we expect not to get any - // notifications from our client since our client is connected to - // miner1. - select { - case ntfn := <-ntfns: - t.Fatalf("received a notification of type %T but expected, "+ - "none", ntfn) - - case <-time.After(time.Millisecond * 500): - } - - // Now generate 3 blocks on miner1. Note that to force our client to - // experience a re-org, miner1 must generate fewer blocks here than - // miner2 so that when they reconnect, miner1 does a re-org to switch - // to the longer chain. - _, err = miner1.Client.Generate(3) - require.NoError(t, err) - - // Read the notifications for the new blocks - for i := 0; i < 3; i++ { - _ = waitForBlockNtfn(t, ntfns, commonHeight+int32(i+1), true) - } - - // Ensure that the two miners have different ideas of what the best - // block is. - hash1, height1, err := miner1.Client.GetBestBlock() - require.NoError(t, err) - require.Equal(t, commonHeight+3, height1) - - hash2, height2, err := miner2.Client.GetBestBlock() - require.NoError(t, err) - require.Equal(t, commonHeight+5, height2) - - require.False(t, hash1.IsEqual(hash2)) - - // Reconnect the miners. This should result in miner1 reorging to match - // miner2. Since our client is connected to a node connected to miner1, - // we should get the expected disconnected and connected notifications. - err = rpctest.ConnectNode(miner1, miner2) - require.NoError(t, err) - - err = rpctest.JoinNodes( - []*rpctest.Harness{miner1, miner2}, rpctest.Blocks, - ) - require.NoError(t, err) - - // Check that the miners are now on the same page. - hash1, height1, err = miner1.Client.GetBestBlock() - require.NoError(t, err) - - hash2, height2, err = miner2.Client.GetBestBlock() - require.NoError(t, err) - - require.Equal(t, commonHeight+5, height2) - require.Equal(t, commonHeight+5, height1) - require.True(t, hash1.IsEqual(hash2)) - - // We expect our client to get 3 BlockDisconnected notifications first - // signaling the unwinding of its top 3 blocks. - for i := 0; i < 3; i++ { - _ = waitForBlockNtfn(t, ntfns, commonHeight+int32(3-i), false) - } - - // Now we expect 5 BlockConnected notifications. - for i := 0; i < 5; i++ { - _ = waitForBlockNtfn(t, ntfns, commonHeight+int32(i+1), true) - } -} - -// waitForBlockNtfn waits on the passed channel for a BlockConnected or -// BlockDisconnected notification for a block of the expectedHeight. It returns -// hash of the notification if received. If the expected notification is not -// received within 2 seconds, the test is failed. Use the `connected` parameter -// to set whether a Connected or Disconnected notification is expected. -func waitForBlockNtfn(t *testing.T, ntfns <-chan interface{}, - expectedHeight int32, connected bool) chainhash.Hash { - - timer := time.NewTimer(2 * time.Second) - for { - select { - case nftn := <-ntfns: - switch ntfnType := nftn.(type) { - case BlockConnected: - if !connected { - fmt.Println("???") - continue - } - - if ntfnType.Height < expectedHeight { - continue - } else if ntfnType.Height != expectedHeight { - t.Fatalf("expected notification for "+ - "height %d, got height %d", - expectedHeight, ntfnType.Height) - } - - return ntfnType.Hash - - case BlockDisconnected: - if connected { - continue - } - - if ntfnType.Height > expectedHeight { - continue - } else if ntfnType.Height != expectedHeight { - t.Fatalf("expected notification for "+ - "height %d, got height %d", - expectedHeight, ntfnType.Height) - } - - return ntfnType.Hash - - default: - } - - case <-timer.C: - t.Fatalf("timed out waiting for block notification") - } - } -} - -// setUpMiners sets up two miners that can be used for a re-org test. -func setupMiners(t *testing.T) (*rpctest.Harness, *rpctest.Harness) { - trickle := fmt.Sprintf("--trickleinterval=%v", 10*time.Millisecond) - args := []string{trickle} - - miner1, err := rpctest.New( - &chaincfg.RegressionNetParams, nil, args, "", - ) - require.NoError(t, err) - - t.Cleanup(func() { - miner1.TearDown() - }) - - require.NoError(t, miner1.SetUp(true, 1)) - - miner2, err := rpctest.New( - &chaincfg.RegressionNetParams, nil, args, "", - ) - require.NoError(t, err) - - t.Cleanup(func() { - miner2.TearDown() - }) - - require.NoError(t, miner2.SetUp(false, 0)) - - // Connect the miners. - require.NoError(t, rpctest.ConnectNode(miner1, miner2)) - - err = rpctest.JoinNodes( - []*rpctest.Harness{miner1, miner2}, rpctest.Blocks, - ) - require.NoError(t, err) - - return miner1, miner2 -} - -// setupBitcoind starts up a bitcoind node with either a zmq connection or -// rpc polling connection and returns a client wrapper of this connection. -func setupBitcoind(t *testing.T, minerAddr string, - rpcPolling bool) *BitcoindClient { - t.Skip("TODO: this test requires the bitcoind binary, skip for now") - - // Start a bitcoind instance and connect it to miner1. - tempBitcoindDir, err := ioutil.TempDir("", "bitcoind") - require.NoError(t, err) - - zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket" - zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket" - t.Cleanup(func() { - os.RemoveAll(tempBitcoindDir) - }) - - rpcPort := rand.Int()%(65536-1024) + 1024 - bitcoind := exec.Command( - "bitcoind", - "-datadir="+tempBitcoindDir, - "-regtest", - "-connect="+minerAddr, - "-txindex", - "-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+ - "d$507c670e800a95284294edb5773b05544b"+ - "220110063096c221be9933c82d38e1", - fmt.Sprintf("-rpcport=%d", rpcPort), - "-disablewallet", - "-zmqpubrawblock="+zmqBlockHost, - "-zmqpubrawtx="+zmqTxHost, - ) - require.NoError(t, bitcoind.Start()) - - t.Cleanup(func() { - bitcoind.Process.Kill() - bitcoind.Wait() - }) - - // Wait for the bitcoind instance to start up. - time.Sleep(time.Second) - - host := fmt.Sprintf("127.0.0.1:%d", rpcPort) - cfg := &BitcoindConfig{ - ChainParams: &chaincfg.RegressionNetParams, - Host: host, - User: "weks", - Pass: "weks", - // Fields only required for pruned nodes, not - // needed for these tests. - Dialer: nil, - PrunedModeMaxPeers: 0, - } - - if rpcPolling { - cfg.PollingConfig = &PollingConfig{ - BlockPollingInterval: time.Millisecond * 100, - TxPollingInterval: time.Millisecond * 100, - } - } else { - cfg.ZMQConfig = &ZMQConfig{ - ZMQBlockHost: zmqBlockHost, - ZMQTxHost: zmqTxHost, - ZMQReadDeadline: 5 * time.Second, - } - } - - chainConn, err := NewBitcoindConn(cfg) - require.NoError(t, err) - require.NoError(t, chainConn.Start()) - - t.Cleanup(func() { - chainConn.Stop() - }) - - // Create a bitcoind client. - btcClient := chainConn.NewBitcoindClient() - require.NoError(t, btcClient.Start()) - - t.Cleanup(func() { - btcClient.Stop() - }) - - require.NoError(t, btcClient.NotifyBlocks()) - - return btcClient -} - -// randPubKeyHashScript generates a P2PKH script that pays to the public key of -// a randomly-generated private key. -func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) { - privKey, err := btcec.NewPrivateKey() - if err != nil { - return nil, nil, err - } - - pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) - addrScript, err := btcutil.NewAddressPubKeyHash( - pubKeyHash, &chaincfg.RegressionNetParams, - ) - if err != nil { - return nil, nil, err - } - - pkScript, err := txscript.PayToAddrScript(addrScript) - if err != nil { - return nil, nil, err - } - - return pkScript, privKey, nil -} diff --git a/btcclient/block_filterer.go b/btcclient/block_filterer.go deleted file mode 100644 index e6fb348f..00000000 --- a/btcclient/block_filterer.go +++ /dev/null @@ -1,217 +0,0 @@ -package btcclient - -import ( - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcwallet/waddrmgr" -) - -// BlockFilterer is used to iteratively scan blocks for a set of addresses of -// interest. This is done by constructing reverse indexes mapping the -// addresses to a ScopedIndex, which permits the reconstruction of the exact -// child deriviation paths that reported matches. -// -// Once initialized, a BlockFilterer can be used to scan any number of blocks -// until a invocation of `FilterBlock` returns true. This allows the reverse -// indexes to be resused in the event that the set of addresses does not need to -// be altered. After a match is reported, a new BlockFilterer should be -// initialized with the updated set of addresses that include any new keys that -// are now within our look-ahead. -// -// We track internal and external addresses separately in order to conserve the -// amount of space occupied in memory. Specifically, the account and branch -// combined contribute only 1-bit of information when using the default scopes -// used by the wallet. Thus we can avoid storing an additional 64-bits per -// address of interest by not storing the full derivation paths, and instead -// opting to allow the caller to contextually infer the account (DefaultAccount) -// and branch (Internal or External). -type BlockFilterer struct { - // Params specifies the chain params of the current network. - Params *chaincfg.Params - - // ExReverseFilter holds a reverse index mapping an external address to - // the scoped index from which it was derived. - ExReverseFilter map[string]waddrmgr.ScopedIndex - - // InReverseFilter holds a reverse index mapping an internal address to - // the scoped index from which it was derived. - InReverseFilter map[string]waddrmgr.ScopedIndex - - // WathcedOutPoints is a global set of outpoints being tracked by the - // wallet. This allows the block filterer to check for spends from an - // outpoint we own. - WatchedOutPoints map[wire.OutPoint]btcutil.Address - - // FoundExternal is a two-layer map recording the scope and index of - // external addresses found in a single block. - FoundExternal map[waddrmgr.KeyScope]map[uint32]struct{} - - // FoundInternal is a two-layer map recording the scope and index of - // internal addresses found in a single block. - FoundInternal map[waddrmgr.KeyScope]map[uint32]struct{} - - // FoundOutPoints is a set of outpoints found in a single block whose - // address belongs to the wallet. - FoundOutPoints map[wire.OutPoint]btcutil.Address - - // RelevantTxns records the transactions found in a particular block - // that contained matches from an address in either ExReverseFilter or - // InReverseFilter. - RelevantTxns []*wire.MsgTx -} - -// NewBlockFilterer constructs the reverse indexes for the current set of -// external and internal addresses that we are searching for, and is used to -// scan successive blocks for addresses of interest. A particular block filter -// can be reused until the first call from `FitlerBlock` returns true. -func NewBlockFilterer(params *chaincfg.Params, - req *FilterBlocksRequest) *BlockFilterer { - - // Construct a reverse index by address string for the requested - // external addresses. - nExAddrs := len(req.ExternalAddrs) - exReverseFilter := make(map[string]waddrmgr.ScopedIndex, nExAddrs) - for scopedIndex, addr := range req.ExternalAddrs { - exReverseFilter[addr.EncodeAddress()] = scopedIndex - } - - // Construct a reverse index by address string for the requested - // internal addresses. - nInAddrs := len(req.InternalAddrs) - inReverseFilter := make(map[string]waddrmgr.ScopedIndex, nInAddrs) - for scopedIndex, addr := range req.InternalAddrs { - inReverseFilter[addr.EncodeAddress()] = scopedIndex - } - - foundExternal := make(map[waddrmgr.KeyScope]map[uint32]struct{}) - foundInternal := make(map[waddrmgr.KeyScope]map[uint32]struct{}) - foundOutPoints := make(map[wire.OutPoint]btcutil.Address) - - return &BlockFilterer{ - Params: params, - ExReverseFilter: exReverseFilter, - InReverseFilter: inReverseFilter, - WatchedOutPoints: req.WatchedOutPoints, - FoundExternal: foundExternal, - FoundInternal: foundInternal, - FoundOutPoints: foundOutPoints, - } -} - -// FilterBlock parses all txns in the provided block, searching for any that -// contain addresses of interest in either the external or internal reverse -// filters. This method return true iff the block contains a non-zero number of -// addresses of interest, or a transaction in the block spends from outpoints -// controlled by the wallet. -func (bf *BlockFilterer) FilterBlock(block *wire.MsgBlock) bool { - var hasRelevantTxns bool - for _, tx := range block.Transactions { - if bf.FilterTx(tx) { - bf.RelevantTxns = append(bf.RelevantTxns, tx) - hasRelevantTxns = true - } - } - - return hasRelevantTxns -} - -// FilterTx scans all txouts in the provided txn, testing to see if any found -// addresses match those contained within the external or internal reverse -// indexes. This method returns true iff the txn contains a non-zero number of -// addresses of interest, or the transaction spends from an outpoint that -// belongs to the wallet. -func (bf *BlockFilterer) FilterTx(tx *wire.MsgTx) bool { - var isRelevant bool - - // First, check the inputs to this transaction to see if they spend any - // inputs belonging to the wallet. In addition to checking - // WatchedOutPoints, we also check FoundOutPoints, in case a txn spends - // from an outpoint created in the same block. - for _, in := range tx.TxIn { - if _, ok := bf.WatchedOutPoints[in.PreviousOutPoint]; ok { - isRelevant = true - } - if _, ok := bf.FoundOutPoints[in.PreviousOutPoint]; ok { - isRelevant = true - } - } - - // Now, parse all of the outputs created by this transactions, and see - // if they contain any addresses known the wallet using our reverse - // indexes for both external and internal addresses. If a new output is - // found, we will add the outpoint to our set of FoundOutPoints. - for i, out := range tx.TxOut { - _, addrs, _, err := txscript.ExtractPkScriptAddrs( - out.PkScript, bf.Params, - ) - if err != nil { - log.Warnf("Could not parse output script in %s:%d: %v", - tx.TxHash(), i, err) - continue - } - - if !bf.FilterOutputAddrs(addrs) { - continue - } - - // If we've reached this point, then the output contains an - // address of interest. - isRelevant = true - - // Record the outpoint that containing the address in our set of - // found outpoints, so that the caller can update its global - // set of watched outpoints. - outPoint := wire.OutPoint{ - Hash: *btcutil.NewTx(tx).Hash(), - Index: uint32(i), - } - - bf.FoundOutPoints[outPoint] = addrs[0] - } - - return isRelevant -} - -// FilterOutputAddrs tests the set of addresses against the block filterer's -// external and internal reverse address indexes. If any are found, they are -// added to set of external and internal found addresses, respectively. This -// method returns true iff a non-zero number of the provided addresses are of -// interest. -func (bf *BlockFilterer) FilterOutputAddrs(addrs []btcutil.Address) bool { - var isRelevant bool - for _, addr := range addrs { - addrStr := addr.EncodeAddress() - if scopedIndex, ok := bf.ExReverseFilter[addrStr]; ok { - bf.foundExternal(scopedIndex) - isRelevant = true - } - if scopedIndex, ok := bf.InReverseFilter[addrStr]; ok { - bf.foundInternal(scopedIndex) - isRelevant = true - } - } - - return isRelevant -} - -// foundExternal marks the scoped index as found within the block filterer's -// FoundExternal map. If this the first index found for a particular scope, the -// scope's second layer map will be initialized before marking the index. -func (bf *BlockFilterer) foundExternal(scopedIndex waddrmgr.ScopedIndex) { - if _, ok := bf.FoundExternal[scopedIndex.Scope]; !ok { - bf.FoundExternal[scopedIndex.Scope] = make(map[uint32]struct{}) - } - bf.FoundExternal[scopedIndex.Scope][scopedIndex.Index] = struct{}{} -} - -// foundInternal marks the scoped index as found within the block filterer's -// FoundInternal map. If this the first index found for a particular scope, the -// scope's second layer map will be initialized before marking the index. -func (bf *BlockFilterer) foundInternal(scopedIndex waddrmgr.ScopedIndex) { - if _, ok := bf.FoundInternal[scopedIndex.Scope]; !ok { - bf.FoundInternal[scopedIndex.Scope] = make(map[uint32]struct{}) - } - bf.FoundInternal[scopedIndex.Scope][scopedIndex.Index] = struct{}{} -} diff --git a/btcclient/block_filterer_test.go b/btcclient/block_filterer_test.go deleted file mode 100644 index 3d0702c6..00000000 --- a/btcclient/block_filterer_test.go +++ /dev/null @@ -1,324 +0,0 @@ -package btcclient_test - -import ( - "reflect" - "testing" - "time" - - "github.com/babylonchain/vigilante/btcclient" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" -) - -var Block100000 = wire.MsgBlock{ - Header: wire.BlockHeader{ - Version: 1, - PrevBlock: chainhash.Hash([32]byte{ // Make go vet happy. - 0x50, 0x12, 0x01, 0x19, 0x17, 0x2a, 0x61, 0x04, - 0x21, 0xa6, 0xc3, 0x01, 0x1d, 0xd3, 0x30, 0xd9, - 0xdf, 0x07, 0xb6, 0x36, 0x16, 0xc2, 0xcc, 0x1f, - 0x1c, 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - }), // 000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250 - MerkleRoot: chainhash.Hash([32]byte{ // Make go vet happy. - 0x66, 0x57, 0xa9, 0x25, 0x2a, 0xac, 0xd5, 0xc0, - 0xb2, 0x94, 0x09, 0x96, 0xec, 0xff, 0x95, 0x22, - 0x28, 0xc3, 0x06, 0x7c, 0xc3, 0x8d, 0x48, 0x85, - 0xef, 0xb5, 0xa4, 0xac, 0x42, 0x47, 0xe9, 0xf3, - }), // f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766 - Timestamp: time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC - Bits: 0x1b04864c, // 453281356 - Nonce: 0x10572b0f, // 274148111 - }, - Transactions: []*wire.MsgTx{ - { - Version: 1, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash{}, - Index: 0xffffffff, - }, - SignatureScript: []byte{ - 0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02, - }, - Sequence: 0xffffffff, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 0x12a05f200, // 5000000000 - PkScript: []byte{ - 0x41, // OP_DATA_65 - 0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25, - 0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73, - 0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7, - 0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16, - 0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24, - 0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed, - 0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28, - 0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf, - 0x84, // 65-byte signature - 0xac, // OP_CHECKSIG - }, - }, - }, - LockTime: 0, - }, - { - Version: 1, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash([32]byte{ // Make go vet happy. - 0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60, - 0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac, - 0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07, - 0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87, - }), // 87a157f3fd88ac7907c05fc55e271dc4acdc5605d187d646604ca8c0e9382e03 - Index: 0, - }, - SignatureScript: []byte{ - 0x49, // OP_DATA_73 - 0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3, - 0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6, - 0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94, - 0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58, - 0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00, - 0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62, - 0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c, - 0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60, - 0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48, - 0x01, // 73-byte signature - 0x41, // OP_DATA_65 - 0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d, - 0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38, - 0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25, - 0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e, - 0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8, - 0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd, - 0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b, - 0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3, - 0xd3, // 65-byte pubkey - }, - Sequence: 0xffffffff, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 0x2123e300, // 556000000 - PkScript: []byte{ - 0x76, // OP_DUP - 0xa9, // OP_HASH160 - 0x14, // OP_DATA_20 - 0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60, - 0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e, - 0xf7, 0xf5, 0x8b, 0x32, - 0x88, // OP_EQUALVERIFY - 0xac, // OP_CHECKSIG - }, - }, - { - Value: 0x108e20f00, // 4444000000 - PkScript: []byte{ - 0x76, // OP_DUP - 0xa9, // OP_HASH160 - 0x14, // OP_DATA_20 - 0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f, - 0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b, - 0x52, 0xde, 0x3d, 0x7c, - 0x88, // OP_EQUALVERIFY - 0xac, // OP_CHECKSIG - }, - }, - }, - LockTime: 0, - }, - { - Version: 1, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash([32]byte{ // Make go vet happy. - 0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d, - 0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27, - 0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65, - 0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf, - }), // cf4e2978d0611ce46592e02d7e7daf8627a316ab69759a9f3df109a7f2bf3ec3 - Index: 1, - }, - SignatureScript: []byte{ - 0x47, // OP_DATA_71 - 0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf, - 0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5, - 0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34, - 0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31, - 0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee, - 0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f, - 0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c, - 0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e, - 0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01, - 0x41, // OP_DATA_65 - 0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78, - 0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5, - 0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39, - 0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21, - 0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee, - 0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3, - 0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95, - 0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85, - 0x0f, // 65-byte pubkey - }, - Sequence: 0xffffffff, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 0xf4240, // 1000000 - PkScript: []byte{ - 0x76, // OP_DUP - 0xa9, // OP_HASH160 - 0x14, // OP_DATA_20 - 0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04, - 0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d, - 0xad, 0xbe, 0x7e, 0x10, - 0x88, // OP_EQUALVERIFY - 0xac, // OP_CHECKSIG - }, - }, - { - Value: 0x11d260c0, // 299000000 - PkScript: []byte{ - 0x76, // OP_DUP - 0xa9, // OP_HASH160 - 0x14, // OP_DATA_20 - 0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1, - 0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab, - 0xb3, 0x40, 0x9c, 0xd9, - 0x88, // OP_EQUALVERIFY - 0xac, // OP_CHECKSIG - }, - }, - }, - LockTime: 0, - }, - { - Version: 1, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash([32]byte{ // Make go vet happy. - 0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73, - 0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac, - 0x3b, 0x24, 0x0c, 0x84, 0xb9, 0x17, 0xa3, 0x90, - 0x9b, 0xa1, 0xc4, 0x3d, 0xed, 0x5f, 0x51, 0xf4, - }), // f4515fed3dc4a19b90a317b9840c243bac26114cf637522373a7d486b372600b - Index: 0, - }, - SignatureScript: []byte{ - 0x49, // OP_DATA_73 - 0x30, 0x46, 0x02, 0x21, 0x00, 0xbb, 0x1a, 0xd2, - 0x6d, 0xf9, 0x30, 0xa5, 0x1c, 0xce, 0x11, 0x0c, - 0xf4, 0x4f, 0x7a, 0x48, 0xc3, 0xc5, 0x61, 0xfd, - 0x97, 0x75, 0x00, 0xb1, 0xae, 0x5d, 0x6b, 0x6f, - 0xd1, 0x3d, 0x0b, 0x3f, 0x4a, 0x02, 0x21, 0x00, - 0xc5, 0xb4, 0x29, 0x51, 0xac, 0xed, 0xff, 0x14, - 0xab, 0xba, 0x27, 0x36, 0xfd, 0x57, 0x4b, 0xdb, - 0x46, 0x5f, 0x3e, 0x6f, 0x8d, 0xa1, 0x2e, 0x2c, - 0x53, 0x03, 0x95, 0x4a, 0xca, 0x7f, 0x78, 0xf3, - 0x01, // 73-byte signature - 0x41, // OP_DATA_65 - 0x04, 0xa7, 0x13, 0x5b, 0xfe, 0x82, 0x4c, 0x97, - 0xec, 0xc0, 0x1e, 0xc7, 0xd7, 0xe3, 0x36, 0x18, - 0x5c, 0x81, 0xe2, 0xaa, 0x2c, 0x41, 0xab, 0x17, - 0x54, 0x07, 0xc0, 0x94, 0x84, 0xce, 0x96, 0x94, - 0xb4, 0x49, 0x53, 0xfc, 0xb7, 0x51, 0x20, 0x65, - 0x64, 0xa9, 0xc2, 0x4d, 0xd0, 0x94, 0xd4, 0x2f, - 0xdb, 0xfd, 0xd5, 0xaa, 0xd3, 0xe0, 0x63, 0xce, - 0x6a, 0xf4, 0xcf, 0xaa, 0xea, 0x4e, 0xa1, 0x4f, - 0xbb, // 65-byte pubkey - }, - Sequence: 0xffffffff, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 0xf4240, // 1000000 - PkScript: []byte{ - 0x76, // OP_DUP - 0xa9, // OP_HASH160 - 0x14, // OP_DATA_20 - 0x39, 0xaa, 0x3d, 0x56, 0x9e, 0x06, 0xa1, 0xd7, - 0x92, 0x6d, 0xc4, 0xbe, 0x11, 0x93, 0xc9, 0x9b, - 0xf2, 0xeb, 0x9e, 0xe0, - 0x88, // OP_EQUALVERIFY - 0xac, // OP_CHECKSIG - }, - }, - }, - LockTime: 0, - }, - }, -} - -// TestBlockFiltererOneInOneOut tests the correctness of the BlockFilterer in -// finding outpoints that spend from a "watched outpoint", even if they do not -// send to an address controlled by the wallet. -func TestBlockFiltererOneInOneOut(t *testing.T) { - // Watch for spend from prev in in first and last tx, both of which are - // single input/single output. - firstTx := Block100000.Transactions[1] - lastTx := Block100000.Transactions[3] - - // Add each of their single previous outpoints to the set of watched - // outpoints to filter for. - watchedOutPoints := make(map[wire.OutPoint]btcutil.Address) - watchedOutPoints[firstTx.TxIn[0].PreviousOutPoint] = &btcutil.AddressWitnessPubKeyHash{} - watchedOutPoints[lastTx.TxIn[0].PreviousOutPoint] = &btcutil.AddressWitnessPubKeyHash{} - - // Construct a filter request, watching only for the outpoints above, - // and construct a block filterer. - req := &btcclient.FilterBlocksRequest{ - WatchedOutPoints: watchedOutPoints, - } - blockFilterer := btcclient.NewBlockFilterer(&chaincfg.SimNetParams, req) - - // Filter block 100000, which should find matches for the watched - // outpoints. - match := blockFilterer.FilterBlock(&Block100000) - if !match { - t.Fatalf("failed to find matches when filtering for " + - "1-in-1-out txns") - } - - // We should find exactly two relevant transactions added to the block - // filterer, then we check that both the first and last txns are found - // in that list. - assertNumRelevantTxns(t, blockFilterer, 2) - assertRelevantTxnsContains(t, blockFilterer, firstTx) - assertRelevantTxnsContains(t, blockFilterer, lastTx) -} - -// assertNumRelevantTxns checks that the set of relevant txns found in a block -// filterer is of a specific size. -func assertNumRelevantTxns(t *testing.T, bf *btcclient.BlockFilterer, size int) { - count := len(bf.RelevantTxns) - if count != size { - t.Fatalf("unexpected number of relevant txns: "+ - "want %v, got %v", size, count) - } -} - -// assertRelevantTxnsContains checks that the wantTx is found in the block -// filterers set of relevant txns. -func assertRelevantTxnsContains(t *testing.T, bf *btcclient.BlockFilterer, wantTx *wire.MsgTx) { - for _, relevantTx := range bf.RelevantTxns { - if reflect.DeepEqual(relevantTx, wantTx) { - return - } - } - - t.Fatalf("unable to find tx: %x in %d relevant txns", wantTx.TxHash(), - len(bf.RelevantTxns)) -} diff --git a/btcclient/client.go b/btcclient/client.go index 8e633f79..b0fea10c 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -6,42 +6,18 @@ package btcclient import ( "errors" - "sync" - "time" "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/btcutil/gcs" - "github.com/btcsuite/btcd/btcutil/gcs/builder" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcwallet/waddrmgr" - "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/btcsuite/btcwallet/chain" ) -var _ Interface = &Client{} +var _ chain.Interface = &Client{} // Client represents a persistent client connection to a bitcoin RPC server // for information regarding the current best block chain. type Client struct { - *rpcclient.Client - connConfig *rpcclient.ConnConfig // Work around unexported field - chainParams *chaincfg.Params - reconnectAttempts int - - enqueueNotification chan interface{} - dequeueNotification chan interface{} - currentBlock chan *waddrmgr.BlockStamp - - quit chan struct{} - wg sync.WaitGroup - started bool - quitMtx sync.Mutex + *chain.RPCClient } // New creates a client connection to the server described by the @@ -58,39 +34,12 @@ func New(cfg *config.BTCConfig) (*Client, error) { certs := readCAFile(cfg) params := netparams.GetParams(cfg.NetParams) - client := &Client{ - connConfig: &rpcclient.ConnConfig{ - Host: cfg.Endpoint, - Endpoint: "ws", - User: cfg.Username, - Pass: cfg.Password, - Certificates: certs, - DisableAutoReconnect: false, - DisableConnectOnNew: true, - DisableTLS: cfg.DisableClientTLS, - }, - chainParams: params.Params, - reconnectAttempts: cfg.ReconnectAttempts, - enqueueNotification: make(chan interface{}), - dequeueNotification: make(chan interface{}), - currentBlock: make(chan *waddrmgr.BlockStamp), - quit: make(chan struct{}), - } - ntfnCallbacks := &rpcclient.NotificationHandlers{ - OnClientConnected: client.onClientConnect, - OnBlockConnected: client.onBlockConnected, - OnBlockDisconnected: client.onBlockDisconnected, - OnRecvTx: client.onRecvTx, - OnRedeemingTx: client.onRedeemingTx, - OnRescanFinished: client.onRescanFinished, - OnRescanProgress: client.onRescanProgress, - } - rpcClient, err := rpcclient.New(client.connConfig, ntfnCallbacks) + rpcClient, err := chain.NewRPCClient(params.Params, cfg.Endpoint, cfg.Username, cfg.Password, certs, cfg.DisableClientTLS, cfg.ReconnectAttempts) if err != nil { return nil, err } - client.Client = rpcClient - return client, nil + client := &Client{rpcClient} + return client, err } func (c *Client) ConnectLoop(cfg *config.BTCConfig) { @@ -103,422 +52,3 @@ func (c *Client) ConnectLoop(cfg *config.BTCConfig) { c.WaitForShutdown() }() } - -// BackEnd returns the name of the driver. -func (c *Client) BackEnd() string { - return "btcd" -} - -// Start attempts to establish a client connection with the remote server. -// If successful, handler goroutines are started to process notifications -// sent by the server. After a limited number of connection attempts, this -// function gives up, and therefore will not block forever waiting for the -// connection to be established to a server that may not exist. -func (c *Client) Start() error { - err := c.Connect(c.reconnectAttempts) - if err != nil { - return err - } - - // Verify that the server is running on the expected network. - net, err := c.GetCurrentNet() - if err != nil { - c.Disconnect() - return err - } - if net != c.chainParams.Net { - c.Disconnect() - return errors.New("mismatched networks") - } - - c.quitMtx.Lock() - c.started = true - c.quitMtx.Unlock() - - c.wg.Add(1) - go c.handler() - return nil -} - -// Stop disconnects the client and signals the shutdown of all goroutines -// started by Start. -func (c *Client) Stop() { - c.quitMtx.Lock() - select { - case <-c.quit: - default: - close(c.quit) - c.Client.Shutdown() - - if !c.started { - close(c.dequeueNotification) - } - } - c.quitMtx.Unlock() -} - -// IsCurrent returns whether the chain backend considers its view of the network -// as "current". -func (c *Client) IsCurrent() bool { - bestHash, _, err := c.GetBestBlock() - if err != nil { - return false - } - bestHeader, err := c.GetBlockHeader(bestHash) - if err != nil { - return false - } - return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta)) -} - -// Rescan wraps the normal Rescan command with an additional parameter that -// allows us to map an outpoint to the address in the chain that it pays to. -// This is useful when using BIP 158 filters as they include the prev pkScript -// rather than the full outpoint. -func (c *Client) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address, - outPoints map[wire.OutPoint]btcutil.Address) error { - - flatOutpoints := make([]*wire.OutPoint, 0, len(outPoints)) - for ops := range outPoints { - ops := ops - - flatOutpoints = append(flatOutpoints, &ops) - } - - return c.Client.Rescan(startHash, addrs, flatOutpoints) // nolint:staticcheck -} - -// WaitForShutdown blocks until both the client has finished disconnecting -// and all handlers have exited. -func (c *Client) WaitForShutdown() { - c.Client.WaitForShutdown() - c.wg.Wait() -} - -// Notifications returns a channel of parsed notifications sent by the remote -// bitcoin RPC server. This channel must be continually read or the process -// may abort for running out memory, as unread notifications are queued for -// later reads. -func (c *Client) Notifications() <-chan interface{} { - return c.dequeueNotification -} - -// BlockStamp returns the latest block notified by the client, or an error -// if the client has been shut down. -func (c *Client) BlockStamp() (*waddrmgr.BlockStamp, error) { - select { - case bs := <-c.currentBlock: - return bs, nil - case <-c.quit: - return nil, errors.New("disconnected") - } -} - -// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any -// addresses of interest. For each requested block, the corresponding compact -// filter will first be checked for matches, skipping those that do not report -// anything. If the filter returns a positive match, the full block will be -// fetched and filtered. This method returns a FilterBlocksResponse for the first -// block containing a matching address. If no matches are found in the range of -// blocks requested, the returned response will be nil. -func (c *Client) FilterBlocks( - req *FilterBlocksRequest) (*FilterBlocksResponse, error) { - - blockFilterer := NewBlockFilterer(c.chainParams, req) - - // Construct the watchlist using the addresses and outpoints contained - // in the filter blocks request. - watchList, err := buildFilterBlocksWatchList(req) - if err != nil { - return nil, err - } - - // Iterate over the requested blocks, fetching the compact filter for - // each one, and matching it against the watchlist generated above. If - // the filter returns a positive match, the full block is then requested - // and scanned for addresses using the block filterer. - for i, blk := range req.Blocks { - rawFilter, err := c.GetCFilter(&blk.Hash, wire.GCSFilterRegular) - if err != nil { - return nil, err - } - - // Ensure the filter is large enough to be deserialized. - if len(rawFilter.Data) < 4 { - continue - } - - filter, err := gcs.FromNBytes( - builder.DefaultP, builder.DefaultM, rawFilter.Data, - ) - if err != nil { - return nil, err - } - - // Skip any empty filters. - if filter.N() == 0 { - continue - } - - key := builder.DeriveKey(&blk.Hash) - matched, err := filter.MatchAny(key, watchList) - if err != nil { - return nil, err - } else if !matched { - continue - } - - log.Infof("Fetching block height=%d hash=%v", - blk.Height, blk.Hash) - - rawBlock, err := c.GetBlock(&blk.Hash) - if err != nil { - return nil, err - } - - if !blockFilterer.FilterBlock(rawBlock) { - continue - } - - // If any external or internal addresses were detected in this - // block, we return them to the caller so that the rescan - // windows can widened with subsequent addresses. The - // `BatchIndex` is returned so that the caller can compute the - // *next* block from which to begin again. - resp := &FilterBlocksResponse{ - BatchIndex: uint32(i), - BlockMeta: blk, - FoundExternalAddrs: blockFilterer.FoundExternal, - FoundInternalAddrs: blockFilterer.FoundInternal, - FoundOutPoints: blockFilterer.FoundOutPoints, - RelevantTxns: blockFilterer.RelevantTxns, - } - - return resp, nil - } - - // No addresses were found for this range. - return nil, nil -} - -// parseBlock parses a btcws definition of the block a tx is mined it to the -// Block structure of the wtxmgr package, and the block index. This is done -// here since rpcclient doesn't parse this nicely for us. -func parseBlock(block *btcjson.BlockDetails) (*wtxmgr.BlockMeta, error) { - if block == nil { - return nil, nil - } - blkHash, err := chainhash.NewHashFromStr(block.Hash) - if err != nil { - return nil, err - } - blk := &wtxmgr.BlockMeta{ - Block: wtxmgr.Block{ - Height: block.Height, - Hash: *blkHash, - }, - Time: time.Unix(block.Time, 0), - } - return blk, nil -} - -func (c *Client) onClientConnect() { - select { - case c.enqueueNotification <- ClientConnected{}: - case <-c.quit: - } -} - -func (c *Client) onBlockConnected(hash *chainhash.Hash, height int32, time time.Time) { - select { - case c.enqueueNotification <- BlockConnected{ - Block: wtxmgr.Block{ - Hash: *hash, - Height: height, - }, - Time: time, - }: - case <-c.quit: - } -} - -func (c *Client) onBlockDisconnected(hash *chainhash.Hash, height int32, time time.Time) { - select { - case c.enqueueNotification <- BlockDisconnected{ - Block: wtxmgr.Block{ - Hash: *hash, - Height: height, - }, - Time: time, - }: - case <-c.quit: - } -} - -func (c *Client) onRecvTx(tx *btcutil.Tx, block *btcjson.BlockDetails) { - blk, err := parseBlock(block) - if err != nil { - // Log and drop improper notification. - log.Errorf("recvtx notification bad block: %v", err) - return - } - - rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(), time.Now()) - if err != nil { - log.Errorf("Cannot create transaction record for relevant "+ - "tx: %v", err) - return - } - select { - case c.enqueueNotification <- RelevantTx{rec, blk}: - case <-c.quit: - } -} - -func (c *Client) onRedeemingTx(tx *btcutil.Tx, block *btcjson.BlockDetails) { - // Handled exactly like recvtx notifications. - c.onRecvTx(tx, block) -} - -func (c *Client) onRescanProgress(hash *chainhash.Hash, height int32, blkTime time.Time) { - select { - case c.enqueueNotification <- &RescanProgress{hash, height, blkTime}: - case <-c.quit: - } -} - -func (c *Client) onRescanFinished(hash *chainhash.Hash, height int32, blkTime time.Time) { - select { - case c.enqueueNotification <- &RescanFinished{hash, height, blkTime}: - case <-c.quit: - } - -} - -// handler maintains a queue of notifications and the current state (best -// block) of the chain. -func (c *Client) handler() { - hash, height, err := c.GetBestBlock() - if err != nil { - log.Errorf("Failed to receive best block from chain server: %v", err) - c.Stop() - c.wg.Done() - return - } - - bs := &waddrmgr.BlockStamp{Hash: *hash, Height: height} - - // TODO: Rather than leaving this as an unbounded queue for all types of - // notifications, try dropping ones where a later enqueued notification - // can fully invalidate one waiting to be processed. For example, - // blockconnected notifications for greater block heights can remove the - // need to process earlier blockconnected notifications still waiting - // here. - - var notifications []interface{} - enqueue := c.enqueueNotification - var dequeue chan interface{} - var next interface{} -out: - for { - select { - case n, ok := <-enqueue: - if !ok { - // If no notifications are queued for handling, - // the queue is finished. - if len(notifications) == 0 { - break out - } - // nil channel so no more reads can occur. - enqueue = nil - continue - } - if len(notifications) == 0 { - next = n - dequeue = c.dequeueNotification - } - notifications = append(notifications, n) - - case dequeue <- next: - if n, ok := next.(BlockConnected); ok { - bs = &waddrmgr.BlockStamp{ - Height: n.Height, - Hash: n.Hash, - } - } - - notifications[0] = nil - notifications = notifications[1:] - if len(notifications) != 0 { - next = notifications[0] - } else { - // If no more notifications can be enqueued, the - // queue is finished. - if enqueue == nil { - break out - } - dequeue = nil - } - - case c.currentBlock <- bs: - - case <-c.quit: - break out - } - } - - c.Stop() - close(c.dequeueNotification) - c.wg.Done() -} - -// POSTClient creates the equivalent HTTP POST rpcclient.Client. -func (c *Client) POSTClient() (*rpcclient.Client, error) { - configCopy := *c.connConfig - configCopy.HTTPPostMode = true - return rpcclient.New(&configCopy, nil) -} - -// buildFilterBlocksWatchList constructs a watchlist used for matching against a -// cfilter from a FilterBlocksRequest. The watchlist will be populated with all -// external addresses, internal addresses, and outpoints contained in the -// request. -func buildFilterBlocksWatchList(req *FilterBlocksRequest) ([][]byte, error) { - // Construct a watch list containing the script addresses of all - // internal and external addresses that were requested, in addition to - // the set of outpoints currently being watched. - watchListSize := len(req.ExternalAddrs) + - len(req.InternalAddrs) + - len(req.WatchedOutPoints) - - watchList := make([][]byte, 0, watchListSize) - - for _, addr := range req.ExternalAddrs { - p2shAddr, err := txscript.PayToAddrScript(addr) - if err != nil { - return nil, err - } - - watchList = append(watchList, p2shAddr) - } - - for _, addr := range req.InternalAddrs { - p2shAddr, err := txscript.PayToAddrScript(addr) - if err != nil { - return nil, err - } - - watchList = append(watchList, p2shAddr) - } - - for _, addr := range req.WatchedOutPoints { - addr, err := txscript.PayToAddrScript(addr) - if err != nil { - return nil, err - } - - watchList = append(watchList, addr) - } - - return watchList, nil -} diff --git a/btcclient/interface.go b/btcclient/interface.go deleted file mode 100644 index bc530dd9..00000000 --- a/btcclient/interface.go +++ /dev/null @@ -1,124 +0,0 @@ -package btcclient - -import ( - "time" - - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcwallet/waddrmgr" - "github.com/btcsuite/btcwallet/wtxmgr" -) - -// isCurrentDelta is the delta duration we'll use from the present time to -// determine if a backend is considered "current", i.e. synced to the tip of -// the chain. -const isCurrentDelta = 2 * time.Hour - -// BackEnds returns a list of the available back ends. -// TODO: Refactor each into a driver and use dynamic registration. -func BackEnds() []string { - return []string{ - "bitcoind", - "btcd", - "bitcoind-rpc-polling", - } -} - -// Interface allows more than one backing blockchain source, such as a -// btcd RPC chain server, or an SPV library, as long as we write a driver for -// it. -type Interface interface { - Start() error - Stop() - WaitForShutdown() - GetBestBlock() (*chainhash.Hash, int32, error) - GetBlock(*chainhash.Hash) (*wire.MsgBlock, error) - GetBlockHash(int64) (*chainhash.Hash, error) - GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error) - IsCurrent() bool - FilterBlocks(*FilterBlocksRequest) (*FilterBlocksResponse, error) - BlockStamp() (*waddrmgr.BlockStamp, error) - SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash, error) - Rescan(*chainhash.Hash, []btcutil.Address, map[wire.OutPoint]btcutil.Address) error - NotifyReceived([]btcutil.Address) error - NotifyBlocks() error - Notifications() <-chan interface{} - BackEnd() string -} - -// Notification types. These are defined here and processed from from reading -// a notificationChan to avoid handling these notifications directly in -// rpcclient callbacks, which isn't very Go-like and doesn't allow -// blocking client calls. -type ( - // ClientConnected is a notification for when a client connection is - // opened or reestablished to the chain server. - ClientConnected struct{} - - // BlockConnected is a notification for a newly-attached block to the - // best chain. - BlockConnected wtxmgr.BlockMeta - - // FilteredBlockConnected is an alternate notification that contains - // both block and relevant transaction information in one struct, which - // allows atomic updates. - FilteredBlockConnected struct { - Block *wtxmgr.BlockMeta - RelevantTxs []*wtxmgr.TxRecord - } - - // FilterBlocksRequest specifies a range of blocks and the set of - // internal and external addresses of interest, indexed by corresponding - // scoped-index of the child address. A global set of watched outpoints - // is also included to monitor for spends. - FilterBlocksRequest struct { - Blocks []wtxmgr.BlockMeta - ExternalAddrs map[waddrmgr.ScopedIndex]btcutil.Address - InternalAddrs map[waddrmgr.ScopedIndex]btcutil.Address - WatchedOutPoints map[wire.OutPoint]btcutil.Address - } - - // FilterBlocksResponse reports the set of all internal and external - // addresses found in response to a FilterBlockRequest, any outpoints - // found that correspond to those addresses, as well as the relevant - // transactions that can modify the wallet's balance. The index of the - // block within the FilterBlocksRequest is returned, such that the - // caller can reinitiate a request for the subsequent block after - // updating the addresses of interest. - FilterBlocksResponse struct { - BatchIndex uint32 - BlockMeta wtxmgr.BlockMeta - FoundExternalAddrs map[waddrmgr.KeyScope]map[uint32]struct{} - FoundInternalAddrs map[waddrmgr.KeyScope]map[uint32]struct{} - FoundOutPoints map[wire.OutPoint]btcutil.Address - RelevantTxns []*wire.MsgTx - } - - // BlockDisconnected is a notifcation that the block described by the - // BlockStamp was reorganized out of the best chain. - BlockDisconnected wtxmgr.BlockMeta - - // RelevantTx is a notification for a transaction which spends wallet - // inputs or pays to a watched address. - RelevantTx struct { - TxRecord *wtxmgr.TxRecord - Block *wtxmgr.BlockMeta // nil if unmined - } - - // RescanProgress is a notification describing the current status - // of an in-progress rescan. - RescanProgress struct { - Hash *chainhash.Hash - Height int32 - Time time.Time - } - - // RescanFinished is a notification that a previous rescan request - // has finished. - RescanFinished struct { - Hash *chainhash.Hash - Height int32 - Time time.Time - } -) diff --git a/btcclient/pruned_block_dispatcher.go b/btcclient/pruned_block_dispatcher.go deleted file mode 100644 index 8a1e7114..00000000 --- a/btcclient/pruned_block_dispatcher.go +++ /dev/null @@ -1,666 +0,0 @@ -package btcclient - -import ( - "encoding/binary" - "encoding/hex" - "errors" - "fmt" - "math/rand" - "net" - "sync" - "time" - - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/peer" - "github.com/btcsuite/btcd/wire" - "github.com/lightninglabs/neutrino/query" - "github.com/lightningnetwork/lnd/ticker" -) - -const ( - // defaultRefreshPeersInterval represents the default polling interval - // at which we attempt to refresh the set of known peers. - defaultRefreshPeersInterval = 30 * time.Second - - // defaultPeerReadyTimeout is the default amount of time we'll wait for - // a query peer to be ready to receive incoming block requests. Peers - // cannot respond to requests until the version exchange is completed - // upon connection establishment. - defaultPeerReadyTimeout = 15 * time.Second - - // requiredServices are the requires services we require any candidate - // peers to signal such that we can retrieve pruned blocks from them. - requiredServices = wire.SFNodeNetwork | wire.SFNodeWitness - - // prunedNodeService is the service bit signaled by pruned nodes on the - // network. Note that this service bit can also be signaled by full - // nodes, except that they also signal wire.SFNodeNetwork, where as - // pruned nodes don't. - prunedNodeService wire.ServiceFlag = 1 << 10 -) - -// queryPeer represents a Bitcoin network peer that we'll query for blocks. -// The ready channel serves as a signal for us to know when we can be sending -// queries to the peer. Any messages received from the peer are sent through the -// msgsRecvd channel. -type queryPeer struct { - *peer.Peer - ready chan struct{} - msgsRecvd chan wire.Message - quit chan struct{} -} - -// signalUponDisconnect closes the peer's quit chan to signal it has -// disconnected. -func (p *queryPeer) signalUponDisconnect(f func()) { - go func() { - p.WaitForDisconnect() - close(p.quit) - f() - }() -} - -// SubscribeRecvMsg adds a OnRead subscription to the peer. All bitcoin messages -// received from this peer will be sent on the returned channel. A closure is -// also returned, that should be called to cancel the subscription. -// -// NOTE: This method exists to satisfy the query.Peer interface. -func (p *queryPeer) SubscribeRecvMsg() (<-chan wire.Message, func()) { - return p.msgsRecvd, func() {} -} - -// OnDisconnect returns a channel that will be closed once the peer disconnects. -// -// NOTE: This method exists to satisfy the query.Peer interface. -func (p *queryPeer) OnDisconnect() <-chan struct{} { - return p.quit -} - -// PrunedBlockDispatcherConfig encompasses all of the dependencies required by -// the PrunedBlockDispatcher to carry out its duties. -type PrunedBlockDispatcherConfig struct { - // ChainParams represents the parameters of the current active chain. - ChainParams *chaincfg.Params - - // NumTargetPeer represents the target number of peers we should - // maintain connections with. This exists to prevent establishing - // connections to all of the bitcoind's peers, which would be - // unnecessary and ineffecient. - NumTargetPeers int - - // Dial establishes connections to Bitcoin peers. This must support - // dialing peers running over Tor if the backend also supports it. - Dial func(string) (net.Conn, error) - - // GetPeers retrieves the active set of peers known to the backend node. - GetPeers func() ([]btcjson.GetPeerInfoResult, error) - - // GetNodeAddresses returns random reachable addresses known to the - // backend node. An optional number of addresses to return can be - // provided, otherwise 8 are returned by default. - GetNodeAddresses func(*int32) ([]btcjson.GetNodeAddressesResult, error) - - // PeerReadyTimeout is the amount of time we'll wait for a query peer to - // be ready to receive incoming block requests. Peers cannot respond to - // requests until the version exchange is completed upon connection - // establishment. - PeerReadyTimeout time.Duration - - // RefreshPeersTicker is the polling ticker that signals us when we - // should attempt to refresh the set of known peers. - RefreshPeersTicker ticker.Ticker - - // AllowSelfPeerConns is only used to allow the tests to bypass the peer - // self connection detecting and disconnect logic since they - // intentionally do so for testing purposes. - AllowSelfPeerConns bool - - // MaxRequestInvs dictates how many invs we should fit in a single - // getdata request to a peer. This only exists to facilitate the testing - // of a request spanning multiple getdata messages. - MaxRequestInvs int -} - -// PrunedBlockDispatcher enables a chain client to request blocks that the -// server has already pruned. This is done by connecting to the server's full -// node peers and querying them directly. Ideally, this is a capability -// supported by the server, though this is not yet possible with bitcoind. -type PrunedBlockDispatcher struct { - cfg PrunedBlockDispatcherConfig - - // workManager handles satisfying all of our incoming pruned block - // requests. - workManager *query.WorkManager - - // blocksQueried represents the set of pruned blocks we've been - // requested to query. Each block maps to a list of clients waiting to - // be notified once the block is received. - // - // NOTE: The blockMtx lock must always be held when accessing this - // field. - blocksQueried map[chainhash.Hash][]chan *wire.MsgBlock - blockMtx sync.Mutex - - // currentPeers represents the set of peers we're currently connected - // to. Each peer found here will have a worker spawned within the - // workManager to handle our queries. - // - // NOTE: The peerMtx lock must always be held when accessing this - // field. - currentPeers map[string]*peer.Peer - - // bannedPeers represents the set of peers who have sent us an invalid - // reply corresponding to a query. Peers within this set should not be - // dialed. - // - // NOTE: The peerMtx lock must always be held when accessing this - // field. - bannedPeers map[string]struct{} - peerMtx sync.Mutex - - // peersConnected is the channel through which we'll send new peers - // we've established connections to. - peersConnected chan query.Peer - - // timeSource provides a mechanism to add several time samples which are - // used to determine a median time which is then used as an offset to - // the local clock when validating blocks received from peers. - timeSource blockchain.MedianTimeSource - - quit chan struct{} - wg sync.WaitGroup -} - -// NewPrunedBlockDispatcher initializes a new PrunedBlockDispatcher instance -// backed by the given config. -func NewPrunedBlockDispatcher(cfg *PrunedBlockDispatcherConfig) ( - *PrunedBlockDispatcher, error) { - - if cfg.NumTargetPeers < 1 { - return nil, errors.New("config option NumTargetPeer must be >= 1") - } - if cfg.MaxRequestInvs > wire.MaxInvPerMsg { - return nil, fmt.Errorf("config option MaxRequestInvs must be "+ - "<= %v", wire.MaxInvPerMsg) - } - - peersConnected := make(chan query.Peer) - return &PrunedBlockDispatcher{ - cfg: *cfg, - workManager: query.New(&query.Config{ - ConnectedPeers: func() (<-chan query.Peer, func(), error) { - return peersConnected, func() {}, nil - }, - NewWorker: query.NewWorker, - Ranking: query.NewPeerRanking(), - }), - blocksQueried: make(map[chainhash.Hash][]chan *wire.MsgBlock), - currentPeers: make(map[string]*peer.Peer), - bannedPeers: make(map[string]struct{}), - peersConnected: peersConnected, - timeSource: blockchain.NewMedianTime(), - quit: make(chan struct{}), - }, nil -} - -// Start allows the PrunedBlockDispatcher to begin handling incoming block -// requests. -func (d *PrunedBlockDispatcher) Start() error { - log.Tracef("Starting pruned block dispatcher") - - if err := d.workManager.Start(); err != nil { - return err - } - - d.wg.Add(1) - go d.pollPeers() - - return nil -} - -// Stop stops the PrunedBlockDispatcher from accepting any more incoming block -// requests. -func (d *PrunedBlockDispatcher) Stop() { - log.Tracef("Stopping pruned block dispatcher") - - close(d.quit) - d.wg.Wait() - - _ = d.workManager.Stop() -} - -// pollPeers continuously polls the backend node for new peers to establish -// connections to. -func (d *PrunedBlockDispatcher) pollPeers() { - defer d.wg.Done() - - if err := d.connectToPeers(); err != nil { - log.Warnf("Unable to establish peer connections: %v", err) - } - - d.cfg.RefreshPeersTicker.Resume() - defer d.cfg.RefreshPeersTicker.Stop() - - for { - select { - case <-d.cfg.RefreshPeersTicker.Ticks(): - // Quickly determine if we need any more peer - // connections. If we don't, we'll wait for our next - // tick. - d.peerMtx.Lock() - peersNeeded := d.cfg.NumTargetPeers - len(d.currentPeers) - d.peerMtx.Unlock() - if peersNeeded <= 0 { - continue - } - - // If we do, attempt to establish connections until - // we've reached our target number. - if err := d.connectToPeers(); err != nil { - log.Warnf("Failed to establish peer "+ - "connections: %v", err) - continue - } - - case <-d.quit: - return - } - } -} - -// connectToPeers attempts to establish new peer connections until the target -// number is reached. Once a connection is successfully established, the peer is -// sent through the peersConnected channel to notify the internal workManager. -func (d *PrunedBlockDispatcher) connectToPeers() error { - // Refresh the list of peers our backend is currently connected to, and - // filter out any that do not meet our requirements. - peers, err := d.cfg.GetPeers() - if err != nil { - return err - } - addrs, err := filterPeers(peers) - if err != nil { - return err - } - rand.Shuffle(len(addrs), func(i, j int) { - addrs[i], addrs[j] = addrs[j], addrs[i] - }) - - for _, addr := range addrs { - needMore, err := d.connectToPeer(addr) - if err != nil { - log.Debugf("Failed connecting to peer %v: %v", addr, err) - continue - } - if !needMore { - return nil - } - } - - // We still need more addresses so we'll also invoke the - // `getnodeaddresses` RPC to receive random reachable addresses. We'll - // also filter out any that do not meet our requirements. The nil - // argument will return a default number of addresses, which is - // currently 8. We don't care how many addresses are returned as long as - // 1 is returned, since this will be polled regularly if needed. - nodeAddrs, err := d.cfg.GetNodeAddresses(nil) - if err != nil { - return err - } - addrs = filterNodeAddrs(nodeAddrs) - for _, addr := range addrs { - if _, err := d.connectToPeer(addr); err != nil { - log.Debugf("Failed connecting to peer %v: %v", addr, err) - } - } - - return nil -} - -// connectToPeer attempts to establish a connection to the given peer and waits -// up to PeerReadyTimeout for the version exchange to complete so that we can -// begin sending it our queries. -func (d *PrunedBlockDispatcher) connectToPeer(addr string) (bool, error) { - // Prevent connections to peers we've already connected to or we've - // banned. - d.peerMtx.Lock() - _, isBanned := d.bannedPeers[addr] - _, isConnected := d.currentPeers[addr] - d.peerMtx.Unlock() - if isBanned || isConnected { - return true, nil - } - - peer, err := d.newQueryPeer(addr) - if err != nil { - return true, fmt.Errorf("unable to configure query peer %v: "+ - "%v", addr, err) - } - - // Establish the connection and wait for the protocol negotiation to - // complete. - conn, err := d.cfg.Dial(addr) - if err != nil { - return true, err - } - peer.AssociateConnection(conn) - - select { - case <-peer.ready: - case <-time.After(d.cfg.PeerReadyTimeout): - peer.Disconnect() - return true, errors.New("timed out waiting for protocol negotiation") - case <-d.quit: - return false, errors.New("shutting down") - } - - // Remove the peer once it has disconnected. - peer.signalUponDisconnect(func() { - d.peerMtx.Lock() - delete(d.currentPeers, peer.Addr()) - d.peerMtx.Unlock() - }) - - d.peerMtx.Lock() - d.currentPeers[addr] = peer.Peer - numPeers := len(d.currentPeers) - d.peerMtx.Unlock() - - // Notify the new peer connection to our workManager. - select { - case d.peersConnected <- peer: - case <-d.quit: - return false, errors.New("shutting down") - } - - // Request more peer connections if we haven't reached our target number - // with the new peer. - return numPeers < d.cfg.NumTargetPeers, nil -} - -// filterPeers filters out any peers which cannot handle arbitrary witness block -// requests, i.e., any peer which is not considered a segwit-enabled -// "full-node". -func filterPeers(peers []btcjson.GetPeerInfoResult) ([]string, error) { - var eligible []string // nolint:prealloc - for _, peer := range peers { - rawServices, err := hex.DecodeString(peer.Services) - if err != nil { - return nil, err - } - services := wire.ServiceFlag(binary.BigEndian.Uint64(rawServices)) - if !satisfiesRequiredServices(services) { - continue - } - eligible = append(eligible, peer.Addr) - } - return eligible, nil -} - -// filterNodeAddrs filters out any peers which cannot handle arbitrary witness -// block requests, i.e., any peer which is not considered a segwit-enabled -// "full-node". -func filterNodeAddrs(nodeAddrs []btcjson.GetNodeAddressesResult) []string { - var eligible []string // nolint:prealloc - for _, nodeAddr := range nodeAddrs { - services := wire.ServiceFlag(nodeAddr.Services) - if !satisfiesRequiredServices(services) { - continue - } - eligible = append(eligible, nodeAddr.Address) - } - return eligible -} - -// satisfiesRequiredServices determines whether the services signaled by a peer -// satisfy our requirements for retrieving pruned blocks from them. We need the -// full chain, and witness data as well. Note that we ignore the limited -// (pruned bit) as nodes can have the full data and set that as well. Pure -// pruned nodes won't set the network bit. -func satisfiesRequiredServices(services wire.ServiceFlag) bool { - return services&requiredServices == requiredServices -} - -// newQueryPeer creates a new peer instance configured to relay any received -// messages to the internal workManager. -func (d *PrunedBlockDispatcher) newQueryPeer(addr string) (*queryPeer, error) { - ready := make(chan struct{}) - msgsRecvd := make(chan wire.Message) - - cfg := &peer.Config{ - ChainParams: d.cfg.ChainParams, - // We're not interested in transactions, so disable their relay. - DisableRelayTx: true, - Listeners: peer.MessageListeners{ - // Add the remote peer time as a sample for creating an - // offset against the local clock to keep the network - // time in sync. - OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) *wire.MsgReject { - d.timeSource.AddTimeSample(p.Addr(), msg.Timestamp) - return nil - }, - // Register a callback to signal us when we can start - // querying the peer for blocks. - OnVerAck: func(*peer.Peer, *wire.MsgVerAck) { - close(ready) - }, - // Register a callback to signal us whenever the peer - // has sent us a block message. - OnRead: func(p *peer.Peer, _ int, msg wire.Message, err error) { - if err != nil { - return - } - - var block *wire.MsgBlock - switch msg := msg.(type) { - case *wire.MsgBlock: - block = msg - case *wire.MsgVersion, *wire.MsgVerAck, - *wire.MsgPing, *wire.MsgPong: - return - default: - log.Debugf("Received unexpected message "+ - "%T from peer %v", msg, p.Addr()) - return - } - - select { - case msgsRecvd <- block: - case <-d.quit: - } - }, - }, - AllowSelfConns: true, - } - p, err := peer.NewOutboundPeer(cfg, addr) - if err != nil { - return nil, err - } - - return &queryPeer{ - Peer: p, - ready: ready, - msgsRecvd: msgsRecvd, - quit: make(chan struct{}), - }, nil -} - -// banPeer bans a peer by disconnecting them and ensuring we don't reconnect. -func (d *PrunedBlockDispatcher) banPeer(peer string) { - d.peerMtx.Lock() - defer d.peerMtx.Unlock() - - d.bannedPeers[peer] = struct{}{} - if p, ok := d.currentPeers[peer]; ok { - p.Disconnect() - } -} - -// Query submits a request to query the information of the given blocks. -func (d *PrunedBlockDispatcher) Query(blocks []*chainhash.Hash, - opts ...query.QueryOption) (<-chan *wire.MsgBlock, <-chan error) { - - reqs, blockChan, err := d.newRequest(blocks) - if err != nil { - errChan := make(chan error, 1) - errChan <- err - return nil, errChan - } - - var errChan chan error - if len(reqs) > 0 { - errChan = d.workManager.Query(reqs, opts...) - } - return blockChan, errChan -} - -// newRequest construct a new query request for the given blocks to submit to -// the internal workManager. A channel is also returned through which the -// requested blocks are sent through. -func (d *PrunedBlockDispatcher) newRequest(blocks []*chainhash.Hash) ( - []*query.Request, <-chan *wire.MsgBlock, error) { - - // Make sure the channel is buffered enough to handle all blocks. - blockChan := make(chan *wire.MsgBlock, len(blocks)) - - d.blockMtx.Lock() - defer d.blockMtx.Unlock() - - // Each GetData message can only include up to MaxRequestInvs invs, - // and each block consumes a single inv. - var ( - reqs []*query.Request - getData *wire.MsgGetData - ) - for i, block := range blocks { - if getData == nil { - getData = wire.NewMsgGetData() - } - - if _, ok := d.blocksQueried[*block]; !ok { - log.Debugf("Queuing new block %v for request", *block) - inv := wire.NewInvVect(wire.InvTypeBlock, block) - if err := getData.AddInvVect(inv); err != nil { - return nil, nil, err - } - } else { - log.Debugf("Received new request for pending query of "+ - "block %v", *block) - } - - d.blocksQueried[*block] = append( - d.blocksQueried[*block], blockChan, - ) - - // If we have any invs to request, or we've reached the maximum - // allowed, queue the getdata message as is, and proceed to the - // next if any. - if (len(getData.InvList) > 0 && i == len(blocks)-1) || - len(getData.InvList) == d.cfg.MaxRequestInvs { - - reqs = append(reqs, &query.Request{ - Req: getData, - HandleResp: d.handleResp, - }) - getData = nil - } - } - - return reqs, blockChan, nil -} - -// handleResp is a response handler that will be called for every message -// received from the peer that the request was made to. It should validate the -// response against the request made, and return a Progress indicating whether -// the request was answered by this particular response. -// -// NOTE: Since the worker's job queue will be stalled while this method is -// running, it should not be doing any expensive operations. It should validate -// the response and immediately return the progress. The response should be -// handed off to another goroutine for processing. -func (d *PrunedBlockDispatcher) handleResp(req, resp wire.Message, - peer string) query.Progress { - - // We only expect MsgBlock as replies. - block, ok := resp.(*wire.MsgBlock) - if !ok { - return query.Progress{ - Progressed: false, - Finished: false, - } - } - - // We only serve MsgGetData requests. - getData, ok := req.(*wire.MsgGetData) - if !ok { - return query.Progress{ - Progressed: false, - Finished: false, - } - } - - // Check that we've actually queried for this block and validate it. - blockHash := block.BlockHash() - d.blockMtx.Lock() - blockChans, ok := d.blocksQueried[blockHash] - if !ok { - d.blockMtx.Unlock() - return query.Progress{ - Progressed: false, - Finished: false, - } - } - - err := blockchain.CheckBlockSanity( - btcutil.NewBlock(block), d.cfg.ChainParams.PowLimit, - d.timeSource, - ) - if err != nil { - d.blockMtx.Unlock() - - log.Warnf("Received invalid block %v from peer %v: %v", - blockHash, peer, err) - d.banPeer(peer) - - return query.Progress{ - Progressed: false, - Finished: false, - } - } - - // Once validated, we can safely remove it. - delete(d.blocksQueried, blockHash) - - // Check whether we have any other pending blocks we've yet to receive. - // If we do, we'll mark the response as progressing our query, but not - // completing it yet. - progress := query.Progress{Progressed: true, Finished: true} - for _, inv := range getData.InvList { - if _, ok := d.blocksQueried[inv.Hash]; ok { - progress.Finished = false - break - } - } - d.blockMtx.Unlock() - - // Launch a goroutine to notify all clients of the block as we don't - // want to potentially block our workManager. - d.wg.Add(1) - go func() { - defer d.wg.Done() - - for _, blockChan := range blockChans { - select { - case blockChan <- block: - case <-d.quit: - return - } - } - }() - - return progress -} diff --git a/btcclient/pruned_block_dispatcher_test.go b/btcclient/pruned_block_dispatcher_test.go deleted file mode 100644 index 0b5104af..00000000 --- a/btcclient/pruned_block_dispatcher_test.go +++ /dev/null @@ -1,659 +0,0 @@ -package btcclient - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - "net" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/peer" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/ticker" - "github.com/stretchr/testify/require" -) - -var ( - addrCounter int32 // Increased atomically. - - chainParams = chaincfg.RegressionNetParams -) - -func nextAddr() string { - port := atomic.AddInt32(&addrCounter, 1) - return fmt.Sprintf("10.0.0.1:%d", port) -} - -// prunedBlockDispatcherHarness is a harness used to facilitate the testing of the -// PrunedBlockDispatcher. -type prunedBlockDispatcherHarness struct { - t *testing.T - - dispatcher *PrunedBlockDispatcher - - hashes []*chainhash.Hash - blocks map[chainhash.Hash]*wire.MsgBlock - - peerMtx sync.Mutex - peers map[string]*peer.Peer - fallbackAddrs map[string]*peer.Peer - localConns map[string]net.Conn // Connections to peers. - remoteConns map[string]net.Conn // Connections from peers. - - dialedPeer chan string - queriedPeer chan struct{} - blocksQueried map[chainhash.Hash]int - - shouldReply uint32 // 0 == true, 1 == false, 2 == invalid reply -} - -// newNetworkBlockTestHarness initializes a new PrunedBlockDispatcher test harness -// backed by a custom chain and peers. -func newNetworkBlockTestHarness(t *testing.T, numBlocks, - numPeers, numWorkers uint32) *prunedBlockDispatcherHarness { - - h := &prunedBlockDispatcherHarness{ - t: t, - dispatcher: &PrunedBlockDispatcher{}, - peers: make(map[string]*peer.Peer, numPeers), - fallbackAddrs: make(map[string]*peer.Peer, numPeers), - localConns: make(map[string]net.Conn, numPeers), - remoteConns: make(map[string]net.Conn, numPeers), - dialedPeer: make(chan string), - queriedPeer: make(chan struct{}), - blocksQueried: make(map[chainhash.Hash]int), - shouldReply: 0, - } - - h.hashes, h.blocks = genBlockChain(numBlocks) - for i := uint32(0); i < numPeers; i++ { - h.addPeer(false) - } - - dial := func(addr string) (net.Conn, error) { - go func() { - h.dialedPeer <- addr - }() - - h.peerMtx.Lock() - defer h.peerMtx.Unlock() - - localConn, ok := h.localConns[addr] - if !ok { - return nil, fmt.Errorf("local conn %v not found", addr) - } - remoteConn, ok := h.remoteConns[addr] - if !ok { - return nil, fmt.Errorf("remote conn %v not found", addr) - } - - if p, ok := h.peers[addr]; ok { - p.AssociateConnection(remoteConn) - } - if p, ok := h.fallbackAddrs[addr]; ok { - p.AssociateConnection(remoteConn) - } - return localConn, nil - } - - var err error - h.dispatcher, err = NewPrunedBlockDispatcher(&PrunedBlockDispatcherConfig{ - ChainParams: &chainParams, - NumTargetPeers: int(numWorkers), - Dial: dial, - GetPeers: func() ([]btcjson.GetPeerInfoResult, error) { - h.peerMtx.Lock() - defer h.peerMtx.Unlock() - - res := make([]btcjson.GetPeerInfoResult, 0, len(h.peers)) - for addr, peer := range h.peers { - var rawServices [8]byte - binary.BigEndian.PutUint64( - rawServices[:], uint64(peer.Services()), - ) - - res = append(res, btcjson.GetPeerInfoResult{ - Addr: addr, - Services: hex.EncodeToString(rawServices[:]), - }) - } - - return res, nil - }, - GetNodeAddresses: func(*int32) ([]btcjson.GetNodeAddressesResult, error) { - h.peerMtx.Lock() - defer h.peerMtx.Unlock() - - res := make( - []btcjson.GetNodeAddressesResult, 0, - len(h.fallbackAddrs), - ) - for addr, peer := range h.fallbackAddrs { - res = append(res, btcjson.GetNodeAddressesResult{ - Services: uint64(peer.Services()), - Address: addr, - }) - } - return res, nil - }, - PeerReadyTimeout: time.Hour, - RefreshPeersTicker: ticker.NewForce(time.Hour), - AllowSelfPeerConns: true, - MaxRequestInvs: wire.MaxInvPerMsg, - }) - require.NoError(t, err) - - return h -} - -// start starts the PrunedBlockDispatcher and asserts that connections are made -// to all available peers. -func (h *prunedBlockDispatcherHarness) start() { - h.t.Helper() - - err := h.dispatcher.Start() - require.NoError(h.t, err) - - h.peerMtx.Lock() - numPeers := len(h.peers) - h.peerMtx.Unlock() - - for i := 0; i < numPeers; i++ { - h.assertPeerDialed() - } -} - -// stop stops the PrunedBlockDispatcher and asserts that all internal fields of -// the harness have been properly consumed. -func (h *prunedBlockDispatcherHarness) stop() { - h.dispatcher.Stop() - - select { - case <-h.dialedPeer: - h.t.Fatal("did not consume all dialedPeer signals") - default: - } - - select { - case <-h.queriedPeer: - h.t.Fatal("did not consume all queriedPeer signals") - default: - } - - require.Empty(h.t, h.blocksQueried) -} - -// addPeer adds a new random peer available for use by the -// PrunedBlockDispatcher. -func (h *prunedBlockDispatcherHarness) addPeer(fallback bool) string { - addr := nextAddr() - - h.peerMtx.Lock() - defer h.peerMtx.Unlock() - - h.resetPeer(addr, fallback) - return addr -} - -// resetPeer resets the internal peer connection state allowing the -// PrunedBlockDispatcher to establish a mock connection to it. -func (h *prunedBlockDispatcherHarness) resetPeer(addr string, fallback bool) { - if fallback { - h.fallbackAddrs[addr] = h.newPeer() - } else { - h.peers[addr] = h.newPeer() - } - - inConn, outConn, err := setupConnPair() - if err != nil { - h.t.Fatalf("failed to setup conn pair: %v", err) - } - - h.localConns[addr] = outConn - h.remoteConns[addr] = inConn -} - -// newPeer returns a new properly configured peer.Peer instance that will be -// used by the PrunedBlockDispatcher. -func (h *prunedBlockDispatcherHarness) newPeer() *peer.Peer { - return peer.NewInboundPeer(&peer.Config{ - ChainParams: &chainParams, - DisableRelayTx: true, - Listeners: peer.MessageListeners{ - OnGetData: func(p *peer.Peer, msg *wire.MsgGetData) { - go func() { - h.queriedPeer <- struct{}{} - }() - - for _, inv := range msg.InvList { - // Invs should always be for blocks. - require.Equal(h.t, wire.InvTypeBlock, inv.Type) - - // Invs should always be for known blocks. - block, ok := h.blocks[inv.Hash] - require.True(h.t, ok) - - switch atomic.LoadUint32(&h.shouldReply) { - // Don't reply if requested. - case 1: - continue - // Make the block invalid and send it. - case 2: - block = produceInvalidBlock(block) - } - - go p.QueueMessage(block, nil) - } - }, - }, - Services: wire.SFNodeNetwork | wire.SFNodeWitness, - AllowSelfConns: true, - }) -} - -// query requests the given blocks from the PrunedBlockDispatcher. -func (h *prunedBlockDispatcherHarness) query(blocks []*chainhash.Hash) ( - <-chan *wire.MsgBlock, <-chan error) { - - h.t.Helper() - - blockChan, errChan := h.dispatcher.Query(blocks) - select { - case err := <-errChan: - require.NoError(h.t, err) - default: - } - - for _, block := range blocks { - h.blocksQueried[*block]++ - } - - return blockChan, errChan -} - -// disablePeerReplies prevents the query peer from replying. -func (h *prunedBlockDispatcherHarness) disablePeerReplies() { - atomic.StoreUint32(&h.shouldReply, 1) -} - -// enablePeerReplies allows the query peer to reply. -func (h *prunedBlockDispatcherHarness) enablePeerReplies() { - atomic.StoreUint32(&h.shouldReply, 0) -} - -// enableInvalidPeerReplies -func (h *prunedBlockDispatcherHarness) enableInvalidPeerReplies() { - atomic.StoreUint32(&h.shouldReply, 2) -} - -// refreshPeers forces the RefreshPeersTicker to fire. -func (h *prunedBlockDispatcherHarness) refreshPeers() { - h.t.Helper() - - h.dispatcher.cfg.RefreshPeersTicker.(*ticker.Force).Force <- time.Now() -} - -// disconnectPeer simulates a peer disconnecting from the PrunedBlockDispatcher. -func (h *prunedBlockDispatcherHarness) disconnectPeer(addr string, fallback bool) { - h.t.Helper() - - h.peerMtx.Lock() - defer h.peerMtx.Unlock() - - require.Contains(h.t, h.peers, addr) - - // Obtain the current number of peers before disconnecting such that we - // can block until the peer has been fully disconnected. - h.dispatcher.peerMtx.Lock() - numPeers := len(h.dispatcher.currentPeers) - h.dispatcher.peerMtx.Unlock() - - h.peers[addr].Disconnect() - - require.Eventually(h.t, func() bool { - h.dispatcher.peerMtx.Lock() - defer h.dispatcher.peerMtx.Unlock() - return len(h.dispatcher.currentPeers) == numPeers-1 - }, time.Second, 200*time.Millisecond) - - // Reset the peer connection state to allow connections to them again. - h.resetPeer(addr, fallback) -} - -// assertPeerDialed asserts that a connection was made to the given peer. -func (h *prunedBlockDispatcherHarness) assertPeerDialed() { - h.t.Helper() - - select { - case <-h.dialedPeer: - case <-time.After(5 * time.Second): - h.t.Fatalf("expected peer to be dialed") - } -} - -// assertPeerDialedWithAddr asserts that a connection was made to the given peer. -func (h *prunedBlockDispatcherHarness) assertPeerDialedWithAddr(addr string) { - h.t.Helper() - - select { - case dialedAddr := <-h.dialedPeer: - require.Equal(h.t, addr, dialedAddr) - case <-time.After(5 * time.Second): - h.t.Fatalf("expected peer to be dialed") - } -} - -// assertPeerQueried asserts that query was sent to the given peer. -func (h *prunedBlockDispatcherHarness) assertPeerQueried() { - h.t.Helper() - - select { - case <-h.queriedPeer: - case <-time.After(5 * time.Second): - h.t.Fatalf("expected a peer to be queried") - } -} - -// assertPeerReplied asserts that the query peer replies with a block the -// PrunedBlockDispatcher queried for. -func (h *prunedBlockDispatcherHarness) assertPeerReplied( - blockChan <-chan *wire.MsgBlock, errChan <-chan error, - expectCompletionSignal bool) { - - h.t.Helper() - - select { - case block := <-blockChan: - blockHash := block.BlockHash() - _, ok := h.blocksQueried[blockHash] - require.True(h.t, ok) - - expBlock, ok := h.blocks[blockHash] - require.True(h.t, ok) - require.Equal(h.t, expBlock, block) - - // Decrement how many clients queried the same block. Once we - // have none left, remove it from the map. - h.blocksQueried[blockHash]-- - if h.blocksQueried[blockHash] == 0 { - delete(h.blocksQueried, blockHash) - } - - case <-time.After(5 * time.Second): - select { - case err := <-errChan: - h.t.Fatalf("received unexpected error send: %v", err) - default: - } - h.t.Fatal("expected reply from peer") - } - - // If we should expect a nil error to be sent by the internal - // workManager to signal completion of the request, wait for it now. - if expectCompletionSignal { - select { - case err := <-errChan: - require.NoError(h.t, err) - case <-time.After(5 * time.Second): - h.t.Fatal("expected nil err to signal completion") - } - } -} - -// assertNoPeerDialed asserts that the PrunedBlockDispatcher hasn't established -// a new peer connection. -func (h *prunedBlockDispatcherHarness) assertNoPeerDialed() { - h.t.Helper() - - select { - case peer := <-h.dialedPeer: - h.t.Fatalf("unexpected connection established with peer %v", peer) - case <-time.After(2 * time.Second): - } -} - -// assertNoReply asserts that the peer hasn't replied to a query. -func (h *prunedBlockDispatcherHarness) assertNoReply( - blockChan <-chan *wire.MsgBlock, errChan <-chan error) { - - h.t.Helper() - - select { - case block := <-blockChan: - h.t.Fatalf("received unexpected block %v", block.BlockHash()) - case err := <-errChan: - h.t.Fatalf("received unexpected error send: %v", err) - case <-time.After(2 * time.Second): - } -} - -// TestPrunedBlockDispatcherQuerySameBlock tests that client requests for the -// same block result in only fetching the block once while pending. -func TestPrunedBlockDispatcherQuerySameBlock(t *testing.T) { - t.Parallel() - - const numBlocks = 1 - const numPeers = 5 - const numRequests = numBlocks * numPeers - - h := newNetworkBlockTestHarness(t, numBlocks, numPeers, numPeers) - h.start() - defer h.stop() - - // Queue all the block requests one by one. - blockChans := make([]<-chan *wire.MsgBlock, 0, numRequests) - errChans := make([]<-chan error, 0, numRequests) - for i := 0; i < numRequests; i++ { - blockChan, errChan := h.query(h.hashes) - blockChans = append(blockChans, blockChan) - errChans = append(errChans, errChan) - } - - // We should only see one query. - h.assertPeerQueried() - for i := 0; i < numRequests; i++ { - h.assertPeerReplied(blockChans[i], errChans[i], i == 0) - } -} - -// TestPrunedBlockDispatcherMultipleGetData tests that a client requesting blocks -// that span across multiple queries works as intended. -func TestPrunedBlockDispatcherMultipleGetData(t *testing.T) { - t.Parallel() - - const maxRequestInvs = 5 - const numBlocks = (maxRequestInvs * 5) + 1 - - h := newNetworkBlockTestHarness(t, numBlocks, 1, 1) - h.dispatcher.cfg.MaxRequestInvs = maxRequestInvs - h.start() - defer h.stop() - - // Request all blocks. - blockChan, errChan := h.query(h.hashes) - - // Since we have more blocks than can fit in a single GetData message, - // we should expect multiple queries. For each query, we should expect - // wire.MaxInvPerMsg replies until we've received all of them. - blocksRecvd := 0 - numMsgs := (numBlocks / maxRequestInvs) - if numBlocks%wire.MaxInvPerMsg > 0 { - numMsgs++ - } - for i := 0; i < numMsgs; i++ { - h.assertPeerQueried() - for j := 0; j < maxRequestInvs; j++ { - expectCompletionSignal := blocksRecvd == numBlocks-1 - h.assertPeerReplied( - blockChan, errChan, expectCompletionSignal, - ) - - blocksRecvd++ - if blocksRecvd == numBlocks { - break - } - } - } -} - -// TestPrunedBlockDispatcherMultipleQueryPeers tests that client requests are -// distributed across multiple query peers. -func TestPrunedBlockDispatcherMultipleQueryPeers(t *testing.T) { - t.Parallel() - - const numBlocks = 10 - const numPeers = numBlocks / 2 - - h := newNetworkBlockTestHarness(t, numBlocks, numPeers, numPeers) - h.start() - defer h.stop() - - // Queue all the block requests one by one. - blockChans := make([]<-chan *wire.MsgBlock, 0, numBlocks) - errChans := make([]<-chan error, 0, numBlocks) - for i := 0; i < numBlocks; i++ { - blockChan, errChan := h.query(h.hashes[i : i+1]) - blockChans = append(blockChans, blockChan) - errChans = append(errChans, errChan) - } - - // We should see one query per block. - for i := 0; i < numBlocks; i++ { - h.assertPeerQueried() - h.assertPeerReplied(blockChans[i], errChans[i], true) - } -} - -// TestPrunedBlockDispatcherPeerPoller ensures that the peer poller can detect -// when more connections are required to satisfy a request. -func TestPrunedBlockDispatcherPeerPoller(t *testing.T) { - t.Parallel() - - // Initialize our harness as usual, but don't create any peers yet. - h := newNetworkBlockTestHarness(t, 1, 0, 2) - h.start() - defer h.stop() - - // We shouldn't see any peers dialed since we don't have any. - h.assertNoPeerDialed() - - // We'll then query for a block. - blockChan, errChan := h.query(h.hashes) - - // Refresh our peers. This would dial some peers, but we don't have any - // yet. - h.refreshPeers() - h.assertNoPeerDialed() - - // Add a new peer and force a refresh. We should see the peer be dialed. - // We'll disable replies for now, as we'll want to test the disconnect - // case. - h.disablePeerReplies() - peer := h.addPeer(false) - h.refreshPeers() - h.assertPeerDialedWithAddr(peer) - h.assertPeerQueried() - - // Disconnect our peer and re-enable replies. - h.disconnectPeer(peer, false) - h.enablePeerReplies() - h.assertNoReply(blockChan, errChan) - - // Force a refresh once again. Since the peer has disconnected, a new - // connection should be made and the peer should be queried again. - h.refreshPeers() - h.assertPeerDialed() - h.assertPeerQueried() - - // Add a fallback addresses and force refresh our peers again. We can - // afford to have one more query peer, so a connection should be made. - fallbackPeer := h.addPeer(true) - h.refreshPeers() - h.assertPeerDialedWithAddr(fallbackPeer) - - // Now that we know we've connected to the peer, we should be able to - // receive their response. - h.assertPeerReplied(blockChan, errChan, true) -} - -// TestPrunedBlockDispatcherInvalidBlock ensures that validation is performed on -// blocks received from peers, and that any peers which have sent an invalid -// block are banned and not connected to. -func TestPrunedBlockDispatcherInvalidBlock(t *testing.T) { - t.Parallel() - - h := newNetworkBlockTestHarness(t, 1, 1, 1) - h.start() - defer h.stop() - - // We'll start the test by signaling our peer to send an invalid block. - h.enableInvalidPeerReplies() - - // We'll then query for a block. We shouldn't see a response as the - // block should have failed validation. - blockChan, errChan := h.query(h.hashes) - h.assertPeerQueried() - h.assertNoReply(blockChan, errChan) - - // Since the peer sent us an invalid block, they should have been - // disconnected and banned. Refreshing our peers shouldn't result in a - // new connection attempt because we don't have any other peers - // available. - h.refreshPeers() - h.assertNoPeerDialed() - - // Signal to our peers to send valid replies and add a new peer. - h.enablePeerReplies() - _ = h.addPeer(false) - - // Force a refresh, which should cause our new peer to be dialed and - // queried. We expect them to send a valid block and fulfill our - // request. - h.refreshPeers() - h.assertPeerDialed() - h.assertPeerQueried() - h.assertPeerReplied(blockChan, errChan, true) -} - -func TestSatisfiesRequiredServices(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - services wire.ServiceFlag - ok bool - }{ - { - name: "full node, segwit", - services: wire.SFNodeWitness | wire.SFNodeNetwork, - ok: true, - }, - { - name: "full node segwit, signals limited", - services: wire.SFNodeWitness | wire.SFNodeNetwork | prunedNodeService, - ok: true, - }, - { - name: "full node, no segwit", - services: wire.SFNodeNetwork, - ok: false, - }, - { - name: "segwit, pure pruned", - services: wire.SFNodeWitness | prunedNodeService, - ok: false, - }, - } - for _, testCase := range testCases { - ok := satisfiesRequiredServices(testCase.services) - require.Equal( - t, testCase.ok, ok, fmt.Sprintf("test case: %v", testCase.name), - ) - } -} diff --git a/btcclient/queue.go b/btcclient/queue.go deleted file mode 100644 index 5d15c157..00000000 --- a/btcclient/queue.go +++ /dev/null @@ -1,88 +0,0 @@ -package btcclient - -import ( - "container/list" -) - -// ConcurrentQueue is a concurrent-safe FIFO queue with unbounded capacity. -// Clients interact with the queue by pushing items into the in channel and -// popping items from the out channel. There is a goroutine that manages moving -// items from the in channel to the out channel in the correct order that must -// be started by calling Start(). -type ConcurrentQueue struct { - chanIn chan interface{} - chanOut chan interface{} - quit chan struct{} - overflow *list.List -} - -// NewConcurrentQueue constructs a ConcurrentQueue. The bufferSize parameter is -// the capacity of the output channel. When the size of the queue is below this -// threshold, pushes do not incur the overhead of the less efficient overflow -// structure. -func NewConcurrentQueue(bufferSize int) *ConcurrentQueue { - return &ConcurrentQueue{ - chanIn: make(chan interface{}), - chanOut: make(chan interface{}, bufferSize), - quit: make(chan struct{}), - overflow: list.New(), - } -} - -// ChanIn returns a channel that can be used to push new items into the queue. -func (cq *ConcurrentQueue) ChanIn() chan<- interface{} { - return cq.chanIn -} - -// ChanOut returns a channel that can be used to pop items from the queue. -func (cq *ConcurrentQueue) ChanOut() <-chan interface{} { - return cq.chanOut -} - -// Start begins a goroutine that manages moving items from the in channel to -// the out channel. The queue tries to move items directly to the out channel -// minimize overhead, but if the out channel is full it pushes items to an -// overflow queue. This must be called before using the queue. -func (cq *ConcurrentQueue) Start() { - go func() { - for { - nextElement := cq.overflow.Front() - if nextElement == nil { - // The overflow queue is empty, so incoming - // items can be pushed directly to the output - // channel. However, if output channel is full, - // we'll push to the overflow list instead. - select { - case item := <-cq.chanIn: - select { - case cq.chanOut <- item: - case <-cq.quit: - return - default: - cq.overflow.PushBack(item) - } - case <-cq.quit: - return - } - } else { - // The overflow queue is not empty, so any new - // items get pushed to the back to preserve - // order. - select { - case item := <-cq.chanIn: - cq.overflow.PushBack(item) - case cq.chanOut <- nextElement.Value: - cq.overflow.Remove(nextElement) - case <-cq.quit: - return - } - } - } - }() -} - -// Stop ends the goroutine that moves items from the in channel to the out -// channel. -func (cq *ConcurrentQueue) Stop() { - close(cq.quit) -} diff --git a/btcclient/utils_test.go b/btcclient/utils_test.go deleted file mode 100644 index 10244aa6..00000000 --- a/btcclient/utils_test.go +++ /dev/null @@ -1,248 +0,0 @@ -package btcclient - -import ( - "errors" - "fmt" - "math" - "net" - "runtime" - "sync" - "time" - - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" -) - -// setupConnPair initiates a tcp connection between two peers. -func setupConnPair() (net.Conn, net.Conn, error) { - // listenFunc is a function closure that listens for a tcp connection. - // The tcp connection will be the one the inbound peer uses. - listenFunc := func(l *net.TCPListener, errChan chan error, - listenChan chan struct{}, connChan chan net.Conn) { - - listenChan <- struct{}{} - - conn, err := l.Accept() - if err != nil { - errChan <- err - return - } - - connChan <- conn - } - - // dialFunc is a function closure that initiates the tcp connection. - // This tcp connection will be the one the outbound peer uses. - dialFunc := func(addr *net.TCPAddr) (net.Conn, error) { - conn, err := net.Dial("tcp", addr.String()) - if err != nil { - return nil, err - } - - return conn, nil - } - - listenAddr := "localhost:0" - - addr, err := net.ResolveTCPAddr("tcp", listenAddr) - if err != nil { - return nil, nil, err - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return nil, nil, err - } - - errChan := make(chan error, 1) - listenChan := make(chan struct{}, 1) - connChan := make(chan net.Conn, 1) - - go listenFunc(l, errChan, listenChan, connChan) - <-listenChan - - outConn, err := dialFunc(l.Addr().(*net.TCPAddr)) - if err != nil { - return nil, nil, err - } - - select { - case err = <-errChan: - return nil, nil, err - case inConn := <-connChan: - return inConn, outConn, nil - case <-time.After(time.Second * 5): - return nil, nil, errors.New("failed to create connection") - } -} - -// calcMerkleRoot creates a merkle tree from the slice of transactions and -// returns the root of the tree. -// -// This function was copied from: -// https://github.com/btcsuite/btcd/blob/36a96f6a0025b6aeaebe4106821c2d46ee4be8d4/blockchain/fullblocktests/generate.go#L303 -func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { - if len(txns) == 0 { - return chainhash.Hash{} - } - - utilTxns := make([]*btcutil.Tx, 0, len(txns)) - for _, tx := range txns { - utilTxns = append(utilTxns, btcutil.NewTx(tx)) - } - merkles := blockchain.BuildMerkleTreeStore(utilTxns, false) - return *merkles[len(merkles)-1] -} - -// solveBlock attempts to find a nonce which makes the passed block header hash -// to a value less than the target difficulty. When a successful solution is -// found true is returned and the nonce field of the passed header is updated -// with the solution. False is returned if no solution exists. -// -// This function was copied from: -// https://github.com/btcsuite/btcd/blob/36a96f6a0025b6aeaebe4106821c2d46ee4be8d4/blockchain/fullblocktests/generate.go#L324 -func solveBlock(header *wire.BlockHeader) bool { - // sbResult is used by the solver goroutines to send results. - type sbResult struct { - found bool - nonce uint32 - } - - // Make sure all spawned goroutines finish executing before returning. - var wg sync.WaitGroup - defer func() { - wg.Wait() - }() - - // solver accepts a block header and a nonce range to test. It is - // intended to be run as a goroutine. - targetDifficulty := blockchain.CompactToBig(header.Bits) - quit := make(chan bool) - results := make(chan sbResult) - solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) { - defer wg.Done() - - // We need to modify the nonce field of the header, so make sure - // we work with a copy of the original header. - for i := startNonce; i >= startNonce && i <= stopNonce; i++ { - select { - case <-quit: - return - default: - hdr.Nonce = i - hash := hdr.BlockHash() - if blockchain.HashToBig(&hash).Cmp( - targetDifficulty) <= 0 { - - select { - case results <- sbResult{true, i}: - case <-quit: - } - - return - } - } - } - - select { - case results <- sbResult{false, 0}: - case <-quit: - } - } - - startNonce := uint32(1) - stopNonce := uint32(math.MaxUint32) - numCores := uint32(runtime.NumCPU()) - noncesPerCore := (stopNonce - startNonce) / numCores - wg.Add(int(numCores)) - for i := uint32(0); i < numCores; i++ { - rangeStart := startNonce + (noncesPerCore * i) - rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1 - if i == numCores-1 { - rangeStop = stopNonce - } - go solver(*header, rangeStart, rangeStop) - } - for i := uint32(0); i < numCores; i++ { - result := <-results - if result.found { - close(quit) - header.Nonce = result.nonce - return true - } - } - - return false -} - -// genBlockChain generates a test chain with the given number of blocks. -func genBlockChain(numBlocks uint32) ([]*chainhash.Hash, map[chainhash.Hash]*wire.MsgBlock) { - prevHash := chainParams.GenesisHash - prevHeader := &chainParams.GenesisBlock.Header - - hashes := make([]*chainhash.Hash, numBlocks) - blocks := make(map[chainhash.Hash]*wire.MsgBlock, numBlocks) - - // Each block contains three transactions, including the coinbase - // transaction. Each non-coinbase transaction spends outputs from - // the previous block. We also need to produce blocks that succeed - // validation through blockchain.CheckBlockSanity. - script := []byte{0x01, 0x01} - createTx := func(prevOut wire.OutPoint) *wire.MsgTx { - return &wire.MsgTx{ - TxIn: []*wire.TxIn{{ - PreviousOutPoint: prevOut, - SignatureScript: script, - }}, - TxOut: []*wire.TxOut{{PkScript: script}}, - } - } - for i := uint32(0); i < numBlocks; i++ { - txs := []*wire.MsgTx{ - createTx(wire.OutPoint{Index: wire.MaxPrevOutIndex}), - createTx(wire.OutPoint{Hash: *prevHash, Index: 0}), - createTx(wire.OutPoint{Hash: *prevHash, Index: 1}), - } - header := &wire.BlockHeader{ - Version: 1, - PrevBlock: *prevHash, - MerkleRoot: calcMerkleRoot(txs), - Timestamp: prevHeader.Timestamp.Add(10 * time.Minute), - Bits: chainParams.PowLimitBits, - Nonce: 0, - } - if !solveBlock(header) { - panic(fmt.Sprintf("could not solve block at idx %v", i)) - } - block := &wire.MsgBlock{ - Header: *header, - Transactions: txs, - } - - blockHash := block.BlockHash() - hashes[i] = &blockHash - blocks[blockHash] = block - - prevHash = &blockHash - prevHeader = header - } - - return hashes, blocks -} - -// producesInvalidBlock produces a copy of the block that duplicates the last -// transaction. When the block has an odd number of transactions, this results -// in the invalid block maintaining the same hash as the valid block. -func produceInvalidBlock(block *wire.MsgBlock) *wire.MsgBlock { - numTxs := len(block.Transactions) - lastTx := block.Transactions[numTxs-1] - blockCopy := &wire.MsgBlock{ - Header: block.Header, - Transactions: make([]*wire.MsgTx, numTxs), - } - copy(blockCopy.Transactions, block.Transactions) - blockCopy.AddTransaction(lastTx) - return blockCopy -} diff --git a/go.mod b/go.mod index 52cb8880..471a8342 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,11 @@ go 1.18 require ( github.com/btcsuite/btcd v0.23.1 - github.com/btcsuite/btcd/btcec/v2 v2.1.3 github.com/btcsuite/btcd/btcutil v1.1.1 - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/btcsuite/btcwallet v0.15.1 - github.com/btcsuite/btcwallet/wtxmgr v1.5.0 - github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf - github.com/lightninglabs/neutrino v0.14.2 - github.com/lightningnetwork/lnd/ticker v1.1.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 github.com/spf13/viper v1.12.0 - github.com/stretchr/testify v1.8.0 golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b google.golang.org/grpc v1.48.0 google.golang.org/protobuf v1.28.1 @@ -23,8 +16,11 @@ require ( require ( github.com/aead/siphash v1.0.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect + github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -36,17 +32,21 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/lightninglabs/neutrino v0.14.2 // indirect github.com/lightningnetwork/lnd/clock v1.0.1 // indirect + github.com/lightningnetwork/lnd/queue v1.0.1 // indirect + github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect diff --git a/go.sum b/go.sum index c5b31c2e..787a5107 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,11 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcwallet v0.15.1 h1:SKfh/l2Bgz9sJwHZvfiVbZ8Pl3N/8fFcWWXzsAPz9GU= github.com/btcsuite/btcwallet v0.15.1/go.mod h1:7OFsQ8ypiRwmr67hE0z98uXgJgXGAihE79jCib9x6ag= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 h1:M2yr5UlULvpqtxUqpMxTME/pA92Z9cpqeyvAFk9lAg0= github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3/go.mod h1:T2xSiKGpUkSLCh68aF+FMXmKK9mFqNdHl9VaqOr+JjU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8= github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= @@ -218,6 +221,7 @@ github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/c github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc= github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= diff --git a/vigilante/reporter.go b/vigilante/reporter.go index cbe3345b..c6a387ca 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -10,7 +10,7 @@ import ( ) type Reporter struct { - btcClient btcclient.Interface + btcClient *btcclient.Client btcClientLock sync.Mutex // TODO: add Babylon client @@ -23,7 +23,7 @@ type Reporter struct { quitMu sync.Mutex } -func NewReporter(cfg *config.ReporterConfig, btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Reporter, error) { +func NewReporter(cfg *config.ReporterConfig, btcClient *btcclient.Client, btcParams *netparams.BTCParams) (*Reporter, error) { return &Reporter{ btcClient: btcClient, btcParams: btcParams, @@ -62,7 +62,7 @@ func (r *Reporter) Start() { // // This method is unstable and will be removed when all syncing logic is moved // outside of the vigilante package. -func (r *Reporter) SynchronizeRPC(btcClient btcclient.Interface) { +func (r *Reporter) SynchronizeRPC(btcClient *btcclient.Client) { r.quitMu.Lock() select { case <-r.quit: @@ -98,7 +98,7 @@ func (r *Reporter) SynchronizeRPC(btcClient btcclient.Interface) { // consensus RPC server is set. This function and all functions that call it // are unstable and will need to be moved when the syncing code is moved out of // the vigilante. -func (r *Reporter) requireGetBtcClient() (btcclient.Interface, error) { +func (r *Reporter) requireGetBtcClient() (*btcclient.Client, error) { r.btcClientLock.Lock() btcClient := r.btcClient r.btcClientLock.Unlock() @@ -113,7 +113,7 @@ func (r *Reporter) requireGetBtcClient() (btcclient.Interface, error) { // // This function is unstable and will be removed once sync logic is moved out of // the vigilante. -func (r *Reporter) getBtcClient() btcclient.Interface { +func (r *Reporter) getBtcClient() *btcclient.Client { r.btcClientLock.Lock() btcClient := r.btcClient r.btcClientLock.Unlock() diff --git a/vigilante/submitter.go b/vigilante/submitter.go index 1680c0bd..58a7e2f1 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -10,7 +10,7 @@ import ( ) type Submitter struct { - btcClient btcclient.Interface + btcClient *btcclient.Client btcClientLock sync.Mutex // TODO: add Babylon client // TODO: add wallet client @@ -24,7 +24,7 @@ type Submitter struct { quitMu sync.Mutex } -func NewSubmitter(cfg *config.SubmitterConfig, btcClient btcclient.Interface, btcParams *netparams.BTCParams) (*Submitter, error) { +func NewSubmitter(cfg *config.SubmitterConfig, btcClient *btcclient.Client, btcParams *netparams.BTCParams) (*Submitter, error) { return &Submitter{ btcClient: btcClient, btcParams: btcParams, @@ -63,7 +63,7 @@ func (s *Submitter) Start() { // // This method is unstable and will be removed when all syncing logic is moved // outside of the vigilante package. -func (s *Submitter) SynchronizeRPC(btcClient btcclient.Interface) { +func (s *Submitter) SynchronizeRPC(btcClient *btcclient.Client) { s.quitMu.Lock() select { case <-s.quit: @@ -99,7 +99,7 @@ func (s *Submitter) SynchronizeRPC(btcClient btcclient.Interface) { // consensus RPC server is set. This function and all functions that call it // are unstable and will need to be moved when the syncing code is moved out of // the vigilante. -func (s *Submitter) requireGetBtcClient() (btcclient.Interface, error) { +func (s *Submitter) requireGetBtcClient() (*btcclient.Client, error) { s.btcClientLock.Lock() btcClient := s.btcClient s.btcClientLock.Unlock() @@ -114,7 +114,7 @@ func (s *Submitter) requireGetBtcClient() (btcclient.Interface, error) { // // This function is unstable and will be removed once sync logic is moved out of // the vigilante. -func (s *Submitter) getBtcClient() btcclient.Interface { +func (s *Submitter) getBtcClient() *btcclient.Client { s.btcClientLock.Lock() btcClient := s.btcClient s.btcClientLock.Unlock() From df55428e7be95c7397faab708abb43a8dd860c75 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 10:53:29 +1000 Subject: [PATCH 20/28] copyright --- LICENSE | 17 +++++++++++++++++ btcclient/client.go | 1 + cmd/utils/signal.go | 1 + cmd/utils/signalsigterm.go | 1 + netparams/bitcoin.go | 1 + rpcserver/server.go | 1 + 6 files changed, 22 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ff400f8e --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +ISC License + +Copyright (c) 2022-2022 The Babylon developers +Copyright (c) 2013-2017 The btcsuite developers +Copyright (c) 2015-2016 The Decred developers + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/btcclient/client.go b/btcclient/client.go index b0fea10c..9380b937 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -1,3 +1,4 @@ +// Copyright (c) 2022-2022 The Babylon developers // Copyright (c) 2013-2016 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. diff --git a/cmd/utils/signal.go b/cmd/utils/signal.go index 3eb7bc57..4805bdc8 100644 --- a/cmd/utils/signal.go +++ b/cmd/utils/signal.go @@ -1,3 +1,4 @@ +// Copyright (c) 2022-2022 The Babylon developers // Copyright (c) 2013-2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. diff --git a/cmd/utils/signalsigterm.go b/cmd/utils/signalsigterm.go index 02d4e2af..b9310309 100644 --- a/cmd/utils/signalsigterm.go +++ b/cmd/utils/signalsigterm.go @@ -1,3 +1,4 @@ +// Copyright (c) 2022-2022 The Babylon developers // Copyright (c) 2016 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. diff --git a/netparams/bitcoin.go b/netparams/bitcoin.go index b7554a21..7b46558b 100644 --- a/netparams/bitcoin.go +++ b/netparams/bitcoin.go @@ -1,3 +1,4 @@ +// Copyright (c) 2022-2022 The Babylon developers // Copyright (c) 2013-2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. diff --git a/rpcserver/server.go b/rpcserver/server.go index b139b12d..557bf06f 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -1,3 +1,4 @@ +// Copyright (c) 2022-2022 The Babylon developers // Copyright (c) 2015-2016 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. From 4fc7bb7d571c0beaf1cd24afebfe4b18e992ff98 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 10:55:47 +1000 Subject: [PATCH 21/28] censor directory --- sample-vigilante.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sample-vigilante.yaml b/sample-vigilante.yaml index e36c4b97..1e0234fa 100644 --- a/sample-vigilante.yaml +++ b/sample-vigilante.yaml @@ -1,7 +1,7 @@ base: placeholder: baseconfig btc: - cafile: /Users/rhan0013/Library/Application Support/Btcd/rpc.cert + cafile: //Btcd/rpc.cert endpoint: localhost:18554 netparams: simnet noclienttls: false @@ -12,8 +12,8 @@ grpc: endpoints: - localhost:8080 onetimetlskey: true - rpccert: /Users/rhan0013/Library/Application Support/Babylon-vigilante/rpc.cert - rpckey: /Users/rhan0013/Library/Application Support/Babylon-vigilante/rpc.key + rpccert: //Babylon-vigilante/rpc.cert + rpckey: //Babylon-vigilante/rpc.key grpc-web: placeholder: grpcwebconfig reporter: From 22f5da021846f422b9583aa5dd70ae04073dfe52 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 10:58:01 +1000 Subject: [PATCH 22/28] version --- rpcserver/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcserver/service.go b/rpcserver/service.go index cc0db54d..73fff9e7 100644 --- a/rpcserver/service.go +++ b/rpcserver/service.go @@ -9,8 +9,8 @@ import ( // Public API version constants const ( - verString = "2.0.1" - verMajor = 2 + verString = "0.0.1" + verMajor = 0 verMinor = 0 verPatch = 1 ) From 779c6e159cafc8fb2ea1b39e08eace8a2b830bfe Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 11:35:08 +1000 Subject: [PATCH 23/28] prometheus metrics --- cmd/reporter/reporter.go | 3 ++ cmd/submitter/submitter.go | 3 ++ go.mod | 7 +++ go.sum | 98 +++++++++++++++++++++++++++++++++++++- metrics/log.go | 7 +++ metrics/prometheus.go | 38 +++++++++++++++ rpcserver/server.go | 3 +- 7 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 metrics/log.go create mode 100644 metrics/prometheus.go diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index d268a563..7f15ccc0 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -5,6 +5,7 @@ import ( "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" vlog "github.com/babylonchain/vigilante/log" + "github.com/babylonchain/vigilante/metrics" "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" @@ -66,6 +67,8 @@ func cmdFunc(cmd *cobra.Command, args []string) { if err != nil { panic(err) } + // start Prometheus metrics server + metrics.Start() // SIGINT handling stuff utils.AddInterruptHandler(func() { diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 83dbcc9c..7c6ab0d1 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -5,6 +5,7 @@ import ( "github.com/babylonchain/vigilante/cmd/utils" "github.com/babylonchain/vigilante/config" vlog "github.com/babylonchain/vigilante/log" + "github.com/babylonchain/vigilante/metrics" "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" @@ -66,6 +67,8 @@ func cmdFunc(cmd *cobra.Command, args []string) { if err != nil { panic(err) } + // start Prometheus metrics server + metrics.Start() // SIGINT handling stuff utils.AddInterruptHandler(func() { diff --git a/go.mod b/go.mod index 471a8342..654bd048 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/btcsuite/btcd v0.23.1 github.com/btcsuite/btcd/btcutil v1.1.1 github.com/btcsuite/btcwallet v0.15.1 + github.com/prometheus/client_golang v1.13.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 github.com/spf13/viper v1.12.0 @@ -16,6 +17,7 @@ require ( require ( github.com/aead/siphash v1.0.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect @@ -23,6 +25,7 @@ require ( github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect @@ -39,9 +42,13 @@ require ( github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index 787a5107..d1305e0f 100644 --- a/go.sum +++ b/go.sum @@ -40,7 +40,16 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= @@ -89,6 +98,8 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -127,6 +138,16 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -172,6 +193,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -202,14 +224,24 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -230,8 +262,17 @@ github.com/lightningnetwork/lnd/tlv v1.0.2 h1:LG7H3Uw/mHYGnEeHRPg+STavAH+UsFvuBf github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -245,15 +286,44 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= @@ -269,8 +339,10 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -295,6 +367,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -344,6 +417,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -352,6 +426,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -377,6 +452,9 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -388,6 +466,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -399,10 +479,13 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -414,6 +497,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -427,6 +511,8 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -435,17 +521,23 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -453,6 +545,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -615,9 +708,11 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= @@ -627,6 +722,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/metrics/log.go b/metrics/log.go new file mode 100644 index 00000000..6a4d97eb --- /dev/null +++ b/metrics/log.go @@ -0,0 +1,7 @@ +package metrics + +import ( + vlog "github.com/babylonchain/vigilante/log" +) + +var log = vlog.Logger.WithField("module", "metrics") diff --git a/metrics/prometheus.go b/metrics/prometheus.go new file mode 100644 index 00000000..9a5a88a7 --- /dev/null +++ b/metrics/prometheus.go @@ -0,0 +1,38 @@ +package metrics + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func Start() { + go start() +} + +func start() { + // Create a new registry. + reg := prometheus.NewRegistry() + + addr := "localhost:2112" // TODO: move this to config + + // Add Go module build info. + reg.MustRegister(collectors.NewBuildInfoCollector()) + reg.MustRegister(collectors.NewGoCollector( + collectors.WithGoCollections(collectors.GoRuntimeMemStatsCollection | collectors.GoRuntimeMetricsCollection), + )) + // TODO: add more metrics + + // Expose the registered metrics via HTTP. + http.Handle("/metrics", promhttp.HandlerFor( + reg, + promhttp.HandlerOpts{ + // Opt into OpenMetrics to support exemplars. + EnableOpenMetrics: true, + }, + )) + log.Infof("Starting Prometheus metrics server at %s", addr) + log.Fatal(http.ListenAndServe(addr, nil)) +} diff --git a/rpcserver/server.go b/rpcserver/server.go index 557bf06f..984c2ffc 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -53,9 +53,8 @@ func New(cfg *config.GRPCConfig) (*grpc.Server, error) { log.Errorf("serve RPC server: %v", err) } }(lis) + log.Infof("Successfully started the GRPC server at %v", lis.Addr().String()) } - log.Infof("Successfully started the GRPC server") - return server, nil } From 53e49d045c274a1f0c9c34bffc8f4b216dd7254c Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 11:57:34 +1000 Subject: [PATCH 24/28] lint log outputs --- config/config.go | 4 ++-- metrics/prometheus.go | 2 +- rpcserver/server.go | 13 ++++++++----- rpcserver/tls.go | 2 -- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/config/config.go b/config/config.go index d0c5b5ea..0436a1e3 100644 --- a/config/config.go +++ b/config/config.go @@ -125,7 +125,7 @@ func New() (Config, error) { if err := viper.ReadInConfig(); err != nil { return Config{}, err } - log.Infof("successfully loaded config file at %s", defaultConfigFile) + log.Infof("Successfully loaded config file at %s", defaultConfigFile) var cfg Config err = viper.Unmarshal(&cfg) return cfg, err @@ -145,7 +145,7 @@ func NewFromFile(configFile string) (Config, error) { if err := viper.ReadInConfig(); err != nil { return Config{}, err } - log.Infof("successfully loaded config file at %s", configFile) + log.Infof("Successfully loaded config file at %s", configFile) var cfg Config err = viper.Unmarshal(&cfg) return cfg, err diff --git a/metrics/prometheus.go b/metrics/prometheus.go index 9a5a88a7..e847e2f7 100644 --- a/metrics/prometheus.go +++ b/metrics/prometheus.go @@ -33,6 +33,6 @@ func start() { EnableOpenMetrics: true, }, )) - log.Infof("Starting Prometheus metrics server at %s", addr) + log.Infof("Successfully started Prometheus metrics server at %s", addr) log.Fatal(http.ListenAndServe(addr, nil)) } diff --git a/rpcserver/server.go b/rpcserver/server.go index 984c2ffc..4851e6b9 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -17,6 +17,7 @@ package rpcserver import ( + "fmt" "net" "github.com/babylonchain/vigilante/config" @@ -28,7 +29,7 @@ import ( func New(cfg *config.GRPCConfig) (*grpc.Server, error) { keyPair, err := openRPCKeyPair(cfg.OneTimeTLSKey, cfg.RPCKeyFile, cfg.RPCCertFile) if err != nil { - return nil, err + return nil, fmt.Errorf("Open RPC key pair: %v", err) } creds := credentials.NewServerTLSFromCert(&keyPair) @@ -41,19 +42,21 @@ func New(cfg *config.GRPCConfig) (*grpc.Server, error) { for _, endpoint := range cfg.Endpoints { lis, err := net.Listen("tcp", endpoint) if err != nil { - log.Fatalf("failed to listen: %v", err) + log.Errorf("Listen: %v", err) + } else { + listeners = append(listeners, lis) } - listeners = append(listeners, lis) } // start the server with listeners, each in a goroutine for _, lis := range listeners { go func(l net.Listener) { if err := server.Serve(l); err != nil { - log.Errorf("serve RPC server: %v", err) + log.Errorf("Serve RPC server: %v", err) + } else { + log.Infof("Successfully started the GRPC server at %v", l.Addr().String()) } }(lis) - log.Infof("Successfully started the GRPC server at %v", lis.Addr().String()) } return server, nil diff --git a/rpcserver/tls.go b/rpcserver/tls.go index 84c4ece1..d7c27725 100644 --- a/rpcserver/tls.go +++ b/rpcserver/tls.go @@ -42,8 +42,6 @@ func openRPCKeyPair(oneTimeTLSKey bool, RPCKeyFile string, RPCCertFile string) ( // possibly also the key in PEM format to the paths specified by the config. If // successful, the new keypair is returned. func generateRPCKeyPair(RPCKeyFile string, RPCCertFile string, writeKey bool) (tls.Certificate, error) { - log.Infof("Generating TLS certificates for the RPC server...") - // Create directories for cert and key files if they do not yet exist. certDir, _ := filepath.Split(RPCCertFile) keyDir, _ := filepath.Split(RPCKeyFile) From 3c64ae673762831033d3ae157771d7de3a710bfb Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 14:25:55 +1000 Subject: [PATCH 25/28] multiple cleanups --- btcclient/client.go | 13 ++++--- cmd/reporter/reporter.go | 31 +++++++++-------- cmd/submitter/submitter.go | 31 +++++++++-------- netparams/bitcoin.go | 70 ++++---------------------------------- rpcserver/server.go | 21 ++++++++---- vigilante/reporter.go | 6 +--- vigilante/submitter.go | 5 +-- 7 files changed, 62 insertions(+), 115 deletions(-) diff --git a/btcclient/client.go b/btcclient/client.go index 9380b937..5322d17f 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -10,6 +10,7 @@ import ( "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/netparams" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/chain" ) @@ -19,6 +20,8 @@ var _ chain.Interface = &Client{} // for information regarding the current best block chain. type Client struct { *chain.RPCClient + Params *chaincfg.Params + Cfg *config.BTCConfig } // New creates a client connection to the server described by the @@ -33,19 +36,19 @@ func New(cfg *config.BTCConfig) (*Client, error) { } certs := readCAFile(cfg) - params := netparams.GetParams(cfg.NetParams) + params := netparams.GetBTCParams(cfg.NetParams) - rpcClient, err := chain.NewRPCClient(params.Params, cfg.Endpoint, cfg.Username, cfg.Password, certs, cfg.DisableClientTLS, cfg.ReconnectAttempts) + rpcClient, err := chain.NewRPCClient(params, cfg.Endpoint, cfg.Username, cfg.Password, certs, cfg.DisableClientTLS, cfg.ReconnectAttempts) if err != nil { return nil, err } - client := &Client{rpcClient} + client := &Client{rpcClient, params, cfg} return client, err } -func (c *Client) ConnectLoop(cfg *config.BTCConfig) { +func (c *Client) ConnectLoop() { go func() { - log.Infof("Start connecting to the BTC node %v", cfg.Endpoint) + log.Infof("Start connecting to the BTC node %v", c.Cfg.Endpoint) if err := c.Start(); err != nil { log.Errorf("Unable to connect to the BTC node: %v", err) } diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index 7f15ccc0..e659d41d 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -6,14 +6,14 @@ import ( "github.com/babylonchain/vigilante/config" vlog "github.com/babylonchain/vigilante/log" "github.com/babylonchain/vigilante/metrics" - "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) var ( - cfgFile string + cfgFile = "" + log = vlog.Logger.WithField("module", "cmd") ) // GetCmd returns the cli query commands for this module @@ -44,45 +44,46 @@ func cmdFunc(cmd *cobra.Command, args []string) { if err != nil { panic(err) } - btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client btcClient, err := btcclient.New(&cfg.BTC) if err != nil { panic(err) } - // create RPC client - reporter, err := vigilante.NewReporter(&cfg.Reporter, btcClient, &btcParams) + // create reporter + reporter, err := vigilante.NewReporter(&cfg.Reporter, btcClient) + if err != nil { + panic(err) + } + // crete RPC server + server, err := rpcserver.New(&cfg.GRPC) if err != nil { panic(err) } // keep trying BTC client - btcClient.ConnectLoop(&cfg.BTC) + btcClient.ConnectLoop() // start reporter and sync reporter.Start() reporter.SynchronizeRPC(btcClient) // start RPC server - server, err := rpcserver.New(&cfg.GRPC) - if err != nil { - panic(err) - } + server.Start() // start Prometheus metrics server metrics.Start() // SIGINT handling stuff utils.AddInterruptHandler(func() { // TODO: Does this need to wait for the grpc server to finish up any requests? - vlog.Logger.WithField("module", "cmd").Info("Stopping RPC server...") + log.Info("Stopping RPC server...") server.Stop() - vlog.Logger.WithField("module", "cmd").Info("RPC server shutdown") + log.Info("RPC server shutdown") }) utils.AddInterruptHandler(func() { - vlog.Logger.WithField("module", "cmd").Info("Stopping BTC client...") + log.Info("Stopping BTC client...") btcClient.Stop() - vlog.Logger.WithField("module", "cmd").Info("BTC client shutdown") + log.Info("BTC client shutdown") }) <-utils.InterruptHandlersDone - vlog.Logger.WithField("module", "cmd").Info("Shutdown complete") + log.Info("Shutdown complete") } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 7c6ab0d1..294f06f3 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -6,14 +6,14 @@ import ( "github.com/babylonchain/vigilante/config" vlog "github.com/babylonchain/vigilante/log" "github.com/babylonchain/vigilante/metrics" - "github.com/babylonchain/vigilante/netparams" "github.com/babylonchain/vigilante/rpcserver" "github.com/babylonchain/vigilante/vigilante" "github.com/spf13/cobra" ) var ( - cfgFile string + cfgFile = "" + log = vlog.Logger.WithField("module", "cmd") ) // GetCmd returns the cli query commands for this module @@ -44,45 +44,46 @@ func cmdFunc(cmd *cobra.Command, args []string) { if err != nil { panic(err) } - btcParams := netparams.GetParams(cfg.BTC.NetParams) // create BTC client btcClient, err := btcclient.New(&cfg.BTC) if err != nil { panic(err) } - // create RPC client - submitter, err := vigilante.NewSubmitter(&cfg.Submitter, btcClient, &btcParams) + // create submitter + submitter, err := vigilante.NewSubmitter(&cfg.Submitter, btcClient) + if err != nil { + panic(err) + } + // crete RPC server + server, err := rpcserver.New(&cfg.GRPC) if err != nil { panic(err) } // keep trying BTC client - btcClient.ConnectLoop(&cfg.BTC) + btcClient.ConnectLoop() // start submitter and sync submitter.Start() submitter.SynchronizeRPC(btcClient) // start RPC server - server, err := rpcserver.New(&cfg.GRPC) - if err != nil { - panic(err) - } + server.Start() // start Prometheus metrics server metrics.Start() // SIGINT handling stuff utils.AddInterruptHandler(func() { // TODO: Does this need to wait for the grpc server to finish up any requests? - vlog.Logger.WithField("module", "cmd").Info("Stopping RPC server...") + log.Info("Stopping RPC server...") server.Stop() - vlog.Logger.WithField("module", "cmd").Info("RPC server shutdown") + log.Info("RPC server shutdown") }) utils.AddInterruptHandler(func() { - vlog.Logger.WithField("module", "cmd").Info("Stopping BTC client...") + log.Info("Stopping BTC client...") btcClient.Stop() - vlog.Logger.WithField("module", "cmd").Info("BTC client shutdown") + log.Info("BTC client shutdown") }) <-utils.InterruptHandlersDone - vlog.Logger.WithField("module", "cmd").Info("Shutdown complete") + log.Info("Shutdown complete") } diff --git a/netparams/bitcoin.go b/netparams/bitcoin.go index 7b46558b..1d6b0f86 100644 --- a/netparams/bitcoin.go +++ b/netparams/bitcoin.go @@ -1,78 +1,20 @@ -// Copyright (c) 2022-2022 The Babylon developers -// Copyright (c) 2013-2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - package netparams import ( "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" ) -// BTCParams is used to group parameters for various networks such as the main -// network and test networks. -type BTCParams struct { - *chaincfg.Params - RPCClientPort string - RPCServerPort string -} - -// MainNetParams contains parameters specific running btcwallet and -// btcd on the main network (wire.MainNet). -var MainNetParams = BTCParams{ - Params: &chaincfg.MainNetParams, - RPCClientPort: "8334", - RPCServerPort: "8332", -} - -// TestNet3Params contains parameters specific running btcwallet and -// btcd on the test network (version 3) (wire.TestNet3). -var TestNet3Params = BTCParams{ - Params: &chaincfg.TestNet3Params, - RPCClientPort: "18334", - RPCServerPort: "18332", -} - -// SimNetParams contains parameters specific to the simulation test network -// (wire.SimNet). -var SimNetParams = BTCParams{ - Params: &chaincfg.SimNetParams, - RPCClientPort: "18556", - RPCServerPort: "18554", -} - -// SigNetParams contains parameters specific to the signet test network -// (wire.SigNet). -var SigNetParams = BTCParams{ - Params: &chaincfg.SigNetParams, - RPCClientPort: "38334", - RPCServerPort: "38332", -} - -// SigNetWire is a helper function that either returns the given chain -// parameter's net value if the parameter represents a signet network or 0 if -// it's not. This is necessary because there can be custom signet networks that -// have a different net value. -func SigNetWire(params *chaincfg.Params) wire.BitcoinNet { - if params.Name == chaincfg.SigNetParams.Name { - return params.Net - } - - return 0 -} - -func GetParams(net string) BTCParams { +func GetBTCParams(net string) *chaincfg.Params { switch net { case "mainnet": - return MainNetParams + return &chaincfg.MainNetParams case "testnet": - return TestNet3Params + return &chaincfg.TestNet3Params case "signet": - return SigNetParams + return &chaincfg.SigNetParams case "simnet": - return SimNetParams + return &chaincfg.SimNetParams default: - return SimNetParams + return &chaincfg.SimNetParams } } diff --git a/rpcserver/server.go b/rpcserver/server.go index 4851e6b9..b1a6eee3 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -26,7 +26,13 @@ import ( "google.golang.org/grpc/reflection" ) -func New(cfg *config.GRPCConfig) (*grpc.Server, error) { +type Server struct { + *grpc.Server + Cfg *config.GRPCConfig + // TODO: access to other system states exposed by RPC server +} + +func New(cfg *config.GRPCConfig) (*Server, error) { keyPair, err := openRPCKeyPair(cfg.OneTimeTLSKey, cfg.RPCKeyFile, cfg.RPCCertFile) if err != nil { return nil, fmt.Errorf("Open RPC key pair: %v", err) @@ -37,9 +43,13 @@ func New(cfg *config.GRPCConfig) (*grpc.Server, error) { reflection.Register(server) StartVigilanteService(server) + return &Server{server, cfg}, nil +} + +func (s *Server) Start() { // create listeners for endpoints listeners := []net.Listener{} - for _, endpoint := range cfg.Endpoints { + for _, endpoint := range s.Cfg.Endpoints { lis, err := net.Listen("tcp", endpoint) if err != nil { log.Errorf("Listen: %v", err) @@ -51,13 +61,10 @@ func New(cfg *config.GRPCConfig) (*grpc.Server, error) { // start the server with listeners, each in a goroutine for _, lis := range listeners { go func(l net.Listener) { - if err := server.Serve(l); err != nil { + if err := s.Serve(l); err != nil { log.Errorf("Serve RPC server: %v", err) - } else { - log.Infof("Successfully started the GRPC server at %v", l.Addr().String()) } }(lis) + log.Infof("Successfully started the GRPC server at %v", lis.Addr().String()) } - - return server, nil } diff --git a/vigilante/reporter.go b/vigilante/reporter.go index c6a387ca..5aa71364 100644 --- a/vigilante/reporter.go +++ b/vigilante/reporter.go @@ -6,7 +6,6 @@ import ( "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" - "github.com/babylonchain/vigilante/netparams" ) type Reporter struct { @@ -14,8 +13,6 @@ type Reporter struct { btcClientLock sync.Mutex // TODO: add Babylon client - btcParams *netparams.BTCParams - // TODO: add Babylon parameters wg sync.WaitGroup started bool @@ -23,10 +20,9 @@ type Reporter struct { quitMu sync.Mutex } -func NewReporter(cfg *config.ReporterConfig, btcClient *btcclient.Client, btcParams *netparams.BTCParams) (*Reporter, error) { +func NewReporter(cfg *config.ReporterConfig, btcClient *btcclient.Client) (*Reporter, error) { return &Reporter{ btcClient: btcClient, - btcParams: btcParams, quit: make(chan struct{}), }, nil } diff --git a/vigilante/submitter.go b/vigilante/submitter.go index 58a7e2f1..51a8b885 100644 --- a/vigilante/submitter.go +++ b/vigilante/submitter.go @@ -6,7 +6,6 @@ import ( "github.com/babylonchain/vigilante/btcclient" "github.com/babylonchain/vigilante/config" - "github.com/babylonchain/vigilante/netparams" ) type Submitter struct { @@ -15,7 +14,6 @@ type Submitter struct { // TODO: add Babylon client // TODO: add wallet client - btcParams *netparams.BTCParams // TODO: add Babylon parameters wg sync.WaitGroup @@ -24,10 +22,9 @@ type Submitter struct { quitMu sync.Mutex } -func NewSubmitter(cfg *config.SubmitterConfig, btcClient *btcclient.Client, btcParams *netparams.BTCParams) (*Submitter, error) { +func NewSubmitter(cfg *config.SubmitterConfig, btcClient *btcclient.Client) (*Submitter, error) { return &Submitter{ btcClient: btcClient, - btcParams: btcParams, quit: make(chan struct{}), }, nil } From e1e7cc85700bfe09c4fe324e95d6643fffcdaa44 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 14:30:50 +1000 Subject: [PATCH 26/28] server's access to submitter/reporter --- cmd/reporter/reporter.go | 2 +- cmd/submitter/submitter.go | 2 +- rpcserver/server.go | 14 ++++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index e659d41d..562d747a 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -56,7 +56,7 @@ func cmdFunc(cmd *cobra.Command, args []string) { panic(err) } // crete RPC server - server, err := rpcserver.New(&cfg.GRPC) + server, err := rpcserver.New(&cfg.GRPC, nil, reporter) if err != nil { panic(err) } diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index 294f06f3..b961170e 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -56,7 +56,7 @@ func cmdFunc(cmd *cobra.Command, args []string) { panic(err) } // crete RPC server - server, err := rpcserver.New(&cfg.GRPC) + server, err := rpcserver.New(&cfg.GRPC, submitter, nil) if err != nil { panic(err) } diff --git a/rpcserver/server.go b/rpcserver/server.go index b1a6eee3..ac84474a 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -21,6 +21,7 @@ import ( "net" "github.com/babylonchain/vigilante/config" + "github.com/babylonchain/vigilante/vigilante" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" @@ -28,11 +29,16 @@ import ( type Server struct { *grpc.Server - Cfg *config.GRPCConfig - // TODO: access to other system states exposed by RPC server + Cfg *config.GRPCConfig + Submitter *vigilante.Submitter + Reporter *vigilante.Reporter } -func New(cfg *config.GRPCConfig) (*Server, error) { +func New(cfg *config.GRPCConfig, submitter *vigilante.Submitter, reporter *vigilante.Reporter) (*Server, error) { + if submitter == nil && reporter == nil { + return nil, fmt.Errorf("At least one of submitter and reporter should be non-empty") + } + keyPair, err := openRPCKeyPair(cfg.OneTimeTLSKey, cfg.RPCKeyFile, cfg.RPCCertFile) if err != nil { return nil, fmt.Errorf("Open RPC key pair: %v", err) @@ -43,7 +49,7 @@ func New(cfg *config.GRPCConfig) (*Server, error) { reflection.Register(server) StartVigilanteService(server) - return &Server{server, cfg}, nil + return &Server{server, cfg, submitter, reporter}, nil } func (s *Server) Start() { From ead15ca57b34763a60f87d68fc4c5989dab03c6b Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Wed, 10 Aug 2022 20:11:53 +1000 Subject: [PATCH 27/28] graceful shutdown of vigilante --- cmd/reporter/reporter.go | 5 +++++ cmd/submitter/submitter.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/cmd/reporter/reporter.go b/cmd/reporter/reporter.go index 562d747a..c87a6778 100644 --- a/cmd/reporter/reporter.go +++ b/cmd/reporter/reporter.go @@ -78,6 +78,11 @@ func cmdFunc(cmd *cobra.Command, args []string) { server.Stop() log.Info("RPC server shutdown") }) + utils.AddInterruptHandler(func() { + log.Info("Stopping reporter...") + reporter.Stop() + log.Info("Reporter shutdown") + }) utils.AddInterruptHandler(func() { log.Info("Stopping BTC client...") btcClient.Stop() diff --git a/cmd/submitter/submitter.go b/cmd/submitter/submitter.go index b961170e..315bf951 100644 --- a/cmd/submitter/submitter.go +++ b/cmd/submitter/submitter.go @@ -78,6 +78,11 @@ func cmdFunc(cmd *cobra.Command, args []string) { server.Stop() log.Info("RPC server shutdown") }) + utils.AddInterruptHandler(func() { + log.Info("Stopping submitter...") + submitter.Stop() + log.Info("Submitter shutdown") + }) utils.AddInterruptHandler(func() { log.Info("Stopping BTC client...") btcClient.Stop() From bc83eb45fb5488baf94c7534af179a58a884eb1d Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Thu, 11 Aug 2022 10:34:08 +1000 Subject: [PATCH 28/28] prometheus middleware for grpc --- go.mod | 2 ++ go.sum | 13 +++++++++++++ rpcserver/server.go | 18 +++++++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 654bd048..903287ae 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/btcsuite/btcd v0.23.1 github.com/btcsuite/btcd/btcutil v1.1.1 github.com/btcsuite/btcwallet v0.15.1 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/prometheus/client_golang v1.13.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum index d1305e0f..d5b9492f 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -212,6 +213,10 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -234,6 +239,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= @@ -282,6 +288,7 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= @@ -366,6 +373,9 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -589,6 +599,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -597,6 +608,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -651,6 +663,7 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= diff --git a/rpcserver/server.go b/rpcserver/server.go index ac84474a..94e4075d 100644 --- a/rpcserver/server.go +++ b/rpcserver/server.go @@ -22,6 +22,8 @@ import ( "github.com/babylonchain/vigilante/config" "github.com/babylonchain/vigilante/vigilante" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" @@ -45,15 +47,25 @@ func New(cfg *config.GRPCConfig, submitter *vigilante.Submitter, reporter *vigil } creds := credentials.NewServerTLSFromCert(&keyPair) - server := grpc.NewServer(grpc.Creds(creds)) - reflection.Register(server) - StartVigilanteService(server) + server := grpc.NewServer( + grpc.Creds(creds), + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( + grpc_prometheus.StreamServerInterceptor, + )), + grpc.ChainUnaryInterceptor(grpc_middleware.ChainUnaryServer( + grpc_prometheus.UnaryServerInterceptor, + )), + ) + reflection.Register(server) // register reflection service + StartVigilanteService(server) // register our vigilante service + grpc_prometheus.Register(server) // register Prometheus metrics service return &Server{server, cfg, submitter, reporter}, nil } func (s *Server) Start() { // create listeners for endpoints + // TODO: negotiate API version listeners := []net.Listener{} for _, endpoint := range s.Cfg.Endpoints { lis, err := net.Listen("tcp", endpoint)