func (pc peerConn) ID() ID {
return PubKeyToID(pc.conn.(*tmconn.SecretConnection).RemotePubKey())
}
节点连接分为inbound和outbound。inbound的意思是别的节点连入自己,outbound的意思是自己主动连接别的节点
// IsOutbound returns true if the connection is outbound, false otherwise.
func (p *peer) IsOutbound() bool {
return p.peerConn.outbound
}
outbound的连接有两种:
inbound的连接只有一种:
节点启动的时候监听connection,有连接连入的时候会判断目前是否有50个连接,如果够了就忽略新的连接。所以一般情况下节点的outbound数量至少为10,总的连接数量为50个(可能会有多于50个的情况,比如刚开始指定过多的seeds)
二.peer.go源码
P2P模块初始化的时候,也会初始化mempool reactor、blockchain reactor、consensus reactor、evidence reactor、pex reactor,然后add到p2p模块,不同的reactor带不同的channel id,
当mempool、blockchain、consensus、evidence模块发送消息的时候,调用p2p模块的send,参数有channel id。
// Send msg bytes to the channel identified by chID byte. Returns false if the
// send queue is full after timeout, specified by MConnection.
func (p *peer) Send(chID byte, msgBytes []byte) bool {
if !p.IsRunning() {
// see Switch#Broadcast, where we fetch the list of peers and loop over
// them - while we're looping, one peer may be removed and stopped.
return false
} else if !p.hasChannel(chID) {
return false
}
return p.mconn.Send(chID, msgBytes)
}
对端节点p2p模块收到消息后,会根据channel id把消息转发给对应的模块。(create channel,join in channel)
三.node_info.go源码分析
1.CompatibleWith(other_ NodeInfo) error: CompatibleWith检查两节点是否互通。如果两节点的块版本一致、网络匹配且至少有一个共同的通道,则他们互通。
func (info DefaultNodeInfo) CompatibleWith(other_ NodeInfo) error {
other, ok := other_.(DefaultNodeInfo)
if !ok {
return fmt.Errorf("wrong NodeInfo type. Expected DefaultNodeInfo, got %v", reflect.TypeOf(other_))
}
if info.ProtocolVersion.Block != other.ProtocolVersion.Block {
return fmt.Errorf("Peer is on a different Block version. Got %v, expected %v",
other.ProtocolVersion.Block, info.ProtocolVersion.Block)
}
// nodes must be on the same network
if info.Network != other.Network {
return fmt.Errorf("Peer is on a different network. Got %v, expected %v", other.Network, info.Network)
}
// if we have no channels, we're just testing
if len(info.Channels) == 0 {
return nil
}
// for each of our channels, check if they have it
found := false
OUTER_LOOP:
for _, ch1 := range info.Channels {
for _, ch2 := range other.Channels {
if ch1 == ch2 {
found = true
break OUTER_LOOP // only need one
}
}
}
if !found {
return fmt.Errorf("Peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels)
}
return nil
}
2.func (info DefaultNodeInfo) ValidateBasic() error: ValidateBasic对自身节点做安全性验证,如果出现如下问题则返回错误:
func (info DefaultNodeInfo) ValidateBasic() error {
// ID is already validated.
// Validate ListenAddr.
_, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr))
if err != nil {
return err
}
// Network is validated in CompatibleWith.
// Validate Version
if len(info.Version) > 0 &&
(!cmn.IsASCIIText(info.Version) || cmn.ASCIITrim(info.Version) == "") {
return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version)
}
// Validate Channels - ensure max and check for duplicates.
if len(info.Channels) > maxNumChannels {
return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels)
}
channels := make(map[byte]struct{})
for _, ch := range info.Channels {
_, ok := channels[ch]
if ok {
return fmt.Errorf("info.Channels contains duplicate channel id %v", ch)
}
channels[ch] = struct{}{}
}
// Validate Moniker.
if !cmn.IsASCIIText(info.Moniker) || cmn.ASCIITrim(info.Moniker) == "" {
return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker)
}
// Validate Other.
other := info.Other
txIndex := other.TxIndex
switch txIndex {
case "", "on", "off":
default:
return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex)
}
// XXX: Should we be more strict about address formats?
rpcAddr := other.RPCAddress
if len(rpcAddr) > 0 && (!cmn.IsASCIIText(rpcAddr) || cmn.ASCIITrim(rpcAddr) == "") {
return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr)
}
return nil
}
3.NetAddress() *NetAddress: NetAddress返回从DefaultNodeInfo派生的NetAddress 。它包含经过身份验证的节点ID和ListenAddr。请注意,ListenAddr未经过身份验证。
func (info DefaultNodeInfo) NetAddress() *NetAddress {
idAddr := IDAddressString(info.ID(), info.ListenAddr)
netAddr, err := NewNetAddressString(idAddr)
}
四.switch.go源码分析
五.MConnection源码分析
1.Tendermint对等网络系统的核心是 MConnection。 每个连接
具有 MaxPacketMsgPayloadSize, 这是最大数据包大小的和有界发送和接收 队列。人们可以限制发送和接收每个连接速率( SendRate,RecvRate)。
2.P2P与网络协议
• 基础P2P层: 在已验证和加密的TCP连接上复用协议(“反应器”)
• peer节点交换 (PEX): gossip已知peer address所以节点见可以互相发现
• Block Sync: gossip区块使节点间尽快同步
• Consensus: gossip投票和区块部分使新区块能够尽快提交
• Mempool: gossip交易使它们能够打包成区块
• Evidence: (???)
3.MConnection 是支持具有不同的多个独立流的多路连接 服务质量保证上层的TCP连接。每个信息流被称为一个channel并且每个channel具有全局性唯一的字节ID。 每个channel也有一个相对优先级来决定该渠道相比其他渠道的服务质量。channel的ID 和 相对优先级在连接的初始化时配置。
4.Msg
信道中的消息被切碎成更小的msgPackets。
type msgPacket struct {
ChannelID byte
EOF byte // 1 means message ends here. Bytes []byte
}
5.多路复用(Multiplexing)
一批数据信息可能包括来自多个channel的消息。消息会排队等待发送到相应的channel,同时每个channel都会持有一个未发送的信息。每次从具有最低最近发送字节与信道优先级比率的信道中为批处理消息选择一条消息。
6.发送消息
发送消息有两种方法:
func (m MConnection) Send(chID byte, msg interface{}) bool {} func (m MConnection) TrySend(chID byte, msg interface{}) bool {}
发送(CHID,MSG) 是一个阻塞调用,等待msg被成功地排队用于与通道CHID。 该消息msg串行通信。
该 tendermint/wire子模块的 WriteBinary() 调用程序。