有些依赖时间弱同步假设的共识,比如PBFT,DPOS,和一些元胞自动机衍生(MoCA)的共识。在实现的时候需要得到消息的到达时间。
学习了一下以太坊如何处理区块到达时间的。这里记录一下。
一切的起点是p2p
模块的server.run()
函数
从这里开始,geth启动了p2p服务器。
func (srv *Server) run(dialstate dialer) {
srv.log.Info("Started P2P networking", "self", srv.localnode.Node().URLv4())
...
go srv.runPeer(p)
peers[c.node.ID()] = p
...
}
调用runPeer
函数 p2p/server.go, L1044 - L1067
func (srv *Server) runPeer(p *Peer) {
...
remoteRequested, err := p.run()
...
}
p2p服务器通过runPeer
函数,调用了peer
的run
函数
启动节点。
run函数中启动了各种监听,
func (p *Peer) run() (remoteRequested bool, err error) {
...
go p.readLoop(readErr)
go p.pingLoop()
...
}
跟进readLoop
中就可以看到对msg
的操作。
func (p *Peer) readLoop(errc chan<- error) {
defer p.wg.Done()
for {
msg, err := p.rw.ReadMsg()
if err != nil {
errc <- err
return
}
msg.ReceivedAt = time.Now()
if err = p.handle(msg); err != nil {
errc <- err
return
}
}
}
可见 readLoop
函数会循环调用rw
中的ReadMsg
向读出的msg
中写入msg.ReceivedAt
值为当前时间
这里的ReceivedAt
就可以在后面用来判断消息的到达时间,通过比较ReceivedAt
和block.header.timestamp
就可以很方便的判断消息在网络中游荡的时间长度。
函数readLoop
中,将处理好的msg
送到了p.handle
里
peer.handle
中,又将msg
给到了proto.in
func (p *Peer) handle(msg Msg) error {
...
proto, err := p.getProto(msg.Code)
...
select {
case proto.in <- msg:
return nil
...
}
进入getProto
函数中,看一看这个proto
是啥。
func (p *Peer) getProto(code uint64) (*protoRW, error) {
for _, proto := range p.running {
if code >= proto.offset && code < proto.offset+proto.Length {
return proto, nil
}
}
...
}
这里可以看到所有proto
来自p.running
域,
在Peer
的结构定义中,我们可以找到running
的数据类型
type Peer struct {
...
running map[string]*protoRW
...
}
running
是一组protoRW
当protoRW.in
被赋值时,protoRW.ReadMsg
函数就不再堵塞。
func (rw *protoRW) ReadMsg() (Msg, error) {
select {
case msg := <-rw.in:
msg.Code -= rw.offset
return msg, nil
case <-rw.closed:
return Msg{}, io.EOF
}
}
这样就又回到了readLoop
函数的开始。
可见readLoop
函数的作用,就是为所有msg加上ReceivedAt
后
重新再放回到ReadMsg
中.
知道了ReceivedAt
是怎么来的。现在看一下在哪里使用了它。
以太坊的用法如下
首先由ProtocolManager
调用handle
开启监听。
func (pm *ProtocolManager) handle(p *peer) error {
...
if err := pm.handleMsg(p); err != nil
...
}
ProtocolManager.handle
中调用消息监听pm.handleMsg
函数。
func (pm *ProtocolManager) handleMsg(p *peer) error {
// Read the next message from the remote peer, and ensure it's fully consumed
msg, err := p.rw.ReadMsg()
...
case msg.Code == NewBlockMsg:
...
request.Block.ReceivedAt = msg.ReceivedAt
request.Block.ReceivedFrom = p
...
可见当收到NewBlockMsg
消息的时候,
解析出的block
会做block.ReceivedAt = msg.ReceivedAt
操作
至此,区块block
就知道自己是什么时候到的该节点了。
如果是从Block.ReceivedAt
一点一点往回追,
会卡在ProtocolManager
的p.rw.ReadMsg
函数上。
这个函数本身没有对ReceivedAt
的定义。
而且其中的in
信道也很难find。
不过,换个角度。从p2p.sevrer
开始追。
一直追到readLoop
就恍然大悟了。
以太坊的消息到达时间处理流程如下。