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),