以太坊源码学习(2):msg到达时间的来源

Kohana ·
更新时间:2024-09-20
· 527 次阅读

背景

有些依赖时间弱同步假设的共识,比如PBFT,DPOS,和一些元胞自动机衍生(MoCA)的共识。在实现的时候需要得到消息的到达时间。
学习了一下以太坊如何处理区块到达时间的。这里记录一下。

消息中ReceivedAt的写入逻辑

一切的起点是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函数,调用了peerrun函数
启动节点。
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就可以在后面用来判断消息的到达时间,通过比较ReceivedAtblock.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的赋值。

知道了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一点一点往回追,
会卡在ProtocolManagerp.rw.ReadMsg函数上。
ProtocolManger
这个函数本身没有对ReceivedAt的定义。
而且其中的in信道也很难find。
不过,换个角度。从p2p.sevrer开始追。
一直追到readLoop
就恍然大悟了。

以太坊的消息到达时间处理流程如下。
以太坊ReceivedAt处理流程


作者:不想起床的胖达



学习 以太坊 源码

需要 登录 后方可回复, 如果你还没有账号请 注册新账号