问题描述
在区块链的p2p网络中,当自己产生或者收到一个新区块的消息时,在自己校验了区块之后应该会广播出去,这里就会思考一个问题,如果每收到一个块都广播给周围的所有人,会不会形成一次广播的风暴?因此我们开始来观察在以太坊中是如何处理广播区块这个问题的。
以太坊的区块广播
首先,我们确定有哪些情况会导致区块广播,进行怎么样的广播,这里我们可以从protocalManger入手,因为在以太坊的devp2p通信中,所有的信息都是通过protocal传输的,从protocalManger的start方法中可以看到
1 | func (pm *ProtocolManager) Start(maxPeers int) { |
广播交易
广播交易这个做法很干脆,订阅了TxPreEvent之后,就开始等待事件的到来,一旦收到交易,就开始进行如下的广播
1 | // 找到周围没有这条交易的peer,对这条交易进行广播 |
这里就留下了一个有趣的问题,如何知道周围节点有哪些交易的
对这个p.knownTxs的add操作一共有两处地方,一处是在
1 | func (p *peer) SendTransactions(txs types.Transactions) error { |
可以看到,这里是在发送交易前就主动在自己的peerSet中对需要被发送交易的peer进行了记录有哪些交易已经被发送了
另外一处是在处理协议消息处1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// /eth/handler.go:657
case msg.Code == TxMsg:
// 交易消息到达时,确保目前是处于一个可以处理交易的状态
if atomic.LoadUint32(&pm.acceptTxs) == 0 {
break
}
//处理交易,并且将其发送到交易池中
var txs []*types.Transaction
if err := msg.Decode(&txs); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
for i, tx := range txs {
if tx == nil {
return errResp(ErrDecode, "transaction %d is nil", i)
}
// 标记该节点的交易有效
p.MarkTransaction(tx.Hash())
}
pm.txpool.AddRemotes(txs)
这里是在协议收到了消息后的处理,可以看到,对于A,B对tx1交易的广播,A在向B广播时,A自己先会标记B已经有了tx1,B在收到A广播时,会标记A有了tx1,这样就不会重复广播了。
区块产生广播
与交易广播类似,区块产生广播也是订阅了挖矿事件,之后开始对挖矿事件进行监听,然后就进入了如下代码
1 |
|
可以看到区块广播和交易的广播很类似,只是区别在于多了一个标志位,当propagate标志位为false时,这个广播只向周围区块广播区块的hash,同样的会在knownBlocks中加上标记,这里和交易广播完全一样。
当propagate标志位为true时,会广播整个区块,不过广播方式会有所区别:
首先需要计算这个区块的TD
TD:total difficulty,所有父区块的复杂度的和,这个不会存在区块中,但是会存在数据库中的一个字段
计算之后广播给当前区块集的子集,子集的算法是总peer数量的开方(暂时不知道为什么用这种方法)
同步区块处理
同步区块协程会周期性的从网络同步hash以及区块数据
1 | func (pm *ProtocolManager) syncer() { |
我们还是从发送方进行分析,首先需要选择一个peer进行主动同步,也即是BestPeer,简单来说,就是当前时刻td最大的peer
1 | // 获取最大td的peer |
接下来,已经找到了最佳的peer,我们就开始关注同步区块的过程
1 | func (pm *ProtocolManager) synchronise(peer *peer) { |
可以看到,主动从远程peer同步区块的核心还是在于td的比较,一切都要远程peer的td比自己大,然后才开始进行区块同步,具体同步的方法是downloader.Synchronise
方法,接下来我们细细分析一下这个方法(等待细化)
1 | func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.Int) (err error) { |
同步交易处理
当有节点到来,我们会将现有的等待打包的交易发送给一个新节点,为了利用带宽,我们会打包交易并且一次性发送给一个peer
1 | // /eth/sync.go:65 |