Hi 游客

更多精彩,请登录!

比特池塘 区块链前沿 正文

P2P 网络的思考

韩氏王子韩hl
71 0 0
P2P网络综述* T9 x1 _% k* T+ f2 Q4 o
, h) t8 j0 Q* \2 R
    P2P网络中每个节点具有完全相同功能,既提供服务又提供客户端访问。这样做的好处是可扩展性非常强。可以随意增加或者减少节点,对整个网络服务不会有很大影响。P2P网络和现在Internet上大部分网络之间最大区别在于,现在大部分网站是C/S(Client/Server)架构,而C/S架构是有一个强依赖。如果中心节点挂掉,Client节点将无法工作。P2P和C/S架构区别就是P2P架构没有一个中心节点,每一个节点其实都是一个小中心,这样网络上只要存在节点,那么整个服务就能继续进行。
  X9 q4 z! X; ]5 D
" S0 _% F) l( P' T0 ?$ X    比特币P2P网络
; R* y) t' N7 y3 N6 f3 a! t
3 I  ?7 A8 ^: ~/ K$ A    了解了P2P网络的定义之后,接下来开始讲解比特币P2P网络。按照顺序,我们从比特币节点启动开始,给大家讲解如何同步到最高高度、出块、转发交易。1 E# j5 L  ?: q# a- M( A

' }5 M+ u6 O, l, i4 X9 h    节点启动
# z7 r9 O( I: h3 W
6 ?* l! Q9 D6 h! B5 Q1 n& d% r) \    首先是启动,启动就有一个节点发现的过程。比特币客户端启动时,有四种选择来连接该网络。8 K  c8 b# E7 k+ Y8 l9 S1 F/ v0 y
$ r3 F" `/ J' P
    用命令行强指定链接某个节点,那么就会去连接。0 b6 R, H" o0 f5 Y% g4 M, \5 p6 q

; G  \( [4 _1 X) @7 Y  v/ K    如果没有命令行指定,就在本地节点查询是否有存储的、连接过的IP地址,如果有就尝试链接。连接不上那么会选择第三步。
% q2 P  `! d& {, J; y1 b7 D
; \5 L4 p( B* S" L9 A    如果没有命令行指定也连接不上之前连接过的IP地址,那么在比特币每次发版时,在代码里面硬编码了一些DNS的服务器,这些服务器可以理解为我们现在Internet里,网址和IP对应的服务器。这个服务器本身可能是静态或者是动态的,如果节点查到有相应服务器的话,就向相应地址发起连接请求,如果还不能连接上,那么就到最后一步。  Y' E* O* m2 `
3 Q7 o$ u, C9 M4 K, n- }( a
    客户端维护了一个活跃节点列表,客户端和他们连接能够获取一些网络的信息。
: c5 Y! k1 }% e* {8 c/ i) y& l' `* Z; G1 ~5 [! W
    (这个感觉有点像你实在找不到人求救的时候还能打110的固定电话)
* R3 Q, S1 y$ ~$ y% U
2 q* K: i( e  W0 M) U! L4 i) T, L    这就是比特币网络发现节点的过程,和普通的一些分布式应用节点发现类似,(在版本编码时)都有一个类似注册中心,如果没有直接指定,节点就会向注册中心发起一些查询工作来寻找可以连接的节点。
& V& E6 V6 [% J! @6 U* U2 F) m* U* H) p' P& b' F# X5 g: V
    查询到可连接IP后
# T, D- A- N7 F7 R* F. o
0 f1 b4 w! u7 U5 C. e( {6 V9 Q    那么下一步,本地节点如果已经链接到可用的IP时候,会做什么?
0 E4 f4 g0 x/ X( m: L3 D9 ~
  V& V3 V0 l5 V, t* Z% W" T6 {    首先,比特币知道IP时,将对远端节点发送Version消息,里面会有自己客户端的型号、版本号、类型(轻节点、全节点或者验证节点)。远端节点收到这个消息之后会根据自己的版本号、能够接受的消息来返回消息,如果这个消息验证结果是我能和这个节点通信,那么就会返回一个消息verack,远端节点也会同时向节点发布一个Version确定自己版本和节点类型。请求verack之后本地节点就向远端节点发送它记录的IP节点的list,就把活跃的list存在数据库,方便以后快速启动节点。
7 _* a5 ?" ~& w4 h: q" R9 z5 L5 R: ?" W% }5 J/ p# [+ X+ Z2 m
    同步策略2 U0 F2 T6 D8 }* {) H, G

8 I1 c# l2 q, F* p    在新节点链接上网络时,本地是没有数据的,这时需要向远端节点请求一些历史数据,同步整个链的数据到本地,这里有两个策略。分别是版本0.9.3之前的Blocks-first策略,和0.10之后的Headers-first策略,对比图如下:
3 I1 K+ R7 ]% L! e7 c( _9 ~2 y' U+ u' X! v4 T
    实际上交互模式是一样的,首先是hash或者header。hash是每个块的hash,每个块的块头会有一个代表整个块的hash,headers-first则将每个块的headers拿过来。之后是用header或者hash向远端节点请求blocks,然后往复进行。看上去协议都差不多,为什么要这样做?
; Q+ R+ R3 M" a5 E. X1 j5 L4 p/ Z! A3 v9 H& o/ g3 g
    Blocks-first问题
5 ~" d$ {- b. r( ^3 f' r( W" ~
8 L) m3 E& ~8 s' U    Blocks-first唯一优点是实现方便,但它存在下面几个问题:% q" _$ \) P0 N; ~2 l

' U! w1 d! {( b+ ]& @1 i    首先,我的节点连接到其他的节点,实际上如果没有断掉就会一直请求数据。那么如果这是一个故意做恶节点呢?它会返回一些不是最大工作量证明的hash,而本地节点是不知道hash里面是什么,只会根据返回的hashlist去请求。最后同步到什么状态呢?Bitcoin每到一定高度会有checkpoint的点,会检查你同步的块是最大工作量证明还是错误的块。如果连接的节点是恶意节点,那么,我的节点只有到checkpoint时,才会发现我同步的几块是错误的,这样导致我本地服务器的磁盘消耗会非常大,一般是几个甚至是几十个G之后发现这些是无效数据。$ m/ Z+ f2 d) S

& n0 e# u, p* f: ~  M    其次,是速度限制。Blocks-first协议规定,如果这个连接没有中断的话,将一直向同一个节点请求数据。如果远端节点上传和下载数据受到限制,本地节点同步的速度将受到限制。
5 t1 v3 c- f7 n3 n* @6 H6 R# s" Z( t: P. @
    最后,是高内存占用。Blocks-first策略请求的是块的hash,一个问题就是协议要求远端节点的hash是完全按照顺序发送的,如果没有按照顺序本地节点就会验证不了,从而导致乱序的块。这样的块如果找不到parentsblock,比特币就会把这些块定义为是孤儿块,会把它们临时存放在一个内存里面,直到请求到完整的parentsblock之后才会把parentsblock和孤儿块入库。这时候如果远端节点做恶的话,先给很多高度很高的块,前面的都不给(也就是没有parentsblocks),每个块需要几k,几十万个块就能把你的内存撑爆。. [# q. R2 }3 g! H3 c7 _& H9 x

$ k& V7 u4 h2 c    Headers-first策略
3 B" b' C+ `& [
$ ]3 [+ O) F. E% z' U5 l- ]: C    Headers-first的策略闪亮登场。这个策略会把整个链的headers全部下载下来,headers里面包括所有block的hash以及parents的hash,可以用headers串起一条链。虽然不知道交易是什么,但是能够从headers里面确定链是连续的,不连续会扔掉孤儿块。并且根据官方验证,一个新的节点同步整个链的话,只需要30m的存储能够把整个链全部同步下来,磁盘消耗非常小。
5 C, M8 I, B& J. o
/ A2 w  M5 h& n6 V    那么速度限制怎么解决?headers一次请求是2000个块的步长。如果请求验证没问题的话,就会把haederhash16个一组发送给所有能够连接到的节点,来验证是不是对的,然后把对应的header的block给他,这样同步的时候不会被某个节点的网络限制,可以获取一批blocks向四周发送同步请求。, D7 y* U) [# t2 B. e! z0 K8 x$ `: B
! M) }5 {" ~) l" }
    这就是比特币的同步策略。相比于blocks-first策略,headers-first策略解决了1、速度限制;2、无效下载;3、高内存占用三个问题。$ V  f& E  }7 I& T9 r
( u7 n6 k2 U/ [
    新块广播
2 L/ S' B0 l+ p' B% `% U
3 |( c, |9 }. M* L. V& l! X    接下来,同步到最高高度的时候,如果是挖矿节点就会涉及到挖矿广播的问题。我发现了下一个块的hash,我就会把块广播给我链接到的所有节点来获取奖励,这时候广播会有三种模式:
( X* y5 y+ V; o8 i5 k% G+ }; c8 E/ P8 t( c6 B; L" x
    1、推送式。这一种用的比较少。只要能连接上对方节点,那就把整个块推送过去,不管对方接不接受。
0 ]/ b2 G) V  c, e/ z8 h, [/ `  I8 R$ H" c  E+ K/ Q
    2、标准转发模式,发现新块时,将新块的hash做一个消息推给周围所有人,如果对方返回消息说他需要hash具体数据,那么就将整个块给他,有一个通知→回复→再回复的过程。$ g3 z1 L! f$ G

2 M8 y" V. p( k$ b    3、0.10.0之后,出现了块头公告的模式。只要是headers-first策略的节点,启动时,会做一件事情告诉连接所有节点,我不要标准转发的模式,我需要的是发现新块时,直接把header给我。这样就能够省去一个「我给列表→你请求列表数据→然后再把数据给我」的整个过程。直接把header给他,然后他就可以向任何人请求headers对应的data。8 p6 D9 L) L: v" C* @1 g: ?1 O5 f

! w% }& F1 P5 a* W% E    这就是新块广播的过程。1 i1 v, H) o9 w# q! ~
1 z  _. W! ^- d& U* g& [9 ^! w! \
    节点交易转发过程& i' i6 q) ^5 [* k* c

4 J7 S5 K1 d2 a; h, F4 t    交易发送到任意能够接受的节点的时候,交易会存在当前节点的内存池里面,然后节点收到交易时,会做一个hash列表,发送给能够连接到的所有人。一个节点最多能够连接8个节点,如果别的节点发现内存池里面没有这个交易,那就返回getdata。那么对方收到的时候会把整个交易的内容转发过去。
, O) D# v: P& k- V* L3 ]2 A" s3 E% Z4 D
% l% P+ J% m3 U4 G% P    这里有一个问题,就是不要相信每个交易都会上链,因为交易是存在内存池里面的,并不上链。如果一个节点没有把交易发送出去就下线了,那么这笔交易就会丢失,这是很正常的一件事情。
/ L) H, }( y" K3 J. T4 k: M% _* O6 B+ k* r4 F* {* J, ~9 {/ t
    这就是交易的转发过程。
9 I. K1 R$ q, s) c# J6 f; m# L- s$ m- h  S8 O) _0 C
    到这里比特币的网络协议基本就是这样节点启动、同步、最高高度之后能够转发交易,能够运作。/ ^/ O- h% Y& d1 S0 d4 i

7 y, X) Q  K% a# p4 U/ d    公有许可链CITA
" s2 F$ \7 u  q1 R- O: w& i$ x* b  {( [. d
    比特币是区块链的鼻祖,但在性能上存在一个问题,10分钟一个块,6个块之后才能确认交易是放在主链上的。之后为了能够有更高的性能就出现了联盟链。CITA项目初创的时候是联盟链,我们讲一下当前状态的CITA联盟链是什么样的。
0 t9 Q$ Y/ t* N/ }6 f( L4 ^+ k3 O0 X" o
    CITA网络综述
! S, v0 M5 T: ?1 h! E6 J* e9 o3 x/ Z7 m. v2 b  R* ^
    CITA的网络,和比特币不一样的地方就是CITA是N个进程结合在一起作为整个逻辑节点的。比特币是一个进程,CITA是微服务架构,所以有七个进程。CITA对外有节点间通信,对内还有每个服务之间的通信,所以会分为节点间的通信和节点内的通信。因为CITA刚开始时,作为联盟链,节点数在链的初始状态就确定了,也不会很多,所以在一开始就使用TCP全连接。/ U# P0 h1 ^8 u% j2 [

) r' ~8 D# _5 m1 w# a    这么做的好处是什么?即我的消息,只要是知道的,只需转发就点对点转发给我所知的所有节点,消息到达速度非常快。比特币消息转发给全世界需要时间,CITA作为联盟链的时候,每个节点之间都是有连接的,如果一个消息要广播给整个网络,只要他保留TCP所有连接,就能够一次性转发出去。节点之间的通信协议并没有比特币那么复杂,并没有Version、transaction、getdata等,而是最简单的协议,只有开始符、长度和数据。
7 N+ o: R6 Y* k& B6 H' y3 o; h* T6 B* o7 u. ~
    节点间同步: ^- u; Y2 w& h6 v: l3 u+ \
# d* V1 s' h' t6 a. S7 S/ C3 j3 I
    目前,CITA共识算法是PBFT,是确定性的、一定不会分叉的共识算法,做恶节点小于三分之一时即可保证不会分叉。所以CITA的同步不需要分叉判断,只需要判断块高。如果我这个节点发现我连接的整个网络块高比我高三个块左右时,就会随机选择一个节点,大概20个块一组去同步。同步的消息是整个块发过来的,而不是像比特币那样先发送一个列表、header之后再把整个块发过来。
1 T. T: g, ]1 z* ?4 N" Z% @5 E  e: D, |% L) f, o9 \
    交易转发4 e! |0 h2 F* F/ J8 F) `
6 Z; L3 y" a. E, Q5 j" w
    关于交易的转发,因为CITA是全连接的,只要接到交易,验证通过之后,交易就会加入本地交易池,之后转发给所有能连接的节点,除了来源之外(转发交易给我的)所有节点都发一遍,在确保每个交易正常和网络正常的情况下,是不会拉下任何一个节点的。! Z3 [5 V9 H6 J
1 ^0 x) `; G9 l4 L  i. b6 n
    共识提出8 O! }0 O2 @6 W9 V# O) R, W

3 `+ A2 R" N2 b1 g  _5 u- z    在CITA中,共识的proposal提出的话,也是以广播的方式去发送的。CITA里面节点是分为共识节点和只读节点。共识节点有权发起投票,只读节点只能进行同步和交易的转发。虽然只读节点不能发起投票但是也能够收到proposal的消息,然后节点会判断自己能不能投票,如果不能投票就会放弃这个交易。这个是CITA节点之间的通信。对比特币来说,CITA节点通信简单了很多,因为网络环境比较简单,毕竟比特币是公网。
( D" V6 O) |3 r0 p) Y  N8 l9 h) y# ]: y4 c  w, k- n* d$ t: k5 h* P
    节点内部通讯4 Z* F$ |8 A, d0 d! c! X- E

0 R& X8 S: D9 e- ]9 i* H" g' G    CITA节点内部的通信,每个微服务的通信都是异步的模式,即我(节点)发出消息之后,不管对方是否回复都先做自己的事情。这里有一个问题就是,如果我这个节点的消息你没有收到,另外一个微服务突然挂了,状态不一致的情况下,就有一个状态同步的问题。现阶段CITA分为共识进程,执行器进程,数据库进程以及网络进程,这些都是有状态的,以数据库为准。如果数据库说这个节点有100的高度,那么整个逻辑节点所有状态都是100的高度。如果不是,就是坏掉了。所以现在最复杂的问题是节点内部每个服务状态同步的问题,如果不一致会让整个节点出现异常。比如:出现对外表现不一致,或者因为状态不一样所以不能发器投票。因此节点内通信最重要的是状态的同步问题,如果我应该接收到的没有收到,就会发起一个request-response来确认状态,如果状态和数据库不一致的话,将调整状态。/ {- J0 l/ L" U0 o0 m# Y

8 |! I# H$ B! K+ ^    未来可能性
& s. I' |& l8 v& H
: D. Z% ]1 Y% {- x. H' P    如果CITA变成公网,我们会遇到的一个问题是网络连接质量下降,如何既保持开放又能为共识提供足够的网络速度?节点间的直连也许依然会是需要的一种方式,同时补充gossip网络协议,即在我发送消息时知道的所有节点中,随机找两个节点发出去,别人收到时也会随机选两个节点转发出去,全网最终能够达到一致的状态的,我的消息都能够传遍全网。这样能够大大降低服务器的网络开销。共识节点可以用直连的方式来保证出块和交易速度。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

韩氏王子韩hl 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    6