您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
免费发信息
三六零分类信息网 > 景德镇分类信息网,免费分类信息发布

从源码层面分析Neo的网络通信

2024/1/6 3:46:22发布29次查看
前言区块链应用
neo支持c#和java开发,在全球社区的共同努力下现已把sdk拓展到了js,python等编程环境,所以进行neo开发是没有太大的语言障碍的。
比特币在解决拜占庭错误这个问题时除了引入了区块链这个重要的概念之外,还引入了工作量证明(pow)这个超前的解决方案,通过数学意义上的难题来保证每个区块创建都需要付出计算量。
然而实践已证明,通过计算来提供工作量证明,实在是太浪费了:全世界所有的完全节点都进行同样的计算,然而只有一个节点计算出的结果会被添加到区块链中,其余节点计算消耗的电力都白白浪费了。尤其,工作量证明存在一个51%的可能攻击方案,即只要有人掌握了世界上超过50%的算力,他就能对比特币系统进行攻击,重置区块链。
中本聪先生发明这个算力工作量证明方法时,大概没有料到会有人专门为了挖矿而开发asic矿机。neo在解决这些问题时提出了一个新的共识机制概念dbft( delegated byzantine fault tolerant),将节点分为两种,一种为不参与共识的普通节点,即不进行认证交易签名区块的过程。另一种为共识节点,即可以参与共识的节点,这部分基础概念可以参考官方文档。
接下来,我将从源码层面分析neo网络通信协议。
源码概览
本文分析的源码地址:
githubcomneo-projectneo
通过git命令下载到本地:
git clone githubcomneo-projectneogit
我用的编译器是vs2017社区版。打开neo项目后可以看到项目根目录文件结构:
- consensus 共识节点间共识协议
- core neo 核心
- cryptography 加密方法
- implementations 数据存储以及钱包的实现
- io neo 的io类
- network 用于p2p网络通信的方法
- smartcontract neo 智能合约的相关类
整个项目代码量不算很大,尤其是项目本身是c#高级语言编写,所以代码很容易读懂。
消息
在neo网络中,所有的消息都以message为单位进行传输,message的定义在messagecs文件中,其结构如下:
magic 字段用来确定当前节点是运行在正式网络还是在测试网络,如果是0x00746e41则为正式网,如果是0x74746e41则为测试网。
_command_命令的内容是直接使用的字符串,所以没有进行严格定义,在所有使用到的地方都是直接使用的字符串。我认为依赖比较严重,应先定义好命令再在别的地方调用。虽然没有明说都有哪些命令,但是在消息路由的代码里,我们可以找到所有使用到的命令。
源码位置:
neonetworkremotenodecsonmessagereceived
以上源码中关于命令的处理部分不是本小节讨论的重点。通过分析代码可以知道,消息种类大致有22种。消息的具体内容在序列化之后存在在message里的payload字段中。
在所有的消息类型中有一类消息非常特殊,与账本相关的三种消息:账目消息(block)、共识消息(consensus)以及交易消息(transaction)。这三中消息分别对应系统中的三个类:
- neocoreblock
- neocoretransaction
- neonetworkpayloadsconsensuspayload
这三个类都实现了接口iinventory,我把inventory翻译为账本,把实现了iinventory接口的类成为账本类,消息称为账本消息。iinventory接口定义了消息的哈希值hash用来存放签名、账本消息类型inventorytype用来保存消息类型以及一个验证函数verify用来对消息进行验证,也就是说所有的账本消息都需要包含签名,并且需要验证。账本消息的类型定义在inventorytypecs文件中:
源码位置:neonetworkinventorytypecs
每个remotenode内部都有两个消息队列,一个高优先级队列和一个低优先级队列,高优先级队列主要负责:
- "alert"
- "consensus"
- "filteradd"
- "filterclear"
- "filterload"
- "getaddr"
- "mempool"
这几个命令,其余的命令都由低优先级队列负责。发送命令的任务由startsendloop方法负责,在这个方法中有一个while循环,在每一轮循环中都会首先检测高优先级队列是否为空,如果不为空则先发送高优先命令,否则发送低优先级任务,循环中的核心源码如下:
源码位置:
neonetwotkremotenodecsstartsendloop
由于每个remotenode对象都只负责和一个相对应的远程节点通信,所以接收消息的地方没有设置消息缓存队列。接收消息的循环就在调用startsendloop位置的下面,由于startsendloop本身是个异步方法,所以不会阻塞代码的接收消息循环的执行,在每次收到消息后,都会触发onmessagereceived方法,并将收到的message消息作为参数传递过去。在上文中也讲了,这个onmessagereceived方法其实是个消息的路由器,会根据消息类型的不同调用响应的处理函数。
新节点组网
节点是组成neo网络的基本单位。neo在network文件夹下有一个localnode的类,这个类的主要工作是与p2p网络建立并管理与远程节点连接,通过其内部的remotenode对象列表与远程节点进行通信。localnode在start方法中创建了新的线程,在新线程中向预设的服务器请求网络中节点的地址信息,之后将本地的服务器地址及端口发送到远程服务器去以便别的节点可以找到自己。
源码位置:
neonetworklocalnodecsstart
通过代码可以看到,在成功获取到节点信息并在服务器中注册过之后,节点会开启一个线程,并在线程中与这些节点建立连接,建立连接在localnode类中最终的接口是connecttopeerasync方法,在connecttopeerasync方法中根据接收到的远程节点地址和端口信息新建一个tcpremotenode类的对象:
源码位置:
neonetworklocalnodecsconnecttopeerasync
tcpremotenode类继承自remotenode,每个对象都代表着一个与自己建立连接的远程节点,remotenode和localnode的关系大致可以这样表示:
tcpremotenode的构造函数在接收到远程节点信息之后会与远程节点建立socket连接并返回一个remotenode对象,所有的远程节点对象都被保存在localnode中的远程节点列表里。
获取网络节点的方式除了从neo服务器获取之外还有一个主动获取的方式,那就是向所有的与本地节点建立连接的节点广播网络节点请求,通过获取这些与远程节点建立连接的节点列表来实时获取整个网络中的节点信息。这部分代码在与远程节点建立连接的线程中:
源码位置:
neonetworklocalnodecsconnecttopeersloop
向远程节点请求节点列表的requestpeers方法在remotenode类中,这个方法通过向远程节点发送指令“getaddr”来获取。由于remotenode的责任是与其对应的远程节点进行通信,所以对“getaddr”这个远程命令的解析和路由也是在remotenode类中进行。在remotenode接收到远程节点信息后会触发onmessagereceived方法对收到的信息进行解析和路由:
源码位置:
neonetworkremotenodecs
switch中对于别的命令的解析我都删掉了,这里只关注“getaddr”命令。在收到“getaddr”命令后,会调用相应的处理函数ongetaddrmessagereceived:
源码位置:
neonetworkremotenodecsongetaddrmessagereceived
由于直接与远程节点进行通信的是与其对应的本地的remotenode对象,而这些对象有需要获取localnode中保存的信息,neo源码的处理方式是直接在创建remotenode对象的时候传入localnode的引用,这里我感觉很不舒服,因为明显有循环引用,尽管在这里功能上不会有什么问题。因为每个节点既做为客户端,又作为服务端,与本节点建立的网络连接里,即存在自己主动发起的socket连接,也存在远程节点将本节点作为服务端而建立的socket连接。监听socket连接的任务在线程中不断的执行,每当接收到一个新的socket连接,当前节点会根据这个socket来创建一个新的tcpremotenode对象并保存在localnode的远程节点列表中:
源码位置:
neonetworklocalnodecsacceptpeers
最后以三个节点的网络拓扑为例:
区块同步
新区快的生成与同步主要依靠共识完成后的广播,但是对于新组网的节点应该如何获取完整的区块链呢?本小节将针对这个问题进行源码的分析。
当一个新的remotenode对象创建之后,会开启这个对象的协议:
源码位置:
neonetworklocalnodecs
在协议开始执行后,会向远程节点发送一个 "version" 命令。在查询这个 "version" 命令的响应方法的时候简直把我吓了一大跳,居然调用的是disconnect而且传的参数是true。本着“新连接建立之后的第一件事肯定不会是断开连接”这个唯物主义价值观,我又对代码进行了一番研究,终于发现这个发送 “version” 的命令是直接由receivemessageasync方法获取的,也就是不经过那个消息路由。由于在两个节点建立连接后。两者做的第一件事都是发送 “version” 命令和自己的versionpayload过去,所以在这个socket连接中节点接收到的第一条消息也都是“version”类型的消息。
源码位置:
neonetworkremotenodecsstartprotocol
这里需要对这个versionpayload进行下讲解,这个versionpayload里包含当前节点的状态信息:
也就是说在连接建立后,当前节点就可以知道远程节点当前的区块链高度,如果自己当前的区块链高度低于远程节点,就会向远程节点发送 "getblocks" 命令请求区块链同步:
源码位置:
neonetworkremotenodecsstartprotocol
因为区块链有非常大的数据量,区块链同步不可能直接一次完成,每次收到 “getblocks”的命令之后,每次发送500个区块的哈希值:
源码位置:
neonetworkremotenodecsongetblocksmessagereceived
之后在每次接收到远程节点的消息之后,如果当前节点区块高度依然小于远程节点,本地节点会继续发送区块链同步请求,直到与远程节点的区块链同步。
景德镇分类信息网,免费分类信息发布

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录