以太坊(ethereum)实现研究

ethereum p2p Kademlia的实现之六

2018-04-20  本文已影响14人  古则

前面的该系列文章中已经分析了网络的建立及维护,包括:

现在开始分析udp中数据协议

1 通信数据的定义

const (
    macSize  = 256 / 8
    sigSize  = 520 / 8
    headSize = macSize + sigSize // space of packet frame data
)

可知通信数据分为三个部分

上面三个部分可以使用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
}
上一篇下一篇

猜你喜欢

热点阅读