去年年初,很多人都说rust开发区块链如何好,然后就学习了一下。最先接触到的是substrate里面的网络模块,当时对libp2p不是很了解,rust语法也一直半解,以致只能看懂应用消息的转发流程,如何发到对方节点就不是很清楚,年初学习了下eth 2.0客户端Lighthouse,里面宏比较少结构相对清晰很多。
本文讨论的节点同步有历史块同步、新区块缓存,可参考以太坊download,fetcher模块。不涉及节点发现,加密连接。
历史块同步
libp2p封装性很好,提供了一系列模块,如果只是简单的发送区块,通过Gossip可以很容易的做到。
如果新节点启动去同步指定节点,发送请求消息
的时候,你发发现这个需要提供的模块很难满足你的要求,你需要实现自己的Behaviour
,ProtocolsHandler
,UpgradeInfo
,InboundUpgrade
,OutboundUpgrade
等一系列trait
。
impl<TSubstream> NetworkBehaviour for P2P<TSubstream>
where TSubstream: AsyncRead + AsyncWrite,
{
type ProtocolsHandler = P2PHandler<TSubstream>;
type OutEvent = P2PMessage;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
P2PHandler::new(
SubstreamProtocol::new(P2PProtocol),
Duration::from_secs(30),
&self.log,
)
}
fn inject_connected(&mut self, peer_id: PeerId, connected_point: ConnectedPoint) {
self.events.push(NetworkBehaviourAction::GenerateEvent(
P2PMessage::InjectConnect(peer_id,connected_point),
));
}
fn inject_disconnected(&mut self, peer_id: &PeerId, _: ConnectedPoint) {
// inform the p2p handler that the peer has disconnected
self.events.push(NetworkBehaviourAction::GenerateEvent(
P2PMessage::PeerDisconnected(peer_id.clone()),
));
}
fn inject_node_event(&mut self, source: PeerId,
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
// send the event to the user
self.events
.push(NetworkBehaviourAction::GenerateEvent(P2PMessage::P2P(
source, event,)));
}
fn poll(&mut self, _: &mut impl PollParameters,
) -> Async<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,>,
> {
if !self.events.is_empty() {
return Async::Ready(self.events.remove(0));
}
Async::NotReady
}
}
如何实现自定义协议内容有点多,打算放到后面讲解。
握手消息
当节点建立连接后,节点需要交换本地的创世hash,区块高度,网络ID,判断和对方是否在同一个区块链网络里面。
#[derive(Serialize, Deserialize,Clone, Debug, PartialEq)]
pub struct StatusMessage {
/// genesis block hash
pub genesis_hash: Hash,
/// Latest finalized root.
pub finalized_root: Hash,
/// Latest finalized number.
pub finalized_number: u64,
/// The latest block root.
pub head_root: Hash,
/// The slot associated with the latest block root.
pub network_id: u16,
}
如果网络id,创世hash都一致,对方的finalized_number
比你高,本地需要维护对方的状态信息,然后启动同步。
// add the peer to the head's pool
self.chains.target_head_slot = remote.finalized_number;
self.chains.target_head_root = remote.finalized_root;
self.chains.add_peer(network, peer_id);
let local = self.chain.read().unwrap().current_block().height();
self.chains.start_syncing(network, local);
批量区块下载请求
由于当新节点加入网络的时候,当前全网出块高度已经很高了,你不可能一个一个的下载,需要一次下载多个区块,这时候每个请求包需要携带起始高度,请求多少个块,多少个块回传一次。
pub struct BlocksByRangeRequest {
/// The hash tree root of a block on the requested chain.
pub head_block_root: Hash,
/// The starting slot to request blocks.
pub start_slot: u64,
/// The number of blocks from the start slot.
pub count: u64,
/// The step increment to receive blocks.
///
/// A value of 1 returns every block.
/// A value of 2 returns every second block.
/// A value of 3 returns every third block and so on.
pub step: u64,
}