ethereum p2p Kademlia的实现之六
2018-04-20 本文已影响14人
古则
前面的该系列文章中已经分析了网络的建立及维护,包括:
- 使用udp维护Kademlia网络
主要的逻辑实现都位于: p2p/discover/udp.go - 使用tcp建立数据通道
现在开始分析udp中数据协议
1 通信数据的定义
const (
macSize = 256 / 8
sigSize = 520 / 8
headSize = macSize + sigSize // space of packet frame data
)
可知通信数据分为三个部分
- mac 32个字节的数据hash(使用sha3算法,Keccak256,对32个字节以后的数据--包括前面进行hash)
- sig 65个字节的私钥签名(签名的实现位于crypto/signature_nocgo.go Sign方法),使用ECDSA(椭圆曲线算法签名)
- 真正的数据,该部分数据使用rlp编码(以太坊rlp编解码规则及实现)
上面三个部分可以使用c语言定义如下
struct data {
char hash[32];
char sig[65];
char payload[];
}
2 payload中四种数据包
维护Kademlia的udp通信时,payload部分又可以分为4种通信包
ping struct {
Version uint
From, To rpcEndpoint
Expiration uint64
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
}
// pong is the reply to ping.
pong struct {
// This field should mirror the UDP envelope address
// of the ping packet, which provides a way to discover the
// the external address (after NAT).
To rpcEndpoint
ReplyTok []byte // This contains the hash of the ping packet.
Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
}
// findnode is a query for nodes close to the given target.
findnode struct {
Target NodeID // doesn't need to be an actual public key
Expiration uint64
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
}
// reply to findnode
neighbors struct {
Nodes []rpcNode
Expiration uint64
// Ignore additional fields (for forward compatibility).
Rest []rlp.RawValue `rlp:"tail"`
}
3 编码
3.1 编码的调用过程
//p2p/discover/table.go
func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16)
=>
func (tab *Table) ping(id NodeID, addr *net.UDPAddr) error
=>
//p2p/discover/udp.go
func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error
=>
func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, hash []byte, err error)
=>
func (t *udp) write(toaddr *net.UDPAddr, what string, packet []byte)
3.2 编码的实现
对上述通信数据的三个部分进行编码,然后组合,实现的代码如下
func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (packet, hash []byte, err error) {
b := new(bytes.Buffer)
b.Write(headSpace)
b.WriteByte(ptype)
####
rlp编码
####
if err := rlp.Encode(b, req); err != nil {
log.Error("Can't encode discv4 packet", "err", err)
return nil, nil, err
}
packet = b.Bytes()
####
签名
####
sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
if err != nil {
log.Error("Can't sign discv4 packet", "err", err)
return nil, nil, err
}
copy(packet[macSize:], sig)
// add the hash to the front. Note: this doesn't protect the
// packet in any way. Our public key will be part of this hash in
// The future.
####
计算hash
####
hash = crypto.Keccak256(packet[macSize:])
copy(packet, hash)
return packet, hash, nil
}
4 解码
4.1 解码的调用过程
调用的过程如下
//p2p/server.go
func (srv *Server) Start() (err error)
=>
//p2p/discover/udp.go
func ListenUDP(c conn, cfg Config) (*Table, error)
=>
func newUDP(c conn, cfg Config) (*Table, *udp, error)
=>
go udp.loop()
go udp.readLoop(cfg.Unhandled)
=>
func decodePacket(buf []byte) (packet, NodeID, []byte, error)
4.2 解码的实现
其实就是编码的逆过程,详细见对代码的中文注释
func decodePacket(buf []byte) (packet, NodeID, []byte, error) {
####
判断数据长度是否合法
####
if len(buf) < headSize+1 {
return nil, NodeID{}, nil, errPacketTooSmall
}
####
将三部分数据分离出来
####
hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:]
####
判断hash值是否正确
####
shouldhash := crypto.Keccak256(buf[macSize:])
if !bytes.Equal(hash, shouldhash) {
return nil, NodeID{}, nil, errBadHash
}
####
验证数据签名,同时找到发送方的nodeID
####
fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig)
if err != nil {
return nil, NodeID{}, hash, err
}
####
根据不同的命令类型解码成不同的数据包(struct)
####
var req packet
switch ptype := sigdata[0]; ptype {
case pingPacket:
req = new(ping)
case pongPacket:
req = new(pong)
case findnodePacket:
req = new(findnode)
case neighborsPacket:
req = new(neighbors)
default:
return nil, fromID, hash, fmt.Errorf("unknown type: %d", ptype)
}
####
将数据进行rlp解码
####
s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
err = s.Decode(req)
return req, fromID, hash, err
}