From 00bddf7540f8aeff7ddea077cf0ce89bc09ea483 Mon Sep 17 00:00:00 2001 From: Javed Khan Date: Fri, 2 Oct 2015 01:03:20 -0500 Subject: [PATCH] peer: Refactor peer code into its own package. This commit introduces package peer which contains peer related features refactored from peer.go. The following is an overview of the features the package provides: - Provides a basic concurrent safe bitcoin peer for handling bitcoin communications via the peer-to-peer protocol - Full duplex reading and writing of bitcoin protocol messages - Automatic handling of the initial handshake process including protocol version negotiation - Automatic periodic keep-alive pinging and pong responses - Asynchronous message queueing of outbound messages with optional channel for notification when the message is actually sent - Inventory message batching and send trickling with known inventory detection and avoidance - Ability to wait for shutdown/disconnect - Flexible peer configuration - Caller is responsible for creating outgoing connections and listening for incoming connections so they have flexibility to establish connections as they see fit (proxies, etc.) - User agent name and version - Bitcoin network - Service support signalling (full nodes, bloom filters, etc.) - Maximum supported protocol version - Ability to register callbacks for handling bitcoin protocol messages - Proper handling of bloom filter related commands when the caller does not specify the related flag to signal support - Disconnects the peer when the protocol version is high enough - Does not invoke the related callbacks for older protocol versions - Snapshottable peer statistics such as the total number of bytes read and written, the remote address, user agent, and negotiated protocol version - Helper functions for pushing addresses, getblocks, getheaders, and reject messages - These could all be sent manually via the standard message output function, but the helpers provide additional nice functionality such as duplicate filtering and address randomization - Full documentation with example usage - Test coverage In addition to the addition of the new package, btcd has been refactored to make use of the new package by extending the basic peer it provides to work with the blockmanager and server to act as a full node. The following is a broad overview of the changes to integrate the package: - The server is responsible for all connection management including persistent peers and banning - Callbacks for all messages that are required to implement a full node are registered - Logic necessary to serve data and behave as a full node is now in the callback registered with the peer Finally, the following peer-related things have been improved as a part of this refactor: - Don't log or send reject message due to peer disconnects - Remove trace logs that aren't particularly helpful - Finish an old TODO to switch the queue WaitGroup over to a channel - Improve various comments and fix some code consistency cases - Improve a few logging bits - Implement a most-recently-used nonce tracking for detecting self connections and generate a unique nonce for each peer --- blockmanager.go | 112 +- config.go | 9 + docs/README.md | 2 + log.go | 3 +- peer.go | 2083 ------------------- peer/README.md | 38 + peer/doc.go | 23 + peer/example_test.go | 113 + peer/export_test.go | 18 + peer/log.go | 241 +++ peer/log_test.go | 65 + mruinvmap.go => peer/mruinvmap.go | 31 +- mruinvmap_test.go => peer/mruinvmap_test.go | 6 +- peer/mrunoncemap.go | 129 ++ peer/mrunoncemap_test.go | 152 ++ peer/peer.go | 1870 +++++++++++++++++ peer/peer_test.go | 659 ++++++ rpcserver.go | 58 +- server.go | 1312 ++++++++++-- 19 files changed, 4559 insertions(+), 2365 deletions(-) delete mode 100644 peer.go create mode 100644 peer/README.md create mode 100644 peer/doc.go create mode 100644 peer/example_test.go create mode 100644 peer/export_test.go create mode 100644 peer/log.go create mode 100644 peer/log_test.go rename mruinvmap.go => peer/mruinvmap.go (82%) rename mruinvmap_test.go => peer/mruinvmap_test.go (97%) create mode 100644 peer/mrunoncemap.go create mode 100644 peer/mrunoncemap_test.go create mode 100644 peer/peer.go create mode 100644 peer/peer_test.go diff --git a/blockmanager.go b/blockmanager.go index 6e544dff..8f48199c 100644 --- a/blockmanager.go +++ b/blockmanager.go @@ -34,48 +34,51 @@ const ( blockDbNamePrefix = "blocks" ) +// zeroHash is the zero value hash (all zeros). It is defined as a convenience. +var zeroHash wire.ShaHash + // newPeerMsg signifies a newly connected peer to the block handler. type newPeerMsg struct { - peer *peer + peer *serverPeer } // blockMsg packages a bitcoin block message and the peer it came from together // so the block handler has access to that information. type blockMsg struct { block *btcutil.Block - peer *peer + peer *serverPeer } // invMsg packages a bitcoin inv message and the peer it came from together // so the block handler has access to that information. type invMsg struct { inv *wire.MsgInv - peer *peer + peer *serverPeer } // headersMsg packages a bitcoin headers message and the peer it came from // together so the block handler has access to that information. type headersMsg struct { headers *wire.MsgHeaders - peer *peer + peer *serverPeer } // donePeerMsg signifies a newly disconnected peer to the block handler. type donePeerMsg struct { - peer *peer + peer *serverPeer } // txMsg packages a bitcoin tx message and the peer it came from together // so the block handler has access to that information. type txMsg struct { tx *btcutil.Tx - peer *peer + peer *serverPeer } // getSyncPeerMsg is a message type to be sent across the message channel for // retrieving the current sync peer. type getSyncPeerMsg struct { - reply chan *peer + reply chan *serverPeer } // checkConnectBlockMsg is a message type to be sent across the message channel @@ -192,7 +195,7 @@ type blockManager struct { receivedLogBlocks int64 receivedLogTx int64 processingReqs bool - syncPeer *peer + syncPeer *serverPeer msgChan chan interface{} chainState chainState wg sync.WaitGroup @@ -289,11 +292,11 @@ func (b *blockManager) startSync(peers *list.List) { return } - var bestPeer *peer + var bestPeer *serverPeer var enext *list.Element for e := peers.Front(); e != nil; e = enext { enext = e.Next() - p := e.Value.(*peer) + sp := e.Value.(*serverPeer) // Remove sync candidate peers that are no longer candidates due // to passing their latest known block. NOTE: The < is @@ -301,14 +304,14 @@ func (b *blockManager) startSync(peers *list.List) { // doesn't have a later block when it's equal, it will likely // have one soon so it is a reasonable choice. It also allows // the case where both are at 0 such as during regression test. - if p.lastBlock < int32(height) { + if sp.LastBlock() < int32(height) { peers.Remove(e) continue } // TODO(davec): Use a better algorithm to choose the best peer. // For now, just pick the first available candidate. - bestPeer = p + bestPeer = sp } // Start syncing from the best peer if one was selected. @@ -321,7 +324,7 @@ func (b *blockManager) startSync(peers *list.List) { } bmgrLog.Infof("Syncing to block height %d from peer %v", - bestPeer.lastBlock, bestPeer.addr) + bestPeer.LastBlock(), bestPeer.Addr()) // When the current height is less than a known checkpoint we // can use block headers to learn about which blocks comprise @@ -347,7 +350,7 @@ func (b *blockManager) startSync(peers *list.List) { b.headersFirstMode = true bmgrLog.Infof("Downloading headers for blocks %d to "+ "%d from peer %s", height+1, - b.nextCheckpoint.Height, bestPeer.addr) + b.nextCheckpoint.Height, bestPeer.Addr()) } else { bestPeer.PushGetBlocksMsg(locator, &zeroHash) } @@ -359,14 +362,14 @@ func (b *blockManager) startSync(peers *list.List) { // isSyncCandidate returns whether or not the peer is a candidate to consider // syncing from. -func (b *blockManager) isSyncCandidate(p *peer) bool { +func (b *blockManager) isSyncCandidate(sp *serverPeer) bool { // Typically a peer is not a candidate for sync if it's not a full node, // however regression test is special in that the regression tool is // not a full node and still needs to be considered a sync candidate. if cfg.RegressionTest { // The peer is not a candidate if it's not coming from localhost // or the hostname can't be determined for some reason. - host, _, err := net.SplitHostPort(p.addr) + host, _, err := net.SplitHostPort(sp.Addr()) if err != nil { return false } @@ -376,7 +379,7 @@ func (b *blockManager) isSyncCandidate(p *peer) bool { } } else { // The peer is not a candidate for sync if it's not a full node. - if p.services&wire.SFNodeNetwork != wire.SFNodeNetwork { + if sp.Services()&wire.SFNodeNetwork != wire.SFNodeNetwork { return false } } @@ -388,21 +391,21 @@ func (b *blockManager) isSyncCandidate(p *peer) bool { // handleNewPeerMsg deals with new peers that have signalled they may // be considered as a sync peer (they have already successfully negotiated). It // also starts syncing if needed. It is invoked from the syncHandler goroutine. -func (b *blockManager) handleNewPeerMsg(peers *list.List, p *peer) { +func (b *blockManager) handleNewPeerMsg(peers *list.List, sp *serverPeer) { // Ignore if in the process of shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { return } - bmgrLog.Infof("New valid peer %s (%s)", p, p.userAgent) + bmgrLog.Infof("New valid peer %s (%s)", sp, sp.UserAgent()) // Ignore the peer if it's not a sync candidate. - if !b.isSyncCandidate(p) { + if !b.isSyncCandidate(sp) { return } // Add the peer as a candidate to sync from. - peers.PushBack(p) + peers.PushBack(sp) // Start syncing by choosing the best candidate if needed. b.startSync(peers) @@ -412,20 +415,20 @@ func (b *blockManager) handleNewPeerMsg(peers *list.List, p *peer) { // removes the peer as a candidate for syncing and in the case where it was // the current sync peer, attempts to select a new best peer to sync from. It // is invoked from the syncHandler goroutine. -func (b *blockManager) handleDonePeerMsg(peers *list.List, p *peer) { +func (b *blockManager) handleDonePeerMsg(peers *list.List, sp *serverPeer) { // Remove the peer from the list of candidate peers. for e := peers.Front(); e != nil; e = e.Next() { - if e.Value == p { + if e.Value == sp { peers.Remove(e) break } } - bmgrLog.Infof("Lost peer %s", p) + bmgrLog.Infof("Lost peer %s", sp) // Remove requested transactions from the global map so that they will // be fetched from elsewhere next time we get an inv. - for k := range p.requestedTxns { + for k := range sp.requestedTxns { delete(b.requestedTxns, k) } @@ -433,14 +436,14 @@ func (b *blockManager) handleDonePeerMsg(peers *list.List, p *peer) { // fetched from elsewhere next time we get an inv. // TODO(oga) we could possibly here check which peers have these blocks // and request them now to speed things up a little. - for k := range p.requestedBlocks { + for k := range sp.requestedBlocks { delete(b.requestedBlocks, k) } // Attempt to find a new peer to sync from if the quitting peer is the // sync peer. Also, reset the headers-first state if in headers-first // mode so - if b.syncPeer != nil && b.syncPeer == p { + if b.syncPeer != nil && b.syncPeer == sp { b.syncPeer = nil if b.headersFirstMode { // This really shouldn't fail. We have a fairly @@ -472,7 +475,7 @@ func (b *blockManager) handleTxMsg(tmsg *txMsg) { // Process the transaction to include validation, insertion in the // memory pool, orphan handling, etc. allowOrphans := cfg.MaxOrphanTxs > 0 - err := tmsg.peer.server.txMemPool.ProcessTransaction(tmsg.tx, + err := b.server.txMemPool.ProcessTransaction(tmsg.tx, allowOrphans, true) // Remove transaction from request maps. Either the mempool/chain @@ -524,7 +527,7 @@ func (b *blockManager) current() bool { // TODO(oga) we can get chain to return the height of each block when we // parse an orphan, which would allow us to update the height of peers // from what it was at initial handshake. - if err != nil || height < b.syncPeer.lastBlock { + if err != nil || height < b.syncPeer.LastBlock() { return false } return true @@ -534,7 +537,7 @@ func (b *blockManager) current() bool { func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { // If we didn't ask for this block then the peer is misbehaving. blockSha := bmsg.block.Sha() - if _, ok := bmsg.peer.requestedBlocks[*blockSha]; !ok { + if _, exists := bmsg.peer.requestedBlocks[*blockSha]; !exists { // The regression test intentionally sends some blocks twice // to test duplicate block insertion fails. Don't disconnect // the peer or ignore the block when we're in regression test @@ -542,7 +545,7 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { // duplicate blocks. if !cfg.RegressionTest { bmgrLog.Warnf("Got unrequested block %v from %s -- "+ - "disconnecting", blockSha, bmsg.peer.addr) + "disconnecting", blockSha, bmsg.peer.Addr()) bmsg.peer.Disconnect() return } @@ -711,12 +714,12 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { err := bmsg.peer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash) if err != nil { bmgrLog.Warnf("Failed to send getheaders message to "+ - "peer %s: %v", bmsg.peer.addr, err) + "peer %s: %v", bmsg.peer.Addr(), err) return } bmgrLog.Infof("Downloading headers for blocks %d to %d from "+ "peer %s", prevHeight+1, b.nextCheckpoint.Height, - b.syncPeer.addr) + b.syncPeer.Addr()) return } @@ -730,7 +733,7 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { err = bmsg.peer.PushGetBlocksMsg(locator, &zeroHash) if err != nil { bmgrLog.Warnf("Failed to send getblocks message to peer %s: %v", - bmsg.peer.addr, err) + bmsg.peer.Addr(), err) return } } @@ -786,7 +789,7 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) { numHeaders := len(msg.Headers) if !b.headersFirstMode { bmgrLog.Warnf("Got %d unrequested headers from %s -- "+ - "disconnecting", numHeaders, hmsg.peer.addr) + "disconnecting", numHeaders, hmsg.peer.Addr()) hmsg.peer.Disconnect() return } @@ -826,7 +829,7 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) { } else { bmgrLog.Warnf("Received block header that does not "+ "properly connect to the chain from peer %s "+ - "-- disconnecting", hmsg.peer.addr) + "-- disconnecting", hmsg.peer.Addr()) hmsg.peer.Disconnect() return } @@ -843,7 +846,7 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) { "%s from peer %s does NOT match "+ "expected checkpoint hash of %s -- "+ "disconnecting", node.height, - node.sha, hmsg.peer.addr, + node.sha, hmsg.peer.Addr(), b.nextCheckpoint.Hash) hmsg.peer.Disconnect() return @@ -874,7 +877,7 @@ func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) { err := hmsg.peer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash) if err != nil { bmgrLog.Warnf("Failed to send getheaders message to "+ - "peer %s: %v", hmsg.peer.addr, err) + "peer %s: %v", hmsg.peer.Addr(), err) return } } @@ -1270,69 +1273,68 @@ func (b *blockManager) handleNotifyMsg(notification *blockchain.Notification) { } // NewPeer informs the block manager of a newly active peer. -func (b *blockManager) NewPeer(p *peer) { +func (b *blockManager) NewPeer(sp *serverPeer) { // Ignore if we are shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { return } - - b.msgChan <- &newPeerMsg{peer: p} + b.msgChan <- &newPeerMsg{peer: sp} } // QueueTx adds the passed transaction message and peer to the block handling // queue. -func (b *blockManager) QueueTx(tx *btcutil.Tx, p *peer) { +func (b *blockManager) QueueTx(tx *btcutil.Tx, sp *serverPeer) { // Don't accept more transactions if we're shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { - p.txProcessed <- struct{}{} + sp.txProcessed <- struct{}{} return } - b.msgChan <- &txMsg{tx: tx, peer: p} + b.msgChan <- &txMsg{tx: tx, peer: sp} } // QueueBlock adds the passed block message and peer to the block handling queue. -func (b *blockManager) QueueBlock(block *btcutil.Block, p *peer) { +func (b *blockManager) QueueBlock(block *btcutil.Block, sp *serverPeer) { // Don't accept more blocks if we're shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { - p.blockProcessed <- struct{}{} + sp.blockProcessed <- struct{}{} return } - b.msgChan <- &blockMsg{block: block, peer: p} + b.msgChan <- &blockMsg{block: block, peer: sp} } // QueueInv adds the passed inv message and peer to the block handling queue. -func (b *blockManager) QueueInv(inv *wire.MsgInv, p *peer) { +func (b *blockManager) QueueInv(inv *wire.MsgInv, sp *serverPeer) { // No channel handling here because peers do not need to block on inv // messages. if atomic.LoadInt32(&b.shutdown) != 0 { return } - b.msgChan <- &invMsg{inv: inv, peer: p} + b.msgChan <- &invMsg{inv: inv, peer: sp} } // QueueHeaders adds the passed headers message and peer to the block handling // queue. -func (b *blockManager) QueueHeaders(headers *wire.MsgHeaders, p *peer) { +func (b *blockManager) QueueHeaders(headers *wire.MsgHeaders, sp *serverPeer) { // No channel handling here because peers do not need to block on // headers messages. if atomic.LoadInt32(&b.shutdown) != 0 { return } - b.msgChan <- &headersMsg{headers: headers, peer: p} + b.msgChan <- &headersMsg{headers: headers, peer: sp} } // DonePeer informs the blockmanager that a peer has disconnected. -func (b *blockManager) DonePeer(p *peer) { +func (b *blockManager) DonePeer(sp *serverPeer) { // Ignore if we are shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { return } - b.msgChan <- &donePeerMsg{peer: p} + b.msgChan <- &donePeerMsg{peer: sp} } // Start begins the core block handler which processes block and inv messages. @@ -1363,8 +1365,8 @@ func (b *blockManager) Stop() error { } // SyncPeer returns the current sync peer. -func (b *blockManager) SyncPeer() *peer { - reply := make(chan *peer) +func (b *blockManager) SyncPeer() *serverPeer { + reply := make(chan *serverPeer) b.msgChan <- getSyncPeerMsg{reply: reply} return <-reply } diff --git a/config.go b/config.go index dd758937..ba8a3caa 100644 --- a/config.go +++ b/config.go @@ -62,6 +62,15 @@ var ( // to parse and execute service commands specified via the -s flag. var runServiceCommand func(string) error +// minUint32 is a helper function to return the minimum of two uint32s. +// This avoids a math import and the need to cast to floats. +func minUint32(a, b uint32) uint32 { + if a < b { + return a + } + return b +} + // config defines the configuration options for btcd. // // See loadConfig for details on the configuration load process. diff --git a/docs/README.md b/docs/README.md index 5f855846..39dc17b2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -203,6 +203,8 @@ information. for the underlying JSON-RPC command and return values * [wire](https://github.com/btcsuite/btcd/tree/master/wire) - Implements the Bitcoin wire protocol + * [peer](https://github.com/btcsuite/btcd/tree/master/peer) - + Provides a common base for creating and managing Bitcoin network peers. * [blockchain](https://github.com/btcsuite/btcd/tree/master/blockchain) - Implements Bitcoin block handling and chain selection rules * [txscript](https://github.com/btcsuite/btcd/tree/master/txscript) - diff --git a/log.go b/log.go index 058fcbf9..ce3adc05 100644 --- a/log.go +++ b/log.go @@ -11,9 +11,9 @@ import ( "time" "github.com/btcsuite/btcd/addrmgr" - "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/database" + "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" @@ -118,6 +118,7 @@ func useLogger(subsystemID string, logger btclog.Logger) { case "PEER": peerLog = logger + peer.UseLogger(logger) case "RPCS": rpcsLog = logger diff --git a/peer.go b/peer.go deleted file mode 100644 index 6e846223..00000000 --- a/peer.go +++ /dev/null @@ -1,2083 +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 main - -import ( - "bytes" - "container/list" - "fmt" - "io" - prand "math/rand" - "net" - "strconv" - "sync" - "sync/atomic" - "time" - - "github.com/btcsuite/btcd/addrmgr" - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/database" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcutil/bloom" - "github.com/btcsuite/go-socks/socks" - "github.com/davecgh/go-spew/spew" -) - -const ( - // maxProtocolVersion is the max protocol version the peer supports. - maxProtocolVersion = 70011 - - // outputBufferSize is the number of elements the output channels use. - outputBufferSize = 50 - - // invTrickleSize is the maximum amount of inventory to send in a single - // message when trickling inventory to remote peers. - maxInvTrickleSize = 1000 - - // maxKnownInventory is the maximum number of items to keep in the known - // inventory cache. - maxKnownInventory = 1000 - - // negotiateTimeoutSeconds is the number of seconds of inactivity before - // we timeout a peer that hasn't completed the initial version - // negotiation. - negotiateTimeoutSeconds = 30 - - // idleTimeoutMinutes is the number of minutes of inactivity before - // we time out a peer. - idleTimeoutMinutes = 5 - - // pingTimeoutMinutes is the number of minutes since we last sent a - // message requiring a reply before we will ping a host. - pingTimeoutMinutes = 2 - - // connectionRetryInterval is the base amount of time to wait in between - // retries when connecting to persistent peers. It is adjusted by the - // number of retries such that there is a retry backoff. - connectionRetryInterval = time.Second * 10 - - // maxConnectionRetryInterval is the max amount of time retrying of a - // persistent peer is allowed to grow to. This is necessary since the - // retry logic uses a backoff mechanism which increases the interval - // base done the number of retries that have been done. - maxConnectionRetryInterval = time.Minute * 5 -) - -var ( - // nodeCount is the total number of peer connections made since startup - // and is used to assign an id to a peer. - nodeCount int32 - - // userAgentName is the user agent name and is used to help identify - // ourselves to other bitcoin peers. - userAgentName = "btcd" - - // userAgentVersion is the user agent version and is used to help - // identify ourselves to other bitcoin peers. - userAgentVersion = fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch) -) - -// zeroHash is the zero value hash (all zeros). It is defined as a convenience. -var zeroHash wire.ShaHash - -// minUint32 is a helper function to return the minimum of two uint32s. -// This avoids a math import and the need to cast to floats. -func minUint32(a, b uint32) uint32 { - if a < b { - return a - } - return b -} - -// newNetAddress attempts to extract the IP address and port from the passed -// net.Addr interface and create a bitcoin NetAddress structure using that -// information. -func newNetAddress(addr net.Addr, services wire.ServiceFlag) (*wire.NetAddress, error) { - // addr will be a net.TCPAddr when not using a proxy. - if tcpAddr, ok := addr.(*net.TCPAddr); ok { - ip := tcpAddr.IP - port := uint16(tcpAddr.Port) - na := wire.NewNetAddressIPPort(ip, port, services) - return na, nil - } - - // addr will be a socks.ProxiedAddr when using a proxy. - if proxiedAddr, ok := addr.(*socks.ProxiedAddr); ok { - ip := net.ParseIP(proxiedAddr.Host) - if ip == nil { - ip = net.ParseIP("0.0.0.0") - } - port := uint16(proxiedAddr.Port) - na := wire.NewNetAddressIPPort(ip, port, services) - return na, nil - } - - // For the most part, addr should be one of the two above cases, but - // to be safe, fall back to trying to parse the information from the - // address string as a last resort. - host, portStr, err := net.SplitHostPort(addr.String()) - if err != nil { - return nil, err - } - ip := net.ParseIP(host) - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return nil, err - } - na := wire.NewNetAddressIPPort(ip, uint16(port), services) - return na, nil -} - -// outMsg is used to house a message to be sent along with a channel to signal -// when the message has been sent (or won't be sent due to things such as -// shutdown) -type outMsg struct { - msg wire.Message - doneChan chan struct{} -} - -// peer provides a bitcoin peer for handling bitcoin communications. The -// overall data flow is split into 3 goroutines and a separate block manager. -// Inbound messages are read via the inHandler goroutine and generally -// dispatched to their own handler. For inbound data-related messages such as -// blocks, transactions, and inventory, the data is passed on to the block -// manager to handle it. Outbound messages are queued via QueueMessage or -// QueueInventory. QueueMessage is intended for all messages, including -// responses to data such as blocks and transactions. QueueInventory, on the -// other hand, is only intended for relaying inventory as it employs a trickling -// mechanism to batch the inventory together. The data flow for outbound -// messages uses two goroutines, queueHandler and outHandler. The first, -// queueHandler, is used as a way for external entities (mainly block manager) -// to queue messages quickly regardless of whether the peer is currently -// sending or not. It acts as the traffic cop between the external world and -// the actual goroutine which writes to the network socket. In addition, the -// peer contains several functions which are of the form pushX, that are used -// to push messages to the peer. Internally they use QueueMessage. -type peer struct { - server *server - btcnet wire.BitcoinNet - started int32 - connected int32 - disconnect int32 // only to be used atomically - conn net.Conn - addr string - na *wire.NetAddress - id int32 - inbound bool - persistent bool - knownAddresses map[string]struct{} - knownInventory *MruInventoryMap - knownInvMutex sync.Mutex - requestedTxns map[wire.ShaHash]struct{} // owned by blockmanager - requestedBlocks map[wire.ShaHash]struct{} // owned by blockmanager - retryCount int64 - prevGetBlocksBegin *wire.ShaHash // owned by blockmanager - prevGetBlocksStop *wire.ShaHash // owned by blockmanager - prevGetHdrsBegin *wire.ShaHash // owned by blockmanager - prevGetHdrsStop *wire.ShaHash // owned by blockmanager - requestQueue []*wire.InvVect - filter *bloom.Filter - relayMtx sync.Mutex - disableRelayTx bool - continueHash *wire.ShaHash - outputQueue chan outMsg - sendQueue chan outMsg - sendDoneQueue chan struct{} - queueWg sync.WaitGroup // TODO(oga) wg -> single use channel? - outputInvChan chan *wire.InvVect - txProcessed chan struct{} - blockProcessed chan struct{} - quit chan struct{} - StatsMtx sync.Mutex // protects all statistics below here. - versionKnown bool - protocolVersion uint32 - versionSent bool - verAckReceived bool - services wire.ServiceFlag - timeOffset int64 - timeConnected time.Time - lastSend time.Time - lastRecv time.Time - bytesReceived uint64 - bytesSent uint64 - userAgent string - startingHeight int32 - lastBlock int32 - lastAnnouncedBlock *wire.ShaHash - lastPingNonce uint64 // Set to nonce if we have a pending ping. - lastPingTime time.Time // Time we sent last ping. - lastPingMicros int64 // Time for last ping to return. -} - -// String returns the peer's address and directionality as a human-readable -// string. -func (p *peer) String() string { - return fmt.Sprintf("%s (%s)", p.addr, directionString(p.inbound)) -} - -// isKnownInventory returns whether or not the peer is known to have the passed -// inventory. It is safe for concurrent access. -func (p *peer) isKnownInventory(invVect *wire.InvVect) bool { - p.knownInvMutex.Lock() - defer p.knownInvMutex.Unlock() - - if p.knownInventory.Exists(invVect) { - return true - } - return false -} - -// UpdateLastBlockHeight updates the last known block for the peer. It is safe -// for concurrent access. -func (p *peer) UpdateLastBlockHeight(newHeight int32) { - p.StatsMtx.Lock() - defer p.StatsMtx.Unlock() - - peerLog.Tracef("Updating last block height of peer %v from %v to %v", - p.addr, p.lastBlock, newHeight) - p.lastBlock = int32(newHeight) -} - -// UpdateLastAnnouncedBlock updates meta-data about the last block sha this -// peer is known to have announced. It is safe for concurrent access. -func (p *peer) UpdateLastAnnouncedBlock(blkSha *wire.ShaHash) { - p.StatsMtx.Lock() - defer p.StatsMtx.Unlock() - - peerLog.Tracef("Updating last blk for peer %v, %v", p.addr, blkSha) - p.lastAnnouncedBlock = blkSha -} - -// AddKnownInventory adds the passed inventory to the cache of known inventory -// for the peer. It is safe for concurrent access. -func (p *peer) AddKnownInventory(invVect *wire.InvVect) { - p.knownInvMutex.Lock() - defer p.knownInvMutex.Unlock() - - p.knownInventory.Add(invVect) -} - -// VersionKnown returns the whether or not the version of a peer is known locally. -// It is safe for concurrent access. -func (p *peer) VersionKnown() bool { - p.StatsMtx.Lock() - defer p.StatsMtx.Unlock() - - return p.versionKnown -} - -// ProtocolVersion returns the peer protocol version in a manner that is safe -// for concurrent access. -func (p *peer) ProtocolVersion() uint32 { - p.StatsMtx.Lock() - defer p.StatsMtx.Unlock() - - return p.protocolVersion -} - -// RelayTxDisabled returns whether or not relaying of transactions is disabled. -// It is safe for concurrent access. -func (p *peer) RelayTxDisabled() bool { - p.relayMtx.Lock() - defer p.relayMtx.Unlock() - - return p.disableRelayTx -} - -// pushVersionMsg sends a version message to the connected peer using the -// current state. -func (p *peer) pushVersionMsg() error { - _, blockNum, err := p.server.db.NewestSha() - if err != nil { - return err - } - - theirNa := p.na - - // If we are behind a proxy and the connection comes from the proxy then - // we return an unroutable address as their address. This is to prevent - // leaking the tor proxy address. - if cfg.Proxy != "" { - proxyaddress, _, err := net.SplitHostPort(cfg.Proxy) - // invalid proxy means poorly configured, be on the safe side. - if err != nil || p.na.IP.String() == proxyaddress { - theirNa = &wire.NetAddress{ - Timestamp: time.Now(), - IP: net.IP([]byte{0, 0, 0, 0}), - } - } - } - - // Version message. - msg := wire.NewMsgVersion( - p.server.addrManager.GetBestLocalAddress(p.na), theirNa, - p.server.nonce, int32(blockNum)) - msg.AddUserAgent(userAgentName, userAgentVersion) - - // XXX: bitcoind appears to always enable the full node services flag - // of the remote peer netaddress field in the version message regardless - // of whether it knows it supports it or not. Also, bitcoind sets - // the services field of the local peer to 0 regardless of support. - // - // Realistically, this should be set as follows: - // - For outgoing connections: - // - Set the local netaddress services to what the local peer - // actually supports - // - Set the remote netaddress services to 0 to indicate no services - // as they are still unknown - // - For incoming connections: - // - Set the local netaddress services to what the local peer - // actually supports - // - Set the remote netaddress services to the what was advertised by - // by the remote peer in its version message - msg.AddrYou.Services = wire.SFNodeNetwork - - // Advertise our supported services. - msg.Services = p.server.services - - // Advertise our max supported protocol version. - msg.ProtocolVersion = maxProtocolVersion - - p.QueueMessage(msg, nil) - return nil -} - -// updateAddresses potentially adds addresses to the address manager and -// requests known addresses from the remote peer depending on whether the peer -// is an inbound or outbound peer and other factors such as address routability -// and the negotiated protocol version. -func (p *peer) updateAddresses(msg *wire.MsgVersion) { - // Outbound connections. - if !p.inbound { - // TODO(davec): Only do this if not doing the initial block - // download and the local address is routable. - if !cfg.DisableListen /* && isCurrent? */ { - // Get address that best matches. - lna := p.server.addrManager.GetBestLocalAddress(p.na) - if addrmgr.IsRoutable(lna) { - addresses := []*wire.NetAddress{lna} - p.pushAddrMsg(addresses) - } - } - - // Request known addresses if the server address manager needs - // more and the peer has a protocol version new enough to - // include a timestamp with addresses. - hasTimestamp := p.ProtocolVersion() >= - wire.NetAddressTimeVersion - if p.server.addrManager.NeedMoreAddresses() && hasTimestamp { - p.QueueMessage(wire.NewMsgGetAddr(), nil) - } - - // Mark the address as a known good address. - p.server.addrManager.Good(p.na) - } else { - // A peer might not be advertising the same address that it - // actually connected from. One example of why this can happen - // is with NAT. Only add the address to the address manager if - // the addresses agree. - if addrmgr.NetAddressKey(&msg.AddrMe) == addrmgr.NetAddressKey(p.na) { - p.server.addrManager.AddAddress(p.na, p.na) - p.server.addrManager.Good(p.na) - } - } -} - -// handleVersionMsg is invoked when a peer receives a version bitcoin message -// and is used to negotiate the protocol version details as well as kick start -// the communications. -func (p *peer) handleVersionMsg(msg *wire.MsgVersion) { - // Detect self connections. - if msg.Nonce == p.server.nonce { - peerLog.Debugf("Disconnecting peer connected to self %s", p) - p.Disconnect() - return - } - - // Notify and disconnect clients that have a protocol version that is - // too old. - if msg.ProtocolVersion < int32(wire.MultipleAddressVersion) { - // Send a reject message indicating the protocol version is - // obsolete and wait for the message to be sent before - // disconnecting. - reason := fmt.Sprintf("protocol version must be %d or greater", - wire.MultipleAddressVersion) - p.PushRejectMsg(msg.Command(), wire.RejectObsolete, reason, - nil, true) - p.Disconnect() - return - } - - // Updating a bunch of stats. - p.StatsMtx.Lock() - - // Limit to one version message per peer. - if p.versionKnown { - p.logError("Only one version message per peer is allowed %s.", - p) - p.StatsMtx.Unlock() - - // Send an reject message indicating the version message was - // incorrectly sent twice and wait for the message to be sent - // before disconnecting. - p.PushRejectMsg(msg.Command(), wire.RejectDuplicate, - "duplicate version message", nil, true) - - p.Disconnect() - return - } - - // Negotiate the protocol version. - p.protocolVersion = minUint32(p.protocolVersion, uint32(msg.ProtocolVersion)) - p.versionKnown = true - peerLog.Debugf("Negotiated protocol version %d for peer %s", - p.protocolVersion, p) - p.lastBlock = msg.LastBlock - p.startingHeight = msg.LastBlock - - // Set the supported services for the peer to what the remote peer - // advertised. - p.services = msg.Services - - // Set the remote peer's user agent. - p.userAgent = msg.UserAgent - - // Set the peer's time offset. - p.timeOffset = msg.Timestamp.Unix() - time.Now().Unix() - - // Set the peer's ID. - p.id = atomic.AddInt32(&nodeCount, 1) - - p.StatsMtx.Unlock() - - // Choose whether or not to relay transactions before a filter command - // is received. - p.relayMtx.Lock() - p.disableRelayTx = msg.DisableRelayTx - p.relayMtx.Unlock() - - // Inbound connections. - if p.inbound { - // Set up a NetAddress for the peer to be used with AddrManager. - // We only do this inbound because outbound set this up - // at connection time and no point recomputing. - na, err := newNetAddress(p.conn.RemoteAddr(), p.services) - if err != nil { - p.logError("Can't get remote address: %v", err) - p.Disconnect() - return - } - p.na = na - - // Send version. - err = p.pushVersionMsg() - if err != nil { - p.logError("Can't send version message to %s: %v", - p, err) - p.Disconnect() - return - } - } - - // Send verack. - p.QueueMessage(wire.NewMsgVerAck(), nil) - - // Update the address manager and request known addresses from the - // remote peer for outbound connections. This is skipped when running - // on the simulation test network since it is only intended to connect - // to specified peers and actively avoids advertising and connecting to - // discovered peers. - if !cfg.SimNet { - p.updateAddresses(msg) - } - - // Add the remote peer time as a sample for creating an offset against - // the local clock to keep the network time in sync. - p.server.timeSource.AddTimeSample(p.addr, msg.Timestamp) - - // Signal the block manager this peer is a new sync candidate. - p.server.blockManager.NewPeer(p) - - // TODO: Relay alerts. -} - -// pushTxMsg sends a tx message for the provided transaction hash to the -// connected peer. An error is returned if the transaction hash is not known. -func (p *peer) pushTxMsg(sha *wire.ShaHash, doneChan, waitChan chan struct{}) error { - // Attempt to fetch the requested transaction from the pool. A - // call could be made to check for existence first, but simply trying - // to fetch a missing transaction results in the same behavior. - tx, err := p.server.txMemPool.FetchTransaction(sha) - if err != nil { - peerLog.Tracef("Unable to fetch tx %v from transaction "+ - "pool: %v", sha, err) - - if doneChan != nil { - doneChan <- struct{}{} - } - return err - } - - // Once we have fetched data wait for any previous operation to finish. - if waitChan != nil { - <-waitChan - } - - p.QueueMessage(tx.MsgTx(), doneChan) - - return nil -} - -// pushBlockMsg sends a block message for the provided block hash to the -// connected peer. An error is returned if the block hash is not known. -func (p *peer) pushBlockMsg(sha *wire.ShaHash, doneChan, waitChan chan struct{}) error { - blk, err := p.server.db.FetchBlockBySha(sha) - if err != nil { - peerLog.Tracef("Unable to fetch requested block sha %v: %v", - sha, err) - - if doneChan != nil { - doneChan <- struct{}{} - } - return err - } - - // Once we have fetched data wait for any previous operation to finish. - if waitChan != nil { - <-waitChan - } - - // We only send the channel for this message if we aren't sending - // an inv straight after. - var dc chan struct{} - sendInv := p.continueHash != nil && p.continueHash.IsEqual(sha) - if !sendInv { - dc = doneChan - } - p.QueueMessage(blk.MsgBlock(), dc) - - // When the peer requests the final block that was advertised in - // response to a getblocks message which requested more blocks than - // would fit into a single message, send it a new inventory message - // to trigger it to issue another getblocks message for the next - // batch of inventory. - if p.continueHash != nil && p.continueHash.IsEqual(sha) { - hash, _, err := p.server.db.NewestSha() - if err == nil { - invMsg := wire.NewMsgInvSizeHint(1) - iv := wire.NewInvVect(wire.InvTypeBlock, hash) - invMsg.AddInvVect(iv) - p.QueueMessage(invMsg, doneChan) - p.continueHash = nil - } else if doneChan != nil { - doneChan <- struct{}{} - } - } - return nil -} - -// pushMerkleBlockMsg sends a merkleblock message for the provided block hash to -// the connected peer. Since a merkle block requires the peer to have a filter -// loaded, this call will simply be ignored if there is no filter loaded. An -// error is returned if the block hash is not known. -func (p *peer) pushMerkleBlockMsg(sha *wire.ShaHash, doneChan, waitChan chan struct{}) error { - // Do not send a response if the peer doesn't have a filter loaded. - if !p.filter.IsLoaded() { - if doneChan != nil { - doneChan <- struct{}{} - } - return nil - } - - blk, err := p.server.db.FetchBlockBySha(sha) - if err != nil { - peerLog.Tracef("Unable to fetch requested block sha %v: %v", - sha, err) - - if doneChan != nil { - doneChan <- struct{}{} - } - return err - } - - // Generate a merkle block by filtering the requested block according - // to the filter for the peer. - merkle, matchedTxIndices := bloom.NewMerkleBlock(blk, p.filter) - - // Once we have fetched data wait for any previous operation to finish. - if waitChan != nil { - <-waitChan - } - - // Send the merkleblock. Only send the done channel with this message - // if no transactions will be sent afterwards. - var dc chan struct{} - if len(matchedTxIndices) == 0 { - dc = doneChan - } - p.QueueMessage(merkle, dc) - - // Finally, send any matched transactions. - blkTransactions := blk.MsgBlock().Transactions - for i, txIndex := range matchedTxIndices { - // Only send the done channel on the final transaction. - var dc chan struct{} - if i == len(matchedTxIndices)-1 { - dc = doneChan - } - if txIndex < uint32(len(blkTransactions)) { - p.QueueMessage(blkTransactions[txIndex], dc) - } - } - - return nil -} - -// PushGetBlocksMsg sends a getblocks message for the provided block locator -// and stop hash. It will ignore back-to-back duplicate requests. -func (p *peer) PushGetBlocksMsg(locator blockchain.BlockLocator, stopHash *wire.ShaHash) error { - // Extract the begin hash from the block locator, if one was specified, - // to use for filtering duplicate getblocks requests. - // request. - var beginHash *wire.ShaHash - if len(locator) > 0 { - beginHash = locator[0] - } - - // Filter duplicate getblocks requests. - if p.prevGetBlocksStop != nil && p.prevGetBlocksBegin != nil && - beginHash != nil && stopHash.IsEqual(p.prevGetBlocksStop) && - beginHash.IsEqual(p.prevGetBlocksBegin) { - - peerLog.Tracef("Filtering duplicate [getblocks] with begin "+ - "hash %v, stop hash %v", beginHash, stopHash) - return nil - } - - // Construct the getblocks request and queue it to be sent. - msg := wire.NewMsgGetBlocks(stopHash) - for _, hash := range locator { - err := msg.AddBlockLocatorHash(hash) - if err != nil { - return err - } - } - p.QueueMessage(msg, nil) - - // Update the previous getblocks request information for filtering - // duplicates. - p.prevGetBlocksBegin = beginHash - p.prevGetBlocksStop = stopHash - return nil -} - -// PushGetHeadersMsg sends a getblocks message for the provided block locator -// and stop hash. It will ignore back-to-back duplicate requests. -func (p *peer) PushGetHeadersMsg(locator blockchain.BlockLocator, stopHash *wire.ShaHash) error { - // Extract the begin hash from the block locator, if one was specified, - // to use for filtering duplicate getheaders requests. - var beginHash *wire.ShaHash - if len(locator) > 0 { - beginHash = locator[0] - } - - // Filter duplicate getheaders requests. - if p.prevGetHdrsStop != nil && p.prevGetHdrsBegin != nil && - beginHash != nil && stopHash.IsEqual(p.prevGetHdrsStop) && - beginHash.IsEqual(p.prevGetHdrsBegin) { - - peerLog.Tracef("Filtering duplicate [getheaders] with begin "+ - "hash %v", beginHash) - return nil - } - - // Construct the getheaders request and queue it to be sent. - msg := wire.NewMsgGetHeaders() - msg.HashStop = *stopHash - for _, hash := range locator { - err := msg.AddBlockLocatorHash(hash) - if err != nil { - return err - } - } - p.QueueMessage(msg, nil) - - // Update the previous getheaders request information for filtering - // duplicates. - p.prevGetHdrsBegin = beginHash - p.prevGetHdrsStop = stopHash - return nil -} - -// PushRejectMsg sends a reject message for the provided command, reject code, -// and reject reason, and hash. The hash will only be used when the command -// is a tx or block and should be nil in other cases. The wait parameter will -// cause the function to block until the reject message has actually been sent. -func (p *peer) PushRejectMsg(command string, code wire.RejectCode, reason string, hash *wire.ShaHash, wait bool) { - // Don't bother sending the reject message if the protocol version - // is too low. - if p.VersionKnown() && p.ProtocolVersion() < wire.RejectVersion { - return - } - - msg := wire.NewMsgReject(command, code, reason) - if command == wire.CmdTx || command == wire.CmdBlock { - if hash == nil { - peerLog.Warnf("Sending a reject message for command "+ - "type %v which should have specified a hash "+ - "but does not", command) - hash = &zeroHash - } - msg.Hash = *hash - } - - // Send the message without waiting if the caller has not requested it. - if !wait { - p.QueueMessage(msg, nil) - return - } - - // Send the message and block until it has been sent before returning. - doneChan := make(chan struct{}, 1) - p.QueueMessage(msg, doneChan) - <-doneChan -} - -// handleMemPoolMsg is invoked when a peer receives a mempool bitcoin message. -// It creates and sends an inventory message with the contents of the memory -// pool up to the maximum inventory allowed per message. When the peer has a -// bloom filter loaded, the contents are filtered accordingly. -func (p *peer) handleMemPoolMsg(msg *wire.MsgMemPool) { - // Generate inventory message with the available transactions in the - // transaction memory pool. Limit it to the max allowed inventory - // per message. The the NewMsgInvSizeHint function automatically limits - // the passed hint to the maximum allowed, so it's safe to pass it - // without double checking it here. - txDescs := p.server.txMemPool.TxDescs() - invMsg := wire.NewMsgInvSizeHint(uint(len(txDescs))) - - for i, txDesc := range txDescs { - // Another thread might have removed the transaction from the - // pool since the initial query. - hash := txDesc.Tx.Sha() - if !p.server.txMemPool.IsTransactionInPool(hash) { - continue - } - - // Either add all transactions when there is no bloom filter, - // or only the transactions that match the filter when there is - // one. - if !p.filter.IsLoaded() || p.filter.MatchTxAndUpdate(txDesc.Tx) { - iv := wire.NewInvVect(wire.InvTypeTx, hash) - invMsg.AddInvVect(iv) - if i+1 >= wire.MaxInvPerMsg { - break - } - } - } - - // Send the inventory message if there is anything to send. - if len(invMsg.InvList) > 0 { - p.QueueMessage(invMsg, nil) - } -} - -// handleTxMsg is invoked when a peer receives a tx bitcoin message. It blocks -// until the bitcoin transaction has been fully processed. Unlock the block -// handler this does not serialize all transactions through a single thread -// transactions don't rely on the previous one in a linear fashion like blocks. -func (p *peer) handleTxMsg(msg *wire.MsgTx) { - // Add the transaction to the known inventory for the peer. - // Convert the raw MsgTx to a btcutil.Tx which provides some convenience - // methods and things such as hash caching. - tx := btcutil.NewTx(msg) - iv := wire.NewInvVect(wire.InvTypeTx, tx.Sha()) - p.AddKnownInventory(iv) - - // Queue the transaction up to be handled by the block manager and - // intentionally block further receives until the transaction is fully - // processed and known good or bad. This helps prevent a malicious peer - // from queueing up a bunch of bad transactions before disconnecting (or - // being disconnected) and wasting memory. - p.server.blockManager.QueueTx(tx, p) - <-p.txProcessed -} - -// handleBlockMsg is invoked when a peer receives a block bitcoin message. It -// blocks until the bitcoin block has been fully processed. -func (p *peer) handleBlockMsg(msg *wire.MsgBlock, buf []byte) { - // Convert the raw MsgBlock to a btcutil.Block which provides some - // convenience methods and things such as hash caching. - block := btcutil.NewBlockFromBlockAndBytes(msg, buf) - - // Add the block to the known inventory for the peer. - iv := wire.NewInvVect(wire.InvTypeBlock, block.Sha()) - p.AddKnownInventory(iv) - - // Queue the block up to be handled by the block - // manager and intentionally block further receives - // until the bitcoin block is fully processed and known - // good or bad. This helps prevent a malicious peer - // from queueing up a bunch of bad blocks before - // disconnecting (or being disconnected) and wasting - // memory. Additionally, this behavior is depended on - // by at least the block acceptance test tool as the - // reference implementation processes blocks in the same - // thread and therefore blocks further messages until - // the bitcoin block has been fully processed. - p.server.blockManager.QueueBlock(block, p) - <-p.blockProcessed -} - -// handleInvMsg is invoked when a peer receives an inv bitcoin message and is -// used to examine the inventory being advertised by the remote peer and react -// accordingly. We pass the message down to blockmanager which will call -// QueueMessage with any appropriate responses. -func (p *peer) handleInvMsg(msg *wire.MsgInv) { - p.server.blockManager.QueueInv(msg, p) -} - -// handleHeadersMsg is invoked when a peer receives a headers bitcoin message. -// The message is passed down to the block manager. -func (p *peer) handleHeadersMsg(msg *wire.MsgHeaders) { - p.server.blockManager.QueueHeaders(msg, p) -} - -// handleGetData is invoked when a peer receives a getdata bitcoin message and -// is used to deliver block and transaction information. -func (p *peer) handleGetDataMsg(msg *wire.MsgGetData) { - numAdded := 0 - notFound := wire.NewMsgNotFound() - - // We wait on the this wait channel periodically to prevent queueing - // far more data than we can send in a reasonable time, wasting memory. - // The waiting occurs after the database fetch for the next one to - // provide a little pipelining. - var waitChan chan struct{} - doneChan := make(chan struct{}, 1) - - for i, iv := range msg.InvList { - var c chan struct{} - // If this will be the last message we send. - if i == len(msg.InvList)-1 && len(notFound.InvList) == 0 { - c = doneChan - } else if (i+1)%3 == 0 { - // Buffered so as to not make the send goroutine block. - c = make(chan struct{}, 1) - } - var err error - switch iv.Type { - case wire.InvTypeTx: - err = p.pushTxMsg(&iv.Hash, c, waitChan) - case wire.InvTypeBlock: - err = p.pushBlockMsg(&iv.Hash, c, waitChan) - case wire.InvTypeFilteredBlock: - err = p.pushMerkleBlockMsg(&iv.Hash, c, waitChan) - default: - peerLog.Warnf("Unknown type in inventory request %d", - iv.Type) - continue - } - if err != nil { - notFound.AddInvVect(iv) - - // When there is a failure fetching the final entry - // and the done channel was sent in due to there - // being no outstanding not found inventory, consume - // it here because there is now not found inventory - // that will use the channel momentarily. - if i == len(msg.InvList)-1 && c != nil { - <-c - } - } - numAdded++ - waitChan = c - } - if len(notFound.InvList) != 0 { - p.QueueMessage(notFound, doneChan) - } - - // Wait for messages to be sent. We can send quite a lot of data at this - // point and this will keep the peer busy for a decent amount of time. - // We don't process anything else by them in this time so that we - // have an idea of when we should hear back from them - else the idle - // timeout could fire when we were only half done sending the blocks. - if numAdded > 0 { - <-doneChan - } -} - -// handleGetBlocksMsg is invoked when a peer receives a getblocks bitcoin message. -func (p *peer) handleGetBlocksMsg(msg *wire.MsgGetBlocks) { - // Return all block hashes to the latest one (up to max per message) if - // no stop hash was specified. - // Attempt to find the ending index of the stop hash if specified. - endIdx := database.AllShas - if !msg.HashStop.IsEqual(&zeroHash) { - height, err := p.server.db.FetchBlockHeightBySha(&msg.HashStop) - if err == nil { - endIdx = height + 1 - } - } - - // Find the most recent known block based on the block locator. - // Use the block after the genesis block if no other blocks in the - // provided locator are known. This does mean the client will start - // over with the genesis block if unknown block locators are provided. - // This mirrors the behavior in the reference implementation. - startIdx := int32(1) - for _, hash := range msg.BlockLocatorHashes { - height, err := p.server.db.FetchBlockHeightBySha(hash) - if err == nil { - // Start with the next hash since we know this one. - startIdx = height + 1 - break - } - } - - // Don't attempt to fetch more than we can put into a single message. - autoContinue := false - if endIdx-startIdx > wire.MaxBlocksPerMsg { - endIdx = startIdx + wire.MaxBlocksPerMsg - autoContinue = true - } - - // Generate inventory message. - // - // The FetchBlockBySha call is limited to a maximum number of hashes - // per invocation. Since the maximum number of inventory per message - // might be larger, call it multiple times with the appropriate indices - // as needed. - invMsg := wire.NewMsgInv() - for start := startIdx; start < endIdx; { - // Fetch the inventory from the block database. - hashList, err := p.server.db.FetchHeightRange(start, endIdx) - if err != nil { - peerLog.Warnf("Block lookup failed: %v", err) - return - } - - // The database did not return any further hashes. Break out of - // the loop now. - if len(hashList) == 0 { - break - } - - // Add block inventory to the message. - for _, hash := range hashList { - hashCopy := hash - iv := wire.NewInvVect(wire.InvTypeBlock, &hashCopy) - invMsg.AddInvVect(iv) - } - start += int32(len(hashList)) - } - - // Send the inventory message if there is anything to send. - if len(invMsg.InvList) > 0 { - invListLen := len(invMsg.InvList) - if autoContinue && invListLen == wire.MaxBlocksPerMsg { - // Intentionally use a copy of the final hash so there - // is not a reference into the inventory slice which - // would prevent the entire slice from being eligible - // for GC as soon as it's sent. - continueHash := invMsg.InvList[invListLen-1].Hash - p.continueHash = &continueHash - } - p.QueueMessage(invMsg, nil) - } -} - -// handleGetHeadersMsg is invoked when a peer receives a getheaders bitcoin -// message. -func (p *peer) handleGetHeadersMsg(msg *wire.MsgGetHeaders) { - // Ignore getheaders requests if not in sync. - if !p.server.blockManager.IsCurrent() { - return - } - - // Attempt to look up the height of the provided stop hash. - endIdx := database.AllShas - height, err := p.server.db.FetchBlockHeightBySha(&msg.HashStop) - if err == nil { - endIdx = height + 1 - } - - // There are no block locators so a specific header is being requested - // as identified by the stop hash. - if len(msg.BlockLocatorHashes) == 0 { - // No blocks with the stop hash were found so there is nothing - // to do. Just return. This behavior mirrors the reference - // implementation. - if endIdx == database.AllShas { - return - } - - // Fetch and send the requested block header. - header, err := p.server.db.FetchBlockHeaderBySha(&msg.HashStop) - if err != nil { - peerLog.Warnf("Lookup of known block hash failed: %v", - err) - return - } - - headersMsg := wire.NewMsgHeaders() - headersMsg.AddBlockHeader(header) - p.QueueMessage(headersMsg, nil) - return - } - - // Find the most recent known block based on the block locator. - // Use the block after the genesis block if no other blocks in the - // provided locator are known. This does mean the client will start - // over with the genesis block if unknown block locators are provided. - // This mirrors the behavior in the reference implementation. - startIdx := int32(1) - for _, hash := range msg.BlockLocatorHashes { - height, err := p.server.db.FetchBlockHeightBySha(hash) - if err == nil { - // Start with the next hash since we know this one. - startIdx = height + 1 - break - } - } - - // Don't attempt to fetch more than we can put into a single message. - if endIdx-startIdx > wire.MaxBlockHeadersPerMsg { - endIdx = startIdx + wire.MaxBlockHeadersPerMsg - } - - // Generate headers message and send it. - // - // The FetchHeightRange call is limited to a maximum number of hashes - // per invocation. Since the maximum number of headers per message - // might be larger, call it multiple times with the appropriate indices - // as needed. - headersMsg := wire.NewMsgHeaders() - for start := startIdx; start < endIdx; { - // Fetch the inventory from the block database. - hashList, err := p.server.db.FetchHeightRange(start, endIdx) - if err != nil { - peerLog.Warnf("Header lookup failed: %v", err) - return - } - - // The database did not return any further hashes. Break out of - // the loop now. - if len(hashList) == 0 { - break - } - - // Add headers to the message. - for _, hash := range hashList { - header, err := p.server.db.FetchBlockHeaderBySha(&hash) - if err != nil { - peerLog.Warnf("Lookup of known block hash "+ - "failed: %v", err) - continue - } - headersMsg.AddBlockHeader(header) - } - - // Start at the next block header after the latest one on the - // next loop iteration. - start += int32(len(hashList)) - } - p.QueueMessage(headersMsg, nil) -} - -// isValidBIP0111 is a helper function for the bloom filter commands to check -// BIP0111 compliance. -func (p *peer) isValidBIP0111(cmd string) bool { - if p.server.services&wire.SFNodeBloom != wire.SFNodeBloom { - if p.ProtocolVersion() >= wire.BIP0111Version { - peerLog.Debugf("%s sent an unsupported %s "+ - "request -- disconnecting", p, cmd) - p.Disconnect() - } else { - peerLog.Debugf("Ignoring %s request from %s -- bloom "+ - "support is disabled", cmd, p) - } - return false - } - - return true -} - -// handleFilterAddMsg is invoked when a peer receives a filteradd bitcoin -// message and is used by remote peers to add data to an already loaded bloom -// filter. The peer will be disconnected if a filter is not loaded when this -// message is received. -func (p *peer) handleFilterAddMsg(msg *wire.MsgFilterAdd) { - if !p.isValidBIP0111(msg.Command()) { - return - } - - if !p.filter.IsLoaded() { - peerLog.Debugf("%s sent a filteradd request with no filter "+ - "loaded -- disconnecting", p) - p.Disconnect() - return - } - - p.filter.Add(msg.Data) -} - -// handleFilterClearMsg is invoked when a peer receives a filterclear bitcoin -// message and is used by remote peers to clear an already loaded bloom filter. -// The peer will be disconnected if a filter is not loaded when this message is -// received. -func (p *peer) handleFilterClearMsg(msg *wire.MsgFilterClear) { - if !p.isValidBIP0111(msg.Command()) { - return - } - - if !p.filter.IsLoaded() { - peerLog.Debugf("%s sent a filterclear request with no "+ - "filter loaded -- disconnecting", p) - p.Disconnect() - return - } - - p.filter.Unload() -} - -// handleFilterLoadMsg is invoked when a peer receives a filterload bitcoin -// message and it used to load a bloom filter that should be used for delivering -// merkle blocks and associated transactions that match the filter. -func (p *peer) handleFilterLoadMsg(msg *wire.MsgFilterLoad) { - if !p.isValidBIP0111(msg.Command()) { - return - } - - // Transaction relay is no longer disabled once a filterload message is - // received regardless of its original state. - p.relayMtx.Lock() - p.disableRelayTx = false - p.relayMtx.Unlock() - - p.filter.Reload(msg) -} - -// handleGetAddrMsg is invoked when a peer receives a getaddr bitcoin message -// and is used to provide the peer with known addresses from the address -// manager. -func (p *peer) handleGetAddrMsg(msg *wire.MsgGetAddr) { - // Don't return any addresses when running on the simulation test - // network. This helps prevent the network from becoming another - // public test network since it will not be able to learn about other - // peers that have not specifically been provided. - if cfg.SimNet { - return - } - - // Do not accept getaddr requests from outbound peers. This reduces - // fingerprinting attacks. - if !p.inbound { - return - } - - // Get the current known addresses from the address manager. - addrCache := p.server.addrManager.AddressCache() - - // Push the addresses. - err := p.pushAddrMsg(addrCache) - if err != nil { - p.logError("Can't push address message to %s: %v", p, err) - p.Disconnect() - return - } -} - -// pushAddrMsg sends one, or more, addr message(s) to the connected peer using -// the provided addresses. -func (p *peer) pushAddrMsg(addresses []*wire.NetAddress) error { - // Nothing to send. - if len(addresses) == 0 { - return nil - } - - r := prand.New(prand.NewSource(time.Now().UnixNano())) - numAdded := 0 - msg := wire.NewMsgAddr() - for _, na := range addresses { - // Filter addresses the peer already knows about. - if _, exists := p.knownAddresses[addrmgr.NetAddressKey(na)]; exists { - continue - } - - // If the maxAddrs limit has been reached, randomize the list - // with the remaining addresses. - if numAdded == wire.MaxAddrPerMsg { - msg.AddrList[r.Intn(wire.MaxAddrPerMsg)] = na - continue - } - - // Add the address to the message. - err := msg.AddAddress(na) - if err != nil { - return err - } - numAdded++ - } - if numAdded > 0 { - for _, na := range msg.AddrList { - // Add address to known addresses for this peer. - p.knownAddresses[addrmgr.NetAddressKey(na)] = struct{}{} - } - - p.QueueMessage(msg, nil) - } - return nil -} - -// handleAddrMsg is invoked when a peer receives an addr bitcoin message and -// is used to notify the server about advertised addresses. -func (p *peer) handleAddrMsg(msg *wire.MsgAddr) { - // Ignore addresses when running on the simulation test network. This - // helps prevent the network from becoming another public test network - // since it will not be able to learn about other peers that have not - // specifically been provided. - if cfg.SimNet { - return - } - - // Ignore old style addresses which don't include a timestamp. - if p.ProtocolVersion() < wire.NetAddressTimeVersion { - return - } - - // A message that has no addresses is invalid. - if len(msg.AddrList) == 0 { - p.logError("Command [%s] from %s does not contain any addresses", - msg.Command(), p) - p.Disconnect() - return - } - - for _, na := range msg.AddrList { - // Don't add more address if we're disconnecting. - if atomic.LoadInt32(&p.disconnect) != 0 { - return - } - - // Set the timestamp to 5 days ago if it's more than 24 hours - // in the future so this address is one of the first to be - // removed when space is needed. - now := time.Now() - if na.Timestamp.After(now.Add(time.Minute * 10)) { - na.Timestamp = now.Add(-1 * time.Hour * 24 * 5) - } - - // Add address to known addresses for this peer. - p.knownAddresses[addrmgr.NetAddressKey(na)] = struct{}{} - } - - // Add addresses to server address manager. The address manager handles - // the details of things such as preventing duplicate addresses, max - // addresses, and last seen updates. - // XXX bitcoind gives a 2 hour time penalty here, do we want to do the - // same? - p.server.addrManager.AddAddresses(msg.AddrList, p.na) -} - -// handlePingMsg is invoked when a peer receives a ping bitcoin message. For -// recent clients (protocol version > BIP0031Version), it replies with a pong -// message. For older clients, it does nothing and anything other than failure -// is considered a successful ping. -func (p *peer) handlePingMsg(msg *wire.MsgPing) { - // Only Reply with pong is message comes from a new enough client. - if p.ProtocolVersion() > wire.BIP0031Version { - // Include nonce from ping so pong can be identified. - p.QueueMessage(wire.NewMsgPong(msg.Nonce), nil) - } -} - -// handlePongMsg is invoked when a peer received a pong bitcoin message. -// recent clients (protocol version > BIP0031Version), and if we had send a ping -// previosuly we update our ping time statistics. If the client is too old or -// we had not send a ping we ignore it. -func (p *peer) handlePongMsg(msg *wire.MsgPong) { - p.StatsMtx.Lock() - defer p.StatsMtx.Unlock() - - // Arguably we could use a buffered channel here sending data - // in a fifo manner whenever we send a ping, or a list keeping track of - // the times of each ping. For now we just make a best effort and - // only record stats if it was for the last ping sent. Any preceding - // and overlapping pings will be ignored. It is unlikely to occur - // without large usage of the ping rpc call since we ping - // infrequently enough that if they overlap we would have timed out - // the peer. - if p.protocolVersion > wire.BIP0031Version && - p.lastPingNonce != 0 && msg.Nonce == p.lastPingNonce { - p.lastPingMicros = time.Now().Sub(p.lastPingTime).Nanoseconds() - p.lastPingMicros /= 1000 // convert to usec. - p.lastPingNonce = 0 - } -} - -// readMessage reads the next bitcoin message from the peer with logging. -func (p *peer) readMessage() (wire.Message, []byte, error) { - n, msg, buf, err := wire.ReadMessageN(p.conn, p.ProtocolVersion(), - p.btcnet) - p.StatsMtx.Lock() - p.bytesReceived += uint64(n) - p.StatsMtx.Unlock() - p.server.AddBytesReceived(uint64(n)) - if err != nil { - return nil, nil, err - } - - // Use closures to log expensive operations so they are only run when - // the logging level requires it. - peerLog.Debugf("%v", newLogClosure(func() string { - // Debug summary of message. - summary := messageSummary(msg) - if len(summary) > 0 { - summary = " (" + summary + ")" - } - return fmt.Sprintf("Received %v%s from %s", - msg.Command(), summary, p) - })) - peerLog.Tracef("%v", newLogClosure(func() string { - return spew.Sdump(msg) - })) - peerLog.Tracef("%v", newLogClosure(func() string { - return spew.Sdump(buf) - })) - - return msg, buf, nil -} - -// writeMessage sends a bitcoin Message to the peer with logging. -func (p *peer) writeMessage(msg wire.Message) { - // Don't do anything if we're disconnecting. - if atomic.LoadInt32(&p.disconnect) != 0 { - return - } - if !p.VersionKnown() { - switch msg.(type) { - case *wire.MsgVersion: - // This is OK. - case *wire.MsgReject: - // This is OK. - default: - // Drop all messages other than version and reject if - // the handshake has not already been done. - return - } - } - - // Use closures to log expensive operations so they are only run when - // the logging level requires it. - peerLog.Debugf("%v", newLogClosure(func() string { - // Debug summary of message. - summary := messageSummary(msg) - if len(summary) > 0 { - summary = " (" + summary + ")" - } - return fmt.Sprintf("Sending %v%s to %s", msg.Command(), - summary, p) - })) - peerLog.Tracef("%v", newLogClosure(func() string { - return spew.Sdump(msg) - })) - peerLog.Tracef("%v", newLogClosure(func() string { - var buf bytes.Buffer - err := wire.WriteMessage(&buf, msg, p.ProtocolVersion(), - p.btcnet) - if err != nil { - return err.Error() - } - return spew.Sdump(buf.Bytes()) - })) - - // Write the message to the peer. - n, err := wire.WriteMessageN(p.conn, msg, p.ProtocolVersion(), - p.btcnet) - p.StatsMtx.Lock() - p.bytesSent += uint64(n) - p.StatsMtx.Unlock() - p.server.AddBytesSent(uint64(n)) - if err != nil { - p.Disconnect() - p.logError("Can't send message to %s: %v", p, err) - return - } -} - -// isAllowedByRegression returns whether or not the passed error is allowed by -// regression tests without disconnecting the peer. In particular, regression -// tests need to be allowed to send malformed messages without the peer being -// disconnected. -func (p *peer) isAllowedByRegression(err error) bool { - // Don't allow the error if it's not specifically a malformed message - // error. - if _, ok := err.(*wire.MessageError); !ok { - return false - } - - // Don't allow the error if it's not coming from localhost or the - // hostname can't be determined for some reason. - host, _, err := net.SplitHostPort(p.addr) - if err != nil { - return false - } - - if host != "127.0.0.1" && host != "localhost" { - return false - } - - // Allowed if all checks passed. - return true -} - -// inHandler handles all incoming messages for the peer. It must be run as a -// goroutine. -func (p *peer) inHandler() { - // Peers must complete the initial version negotiation within a shorter - // timeframe than a general idle timeout. The timer is then reset below - // to idleTimeoutMinutes for all future messages. - idleTimer := time.AfterFunc(negotiateTimeoutSeconds*time.Second, func() { - if p.VersionKnown() { - peerLog.Warnf("Peer %s no answer for %d minutes, "+ - "disconnecting", p, idleTimeoutMinutes) - } - p.Disconnect() - }) -out: - for atomic.LoadInt32(&p.disconnect) == 0 { - rmsg, buf, err := p.readMessage() - // Stop the timer now, if we go around again we will reset it. - idleTimer.Stop() - if err != nil { - // In order to allow regression tests with malformed - // messages, don't disconnect the peer when we're in - // regression test mode and the error is one of the - // allowed errors. - if cfg.RegressionTest && p.isAllowedByRegression(err) { - peerLog.Errorf("Allowed regression test "+ - "error from %s: %v", p, err) - idleTimer.Reset(idleTimeoutMinutes * time.Minute) - continue - } - - // Only log the error and possibly send reject message - // if we're not forcibly disconnecting. - if atomic.LoadInt32(&p.disconnect) == 0 { - errMsg := fmt.Sprintf("Can't read message "+ - "from %s: %v", p, err) - p.logError(errMsg) - - // Only send the reject message if it's not - // because the remote client disconnected. - if err != io.EOF { - // Push a reject message for the - // malformed message and wait for the - // message to be sent before - // disconnecting. - // - // NOTE: Ideally this would include the - // command in the header if at least - // that much of the message was valid, - // but that is not currently exposed by - // wire, so just used malformed for the - // command. - p.PushRejectMsg("malformed", - wire.RejectMalformed, errMsg, - nil, true) - } - - } - break out - } - p.StatsMtx.Lock() - p.lastRecv = time.Now() - p.StatsMtx.Unlock() - - // Ensure version message comes first. - if vmsg, ok := rmsg.(*wire.MsgVersion); !ok && !p.VersionKnown() { - errStr := "A version message must precede all others" - p.logError(errStr) - - // Push a reject message and wait for the message to be - // sent before disconnecting. - p.PushRejectMsg(vmsg.Command(), wire.RejectMalformed, - errStr, nil, true) - break out - } - - // Handle each supported message type. - switch msg := rmsg.(type) { - case *wire.MsgVersion: - p.handleVersionMsg(msg) - - case *wire.MsgVerAck: - p.StatsMtx.Lock() - versionSent := p.versionSent - verAckReceived := p.verAckReceived - p.StatsMtx.Unlock() - - if !versionSent { - peerLog.Infof("Received 'verack' from peer %v "+ - "before version was sent -- disconnecting", p) - break out - } - if verAckReceived { - peerLog.Infof("Already received 'verack' from "+ - "peer %v -- disconnecting", p) - break out - } - p.verAckReceived = true - - case *wire.MsgGetAddr: - p.handleGetAddrMsg(msg) - - case *wire.MsgAddr: - p.handleAddrMsg(msg) - - case *wire.MsgPing: - p.handlePingMsg(msg) - - case *wire.MsgPong: - p.handlePongMsg(msg) - - case *wire.MsgAlert: - // Intentionally ignore alert messages. - // - // The reference client currently bans peers that send - // alerts not signed with its key. We could verify - // against their key, but since the reference client - // is currently unwilling to support other - // implementions' alert messages, we will not relay - // theirs. - - case *wire.MsgMemPool: - p.handleMemPoolMsg(msg) - - case *wire.MsgTx: - p.handleTxMsg(msg) - - case *wire.MsgBlock: - p.handleBlockMsg(msg, buf) - - case *wire.MsgInv: - p.handleInvMsg(msg) - - case *wire.MsgHeaders: - p.handleHeadersMsg(msg) - - case *wire.MsgNotFound: - // TODO(davec): Ignore this for now, but ultimately - // it should probably be used to detect when something - // we requested needs to be re-requested from another - // peer. - - case *wire.MsgGetData: - p.handleGetDataMsg(msg) - - case *wire.MsgGetBlocks: - p.handleGetBlocksMsg(msg) - - case *wire.MsgGetHeaders: - p.handleGetHeadersMsg(msg) - - case *wire.MsgFilterAdd: - p.handleFilterAddMsg(msg) - - case *wire.MsgFilterClear: - p.handleFilterClearMsg(msg) - - case *wire.MsgFilterLoad: - p.handleFilterLoadMsg(msg) - - case *wire.MsgReject: - // Nothing to do currently. Logging of the rejected - // message is handled already in readMessage. - - default: - peerLog.Debugf("Received unhandled message of type %v: Fix Me", - rmsg.Command()) - } - - // ok we got a message, reset the timer. - // timer just calls p.Disconnect() after logging. - idleTimer.Reset(idleTimeoutMinutes * time.Minute) - p.retryCount = 0 - } - - idleTimer.Stop() - - // Ensure connection is closed and notify the server that the peer is - // done. - p.Disconnect() - p.server.donePeers <- p - - // Only tell block manager we are gone if we ever told it we existed. - if p.VersionKnown() { - p.server.blockManager.DonePeer(p) - } - - peerLog.Tracef("Peer input handler done for %s", p) -} - -// queueHandler handles the queueing of outgoing data for the peer. This runs -// as a muxer for various sources of input so we can ensure that blockmanager -// and the server goroutine both will not block on us sending a message. -// We then pass the data on to outHandler to be actually written. -func (p *peer) queueHandler() { - pendingMsgs := list.New() - invSendQueue := list.New() - trickleTicker := time.NewTicker(time.Second * 10) - defer trickleTicker.Stop() - - // We keep the waiting flag so that we know if we have a message queued - // to the outHandler or not. We could use the presence of a head of - // the list for this but then we have rather racy concerns about whether - // it has gotten it at cleanup time - and thus who sends on the - // message's done channel. To avoid such confusion we keep a different - // flag and pendingMsgs only contains messages that we have not yet - // passed to outHandler. - waiting := false - - // To avoid duplication below. - queuePacket := func(msg outMsg, list *list.List, waiting bool) bool { - if !waiting { - peerLog.Tracef("%s: sending to outHandler", p) - p.sendQueue <- msg - peerLog.Tracef("%s: sent to outHandler", p) - } else { - list.PushBack(msg) - } - // we are always waiting now. - return true - } -out: - for { - select { - case msg := <-p.outputQueue: - waiting = queuePacket(msg, pendingMsgs, waiting) - - // This channel is notified when a message has been sent across - // the network socket. - case <-p.sendDoneQueue: - peerLog.Tracef("%s: acked by outhandler", p) - - // No longer waiting if there are no more messages - // in the pending messages queue. - next := pendingMsgs.Front() - if next == nil { - waiting = false - continue - } - - // Notify the outHandler about the next item to - // asynchronously send. - val := pendingMsgs.Remove(next) - peerLog.Tracef("%s: sending to outHandler", p) - p.sendQueue <- val.(outMsg) - peerLog.Tracef("%s: sent to outHandler", p) - - case iv := <-p.outputInvChan: - // No handshake? They'll find out soon enough. - if p.VersionKnown() { - invSendQueue.PushBack(iv) - } - - case <-trickleTicker.C: - // Don't send anything if we're disconnecting or there - // is no queued inventory. - // version is known if send queue has any entries. - if atomic.LoadInt32(&p.disconnect) != 0 || - invSendQueue.Len() == 0 { - continue - } - - // Create and send as many inv messages as needed to - // drain the inventory send queue. - invMsg := wire.NewMsgInvSizeHint(uint(invSendQueue.Len())) - for e := invSendQueue.Front(); e != nil; e = invSendQueue.Front() { - iv := invSendQueue.Remove(e).(*wire.InvVect) - - // Don't send inventory that became known after - // the initial check. - if p.isKnownInventory(iv) { - continue - } - - invMsg.AddInvVect(iv) - if len(invMsg.InvList) >= maxInvTrickleSize { - waiting = queuePacket( - outMsg{msg: invMsg}, - pendingMsgs, waiting) - invMsg = wire.NewMsgInvSizeHint(uint(invSendQueue.Len())) - } - - // Add the inventory that is being relayed to - // the known inventory for the peer. - p.AddKnownInventory(iv) - } - if len(invMsg.InvList) > 0 { - waiting = queuePacket(outMsg{msg: invMsg}, - pendingMsgs, waiting) - } - - case <-p.quit: - break out - } - } - - // Drain any wait channels before we go away so we don't leave something - // waiting for us. - for e := pendingMsgs.Front(); e != nil; e = pendingMsgs.Front() { - val := pendingMsgs.Remove(e) - msg := val.(outMsg) - if msg.doneChan != nil { - msg.doneChan <- struct{}{} - } - } -cleanup: - for { - select { - case msg := <-p.outputQueue: - if msg.doneChan != nil { - msg.doneChan <- struct{}{} - } - case <-p.outputInvChan: - // Just drain channel - // sendDoneQueue is buffered so doesn't need draining. - default: - break cleanup - } - } - p.queueWg.Done() - peerLog.Tracef("Peer queue handler done for %s", p) -} - -// outHandler handles all outgoing messages for the peer. It must be run as a -// goroutine. It uses a buffered channel to serialize output messages while -// allowing the sender to continue running asynchronously. -func (p *peer) outHandler() { - pingTimer := time.AfterFunc(pingTimeoutMinutes*time.Minute, func() { - nonce, err := wire.RandomUint64() - if err != nil { - peerLog.Errorf("Not sending ping on timeout to %s: %v", - p, err) - return - } - p.QueueMessage(wire.NewMsgPing(nonce), nil) - }) -out: - for { - select { - case msg := <-p.sendQueue: - // If the message is one we should get a reply for - // then reset the timer, we only want to send pings - // when otherwise we would not receive a reply from - // the peer. We specifically do not count block or inv - // messages here since they are not sure of a reply if - // the inv is of no interest explicitly solicited invs - // should elicit a reply but we don't track them - // specially. - peerLog.Tracef("%s: received from queuehandler", p) - reset := true - switch m := msg.msg.(type) { - case *wire.MsgVersion: - // should get a verack - p.StatsMtx.Lock() - p.versionSent = true - p.StatsMtx.Unlock() - case *wire.MsgGetAddr: - // should get addresses - case *wire.MsgPing: - // expects pong - // Also set up statistics. - p.StatsMtx.Lock() - if p.protocolVersion > wire.BIP0031Version { - p.lastPingNonce = m.Nonce - p.lastPingTime = time.Now() - } - p.StatsMtx.Unlock() - case *wire.MsgMemPool: - // Should return an inv. - case *wire.MsgGetData: - // Should get us block, tx, or not found. - case *wire.MsgGetHeaders: - // Should get us headers back. - default: - // Not one of the above, no sure reply. - // We want to ping if nothing else - // interesting happens. - reset = false - } - if reset { - pingTimer.Reset(pingTimeoutMinutes * time.Minute) - } - p.writeMessage(msg.msg) - p.StatsMtx.Lock() - p.lastSend = time.Now() - p.StatsMtx.Unlock() - if msg.doneChan != nil { - msg.doneChan <- struct{}{} - } - peerLog.Tracef("%s: acking queuehandler", p) - p.sendDoneQueue <- struct{}{} - peerLog.Tracef("%s: acked queuehandler", p) - - case <-p.quit: - break out - } - } - - pingTimer.Stop() - - p.queueWg.Wait() - - // Drain any wait channels before we go away so we don't leave something - // waiting for us. We have waited on queueWg and thus we can be sure - // that we will not miss anything sent on sendQueue. -cleanup: - for { - select { - case msg := <-p.sendQueue: - if msg.doneChan != nil { - msg.doneChan <- struct{}{} - } - // no need to send on sendDoneQueue since queueHandler - // has been waited on and already exited. - default: - break cleanup - } - } - peerLog.Tracef("Peer output handler done for %s", p) -} - -// QueueMessage adds the passed bitcoin message to the peer send queue. It -// uses a buffered channel to communicate with the output handler goroutine so -// it is automatically rate limited and safe for concurrent access. -func (p *peer) QueueMessage(msg wire.Message, doneChan chan struct{}) { - // Avoid risk of deadlock if goroutine already exited. The goroutine - // we will be sending to hangs around until it knows for a fact that - // it is marked as disconnected. *then* it drains the channels. - if !p.Connected() { - // avoid deadlock... - if doneChan != nil { - go func() { - doneChan <- struct{}{} - }() - } - return - } - p.outputQueue <- outMsg{msg: msg, doneChan: doneChan} -} - -// QueueInventory adds the passed inventory to the inventory send queue which -// might not be sent right away, rather it is trickled to the peer in batches. -// Inventory that the peer is already known to have is ignored. It is safe for -// concurrent access. -func (p *peer) QueueInventory(invVect *wire.InvVect) { - // Don't add the inventory to the send queue if the peer is - // already known to have it. - if p.isKnownInventory(invVect) { - return - } - - // Avoid risk of deadlock if goroutine already exited. The goroutine - // we will be sending to hangs around until it knows for a fact that - // it is marked as disconnected. *then* it drains the channels. - if !p.Connected() { - return - } - - p.outputInvChan <- invVect -} - -// Connected returns whether or not the peer is currently connected. -func (p *peer) Connected() bool { - return atomic.LoadInt32(&p.connected) != 0 && - atomic.LoadInt32(&p.disconnect) == 0 -} - -// Disconnect disconnects the peer by closing the connection. It also sets -// a flag so the impending shutdown can be detected. -func (p *peer) Disconnect() { - // did we win the race? - if atomic.AddInt32(&p.disconnect, 1) != 1 { - return - } - - // Update the address' last seen time if the peer has acknowledged - // our version and has sent us its version as well. - p.StatsMtx.Lock() - if p.verAckReceived && p.versionKnown && p.na != nil { - p.server.addrManager.Connected(p.na) - } - p.StatsMtx.Unlock() - - peerLog.Tracef("disconnecting %s", p) - close(p.quit) - if atomic.LoadInt32(&p.connected) != 0 { - p.conn.Close() - } -} - -// Start begins processing input and output messages. It also sends the initial -// version message for outbound connections to start the negotiation process. -func (p *peer) Start() error { - // Already started? - if atomic.AddInt32(&p.started, 1) != 1 { - return nil - } - - peerLog.Tracef("Starting peer %s", p) - - // Send an initial version message if this is an outbound connection. - if !p.inbound { - err := p.pushVersionMsg() - if err != nil { - p.logError("Can't send outbound version message %v", err) - p.Disconnect() - return err - } - } - - // Start processing input and output. - go p.inHandler() - // queueWg is kept so that outHandler knows when the queue has exited so - // it can drain correctly. - p.queueWg.Add(1) - go p.queueHandler() - go p.outHandler() - - return nil -} - -// Shutdown gracefully shuts down the peer by disconnecting it. -func (p *peer) Shutdown() { - peerLog.Tracef("Shutdown peer %s", p) - p.Disconnect() -} - -// newPeerBase returns a new base bitcoin peer for the provided server and -// inbound flag. This is used by the newInboundPeer and newOutboundPeer -// functions to perform base setup needed by both types of peers. -func newPeerBase(s *server, inbound bool) *peer { - p := peer{ - server: s, - protocolVersion: maxProtocolVersion, - btcnet: s.chainParams.Net, - services: wire.SFNodeNetwork, - inbound: inbound, - knownAddresses: make(map[string]struct{}), - knownInventory: NewMruInventoryMap(maxKnownInventory), - requestedTxns: make(map[wire.ShaHash]struct{}), - requestedBlocks: make(map[wire.ShaHash]struct{}), - filter: bloom.LoadFilter(nil), - outputQueue: make(chan outMsg, outputBufferSize), - sendQueue: make(chan outMsg, 1), // nonblocking sync - sendDoneQueue: make(chan struct{}, 1), // nonblocking sync - outputInvChan: make(chan *wire.InvVect, outputBufferSize), - txProcessed: make(chan struct{}, 1), - blockProcessed: make(chan struct{}, 1), - quit: make(chan struct{}), - } - return &p -} - -// newInboundPeer returns a new inbound bitcoin peer for the provided server and -// connection. Use Start to begin processing incoming and outgoing messages. -func newInboundPeer(s *server, conn net.Conn) *peer { - p := newPeerBase(s, true) - p.conn = conn - p.addr = conn.RemoteAddr().String() - p.timeConnected = time.Now() - atomic.AddInt32(&p.connected, 1) - return p -} - -// newOutbountPeer returns a new outbound bitcoin peer for the provided server and -// address and connects to it asynchronously. If the connection is successful -// then the peer will also be started. -func newOutboundPeer(s *server, addr string, persistent bool, retryCount int64) *peer { - p := newPeerBase(s, false) - p.addr = addr - p.persistent = persistent - p.retryCount = retryCount - - // Setup p.na with a temporary address that we are connecting to with - // faked up service flags. We will replace this with the real one after - // version negotiation is successful. The only failure case here would - // be if the string was incomplete for connection so can't be split - // into address and port, and thus this would be invalid anyway. In - // which case we return nil to be handled by the caller. This must be - // done before we fork off the goroutine because as soon as this - // function returns the peer must have a valid netaddress. - host, portStr, err := net.SplitHostPort(addr) - if err != nil { - p.logError("Tried to create a new outbound peer with invalid "+ - "address %s: %v", addr, err) - return nil - } - - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - p.logError("Tried to create a new outbound peer with invalid "+ - "port %s: %v", portStr, err) - return nil - } - - p.na, err = s.addrManager.HostToNetAddress(host, uint16(port), 0) - if err != nil { - p.logError("Can not turn host %s into netaddress: %v", - host, err) - return nil - } - - go func() { - if atomic.LoadInt32(&p.disconnect) != 0 { - return - } - if p.retryCount > 0 { - scaledInterval := connectionRetryInterval.Nanoseconds() * p.retryCount / 2 - scaledDuration := time.Duration(scaledInterval) - if scaledDuration > maxConnectionRetryInterval { - scaledDuration = maxConnectionRetryInterval - } - srvrLog.Debugf("Retrying connection to %s in %s", addr, scaledDuration) - time.Sleep(scaledDuration) - } - srvrLog.Debugf("Attempting to connect to %s", addr) - conn, err := btcdDial("tcp", addr) - if err != nil { - srvrLog.Debugf("Failed to connect to %s: %v", addr, err) - p.server.donePeers <- p - return - } - - // We may have slept and the server may have scheduled a shutdown. In that - // case ditch the peer immediately. - if atomic.LoadInt32(&p.disconnect) == 0 { - p.timeConnected = time.Now() - p.server.addrManager.Attempt(p.na) - - // Connection was successful so log it and start peer. - srvrLog.Debugf("Connected to %s", conn.RemoteAddr()) - p.conn = conn - atomic.AddInt32(&p.connected, 1) - p.Start() - } - }() - return p -} - -// logError makes sure that we only log errors loudly on user peers. -func (p *peer) logError(fmt string, args ...interface{}) { - if p.persistent { - peerLog.Errorf(fmt, args...) - } else { - peerLog.Debugf(fmt, args...) - } -} diff --git a/peer/README.md b/peer/README.md new file mode 100644 index 00000000..af852634 --- /dev/null +++ b/peer/README.md @@ -0,0 +1,38 @@ +peer +==== + +[![Build Status](https://travis-ci.org/btcsuite/btcd.png?branch=master)] +(https://travis-ci.org/btcsuite/btcd) + +Package peer provides a common base for creating and managing bitcoin network +peers. + +## Overview + +- Create peers for full nodes, Simplified Payment Verification (SPV) nodes, + proxies etc +- Built-in handlers for common messages like initial message version + negotiation, handling and responding to pings +- Register and manage multiple custom handlers for all messages + +## Documentation + +[![GoDoc](https://godoc.org/github.com/btcsuite/btcd/peer?status.png)] +(http://godoc.org/github.com/btcsuite/btcd/peer) + +Full `go doc` style documentation for the project can be viewed online without +installing this package by using the GoDoc site here: +http://godoc.org/github.com/btcsuite/btcd/peer + +You can also view the documentation locally once the package is installed with +the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to +http://localhost:6060/pkg/github.com/btcsuite/btcd/peer + +## Installation + +```bash +$ go get github.com/btcsuite/btcd/peer +``` + +Package peer is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/peer/doc.go b/peer/doc.go new file mode 100644 index 00000000..7067db87 --- /dev/null +++ b/peer/doc.go @@ -0,0 +1,23 @@ +// 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 peer provides a common base for creating and managing bitcoin network +peers for fully validating nodes, Simplified Payment Verification (SPV) nodes, +proxies, etc. It includes basic protocol exchanges like version negotiation, +responding to pings etc. + +Inbound peers accept a connection and respond to the version message to begin +version negotiation. + +Outbound peers connect and push the initial version message over a given +connection. + +Both peers accept a configuration to customize options such as user agent, +service flag, protocol version, chain parameters, and proxy. + +To extend the basic peer functionality provided by package peer, listeners can +be configured for all message types using callbacks in the peer configuration. +*/ +package peer diff --git a/peer/example_test.go b/peer/example_test.go new file mode 100644 index 00000000..2abfaf94 --- /dev/null +++ b/peer/example_test.go @@ -0,0 +1,113 @@ +// 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 peer_test + +import ( + "fmt" + "net" + "time" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/peer" + "github.com/btcsuite/btcd/wire" +) + +// mockRemotePeer creates a basic inbound peer listening on the simnet port for +// use with Example_peerConnection. It does not return until the listner is +// active. +func mockRemotePeer() error { + // Configure peer to act as a simnet node that offers no services. + peerCfg := &peer.Config{ + UserAgentName: "peer", // User agent name to advertise. + UserAgentVersion: "1.0.0", // User agent version to advertise. + ChainParams: &chaincfg.SimNetParams, + } + + // Accept connections on the simnet port. + listener, err := net.Listen("tcp", "127.0.0.1:18555") + if err != nil { + return err + } + go func() { + conn, err := listener.Accept() + if err != nil { + fmt.Printf("Accept: error %v\n", err) + return + } + + // Create and start the inbound peer. + p := peer.NewInboundPeer(peerCfg, conn) + if err := p.Start(); err != nil { + fmt.Printf("Start: error %v\n", err) + return + } + }() + + return nil +} + +// This example demonstrates the basic process for initializing and creating an +// outbound peer. Peers negotiate by exchanging version and verack messages. +// For demonstration, a simple handler for version message is attached to the +// peer. +func Example_newOutboundPeer() { + // Ordinarily this will not be needed since the outbound peer will be + // connecting to a remote peer, however, since this example is executed + // and tested, a mock remote peer is needed to listen for the outbound + // peer. + if err := mockRemotePeer(); err != nil { + fmt.Printf("mockRemotePeer: unexpected error %v\n", err) + return + } + + // Create an outbound peer that is configured to act as a simnet node + // that offers no services and has listeners for the version and verack + // messages. The verack listener is used here to signal the code below + // when the handshake has been finished by signalling a channel. + verack := make(chan struct{}) + peerCfg := &peer.Config{ + UserAgentName: "peer", // User agent name to advertise. + UserAgentVersion: "1.0.0", // User agent version to advertise. + ChainParams: &chaincfg.SimNetParams, + Services: 0, + Listeners: peer.MessageListeners{ + OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) { + fmt.Println("outbound: received version") + }, + OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { + verack <- struct{}{} + }, + }, + } + p, err := peer.NewOutboundPeer(peerCfg, "127.0.0.1:18555") + if err != nil { + fmt.Printf("NewOutboundPeer: error %v\n", err) + return + } + + // Establish the connection to the peer address and mark it connected. + conn, err := net.Dial("tcp", p.Addr()) + if err != nil { + fmt.Printf("net.Dial: error %v\n", err) + return + } + if err := p.Connect(conn); err != nil { + fmt.Printf("Connect: error %v\n", err) + return + } + + // Wait for the verack message or timeout in case of failure. + select { + case <-verack: + case <-time.After(time.Second * 1): + fmt.Printf("Example_peerConnection: verack timeout") + } + + // Shutdown the peer. + p.Shutdown() + + // Output: + // outbound: received version +} diff --git a/peer/export_test.go b/peer/export_test.go new file mode 100644 index 00000000..06ec78a1 --- /dev/null +++ b/peer/export_test.go @@ -0,0 +1,18 @@ +// 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 test file is part of the peer package rather than than the peer_test +package so it can bridge access to the internals to properly test cases which +are either not possible or can't reliably be tested via the public interface. +The functions are only exported while the tests are being run. +*/ + +package peer + +// TstAllowSelfConns allows the test package to allow self connections by +// disabling the detection logic. +func TstAllowSelfConns() { + allowSelfConns = true +} diff --git a/peer/log.go b/peer/log.go new file mode 100644 index 00000000..acfdbf47 --- /dev/null +++ b/peer/log.go @@ -0,0 +1,241 @@ +// 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 peer + +import ( + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btclog" +) + +const ( + // maxRejectReasonLen is the maximum length of a sanitized reject reason + // that will be logged. + maxRejectReasonLen = 250 +) + +// 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 +} + +// SetLogWriter uses a specified io.Writer to output package logging info. +// This allows a caller to direct package logging output without needing a +// dependency on seelog. If the caller is also using btclog, UseLogger should +// be used instead. +func SetLogWriter(w io.Writer, level string) error { + if w == nil { + return errors.New("nil writer") + } + + lvl, ok := btclog.LogLevelFromString(level) + if !ok { + return errors.New("invalid log level") + } + + l, err := btclog.NewLoggerFromWriter(w, lvl) + if err != nil { + return err + } + + UseLogger(l) + return nil +} + +// LogClosure is a closure that can be printed with %v to be used to +// generate expensive-to-create data for a detailed log level and avoid doing +// the work if the data isn't printed. +type logClosure func() string + +func (c logClosure) String() string { + return c() +} + +func newLogClosure(c func() string) logClosure { + return logClosure(c) +} + +// directionString is a helper function that returns a string that represents +// the direction of a connection (inbound or outbound). +func directionString(inbound bool) string { + if inbound { + return "inbound" + } + return "outbound" +} + +// formatLockTime returns a transaction lock time as a human-readable string. +func formatLockTime(lockTime uint32) string { + // The lock time field of a transaction is either a block height at + // which the transaction is finalized or a timestamp depending on if the + // value is before the lockTimeThreshold. When it is under the + // threshold it is a block height. + if lockTime < txscript.LockTimeThreshold { + return fmt.Sprintf("height %d", lockTime) + } + + return time.Unix(int64(lockTime), 0).String() +} + +// invSummary returns an inventory message as a human-readable string. +func invSummary(invList []*wire.InvVect) string { + // No inventory. + invLen := len(invList) + if invLen == 0 { + return "empty" + } + + // One inventory item. + if invLen == 1 { + iv := invList[0] + switch iv.Type { + case wire.InvTypeError: + return fmt.Sprintf("error %s", iv.Hash) + case wire.InvTypeBlock: + return fmt.Sprintf("block %s", iv.Hash) + case wire.InvTypeTx: + return fmt.Sprintf("tx %s", iv.Hash) + } + + return fmt.Sprintf("unknown (%d) %s", uint32(iv.Type), iv.Hash) + } + + // More than one inv item. + return fmt.Sprintf("size %d", invLen) +} + +// locatorSummary returns a block locator as a human-readable string. +func locatorSummary(locator []*wire.ShaHash, stopHash *wire.ShaHash) string { + if len(locator) > 0 { + return fmt.Sprintf("locator %s, stop %s", locator[0], stopHash) + } + + return fmt.Sprintf("no locator, stop %s", stopHash) + +} + +// sanitizeString strips any characters which are even remotely dangerous, such +// as html control characters, from the passed string. It also limits it to +// the passed maximum size, which can be 0 for unlimited. When the string is +// limited, it will also add "..." to the string to indicate it was truncated. +func sanitizeString(str string, maxLength uint) string { + const safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY" + + "Z01234567890 .,;_/:?@" + + // Strip any characters not in the safeChars string removed. + str = strings.Map(func(r rune) rune { + if strings.IndexRune(safeChars, r) >= 0 { + return r + } + return -1 + }, str) + + // Limit the string to the max allowed length. + if maxLength > 0 && uint(len(str)) > maxLength { + str = str[:maxLength] + str = str + "..." + } + return str +} + +// messageSummary returns a human-readable string which summarizes a message. +// Not all messages have or need a summary. This is used for debug logging. +func messageSummary(msg wire.Message) string { + switch msg := msg.(type) { + case *wire.MsgVersion: + return fmt.Sprintf("agent %s, pver %d, block %d", + msg.UserAgent, msg.ProtocolVersion, msg.LastBlock) + + case *wire.MsgVerAck: + // No summary. + + case *wire.MsgGetAddr: + // No summary. + + case *wire.MsgAddr: + return fmt.Sprintf("%d addr", len(msg.AddrList)) + + case *wire.MsgPing: + // No summary - perhaps add nonce. + + case *wire.MsgPong: + // No summary - perhaps add nonce. + + case *wire.MsgAlert: + // No summary. + + case *wire.MsgMemPool: + // No summary. + + case *wire.MsgTx: + return fmt.Sprintf("hash %s, %d inputs, %d outputs, lock %s", + msg.TxSha(), len(msg.TxIn), len(msg.TxOut), + formatLockTime(msg.LockTime)) + + case *wire.MsgBlock: + header := &msg.Header + return fmt.Sprintf("hash %s, ver %d, %d tx, %s", msg.BlockSha(), + header.Version, len(msg.Transactions), header.Timestamp) + + case *wire.MsgInv: + return invSummary(msg.InvList) + + case *wire.MsgNotFound: + return invSummary(msg.InvList) + + case *wire.MsgGetData: + return invSummary(msg.InvList) + + case *wire.MsgGetBlocks: + return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop) + + case *wire.MsgGetHeaders: + return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop) + + case *wire.MsgHeaders: + return fmt.Sprintf("num %d", len(msg.Headers)) + + case *wire.MsgReject: + // Ensure the variable length strings don't contain any + // characters which are even remotely dangerous such as HTML + // control characters, etc. Also limit them to sane length for + // logging. + rejCommand := sanitizeString(msg.Cmd, wire.CommandSize) + rejReason := sanitizeString(msg.Reason, maxRejectReasonLen) + summary := fmt.Sprintf("cmd %v, code %v, reason %v", rejCommand, + msg.Code, rejReason) + if rejCommand == wire.CmdBlock || rejCommand == wire.CmdTx { + summary += fmt.Sprintf(", hash %v", msg.Hash) + } + return summary + } + + // No summary for other messages. + return "" +} diff --git a/peer/log_test.go b/peer/log_test.go new file mode 100644 index 00000000..39b49388 --- /dev/null +++ b/peer/log_test.go @@ -0,0 +1,65 @@ +// 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 peer_test + +import ( + "bytes" + "errors" + "io" + "testing" + + "github.com/btcsuite/btcd/peer" +) + +func TestSetLogWriter(t *testing.T) { + tests := []struct { + name string + w io.Writer + level string + expected error + }{ + { + name: "nil writer", + w: nil, + level: "trace", + expected: errors.New("nil writer"), + }, + { + name: "invalid log level", + w: bytes.NewBuffer(nil), + level: "wrong", + expected: errors.New("invalid log level"), + }, + { + name: "use off level", + w: bytes.NewBuffer(nil), + level: "off", + expected: errors.New("min level can't be greater than max. Got min: 6, max: 5"), + }, + { + name: "pass", + w: bytes.NewBuffer(nil), + level: "debug", + expected: nil, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + err := peer.SetLogWriter(test.w, test.level) + if err != nil { + if err.Error() != test.expected.Error() { + t.Errorf("SetLogWriter #%d (%s) wrong result\n"+ + "got: %v\nwant: %v", i, test.name, err, + test.expected) + } + } else { + if test.expected != nil { + t.Errorf("SetLogWriter #%d (%s) wrong result\n"+ + "got: %v\nwant: %v", i, test.name, err, + test.expected) + } + } + } +} diff --git a/mruinvmap.go b/peer/mruinvmap.go similarity index 82% rename from mruinvmap.go rename to peer/mruinvmap.go index f2e1f6ab..7d4d2ae8 100644 --- a/mruinvmap.go +++ b/peer/mruinvmap.go @@ -2,26 +2,34 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package main +package peer import ( "bytes" "container/list" "fmt" + "sync" "github.com/btcsuite/btcd/wire" ) -// MruInventoryMap provides a map that is limited to a maximum number of items -// with eviction for the oldest entry when the limit is exceeded. +// MruInventoryMap provides a concurrency safe map that is limited to a maximum +// number of items with eviction for the oldest entry when the limit is +// exceeded. type MruInventoryMap struct { + invMtx sync.Mutex invMap map[wire.InvVect]*list.Element // nearly O(1) lookups invList *list.List // O(1) insert, update, delete limit uint } // String returns the map as a human-readable string. -func (m MruInventoryMap) String() string { +// +// This function is safe for concurrent access. +func (m *MruInventoryMap) String() string { + m.invMtx.Lock() + defer m.invMtx.Unlock() + lastEntryNum := len(m.invMap) - 1 curEntry := 0 buf := bytes.NewBufferString("[") @@ -38,7 +46,12 @@ func (m MruInventoryMap) String() string { } // Exists returns whether or not the passed inventory item is in the map. +// +// This function is safe for concurrent access. func (m *MruInventoryMap) Exists(iv *wire.InvVect) bool { + m.invMtx.Lock() + defer m.invMtx.Unlock() + if _, exists := m.invMap[*iv]; exists { return true } @@ -48,7 +61,12 @@ func (m *MruInventoryMap) Exists(iv *wire.InvVect) bool { // Add adds the passed inventory to the map and handles eviction of the oldest // item if adding the new item would exceed the max limit. Adding an existing // item makes it the most recently used item. +// +// This function is safe for concurrent access. func (m *MruInventoryMap) Add(iv *wire.InvVect) { + m.invMtx.Lock() + defer m.invMtx.Unlock() + // When the limit is zero, nothing can be added to the map, so just // return. if m.limit == 0 { @@ -87,7 +105,12 @@ func (m *MruInventoryMap) Add(iv *wire.InvVect) { } // Delete deletes the passed inventory item from the map (if it exists). +// +// This function is safe for concurrent access. func (m *MruInventoryMap) Delete(iv *wire.InvVect) { + m.invMtx.Lock() + defer m.invMtx.Unlock() + if node, exists := m.invMap[*iv]; exists { m.invList.Remove(node) delete(m.invMap, *iv) diff --git a/mruinvmap_test.go b/peer/mruinvmap_test.go similarity index 97% rename from mruinvmap_test.go rename to peer/mruinvmap_test.go index 10a2d9ee..c086034c 100644 --- a/mruinvmap_test.go +++ b/peer/mruinvmap_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package main +package peer import ( "crypto/rand" @@ -51,7 +51,7 @@ testLoop: // Ensure the limited number of most recent entries in the // inventory vector list exist. - for j := numInvVects - 1; j >= numInvVects-test.limit; j-- { + for j := numInvVects - test.limit; j < numInvVects; j++ { if !mruInvMap.Exists(invVects[j]) { t.Errorf("Exists #%d (%s) entry %s does not "+ "exist", i, test.name, *invVects[j]) @@ -61,7 +61,7 @@ testLoop: // Ensure the entries before the limited number of most recent // entries in the inventory vector list do not exist. - for j := numInvVects - test.limit - 1; j >= 0; j-- { + for j := 0; j < numInvVects-test.limit; j++ { if mruInvMap.Exists(invVects[j]) { t.Errorf("Exists #%d (%s) entry %s exists", i, test.name, *invVects[j]) diff --git a/peer/mrunoncemap.go b/peer/mrunoncemap.go new file mode 100644 index 00000000..1bf54429 --- /dev/null +++ b/peer/mrunoncemap.go @@ -0,0 +1,129 @@ +// 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 peer + +import ( + "bytes" + "container/list" + "fmt" + "sync" +) + +// mruNonceMap provides a concurrency safe map that is limited to a maximum +// number of items with eviction for the oldest entry when the limit is +// exceeded. +type mruNonceMap struct { + mtx sync.Mutex + nonceMap map[uint64]*list.Element // nearly O(1) lookups + nonceList *list.List // O(1) insert, update, delete + limit uint +} + +// String returns the map as a human-readable string. +// +// This function is safe for concurrent access. +func (m *mruNonceMap) String() string { + m.mtx.Lock() + defer m.mtx.Unlock() + + lastEntryNum := len(m.nonceMap) - 1 + curEntry := 0 + buf := bytes.NewBufferString("[") + for nonce := range m.nonceMap { + buf.WriteString(fmt.Sprintf("%d", nonce)) + if curEntry < lastEntryNum { + buf.WriteString(", ") + } + curEntry++ + } + buf.WriteString("]") + + return fmt.Sprintf("<%d>%s", m.limit, buf.String()) +} + +// Exists returns whether or not the passed nonce is in the map. +// +// This function is safe for concurrent access. +func (m *mruNonceMap) Exists(nonce uint64) bool { + m.mtx.Lock() + defer m.mtx.Unlock() + + if _, exists := m.nonceMap[nonce]; exists { + return true + } + return false +} + +// Add adds the passed nonce to the map and handles eviction of the oldest item +// if adding the new item would exceed the max limit. Adding an existing item +// makes it the most recently used item. +// +// This function is safe for concurrent access. +func (m *mruNonceMap) Add(nonce uint64) { + m.mtx.Lock() + defer m.mtx.Unlock() + + // When the limit is zero, nothing can be added to the map, so just + // return. + if m.limit == 0 { + return + } + + // When the entry already exists move it to the front of the list + // thereby marking it most recently used. + if node, exists := m.nonceMap[nonce]; exists { + m.nonceList.MoveToFront(node) + return + } + + // Evict the least recently used entry (back of the list) if the the new + // entry would exceed the size limit for the map. Also reuse the list + // node so a new one doesn't have to be allocated. + if uint(len(m.nonceMap))+1 > m.limit { + node := m.nonceList.Back() + lru := node.Value.(uint64) + + // Evict least recently used item. + delete(m.nonceMap, lru) + + // Reuse the list node of the item that was just evicted for the + // new item. + node.Value = nonce + m.nonceList.MoveToFront(node) + m.nonceMap[nonce] = node + return + } + + // The limit hasn't been reached yet, so just add the new item. + node := m.nonceList.PushFront(nonce) + m.nonceMap[nonce] = node + return +} + +// Delete deletes the passed nonce from the map (if it exists). +// +// This function is safe for concurrent access. +func (m *mruNonceMap) Delete(nonce uint64) { + m.mtx.Lock() + defer m.mtx.Unlock() + + if node, exists := m.nonceMap[nonce]; exists { + m.nonceList.Remove(node) + delete(m.nonceMap, nonce) + } +} + +// newMruNonceMap returns a new nonce map that is limited to the number of +// entries specified by limit. When the number of entries exceeds the limit, +// the oldest (least recently used) entry will be removed to make room for the +// new entry. +func newMruNonceMap(limit uint) *mruNonceMap { + m := mruNonceMap{ + nonceMap: make(map[uint64]*list.Element), + nonceList: list.New(), + limit: limit, + } + return &m +} diff --git a/peer/mrunoncemap_test.go b/peer/mrunoncemap_test.go new file mode 100644 index 00000000..bd7fb108 --- /dev/null +++ b/peer/mrunoncemap_test.go @@ -0,0 +1,152 @@ +// 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 peer + +import ( + "fmt" + "testing" +) + +// TestMruNonceMap ensures the mruNonceMap behaves as expected including +// limiting, eviction of least-recently used entries, specific entry removal, +// and existence tests. +func TestMruNonceMap(t *testing.T) { + // Create a bunch of fake nonces to use in testing the mru nonce code. + numNonces := 10 + nonces := make([]uint64, 0, numNonces) + for i := 0; i < numNonces; i++ { + nonces = append(nonces, uint64(i)) + } + + tests := []struct { + name string + limit int + }{ + {name: "limit 0", limit: 0}, + {name: "limit 1", limit: 1}, + {name: "limit 5", limit: 5}, + {name: "limit 7", limit: 7}, + {name: "limit one less than available", limit: numNonces - 1}, + {name: "limit all available", limit: numNonces}, + } + +testLoop: + for i, test := range tests { + // Create a new mru nonce map limited by the specified test + // limit and add all of the test nonces. This will cause + // evicition since there are more test nonces than the limits. + mruNonceMap := newMruNonceMap(uint(test.limit)) + for j := 0; j < numNonces; j++ { + mruNonceMap.Add(nonces[j]) + } + + // Ensure the limited number of most recent entries in the list + // exist. + for j := numNonces - test.limit; j < numNonces; j++ { + if !mruNonceMap.Exists(nonces[j]) { + t.Errorf("Exists #%d (%s) entry %d does not "+ + "exist", i, test.name, nonces[j]) + continue testLoop + } + } + + // Ensure the entries before the limited number of most recent + // entries in the list do not exist. + for j := 0; j < numNonces-test.limit; j++ { + if mruNonceMap.Exists(nonces[j]) { + t.Errorf("Exists #%d (%s) entry %d exists", i, + test.name, nonces[j]) + continue testLoop + } + } + + // Readd the entry that should currently be the least-recently + // used entry so it becomes the most-recently used entry, then + // force an eviction by adding an entry that doesn't exist and + // ensure the evicted entry is the new least-recently used + // entry. + // + // This check needs at least 2 entries. + if test.limit > 1 { + origLruIndex := numNonces - test.limit + mruNonceMap.Add(nonces[origLruIndex]) + + mruNonceMap.Add(uint64(numNonces) + 1) + + // Ensure the original lru entry still exists since it + // was updated and should've have become the mru entry. + if !mruNonceMap.Exists(nonces[origLruIndex]) { + t.Errorf("MRU #%d (%s) entry %d does not exist", + i, test.name, nonces[origLruIndex]) + continue testLoop + } + + // Ensure the entry that should've become the new lru + // entry was evicted. + newLruIndex := origLruIndex + 1 + if mruNonceMap.Exists(nonces[newLruIndex]) { + t.Errorf("MRU #%d (%s) entry %d exists", i, + test.name, nonces[newLruIndex]) + continue testLoop + } + } + + // Delete all of the entries in the list, including those that + // don't exist in the map, and ensure they no longer exist. + for j := 0; j < numNonces; j++ { + mruNonceMap.Delete(nonces[j]) + if mruNonceMap.Exists(nonces[j]) { + t.Errorf("Delete #%d (%s) entry %d exists", i, + test.name, nonces[j]) + continue testLoop + } + } + } +} + +// TestMruNonceMapStringer tests the stringized output for the mruNonceMap type. +func TestMruNonceMapStringer(t *testing.T) { + // Create a couple of fake nonces to use in testing the mru nonce + // stringer code. + nonce1 := uint64(10) + nonce2 := uint64(20) + + // Create new mru nonce map and add the nonces. + mruNonceMap := newMruNonceMap(uint(2)) + mruNonceMap.Add(nonce1) + mruNonceMap.Add(nonce2) + + // Ensure the stringer gives the expected result. Since map iteration + // is not ordered, either entry could be first, so account for both + // cases. + wantStr1 := fmt.Sprintf("<%d>[%d, %d]", 2, nonce1, nonce2) + wantStr2 := fmt.Sprintf("<%d>[%d, %d]", 2, nonce2, nonce1) + gotStr := mruNonceMap.String() + if gotStr != wantStr1 && gotStr != wantStr2 { + t.Fatalf("unexpected string representation - got %q, want %q "+ + "or %q", gotStr, wantStr1, wantStr2) + } +} + +// BenchmarkMruNonceList performs basic benchmarks on the most recently used +// nonce handling. +func BenchmarkMruNonceList(b *testing.B) { + // Create a bunch of fake nonces to use in benchmarking the mru nonce + // code. + b.StopTimer() + numNonces := 100000 + nonces := make([]uint64, 0, numNonces) + for i := 0; i < numNonces; i++ { + nonces = append(nonces, uint64(i)) + } + b.StartTimer() + + // Benchmark the add plus evicition code. + limit := 20000 + mruNonceMap := newMruNonceMap(uint(limit)) + for i := 0; i < b.N; i++ { + mruNonceMap.Add(nonces[i%numNonces]) + } +} diff --git a/peer/peer.go b/peer/peer.go new file mode 100644 index 00000000..657bebc2 --- /dev/null +++ b/peer/peer.go @@ -0,0 +1,1870 @@ +// 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 peer + +import ( + "bytes" + "container/list" + "fmt" + "io" + prand "math/rand" + "net" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/go-socks/socks" + "github.com/davecgh/go-spew/spew" +) + +const ( + // MaxProtocolVersion is the max protocol version the peer supports. + MaxProtocolVersion = 70011 + + // BlockStallTimeout is the number of seconds we will wait for a + // "block" response after we send out a "getdata" for an announced + // block before we deem the peer inactive, and disconnect it. + BlockStallTimeout = 5 * time.Second + + // outputBufferSize is the number of elements the output channels use. + outputBufferSize = 50 + + // invTrickleSize is the maximum amount of inventory to send in a single + // message when trickling inventory to remote peers. + maxInvTrickleSize = 1000 + + // maxKnownInventory is the maximum number of items to keep in the known + // inventory cache. + maxKnownInventory = 1000 + + // pingTimeout is the duration since we last sent a message requiring a + // reply before we will ping a host. + pingTimeout = 2 * time.Minute + + // negotiateTimeout is the duration of inactivity before we timeout a + // peer that hasn't completed the initial version negotiation. + negotiateTimeout = 30 * time.Second + + // idleTimeout is the duration of inactivity before we time out a peer. + idleTimeout = 5 * time.Minute + + // trickleTimeout is the duration of the ticker which trickles down the + // inventory to a peer. + trickleTimeout = 10 * time.Second +) + +var ( + // nodeCount is the total number of peer connections made since startup + // and is used to assign an id to a peer. + nodeCount int32 + + // zeroHash is the zero value hash (all zeros). It is defined as a + // convenience. + zeroHash wire.ShaHash + + // sentNonces houses the unique nonces that are generated when pushing + // version messages that are used to detect self connections. + sentNonces = newMruNonceMap(50) + + // allowSelfConns is only used to allow the tests to bypass the self + // connection detecting and disconnect logic since they intentionally + // do so for testing purposes. + allowSelfConns bool +) + +// MessageListeners defines callback function pointers to invoke with message +// listeners for a peer. Any listener which is not set to a concrete callback +// during peer initialization is ignored. Execution of multiple message +// listeners occurs serially, so one callback blocks the excution of the next. +// +// NOTE: Unless otherwise documented, these listeners must NOT directly call +// any blocking calls (such as WaitForShutdown) on the peer instance since the +// input handler goroutine blocks until the callback has completed. Doing so +// will result in a deadlock situation. +type MessageListeners struct { + // OnGetAddr is invoked when a peer receives a getaddr bitcoin message. + OnGetAddr func(p *Peer, msg *wire.MsgGetAddr) + + // OnAddr is invoked when a peer receives an addr bitcoin message. + OnAddr func(p *Peer, msg *wire.MsgAddr) + + // OnPing is invoked when a peer receives a ping bitcoin message. + OnPing func(p *Peer, msg *wire.MsgPing) + + // OnPong is invoked when a peer receives a pong bitcoin message. + OnPong func(p *Peer, msg *wire.MsgPong) + + // OnAlert is invoked when a peer receives an alert bitcoin message. + OnAlert func(p *Peer, msg *wire.MsgAlert) + + // OnMemPool is invoked when a peer receives a mempool bitcoin message. + OnMemPool func(p *Peer, msg *wire.MsgMemPool) + + // OnTx is invoked when a peer receives a tx bitcoin message. + OnTx func(p *Peer, msg *wire.MsgTx) + + // OnBlock is invoked when a peer receives a block bitcoin message. + OnBlock func(p *Peer, msg *wire.MsgBlock, buf []byte) + + // OnInv is invoked when a peer receives an inv bitcoin message. + OnInv func(p *Peer, msg *wire.MsgInv) + + // OnHeaders is invoked when a peer receives a headers bitcoin message. + OnHeaders func(p *Peer, msg *wire.MsgHeaders) + + // OnNotFound is invoked when a peer receives a notfound bitcoin + // message. + OnNotFound func(p *Peer, msg *wire.MsgNotFound) + + // OnGetData is invoked when a peer receives a getdata bitcoin message. + OnGetData func(p *Peer, msg *wire.MsgGetData) + + // OnGetBlocks is invoked when a peer receives a getblocks bitcoin + // message. + OnGetBlocks func(p *Peer, msg *wire.MsgGetBlocks) + + // OnGetHeaders is invoked when a peer receives a getheaders bitcoin + // message. + OnGetHeaders func(p *Peer, msg *wire.MsgGetHeaders) + + // OnFilterAdd is invoked when a peer receives a filteradd bitcoin message. + // Peers that do not advertise support for bloom filters and negotiate to a + // protocol version before BIP0111 will simply ignore the message while + // those that negotiate to the BIP0111 protocol version or higher will be + // immediately disconnected. + OnFilterAdd func(p *Peer, msg *wire.MsgFilterAdd) + + // OnFilterClear is invoked when a peer receives a filterclear bitcoin + // message. + // Peers that do not advertise support for bloom filters and negotiate to a + // protocol version before BIP0111 will simply ignore the message while + // those that negotiate to the BIP0111 protocol version or higher will be + // immediately disconnected. + OnFilterClear func(p *Peer, msg *wire.MsgFilterClear) + + // OnFilterLoad is invoked when a peer receives a filterload bitcoin + // message. + // Peers that do not advertise support for bloom filters and negotiate to a + // protocol version before BIP0111 will simply ignore the message while + // those that negotiate to the BIP0111 protocol version or higher will be + // immediately disconnected. + OnFilterLoad func(p *Peer, msg *wire.MsgFilterLoad) + + // OnMerkleBlock is invoked when a peer receives a merkleblock bitcoin + // message. + OnMerkleBlock func(p *Peer, msg *wire.MsgMerkleBlock) + + // OnVersion is invoked when a peer receives a version bitcoin message. + OnVersion func(p *Peer, msg *wire.MsgVersion) + + // OnVerAck is invoked when a peer receives a verack bitcoin message. + OnVerAck func(p *Peer, msg *wire.MsgVerAck) + + // OnReject is invoked when a peer receives a reject bitcoin message. + OnReject func(p *Peer, msg *wire.MsgReject) + + // OnRead is invoked when a peer receives a bitcoin message. It + // consists of the number of bytes read, the message, and whether or not + // an error in the read occurred. Typically, callers will opt to use + // the callbacks for the specific message types, however this can be + // useful for circumstances such as keeping track of server-wide byte + // counts or working with custom message types for which the peer does + // not directly provide a callback. + OnRead func(p *Peer, bytesRead int, msg wire.Message, err error) + + // OnWrite is invoked when a peer receives a bitcoin message. It + // consists of the number of bytes written, the message, and whether or + // not an error in the write occurred. This can be useful for + // circumstances such as keeping track of server-wide byte counts. + OnWrite func(p *Peer, bytesWritten int, msg wire.Message, err error) +} + +// Config is the struct to hold configuration options useful to Peer. +type Config struct { + // NewestBlock specifies a callback which provides the newest block + // details to the peer as needed. This can be nil in which case the + // peer will report a block height of 0. Typically, only full nodes + // will need to specify this. + NewestBlock ShaFunc + + // BestLocalAddress returns the best local address for a given address. + BestLocalAddress AddrFunc + + // HostToNetAddress returns the netaddress for the given host. This can be + // nil in which case the host will be parsed as an IP address. + HostToNetAddress HostToNetAddrFunc + + // Proxy specifies a SOCKS5 proxy (eg. 127.0.0.1:9050) to use for + // connections. + Proxy string + + // UserAgentName specifies the user agent name to advertise. It is + // highly recommended to specify this value. + UserAgentName string + + // UserAgentVersion specifies the user agent version to advertise. It + // is highly recommended to specify this value and that it follows the + // form "major.minor.revision" e.g. "2.6.41". + UserAgentVersion string + + // ChainParams identifies which chain parameters the peer is associated + // with. It is highly recommended to specify this field, however it can + // be omitted in which case the test network will be used. + ChainParams *chaincfg.Params + + // Services specifies which services to advertise as supported by the + // local peer. This field can be omitted in which case it will be 0 + // and therefore advertise no supported services. + Services wire.ServiceFlag + + // ProtocolVersion specifies the maximum protocol version to use and + // advertise. This field can be omitted in which case + // peer.MaxProtocolVersion will be used. + ProtocolVersion uint32 + + // Listeners houses callback functions to be invoked on receiving peer + // messages. + Listeners MessageListeners +} + +// minUint32 is a helper function to return the minimum of two uint32s. +// This avoids a math import and the need to cast to floats. +func minUint32(a, b uint32) uint32 { + if a < b { + return a + } + return b +} + +// newNetAddress attempts to extract the IP address and port from the passed +// net.Addr interface and create a bitcoin NetAddress structure using that +// information. +func newNetAddress(addr net.Addr, services wire.ServiceFlag) (*wire.NetAddress, error) { + // addr will be a net.TCPAddr when not using a proxy. + if tcpAddr, ok := addr.(*net.TCPAddr); ok { + ip := tcpAddr.IP + port := uint16(tcpAddr.Port) + na := wire.NewNetAddressIPPort(ip, port, services) + return na, nil + } + + // addr will be a socks.ProxiedAddr when using a proxy. + if proxiedAddr, ok := addr.(*socks.ProxiedAddr); ok { + ip := net.ParseIP(proxiedAddr.Host) + if ip == nil { + ip = net.ParseIP("0.0.0.0") + } + port := uint16(proxiedAddr.Port) + na := wire.NewNetAddressIPPort(ip, port, services) + return na, nil + } + + // For the most part, addr should be one of the two above cases, but + // to be safe, fall back to trying to parse the information from the + // address string as a last resort. + host, portStr, err := net.SplitHostPort(addr.String()) + if err != nil { + return nil, err + } + ip := net.ParseIP(host) + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, err + } + na := wire.NewNetAddressIPPort(ip, uint16(port), services) + return na, nil +} + +// outMsg is used to house a message to be sent along with a channel to signal +// when the message has been sent (or won't be sent due to things such as +// shutdown) +type outMsg struct { + msg wire.Message + doneChan chan struct{} +} + +// stats is the collection of stats related to a peer. +type stats struct { + statsMtx sync.RWMutex // protects all statistics below here. + timeOffset int64 + timeConnected time.Time + lastSend time.Time + lastRecv time.Time + bytesReceived uint64 + bytesSent uint64 + startingHeight int32 + lastBlock int32 + lastAnnouncedBlock *wire.ShaHash + lastPingNonce uint64 // Set to nonce if we have a pending ping. + lastPingTime time.Time // Time we sent last ping. + lastPingMicros int64 // Time for last ping to return. +} + +// StatsSnap is a snapshot of peer stats at a point in time. +type StatsSnap struct { + ID int32 + Addr string + Services wire.ServiceFlag + LastSend time.Time + LastRecv time.Time + BytesSent uint64 + BytesRecv uint64 + ConnTime time.Time + TimeOffset int64 + Version uint32 + UserAgent string + Inbound bool + StartingHeight int32 + LastBlock int32 + LastPingNonce uint64 + LastPingTime time.Time + LastPingMicros int64 +} + +// ShaFunc is a function which returns a block sha, height and error +// It is used as a callback to get newest block details. +type ShaFunc func() (sha *wire.ShaHash, height int32, err error) + +// AddrFunc is a func which takes an address and returns a related address. +type AddrFunc func(remoteAddr *wire.NetAddress) *wire.NetAddress + +// HostToNetAddrFunc is a func which takes a host, port, services and returns +// the netaddress. +type HostToNetAddrFunc func(host string, port uint16, + services wire.ServiceFlag) (*wire.NetAddress, error) + +// NOTE: The overall data flow of a peer is split into 3 goroutines. Inbound +// messages are read via the inHandler goroutine and generally dispatched to +// their own handler. For inbound data-related messages such as blocks, +// transactions, and inventory, the data is handled by the corresponding +// message handlers. The data flow for outbound messages is split into 2 +// goroutines, queueHandler and outHandler. The first, queueHandler, is used +// as a way for external entities to queue messages, by way of the QueueMessage +// function, quickly regardless of whether the peer is currently sending or not. +// It acts as the traffic cop between the external world and the actual +// goroutine which writes to the network socket. + +// Peer provides a basic concurrent safe bitcoin peer for handling bitcoin +// communications via the peer-to-peer protocol. It provides full duplex +// reading and writing, automatic handling of the initial handshake process, +// querying of usage statistics and other information about the remote peer such +// as its address, user agent, and protocol version, output message queueing, +// inventory trickling, and the ability to dynamically register and unregister +// callbacks for handling bitcoin protocol messages. +// +// Outbound messages are typically queued via QueueMessage or QueueInventory. +// QueueMessage is intended for all messages, including responses to data such +// as blocks and transactions. QueueInventory, on the other hand, is only +// intended for relaying inventory as it employs a trickling mechanism to batch +// the inventory together. However, some helper functions for pushing messages +// of specific types that typically require common special handling are +// provided as a convenience. +type Peer struct { + started int32 + connected int32 + disconnect int32 // only to be used atomically + conn net.Conn + + // These fields are set at creation time and never modified, so they are + // safe to read from concurrently without a mutex. + addr string + cfg Config + inbound bool + + flagsMtx sync.Mutex // protects the peer flags below + na *wire.NetAddress + id int32 + userAgent string + services wire.ServiceFlag + versionKnown bool + protocolVersion uint32 + versionSent bool + verAckReceived bool + + knownInventory *MruInventoryMap + prevGetBlocksMtx sync.Mutex + prevGetBlocksBegin *wire.ShaHash + prevGetBlocksStop *wire.ShaHash + prevGetHdrsMtx sync.Mutex + prevGetHdrsBegin *wire.ShaHash + prevGetHdrsStop *wire.ShaHash + outputQueue chan outMsg + sendQueue chan outMsg + sendDoneQueue chan struct{} + outputInvChan chan *wire.InvVect + queueQuit chan struct{} + quit chan struct{} + + stats +} + +// String returns the peer's address and directionality as a human-readable +// string. +// +// This function is safe for concurrent access. +func (p *Peer) String() string { + return fmt.Sprintf("%s (%s)", p.addr, directionString(p.inbound)) +} + +// UpdateLastBlockHeight updates the last known block for the peer. +// +// This function is safe for concurrent access. +func (p *Peer) UpdateLastBlockHeight(newHeight int32) { + p.statsMtx.Lock() + log.Tracef("Updating last block height of peer %v from %v to %v", + p.addr, p.lastBlock, newHeight) + p.lastBlock = int32(newHeight) + p.statsMtx.Unlock() +} + +// UpdateLastAnnouncedBlock updates meta-data about the last block sha this +// peer is known to have announced. +// +// This function is safe for concurrent access. +func (p *Peer) UpdateLastAnnouncedBlock(blkSha *wire.ShaHash) { + log.Tracef("Updating last blk for peer %v, %v", p.addr, blkSha) + + p.statsMtx.Lock() + p.lastAnnouncedBlock = blkSha + p.statsMtx.Unlock() +} + +// AddKnownInventory adds the passed inventory to the cache of known inventory +// for the peer. +// +// This function is safe for concurrent access. +func (p *Peer) AddKnownInventory(invVect *wire.InvVect) { + p.knownInventory.Add(invVect) +} + +// StatsSnapshot returns a snapshot of the current peer flags and statistics. +// +// This function is safe for concurrent access. +func (p *Peer) StatsSnapshot() *StatsSnap { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + p.flagsMtx.Lock() + id := p.id + addr := p.addr + userAgent := p.userAgent + services := p.services + protocolVersion := p.protocolVersion + p.flagsMtx.Unlock() + + // Get a copy of all relevant flags and stats. + return &StatsSnap{ + ID: id, + Addr: addr, + UserAgent: userAgent, + Services: services, + LastSend: p.lastSend, + LastRecv: p.lastRecv, + BytesSent: p.bytesSent, + BytesRecv: p.bytesReceived, + ConnTime: p.timeConnected, + TimeOffset: p.timeOffset, + Version: protocolVersion, + Inbound: p.inbound, + StartingHeight: p.startingHeight, + LastBlock: p.lastBlock, + LastPingNonce: p.lastPingNonce, + LastPingMicros: p.lastPingMicros, + LastPingTime: p.lastPingTime, + } +} + +// ID returns the peer id. +// +// This function is safe for concurrent access. +func (p *Peer) ID() int32 { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.id +} + +// NA returns the peer network address. +// +// This function is safe for concurrent access. +func (p *Peer) NA() *wire.NetAddress { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.na +} + +// Addr returns the peer address. +// +// This function is safe for concurrent access. +func (p *Peer) Addr() string { + // The address doesn't change after initialization, therefore it is not + // protected by a mutex. + return p.addr +} + +// Inbound returns whether the peer is inbound. +// +// This function is safe for concurrent access. +func (p *Peer) Inbound() bool { + return p.inbound +} + +// Services returns the services flag of the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) Services() wire.ServiceFlag { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.services +} + +// UserAgent returns the user agent of the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) UserAgent() string { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.userAgent +} + +// LastAnnouncedBlock returns the last announced block of the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastAnnouncedBlock() *wire.ShaHash { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastAnnouncedBlock +} + +// LastPingNonce returns the last ping nonce of the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastPingNonce() uint64 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastPingNonce +} + +// LastPingTime returns the last ping time of the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastPingTime() time.Time { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastPingTime +} + +// LastPingMicros returns the last ping micros of the remote peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastPingMicros() int64 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastPingMicros +} + +// VersionKnown returns the whether or not the version of a peer is known +// locally. +// +// This function is safe for concurrent access. +func (p *Peer) VersionKnown() bool { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.versionKnown +} + +// VerAckReceived returns whether or not a verack message was received by the +// peer. +// +// This function is safe for concurrent access. +func (p *Peer) VerAckReceived() bool { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.verAckReceived +} + +// ProtocolVersion returns the peer protocol version. +// +// This function is safe for concurrent access. +func (p *Peer) ProtocolVersion() uint32 { + p.flagsMtx.Lock() + defer p.flagsMtx.Unlock() + + return p.protocolVersion +} + +// LastBlock returns the last block of the peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastBlock() int32 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastBlock +} + +// LastSend returns the last send time of the peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastSend() time.Time { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastSend +} + +// LastRecv returns the last recv time of the peer. +// +// This function is safe for concurrent access. +func (p *Peer) LastRecv() time.Time { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.lastRecv +} + +// BytesSent returns the total number of bytes sent by the peer. +// +// This function is safe for concurrent access. +func (p *Peer) BytesSent() uint64 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.bytesSent +} + +// BytesReceived returns the total number of bytes received by the peer. +// +// This function is safe for concurrent access. +func (p *Peer) BytesReceived() uint64 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.bytesReceived +} + +// TimeConnected returns the time at which the peer connected. +// +// This function is safe for concurrent access. +func (p *Peer) TimeConnected() time.Time { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.timeConnected +} + +// TimeOffset returns the number of seconds the local time was offset from the +// time the peer reported during the initial negotiation phase. Negative values +// indicate the remote peer's time is before the local time. +// +// This function is safe for concurrent access. +func (p *Peer) TimeOffset() int64 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.timeOffset +} + +// StartingHeight returns the last known height the peer reported during the +// initial negotiation phase. +// +// This function is safe for concurrent access. +func (p *Peer) StartingHeight() int32 { + p.statsMtx.RLock() + defer p.statsMtx.RUnlock() + + return p.startingHeight +} + +// pushVersionMsg sends a version message to the connected peer using the +// current state. +func (p *Peer) pushVersionMsg() error { + var blockNum int32 + if p.cfg.NewestBlock != nil { + var err error + _, blockNum, err = p.cfg.NewestBlock() + if err != nil { + return err + } + } + + theirNa := p.na + + // If we are behind a proxy and the connection comes from the proxy then + // we return an unroutable address as their address. This is to prevent + // leaking the tor proxy address. + if p.cfg.Proxy != "" { + proxyaddress, _, err := net.SplitHostPort(p.cfg.Proxy) + // invalid proxy means poorly configured, be on the safe side. + if err != nil || p.na.IP.String() == proxyaddress { + theirNa = &wire.NetAddress{ + Timestamp: time.Now(), + IP: net.IP([]byte{0, 0, 0, 0}), + } + } + } + + // TODO(tuxcanfly): In case BestLocalAddress is nil, ourNA defaults to + // remote NA, which is wrong. Need to fix this. + ourNA := p.na + if p.cfg.BestLocalAddress != nil { + ourNA = p.cfg.BestLocalAddress(p.na) + } + + // Generate a unique nonce for this peer so self connections can be + // detected. This is accomplished by adding it to a size-limited map of + // recently seen nonces. + nonce, err := wire.RandomUint64() + if err != nil { + fmt.Println(err) + return err + } + sentNonces.Add(nonce) + + // Version message. + msg := wire.NewMsgVersion(ourNA, theirNa, nonce, int32(blockNum)) + msg.AddUserAgent(p.cfg.UserAgentName, p.cfg.UserAgentVersion) + + // XXX: bitcoind appears to always enable the full node services flag + // of the remote peer netaddress field in the version message regardless + // of whether it knows it supports it or not. Also, bitcoind sets + // the services field of the local peer to 0 regardless of support. + // + // Realistically, this should be set as follows: + // - For outgoing connections: + // - Set the local netaddress services to what the local peer + // actually supports + // - Set the remote netaddress services to 0 to indicate no services + // as they are still unknown + // - For incoming connections: + // - Set the local netaddress services to what the local peer + // actually supports + // - Set the remote netaddress services to the what was advertised by + // by the remote peer in its version message + msg.AddrYou.Services = wire.SFNodeNetwork + + // Advertise the services flag + msg.Services = p.cfg.Services + + // Advertise our max supported protocol version. + msg.ProtocolVersion = int32(p.ProtocolVersion()) + + p.QueueMessage(msg, nil) + return nil +} + +// PushAddrMsg sends an addr message to the connected peer using the provided +// addresses. This function is useful over manually sending the message via +// QueueMessage since it automatically limits the addresses to the maximum +// number allowed by the message and randomizes the chosen addresses when there +// are too many. No message will be sent if there are no entries in the +// provided addresses slice. +// It is safe for concurrent access. +func (p *Peer) PushAddrMsg(addresses []*wire.NetAddress) ([]*wire.NetAddress, error) { + // Nothing to send. + if len(addresses) == 0 { + return nil, nil + } + + r := prand.New(prand.NewSource(time.Now().UnixNano())) + numAdded := 0 + msg := wire.NewMsgAddr() + for _, na := range addresses { + // Randomize the list with the remaining addresses when the + // max addresses limit has been reached. + if numAdded == wire.MaxAddrPerMsg { + msg.AddrList[r.Intn(wire.MaxAddrPerMsg)] = na + continue + } + + // Add the address to the message. + err := msg.AddAddress(na) + if err != nil { + return nil, err + } + numAdded++ + } + if numAdded > 0 { + p.QueueMessage(msg, nil) + } + return msg.AddrList, nil +} + +// PushGetBlocksMsg sends a getblocks message for the provided block locator +// and stop hash. It will ignore back-to-back duplicate requests. +// +// This function is safe for concurrent access. +func (p *Peer) PushGetBlocksMsg(locator blockchain.BlockLocator, stopHash *wire.ShaHash) error { + // Extract the begin hash from the block locator, if one was specified, + // to use for filtering duplicate getblocks requests. + var beginHash *wire.ShaHash + if len(locator) > 0 { + beginHash = locator[0] + } + + // Filter duplicate getblocks requests. + p.prevGetBlocksMtx.Lock() + isDuplicate := p.prevGetBlocksStop != nil && p.prevGetBlocksBegin != nil && + beginHash != nil && stopHash.IsEqual(p.prevGetBlocksStop) && + beginHash.IsEqual(p.prevGetBlocksBegin) + p.prevGetBlocksMtx.Unlock() + + if isDuplicate { + log.Tracef("Filtering duplicate [getblocks] with begin "+ + "hash %v, stop hash %v", beginHash, stopHash) + return nil + } + + // Construct the getblocks request and queue it to be sent. + msg := wire.NewMsgGetBlocks(stopHash) + for _, hash := range locator { + err := msg.AddBlockLocatorHash(hash) + if err != nil { + return err + } + } + p.QueueMessage(msg, nil) + + // Update the previous getblocks request information for filtering + // duplicates. + p.prevGetBlocksMtx.Lock() + p.prevGetBlocksBegin = beginHash + p.prevGetBlocksStop = stopHash + p.prevGetBlocksMtx.Unlock() + return nil +} + +// PushGetHeadersMsg sends a getblocks message for the provided block locator +// and stop hash. It will ignore back-to-back duplicate requests. +// +// This function is safe for concurrent access. +func (p *Peer) PushGetHeadersMsg(locator blockchain.BlockLocator, stopHash *wire.ShaHash) error { + // Extract the begin hash from the block locator, if one was specified, + // to use for filtering duplicate getheaders requests. + var beginHash *wire.ShaHash + if len(locator) > 0 { + beginHash = locator[0] + } + + // Filter duplicate getheaders requests. + p.prevGetHdrsMtx.Lock() + isDuplicate := p.prevGetHdrsStop != nil && p.prevGetHdrsBegin != nil && + beginHash != nil && stopHash.IsEqual(p.prevGetHdrsStop) && + beginHash.IsEqual(p.prevGetHdrsBegin) + p.prevGetHdrsMtx.Unlock() + + if isDuplicate { + log.Tracef("Filtering duplicate [getheaders] with begin "+ + "hash %v", beginHash) + return nil + } + + // Construct the getheaders request and queue it to be sent. + msg := wire.NewMsgGetHeaders() + msg.HashStop = *stopHash + for _, hash := range locator { + err := msg.AddBlockLocatorHash(hash) + if err != nil { + return err + } + } + p.QueueMessage(msg, nil) + + // Update the previous getheaders request information for filtering + // duplicates. + p.prevGetHdrsMtx.Lock() + p.prevGetHdrsBegin = beginHash + p.prevGetHdrsStop = stopHash + p.prevGetHdrsMtx.Unlock() + return nil +} + +// PushRejectMsg sends a reject message for the provided command, reject code, +// reject reason, and hash. The hash will only be used when the command is a tx +// or block and should be nil in other cases. The wait parameter will cause the +// function to block until the reject message has actually been sent. +// +// This function is safe for concurrent access. +func (p *Peer) PushRejectMsg(command string, code wire.RejectCode, reason string, hash *wire.ShaHash, wait bool) { + // Don't bother sending the reject message if the protocol version + // is too low. + if p.VersionKnown() && p.ProtocolVersion() < wire.RejectVersion { + return + } + + msg := wire.NewMsgReject(command, code, reason) + if command == wire.CmdTx || command == wire.CmdBlock { + if hash == nil { + log.Warnf("Sending a reject message for command "+ + "type %v which should have specified a hash "+ + "but does not", command) + hash = &zeroHash + } + msg.Hash = *hash + } + + // Send the message without waiting if the caller has not requested it. + if !wait { + p.QueueMessage(msg, nil) + return + } + + // Send the message and block until it has been sent before returning. + doneChan := make(chan struct{}, 1) + p.QueueMessage(msg, doneChan) + <-doneChan +} + +// handleVersionMsg is invoked when a peer receives a version bitcoin message +// and is used to negotiate the protocol version details as well as kick start +// the communications. +func (p *Peer) handleVersionMsg(msg *wire.MsgVersion) { + // Detect self connections. + if !allowSelfConns && sentNonces.Exists(msg.Nonce) { + log.Debugf("Disconnecting peer connected to self %s", p) + p.Disconnect() + return + } + + // Notify and disconnect clients that have a protocol version that is + // too old. + if msg.ProtocolVersion < int32(wire.MultipleAddressVersion) { + // Send a reject message indicating the protocol version is + // obsolete and wait for the message to be sent before + // disconnecting. + reason := fmt.Sprintf("protocol version must be %d or greater", + wire.MultipleAddressVersion) + p.PushRejectMsg(msg.Command(), wire.RejectObsolete, reason, + nil, true) + p.Disconnect() + return + } + + // Limit to one version message per peer. + // No read lock is necessary because versionKnown is not written to in any + // other goroutine + if p.versionKnown { + log.Errorf("Only one version message per peer is allowed %s.", + p) + + // Send an reject message indicating the version message was + // incorrectly sent twice and wait for the message to be sent + // before disconnecting. + p.PushRejectMsg(msg.Command(), wire.RejectDuplicate, + "duplicate version message", nil, true) + + p.Disconnect() + return + } + + // Updating a bunch of stats. + p.statsMtx.Lock() + p.lastBlock = msg.LastBlock + p.startingHeight = msg.LastBlock + // Set the peer's time offset. + p.timeOffset = msg.Timestamp.Unix() - time.Now().Unix() + p.statsMtx.Unlock() + + // Negotiate the protocol version. + p.flagsMtx.Lock() + p.protocolVersion = minUint32(p.protocolVersion, uint32(msg.ProtocolVersion)) + p.versionKnown = true + log.Debugf("Negotiated protocol version %d for peer %s", + p.protocolVersion, p) + // Set the peer's ID. + p.id = atomic.AddInt32(&nodeCount, 1) + // Set the supported services for the peer to what the remote peer + // advertised. + p.services = msg.Services + // Set the remote peer's user agent. + p.userAgent = msg.UserAgent + p.flagsMtx.Unlock() + + // Inbound connections. + if p.inbound { + // Set up a NetAddress for the peer to be used with AddrManager. + // We only do this inbound because outbound set this up + // at connection time and no point recomputing. + na, err := newNetAddress(p.conn.RemoteAddr(), p.services) + if err != nil { + log.Errorf("Can't get remote address: %v", err) + p.Disconnect() + return + } + p.na = na + + // Send version. + err = p.pushVersionMsg() + if err != nil { + log.Errorf("Can't send version message to %s: %v", + p, err) + p.Disconnect() + return + } + } + + // Send verack. + p.QueueMessage(wire.NewMsgVerAck(), nil) +} + +// isValidBIP0111 is a helper function for the bloom filter commands to check +// BIP0111 compliance. +func (p *Peer) isValidBIP0111(cmd string) bool { + if p.Services()&wire.SFNodeBloom != wire.SFNodeBloom { + if p.ProtocolVersion() >= wire.BIP0111Version { + log.Debugf("%s sent an unsupported %s "+ + "request -- disconnecting", p, cmd) + p.Disconnect() + } else { + log.Debugf("Ignoring %s request from %s -- bloom "+ + "support is disabled", cmd, p) + } + return false + } + + return true +} + +// handlePingMsg is invoked when a peer receives a ping bitcoin message. For +// recent clients (protocol version > BIP0031Version), it replies with a pong +// message. For older clients, it does nothing and anything other than failure +// is considered a successful ping. +func (p *Peer) handlePingMsg(msg *wire.MsgPing) { + // Only reply with pong if the message is from a new enough client. + if p.ProtocolVersion() > wire.BIP0031Version { + // Include nonce from ping so pong can be identified. + p.QueueMessage(wire.NewMsgPong(msg.Nonce), nil) + } +} + +// handlePongMsg is invoked when a peer receives a pong bitcoin message. It +// updates the ping statistics as required for recent clients (protocol +// version > BIP0031Version). There is no effect for older clients or when a +// ping was not previously sent. +func (p *Peer) handlePongMsg(msg *wire.MsgPong) { + p.statsMtx.Lock() + defer p.statsMtx.Unlock() + + // Arguably we could use a buffered channel here sending data + // in a fifo manner whenever we send a ping, or a list keeping track of + // the times of each ping. For now we just make a best effort and + // only record stats if it was for the last ping sent. Any preceding + // and overlapping pings will be ignored. It is unlikely to occur + // without large usage of the ping rpc call since we ping infrequently + // enough that if they overlap we would have timed out the peer. + if p.ProtocolVersion() > wire.BIP0031Version && p.lastPingNonce != 0 && + msg.Nonce == p.lastPingNonce { + + p.lastPingMicros = time.Now().Sub(p.lastPingTime).Nanoseconds() + p.lastPingMicros /= 1000 // convert to usec. + p.lastPingNonce = 0 + } +} + +// readMessage reads the next bitcoin message from the peer with logging. +func (p *Peer) readMessage() (wire.Message, []byte, error) { + n, msg, buf, err := wire.ReadMessageN(p.conn, p.ProtocolVersion(), + p.cfg.ChainParams.Net) + p.statsMtx.Lock() + p.bytesReceived += uint64(n) + p.statsMtx.Unlock() + if p.cfg.Listeners.OnRead != nil { + p.cfg.Listeners.OnRead(p, n, msg, err) + } + if err != nil { + return nil, nil, err + } + + // Use closures to log expensive operations so they are only run when + // the logging level requires it. + log.Debugf("%v", newLogClosure(func() string { + // Debug summary of message. + summary := messageSummary(msg) + if len(summary) > 0 { + summary = " (" + summary + ")" + } + return fmt.Sprintf("Received %v%s from %s", + msg.Command(), summary, p) + })) + log.Tracef("%v", newLogClosure(func() string { + return spew.Sdump(msg) + })) + log.Tracef("%v", newLogClosure(func() string { + return spew.Sdump(buf) + })) + + return msg, buf, nil +} + +// writeMessage sends a bitcoin message to the peer with logging. +func (p *Peer) writeMessage(msg wire.Message) { + // Don't do anything if we're disconnecting. + if atomic.LoadInt32(&p.disconnect) != 0 { + return + } + if !p.VersionKnown() { + switch msg.(type) { + case *wire.MsgVersion: + // This is OK. + case *wire.MsgReject: + // This is OK. + default: + // Drop all messages other than version and reject if + // the handshake has not already been done. + return + } + } + + // Use closures to log expensive operations so they are only run when + // the logging level requires it. + log.Debugf("%v", newLogClosure(func() string { + // Debug summary of message. + summary := messageSummary(msg) + if len(summary) > 0 { + summary = " (" + summary + ")" + } + return fmt.Sprintf("Sending %v%s to %s", msg.Command(), + summary, p) + })) + log.Tracef("%v", newLogClosure(func() string { + return spew.Sdump(msg) + })) + log.Tracef("%v", newLogClosure(func() string { + var buf bytes.Buffer + err := wire.WriteMessage(&buf, msg, p.ProtocolVersion(), + p.cfg.ChainParams.Net) + if err != nil { + return err.Error() + } + return spew.Sdump(buf.Bytes()) + })) + + // Write the message to the peer. + n, err := wire.WriteMessageN(p.conn, msg, p.ProtocolVersion(), + p.cfg.ChainParams.Net) + p.statsMtx.Lock() + p.bytesSent += uint64(n) + p.statsMtx.Unlock() + if p.cfg.Listeners.OnWrite != nil { + p.cfg.Listeners.OnWrite(p, n, msg, err) + } + if err != nil { + p.Disconnect() + log.Errorf("Can't send message to %s: %v", p, err) + return + } +} + +// isAllowedByRegression returns whether or not the passed error is allowed by +// regression tests without disconnecting the peer. In particular, regression +// tests need to be allowed to send malformed messages without the peer being +// disconnected. +func (p *Peer) isAllowedByRegression(err error) bool { + // Don't allow the error if it's not specifically a malformed message + // error. + if _, ok := err.(*wire.MessageError); !ok { + return false + } + + // Don't allow the error if it's not coming from localhost or the + // hostname can't be determined for some reason. + host, _, err := net.SplitHostPort(p.addr) + if err != nil { + return false + } + + if host != "127.0.0.1" && host != "localhost" { + return false + } + + // Allowed if all checks passed. + return true +} + +// isRegTestNetwork returns whether or not the peer is running on the regression +// test network. +func (p *Peer) isRegTestNetwork() bool { + return p.cfg.ChainParams.Net == wire.TestNet +} + +// shouldHandleReadError returns whether or not the passed error, which is +// expected to have come from reading from the remote peer in the inHandler, +// should be logged and responded to with a reject message. +func (p *Peer) shouldHandleReadError(err error) bool { + // No logging or reject message when the peer is being forcibly + // disconnected. + if atomic.LoadInt32(&p.disconnect) != 0 { + return false + } + + // No logging or reject message when the remote peer has been + // disconnected. + if err == io.EOF { + return false + } + if opErr, ok := err.(*net.OpError); ok && !opErr.Temporary() { + return false + } + + return true +} + +// inHandler handles all incoming messages for the peer. It must be run as a +// goroutine. +func (p *Peer) inHandler() { + // Peers must complete the initial version negotiation within a shorter + // timeframe than a general idle timeout. The timer is then reset below + // to idleTimeout for all future messages. + idleTimer := time.AfterFunc(negotiateTimeout, func() { + if p.VersionKnown() { + log.Warnf("Peer %s no answer for %s -- disconnecting", + p, idleTimeout) + } else { + log.Debugf("Peer %s no valid version message for %s -- "+ + "disconnecting", p, negotiateTimeout) + } + p.Disconnect() + }) +out: + for atomic.LoadInt32(&p.disconnect) == 0 { + // Read a message and stop the idle timer as soon as the read + // is done. The timer is reset below for the next iteration if + // needed. + rmsg, buf, err := p.readMessage() + idleTimer.Stop() + if err != nil { + // In order to allow regression tests with malformed + // messages, don't disconnect the peer when we're in + // regression test mode and the error is one of the + // allowed errors. + if p.isRegTestNetwork() && p.isAllowedByRegression(err) { + log.Errorf("Allowed regression test error "+ + "from %s: %v", p, err) + idleTimer.Reset(idleTimeout) + continue + } + + // Only log the error and send reject message if the + // local peer is not forcibly disconnecting and the + // remote peer has not disconnected. + if p.shouldHandleReadError(err) { + errMsg := fmt.Sprintf("Can't read message "+ + "from %s: %v", p, err) + log.Errorf(errMsg) + + // Push a reject message for the malformed + // message and wait for the message to be sent + // before disconnecting. + // + // NOTE: Ideally this would include the command + // in the header if at least that much of the + // message was valid, but that is not currently + // exposed by wire, so just used malformed for + // the command. + p.PushRejectMsg("malformed", + wire.RejectMalformed, errMsg, nil, true) + } + break out + } + p.statsMtx.Lock() + p.lastRecv = time.Now() + p.statsMtx.Unlock() + + // Ensure version message comes first. + if vmsg, ok := rmsg.(*wire.MsgVersion); !ok && !p.VersionKnown() { + errStr := "A version message must precede all others" + log.Errorf(errStr) + + // Push a reject message and wait for the message to be + // sent before disconnecting. + p.PushRejectMsg(vmsg.Command(), wire.RejectMalformed, + errStr, nil, true) + break out + } + + // Handle each supported message type. + switch msg := rmsg.(type) { + case *wire.MsgVersion: + p.handleVersionMsg(msg) + if p.cfg.Listeners.OnVersion != nil { + p.cfg.Listeners.OnVersion(p, msg) + } + + case *wire.MsgVerAck: + p.flagsMtx.Lock() + versionSent := p.versionSent + p.flagsMtx.Unlock() + if !versionSent { + log.Infof("Received 'verack' from peer %v "+ + "before version was sent -- "+ + "disconnecting", p) + break out + } + + // No read lock is necessary because verAckReceived is + // not written to in any other goroutine. + if p.verAckReceived { + log.Infof("Already received 'verack' from "+ + "peer %v -- disconnecting", p) + break out + } + p.flagsMtx.Lock() + p.verAckReceived = true + p.flagsMtx.Unlock() + if p.cfg.Listeners.OnVerAck != nil { + p.cfg.Listeners.OnVerAck(p, msg) + } + + case *wire.MsgGetAddr: + if p.cfg.Listeners.OnGetAddr != nil { + p.cfg.Listeners.OnGetAddr(p, msg) + } + + case *wire.MsgAddr: + if p.cfg.Listeners.OnAddr != nil { + p.cfg.Listeners.OnAddr(p, msg) + } + + case *wire.MsgPing: + p.handlePingMsg(msg) + if p.cfg.Listeners.OnPing != nil { + p.cfg.Listeners.OnPing(p, msg) + } + + case *wire.MsgPong: + p.handlePongMsg(msg) + if p.cfg.Listeners.OnPong != nil { + p.cfg.Listeners.OnPong(p, msg) + } + + case *wire.MsgAlert: + // Note: The reference client currently bans peers that send alerts + // not signed with its key. We could verify against their key, but + // since the reference client is currently unwilling to support + // other implementions' alert messages, we will not relay theirs. + if p.cfg.Listeners.OnAlert != nil { + p.cfg.Listeners.OnAlert(p, msg) + } + + case *wire.MsgMemPool: + if p.cfg.Listeners.OnMemPool != nil { + p.cfg.Listeners.OnMemPool(p, msg) + } + + case *wire.MsgTx: + if p.cfg.Listeners.OnTx != nil { + p.cfg.Listeners.OnTx(p, msg) + } + + case *wire.MsgBlock: + if p.cfg.Listeners.OnBlock != nil { + p.cfg.Listeners.OnBlock(p, msg, buf) + } + + case *wire.MsgInv: + if p.cfg.Listeners.OnInv != nil { + p.cfg.Listeners.OnInv(p, msg) + } + + case *wire.MsgHeaders: + if p.cfg.Listeners.OnHeaders != nil { + p.cfg.Listeners.OnHeaders(p, msg) + } + + case *wire.MsgNotFound: + if p.cfg.Listeners.OnNotFound != nil { + p.cfg.Listeners.OnNotFound(p, msg) + } + + case *wire.MsgGetData: + if p.cfg.Listeners.OnGetData != nil { + p.cfg.Listeners.OnGetData(p, msg) + } + + case *wire.MsgGetBlocks: + if p.cfg.Listeners.OnGetBlocks != nil { + p.cfg.Listeners.OnGetBlocks(p, msg) + } + + case *wire.MsgGetHeaders: + if p.cfg.Listeners.OnGetHeaders != nil { + p.cfg.Listeners.OnGetHeaders(p, msg) + } + + case *wire.MsgFilterAdd: + if p.isValidBIP0111(msg.Command()) && p.cfg.Listeners.OnFilterAdd != nil { + p.cfg.Listeners.OnFilterAdd(p, msg) + } + + case *wire.MsgFilterClear: + if p.isValidBIP0111(msg.Command()) && p.cfg.Listeners.OnFilterClear != nil { + p.cfg.Listeners.OnFilterClear(p, msg) + } + + case *wire.MsgFilterLoad: + if p.isValidBIP0111(msg.Command()) && p.cfg.Listeners.OnFilterLoad != nil { + p.cfg.Listeners.OnFilterLoad(p, msg) + } + + case *wire.MsgMerkleBlock: + if p.cfg.Listeners.OnMerkleBlock != nil { + p.cfg.Listeners.OnMerkleBlock(p, msg) + } + + case *wire.MsgReject: + if p.cfg.Listeners.OnReject != nil { + p.cfg.Listeners.OnReject(p, msg) + } + + default: + log.Debugf("Received unhandled message of type %v:", + rmsg.Command()) + } + + // A message was received so reset the idle timer. + idleTimer.Reset(idleTimeout) + } + + // Ensure the idle timer is stopped to avoid leaking the resource. + idleTimer.Stop() + + // Ensure connection is closed. + p.Disconnect() + + log.Tracef("Peer input handler done for %s", p) +} + +// queueHandler handles the queueing of outgoing data for the peer. This runs +// as a muxer for various sources of input so we can ensure that server and +// peer handlers will not block on us sending a message. +// We then pass the data on to outHandler to be actually written. +func (p *Peer) queueHandler() { + pendingMsgs := list.New() + invSendQueue := list.New() + trickleTicker := time.NewTicker(trickleTimeout) + defer trickleTicker.Stop() + + // We keep the waiting flag so that we know if we have a message queued + // to the outHandler or not. We could use the presence of a head of + // the list for this but then we have rather racy concerns about whether + // it has gotten it at cleanup time - and thus who sends on the + // message's done channel. To avoid such confusion we keep a different + // flag and pendingMsgs only contains messages that we have not yet + // passed to outHandler. + waiting := false + + // To avoid duplication below. + queuePacket := func(msg outMsg, list *list.List, waiting bool) bool { + if !waiting { + p.sendQueue <- msg + } else { + list.PushBack(msg) + } + // we are always waiting now. + return true + } +out: + for { + select { + case msg := <-p.outputQueue: + waiting = queuePacket(msg, pendingMsgs, waiting) + + // This channel is notified when a message has been sent across + // the network socket. + case <-p.sendDoneQueue: + // No longer waiting if there are no more messages + // in the pending messages queue. + next := pendingMsgs.Front() + if next == nil { + waiting = false + continue + } + + // Notify the outHandler about the next item to + // asynchronously send. + val := pendingMsgs.Remove(next) + p.sendQueue <- val.(outMsg) + + case iv := <-p.outputInvChan: + // No handshake? They'll find out soon enough. + if p.VersionKnown() { + invSendQueue.PushBack(iv) + } + + case <-trickleTicker.C: + // Don't send anything if we're disconnecting or there + // is no queued inventory. + // version is known if send queue has any entries. + if atomic.LoadInt32(&p.disconnect) != 0 || + invSendQueue.Len() == 0 { + continue + } + + // Create and send as many inv messages as needed to + // drain the inventory send queue. + invMsg := wire.NewMsgInvSizeHint(uint(invSendQueue.Len())) + for e := invSendQueue.Front(); e != nil; e = invSendQueue.Front() { + iv := invSendQueue.Remove(e).(*wire.InvVect) + + // Don't send inventory that became known after + // the initial check. + if p.knownInventory.Exists(iv) { + continue + } + + invMsg.AddInvVect(iv) + if len(invMsg.InvList) >= maxInvTrickleSize { + waiting = queuePacket( + outMsg{msg: invMsg}, + pendingMsgs, waiting) + invMsg = wire.NewMsgInvSizeHint(uint(invSendQueue.Len())) + } + + // Add the inventory that is being relayed to + // the known inventory for the peer. + p.AddKnownInventory(iv) + } + if len(invMsg.InvList) > 0 { + waiting = queuePacket(outMsg{msg: invMsg}, + pendingMsgs, waiting) + } + + case <-p.quit: + break out + } + } + + // Drain any wait channels before we go away so we don't leave something + // waiting for us. + for e := pendingMsgs.Front(); e != nil; e = pendingMsgs.Front() { + val := pendingMsgs.Remove(e) + msg := val.(outMsg) + if msg.doneChan != nil { + msg.doneChan <- struct{}{} + } + } +cleanup: + for { + select { + case msg := <-p.outputQueue: + if msg.doneChan != nil { + msg.doneChan <- struct{}{} + } + case <-p.outputInvChan: + // Just drain channel + // sendDoneQueue is buffered so doesn't need draining. + default: + break cleanup + } + } + close(p.queueQuit) + log.Tracef("Peer queue handler done for %s", p) +} + +// outHandler handles all outgoing messages for the peer. It must be run as a +// goroutine. It uses a buffered channel to serialize output messages while +// allowing the sender to continue running asynchronously. +func (p *Peer) outHandler() { + pingTimer := time.AfterFunc(pingTimeout, func() { + nonce, err := wire.RandomUint64() + if err != nil { + log.Errorf("Not sending ping on timeout to %s: %v", + p, err) + return + } + p.QueueMessage(wire.NewMsgPing(nonce), nil) + }) +out: + for { + select { + case msg := <-p.sendQueue: + // Reset the ping timer for messages that expect a + // reply since we only want to send pings when we would + // otherwise not receive a reply from the peer. The + // getblocks and inv messages are specifically not + // counted here since there is no guarantee they will + // result in a reply. + reset := true + switch m := msg.msg.(type) { + case *wire.MsgVersion: + // Expects a verack message. Also set the flag + // which indicates the version has been sent. + p.flagsMtx.Lock() + p.versionSent = true + p.flagsMtx.Unlock() + + case *wire.MsgGetAddr: + // Expects an addr message. + + case *wire.MsgPing: + // Expects a pong message in later protocol + // versions. Also set up statistics. + if p.ProtocolVersion() > wire.BIP0031Version { + p.statsMtx.Lock() + p.lastPingNonce = m.Nonce + p.lastPingTime = time.Now() + p.statsMtx.Unlock() + } + + case *wire.MsgMemPool: + // Expects an inv message. + + case *wire.MsgGetData: + // Expects a block, tx, or notfound message. + + case *wire.MsgGetHeaders: + // Expects a headers message. + + default: + // Not one of the above, no sure reply. + // We want to ping if nothing else + // interesting happens. + reset = false + } + + if reset { + pingTimer.Reset(pingTimeout) + } + p.writeMessage(msg.msg) + p.statsMtx.Lock() + p.lastSend = time.Now() + p.statsMtx.Unlock() + if msg.doneChan != nil { + msg.doneChan <- struct{}{} + } + p.sendDoneQueue <- struct{}{} + + case <-p.quit: + break out + } + } + + pingTimer.Stop() + + <-p.queueQuit + + // Drain any wait channels before we go away so we don't leave something + // waiting for us. We have waited on queueQuit and thus we can be sure + // that we will not miss anything sent on sendQueue. +cleanup: + for { + select { + case msg := <-p.sendQueue: + if msg.doneChan != nil { + msg.doneChan <- struct{}{} + } + // no need to send on sendDoneQueue since queueHandler + // has been waited on and already exited. + default: + break cleanup + } + } + log.Tracef("Peer output handler done for %s", p) +} + +// QueueMessage adds the passed bitcoin message to the peer send queue. +// +// This function is safe for concurrent access. +func (p *Peer) QueueMessage(msg wire.Message, doneChan chan struct{}) { + // Avoid risk of deadlock if goroutine already exited. The goroutine + // we will be sending to hangs around until it knows for a fact that + // it is marked as disconnected and *then* it drains the channels. + if !p.Connected() { + if doneChan != nil { + go func() { + doneChan <- struct{}{} + }() + } + return + } + p.outputQueue <- outMsg{msg: msg, doneChan: doneChan} +} + +// QueueInventory adds the passed inventory to the inventory send queue which +// might not be sent right away, rather it is trickled to the peer in batches. +// Inventory that the peer is already known to have is ignored. +// +// This function is safe for concurrent access. +func (p *Peer) QueueInventory(invVect *wire.InvVect) { + // Don't add the inventory to the send queue if the peer is already + // known to have it. + if p.knownInventory.Exists(invVect) { + return + } + + // Avoid risk of deadlock if goroutine already exited. The goroutine + // we will be sending to hangs around until it knows for a fact that + // it is marked as disconnected and *then* it drains the channels. + if !p.Connected() { + return + } + + p.outputInvChan <- invVect +} + +// Connect uses the given conn to connect to the peer. Calling this function when +// the peer is already connected will have no effect. +func (p *Peer) Connect(conn net.Conn) error { + // Already connected? + if atomic.LoadInt32(&p.connected) != 0 { + return nil + } + + p.conn = conn + p.timeConnected = time.Now() + + atomic.AddInt32(&p.connected, 1) + return p.Start() +} + +// Connected returns whether or not the peer is currently connected. +// +// This function is safe for concurrent access. +func (p *Peer) Connected() bool { + return atomic.LoadInt32(&p.connected) != 0 && + atomic.LoadInt32(&p.disconnect) == 0 +} + +// Disconnect disconnects the peer by closing the connection. Calling this +// function when the peer is already disconnected or in the process of +// disconnecting will have no effect. +func (p *Peer) Disconnect() { + if atomic.AddInt32(&p.disconnect, 1) != 1 { + return + } + + log.Tracef("Disconnecting %s", p) + if atomic.LoadInt32(&p.connected) != 0 { + p.conn.Close() + } + close(p.quit) +} + +// Start begins processing input and output messages. It also sends the initial +// version message for outbound connections to start the negotiation process. +func (p *Peer) Start() error { + // Already started? + if atomic.AddInt32(&p.started, 1) != 1 { + return nil + } + + log.Tracef("Starting peer %s", p) + + // Send an initial version message if this is an outbound connection. + if !p.inbound { + err := p.pushVersionMsg() + if err != nil { + log.Errorf("Can't send outbound version message %v", err) + p.Disconnect() + return err + } + } + + // Start processing input and output. + go p.inHandler() + go p.queueHandler() + go p.outHandler() + + return nil +} + +// Shutdown gracefully shuts down the peer by disconnecting it. +func (p *Peer) Shutdown() { + log.Tracef("Shutdown peer %s", p) + p.Disconnect() +} + +// WaitForShutdown waits until the peer has completely shutdown. This will +// happen if either the local or remote side has been disconnected or the peer +// is forcibly shutdown via Shutdown. +func (p *Peer) WaitForShutdown() { + <-p.quit +} + +// newPeerBase returns a new base bitcoin peer based on the inbound flag. This +// is used by the NewInboundPeer and NewOutboundPeer functions to perform base +// setup needed by both types of peers. +func newPeerBase(cfg *Config, inbound bool) *Peer { + // Default to the max supported protocol version. Override to the + // version specified by the caller if configured. + protocolVersion := uint32(MaxProtocolVersion) + if cfg.ProtocolVersion != 0 { + protocolVersion = cfg.ProtocolVersion + } + + // Set the chain parameters to testnet if the caller did not specify any. + if cfg.ChainParams == nil { + cfg.ChainParams = &chaincfg.TestNet3Params + } + + p := Peer{ + inbound: inbound, + knownInventory: NewMruInventoryMap(maxKnownInventory), + outputQueue: make(chan outMsg, outputBufferSize), + sendQueue: make(chan outMsg, 1), // nonblocking sync + sendDoneQueue: make(chan struct{}, 1), // nonblocking sync + outputInvChan: make(chan *wire.InvVect, outputBufferSize), + queueQuit: make(chan struct{}), + quit: make(chan struct{}), + stats: stats{}, + cfg: *cfg, // Copy so caller can't mutate. + services: cfg.Services, + protocolVersion: protocolVersion, + } + return &p +} + +// NewInboundPeer returns a new inbound bitcoin peer. Use Start to begin +// processing incoming and outgoing messages. +func NewInboundPeer(cfg *Config, conn net.Conn) *Peer { + p := newPeerBase(cfg, true) + p.conn = conn + p.addr = conn.RemoteAddr().String() + p.timeConnected = time.Now() + atomic.AddInt32(&p.connected, 1) + return p +} + +// NewOutboundPeer returns a new outbound bitcoin peer. +func NewOutboundPeer(cfg *Config, addr string) (*Peer, error) { + p := newPeerBase(cfg, false) + p.addr = addr + + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + port, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return nil, err + } + + if cfg.HostToNetAddress != nil { + na, err := cfg.HostToNetAddress(host, uint16(port), cfg.Services) + if err != nil { + return nil, err + } + p.na = na + } else { + p.na = wire.NewNetAddressIPPort(net.ParseIP(host), uint16(port), + cfg.Services) + } + + return p, nil +} diff --git a/peer/peer_test.go b/peer/peer_test.go new file mode 100644 index 00000000..961fe6a0 --- /dev/null +++ b/peer/peer_test.go @@ -0,0 +1,659 @@ +// 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 peer_test + +import ( + "errors" + "io" + "net" + "strconv" + "testing" + "time" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/peer" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/go-socks/socks" +) + +// conn mocks a network connection by implementing the net.Conn interface. It +// is used to test peer connection without actually opening a network +// connection. +type conn struct { + io.Reader + io.Writer + io.Closer + + // local network, address for the connection. + lnet, laddr string + + // remote network, address for the connection. + rnet, raddr string + + // mocks socks proxy if true + proxy bool +} + +// LocalAddr returns the local address for the connection. +func (c conn) LocalAddr() net.Addr { + return &addr{c.lnet, c.laddr} +} + +// Remote returns the remote address for the connection. +func (c conn) RemoteAddr() net.Addr { + if !c.proxy { + return &addr{c.rnet, c.raddr} + } + host, strPort, _ := net.SplitHostPort(c.raddr) + port, _ := strconv.Atoi(strPort) + return &socks.ProxiedAddr{ + Net: c.rnet, + Host: host, + Port: port, + } +} + +// Close handles closing the connection. +func (c conn) Close() error { + return nil +} + +func (c conn) SetDeadline(t time.Time) error { return nil } +func (c conn) SetReadDeadline(t time.Time) error { return nil } +func (c conn) SetWriteDeadline(t time.Time) error { return nil } + +// addr mocks a network address +type addr struct { + net, address string +} + +func (m addr) Network() string { return m.net } +func (m addr) String() string { return m.address } + +// pipe turns two mock connections into a full-duplex connection similar to +// net.Pipe to allow pipe's with (fake) addresses. +func pipe(c1, c2 *conn) (*conn, *conn) { + r1, w1 := io.Pipe() + r2, w2 := io.Pipe() + + c1.Writer = w1 + c2.Reader = r1 + c1.Reader = r2 + c2.Writer = w2 + + return c1, c2 +} + +// peerStats holds the expected peer stats used for testing peer. +type peerStats struct { + wantUserAgent string + wantServices wire.ServiceFlag + wantProtocolVersion uint32 + wantConnected bool + wantVersionKnown bool + wantVerAckReceived bool + wantLastBlock int32 + wantStartingHeight int32 + wantLastPingTime time.Time + wantLastPingNonce uint64 + wantLastPingMicros int64 + wantTimeOffset int64 + wantBytesSent uint64 + wantBytesReceived uint64 +} + +// testPeer tests the given peer's flags and stats +func testPeer(t *testing.T, p *peer.Peer, s peerStats) { + if p.UserAgent() != s.wantUserAgent { + t.Errorf("testPeer: wrong UserAgent - got %v, want %v", p.UserAgent(), s.wantUserAgent) + return + } + + if p.Services() != s.wantServices { + t.Errorf("testPeer: wrong Services - got %v, want %v", p.Services(), s.wantServices) + return + } + + if !p.LastPingTime().Equal(s.wantLastPingTime) { + t.Errorf("testPeer: wrong LastPingTime - got %v, want %v", p.LastPingTime(), s.wantLastPingTime) + return + } + + if p.LastPingNonce() != s.wantLastPingNonce { + t.Errorf("testPeer: wrong LastPingNonce - got %v, want %v", p.LastPingNonce(), s.wantLastPingNonce) + return + } + + if p.LastPingMicros() != s.wantLastPingMicros { + t.Errorf("testPeer: wrong LastPingMicros - got %v, want %v", p.LastPingMicros(), s.wantLastPingMicros) + return + } + + if p.VerAckReceived() != s.wantVerAckReceived { + t.Errorf("testPeer: wrong VerAckReceived - got %v, want %v", p.VerAckReceived(), s.wantVerAckReceived) + return + } + + if p.VersionKnown() != s.wantVersionKnown { + t.Errorf("testPeer: wrong VersionKnown - got %v, want %v", p.VersionKnown(), s.wantVersionKnown) + return + } + + if p.ProtocolVersion() != s.wantProtocolVersion { + t.Errorf("testPeer: wrong ProtocolVersion - got %v, want %v", p.ProtocolVersion(), s.wantProtocolVersion) + return + } + + if p.LastBlock() != s.wantLastBlock { + t.Errorf("testPeer: wrong LastBlock - got %v, want %v", p.LastBlock(), s.wantLastBlock) + return + } + + if p.TimeOffset() != s.wantTimeOffset { + t.Errorf("testPeer: wrong TimeOffset - got %v, want %v", p.TimeOffset(), s.wantTimeOffset) + return + } + + if p.BytesSent() != s.wantBytesSent { + t.Errorf("testPeer: wrong BytesSent - got %v, want %v", p.BytesSent(), s.wantBytesSent) + return + } + + if p.BytesReceived() != s.wantBytesReceived { + t.Errorf("testPeer: wrong BytesReceived - got %v, want %v", p.BytesReceived(), s.wantBytesReceived) + return + } + + if p.StartingHeight() != s.wantStartingHeight { + t.Errorf("testPeer: wrong StartingHeight - got %v, want %v", p.StartingHeight(), s.wantStartingHeight) + return + } + + if p.Connected() != s.wantConnected { + t.Errorf("testPeer: wrong Connected - got %v, want %v", p.Connected(), s.wantConnected) + return + } + + stats := p.StatsSnapshot() + + if p.ID() != stats.ID { + t.Errorf("testPeer: wrong ID - got %v, want %v", p.ID(), stats.ID) + return + } + + if p.Addr() != stats.Addr { + t.Errorf("testPeer: wrong Addr - got %v, want %v", p.Addr(), stats.Addr) + return + } + + if p.LastSend() != stats.LastSend { + t.Errorf("testPeer: wrong LastSend - got %v, want %v", p.LastSend(), stats.LastSend) + return + } + + if p.LastRecv() != stats.LastRecv { + t.Errorf("testPeer: wrong LastRecv - got %v, want %v", p.LastRecv(), stats.LastRecv) + return + } +} + +// TestPeerConnection tests connection between inbound and outbound peers. +func TestPeerConnection(t *testing.T) { + verack := make(chan struct{}, 1) + peerCfg := &peer.Config{ + Listeners: peer.MessageListeners{ + OnWrite: func(p *peer.Peer, bytesWritten int, msg wire.Message, err error) { + switch msg.(type) { + case *wire.MsgVerAck: + verack <- struct{}{} + } + }, + }, + UserAgentName: "peer", + UserAgentVersion: "1.0", + ChainParams: &chaincfg.MainNetParams, + Services: 0, + } + wantStats := peerStats{ + wantUserAgent: wire.DefaultUserAgent + "peer:1.0/", + wantServices: 0, + wantProtocolVersion: peer.MaxProtocolVersion, + wantConnected: true, + wantVersionKnown: true, + wantVerAckReceived: true, + wantLastPingTime: time.Time{}, + wantLastPingNonce: uint64(0), + wantLastPingMicros: int64(0), + wantTimeOffset: int64(0), + wantBytesSent: 158, // 134 version + 24 verack + wantBytesReceived: 158, + } + tests := []struct { + name string + setup func() (*peer.Peer, *peer.Peer, error) + }{ + { + "basic handshake", + func() (*peer.Peer, *peer.Peer, error) { + inConn, outConn := pipe( + &conn{raddr: "10.0.0.1:8333"}, + &conn{raddr: "10.0.0.2:8333"}, + ) + inPeer := peer.NewInboundPeer(peerCfg, inConn) + err := inPeer.Start() + if err != nil { + return nil, nil, err + } + outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.2:8333") + if err != nil { + return nil, nil, err + } + if err := outPeer.Connect(outConn); err != nil { + return nil, nil, err + } + for i := 0; i < 2; i++ { + select { + case <-verack: + case <-time.After(time.Second * 1): + return nil, nil, errors.New("verack timeout") + } + } + return inPeer, outPeer, nil + }, + }, + { + "socks proxy", + func() (*peer.Peer, *peer.Peer, error) { + inConn, outConn := pipe( + &conn{raddr: "10.0.0.1:8333", proxy: true}, + &conn{raddr: "10.0.0.2:8333"}, + ) + inPeer := peer.NewInboundPeer(peerCfg, inConn) + err := inPeer.Start() + if err != nil { + return nil, nil, err + } + outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.2:8333") + if err != nil { + return nil, nil, err + } + if err := outPeer.Connect(outConn); err != nil { + return nil, nil, err + } + for i := 0; i < 2; i++ { + select { + case <-verack: + case <-time.After(time.Second * 1): + return nil, nil, errors.New("verack timeout") + } + } + return inPeer, outPeer, nil + }, + }, + } + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + inPeer, outPeer, err := test.setup() + if err != nil { + t.Errorf("TestPeerConnection setup #%d: unexpected err %v\n", i, err) + return + } + testPeer(t, inPeer, wantStats) + testPeer(t, outPeer, wantStats) + + inPeer.Shutdown() + outPeer.Shutdown() + } +} + +// TestPeerListeners tests that the peer listeners are called as expected. +func TestPeerListeners(t *testing.T) { + verack := make(chan struct{}, 1) + ok := make(chan wire.Message, 20) + peerCfg := &peer.Config{ + Listeners: peer.MessageListeners{ + OnGetAddr: func(p *peer.Peer, msg *wire.MsgGetAddr) { + ok <- msg + }, + OnAddr: func(p *peer.Peer, msg *wire.MsgAddr) { + ok <- msg + }, + OnPing: func(p *peer.Peer, msg *wire.MsgPing) { + ok <- msg + }, + OnPong: func(p *peer.Peer, msg *wire.MsgPong) { + ok <- msg + }, + OnAlert: func(p *peer.Peer, msg *wire.MsgAlert) { + ok <- msg + }, + OnMemPool: func(p *peer.Peer, msg *wire.MsgMemPool) { + ok <- msg + }, + OnTx: func(p *peer.Peer, msg *wire.MsgTx) { + ok <- msg + }, + OnBlock: func(p *peer.Peer, msg *wire.MsgBlock, buf []byte) { + ok <- msg + }, + OnInv: func(p *peer.Peer, msg *wire.MsgInv) { + ok <- msg + }, + OnHeaders: func(p *peer.Peer, msg *wire.MsgHeaders) { + ok <- msg + }, + OnNotFound: func(p *peer.Peer, msg *wire.MsgNotFound) { + ok <- msg + }, + OnGetData: func(p *peer.Peer, msg *wire.MsgGetData) { + ok <- msg + }, + OnGetBlocks: func(p *peer.Peer, msg *wire.MsgGetBlocks) { + ok <- msg + }, + OnGetHeaders: func(p *peer.Peer, msg *wire.MsgGetHeaders) { + ok <- msg + }, + OnFilterAdd: func(p *peer.Peer, msg *wire.MsgFilterAdd) { + ok <- msg + }, + OnFilterClear: func(p *peer.Peer, msg *wire.MsgFilterClear) { + ok <- msg + }, + OnFilterLoad: func(p *peer.Peer, msg *wire.MsgFilterLoad) { + ok <- msg + }, + OnMerkleBlock: func(p *peer.Peer, msg *wire.MsgMerkleBlock) { + ok <- msg + }, + OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) { + ok <- msg + }, + OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { + verack <- struct{}{} + }, + OnReject: func(p *peer.Peer, msg *wire.MsgReject) { + ok <- msg + }, + }, + UserAgentName: "peer", + UserAgentVersion: "1.0", + ChainParams: &chaincfg.MainNetParams, + Services: wire.SFNodeBloom, + } + inConn, outConn := pipe( + &conn{raddr: "10.0.0.1:8333"}, + &conn{raddr: "10.0.0.2:8333"}, + ) + inPeer := peer.NewInboundPeer(peerCfg, inConn) + err := inPeer.Start() + if err != nil { + t.Errorf("TestPeerListeners: unexpected err %v\n", err) + return + } + peerCfg.Listeners = peer.MessageListeners{ + OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { + verack <- struct{}{} + }, + } + outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") + if err != nil { + t.Errorf("NewOutboundPeer: unexpected err %v\n", err) + return + } + if err := outPeer.Connect(outConn); err != nil { + t.Errorf("TestPeerListeners: unexpected err %v\n", err) + return + } + for i := 0; i < 2; i++ { + select { + case <-verack: + case <-time.After(time.Second * 1): + t.Errorf("TestPeerListeners: verack timeout\n") + return + } + } + + tests := []struct { + listener string + msg wire.Message + }{ + { + "OnGetAddr", + wire.NewMsgGetAddr(), + }, + { + "OnAddr", + wire.NewMsgAddr(), + }, + { + "OnPing", + wire.NewMsgPing(42), + }, + { + "OnPong", + wire.NewMsgPong(42), + }, + { + "OnAlert", + wire.NewMsgAlert([]byte("payload"), []byte("signature")), + }, + { + "OnMemPool", + wire.NewMsgMemPool(), + }, + { + "OnTx", + wire.NewMsgTx(), + }, + { + "OnBlock", + wire.NewMsgBlock(wire.NewBlockHeader(&wire.ShaHash{}, &wire.ShaHash{}, 1, 1)), + }, + { + "OnInv", + wire.NewMsgInv(), + }, + { + "OnHeaders", + wire.NewMsgHeaders(), + }, + { + "OnNotFound", + wire.NewMsgNotFound(), + }, + { + "OnGetData", + wire.NewMsgGetData(), + }, + { + "OnGetBlocks", + wire.NewMsgGetBlocks(&wire.ShaHash{}), + }, + { + "OnGetHeaders", + wire.NewMsgGetHeaders(), + }, + { + "OnFilterAdd", + wire.NewMsgFilterAdd([]byte{0x01}), + }, + { + "OnFilterClear", + wire.NewMsgFilterClear(), + }, + { + "OnFilterLoad", + wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone), + }, + { + "OnMerkleBlock", + wire.NewMsgMerkleBlock(wire.NewBlockHeader(&wire.ShaHash{}, &wire.ShaHash{}, 1, 1)), + }, + // only one version message is allowed + // only one verack message is allowed + { + "OnMsgReject", + wire.NewMsgReject("block", wire.RejectDuplicate, "dupe block"), + }, + } + t.Logf("Running %d tests", len(tests)) + for _, test := range tests { + // Queue the test message + outPeer.QueueMessage(test.msg, nil) + select { + case <-ok: + case <-time.After(time.Second * 1): + t.Errorf("TestPeerListeners: %s timeout", test.listener) + return + } + } + inPeer.Shutdown() + outPeer.Shutdown() +} + +// TestOutboundPeer tests that the outbound peer works as expected. +func TestOutboundPeer(t *testing.T) { + // Use a mock NewestBlock func to test errs + var errBlockNotFound = errors.New("newest block not found") + var mockNewestSha = func() (*wire.ShaHash, int32, error) { + return nil, 0, errBlockNotFound + } + + peerCfg := &peer.Config{ + NewestBlock: mockNewestSha, + UserAgentName: "peer", + UserAgentVersion: "1.0", + ChainParams: &chaincfg.MainNetParams, + Services: 0, + } + + r, w := io.Pipe() + c := &conn{raddr: "10.0.0.1:8333", Writer: w, Reader: r} + + p, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") + if err != nil { + t.Errorf("NewOutboundPeer: unexpected err - %v\n", err) + return + } + + // Test Connect err + wantErr := errBlockNotFound + if err := p.Connect(c); err != wantErr { + t.Errorf("Connect: expected err %v, got %v\n", wantErr, err) + return + } + // Test already connected + if err := p.Connect(c); err != nil { + t.Errorf("Connect: unexpected err %v\n", err) + return + } + // Test already started + if err := p.Start(); err != nil { + t.Errorf("Start: unexpected err %v\n", err) + return + } + + // Test Queue Inv + fakeBlockHash := &wire.ShaHash{0x00, 0x01} + fakeInv := wire.NewInvVect(wire.InvTypeBlock, fakeBlockHash) + p.QueueInventory(fakeInv) + p.AddKnownInventory(fakeInv) + p.QueueInventory(fakeInv) + + // Test Queue Message + fakeMsg := wire.NewMsgVerAck() + p.QueueMessage(fakeMsg, nil) + done := make(chan struct{}, 5) + p.QueueMessage(fakeMsg, done) + <-done + p.Shutdown() + + // Test NewestBlock + var newestBlock = func() (*wire.ShaHash, int32, error) { + hashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" + hash, err := wire.NewShaHashFromStr(hashStr) + if err != nil { + return nil, 0, err + } + return hash, 234439, nil + } + peerCfg.NewestBlock = newestBlock + p1, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") + if err != nil { + t.Errorf("NewOutboundPeer: unexpected err - %v\n", err) + return + } + if err := p1.Connect(c); err != nil { + t.Errorf("Connect: unexpected err %v\n", err) + return + } + + // Test update latest block + latestBlockSha, err := wire.NewShaHashFromStr("1a63f9cdff1752e6375c8c76e543a71d239e1a2e5c6db1aa679") + if err != nil { + t.Errorf("NewShaHashFromStr: unexpected err %v\n", err) + return + } + p1.UpdateLastAnnouncedBlock(latestBlockSha) + p1.UpdateLastBlockHeight(234440) + if p1.LastAnnouncedBlock() != latestBlockSha { + t.Errorf("LastAnnouncedBlock: wrong block - got %v, want %v", + p1.LastAnnouncedBlock(), latestBlockSha) + return + } + + // Test Queue Inv after connection + p1.QueueInventory(fakeInv) + p1.Shutdown() + + // Test regression + peerCfg.ChainParams = &chaincfg.RegressionNetParams + peerCfg.Services = wire.SFNodeBloom + p2, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") + if err != nil { + t.Errorf("NewOutboundPeer: unexpected err - %v\n", err) + return + } + if err := p2.Connect(c); err != nil { + t.Errorf("Connect: unexpected err %v\n", err) + return + } + + // Test PushXXX + var addrs []*wire.NetAddress + for i := 0; i < 5; i++ { + na := wire.NetAddress{} + addrs = append(addrs, &na) + } + if _, err := p2.PushAddrMsg(addrs); err != nil { + t.Errorf("PushAddrMsg: unexpected err %v\n", err) + return + } + if err := p2.PushGetBlocksMsg(nil, &wire.ShaHash{}); err != nil { + t.Errorf("PushGetBlocksMsg: unexpected err %v\n", err) + return + } + if err := p2.PushGetHeadersMsg(nil, &wire.ShaHash{}); err != nil { + t.Errorf("PushGetHeadersMsg: unexpected err %v\n", err) + return + } + p2.PushRejectMsg("block", wire.RejectMalformed, "malformed", nil, true) + p2.PushRejectMsg("block", wire.RejectInvalid, "invalid", nil, false) + + // Test Queue Messages + p2.QueueMessage(wire.NewMsgGetAddr(), done) + p2.QueueMessage(wire.NewMsgPing(1), done) + p2.QueueMessage(wire.NewMsgMemPool(), done) + p2.QueueMessage(wire.NewMsgGetData(), done) + p2.QueueMessage(wire.NewMsgGetHeaders(), done) + + p2.Shutdown() +} + +func init() { + // Allow self connection when running the tests. + peer.TstAllowSelfConns() +} diff --git a/rpcserver.go b/rpcserver.go index 711e6f25..3af8a5b4 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -76,6 +76,9 @@ const ( // changed and there have been changes to the available transactions // in the memory pool. gbtRegenerateSeconds = 60 + + // maxProtocolVersion is the max protocol version the server supports. + maxProtocolVersion = 70002 ) var ( @@ -415,7 +418,7 @@ func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter } } } - if err != nil && peerExists(s.server.PeerInfo(), addr, int32(nodeID)) { + if err != nil && peerExists(s.server.Peers(), addr, int32(nodeID)) { return nil, &btcjson.RPCError{ Code: btcjson.ErrRPCMisc, Message: "can't disconnect a permanent peer, use remove", @@ -438,7 +441,7 @@ func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter } } } - if err != nil && peerExists(s.server.PeerInfo(), addr, int32(nodeID)) { + if err != nil && peerExists(s.server.Peers(), addr, int32(nodeID)) { return nil, &btcjson.RPCError{ Code: btcjson.ErrRPCMisc, Message: "can't remove a temporary peer, use disconnect", @@ -483,9 +486,9 @@ func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter // peerExists determines if a certain peer is currently connected given // information about all currently connected peers. Peer existence is // determined using either a target address or node id. -func peerExists(peerInfos []*btcjson.GetPeerInfoResult, addr string, nodeID int32) bool { - for _, peerInfo := range peerInfos { - if peerInfo.ID == nodeID || peerInfo.Addr == addr { +func peerExists(peers []*serverPeer, addr string, nodeID int32) bool { + for _, p := range peers { + if p.ID() == nodeID || p.Addr() == addr { return true } } @@ -945,7 +948,7 @@ func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan stru node := *c.Node found := false for i, peer := range peers { - if peer.addr == node { + if peer.Addr() == node { peers = peers[i : i+1] found = true } @@ -963,7 +966,7 @@ func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan stru if !c.DNS { results := make([]string, 0, len(peers)) for _, peer := range peers { - results = append(results, peer.addr) + results = append(results, peer.Addr()) } return results, nil } @@ -975,15 +978,15 @@ func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan stru // Set the "address" of the peer which could be an ip address // or a domain name. var result btcjson.GetAddedNodeInfoResult - result.AddedNode = peer.addr + result.AddedNode = peer.Addr() result.Connected = btcjson.Bool(peer.Connected()) // Split the address into host and port portions so we can do // a DNS lookup against the host. When no port is specified in // the address, just use the address as the host. - host, _, err := net.SplitHostPort(peer.addr) + host, _, err := net.SplitHostPort(peer.Addr()) if err != nil { - host = peer.addr + host = peer.Addr() } // Do a DNS lookup for the address. If the lookup fails, just @@ -1007,7 +1010,7 @@ func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan stru addr.Address = ip addr.Connected = "false" if ip == host && peer.Connected() { - addr.Connected = directionString(peer.inbound) + addr.Connected = directionString(peer.Inbound()) } addrs = append(addrs, addr) } @@ -2282,7 +2285,38 @@ func handleGetNetworkHashPS(s *rpcServer, cmd interface{}, closeChan <-chan stru // handleGetPeerInfo implements the getpeerinfo command. func handleGetPeerInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - return s.server.PeerInfo(), nil + peers := s.server.Peers() + syncPeer := s.server.blockManager.SyncPeer() + infos := make([]*btcjson.GetPeerInfoResult, 0, len(peers)) + for _, p := range peers { + statsSnap := p.StatsSnapshot() + info := &btcjson.GetPeerInfoResult{ + ID: statsSnap.ID, + Addr: statsSnap.Addr, + Services: fmt.Sprintf("%08d", uint64(statsSnap.Services)), + LastSend: statsSnap.LastSend.Unix(), + LastRecv: statsSnap.LastRecv.Unix(), + BytesSent: statsSnap.BytesSent, + BytesRecv: statsSnap.BytesRecv, + ConnTime: statsSnap.ConnTime.Unix(), + PingTime: float64(statsSnap.LastPingMicros), + TimeOffset: statsSnap.TimeOffset, + Version: statsSnap.Version, + SubVer: statsSnap.UserAgent, + Inbound: statsSnap.Inbound, + StartingHeight: statsSnap.StartingHeight, + CurrentHeight: statsSnap.LastBlock, + BanScore: 0, + SyncNode: p == syncPeer, + } + if p.LastPingNonce() != 0 { + wait := float64(time.Now().Sub(statsSnap.LastPingTime).Nanoseconds()) + // We actually want microseconds. + info.PingWait = wait / 1000 + } + infos = append(infos, info) + } + return infos, nil } // handleGetRawMempool implements the getrawmempool command. diff --git a/server.go b/server.go index 29a5a66d..0568ae16 100644 --- a/server.go +++ b/server.go @@ -1,4 +1,4 @@ -// Copyright (c) 2013-2014 The btcsuite 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. @@ -20,12 +20,13 @@ import ( "github.com/btcsuite/btcd/addrmgr" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/database" + "github.com/btcsuite/btcd/peer" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/bloom" ) const ( @@ -42,13 +43,34 @@ const ( // defaultMaxOutbound is the default number of max outbound peers. defaultMaxOutbound = 8 + + // connectionRetryInterval is the base amount of time to wait in between + // retries when connecting to persistent peers. It is adjusted by the + // number of retries such that there is a retry backoff. + connectionRetryInterval = time.Second * 10 + + // maxConnectionRetryInterval is the max amount of time retrying of a + // persistent peer is allowed to grow to. This is necessary since the + // retry logic uses a backoff mechanism which increases the interval + // base done the number of retries that have been done. + maxConnectionRetryInterval = time.Minute * 5 +) + +var ( + // userAgentName is the user agent name and is used to help identify + // ourselves to other bitcoin peers. + userAgentName = "btcd" + + // userAgentVersion is the user agent version and is used to help + // identify ourselves to other bitcoin peers. + userAgentVersion = fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch) ) // broadcastMsg provides the ability to house a bitcoin message to be broadcast // to all connected peers except specified excluded peers. type broadcastMsg struct { message wire.Message - excludePeers []*peer + excludePeers []*serverPeer } // broadcastInventoryAdd is a type used to declare that the InvVect it contains @@ -75,13 +97,73 @@ type relayMsg struct { type updatePeerHeightsMsg struct { newSha *wire.ShaHash newHeight int32 - originPeer *peer + originPeer *serverPeer +} + +// peerState maintains state of inbound, persistent, outbound peers as well +// as banned peers and outbound groups. +type peerState struct { + pendingPeers map[string]*serverPeer + peers map[int32]*serverPeer + outboundPeers map[int32]*serverPeer + persistentPeers map[int32]*serverPeer + banned map[string]time.Time + outboundGroups map[string]int + maxOutboundPeers int +} + +// Count returns the count of all known peers. +func (ps *peerState) Count() int { + return len(ps.peers) + len(ps.outboundPeers) + len(ps.persistentPeers) +} + +// OutboundCount returns the count of known outbound peers. +func (ps *peerState) OutboundCount() int { + return len(ps.outboundPeers) + len(ps.persistentPeers) +} + +// NeedMoreOutbound returns true if more outbound peers are required. +func (ps *peerState) NeedMoreOutbound() bool { + return ps.OutboundCount() < ps.maxOutboundPeers && + ps.Count() < cfg.MaxPeers +} + +// NeedMoreTries returns true if more outbound peer attempts can be tried. +func (ps *peerState) NeedMoreTries() bool { + return len(ps.pendingPeers) < 2*(ps.maxOutboundPeers-ps.OutboundCount()) +} + +// forAllOutboundPeers is a helper function that runs closure on all outbound +// peers known to peerState. +func (ps *peerState) forAllOutboundPeers(closure func(sp *serverPeer)) { + for _, e := range ps.outboundPeers { + closure(e) + } + for _, e := range ps.persistentPeers { + closure(e) + } +} + +// forPendingPeers is a helper function that runs closure on all pending peers +// known to peerState. +func (ps *peerState) forPendingPeers(closure func(sp *serverPeer)) { + for _, e := range ps.pendingPeers { + closure(e) + } +} + +// forAllPeers is a helper function that runs closure on all peers known to +// peerState. +func (ps *peerState) forAllPeers(closure func(sp *serverPeer)) { + for _, e := range ps.peers { + closure(e) + } + ps.forAllOutboundPeers(closure) } // server provides a bitcoin server for handling communications to and from // bitcoin peers. type server struct { - nonce uint64 listeners []net.Listener chainParams *chaincfg.Params started int32 // atomic @@ -98,9 +180,11 @@ type server struct { txMemPool *txMemPool cpuMiner *CPUMiner modifyRebroadcastInv chan interface{} - newPeers chan *peer - donePeers chan *peer - banPeers chan *peer + pendingPeers chan *serverPeer + newPeers chan *serverPeer + donePeers chan *serverPeer + banPeers chan *serverPeer + retryPeers chan *serverPeer wakeup chan struct{} query chan interface{} relayInv chan relayMsg @@ -114,13 +198,630 @@ type server struct { services wire.ServiceFlag } -type peerState struct { - peers map[*peer]struct{} - outboundPeers map[*peer]struct{} - persistentPeers map[*peer]struct{} - banned map[string]time.Time - outboundGroups map[string]int - maxOutboundPeers int +// serverPeer extends the peer to maintain state shared by the server and +// the blockmanager. +type serverPeer struct { + *peer.Peer + + server *server + persistent bool + continueHash *wire.ShaHash + relayMtx sync.Mutex + disableRelayTx bool + requestQueue []*wire.InvVect + requestedTxns map[wire.ShaHash]struct{} + requestedBlocks map[wire.ShaHash]struct{} + filter *bloom.Filter + knownAddresses map[string]struct{} + quit chan struct{} + + // The following chans are used to sync blockmanager and server. + txProcessed chan struct{} + blockProcessed chan struct{} +} + +// newServerPeer returns a new serverPeer instance. The peer needs to be set by +// the caller. +func newServerPeer(s *server, isPersistent bool) *serverPeer { + return &serverPeer{ + server: s, + persistent: isPersistent, + requestedTxns: make(map[wire.ShaHash]struct{}), + requestedBlocks: make(map[wire.ShaHash]struct{}), + filter: bloom.LoadFilter(nil), + knownAddresses: make(map[string]struct{}), + quit: make(chan struct{}), + txProcessed: make(chan struct{}, 1), + blockProcessed: make(chan struct{}, 1), + } +} + +// addKnownAddresses adds the given addresses to the set of known addreses to +// the peer to prevent sending duplicate addresses. +func (sp *serverPeer) addKnownAddresses(addresses []*wire.NetAddress) { + for _, na := range addresses { + sp.knownAddresses[addrmgr.NetAddressKey(na)] = struct{}{} + } +} + +// addressKnown true if the given address is already known to the peer. +func (sp *serverPeer) addressKnown(na *wire.NetAddress) bool { + _, exists := sp.knownAddresses[addrmgr.NetAddressKey(na)] + return exists +} + +// setDisableRelayTx toggles relaying of transactions for the given peer. +// It is safe for concurrent access. +func (sp *serverPeer) setDisableRelayTx(disable bool) { + sp.relayMtx.Lock() + sp.disableRelayTx = disable + sp.relayMtx.Unlock() +} + +// relayTxDisabled returns whether or not relaying of transactions for the given +// peer is disabled. +// It is safe for concurrent access. +func (sp *serverPeer) relayTxDisabled() bool { + sp.relayMtx.Lock() + defer sp.relayMtx.Unlock() + + return sp.disableRelayTx +} + +// pushAddrMsg sends an addr message to the connected peer using the provided +// addresses. +func (sp *serverPeer) pushAddrMsg(addresses []*wire.NetAddress) { + // Filter addresses already known to the peer. + addrs := make([]*wire.NetAddress, 0, len(addresses)) + for _, addr := range addresses { + if !sp.addressKnown(addr) { + addrs = append(addrs, addr) + } + } + known, err := sp.PushAddrMsg(addrs) + if err != nil { + peerLog.Errorf("Can't push address message to %s: %v", sp.Peer, err) + sp.Disconnect() + return + } + sp.addKnownAddresses(known) +} + +// OnVersion is invoked when a peer receives a version bitcoin message +// and is used to negotiate the protocol version details as well as kick start +// the communications. +func (sp *serverPeer) OnVersion(p *peer.Peer, msg *wire.MsgVersion) { + // Add the remote peer time as a sample for creating an offset against + // the local clock to keep the network time in sync. + sp.server.timeSource.AddTimeSample(p.Addr(), msg.Timestamp) + + // Signal the block manager this peer is a new sync candidate. + sp.server.blockManager.NewPeer(sp) + + // Choose whether or not to relay transactions before a filter command + // is received. + sp.setDisableRelayTx(msg.DisableRelayTx) + + // Update the address manager and request known addresses from the + // remote peer for outbound connections. This is skipped when running + // on the simulation test network since it is only intended to connect + // to specified peers and actively avoids advertising and connecting to + // discovered peers. + if !cfg.SimNet { + addrManager := sp.server.addrManager + // Outbound connections. + if !p.Inbound() { + // TODO(davec): Only do this if not doing the initial block + // download and the local address is routable. + if !cfg.DisableListen /* && isCurrent? */ { + // Get address that best matches. + lna := addrManager.GetBestLocalAddress(p.NA()) + if addrmgr.IsRoutable(lna) { + // Filter addresses the peer already knows about. + addresses := []*wire.NetAddress{lna} + sp.pushAddrMsg(addresses) + } + } + + // Request known addresses if the server address manager needs + // more and the peer has a protocol version new enough to + // include a timestamp with addresses. + hasTimestamp := p.ProtocolVersion() >= + wire.NetAddressTimeVersion + if addrManager.NeedMoreAddresses() && hasTimestamp { + p.QueueMessage(wire.NewMsgGetAddr(), nil) + } + + // Mark the address as a known good address. + addrManager.Good(p.NA()) + } else { + // A peer might not be advertising the same address that it + // actually connected from. One example of why this can happen + // is with NAT. Only add the address to the address manager if + // the addresses agree. + if addrmgr.NetAddressKey(&msg.AddrMe) == addrmgr.NetAddressKey(p.NA()) { + addrManager.AddAddress(p.NA(), p.NA()) + addrManager.Good(p.NA()) + } + } + } + + // Add valid peer to the server. + sp.server.AddPeer(sp) +} + +// OnMemPool is invoked when a peer receives a mempool bitcoin message. +// It creates and sends an inventory message with the contents of the memory +// pool up to the maximum inventory allowed per message. When the peer has a +// bloom filter loaded, the contents are filtered accordingly. +func (sp *serverPeer) OnMemPool(p *peer.Peer, msg *wire.MsgMemPool) { + // Generate inventory message with the available transactions in the + // transaction memory pool. Limit it to the max allowed inventory + // per message. The the NewMsgInvSizeHint function automatically limits + // the passed hint to the maximum allowed, so it's safe to pass it + // without double checking it here. + txMemPool := sp.server.txMemPool + txDescs := txMemPool.TxDescs() + invMsg := wire.NewMsgInvSizeHint(uint(len(txDescs))) + + for i, txDesc := range txDescs { + // Another thread might have removed the transaction from the + // pool since the initial query. + hash := txDesc.Tx.Sha() + if !txMemPool.IsTransactionInPool(hash) { + continue + } + + // Either add all transactions when there is no bloom filter, + // or only the transactions that match the filter when there is + // one. + if !sp.filter.IsLoaded() || sp.filter.MatchTxAndUpdate(txDesc.Tx) { + iv := wire.NewInvVect(wire.InvTypeTx, hash) + invMsg.AddInvVect(iv) + if i+1 >= wire.MaxInvPerMsg { + break + } + } + } + + // Send the inventory message if there is anything to send. + if len(invMsg.InvList) > 0 { + p.QueueMessage(invMsg, nil) + } +} + +// OnTx is invoked when a peer receives a tx bitcoin message. It blocks +// until the bitcoin transaction has been fully processed. Unlock the block +// handler this does not serialize all transactions through a single thread +// transactions don't rely on the previous one in a linear fashion like blocks. +func (sp *serverPeer) OnTx(p *peer.Peer, msg *wire.MsgTx) { + // Add the transaction to the known inventory for the peer. + // Convert the raw MsgTx to a btcutil.Tx which provides some convenience + // methods and things such as hash caching. + tx := btcutil.NewTx(msg) + iv := wire.NewInvVect(wire.InvTypeTx, tx.Sha()) + p.AddKnownInventory(iv) + + // Queue the transaction up to be handled by the block manager and + // intentionally block further receives until the transaction is fully + // processed and known good or bad. This helps prevent a malicious peer + // from queueing up a bunch of bad transactions before disconnecting (or + // being disconnected) and wasting memory. + sp.server.blockManager.QueueTx(tx, sp) + <-sp.txProcessed +} + +// OnBlock is invoked when a peer receives a block bitcoin message. It +// blocks until the bitcoin block has been fully processed. +func (sp *serverPeer) OnBlock(p *peer.Peer, msg *wire.MsgBlock, buf []byte) { + // Convert the raw MsgBlock to a btcutil.Block which provides some + // convenience methods and things such as hash caching. + block := btcutil.NewBlockFromBlockAndBytes(msg, buf) + + // Add the block to the known inventory for the peer. + iv := wire.NewInvVect(wire.InvTypeBlock, block.Sha()) + p.AddKnownInventory(iv) + + // Queue the block up to be handled by the block + // manager and intentionally block further receives + // until the bitcoin block is fully processed and known + // good or bad. This helps prevent a malicious peer + // from queueing up a bunch of bad blocks before + // disconnecting (or being disconnected) and wasting + // memory. Additionally, this behavior is depended on + // by at least the block acceptance test tool as the + // reference implementation processes blocks in the same + // thread and therefore blocks further messages until + // the bitcoin block has been fully processed. + sp.server.blockManager.QueueBlock(block, sp) + <-sp.blockProcessed +} + +// OnInv is invoked when a peer receives an inv bitcoin message and is +// used to examine the inventory being advertised by the remote peer and react +// accordingly. We pass the message down to blockmanager which will call +// QueueMessage with any appropriate responses. +func (sp *serverPeer) OnInv(p *peer.Peer, msg *wire.MsgInv) { + sp.server.blockManager.QueueInv(msg, sp) +} + +// OnHeaders is invoked when a peer receives a headers bitcoin +// message. The message is passed down to the block manager. +func (sp *serverPeer) OnHeaders(p *peer.Peer, msg *wire.MsgHeaders) { + sp.server.blockManager.QueueHeaders(msg, sp) +} + +// handleGetData is invoked when a peer receives a getdata bitcoin message and +// is used to deliver block and transaction information. +func (sp *serverPeer) OnGetData(p *peer.Peer, msg *wire.MsgGetData) { + numAdded := 0 + notFound := wire.NewMsgNotFound() + + // We wait on this wait channel periodically to prevent queueing + // far more data than we can send in a reasonable time, wasting memory. + // The waiting occurs after the database fetch for the next one to + // provide a little pipelining. + var waitChan chan struct{} + doneChan := make(chan struct{}, 1) + + for i, iv := range msg.InvList { + var c chan struct{} + // If this will be the last message we send. + if i == len(msg.InvList)-1 && len(notFound.InvList) == 0 { + c = doneChan + } else if (i+1)%3 == 0 { + // Buffered so as to not make the send goroutine block. + c = make(chan struct{}, 1) + } + var err error + switch iv.Type { + case wire.InvTypeTx: + err = sp.server.pushTxMsg(sp, &iv.Hash, c, waitChan) + case wire.InvTypeBlock: + err = sp.server.pushBlockMsg(sp, &iv.Hash, c, waitChan) + case wire.InvTypeFilteredBlock: + err = sp.server.pushMerkleBlockMsg(sp, &iv.Hash, c, waitChan) + default: + peerLog.Warnf("Unknown type in inventory request %d", + iv.Type) + continue + } + if err != nil { + notFound.AddInvVect(iv) + + // When there is a failure fetching the final entry + // and the done channel was sent in due to there + // being no outstanding not found inventory, consume + // it here because there is now not found inventory + // that will use the channel momentarily. + if i == len(msg.InvList)-1 && c != nil { + <-c + } + } + numAdded++ + waitChan = c + } + if len(notFound.InvList) != 0 { + p.QueueMessage(notFound, doneChan) + } + + // Wait for messages to be sent. We can send quite a lot of data at this + // point and this will keep the peer busy for a decent amount of time. + // We don't process anything else by them in this time so that we + // have an idea of when we should hear back from them - else the idle + // timeout could fire when we were only half done sending the blocks. + if numAdded > 0 { + <-doneChan + } +} + +// OnGetBlocks is invoked when a peer receives a getblocks bitcoin +// message. +func (sp *serverPeer) OnGetBlocks(p *peer.Peer, msg *wire.MsgGetBlocks) { + db := sp.server.db + + // Return all block hashes to the latest one (up to max per message) if + // no stop hash was specified. + // Attempt to find the ending index of the stop hash if specified. + endIdx := database.AllShas + if !msg.HashStop.IsEqual(&zeroHash) { + height, err := db.FetchBlockHeightBySha(&msg.HashStop) + if err == nil { + endIdx = height + 1 + } + } + + // Find the most recent known block based on the block locator. + // Use the block after the genesis block if no other blocks in the + // provided locator are known. This does mean the client will start + // over with the genesis block if unknown block locators are provided. + // This mirrors the behavior in the reference implementation. + startIdx := int32(1) + for _, hash := range msg.BlockLocatorHashes { + height, err := db.FetchBlockHeightBySha(hash) + if err == nil { + // Start with the next hash since we know this one. + startIdx = height + 1 + break + } + } + + // Don't attempt to fetch more than we can put into a single message. + autoContinue := false + if endIdx-startIdx > wire.MaxBlocksPerMsg { + endIdx = startIdx + wire.MaxBlocksPerMsg + autoContinue = true + } + + // Generate inventory message. + // + // The FetchBlockBySha call is limited to a maximum number of hashes + // per invocation. Since the maximum number of inventory per message + // might be larger, call it multiple times with the appropriate indices + // as needed. + invMsg := wire.NewMsgInv() + for start := startIdx; start < endIdx; { + // Fetch the inventory from the block database. + hashList, err := db.FetchHeightRange(start, endIdx) + if err != nil { + peerLog.Warnf("Block lookup failed: %v", err) + return + } + + // The database did not return any further hashes. Break out of + // the loop now. + if len(hashList) == 0 { + break + } + + // Add block inventory to the message. + for _, hash := range hashList { + hashCopy := hash + iv := wire.NewInvVect(wire.InvTypeBlock, &hashCopy) + invMsg.AddInvVect(iv) + } + start += int32(len(hashList)) + } + + // Send the inventory message if there is anything to send. + if len(invMsg.InvList) > 0 { + invListLen := len(invMsg.InvList) + if autoContinue && invListLen == wire.MaxBlocksPerMsg { + // Intentionally use a copy of the final hash so there + // is not a reference into the inventory slice which + // would prevent the entire slice from being eligible + // for GC as soon as it's sent. + continueHash := invMsg.InvList[invListLen-1].Hash + sp.continueHash = &continueHash + } + p.QueueMessage(invMsg, nil) + } +} + +// OnGetHeaders is invoked when a peer receives a getheaders bitcoin +// message. +func (sp *serverPeer) OnGetHeaders(p *peer.Peer, msg *wire.MsgGetHeaders) { + // Ignore getheaders requests if not in sync. + if !sp.server.blockManager.IsCurrent() { + return + } + + db := sp.server.db + + // Attempt to look up the height of the provided stop hash. + endIdx := database.AllShas + height, err := db.FetchBlockHeightBySha(&msg.HashStop) + if err == nil { + endIdx = height + 1 + } + + // There are no block locators so a specific header is being requested + // as identified by the stop hash. + if len(msg.BlockLocatorHashes) == 0 { + // No blocks with the stop hash were found so there is nothing + // to do. Just return. This behavior mirrors the reference + // implementation. + if endIdx == database.AllShas { + return + } + + // Fetch and send the requested block header. + header, err := db.FetchBlockHeaderBySha(&msg.HashStop) + if err != nil { + peerLog.Warnf("Lookup of known block hash failed: %v", + err) + return + } + + headersMsg := wire.NewMsgHeaders() + headersMsg.AddBlockHeader(header) + p.QueueMessage(headersMsg, nil) + return + } + + // Find the most recent known block based on the block locator. + // Use the block after the genesis block if no other blocks in the + // provided locator are known. This does mean the client will start + // over with the genesis block if unknown block locators are provided. + // This mirrors the behavior in the reference implementation. + startIdx := int32(1) + for _, hash := range msg.BlockLocatorHashes { + height, err := db.FetchBlockHeightBySha(hash) + if err == nil { + // Start with the next hash since we know this one. + startIdx = height + 1 + break + } + } + + // Don't attempt to fetch more than we can put into a single message. + if endIdx-startIdx > wire.MaxBlockHeadersPerMsg { + endIdx = startIdx + wire.MaxBlockHeadersPerMsg + } + + // Generate headers message and send it. + // + // The FetchHeightRange call is limited to a maximum number of hashes + // per invocation. Since the maximum number of headers per message + // might be larger, call it multiple times with the appropriate indices + // as needed. + headersMsg := wire.NewMsgHeaders() + for start := startIdx; start < endIdx; { + // Fetch the inventory from the block database. + hashList, err := db.FetchHeightRange(start, endIdx) + if err != nil { + peerLog.Warnf("Header lookup failed: %v", err) + return + } + + // The database did not return any further hashes. Break out of + // the loop now. + if len(hashList) == 0 { + break + } + + // Add headers to the message. + for _, hash := range hashList { + header, err := db.FetchBlockHeaderBySha(&hash) + if err != nil { + peerLog.Warnf("Lookup of known block hash "+ + "failed: %v", err) + continue + } + headersMsg.AddBlockHeader(header) + } + + // Start at the next block header after the latest one on the + // next loop iteration. + start += int32(len(hashList)) + } + p.QueueMessage(headersMsg, nil) +} + +// OnFilterAdd is invoked when a peer receives a filteradd bitcoin +// message and is used by remote peers to add data to an already loaded bloom +// filter. The peer will be disconnected if a filter is not loaded when this +// message is received. +func (sp *serverPeer) OnFilterAdd(p *peer.Peer, msg *wire.MsgFilterAdd) { + if sp.filter.IsLoaded() { + peerLog.Debugf("%s sent a filteradd request with no filter "+ + "loaded -- disconnecting", p) + p.Disconnect() + return + } + + sp.filter.Add(msg.Data) +} + +// OnFilterClear is invoked when a peer receives a filterclear bitcoin +// message and is used by remote peers to clear an already loaded bloom filter. +// The peer will be disconnected if a filter is not loaded when this message is +// received. +func (sp *serverPeer) OnFilterClear(p *peer.Peer, msg *wire.MsgFilterClear) { + if !sp.filter.IsLoaded() { + peerLog.Debugf("%s sent a filterclear request with no "+ + "filter loaded -- disconnecting", p) + p.Disconnect() + return + } + + sp.filter.Unload() +} + +// OnFilterLoad is invoked when a peer receives a filterload bitcoin +// message and it used to load a bloom filter that should be used for +// delivering merkle blocks and associated transactions that match the filter. +func (sp *serverPeer) OnFilterLoad(p *peer.Peer, msg *wire.MsgFilterLoad) { + sp.setDisableRelayTx(false) + + sp.filter.Reload(msg) +} + +// OnGetAddr is invoked when a peer receives a getaddr bitcoin message +// and is used to provide the peer with known addresses from the address +// manager. +func (sp *serverPeer) OnGetAddr(p *peer.Peer, msg *wire.MsgGetAddr) { + // Don't return any addresses when running on the simulation test + // network. This helps prevent the network from becoming another + // public test network since it will not be able to learn about other + // peers that have not specifically been provided. + if cfg.SimNet { + return + } + + // Do not accept getaddr requests from outbound peers. This reduces + // fingerprinting attacks. + if !p.Inbound() { + return + } + + // Get the current known addresses from the address manager. + addrCache := sp.server.addrManager.AddressCache() + + // Push the addresses. + sp.pushAddrMsg(addrCache) +} + +// OnAddr is invoked when a peer receives an addr bitcoin message and is +// used to notify the server about advertised addresses. +func (sp *serverPeer) OnAddr(p *peer.Peer, msg *wire.MsgAddr) { + // Ignore addresses when running on the simulation test network. This + // helps prevent the network from becoming another public test network + // since it will not be able to learn about other peers that have not + // specifically been provided. + if cfg.SimNet { + return + } + + // Ignore old style addresses which don't include a timestamp. + if p.ProtocolVersion() < wire.NetAddressTimeVersion { + return + } + + // A message that has no addresses is invalid. + if len(msg.AddrList) == 0 { + peerLog.Errorf("Command [%s] from %s does not contain any addresses", + msg.Command(), p) + p.Disconnect() + return + } + + for _, na := range msg.AddrList { + // Don't add more address if we're disconnecting. + if !p.Connected() { + return + } + + // Set the timestamp to 5 days ago if it's more than 24 hours + // in the future so this address is one of the first to be + // removed when space is needed. + now := time.Now() + if na.Timestamp.After(now.Add(time.Minute * 10)) { + na.Timestamp = now.Add(-1 * time.Hour * 24 * 5) + } + + // Add address to known addresses for this peer. + sp.addKnownAddresses([]*wire.NetAddress{na}) + } + + // Add addresses to server address manager. The address manager handles + // the details of things such as preventing duplicate addresses, max + // addresses, and last seen updates. + // XXX bitcoind gives a 2 hour time penalty here, do we want to do the + // same? + sp.server.addrManager.AddAddresses(msg.AddrList, p.NA()) +} + +// OnRead is invoked when a peer receives a message and it is used to update +// the bytes received by the server. +func (sp *serverPeer) OnRead(p *peer.Peer, bytesRead int, msg wire.Message, err error) { + sp.server.AddBytesReceived(uint64(bytesRead)) +} + +// OnWrite is invoked when a peer sends a message and it is used to update +// the bytes sent by the server. +func (sp *serverPeer) OnWrite(p *peer.Peer, bytesWritten int, msg wire.Message, err error) { + sp.server.AddBytesSent(uint64(bytesWritten)) } // randomUint16Number returns a random uint16 in a specified input range. Note @@ -163,97 +864,194 @@ func (s *server) RemoveRebroadcastInventory(iv *wire.InvVect) { s.modifyRebroadcastInv <- broadcastInventoryDel(iv) } -func (p *peerState) Count() int { - return len(p.peers) + len(p.outboundPeers) + len(p.persistentPeers) -} +// pushTxMsg sends a tx message for the provided transaction hash to the +// connected peer. An error is returned if the transaction hash is not known. +func (s *server) pushTxMsg(sp *serverPeer, sha *wire.ShaHash, doneChan, waitChan chan struct{}) error { + // Attempt to fetch the requested transaction from the pool. A + // call could be made to check for existence first, but simply trying + // to fetch a missing transaction results in the same behavior. + tx, err := s.txMemPool.FetchTransaction(sha) + if err != nil { + peerLog.Tracef("Unable to fetch tx %v from transaction "+ + "pool: %v", sha, err) -func (p *peerState) OutboundCount() int { - return len(p.outboundPeers) + len(p.persistentPeers) -} - -func (p *peerState) NeedMoreOutbound() bool { - return p.OutboundCount() < p.maxOutboundPeers && - p.Count() < cfg.MaxPeers -} - -// forAllOutboundPeers is a helper function that runs closure on all outbound -// peers known to peerState. -func (p *peerState) forAllOutboundPeers(closure func(p *peer)) { - for e := range p.outboundPeers { - closure(e) + if doneChan != nil { + doneChan <- struct{}{} + } + return err } - for e := range p.persistentPeers { - closure(e) + + // Once we have fetched data wait for any previous operation to finish. + if waitChan != nil { + <-waitChan } + + sp.QueueMessage(tx.MsgTx(), doneChan) + + return nil } -// forAllPeers is a helper function that runs closure on all peers known to -// peerState. -func (p *peerState) forAllPeers(closure func(p *peer)) { - for e := range p.peers { - closure(e) +// pushBlockMsg sends a block message for the provided block hash to the +// connected peer. An error is returned if the block hash is not known. +func (s *server) pushBlockMsg(sp *serverPeer, sha *wire.ShaHash, doneChan, waitChan chan struct{}) error { + blk, err := s.db.FetchBlockBySha(sha) + if err != nil { + peerLog.Tracef("Unable to fetch requested block sha %v: %v", + sha, err) + + if doneChan != nil { + doneChan <- struct{}{} + } + return err } - p.forAllOutboundPeers(closure) + + // Once we have fetched data wait for any previous operation to finish. + if waitChan != nil { + <-waitChan + } + + // We only send the channel for this message if we aren't sending + // an inv straight after. + var dc chan struct{} + continueHash := sp.continueHash + sendInv := continueHash != nil && continueHash.IsEqual(sha) + if !sendInv { + dc = doneChan + } + sp.QueueMessage(blk.MsgBlock(), dc) + + // When the peer requests the final block that was advertised in + // response to a getblocks message which requested more blocks than + // would fit into a single message, send it a new inventory message + // to trigger it to issue another getblocks message for the next + // batch of inventory. + if sendInv { + hash, _, err := s.db.NewestSha() + if err == nil { + invMsg := wire.NewMsgInvSizeHint(1) + iv := wire.NewInvVect(wire.InvTypeBlock, hash) + invMsg.AddInvVect(iv) + sp.QueueMessage(invMsg, doneChan) + sp.continueHash = nil + } else if doneChan != nil { + doneChan <- struct{}{} + } + } + return nil +} + +// pushMerkleBlockMsg sends a merkleblock message for the provided block hash to +// the connected peer. Since a merkle block requires the peer to have a filter +// loaded, this call will simply be ignored if there is no filter loaded. An +// error is returned if the block hash is not known. +func (s *server) pushMerkleBlockMsg(sp *serverPeer, sha *wire.ShaHash, doneChan, waitChan chan struct{}) error { + // Do not send a response if the peer doesn't have a filter loaded. + if !sp.filter.IsLoaded() { + if doneChan != nil { + doneChan <- struct{}{} + } + return nil + } + + blk, err := s.db.FetchBlockBySha(sha) + if err != nil { + peerLog.Tracef("Unable to fetch requested block sha %v: %v", + sha, err) + + if doneChan != nil { + doneChan <- struct{}{} + } + return err + } + + // Generate a merkle block by filtering the requested block according + // to the filter for the peer. + merkle, matchedTxIndices := bloom.NewMerkleBlock(blk, sp.filter) + + // Once we have fetched data wait for any previous operation to finish. + if waitChan != nil { + <-waitChan + } + + // Send the merkleblock. Only send the done channel with this message + // if no transactions will be sent afterwards. + var dc chan struct{} + if len(matchedTxIndices) == 0 { + dc = doneChan + } + sp.QueueMessage(merkle, dc) + + // Finally, send any matched transactions. + blkTransactions := blk.MsgBlock().Transactions + for i, txIndex := range matchedTxIndices { + // Only send the done channel on the final transaction. + var dc chan struct{} + if i == len(matchedTxIndices)-1 { + dc = doneChan + } + if txIndex < uint32(len(blkTransactions)) { + sp.QueueMessage(blkTransactions[txIndex], dc) + } + } + + return nil } // handleUpdatePeerHeight updates the heights of all peers who were known to // announce a block we recently accepted. func (s *server) handleUpdatePeerHeights(state *peerState, umsg updatePeerHeightsMsg) { - state.forAllPeers(func(p *peer) { + state.forAllPeers(func(sp *serverPeer) { // The origin peer should already have the updated height. - if p == umsg.originPeer { - return - } - - // Skip this peer if it hasn't recently announced any new blocks. - p.StatsMtx.Lock() - if p.lastAnnouncedBlock == nil { - p.StatsMtx.Unlock() + if sp == umsg.originPeer { return } // This is a pointer to the underlying memory which doesn't // change. - latestBlkSha := p.lastAnnouncedBlock - p.StatsMtx.Unlock() + latestBlkSha := sp.LastAnnouncedBlock() + + // Skip this peer if it hasn't recently announced any new blocks. + if latestBlkSha == nil { + return + } // If the peer has recently announced a block, and this block // matches our newly accepted block, then update their block // height. if *latestBlkSha == *umsg.newSha { - p.UpdateLastBlockHeight(umsg.newHeight) - p.UpdateLastAnnouncedBlock(nil) + sp.UpdateLastBlockHeight(umsg.newHeight) + sp.UpdateLastAnnouncedBlock(nil) } }) } // handleAddPeerMsg deals with adding new peers. It is invoked from the // peerHandler goroutine. -func (s *server) handleAddPeerMsg(state *peerState, p *peer) bool { - if p == nil { +func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { + if sp == nil { return false } // Ignore new peers if we're shutting down. if atomic.LoadInt32(&s.shutdown) != 0 { srvrLog.Infof("New peer %s ignored - server is shutting "+ - "down", p) - p.Shutdown() + "down", sp) + sp.Shutdown() return false } // Disconnect banned peers. - host, _, err := net.SplitHostPort(p.addr) + host, _, err := net.SplitHostPort(sp.Addr()) if err != nil { srvrLog.Debugf("can't split hostport %v", err) - p.Shutdown() + sp.Shutdown() return false } if banEnd, ok := state.banned[host]; ok { if time.Now().Before(banEnd) { srvrLog.Debugf("Peer %s is banned for another %v - "+ "disconnecting", host, banEnd.Sub(time.Now())) - p.Shutdown() + sp.Shutdown() return false } @@ -263,28 +1061,39 @@ func (s *server) handleAddPeerMsg(state *peerState, p *peer) bool { // TODO: Check for max peers from a single IP. + // Limit max outbound peers. + if _, ok := state.pendingPeers[sp.Addr()]; ok { + if state.OutboundCount() >= state.maxOutboundPeers { + srvrLog.Infof("Max outbound peers reached [%d] - disconnecting "+ + "peer %s", state.maxOutboundPeers, sp) + sp.Shutdown() + return false + } + } + // Limit max number of total peers. if state.Count() >= cfg.MaxPeers { srvrLog.Infof("Max peers reached [%d] - disconnecting "+ - "peer %s", cfg.MaxPeers, p) - p.Shutdown() + "peer %s", cfg.MaxPeers, sp) + sp.Shutdown() // TODO(oga) how to handle permanent peers here? // they should be rescheduled. return false } // Add the new peer and start it. - srvrLog.Debugf("New peer %s", p) - if p.inbound { - state.peers[p] = struct{}{} - p.Start() + srvrLog.Debugf("New peer %s", sp) + if sp.Inbound() { + state.peers[sp.ID()] = sp } else { - state.outboundGroups[addrmgr.GroupKey(p.na)]++ - if p.persistent { - state.persistentPeers[p] = struct{}{} + state.outboundGroups[addrmgr.GroupKey(sp.NA())]++ + if sp.persistent { + state.persistentPeers[sp.ID()] = sp } else { - state.outboundPeers[p] = struct{}{} + state.outboundPeers[sp.ID()] = sp } + // Remove from pending peers. + delete(state.pendingPeers, sp.Addr()) } return true @@ -292,70 +1101,83 @@ func (s *server) handleAddPeerMsg(state *peerState, p *peer) bool { // handleDonePeerMsg deals with peers that have signalled they are done. It is // invoked from the peerHandler goroutine. -func (s *server) handleDonePeerMsg(state *peerState, p *peer) { - var list map[*peer]struct{} - if p.persistent { +func (s *server) handleDonePeerMsg(state *peerState, sp *serverPeer) { + if _, ok := state.pendingPeers[sp.Addr()]; ok { + delete(state.pendingPeers, sp.Addr()) + srvrLog.Debugf("Removed pending peer %s", sp) + return + } + + var list map[int32]*serverPeer + if sp.persistent { list = state.persistentPeers - } else if p.inbound { + } else if sp.Inbound() { list = state.peers } else { list = state.outboundPeers } - for e := range list { - if e == p { + for id, e := range list { + if e == sp { // Issue an asynchronous reconnect if the peer was a // persistent outbound connection. - if !p.inbound && p.persistent && atomic.LoadInt32(&s.shutdown) == 0 { - delete(list, e) - e = newOutboundPeer(s, p.addr, true, p.retryCount+1) - list[e] = struct{}{} + if !sp.Inbound() && sp.persistent && atomic.LoadInt32(&s.shutdown) == 0 { + // Retry peer + sp = s.newOutboundPeer(sp.Addr(), sp.persistent) + if sp != nil { + go s.retryConn(sp, connectionRetryInterval/2) + } + list[id] = sp return } - if !p.inbound { - state.outboundGroups[addrmgr.GroupKey(p.na)]-- + if !sp.Inbound() && sp.VersionKnown() { + state.outboundGroups[addrmgr.GroupKey(sp.NA())]-- } - delete(list, e) - srvrLog.Debugf("Removed peer %s", p) + delete(list, id) + srvrLog.Debugf("Removed peer %s", sp) return } } + // Update the address' last seen time if the peer has acknowledged + // our version and has sent us its version as well. + if sp.VerAckReceived() && sp.VersionKnown() && sp.NA() != nil { + s.addrManager.Connected(sp.NA()) + } + // If we get here it means that either we didn't know about the peer // or we purposefully deleted it. } // handleBanPeerMsg deals with banning peers. It is invoked from the // peerHandler goroutine. -func (s *server) handleBanPeerMsg(state *peerState, p *peer) { - host, _, err := net.SplitHostPort(p.addr) +func (s *server) handleBanPeerMsg(state *peerState, sp *serverPeer) { + host, _, err := net.SplitHostPort(sp.Addr()) if err != nil { - srvrLog.Debugf("can't split ban peer %s %v", p.addr, err) + srvrLog.Debugf("can't split ban peer %s %v", sp.Addr(), err) return } - direction := directionString(p.inbound) + direction := directionString(sp.Inbound()) srvrLog.Infof("Banned peer %s (%s) for %v", host, direction, cfg.BanDuration) state.banned[host] = time.Now().Add(cfg.BanDuration) - } // handleRelayInvMsg deals with relaying inventory to peers that are not already // known to have it. It is invoked from the peerHandler goroutine. func (s *server) handleRelayInvMsg(state *peerState, msg relayMsg) { - state.forAllPeers(func(p *peer) { - if !p.Connected() { + state.forAllPeers(func(sp *serverPeer) { + if !sp.Connected() { return } if msg.invVect.Type == wire.InvTypeTx { // Don't relay the transaction to the peer when it has // transaction relaying disabled. - if p.RelayTxDisabled() { + if sp.relayTxDisabled() { return } - // Don't relay the transaction if there is a bloom // filter loaded and the transaction doesn't match it. - if p.filter.IsLoaded() { + if sp.filter.IsLoaded() { tx, ok := msg.data.(*btcutil.Tx) if !ok { peerLog.Warnf("Underlying data for tx" + @@ -363,7 +1185,7 @@ func (s *server) handleRelayInvMsg(state *peerState, msg relayMsg) { return } - if !p.filter.MatchTxAndUpdate(tx) { + if !sp.filter.MatchTxAndUpdate(tx) { return } } @@ -372,26 +1194,26 @@ func (s *server) handleRelayInvMsg(state *peerState, msg relayMsg) { // Queue the inventory to be relayed with the next batch. // It will be ignored if the peer is already known to // have the inventory. - p.QueueInventory(msg.invVect) + sp.QueueInventory(msg.invVect) }) } // handleBroadcastMsg deals with broadcasting messages to peers. It is invoked // from the peerHandler goroutine. func (s *server) handleBroadcastMsg(state *peerState, bmsg *broadcastMsg) { - state.forAllPeers(func(p *peer) { + state.forAllPeers(func(sp *serverPeer) { excluded := false for _, ep := range bmsg.excludePeers { - if p == ep { + if sp == ep { excluded = true } } // Don't broadcast to still connecting outbound peers . - if !p.Connected() { + if !sp.Connected() { excluded = true } if !excluded { - p.QueueMessage(bmsg.message, nil) + sp.QueueMessage(bmsg.message, nil) } }) } @@ -400,16 +1222,16 @@ type getConnCountMsg struct { reply chan int32 } -type getPeerInfoMsg struct { - reply chan []*btcjson.GetPeerInfoResult +type getPeersMsg struct { + reply chan []*serverPeer } type getAddedNodesMsg struct { - reply chan []*peer + reply chan []*serverPeer } type disconnectNodeMsg struct { - cmp func(*peer) bool + cmp func(*serverPeer) bool reply chan error } @@ -420,69 +1242,37 @@ type connectNodeMsg struct { } type removeNodeMsg struct { - cmp func(*peer) bool + cmp func(*serverPeer) bool reply chan error } // handleQuery is the central handler for all queries and commands from other // goroutines related to peer state. -func (s *server) handleQuery(querymsg interface{}, state *peerState) { +func (s *server) handleQuery(state *peerState, querymsg interface{}) { switch msg := querymsg.(type) { case getConnCountMsg: nconnected := int32(0) - state.forAllPeers(func(p *peer) { - if p.Connected() { + state.forAllPeers(func(sp *serverPeer) { + if sp.Connected() { nconnected++ } }) msg.reply <- nconnected - case getPeerInfoMsg: - syncPeer := s.blockManager.SyncPeer() - infos := make([]*btcjson.GetPeerInfoResult, 0, len(state.peers)) - state.forAllPeers(func(p *peer) { - if !p.Connected() { + case getPeersMsg: + peers := make([]*serverPeer, 0, state.Count()) + state.forAllPeers(func(sp *serverPeer) { + if !sp.Connected() { return } - - // A lot of this will make the race detector go mad, - // however it is statistics for purely informational purposes - // and we don't really care if they are raced to get the new - // version. - p.StatsMtx.Lock() - info := &btcjson.GetPeerInfoResult{ - ID: p.id, - Addr: p.addr, - Services: fmt.Sprintf("%08d", p.services), - LastSend: p.lastSend.Unix(), - LastRecv: p.lastRecv.Unix(), - BytesSent: p.bytesSent, - BytesRecv: p.bytesReceived, - ConnTime: p.timeConnected.Unix(), - TimeOffset: p.timeOffset, - Version: p.protocolVersion, - SubVer: p.userAgent, - Inbound: p.inbound, - StartingHeight: p.startingHeight, - CurrentHeight: p.lastBlock, - BanScore: 0, - SyncNode: p == syncPeer, - } - info.PingTime = float64(p.lastPingMicros) - if p.lastPingNonce != 0 { - wait := float64(time.Now().Sub(p.lastPingTime).Nanoseconds()) - // We actually want microseconds. - info.PingWait = wait / 1000 - } - p.StatsMtx.Unlock() - infos = append(infos, info) + peers = append(peers, sp) }) - msg.reply <- infos + msg.reply <- peers case connectNodeMsg: // XXX(oga) duplicate oneshots? - for peer := range state.persistentPeers { - if peer.addr == msg.addr { + for _, peer := range state.persistentPeers { + if peer.Addr() == msg.addr { if msg.permanent { msg.reply <- errors.New("peer already connected") } else { @@ -493,17 +1283,18 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) { } // TODO(oga) if too many, nuke a non-perm peer. - if s.handleAddPeerMsg(state, - newOutboundPeer(s, msg.addr, msg.permanent, 0)) { + sp := s.newOutboundPeer(msg.addr, msg.permanent) + if sp != nil { + go s.peerConnHandler(sp) msg.reply <- nil } else { msg.reply <- errors.New("failed to add peer") } case removeNodeMsg: - found := disconnectPeer(state.persistentPeers, msg.cmp, func(p *peer) { + found := disconnectPeer(state.persistentPeers, msg.cmp, func(sp *serverPeer) { // Keep group counts ok since we remove from // the list now. - state.outboundGroups[addrmgr.GroupKey(p.na)]-- + state.outboundGroups[addrmgr.GroupKey(sp.NA())]-- }) if found { @@ -514,9 +1305,9 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) { // Request a list of the persistent (added) peers. case getAddedNodesMsg: // Respond with a slice of the relavent peers. - peers := make([]*peer, 0, len(state.persistentPeers)) - for peer := range state.persistentPeers { - peers = append(peers, peer) + peers := make([]*serverPeer, 0, len(state.persistentPeers)) + for _, sp := range state.persistentPeers { + peers = append(peers, sp) } msg.reply <- peers case disconnectNodeMsg: @@ -529,18 +1320,18 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) { } // Check outbound peers. - found = disconnectPeer(state.outboundPeers, msg.cmp, func(p *peer) { + found = disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) { // Keep group counts ok since we remove from // the list now. - state.outboundGroups[addrmgr.GroupKey(p.na)]-- + state.outboundGroups[addrmgr.GroupKey(sp.NA())]-- }) if found { // If there are multiple outbound connections to the same // ip:port, continue disconnecting them all until no such // peers are found. for found { - found = disconnectPeer(state.outboundPeers, msg.cmp, func(p *peer) { - state.outboundGroups[addrmgr.GroupKey(p.na)]-- + found = disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) { + state.outboundGroups[addrmgr.GroupKey(sp.NA())]-- }) } msg.reply <- nil @@ -558,8 +1349,8 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) { // to be located. If the peer is found, and the passed callback: `whenFound' // isn't nil, we call it with the peer as the argument before it is removed // from the peerList, and is disconnected from the server. -func disconnectPeer(peerList map[*peer]struct{}, compareFunc func(*peer) bool, whenFound func(*peer)) bool { - for peer := range peerList { +func disconnectPeer(peerList map[int32]*serverPeer, compareFunc func(*serverPeer) bool, whenFound func(*serverPeer)) bool { + for addr, peer := range peerList { if compareFunc(peer) { if whenFound != nil { whenFound(peer) @@ -567,7 +1358,7 @@ func disconnectPeer(peerList map[*peer]struct{}, compareFunc func(*peer) bool, w // This is ok because we are not continuing // to iterate so won't corrupt the loop. - delete(peerList, peer) + delete(peerList, addr) peer.Disconnect() return true } @@ -575,6 +1366,38 @@ func disconnectPeer(peerList map[*peer]struct{}, compareFunc func(*peer) bool, w return false } +// newPeerConfig returns the configuration for the given serverPeer. +func newPeerConfig(sp *serverPeer) *peer.Config { + return &peer.Config{ + Listeners: peer.MessageListeners{ + OnVersion: sp.OnVersion, + OnMemPool: sp.OnMemPool, + OnTx: sp.OnTx, + OnBlock: sp.OnBlock, + OnInv: sp.OnInv, + OnHeaders: sp.OnHeaders, + OnGetData: sp.OnGetData, + OnGetBlocks: sp.OnGetBlocks, + OnGetHeaders: sp.OnGetHeaders, + OnFilterAdd: sp.OnFilterAdd, + OnFilterClear: sp.OnFilterClear, + OnFilterLoad: sp.OnFilterLoad, + OnGetAddr: sp.OnGetAddr, + OnAddr: sp.OnAddr, + OnRead: sp.OnRead, + OnWrite: sp.OnWrite, + }, + NewestBlock: sp.server.db.NewestSha, + BestLocalAddress: sp.server.addrManager.GetBestLocalAddress, + HostToNetAddress: sp.server.addrManager.HostToNetAddress, + Proxy: cfg.Proxy, + UserAgentName: userAgentName, + UserAgentVersion: userAgentVersion, + ChainParams: sp.server.chainParams, + Services: sp.server.services, + } +} + // listenHandler is the main listener which accepts incoming connections for the // server. It must be run as a goroutine. func (s *server) listenHandler(listener net.Listener) { @@ -589,7 +1412,10 @@ func (s *server) listenHandler(listener net.Listener) { } continue } - s.AddPeer(newInboundPeer(s, conn)) + sp := newServerPeer(s, false) + sp.Peer = peer.NewInboundPeer(newPeerConfig(sp), conn) + sp.Start() + go s.peerDoneHandler(sp) } s.wg.Done() srvrLog.Tracef("Listener handler done for %s", listener.Addr()) @@ -642,6 +1468,76 @@ func (s *server) seedFromDNS() { } } +// newOutboundPeer initializes a new outbound peer and setups the message +// listeners. +func (s *server) newOutboundPeer(addr string, persistent bool) *serverPeer { + sp := newServerPeer(s, persistent) + p, err := peer.NewOutboundPeer(newPeerConfig(sp), addr) + if err != nil { + srvrLog.Errorf("Cannot create outbound peer: %v", err) + return nil + } + sp.Peer = p + go s.peerDoneHandler(sp) + return sp +} + +// peerConnHandler handles peer connections. It must be run in a goroutine. +func (s *server) peerConnHandler(sp *serverPeer) { + err := s.establishConn(sp) + if err != nil { + srvrLog.Debugf("Failed to connect to %s: %v", sp.Addr(), err) + s.donePeers <- sp + } +} + +// peerDoneHandler handles peer disconnects by notifiying the server that it's +// done. +func (s *server) peerDoneHandler(sp *serverPeer) { + sp.WaitForShutdown() + s.donePeers <- sp + + // Only tell block manager we are gone if we ever told it we existed. + if sp.VersionKnown() { + s.blockManager.DonePeer(sp) + } + close(sp.quit) +} + +// establishConn establishes a connection to the peer. +func (s *server) establishConn(sp *serverPeer) error { + srvrLog.Debugf("Attempting to connect to %s", sp.Addr()) + conn, err := btcdDial("tcp", sp.Addr()) + if err != nil { + return err + } + if err := sp.Connect(conn); err != nil { + return err + } + srvrLog.Debugf("Connected to %s", sp.Addr()) + s.addrManager.Attempt(sp.NA()) + return nil +} + +// retryConn retries connection to the peer after the given duration. +func (s *server) retryConn(sp *serverPeer, retryDuration time.Duration) { + srvrLog.Debugf("Retrying connection to %s in %s", sp.Addr(), retryDuration) + select { + case <-time.After(retryDuration): + err := s.establishConn(sp) + if err != nil { + retryDuration += connectionRetryInterval / 2 + if retryDuration > maxConnectionRetryInterval { + retryDuration = maxConnectionRetryInterval + } + go s.retryConn(sp, retryDuration) + return + } + case <-sp.quit: + case <-s.quit: + } +} + // peerHandler is used to handle peer operations such as adding and removing // peers to and from the server, banning peers, and broadcasting messages to // peers. It must be run in a goroutine. @@ -655,10 +1551,12 @@ func (s *server) peerHandler() { s.blockManager.Start() srvrLog.Tracef("Starting peer handler") + state := &peerState{ - peers: make(map[*peer]struct{}), - persistentPeers: make(map[*peer]struct{}), - outboundPeers: make(map[*peer]struct{}), + pendingPeers: make(map[string]*serverPeer), + peers: make(map[int32]*serverPeer), + persistentPeers: make(map[int32]*serverPeer), + outboundPeers: make(map[int32]*serverPeer), banned: make(map[string]time.Time), maxOutboundPeers: defaultMaxOutbound, outboundGroups: make(map[string]int), @@ -666,7 +1564,6 @@ func (s *server) peerHandler() { if cfg.MaxPeers < state.maxOutboundPeers { state.maxOutboundPeers = cfg.MaxPeers } - // Add peers discovered through DNS to the address manager. s.seedFromDNS() @@ -676,7 +1573,10 @@ func (s *server) peerHandler() { permanentPeers = cfg.AddPeers } for _, addr := range permanentPeers { - s.handleAddPeerMsg(state, newOutboundPeer(s, addr, true, 0)) + sp := s.newOutboundPeer(addr, true) + if sp != nil { + go s.peerConnHandler(sp) + } } // if nothing else happens, wake us up soon. @@ -715,13 +1615,13 @@ out: // this page left intentionally blank case qmsg := <-s.query: - s.handleQuery(qmsg, state) + s.handleQuery(state, qmsg) // Shutdown the peer handler. case <-s.quit: // Shutdown peers. - state.forAllPeers(func(p *peer) { - p.Shutdown() + state.forAllPeers(func(sp *serverPeer) { + sp.Shutdown() }) break out } @@ -737,15 +1637,15 @@ out: // Only try connect to more peers if we actually need more. if !state.NeedMoreOutbound() || len(cfg.ConnectPeers) > 0 || atomic.LoadInt32(&s.shutdown) != 0 { + state.forPendingPeers(func(sp *serverPeer) { + sp.Shutdown() + }) continue } tries := 0 for state.NeedMoreOutbound() && + state.NeedMoreTries() && atomic.LoadInt32(&s.shutdown) == 0 { - nPeers := state.OutboundCount() - if nPeers > 8 { - nPeers = 8 - } addr := s.addrManager.GetAddress("any") if addr == nil { break @@ -761,6 +1661,12 @@ out: break } + // Check that we don't have a pending connection to this addr. + addrStr := addrmgr.NetAddressKey(addr.NetAddress()) + if _, ok := state.pendingPeers[addrStr]; ok { + continue + } + tries++ // After 100 bad tries exit the loop and we'll try again // later. @@ -782,13 +1688,11 @@ out: continue } - addrStr := addrmgr.NetAddressKey(addr.NetAddress()) - tries = 0 - // any failure will be due to banned peers etc. we have - // already checked that we have room for more peers. - if s.handleAddPeerMsg(state, - newOutboundPeer(s, addrStr, false, 0)) { + sp := s.newOutboundPeer(addrStr, false) + if sp != nil { + go s.peerConnHandler(sp) + state.pendingPeers[sp.Addr()] = sp } } @@ -810,13 +1714,13 @@ out: } // AddPeer adds a new peer that has already been connected to the server. -func (s *server) AddPeer(p *peer) { - s.newPeers <- p +func (s *server) AddPeer(sp *serverPeer) { + s.newPeers <- sp } // BanPeer bans a peer that has already been connected to the server by ip. -func (s *server) BanPeer(p *peer) { - s.banPeers <- p +func (s *server) BanPeer(sp *serverPeer) { + s.banPeers <- sp } // RelayInventory relays the passed inventory to all connected peers that are @@ -827,7 +1731,7 @@ func (s *server) RelayInventory(invVect *wire.InvVect, data interface{}) { // BroadcastMessage sends msg to all peers currently connected to the server // except those in the passed peers to exclude. -func (s *server) BroadcastMessage(msg wire.Message, exclPeers ...*peer) { +func (s *server) BroadcastMessage(msg wire.Message, exclPeers ...*serverPeer) { // XXX: Need to determine if this is an alert that has already been // broadcast and refrain from broadcasting again. bmsg := broadcastMsg{message: msg, excludePeers: exclPeers} @@ -845,18 +1749,17 @@ func (s *server) ConnectedCount() int32 { // AddedNodeInfo returns an array of btcjson.GetAddedNodeInfoResult structures // describing the persistent (added) nodes. -func (s *server) AddedNodeInfo() []*peer { - replyChan := make(chan []*peer) +func (s *server) AddedNodeInfo() []*serverPeer { + replyChan := make(chan []*serverPeer) s.query <- getAddedNodesMsg{reply: replyChan} return <-replyChan } -// PeerInfo returns an array of PeerInfo structures describing all connected -// peers. -func (s *server) PeerInfo() []*btcjson.GetPeerInfoResult { - replyChan := make(chan []*btcjson.GetPeerInfoResult) +// Peers returns an array of all connected peers. +func (s *server) Peers() []*serverPeer { + replyChan := make(chan []*serverPeer) - s.query <- getPeerInfoMsg{reply: replyChan} + s.query <- getPeersMsg{reply: replyChan} return <-replyChan } @@ -868,7 +1771,7 @@ func (s *server) DisconnectNodeByAddr(addr string) error { replyChan := make(chan error) s.query <- disconnectNodeMsg{ - cmp: func(p *peer) bool { return p.addr == addr }, + cmp: func(sp *serverPeer) bool { return sp.Addr() == addr }, reply: replyChan, } @@ -882,7 +1785,7 @@ func (s *server) DisconnectNodeByID(id int32) error { replyChan := make(chan error) s.query <- disconnectNodeMsg{ - cmp: func(p *peer) bool { return p.id == id }, + cmp: func(sp *serverPeer) bool { return sp.ID() == id }, reply: replyChan, } @@ -895,7 +1798,7 @@ func (s *server) RemoveNodeByAddr(addr string) error { replyChan := make(chan error) s.query <- removeNodeMsg{ - cmp: func(p *peer) bool { return p.addr == addr }, + cmp: func(sp *serverPeer) bool { return sp.Addr() == addr }, reply: replyChan, } @@ -908,7 +1811,7 @@ func (s *server) RemoveNodeByID(id int32) error { replyChan := make(chan error) s.query <- removeNodeMsg{ - cmp: func(p *peer) bool { return p.id == id }, + cmp: func(sp *serverPeer) bool { return sp.ID() == id }, reply: replyChan, } @@ -957,7 +1860,7 @@ func (s *server) NetTotals() (uint64, uint64) { // the latest connected main chain block, or a recognized orphan. These height // updates allow us to dynamically refresh peer heights, ensuring sync peer // selection has access to the latest block heights for each peer. -func (s *server) UpdatePeerHeights(latestBlkSha *wire.ShaHash, latestHeight int32, updateSource *peer) { +func (s *server) UpdatePeerHeights(latestBlkSha *wire.ShaHash, latestHeight int32, updateSource *serverPeer) { s.peerHeightsUpdate <- updatePeerHeightsMsg{ newSha: latestBlkSha, newHeight: latestHeight, @@ -1248,11 +2151,6 @@ out: // bitcoin network type specified by chainParams. Use start to begin accepting // connections from peers. func newServer(listenAddrs []string, db database.Db, chainParams *chaincfg.Params) (*server, error) { - nonce, err := wire.RandomUint64() - if err != nil { - return nil, err - } - services := defaultServices if cfg.NoPeerBloomFilters { services &^= wire.SFNodeBloom @@ -1385,13 +2283,13 @@ func newServer(listenAddrs []string, db database.Db, chainParams *chaincfg.Param } s := server{ - nonce: nonce, listeners: listeners, chainParams: chainParams, addrManager: amgr, - newPeers: make(chan *peer, cfg.MaxPeers), - donePeers: make(chan *peer, cfg.MaxPeers), - banPeers: make(chan *peer, cfg.MaxPeers), + newPeers: make(chan *serverPeer, cfg.MaxPeers), + donePeers: make(chan *serverPeer, cfg.MaxPeers), + banPeers: make(chan *serverPeer, cfg.MaxPeers), + retryPeers: make(chan *serverPeer, cfg.MaxPeers), wakeup: make(chan struct{}), query: make(chan interface{}), relayInv: make(chan relayMsg, cfg.MaxPeers),