2017-02-14 00:00:02 +01:00
|
|
|
// Copyright (c) 2013-2016 The btcsuite developers
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package spvchain
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/addrmgr"
|
2017-04-25 21:21:29 +02:00
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
2017-04-25 05:44:45 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2017-02-14 00:00:02 +01:00
|
|
|
"github.com/btcsuite/btcd/connmgr"
|
2017-04-25 05:44:45 +02:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2017-04-25 21:21:29 +02:00
|
|
|
"github.com/btcsuite/btcutil"
|
2017-04-19 01:51:56 +02:00
|
|
|
"github.com/btcsuite/btcutil/gcs"
|
2017-04-25 05:44:45 +02:00
|
|
|
"github.com/btcsuite/btcutil/gcs/builder"
|
2017-02-14 00:00:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type getConnCountMsg struct {
|
|
|
|
reply chan int32
|
|
|
|
}
|
|
|
|
|
|
|
|
type getPeersMsg struct {
|
|
|
|
reply chan []*serverPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
type getOutboundGroup struct {
|
|
|
|
key string
|
|
|
|
reply chan int
|
|
|
|
}
|
|
|
|
|
|
|
|
type getAddedNodesMsg struct {
|
|
|
|
reply chan []*serverPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
type disconnectNodeMsg struct {
|
|
|
|
cmp func(*serverPeer) bool
|
|
|
|
reply chan error
|
|
|
|
}
|
|
|
|
|
|
|
|
type connectNodeMsg struct {
|
|
|
|
addr string
|
|
|
|
permanent bool
|
|
|
|
reply chan error
|
|
|
|
}
|
|
|
|
|
|
|
|
type removeNodeMsg struct {
|
|
|
|
cmp func(*serverPeer) bool
|
|
|
|
reply chan error
|
|
|
|
}
|
|
|
|
|
2017-04-11 21:24:26 +02:00
|
|
|
type forAllPeersMsg struct {
|
|
|
|
closure func(*serverPeer)
|
|
|
|
}
|
|
|
|
|
2017-04-25 05:44:45 +02:00
|
|
|
type getCFilterMsg struct {
|
|
|
|
cfRequest
|
|
|
|
prevHeader *chainhash.Hash
|
|
|
|
curHeader *chainhash.Hash
|
|
|
|
reply chan *gcs.Filter
|
|
|
|
}
|
|
|
|
|
2017-04-25 21:21:29 +02:00
|
|
|
type getBlockMsg struct {
|
|
|
|
blockHeader *wire.BlockHeader
|
|
|
|
height uint32
|
|
|
|
reply chan *btcutil.Block
|
2017-04-19 01:51:56 +02:00
|
|
|
}
|
|
|
|
|
2017-04-25 05:44:45 +02:00
|
|
|
// TODO: General - abstract out more of blockmanager into queries. It'll make
|
|
|
|
// this way more maintainable and usable.
|
|
|
|
|
2017-02-14 00:00:02 +01:00
|
|
|
// handleQuery is the central handler for all queries and commands from other
|
|
|
|
// goroutines related to peer state.
|
|
|
|
func (s *ChainService) handleQuery(state *peerState, querymsg interface{}) {
|
|
|
|
switch msg := querymsg.(type) {
|
|
|
|
case getConnCountMsg:
|
|
|
|
nconnected := int32(0)
|
|
|
|
state.forAllPeers(func(sp *serverPeer) {
|
|
|
|
if sp.Connected() {
|
|
|
|
nconnected++
|
|
|
|
}
|
|
|
|
})
|
|
|
|
msg.reply <- nconnected
|
|
|
|
|
|
|
|
case getPeersMsg:
|
|
|
|
peers := make([]*serverPeer, 0, state.Count())
|
|
|
|
state.forAllPeers(func(sp *serverPeer) {
|
|
|
|
if !sp.Connected() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
peers = append(peers, sp)
|
|
|
|
})
|
|
|
|
msg.reply <- peers
|
|
|
|
|
|
|
|
case connectNodeMsg:
|
|
|
|
// TODO: duplicate oneshots?
|
|
|
|
// Limit max number of total peers.
|
|
|
|
if state.Count() >= MaxPeers {
|
|
|
|
msg.reply <- errors.New("max peers reached")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, peer := range state.persistentPeers {
|
|
|
|
if peer.Addr() == msg.addr {
|
|
|
|
if msg.permanent {
|
|
|
|
msg.reply <- errors.New("peer already connected")
|
|
|
|
} else {
|
|
|
|
msg.reply <- errors.New("peer exists as a permanent peer")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
netAddr, err := addrStringToNetAddr(msg.addr)
|
|
|
|
if err != nil {
|
|
|
|
msg.reply <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: if too many, nuke a non-perm peer.
|
|
|
|
go s.connManager.Connect(&connmgr.ConnReq{
|
|
|
|
Addr: netAddr,
|
|
|
|
Permanent: msg.permanent,
|
|
|
|
})
|
|
|
|
msg.reply <- nil
|
|
|
|
case removeNodeMsg:
|
|
|
|
found := disconnectPeer(state.persistentPeers, msg.cmp, func(sp *serverPeer) {
|
|
|
|
// Keep group counts ok since we remove from
|
|
|
|
// the list now.
|
|
|
|
state.outboundGroups[addrmgr.GroupKey(sp.NA())]--
|
|
|
|
})
|
|
|
|
|
|
|
|
if found {
|
|
|
|
msg.reply <- nil
|
|
|
|
} else {
|
|
|
|
msg.reply <- errors.New("peer not found")
|
|
|
|
}
|
|
|
|
case getOutboundGroup:
|
|
|
|
count, ok := state.outboundGroups[msg.key]
|
|
|
|
if ok {
|
|
|
|
msg.reply <- count
|
|
|
|
} else {
|
|
|
|
msg.reply <- 0
|
|
|
|
}
|
|
|
|
// Request a list of the persistent (added) peers.
|
|
|
|
case getAddedNodesMsg:
|
|
|
|
// Respond with a slice of the relavent peers.
|
|
|
|
peers := make([]*serverPeer, 0, len(state.persistentPeers))
|
|
|
|
for _, sp := range state.persistentPeers {
|
|
|
|
peers = append(peers, sp)
|
|
|
|
}
|
|
|
|
msg.reply <- peers
|
|
|
|
case disconnectNodeMsg:
|
|
|
|
// Check outbound peers.
|
|
|
|
found := disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) {
|
|
|
|
// Keep group counts ok since we remove from
|
|
|
|
// the list now.
|
|
|
|
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(sp *serverPeer) {
|
|
|
|
state.outboundGroups[addrmgr.GroupKey(sp.NA())]--
|
|
|
|
})
|
|
|
|
}
|
|
|
|
msg.reply <- nil
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.reply <- errors.New("peer not found")
|
2017-04-11 21:24:26 +02:00
|
|
|
case forAllPeersMsg:
|
|
|
|
// Run the closure on all peers in the passed state.
|
|
|
|
state.forAllPeers(msg.closure)
|
|
|
|
// Even though this is a query, there's no reply channel as the
|
|
|
|
// forAllPeers method doesn't return anything. An error might be
|
|
|
|
// useful in the future.
|
2017-04-25 05:44:45 +02:00
|
|
|
case getCFilterMsg:
|
|
|
|
found := false
|
|
|
|
state.queryPeers(
|
|
|
|
// Should we query this peer?
|
|
|
|
func(sp *serverPeer) bool {
|
|
|
|
// Don't send requests to disconnected peers.
|
|
|
|
return sp.Connected()
|
|
|
|
},
|
|
|
|
// Send a wire.GetCFilterMsg
|
|
|
|
wire.NewMsgGetCFilter(&msg.blockHash, msg.extended),
|
|
|
|
// Check responses and if we get one that matches,
|
|
|
|
// end the query early.
|
|
|
|
func(sp *serverPeer, resp wire.Message,
|
|
|
|
quit chan<- struct{}) {
|
|
|
|
switch response := resp.(type) {
|
|
|
|
// We're only interested in "cfilter" messages.
|
|
|
|
case *wire.MsgCFilter:
|
|
|
|
if len(response.Data) < 4 {
|
|
|
|
// Filter data is too short.
|
|
|
|
// Ignore this message.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
filter, err :=
|
|
|
|
gcs.FromNBytes(builder.DefaultP,
|
|
|
|
response.Data)
|
|
|
|
if err != nil {
|
|
|
|
// Malformed filter data. We
|
|
|
|
// can ignore this message.
|
|
|
|
return
|
|
|
|
}
|
2017-04-25 21:21:29 +02:00
|
|
|
if MakeHeaderForFilter(filter,
|
2017-04-25 05:44:45 +02:00
|
|
|
*msg.prevHeader) !=
|
|
|
|
*msg.curHeader {
|
|
|
|
// Filter data doesn't match
|
|
|
|
// the headers we know about.
|
|
|
|
// Ignore this response.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// At this point, the filter matches
|
|
|
|
// what we know about it and we declare
|
|
|
|
// it sane. We can kill the query and
|
|
|
|
// pass the response back to the caller.
|
|
|
|
found = true
|
|
|
|
close(quit)
|
|
|
|
msg.reply <- filter
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
// We timed out without finding a correct answer to our query.
|
|
|
|
if !found {
|
|
|
|
msg.reply <- nil
|
|
|
|
}
|
2017-04-25 21:21:29 +02:00
|
|
|
case getBlockMsg:
|
|
|
|
found := false
|
|
|
|
getData := wire.NewMsgGetData()
|
|
|
|
blockHash := msg.blockHeader.BlockHash()
|
|
|
|
getData.AddInvVect(wire.NewInvVect(wire.InvTypeBlock,
|
|
|
|
&blockHash))
|
|
|
|
state.queryPeers(
|
|
|
|
// Should we query this peer?
|
|
|
|
func(sp *serverPeer) bool {
|
|
|
|
// Don't send requests to disconnected peers.
|
|
|
|
return sp.Connected()
|
|
|
|
},
|
|
|
|
// Send a wire.GetCFilterMsg
|
|
|
|
getData,
|
|
|
|
// Check responses and if we get one that matches,
|
|
|
|
// end the query early.
|
|
|
|
func(sp *serverPeer, resp wire.Message,
|
|
|
|
quit chan<- struct{}) {
|
|
|
|
switch response := resp.(type) {
|
|
|
|
// We're only interested in "block" messages.
|
|
|
|
case *wire.MsgBlock:
|
|
|
|
// If this isn't our block, ignore it.
|
|
|
|
if response.BlockHash() !=
|
|
|
|
blockHash {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
block := btcutil.NewBlock(response)
|
|
|
|
// Only set height if btcutil hasn't
|
|
|
|
// automagically put one in.
|
|
|
|
if block.Height() ==
|
|
|
|
btcutil.BlockHeightUnknown {
|
|
|
|
block.SetHeight(
|
|
|
|
int32(msg.height))
|
|
|
|
}
|
|
|
|
// If this claims our block but doesn't
|
|
|
|
// pass the sanity check, the peer is
|
|
|
|
// trying to bamboozle us. Disconnect
|
|
|
|
// it.
|
|
|
|
if err := blockchain.CheckBlockSanity(
|
|
|
|
block,
|
|
|
|
// We don't need to check PoW
|
|
|
|
// because by the time we get
|
|
|
|
// here, it's been checked
|
|
|
|
// during header synchronization
|
|
|
|
s.chainParams.PowLimit,
|
|
|
|
s.timeSource,
|
|
|
|
); err != nil {
|
|
|
|
log.Warnf("Invalid block for "+
|
|
|
|
"%s received from %s "+
|
|
|
|
"-- disconnecting peer",
|
|
|
|
blockHash, sp.Addr())
|
|
|
|
sp.Disconnect()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
found = true
|
|
|
|
close(quit)
|
|
|
|
msg.reply <- block
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
// We timed out without finding a correct answer to our query.
|
|
|
|
if !found {
|
2017-04-25 05:44:45 +02:00
|
|
|
msg.reply <- nil
|
|
|
|
}
|
2017-02-14 00:00:02 +01:00
|
|
|
}
|
2017-04-25 05:44:45 +02:00
|
|
|
}
|