diff --git a/blockmanager.go b/blockmanager.go
index 43b4e9f0..da02ebb4 100644
--- a/blockmanager.go
+++ b/blockmanager.go
@@ -46,48 +46,51 @@ const (
maxResendLimit = 3
)
+// zeroHash is the zero value hash (all zeros). It is defined as a convenience.
+var zeroHash chainhash.Hash
+
// newPeerMsg signifies a newly connected peer to the block handler.
type newPeerMsg struct {
- peer *peer
+ peer *serverPeer
}
// blockMsg packages a decred block message and the peer it came from together
// so the block handler has access to that information.
type blockMsg struct {
block *dcrutil.Block
- peer *peer
+ peer *serverPeer
}
// invMsg packages a decred 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 decred 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 decred tx message and the peer it came from together
// so the block handler has access to that information.
type txMsg struct {
tx *dcrutil.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
}
// requestFromPeerMsg is a message type to be sent across the message channel
@@ -95,13 +98,13 @@ type getSyncPeerMsg struct {
// this through the block manager so the block manager doesn't ban the peer
// when it sends this information back.
type requestFromPeerMsg struct {
- peer *peer
+ peer *serverPeer
blocks []*chainhash.Hash
txs []*chainhash.Hash
reply chan requestFromPeerResponse
}
-// requestFromPeerRespons eis a response sent to the reply channel of a
+// requestFromPeerResponse is a response sent to the reply channel of a
// requestFromPeerMsg query.
type requestFromPeerResponse struct {
err error
@@ -567,7 +570,7 @@ type blockManager struct {
receivedLogTx int64
lastBlockLogTime time.Time
processingReqs bool
- syncPeer *peer
+ syncPeer *serverPeer
msgChan chan interface{}
chainState chainState
wg sync.WaitGroup
@@ -686,11 +689,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
@@ -698,14 +701,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.
@@ -718,7 +721,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
@@ -744,7 +747,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)
}
@@ -756,9 +759,9 @@ 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 {
// 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
}
@@ -769,14 +772,16 @@ func (b *blockManager) isSyncCandidate(p *peer) bool {
// syncMiningStateAfterSync polls the blockMananger for the current sync
// state; if the mananger is synced, it executes a call to the peer to
// sync the mining state to the network.
-func (b *blockManager) syncMiningStateAfterSync(p *peer) {
- ticker := time.NewTicker(time.Second * 3)
+func (b *blockManager) syncMiningStateAfterSync(sp *serverPeer) {
go func() {
+ ticker := time.NewTicker(time.Second * 3)
+ defer ticker.Stop()
for {
select {
case <-ticker.C:
if b.IsCurrent() {
- p.PushGetMiningStateMsg()
+ msg := wire.NewMsgGetMiningState()
+ sp.QueueMessage(msg, nil)
return
}
}
@@ -787,28 +792,28 @@ func (b *blockManager) syncMiningStateAfterSync(p *peer) {
// 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)
// Grab the mining state from this peer after we're synced.
if !cfg.NoMiningStateSync {
- b.syncMiningStateAfterSync(p)
+ b.syncMiningStateAfterSync(sp)
}
}
@@ -816,20 +821,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)
}
@@ -837,14 +842,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
@@ -967,7 +972,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 < int64(b.syncPeer.startingHeight) {
+ if err != nil || height < int64(b.syncPeer.LastBlock()) {
return false
}
@@ -1157,24 +1162,24 @@ func (b *blockManager) checkBlockForHiddenVotes(block *dcrutil.Block) {
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 {
// Check to see if we ever requested this block, since it may
// have been accidentally sent in duplicate. If it was,
// increment the counter in the ever requested map and make
// sure that the node isn't spamming us with these blocks.
- received, ok := b.requestedEverBlocks[*blockSha]
- if ok {
+ received, exists := b.requestedEverBlocks[*blockSha]
+ if exists {
if received > maxResendLimit {
bmgrLog.Warnf("Got duplicate block %v from %s -- "+
"too many times, disconnecting", blockSha,
- bmsg.peer.addr)
+ bmsg.peer.Addr())
bmsg.peer.Disconnect()
return
}
b.requestedEverBlocks[*blockSha]++
} else {
bmgrLog.Warnf("Got unrequested block %v from %s -- "+
- "disconnecting", blockSha, bmsg.peer.addr)
+ "disconnecting", blockSha, bmsg.peer.Addr())
bmsg.peer.Disconnect()
return
}
@@ -1424,12 +1429,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
}
@@ -1443,7 +1448,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
}
}
@@ -1493,14 +1498,14 @@ func (b *blockManager) fetchHeaderBlocks() {
}
}
-// handleHeadersMsghandles headers messages from all peers.
+// handleHeadersMsg handles headers messages from all peers.
func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) {
// The remote peer is misbehaving if we didn't request headers.
msg := hmsg.headers
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
}
@@ -1540,7 +1545,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
}
@@ -1557,7 +1562,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
@@ -1588,7 +1593,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
}
}
@@ -2440,69 +2445,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 *dcrutil.Tx, p *peer) {
+func (b *blockManager) QueueTx(tx *dcrutil.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 *dcrutil.Block, p *peer) {
+func (b *blockManager) QueueBlock(block *dcrutil.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.
@@ -2533,8 +2537,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
}
@@ -2542,7 +2546,7 @@ func (b *blockManager) SyncPeer() *peer {
// RequestFromPeer allows an outside caller to request blocks or transactions
// from a peer. The requests are logged in the blockmanager's internal map of
// requests so they do not later ban the peer for sending the respective data.
-func (b *blockManager) RequestFromPeer(p *peer, blocks, txs []*chainhash.Hash) error {
+func (b *blockManager) RequestFromPeer(p *serverPeer, blocks, txs []*chainhash.Hash) error {
reply := make(chan requestFromPeerResponse)
b.msgChan <- requestFromPeerMsg{peer: p, blocks: blocks, txs: txs,
reply: reply}
@@ -2551,7 +2555,7 @@ func (b *blockManager) RequestFromPeer(p *peer, blocks, txs []*chainhash.Hash) e
return response.err
}
-func (b *blockManager) requestFromPeer(p *peer, blocks, txs []*chainhash.Hash) error {
+func (b *blockManager) requestFromPeer(p *serverPeer, blocks, txs []*chainhash.Hash) error {
msgResp := wire.NewMsgGetData()
// Add the blocks to the request.
diff --git a/config.go b/config.go
index 2c4937d4..0289bc47 100644
--- a/config.go
+++ b/config.go
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2014 The btcsuite developers
-// Copyright (c) 2015 The Decred developers
+// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@@ -66,6 +66,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 dcrd.
//
// See loadConfig for details on the configuration load process.
diff --git a/docs/README.md b/docs/README.md
index 3b5a2589..550da444 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,198 +1,202 @@
-### Table of Contents
-1. [About](#About)
-2. [Getting Started](#GettingStarted)
- 1. [Installation](#Installation)
- 1. [Windows](#WindowsInstallation)
- 2. [Linux/BSD/MacOSX/POSIX](#PosixInstallation)
- 2. [Configuration](#Configuration)
- 3. [Controlling and Querying dcrd via dcrctl](#DcrctlConfig)
- 4. [Mining](#Mining)
-3. [Help](#Help)
- 1. [Startup](#Startup)
- 1. [Using bootstrap.dat](#BootstrapDat)
- 2. [Network Configuration](#NetworkConfig)
- 3. [Wallet](#Wallet)
-4. [Contact](#Contact)
- 1. [IRC](#ContactIRC)
- 2. [Mailing Lists](#MailingLists)
-5. [Developer Resources](#DeveloperResources)
- 1. [Code Contribution Guidelines](#ContributionGuidelines)
- 2. [JSON-RPC Reference](#JSONRPCReference)
- 3. [The Decred-related Go Packages](#GoPackages)
-
-
-### 1. About
-dcrd is a full node decred implementation written in [Go](http://golang.org),
-licensed under the [copyfree](http://www.copyfree.org) ISC License.
-
-This project is currently under active development and is in a Beta state. It is
-extremely stable and has been in production use for over 6 months as of May
-2014, however there are still a couple of major features we want to add before
-we come out of beta.
-
-It also properly relays newly mined blocks, maintains a transaction pool, and
-relays individual transactions that have not yet made it into a block. It
-ensures all individual transactions admitted to the pool follow the rules
-required into the block chain and also includes the vast majority of the more
-strict checks which filter transactions based on miner requirements ("standard"
-transactions).
-
-
-### 2. Getting Started
-
-
-**2.1 Installation**
-
-The first step is to install dcrd. See one of the following sections for
-details on how to install on the supported operating systems.
-
-
-**2.1.1 Windows Installation**
-
-* Install the MSI available at: https://github.com/decred/dcrd/releases
-* Launch dcrd from the Start Menu
-
-
-**2.1.2 Linux/BSD/MacOSX/POSIX Installation**
-
-* Install Go according to the installation instructions here: http://golang.org/doc/install
-* Run the following command to ensure your Go version is at least version 1.2: `$ go version`
-* Run the following command to obtain dcrd, its dependencies, and install it: `$ go get github.com/decred/dcrd/...`
- * To upgrade, run the following command: `$ go get -u github.com/decred/dcrd/...`
-* Run dcrd: `$ dcrd`
-
-
-**2.2 Configuration**
-
-dcrd has a number of [configuration](http://godoc.org/github.com/decred/dcrd)
-options, which can be viewed by running: `$ dcrd --help`.
-
-
-**2.3 Controlling and Querying dcrd via dcrctl**
-
-dcrctl is a command line utility that can be used to both control and query dcrd
-via [RPC](http://www.wikipedia.org/wiki/Remote_procedure_call). dcrd does
-**not** enable its RPC server by default; You must configure at minimum both an
-RPC username and password or both an RPC limited username and password:
-
-* dcrd.conf configuration file
-```
-[Application Options]
-rpcuser=myuser
-rpcpass=SomeDecentp4ssw0rd
-rpclimituser=mylimituser
-rpclimitpass=Limitedp4ssw0rd
-```
-* dcrctl.conf configuration file
-```
-[Application Options]
-rpcuser=myuser
-rpcpass=SomeDecentp4ssw0rd
-```
-OR
-```
-[Application Options]
-rpclimituser=mylimituser
-rpclimitpass=Limitedp4ssw0rd
-```
-For a list of available options, run: `$ dcrctl --help`
-
-
-**2.4 Mining**
-dcrd supports both the `getwork` and `getblocktemplate` RPCs although the
-`getwork` RPC is deprecated and will likely be removed in a future release.
-The limited user cannot access these RPCs.
-
-**1. Add the payment addresses with the `miningaddr` option.**
-
-```
-[Application Options]
-rpcuser=myuser
-rpcpass=SomeDecentp4ssw0rd
-miningaddr=12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX
-miningaddr=1M83ju3EChKYyysmM2FXtLNftbacagd8FR
-```
-
-**2. Add dcrd's RPC TLS certificate to system Certificate Authority list.**
-
-`cgminer` uses [curl](http://curl.haxx.se/) to fetch data from the RPC server.
-Since curl validates the certificate by default, we must install the `dcrd` RPC
-certificate into the default system Certificate Authority list.
-
-**Ubuntu**
-
-1. Copy rpc.cert to /usr/share/ca-certificates: `# cp /home/user/.dcrd/rpc.cert /usr/share/ca-certificates/dcrd.crt`
-2. Add dcrd.crt to /etc/ca-certificates.conf: `# echo dcrd.crt >> /etc/ca-certificates.conf`
-3. Update the CA certificate list: `# update-ca-certificates`
-
-**3. Set your mining software url to use https.**
-
-`$ cgminer -o https://127.0.0.1:9109 -u rpcuser -p rpcpassword`
-
-
-### 3. Help
-
-
-**3.1 Startup**
-
-Typically dcrd will run and start downloading the block chain with no extra
-configuration necessary, however, there is an optional method to use a
-`bootstrap.dat` file that may speed up the initial block chain download process.
-
-
-**3.1.1 bootstrap.dat**
-* [Using bootstrap.dat](https://github.com/decred/dcrd/tree/master/docs/using_bootstrap_dat.md)
-
-
-**3.1.2 Network Configuration**
-* [What Ports Are Used by Default?](https://github.com/decred/dcrd/tree/master/docs/default_ports.md)
-* [How To Listen on Specific Interfaces](https://github.com/decred/dcrd/tree/master/docs/configure_peer_server_listen_interfaces.md)
-* [How To Configure RPC Server to Listen on Specific Interfaces](https://github.com/decred/dcrd/tree/master/docs/configure_rpc_server_listen_interfaces.md)
-* [Configuring dcrd with Tor](https://github.com/decred/dcrd/tree/master/docs/configuring_tor.md)
-
-
-**3.1 Wallet**
-
-dcrd was intentionally developed without an integrated wallet for security
-reasons. Please see [dcrwallet](https://github.com/decred/dcrwallet) for more
-information.
-
-
-### 4. Contact
-
-
-**4.1 IRC**
-* [irc.freenode.net](irc://irc.freenode.net), channel #dcrd
-
-
-**4.2 Mailing Lists**
-* dcrd: discussion
- of dcrd and its packages.
-* dcrd-commits:
- readonly mail-out of source code changes.
-
-
-### 5. Developer Resources
-
-
-* [Code Contribution Guidelines](https://github.com/decred/dcrd/tree/master/docs/code_contribution_guidelines.md)
-
-* [JSON-RPC Reference](https://github.com/decred/dcrd/tree/master/docs/json_rpc_api.md)
- * [RPC Examples](https://github.com/decred/dcrd/tree/master/docs/json_rpc_api.md#ExampleCode)
-
-* The Decred-related Go Packages:
- * [dcrrpcclient](https://github.com/decred/dcrrpcclient) - Implements a
- robust and easy to use Websocket-enabled Decred JSON-RPC client
- * [wire](https://github.com/decred/dcrd/tree/master/wire) - Implements the
- Decred wire protocol
- * [blockchain](https://github.com/decred/dcrd/tree/master/blockchain) -
- Implements Decred block handling and chain selection rules
- * [txscript](https://github.com/decred/dcrd/tree/master/txscript) -
- Implements the Decred transaction scripting language
- * [dcrec](https://github.com/decred/dcrd/tree/master/dcrec) - Implements
- support for the elliptic curve cryptographic functions needed for the
- Decred scripts
- * [database](https://github.com/decred/dcrd/tree/master/database) -
- Provides a database interface for the Decred block chain
- * [dcrutil](https://github.com/decred/dcrutil) - Provides Decred-specific
- convenience functions and types
+### Table of Contents
+1. [About](#About)
+2. [Getting Started](#GettingStarted)
+ 1. [Installation](#Installation)
+ 1. [Windows](#WindowsInstallation)
+ 2. [Linux/BSD/MacOSX/POSIX](#PosixInstallation)
+ 2. [Configuration](#Configuration)
+ 3. [Controlling and Querying dcrd via dcrctl](#DcrctlConfig)
+ 4. [Mining](#Mining)
+3. [Help](#Help)
+ 1. [Startup](#Startup)
+ 1. [Using bootstrap.dat](#BootstrapDat)
+ 2. [Network Configuration](#NetworkConfig)
+ 3. [Wallet](#Wallet)
+4. [Contact](#Contact)
+ 1. [IRC](#ContactIRC)
+ 2. [Mailing Lists](#MailingLists)
+5. [Developer Resources](#DeveloperResources)
+ 1. [Code Contribution Guidelines](#ContributionGuidelines)
+ 2. [JSON-RPC Reference](#JSONRPCReference)
+ 3. [The Decred-related Go Packages](#GoPackages)
+
+
+### 1. About
+dcrd is a full node decred implementation written in [Go](http://golang.org),
+licensed under the [copyfree](http://www.copyfree.org) ISC License.
+
+This project is currently under active development and is in a Beta state. It is
+extremely stable and has been in production use for over 6 months as of May
+2014, however there are still a couple of major features we want to add before
+we come out of beta.
+
+It also properly relays newly mined blocks, maintains a transaction pool, and
+relays individual transactions that have not yet made it into a block. It
+ensures all individual transactions admitted to the pool follow the rules
+required into the block chain and also includes the vast majority of the more
+strict checks which filter transactions based on miner requirements ("standard"
+transactions).
+
+
+### 2. Getting Started
+
+
+**2.1 Installation**
+
+The first step is to install dcrd. See one of the following sections for
+details on how to install on the supported operating systems.
+
+
+**2.1.1 Windows Installation**
+
+* Install the MSI available at: https://github.com/decred/dcrd/releases
+* Launch dcrd from the Start Menu
+
+
+**2.1.2 Linux/BSD/MacOSX/POSIX Installation**
+
+* Install Go according to the installation instructions here: http://golang.org/doc/install
+* Run the following command to ensure your Go version is at least version 1.2: `$ go version`
+* Run the following command to obtain dcrd, its dependencies, and install it: `$ go get github.com/decred/dcrd/...`
+ * To upgrade, run the following command: `$ go get -u github.com/decred/dcrd/...`
+* Run dcrd: `$ dcrd`
+
+
+**2.2 Configuration**
+
+dcrd has a number of [configuration](http://godoc.org/github.com/decred/dcrd)
+options, which can be viewed by running: `$ dcrd --help`.
+
+
+**2.3 Controlling and Querying dcrd via dcrctl**
+
+dcrctl is a command line utility that can be used to both control and query dcrd
+via [RPC](http://www.wikipedia.org/wiki/Remote_procedure_call). dcrd does
+**not** enable its RPC server by default; You must configure at minimum both an
+RPC username and password or both an RPC limited username and password:
+
+* dcrd.conf configuration file
+```
+[Application Options]
+rpcuser=myuser
+rpcpass=SomeDecentp4ssw0rd
+rpclimituser=mylimituser
+rpclimitpass=Limitedp4ssw0rd
+```
+* dcrctl.conf configuration file
+```
+[Application Options]
+rpcuser=myuser
+rpcpass=SomeDecentp4ssw0rd
+```
+OR
+```
+[Application Options]
+rpclimituser=mylimituser
+rpclimitpass=Limitedp4ssw0rd
+```
+For a list of available options, run: `$ dcrctl --help`
+
+
+**2.4 Mining**
+dcrd supports both the `getwork` and `getblocktemplate` RPCs although the
+`getwork` RPC is deprecated and will likely be removed in a future release.
+The limited user cannot access these RPCs.
+
+**1. Add the payment addresses with the `miningaddr` option.**
+
+```
+[Application Options]
+rpcuser=myuser
+rpcpass=SomeDecentp4ssw0rd
+miningaddr=12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX
+miningaddr=1M83ju3EChKYyysmM2FXtLNftbacagd8FR
+```
+
+**2. Add dcrd's RPC TLS certificate to system Certificate Authority list.**
+
+`cgminer` uses [curl](http://curl.haxx.se/) to fetch data from the RPC server.
+Since curl validates the certificate by default, we must install the `dcrd` RPC
+certificate into the default system Certificate Authority list.
+
+**Ubuntu**
+
+1. Copy rpc.cert to /usr/share/ca-certificates: `# cp /home/user/.dcrd/rpc.cert /usr/share/ca-certificates/dcrd.crt`
+2. Add dcrd.crt to /etc/ca-certificates.conf: `# echo dcrd.crt >> /etc/ca-certificates.conf`
+3. Update the CA certificate list: `# update-ca-certificates`
+
+**3. Set your mining software url to use https.**
+
+`$ cgminer -o https://127.0.0.1:9109 -u rpcuser -p rpcpassword`
+
+
+### 3. Help
+
+
+**3.1 Startup**
+
+Typically dcrd will run and start downloading the block chain with no extra
+configuration necessary, however, there is an optional method to use a
+`bootstrap.dat` file that may speed up the initial block chain download process.
+
+
+**3.1.1 bootstrap.dat**
+* [Using bootstrap.dat](https://github.com/decred/dcrd/tree/master/docs/using_bootstrap_dat.md)
+
+
+**3.1.2 Network Configuration**
+* [What Ports Are Used by Default?](https://github.com/decred/dcrd/tree/master/docs/default_ports.md)
+* [How To Listen on Specific Interfaces](https://github.com/decred/dcrd/tree/master/docs/configure_peer_server_listen_interfaces.md)
+* [How To Configure RPC Server to Listen on Specific Interfaces](https://github.com/decred/dcrd/tree/master/docs/configure_rpc_server_listen_interfaces.md)
+* [Configuring dcrd with Tor](https://github.com/decred/dcrd/tree/master/docs/configuring_tor.md)
+
+
+**3.1 Wallet**
+
+dcrd was intentionally developed without an integrated wallet for security
+reasons. Please see [dcrwallet](https://github.com/decred/dcrwallet) for more
+information.
+
+
+### 4. Contact
+
+
+**4.1 IRC**
+* [irc.freenode.net](irc://irc.freenode.net), channel #dcrd
+
+
+**4.2 Mailing Lists**
+* dcrd: discussion
+ of dcrd and its packages.
+* dcrd-commits:
+ readonly mail-out of source code changes.
+
+
+### 5. Developer Resources
+
+
+* [Code Contribution Guidelines](https://github.com/decred/dcrd/tree/master/docs/code_contribution_guidelines.md)
+
+* [JSON-RPC Reference](https://github.com/decred/dcrd/tree/master/docs/json_rpc_api.md)
+ * [RPC Examples](https://github.com/decred/dcrd/tree/master/docs/json_rpc_api.md#ExampleCode)
+
+* The Decred-related Go Packages:
+ * [dcrrpcclient](https://github.com/decred/dcrrpcclient) - Implements a
+ robust and easy to use Websocket-enabled Decred JSON-RPC client
+ * [dcrjson](https://github.com/decred/dcrjson) - Provides an extensive API
+ for the underlying JSON-RPC command and return values
+ * [wire](https://github.com/decred/dcrd/tree/master/wire) - Implements the
+ Decred wire protocol
+ * [peer](https://github.com/decred/dcrd/tree/master/peer) -
+ Provides a common base for creating and managing Decred network peers.
+ * [blockchain](https://github.com/decred/dcrd/tree/master/blockchain) -
+ Implements Decred block handling and chain selection rules
+ * [txscript](https://github.com/decred/dcrd/tree/master/txscript) -
+ Implements the Decred transaction scripting language
+ * [dcrec](https://github.com/decred/dcrd/tree/master/dcrec) - Implements
+ support for the elliptic curve cryptographic functions needed for the
+ Decred scripts
+ * [database](https://github.com/decred/dcrd/tree/master/database) -
+ Provides a database interface for the Decred block chain
+ * [dcrutil](https://github.com/decred/dcrutil) - Provides Decred-specific
+ convenience functions and types
diff --git a/log.go b/log.go
index 77487fd1..47bd7c8a 100644
--- a/log.go
+++ b/log.go
@@ -19,6 +19,7 @@ import (
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
+ "github.com/decred/dcrd/peer"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
)
@@ -123,6 +124,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 d4cd2d13..00000000
--- a/peer.go
+++ /dev/null
@@ -1,2183 +0,0 @@
-// Copyright (c) 2013-2014 The btcsuite developers
-// Copyright (c) 2015 The Decred 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"
- "errors"
- "fmt"
- "io"
- prand "math/rand"
- "net"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/btcsuite/go-socks/socks"
- "github.com/davecgh/go-spew/spew"
-
- "github.com/decred/dcrd/addrmgr"
- "github.com/decred/dcrd/blockchain"
- "github.com/decred/dcrd/chaincfg/chainhash"
- "github.com/decred/dcrd/database"
- "github.com/decred/dcrd/wire"
- "github.com/decred/dcrutil"
- "github.com/decred/dcrutil/bloom"
-)
-
-const (
- // maxProtocolVersion is the max protocol version the peer supports.
- maxProtocolVersion = 2
-
- // outputBufferSize is the number of elements the output channels use.
- outputBufferSize = 5000
-
- // 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
-
- // queueEmptyFrequency is the frequency for the emptying of the queue.
- queueEmptyFrequency = 500 * time.Millisecond
-
- // 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 decred peers.
- userAgentName = "dcrd"
-
- // userAgentVersion is the user agent version and is used to help
- // identify ourselves to other decred peers.
- userAgentVersion = fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
-
- // 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
-)
-
-// zeroHash is the zero value hash (all zeros). It is defined as a convenience.
-var zeroHash chainhash.Hash
-
-// 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 decred 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 decred peer for handling decred 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
- dcrnet wire.CurrencyNet
- 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[chainhash.Hash]struct{} // owned by blockmanager
- requestedBlocks map[chainhash.Hash]struct{} // owned by blockmanager
- retryCount int64
- prevGetBlocksBegin *chainhash.Hash // owned by blockmanager
- prevGetBlocksStop *chainhash.Hash // owned by blockmanager
- prevGetHdrsBegin *chainhash.Hash // owned by blockmanager
- prevGetHdrsStop *chainhash.Hash // owned by blockmanager
- requestQueue []*wire.InvVect
- filter *bloom.Filter
- relayMtx sync.Mutex
- disableRelayTx bool
- continueHash *chainhash.Hash
- 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 *chainhash.Hash
- 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 *chainhash.Hash) {
- 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.
- if p.server.addrManager.NeedMoreAddresses() {
- 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 decred message
-// and is used to negotiate the protocol version details as well as kick start
-// the communications.
-func (p *peer) handleVersionMsg(msg *wire.MsgVersion) error {
- // Detect self connections.
- if !allowSelfConns && msg.Nonce == p.server.nonce {
- return errors.New("disconnecting peer connected to self")
- }
-
- // Notify and disconnect clients that have a protocol version that is
- // too old.
- if msg.ProtocolVersion < int32(wire.InitialProcotolVersion) {
- // 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",
- 1)
- p.PushRejectMsg(msg.Command(), wire.RejectObsolete, reason,
- nil, true)
- return errors.New(reason)
- }
-
- // Updating a bunch of stats.
- p.StatsMtx.Lock()
-
- // Limit to one version message per peer.
- if p.versionKnown {
- 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)
-
- return errors.New("only one version message per peer is allowed")
- }
-
- // 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 {
- return err
- }
- p.na = na
-
- // Send version.
- err = p.pushVersionMsg()
- if err != nil {
- return err
- }
- }
-
- // 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.
-
- return nil
-}
-
-// 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 *chainhash.Hash, 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 *chainhash.Hash, doneChan, waitChan chan struct{}) error {
- blk, err := p.server.blockManager.GetBlockFromHash(*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 *chainhash.Hash, 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 *chainhash.Hash) error {
- // Extract the begin hash from the block locator, if one was specified,
- // to use for filtering duplicate getblocks requests.
- // request.
- var beginHash *chainhash.Hash
- 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 *chainhash.Hash) error {
- // Extract the begin hash from the block locator, if one was specified,
- // to use for filtering duplicate getheaders requests.
- var beginHash *chainhash.Hash
- 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
-}
-
-// PushGetMiningStateMsg sends a request to a peer for mining state information.
-func (p *peer) PushGetMiningStateMsg() error {
- msg := wire.NewMsgGetMiningState()
-
- p.QueueMessage(msg, nil)
- return nil
-}
-
-// PushMiningStateMsg pushes a mining state message to the queue for a requesting
-// peer.
-func (p *peer) PushMiningStateMsg(height uint32, blockHashes []*chainhash.Hash, voteHashes []*chainhash.Hash) error {
- // Nothing to send, abort.
- if len(blockHashes) == 0 {
- return nil
- }
-
- // Construct the mining state request and queue it to be sent.
- msg := wire.NewMsgMiningState()
- msg.Height = height
- for _, hash := range blockHashes {
- err := msg.AddBlockHash(hash)
- if err != nil {
- return err
- }
- }
- for _, hash := range voteHashes {
- err := msg.AddVoteHash(hash)
- if err != nil {
- return err
- }
- }
-
- p.QueueMessage(msg, nil)
-
- 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 *chainhash.Hash, wait bool) {
- // Don't bother sending the reject message if the protocol version
- // is too low.
- if p.VersionKnown() && p.ProtocolVersion() < wire.InitialProcotolVersion {
- 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 decred 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)
- }
-}
-
-// handleGetMiningStateMsg handles a request from a peer to get the current
-// mining state.
-func (p *peer) handleGetMiningStateMsg(msg *wire.MsgGetMiningState) {
- // Access the block manager and get the list of best blocks to
- // mine on.
- bm := p.server.blockManager
- mp := p.server.txMemPool
- newest, height := bm.chainState.Best()
-
- // Send out blank mining states if it's early in the blockchain.
- if height < activeNetParams.StakeValidationHeight-1 {
- err := p.PushMiningStateMsg(0, nil, nil)
- if err != nil {
- peerLog.Warnf("unexpected error while pushing data for "+
- "mining state request: %v", err.Error())
- }
-
- return
- }
-
- children, err := bm.GetGeneration(*newest)
- if err != nil {
- peerLog.Warnf("failed to access block manager to get the generation "+
- "for a mining state request (block: %v)", newest)
- return
- }
-
- // Get the list of blocks that we can actually build on top of.
- eligibleParents, err := mp.SortParentsByVotes(*newest, children)
- if err != nil {
- // We couldn't find enough voters for any block, so just return now.
- if err.(MiningRuleError).GetCode() == ErrNotEnoughVoters {
- return
- }
- peerLog.Warnf("unexpected mempool error while sorting eligible "+
- "parents for mining state request: %v", err.Error())
- return
- }
-
- // Nothing to send, abort.
- if len(eligibleParents) == 0 {
- return
- }
-
- // Construct the set of block hashes to send.
- numBlocks := len(eligibleParents)
- if numBlocks > wire.MaxMSBlocksAtHeadPerMsg {
- numBlocks = wire.MaxMSBlocksAtHeadPerMsg
- }
- blockHashes := make([]*chainhash.Hash, numBlocks, numBlocks)
- for i, h := range eligibleParents {
- if i >= wire.MaxMSBlocksAtHeadPerMsg {
- break
- }
- hP := new(chainhash.Hash)
- hP.SetBytes(h.Bytes())
- blockHashes[i] = hP
- }
-
- // Construct the set of votes to send.
- voteHashes := make([]*chainhash.Hash, 0, wire.MaxMSVotesAtHeadPerMsg)
- for _, bh := range blockHashes {
- // Fetch the vote hashes themselves and append them.
- vhsForBlock, err := mp.GetVoteHashesForBlock(*bh)
- if err != nil {
- peerLog.Warnf("unexpected error while fetching vote hashes "+
- "for block %v for a mining state request: %v",
- bh, err.Error())
- return
- }
- for _, vh := range vhsForBlock {
- vhP := new(chainhash.Hash)
- vhP.SetBytes(vh.Bytes())
- voteHashes = append(voteHashes, vhP)
- }
- }
-
- err = p.PushMiningStateMsg(uint32(height), blockHashes, voteHashes)
- if err != nil {
- peerLog.Warnf("unexpected error while pushing data for "+
- "mining state request: %v", err.Error())
- }
-
- return
-}
-
-// handleMiningStateMsg handles incoming mining state information from a mining
-// state message. The response is to request these blocks and vote transactions
-// from the peer that sent them.
-func (p *peer) handleMiningStateMsg(msg *wire.MsgMiningState) {
- err := p.server.blockManager.RequestFromPeer(p, msg.BlockHashes,
- msg.VoteHashes)
- if err != nil {
- peerLog.Warnf("couldn't handle mining state message: %v", err.Error())
- }
-
- return
-}
-
-// handleTxMsg is invoked when a peer receives a tx decred message. It blocks
-// until the decred 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 dcrutil.Tx which provides some convenience
- // methods and things such as hash caching.
- tx := dcrutil.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 decred message. It
-// blocks until the decred block has been fully processed.
-func (p *peer) handleBlockMsg(msg *wire.MsgBlock, buf []byte) {
- // Convert the raw MsgBlock to a dcrutil.Block which provides some
- // convenience methods and things such as hash caching.
- block := dcrutil.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 decred 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 decred block has been fully processed.
- p.server.blockManager.QueueBlock(block, p)
- <-p.blockProcessed
-}
-
-// handleInvMsg is invoked when a peer receives an inv decred 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 decred 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 decred 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{}, 10000)
- }
- 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 decred 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 := int64(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 += int64(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 decred
-// 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 := int64(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 += int64(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 decred
-// 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 decred
-// 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 decred
-// 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 decred 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 decred 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
- }
-
- // 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 decred 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) {
- // 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 decred 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.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 decred 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.dcrnet)
- 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 decred 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.dcrnet)
- 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.dcrnet)
- 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
- }
-}
-
-// 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 {
- // 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:
- err := p.handleVersionMsg(msg)
- if err != nil {
- peerLog.Debugf("New peer %v - error negotiating protocol: %v",
- p, err)
- p.Disconnect()
- break out
- }
-
- 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.MsgMiningState:
- p.handleMiningStateMsg(msg)
-
- case *wire.MsgGetMiningState:
- p.handleGetMiningStateMsg(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(queueEmptyFrequency)
- 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()
- 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 decred 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 decred 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,
- dcrnet: s.chainParams.Net,
- services: wire.SFNodeNetwork,
- inbound: inbound,
- knownAddresses: make(map[string]struct{}),
- knownInventory: NewMruInventoryMap(maxKnownInventory),
- requestedTxns: make(map[chainhash.Hash]struct{}),
- requestedBlocks: make(map[chainhash.Hash]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 decred 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 decred 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 := dcrdDial("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..872db32e
--- /dev/null
+++ b/peer/README.md
@@ -0,0 +1,87 @@
+peer
+====
+
+[]
+(https://travis-ci.org/btcsuite/btcd) [![ISC License]
+(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
+
+Package peer provides a common base for creating and managing bitcoin network
+peers.
+
+This package has intentionally been designed so it can be used as a standalone
+package for any projects needing a full featured bitcoin peer base to build on.
+
+## Overview
+
+This package builds upon the wire package, which provides the fundamental
+primitives necessary to speak the bitcoin wire protocol, in order to simplify
+the process of creating fully functional peers. In essence, it provides a
+common base for creating concurrent safe fully validating nodes, Simplified
+Payment Verification (SPV) nodes, proxies, etc.
+
+A quick overview of the major features peer provides are as follows:
+
+ - Provides a basic concurrent safe bitcoin peer for handling bitcoin
+ communications via the peer-to-peer protocol
+ - Full duplex reading and writing of bitcoin protocol messages
+ - Automatic handling of the initial handshake process including protocol
+ version negotiation
+ - Asynchronous message queueing of outbound messages with optional channel for
+ notification when the message is actually sent
+ - Flexible peer configuration
+ - Caller is responsible for creating outgoing connections and listening for
+ incoming connections so they have flexibility to establish connections as
+ they see fit (proxies, etc)
+ - User agent name and version
+ - Bitcoin network
+ - Service support signalling (full nodes, bloom filters, etc)
+ - Maximum supported protocol version
+ - Ability to register callbacks for handling bitcoin protocol messages
+ - Inventory message batching and send trickling with known inventory detection
+ and avoidance
+ - Automatic periodic keep-alive pinging and pong responses
+ - Random nonce generation and self connection detection
+ - Proper handling of bloom filter related commands when the caller does not
+ specify the related flag to signal support
+ - Disconnects the peer when the protocol version is high enough
+ - Does not invoke the related callbacks for older protocol versions
+ - Snapshottable peer statistics such as the total number of bytes read and
+ written, the remote address, user agent, and negotiated protocol version
+ - Helper functions pushing addresses, getblocks, getheaders, and reject
+ messages
+ - These could all be sent manually via the standard message output function,
+ but the helpers provide additional nice functionality such as duplicate
+ filtering and address randomization
+ - Ability to wait for shutdown/disconnect
+ - Comprehensive test coverage
+
+## Documentation
+
+[]
+(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 and Updating
+
+```bash
+$ go get -u github.com/btcsuite/btcd/peer
+```
+
+## Examples
+
+* [New Outbound Peer Example]
+ (https://godoc.org/github.com/btcsuite/btcd/peer#example-package--NewOutboundPeer)
+ 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 the version message is attached to the peer.
+
+## License
+
+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..47eb6ab0
--- /dev/null
+++ b/peer/doc.go
@@ -0,0 +1,154 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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 Decred network
+peers.
+
+Overview
+
+This package builds upon the wire package, which provides the fundamental
+primitives necessary to speak the decred wire protocol, in order to simplify
+the process of creating fully functional peers. In essence, it provides a
+common base for creating concurrent safe fully validating nodes, Simplified
+Payment Verification (SPV) nodes, proxies, etc.
+
+A quick overview of the major features peer provides are as follows:
+
+ - Provides a basic concurrent safe decred peer for handling decred
+ communications via the peer-to-peer protocol
+ - Full duplex reading and writing of decred protocol messages
+ - Automatic handling of the initial handshake process including protocol
+ version negotiation
+ - Asynchronous message queueing of outbound messages with optional channel for
+ notification when the message is actually sent
+ - Flexible peer configuration
+ - Caller is responsible for creating outgoing connections and listening for
+ incoming connections so they have flexibility to establish connections as
+ they see fit (proxies, etc)
+ - User agent name and version
+ - Decred network
+ - Service support signalling (full nodes, bloom filters, etc)
+ - Maximum supported protocol version
+ - Ability to register callbacks for handling decred protocol messages
+ - Inventory message batching and send trickling with known inventory detection
+ and avoidance
+ - Automatic periodic keep-alive pinging and pong responses
+ - Random nonce generation and self connection detection
+ - Proper handling of bloom filter related commands when the caller does not
+ specify the related flag to signal support
+ - Disconnects the peer when the protocol version is high enough
+ - Does not invoke the related callbacks for older protocol versions
+ - Snapshottable peer statistics such as the total number of bytes read and
+ written, the remote address, user agent, and negotiated protocol version
+ - Helper functions pushing addresses, getblocks, getheaders, and reject
+ messages
+ - These could all be sent manually via the standard message output function,
+ but the helpers provide additional nice functionality such as duplicate
+ filtering and address randomization
+ - Ability to wait for shutdown/disconnect
+ - Comprehensive test coverage
+
+Peer Configuration
+
+All peer configuration is handled with the Config struct. This allows the
+caller to specify things such as the user agent name and version, the decred
+network to use, which services it supports, and callbacks to invoke when decred
+messages are received. See the documentation for each field of the Config
+struct for more details.
+
+Inbound and Outbound Peers
+
+A peer can either be inbound or outbound. The caller is responsible for
+establishing the connection to remote peers and listening for incoming peers.
+This provides high flexibility for things such as connecting via proxies, acting
+as a proxy, creating bridge peers, choosing whether to listen for inbound peers,
+etc.
+
+For outgoing peers, the NewOutboundPeer function must be used to specify the
+configuration followed by invoking Connect with the net.Conn instance. This
+ will start all async I/O goroutines and initiate the initial negotiation
+process. Once that has been completed, the peer is fully functional.
+
+For inbound peers, the NewInboundPeer function must be used to specify the
+configuration and net.Conn instance followed by invoking Start. This will start
+all async I/O goroutines and listen for the initial negotiation process. Once
+that has been completed, the peer is fully functional.
+
+Callbacks
+
+In order to do anything useful with a peer, it is necessary to react to decred
+messages. This is accomplished by creating an instance of the MessageListeners
+struct with the callbacks to be invoke specified and setting the Listeners field
+of the Config struct specified when creating a peer to it.
+
+For convenience, a callback hook for all of the currently supported decred
+messages is exposed which receives the peer instance and the concrete message
+type. In addition, a hook for OnRead is provided so even custom messages types
+for which this package does not directly provide a hook, as long as they
+implement the wire.Message interface, can be used. Finally, the OnWrite hook
+is provided, which in conjunction with OnRead, can be used to track server-wide
+byte counts.
+
+It is often useful to use closures which encapsulate state when specifying the
+callback handlers. This provides a clean method for accessing that state when
+callbacks are invoked.
+
+Queuing Messages and Inventory
+
+The QueueMessage function provides the fundamental means to send messages to the
+remote peer. As the name implies, this employs a non-blocking queue. A done
+channel which will be notified when the message is actually sent can optionally
+be specified. There are certain message types which are better sent using other
+functions which provide additional functionality.
+
+Of special interest are inventory messages. Rather than manually sending MsgInv
+messages via Queuemessage, the inventory vectors should be queued using the
+QueueInventory function. It employs batching and trickling along with
+intelligent known remote peer inventory detection and avoidance through the use
+of a most-recently used algorithm.
+
+Message Sending Helper Functions
+
+In addition to the bare QueueMessage function previously described, the
+PushAddrMsg, PushGetBlocksMsg, PushGetHeadersMsg, and PushRejectMsg functions
+are provided as a convenience. While it is of course possible to create and
+send these message manually via QueueMessage, these helper functions provided
+additional useful functionality that is typically desired.
+
+For example, the PushAddrMsg function automatically limits the addresses to the
+maximum number allowed by the message and randomizes the chosen addresses when
+there are too many. This allows the caller to simply provide a slice of known
+addresses, such as that returned by the addrmgr package, without having to worry
+about the details.
+
+Next, the PushGetBlocksMsg and PushGetHeadersMsg functions will construct proper
+messages using a block locator and ignore back to back duplicate requests.
+
+Finally, the PushRejectMsg function can be used to easily create and send an
+appropriate reject message based on the provided parameters as well as
+optionally provides a flag to cause it to block until the message is actually
+sent.
+
+Peer Statistics
+
+A snapshot of the current peer statistics can be obtained with the StatsSnapshot
+function. This includes statistics such as the total number of bytes read and
+written, the remote address, user agent, and negotiated protocol version.
+
+Logging
+
+This package provides extensive logging capabilities through the UseLogger
+function which allows a btclog.Logger to be specified. For example, logging at
+the debug level provides summaries of every message sent and received, and
+logging at the trace level provides full dumps of parsed messages as well as the
+raw message bytes using a format similar to hexdump -C.
+
+Improvement Proposals
+
+This package supports all improvement proposals supported by the wire packge.
+(https://godoc.org/github.com/decred/dcrd/wire#hdr-Bitcoin_Improvement_Proposals)
+*/
+package peer
diff --git a/peer/example_test.go b/peer/example_test.go
new file mode 100644
index 00000000..470a589f
--- /dev/null
+++ b/peer/example_test.go
@@ -0,0 +1,114 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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/decred/dcrd/chaincfg"
+ "github.com/decred/dcrd/peer"
+ "github.com/decred/dcrd/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..fbff9520
--- /dev/null
+++ b/peer/export_test.go
@@ -0,0 +1,19 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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..b20f3071
--- /dev/null
+++ b/peer/log.go
@@ -0,0 +1,243 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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/btclog"
+ "github.com/decred/dcrd/chaincfg/chainhash"
+ "github.com/decred/dcrd/txscript"
+ "github.com/decred/dcrd/wire"
+)
+
+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 []*chainhash.Hash, stopHash *chainhash.Hash) 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..f418effd
--- /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/decred/dcrd/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 3e734d55..ab9525e5 100644
--- a/mruinvmap.go
+++ b/peer/mruinvmap.go
@@ -3,26 +3,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/decred/dcrd/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("[")
@@ -39,7 +47,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
}
@@ -49,7 +62,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 {
@@ -88,7 +106,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 3fcd87e8..9ca49bd9 100644
--- a/mruinvmap_test.go
+++ b/peer/mruinvmap_test.go
@@ -3,7 +3,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"
@@ -53,7 +53,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])
@@ -63,7 +63,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..6a0eb82e
--- /dev/null
+++ b/peer/mrunoncemap.go
@@ -0,0 +1,130 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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..828462a2
--- /dev/null
+++ b/peer/mrunoncemap_test.go
@@ -0,0 +1,153 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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..a0d9d108
--- /dev/null
+++ b/peer/peer.go
@@ -0,0 +1,2044 @@
+// Copyright (c) 2013-2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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/go-socks/socks"
+ "github.com/davecgh/go-spew/spew"
+ "github.com/decred/dcrd/blockchain"
+ "github.com/decred/dcrd/chaincfg"
+ "github.com/decred/dcrd/chaincfg/chainhash"
+ "github.com/decred/dcrd/wire"
+)
+
+const (
+ // MaxProtocolVersion is the max protocol version the peer supports.
+ MaxProtocolVersion = 2
+
+ // outputBufferSize is the number of elements the output channels use.
+ outputBufferSize = 5000
+
+ // 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
+
+ // pingInterval is the interval of time to wait in between sending ping
+ // messages.
+ pingInterval = 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
+
+ // stallTickInterval is the interval of time between each check for
+ // stalled peers.
+ stallTickInterval = 15 * time.Second
+
+ // stallResponseTimeout is the base maximum amount of time messages that
+ // expect a response will wait before disconnecting the peer for
+ // stalling. The deadlines are adjusted for callback running times and
+ // only checked on each stall tick interval.
+ stallResponseTimeout = 30 * time.Second
+
+ // trickleTimeout is the duration of the ticker which trickles down the
+ // inventory to a peer.
+ trickleTimeout = 500 * time.Millisecond
+)
+
+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 chainhash.Hash
+
+ // 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 execution 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.
+type MessageListeners struct {
+ // OnGetAddr is invoked when a peer receives a getaddr wire message.
+ OnGetAddr func(p *Peer, msg *wire.MsgGetAddr)
+
+ // OnAddr is invoked when a peer receives an addr wire message.
+ OnAddr func(p *Peer, msg *wire.MsgAddr)
+
+ // OnPing is invoked when a peer receives a ping wire message.
+ OnPing func(p *Peer, msg *wire.MsgPing)
+
+ // OnPong is invoked when a peer receives a pong wire message.
+ OnPong func(p *Peer, msg *wire.MsgPong)
+
+ // OnAlert is invoked when a peer receives an alert wire message.
+ OnAlert func(p *Peer, msg *wire.MsgAlert)
+
+ // OnMemPool is invoked when a peer receives a mempool wire message.
+ OnMemPool func(p *Peer, msg *wire.MsgMemPool)
+
+ // OnGetMiningState is invoked when a peer receives a getminings wire
+ // message.
+ OnGetMiningState func(p *Peer, msg *wire.MsgGetMiningState)
+
+ // OnMiningState is invoked when a peer receives a miningstate wire
+ // message.
+ OnMiningState func(p *Peer, msg *wire.MsgMiningState)
+
+ // OnTx is invoked when a peer receives a tx wire message.
+ OnTx func(p *Peer, msg *wire.MsgTx)
+
+ // OnBlock is invoked when a peer receives a block wire message.
+ OnBlock func(p *Peer, msg *wire.MsgBlock, buf []byte)
+
+ // OnInv is invoked when a peer receives an inv wire message.
+ OnInv func(p *Peer, msg *wire.MsgInv)
+
+ // OnHeaders is invoked when a peer receives a headers wire message.
+ OnHeaders func(p *Peer, msg *wire.MsgHeaders)
+
+ // OnNotFound is invoked when a peer receives a notfound wire message.
+ OnNotFound func(p *Peer, msg *wire.MsgNotFound)
+
+ // OnGetData is invoked when a peer receives a getdata wire message.
+ OnGetData func(p *Peer, msg *wire.MsgGetData)
+
+ // OnGetBlocks is invoked when a peer receives a getblocks wire message.
+ OnGetBlocks func(p *Peer, msg *wire.MsgGetBlocks)
+
+ // OnGetHeaders is invoked when a peer receives a getheaders wire
+ // message.
+ OnGetHeaders func(p *Peer, msg *wire.MsgGetHeaders)
+
+ // OnFilterAdd is invoked when a peer receives a filteradd wire 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 wire
+ // 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 wire
+ // 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 wire
+ // message.
+ OnMerkleBlock func(p *Peer, msg *wire.MsgMerkleBlock)
+
+ // OnVersion is invoked when a peer receives a version wire message.
+ OnVersion func(p *Peer, msg *wire.MsgVersion)
+
+ // OnVerAck is invoked when a peer receives a verack wire message.
+ OnVerAck func(p *Peer, msg *wire.MsgVerAck)
+
+ // OnReject is invoked when a peer receives a reject wire message.
+ OnReject func(p *Peer, msg *wire.MsgReject)
+
+ // OnRead is invoked when a peer receives a wire 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 wire 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, however it is good practice for
+ // peers to specify this so their currently best known is accurately
+ // reported.
+ 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 indicates a proxy is being used for connections. The only
+ // effect this has is to prevent leaking the tor proxy address, so it
+ // only needs to specified if using a tor proxy.
+ 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 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{}
+}
+
+// stallControlCmd represents the command of a stall control message.
+type stallControlCmd uint8
+
+// Constants for the command of a stall control message.
+const (
+ // sccSendMessage indicates a message is being sent to the remote peer.
+ sccSendMessage stallControlCmd = iota
+
+ // sccReceiveMessage indicates a message has been received from the
+ // remote peer.
+ sccReceiveMessage
+
+ // sccHandlerStart indicates a callback handler is about to be invoked.
+ sccHandlerStart
+
+ // sccHandlerStart indicates a callback handler has completed.
+ sccHandlerDone
+)
+
+// stallControlMsg is used to signal the stall handler about specific events
+// so it can properly detect and handle stalled remote peers.
+type stallControlMsg struct {
+ command stallControlCmd
+ message wire.Message
+}
+
+// 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 *chainhash.Hash
+ 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 *chainhash.Hash, height int64, 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 decred peer for handling decred
+// 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 decred 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 *chainhash.Hash
+ prevGetBlocksStop *chainhash.Hash
+ prevGetHdrsMtx sync.Mutex
+ prevGetHdrsBegin *chainhash.Hash
+ prevGetHdrsStop *chainhash.Hash
+
+ stallControl chan stallControlMsg
+ outputQueue chan outMsg
+ sendQueue chan outMsg
+ sendDoneQueue chan struct{}
+ outputInvChan chan *wire.InvVect
+ inQuit chan struct{}
+ queueQuit chan struct{}
+ outQuit 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 *chainhash.Hash) {
+ 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() *chainhash.Hash {
+ 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 int64
+ 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 *chainhash.Hash) error {
+ // Extract the begin hash from the block locator, if one was specified,
+ // to use for filtering duplicate getblocks requests.
+ var beginHash *chainhash.Hash
+ 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 *chainhash.Hash) error {
+ // Extract the begin hash from the block locator, if one was specified,
+ // to use for filtering duplicate getheaders requests.
+ var beginHash *chainhash.Hash
+ 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 *chainhash.Hash, wait bool) {
+ 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 wire 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.InitialProcotolVersion) {
+ // 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.InitialProcotolVersion)
+ 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 wire 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) {
+ // 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 wire 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.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 wire 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 wire 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
+ }
+}
+
+// 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
+}
+
+// maybeAddDeadline potentially adds a deadline for the appropriate expected
+// response for the passed wire protocol command to the pending responses map.
+func (p *Peer) maybeAddDeadline(pendingResponses map[string]time.Time, msgCmd string) {
+ // Setup a deadline for each message being sent that expects a response.
+ deadline := time.Now().Add(stallResponseTimeout)
+ switch msgCmd {
+ case wire.CmdVersion:
+ // Expects a verack message.
+ pendingResponses[wire.CmdVerAck] = deadline
+
+ case wire.CmdGetAddr:
+ // Expects an addr message.
+ pendingResponses[wire.CmdAddr] = deadline
+
+ case wire.CmdPing:
+ // Expects a pong message.
+ pendingResponses[wire.CmdPong] = deadline
+
+ case wire.CmdMemPool:
+ // Expects an inv message.
+ pendingResponses[wire.CmdInv] = deadline
+
+ case wire.CmdGetBlocks:
+ // Expects an inv message.
+ pendingResponses[wire.CmdInv] = deadline
+
+ case wire.CmdGetData:
+ // Expects a block, tx, or notfound message.
+ pendingResponses[wire.CmdBlock] = deadline
+ pendingResponses[wire.CmdTx] = deadline
+ pendingResponses[wire.CmdNotFound] = deadline
+
+ case wire.CmdGetHeaders:
+ // Expects a headers message. Use a longer deadline since it
+ // can take a while for the remote peer to load all of the
+ // headers.
+ deadline = time.Now().Add(stallResponseTimeout * 3)
+ pendingResponses[wire.CmdHeaders] = deadline
+ }
+}
+
+// stallHandler handles stall detection for the peer. This entails keeping
+// track of expected responses and assigning them deadlines while accounting for
+// the time spent in callbacks. It must be run as a goroutine.
+func (p *Peer) stallHandler() {
+ // These variables are used to adjust the deadline times forward by the
+ // time it takes callbacks to execute. This is done because new
+ // messages aren't read until the previous one is finished processing
+ // (which includes callbacks), so the deadline for receiving a response
+ // for a given message must account for the processing time as well.
+ var handlerActive bool
+ var handlersStartTime time.Time
+ var deadlineOffset time.Duration
+
+ // pendingResponses tracks the expected response deadline times.
+ pendingResponses := make(map[string]time.Time)
+
+ // stallTicker is used to periodically check pending responses that have
+ // exceeded the expected deadline and disconnect the peer due to
+ // stalling.
+ stallTicker := time.NewTicker(stallTickInterval)
+ defer stallTicker.Stop()
+
+ // ioStopped is used to detect when both the input and output handler
+ // goroutines are done.
+ var ioStopped bool
+out:
+ for {
+ select {
+ case msg := <-p.stallControl:
+ switch msg.command {
+ case sccSendMessage:
+ // Add a deadline for the expected response
+ // message if needed.
+ p.maybeAddDeadline(pendingResponses,
+ msg.message.Command())
+
+ case sccReceiveMessage:
+ // Remove received messages from the expected
+ // reponse map. Since certain commands expect
+ // one of a group of responses, remove everyting
+ // in the expected group accordingly.
+ switch msgCmd := msg.message.Command(); msgCmd {
+ case wire.CmdBlock:
+ fallthrough
+ case wire.CmdTx:
+ fallthrough
+ case wire.CmdNotFound:
+ delete(pendingResponses, wire.CmdBlock)
+ delete(pendingResponses, wire.CmdTx)
+ delete(pendingResponses, wire.CmdNotFound)
+
+ default:
+ delete(pendingResponses, msgCmd)
+ }
+
+ case sccHandlerStart:
+ // Warn on unbalanced callback signalling.
+ if handlerActive {
+ log.Warn("Received handler start " +
+ "control command while a " +
+ "handler is already active")
+ continue
+ }
+
+ handlerActive = true
+ handlersStartTime = time.Now()
+
+ case sccHandlerDone:
+ // Warn on unbalanced callback signalling.
+ if !handlerActive {
+ log.Warn("Received handler done " +
+ "control command when a " +
+ "handler is not already active")
+ continue
+ }
+
+ // Extend active deadlines by the time it took
+ // to execute the callback.
+ duration := time.Now().Sub(handlersStartTime)
+ deadlineOffset += duration
+ handlerActive = false
+
+ default:
+ log.Warnf("Unsupported message command %v",
+ msg.command)
+ }
+
+ case <-stallTicker.C:
+ // Calculate the offset to apply to the deadline based
+ // on how long the handlers have taken to execute since
+ // the last tick.
+ now := time.Now()
+ offset := deadlineOffset
+ if handlerActive {
+ offset += now.Sub(handlersStartTime)
+ }
+
+ // Disconnect the peer if any of the pending responses
+ // don't arrive by their adjusted deadline.
+ for command, deadline := range pendingResponses {
+ if now.Before(deadline.Add(offset)) {
+ continue
+ }
+
+ log.Debugf("Peer %s appears to be stalled or "+
+ "misbehaving, %s timeout -- "+
+ "disconnecting", p, command)
+ p.Disconnect()
+ break
+ }
+
+ // Reset the deadline offset for the next tick.
+ deadlineOffset = 0
+
+ case <-p.inQuit:
+ // The stall handler can exit once both the input and
+ // output handler goroutines are done.
+ if ioStopped {
+ break out
+ }
+ ioStopped = true
+
+ case <-p.outQuit:
+ // The stall handler can exit once both the input and
+ // output handler goroutines are done.
+ if ioStopped {
+ break out
+ }
+ ioStopped = true
+ }
+ }
+
+ // Drain any wait channels before going away so there is nothing left
+ // waiting on this goroutine.
+cleanup:
+ for {
+ select {
+ case <-p.stallControl:
+ default:
+ break cleanup
+ }
+ }
+ log.Tracef("Peer stall handler done for %s", p)
+}
+
+// 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 {
+ // 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()
+ p.stallControl <- stallControlMsg{sccReceiveMessage, rmsg}
+
+ // 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.
+ p.stallControl <- stallControlMsg{sccHandlerStart, rmsg}
+ 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.MsgGetMiningState:
+ if p.cfg.Listeners.OnGetMiningState != nil {
+ p.cfg.Listeners.OnGetMiningState(p, msg)
+ }
+
+ case *wire.MsgMiningState:
+ if p.cfg.Listeners.OnMiningState != nil {
+ p.cfg.Listeners.OnMiningState(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.cfg.Listeners.OnFilterAdd != nil &&
+ p.isValidBIP0111(msg.Command()) {
+
+ p.cfg.Listeners.OnFilterAdd(p, msg)
+ }
+
+ case *wire.MsgFilterClear:
+ if p.cfg.Listeners.OnFilterClear != nil &&
+ p.isValidBIP0111(msg.Command()) {
+
+ p.cfg.Listeners.OnFilterClear(p, msg)
+ }
+
+ case *wire.MsgFilterLoad:
+ if p.cfg.Listeners.OnFilterLoad != nil &&
+ p.isValidBIP0111(msg.Command()) {
+
+ 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())
+ }
+ p.stallControl <- stallControlMsg{sccHandlerDone, rmsg}
+
+ // 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()
+
+ close(p.inQuit)
+ 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() {
+ // pingTicker is used to periodically send pings to the remote peer.
+ pingTicker := time.NewTicker(pingInterval)
+ defer pingTicker.Stop()
+
+out:
+ for {
+ select {
+ case msg := <-p.sendQueue:
+ switch m := msg.msg.(type) {
+ case *wire.MsgVersion:
+ // Set the flag which indicates the version has
+ // been sent.
+ p.flagsMtx.Lock()
+ p.versionSent = true
+ p.flagsMtx.Unlock()
+
+ case *wire.MsgPing:
+ // Setup ping statistics.
+ p.statsMtx.Lock()
+ p.lastPingNonce = m.Nonce
+ p.lastPingTime = time.Now()
+ p.statsMtx.Unlock()
+ }
+
+ p.stallControl <- stallControlMsg{sccSendMessage, msg.msg}
+ 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 <-pingTicker.C:
+ nonce, err := wire.RandomUint64()
+ if err != nil {
+ log.Errorf("Not sending ping to %s: %v", p, err)
+ continue
+ }
+ p.QueueMessage(wire.NewMsgPing(nonce), nil)
+
+ case <-p.quit:
+ break out
+ }
+ }
+
+ <-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
+ }
+ }
+ close(p.outQuit)
+ log.Tracef("Peer output handler done for %s", p)
+}
+
+// QueueMessage adds the passed wire 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.stallHandler()
+ 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 decred 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.TestNetParams
+ }
+
+ p := Peer{
+ inbound: inbound,
+ knownInventory: NewMruInventoryMap(maxKnownInventory),
+ stallControl: make(chan stallControlMsg, 1), // nonblocking sync
+ 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),
+ inQuit: make(chan struct{}),
+ queueQuit: make(chan struct{}),
+ outQuit: 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 decred 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 decred 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..b1537a69
--- /dev/null
+++ b/peer/peer_test.go
@@ -0,0 +1,666 @@
+// Copyright (c) 2015 The btcsuite developers
+// Copyright (c) 2016 The Decred 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/go-socks/socks"
+ "github.com/decred/dcrd/chaincfg"
+ "github.com/decred/dcrd/chaincfg/chainhash"
+ "github.com/decred/dcrd/peer"
+ "github.com/decred/dcrd/wire"
+)
+
+// 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(0, &chainhash.Hash{},
+ &chainhash.Hash{}, &chainhash.Hash{}, 1, [6]byte{},
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, [36]byte{})),
+ },
+ {
+ "OnInv",
+ wire.NewMsgInv(),
+ },
+ {
+ "OnHeaders",
+ wire.NewMsgHeaders(),
+ },
+ {
+ "OnNotFound",
+ wire.NewMsgNotFound(),
+ },
+ {
+ "OnGetData",
+ wire.NewMsgGetData(),
+ },
+ {
+ "OnGetBlocks",
+ wire.NewMsgGetBlocks(&chainhash.Hash{}),
+ },
+ {
+ "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(0,
+ &chainhash.Hash{}, &chainhash.Hash{},
+ &chainhash.Hash{}, 1, [6]byte{}, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, [36]byte{})),
+ },
+ // 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() (*chainhash.Hash, int64, 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 := &chainhash.Hash{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() (*chainhash.Hash, int64, error) {
+ hashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef"
+ hash, err := chainhash.NewHashFromStr(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 := chainhash.NewHashFromStr("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 testnet
+ peerCfg.ChainParams = &chaincfg.TestNetParams
+ 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, &chainhash.Hash{}); err != nil {
+ t.Errorf("PushGetBlocksMsg: unexpected err %v\n", err)
+ return
+ }
+ if err := p2.PushGetHeadersMsg(nil, &chainhash.Hash{}); 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 ad30012e..3c1d54e8 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -93,6 +93,9 @@ const (
// sstxCommitmentString is the string to insert when a verbose
// transaction output's pkscript type is a ticket commitment.
sstxCommitmentString = "sstxcommitment"
+
+ // maxProtocolVersion is the max protocol version the server supports.
+ maxProtocolVersion = 2
)
var (
@@ -465,7 +468,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, &dcrjson.RPCError{
Code: dcrjson.ErrRPCMisc,
Message: "can't disconnect a permanent peer, use remove",
@@ -488,7 +491,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, &dcrjson.RPCError{
Code: dcrjson.ErrRPCMisc,
Message: "can't remove a temporary peer, use disconnect",
@@ -533,9 +536,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 []*dcrjson.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
}
}
@@ -2013,7 +2016,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
}
@@ -2031,7 +2034,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
}
@@ -2043,15 +2046,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 dcrjson.GetAddedNodeInfoResult
- result.AddedNode = peer.addr
+ result.AddedNode = peer.Addr()
result.Connected = dcrjson.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
@@ -2075,7 +2078,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)
}
@@ -3703,7 +3706,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([]*dcrjson.GetPeerInfoResult, 0, len(peers))
+ for _, p := range peers {
+ statsSnap := p.StatsSnapshot()
+ info := &dcrjson.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 d5a385c6..fd525dc9 100644
--- a/server.go
+++ b/server.go
@@ -1,5 +1,5 @@
-// Copyright (c) 2013-2014 The btcsuite developers
-// Copyright (c) 2015 The Decred developers
+// Copyright (c) 2013-2015 The btcsuite developers
+// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@@ -26,10 +26,11 @@ import (
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
- "github.com/decred/dcrd/dcrjson"
+ "github.com/decred/dcrd/peer"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrutil"
+ "github.com/decred/dcrutil/bloom"
)
const (
@@ -46,13 +47,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 decred peers.
+ userAgentName = "dcrd"
+
+ // userAgentVersion is the user agent version and is used to help
+ // identify ourselves to other peers.
+ userAgentVersion = fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch)
)
// broadcastMsg provides the ability to house a decred 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
@@ -79,13 +101,73 @@ type relayMsg struct {
type updatePeerHeightsMsg struct {
newSha *chainhash.Hash
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 decred server for handling communications to and from
// decred peers.
type server struct {
- nonce uint64
listeners []net.Listener
chainParams *chaincfg.Params
started int32 // atomic
@@ -102,9 +184,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
@@ -119,13 +203,741 @@ 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 *chainhash.Hash
+ relayMtx sync.Mutex
+ disableRelayTx bool
+ requestQueue []*wire.InvVect
+ requestedTxns map[chainhash.Hash]struct{}
+ requestedBlocks map[chainhash.Hash]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[chainhash.Hash]struct{}),
+ requestedBlocks: make(map[chainhash.Hash]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 wire 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.
+ if addrManager.NeedMoreAddresses() {
+ 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 wire 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)
+ }
+}
+
+// pushMiningStateMsg pushes a mining state message to the queue for a
+// requesting peer.
+func (sp *serverPeer) pushMiningStateMsg(height uint32, blockHashes []*chainhash.Hash, voteHashes []*chainhash.Hash) error {
+ // Nothing to send, abort.
+ if len(blockHashes) == 0 {
+ return nil
+ }
+
+ // Construct the mining state request and queue it to be sent.
+ msg := wire.NewMsgMiningState()
+ msg.Height = height
+ for _, hash := range blockHashes {
+ err := msg.AddBlockHash(hash)
+ if err != nil {
+ return err
+ }
+ }
+ for _, hash := range voteHashes {
+ err := msg.AddVoteHash(hash)
+ if err != nil {
+ return err
+ }
+ }
+
+ sp.QueueMessage(msg, nil)
+
+ return nil
+}
+
+// OnGetMiningState is invoked when a peer receives a getminings wire message.
+// It constructs a list of the current best blocks and votes that should be
+// mined on and pushes a miningstate wire message back to the requesting peer.
+func (sp *serverPeer) OnGetMiningState(p *peer.Peer, msg *wire.MsgGetMiningState) {
+ // Access the block manager and get the list of best blocks to mine on.
+ bm := sp.server.blockManager
+ mp := sp.server.txMemPool
+ newest, height := bm.chainState.Best()
+
+ // Send out blank mining states if it's early in the blockchain.
+ if height < activeNetParams.StakeValidationHeight-1 {
+ err := sp.pushMiningStateMsg(0, nil, nil)
+ if err != nil {
+ peerLog.Warnf("unexpected error while pushing data for "+
+ "mining state request: %v", err.Error())
+ }
+
+ return
+ }
+
+ children, err := bm.GetGeneration(*newest)
+ if err != nil {
+ peerLog.Warnf("failed to access block manager to get the generation "+
+ "for a mining state request (block: %v)", newest)
+ return
+ }
+
+ // Get the list of blocks that we can actually build on top of.
+ eligibleParents, err := mp.SortParentsByVotes(*newest, children)
+ if err != nil {
+ // We couldn't find enough voters for any block, so just return now.
+ if err.(MiningRuleError).GetCode() == ErrNotEnoughVoters {
+ return
+ }
+ peerLog.Warnf("unexpected mempool error while sorting eligible "+
+ "parents for mining state request: %v", err.Error())
+ return
+ }
+
+ // Nothing to send, abort.
+ if len(eligibleParents) == 0 {
+ return
+ }
+
+ // Construct the set of block hashes to send.
+ numBlocks := len(eligibleParents)
+ if numBlocks > wire.MaxMSBlocksAtHeadPerMsg {
+ numBlocks = wire.MaxMSBlocksAtHeadPerMsg
+ }
+ blockHashes := make([]*chainhash.Hash, numBlocks, numBlocks)
+ for i, h := range eligibleParents {
+ if i >= wire.MaxMSBlocksAtHeadPerMsg {
+ break
+ }
+ hP := new(chainhash.Hash)
+ hP.SetBytes(h.Bytes())
+ blockHashes[i] = hP
+ }
+
+ // Construct the set of votes to send.
+ voteHashes := make([]*chainhash.Hash, 0, wire.MaxMSVotesAtHeadPerMsg)
+ for _, bh := range blockHashes {
+ // Fetch the vote hashes themselves and append them.
+ vhsForBlock, err := mp.GetVoteHashesForBlock(*bh)
+ if err != nil {
+ peerLog.Warnf("unexpected error while fetching vote hashes "+
+ "for block %v for a mining state request: %v",
+ bh, err.Error())
+ return
+ }
+ for _, vh := range vhsForBlock {
+ vhP := new(chainhash.Hash)
+ vhP.SetBytes(vh.Bytes())
+ voteHashes = append(voteHashes, vhP)
+ }
+ }
+
+ err = sp.pushMiningStateMsg(uint32(height), blockHashes, voteHashes)
+ if err != nil {
+ peerLog.Warnf("unexpected error while pushing data for "+
+ "mining state request: %v", err.Error())
+ }
+}
+
+// OnMiningState is invoked when a peer receives a miningstate wire message. It
+// requests the data advertised in the message from the peer.
+func (sp *serverPeer) OnMiningState(p *peer.Peer, msg *wire.MsgMiningState) {
+ err := sp.server.blockManager.RequestFromPeer(sp, msg.BlockHashes,
+ msg.VoteHashes)
+ if err != nil {
+ peerLog.Warnf("couldn't handle mining state message: %v",
+ err.Error())
+ }
+}
+
+// OnTx is invoked when a peer receives a tx wire message. It blocks until the
+// 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 dcrutil.Tx which provides some convenience
+ // methods and things such as hash caching.
+ tx := dcrutil.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 wire message. It blocks
+// until the network block has been fully processed.
+func (sp *serverPeer) OnBlock(p *peer.Peer, msg *wire.MsgBlock, buf []byte) {
+ // Convert the raw MsgBlock to a dcrutil.Block which provides some
+ // convenience methods and things such as hash caching.
+ block := dcrutil.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 network 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 network block has been
+ // fully processed.
+ sp.server.blockManager.QueueBlock(block, sp)
+ <-sp.blockProcessed
+}
+
+// OnInv is invoked when a peer receives an inv wire 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 wire 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 wire 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 wire 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 := int64(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 += int64(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 wire 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 := int64(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 += int64(len(hashList))
+ }
+ p.QueueMessage(headersMsg, nil)
+}
+
+// OnFilterAdd is invoked when a peer receives a filteradd wire 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 wire 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 wire message and it
+// is 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) {
+ // Transaction relay is no longer disabled once a filterload message is
+ // received regardless of its original state.
+ sp.setDisableRelayTx(false)
+
+ sp.filter.Reload(msg)
+}
+
+// OnGetAddr is invoked when a peer receives a getaddr wire 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 wire 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
+ }
+
+ // 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
@@ -168,11 +980,6 @@ func (s *server) RemoveRebroadcastInventory(iv *wire.InvVect) {
s.modifyRebroadcastInv <- broadcastInventoryDel(iv)
}
-// both websocket and getblocktemplate long poll clients of the passed
-func (p *peerState) Count() int {
- return len(p.peers) + len(p.outboundPeers) + len(p.persistentPeers)
-}
-
// AnnounceNewTransactions generates and relays inventory vectors and notifies
// both websocket and getblocktemplate long poll clients of the passed
// transactions. This function should be called whenever new transactions
@@ -198,93 +1005,194 @@ func (s *server) AnnounceNewTransactions(newTxs []*dcrutil.Tx) {
}
}
-func (p *peerState) OutboundCount() int {
- return 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 *chainhash.Hash, 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)
+
+ if doneChan != nil {
+ doneChan <- struct{}{}
+ }
+ return err
+ }
+
+ // Once we have fetched data wait for any previous operation to finish.
+ if waitChan != nil {
+ <-waitChan
+ }
+
+ sp.QueueMessage(tx.MsgTx(), doneChan)
+
+ return nil
}
-func (p *peerState) NeedMoreOutbound() bool {
- return p.OutboundCount() < p.maxOutboundPeers &&
- p.Count() < cfg.MaxPeers
+// 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 *chainhash.Hash, doneChan, waitChan chan struct{}) error {
+ blk, err := s.blockManager.GetBlockFromHash(*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{}
+ 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
}
-// 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)
+// 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 *chainhash.Hash, 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
}
- for e := range p.persistentPeers {
- closure(e)
- }
-}
-// 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)
+ blk, err := s.blockManager.GetBlockFromHash(*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)
+
+ // 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
}
@@ -294,28 +1202,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
@@ -323,70 +1242,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.(*dcrutil.Tx)
if !ok {
peerLog.Warnf("Underlying data for tx" +
@@ -394,7 +1326,7 @@ func (s *server) handleRelayInvMsg(state *peerState, msg relayMsg) {
return
}
- if !p.filter.MatchTxAndUpdate(tx) {
+ if !sp.filter.MatchTxAndUpdate(tx) {
return
}
}
@@ -403,26 +1335,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)
}
})
}
@@ -431,16 +1363,16 @@ type getConnCountMsg struct {
reply chan int32
}
-type getPeerInfoMsg struct {
- reply chan []*dcrjson.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
}
@@ -451,69 +1383,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([]*dcrjson.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 := &dcrjson.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 {
@@ -524,17 +1424,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 {
@@ -545,9 +1446,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:
@@ -560,18 +1461,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
@@ -589,8 +1490,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)
@@ -598,7 +1499,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
}
@@ -606,6 +1507,40 @@ 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,
+ OnGetMiningState: sp.OnGetMiningState,
+ OnMiningState: sp.OnMiningState,
+ 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) {
@@ -620,7 +1555,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())
@@ -673,6 +1611,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 := dcrdDial("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.
@@ -686,10 +1694,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),
@@ -697,7 +1707,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()
@@ -707,7 +1716,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.
@@ -746,13 +1758,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
}
@@ -768,15 +1780,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
@@ -792,6 +1804,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.
@@ -813,13 +1831,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
}
}
@@ -841,13 +1857,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 vector to all connected peers
@@ -858,7 +1874,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}
@@ -876,18 +1892,17 @@ func (s *server) ConnectedCount() int32 {
// AddedNodeInfo returns an array of dcrjson.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() []*dcrjson.GetPeerInfoResult {
- replyChan := make(chan []*dcrjson.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
}
@@ -899,7 +1914,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,
}
@@ -913,7 +1928,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,
}
@@ -926,7 +1941,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,
}
@@ -939,7 +1954,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,
}
@@ -988,7 +2003,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 *chainhash.Hash, latestHeight int32, updateSource *peer) {
+func (s *server) UpdatePeerHeights(latestBlkSha *chainhash.Hash, latestHeight int32, updateSource *serverPeer) {
s.peerHeightsUpdate <- updatePeerHeightsMsg{
newSha: latestBlkSha,
newHeight: latestHeight,
@@ -1285,16 +2300,9 @@ out:
// newServer returns a new dcrd server configured to listen on addr for the
// decred network type specified by chainParams. Use start to begin accepting
// connections from peers.
-func newServer(listenAddrs []string,
- database database.Db,
- tmdb *stake.TicketDB,
+func newServer(listenAddrs []string, database database.Db, tmdb *stake.TicketDB,
chainParams *chaincfg.Params) (*server, error) {
- nonce, err := wire.RandomUint64()
- if err != nil {
- return nil, err
- }
-
services := defaultServices
if cfg.NoPeerBloomFilters {
services &^= wire.SFNodeBloom
@@ -1427,13 +2435,13 @@ func newServer(listenAddrs []string,
}
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),
diff --git a/wire/protocol.go b/wire/protocol.go
index 6cacc7a0..4b2527c1 100644
--- a/wire/protocol.go
+++ b/wire/protocol.go
@@ -13,7 +13,7 @@ import (
const (
// InitialProcotolVersion is the initial protocol version for the
- // current network.
+ // network.
InitialProcotolVersion uint32 = 1
// ProtocolVersion is the latest protocol version this package supports.