mynotes/GFS笔记/GFS笔记.md
2019-10-17 11:40:32 +08:00

13 KiB
Raw Permalink Blame History

GFS笔记

端口号被占用的错误信息:

Only one usage of each socket address (protocol/network address/port) is normally permitted

主链

requestLatestLightBlockList函数(从网络中获取主链最新一段)

  • 首先先发送EventRequestLatestCoreChainLightBlockList信号这个信号意思就是获取网络最新的20个lightBlock数据
  • EventLoop.On持续监听网络回复的最新20区块的数据会接受到多次数据
  • 调用callback2将网络收到的 chain 数据缓存到 blList 中,并使用计数器控制调用 callback1
  • 另一个触发callback1的条件是 groutine中定时器设置10s后调用callback1

callback1:处理本地数据和网络数据的函数

  1. 先注销 callback2 相关的 EventLoop 事件;

  2. 从本地提取最新20个块组成的链每一个区块都包含当轮次的核心组所有成员

    • 先从数据库中提取全链数据(lightBlock)
    • 再截取前20个block
    • 再通过lightBlock的ID获取全量块用来补全lightBlock的签名
  3. 将网络获取的chain和本地的chain数据中的所有 base块 和 核心成员都缓存到map中再将 base块的ID和核心成员的ID存储在切片中 baseBlocks validators

  4. 两线程中的公钥线程:

    • 获取所有核心成员的公钥;

    • 遍历blList中的每条chain利用获取的公钥验证将验证通过的append到 list 中最终将blList更新为 list

  5. 两线程中的base块线程

    • 通过前边记录的base块ID将其中所有的base块取出先从本地获取本地没有从网络中获取存储在 key为blockId value为block 的map中最终将这个map返回
    • 将map中的最新的两个base块中的核心成员取出并且记录在 peerStore 中peerStore 是sectionManager 中的全局变量,这些核心成员需要做一些验签之类的事情。
  6. 计算 多次从网络中收到的链 和 本地的链的权重这里同样计算了下一轮block的坐标和核心成员的坐标并且对核心成员的签名排序。计算权重的流程

    • 根据 所有核心成员的Sign的Seed 取hash得到下一轮下一区块的坐标coord
    • 再通过 核心成员的ID和下一区块的坐标coord ,求出每个核心成员的坐标;
    • 然后再求 每个核心成员与下一区块的坐标coord 的距离值;
      • 每个字符分开取异或因为pow这个系数一定会是越来越小而高位值能够使pow提前变得更小所以还是存在高位影响大于低位
    • 距离值越大,权重就越小。

    **问题:**Seed是怎么计算出来的

    所以就可以知道,为什么需要计算 下一轮block的坐标和核心成员的坐标 了。

  7. 合并数据

    • 首先遍历所有链的所有block将所有block按照轮次存储在 lists map[string][]*LightBlock中,如果每一轮存在 id 相同的block那么合并两个block的签名并且更新权重

    • 再利用链的连续性,找出每条链的叶节点,这里存在几个不同的叶节点,就有几条链可能分叉;

    • 用迭代的方式,从叶节点开始往前遍历,准备好需要比较的所有链的数据;

    • 再将上面准备好的链,以“与哪条链分叉”为基准进行分组

      • gid为1代表该链与第一条链分叉gid为0说明该链不分叉不分叉就可能是 平行或无重叠)
    • 通过循环,将每一组中的最优链选出

      • 优先比较:权重->链长度->验证人数->最新块的签名数
    • 获得每组的最优链后,再从中选出一条链:

      • 获得权重,取权重最大的
      • 计算平均权重,若权重是最大的,而平均权重不是最大,意味着该链只是块数量多,所以不足以证明该链是最优链,也许那条平均值最大的链才是,所以返回ErrNoMostConsChain错误。
    • 如何解决分叉?

      • 三种情况:
        • Y型分叉
        • 平行
        • 轮次不相交
      • 平行和轮次不相交的情况都可能需要获取全链的数据,来解决分叉
        • (全链数据太大的问题)处理方案:
          1. 冷热库分离,该方案需要超级节点来做
          2. 临时从本地取10个块若全网认证这10个块就不以这10个块为准不再需要获取全链数据
    • 若合并成功,正常获取了核心主链,先存入数据库,再返回数据;若收到ErrNoMostConsChain错误,需要重新从网络中获取最新全量块数据:

      • 先从本地获取20个块
      • 再通过requestBlockList函数从网络中获取以上20个块往后的 全链信息: 问题:对端接收到请求信号,发送的是全链信息,还是某一部分的数据? **回答:**若requestBlockList方法传入的第二个参数为空,或者全网找不到其中的任何一个基准块,那么可能收到的就是全链信息,只有上述两种情况是全链,其他都是部分链数据。 流程:
        1. 这里的获取流程和上边的流程很类似,不同的在于合并数据的函数CompareBlockList
        2. 不同之处在于,这里接收到的链的数据有一部分可能是整链数据
        3. 遍历链集合以链的头块来区分是否是整链将整链和非整链分别存储在不同的map中fromGenesisBlock fromGivenBlock
        4. 分别对整链和非整链数据,按照 权重->总长度->总验证次数 的优先级,取出最优链
        5. 若以上三个依据都相等,则调用compareTwoList函数进行比较
          • 首先确定两链的重叠轮次区间,以该区间为遍历范围
          • 在区间内遍历出不同ID的block表示出现分叉分别记录分叉区块block1 block2跳出遍历循环
          • 若记录分叉块的变量block1 block2有一个为空则意味着不存在分叉返回最长链
          • 若有分叉取出两链的权重长链的权重还要加上delta较长链多出的长度是短链的几倍作为权重的附加值参数
          • 比较优先级:权重 -> 签名数 -> 时间戳 -> 区块ID
        6. 若既有整链数据,又有非整链数据,同样使用compareTwoList函数进行截取比较
        7. 最终将比较的数据存入数据库,并返回比较的数据,注意:这里因为有全链数据的存在,所以一定能够确认最优链,和上边的requestLatestLightBlockList函数不一样。
  8. 合并数据后入库并使用链的数据更新全量block的数据

  9. 将最终的chain返回

getLocalCoreChainWithBlocks函数,若从网络中获取的为空,则调用这个函数从本地获取

  • 先从数据库中提取全链数据(lightBlock)
  • 再截取前20个block
  • 再通过lightBlock的ID获取全量块用来补全lightBlock的签名

接收到 EventRequestBlockList (获取全量块链)信号处理

接收到的entry中包含对方发送的基准块数据以及基准块数量。

**问题:**为什么要传入多个基准块,这里是否会产生冲突?

**答:**因为如果只传入一个基准块,接收者本地不存在该块的可能性就非常大,传入多个就增加了接收者找到相同基准块的可能性;接收该信号的一方不做冲突处理。

流程:
  1. 先解析基准块的长度和数据。若解析失败给信号发送者回复nil
  2. 为了使监听信号的Go程不阻塞启动Go程做数据库相关的工作
    • 通过getLocalCoreChainWithBlocks(0)函数获取主链数据若获取失败给信号发送者回复nil
      • 获取整链数据链中存储的是lightBlock
      • 若传入的参数count>0截取最新count个块
      • 再从数据库中取出每一个全量块并更新lightBlock中的签名
      • 最终返回取出的链数据
    • 再根据对端发送的基准块,来截取需要回复的块
      • 若对端发送的基准块数量为0那么需要回复 整条链的全量块 给对方
      • 若对端发送的基准块中没有一个在我方链中,同样需要回复 整条链的全量块
    • 找到需要回复的块,从数据库中取全量块,打包回复

账户比对

比较同一账户两个不同版本的关系

func (book *UTXOBook) RelationWith(b *UTXOBook) int8 {...}函数,返回 0相同或参数较短,1参数较长-1有冲突

  1. 若两book相同返回0
  2. 若book为空b不为空返回1
  3. 遍历b若已经检测到b存在book的最后一笔交易并且b中的交易book中没有返回1
  4. 遍历b若还未检测到b存在book的最后一笔交易就已经发现b中的交易book中没有返回-1表明存在冲突
  5. 若b中不存在book的最后一笔交易b中的交易book中全有返回0表明b的数据不足

注意这里之比较RID不会对validator进行比较即使返回0entry的验证人也会不一样。

合并同一账户的两份数据

func (book *UTXOBook) MergeBook(b *UTXOBook) (bool, bool) {...}函数

  1. 判断是否是同一账户的数据
  2. 找到分叉的起始点 func (book *UTXOBook) findForkEntry(entries []*UTXOEntry) (*UTXOEntry, bool)
    • 若 entries 存在 book 中不存在的交易,并且这时候还未检测到 entries中包含book的最后一笔交易则表明有冲突返回分叉点交易分叉前的第一笔交易
    • 若先检测到 entries 中包含book的最后一笔交易那么表明不存在冲突
    • 若发现冲突判断是否是第一笔交易就发现了冲突如果是第二个参数返回true第一个参数返回book的第一笔交易
  3. 若没有检测到冲突合并book和b func (book *UTXOBook) appendOrMergeEntries(entries []*UTXOEntry)
    • 遍历entries如果是已存在的entry就合并func (e *UTXOEntry) MergeEntry(entry *UTXOEntry) bool
      • 遍历validators合并验证人
        • 若没有该验证人,则添加
        • 若存在同一个验证人有两个不同的签名,取权重大的,权重一样取签名更早的func (e *UTXOEntry) AddValidator(v *Validator) bool
      • 若entry的validators有变动重新生成EID
    • 如果不存在,就往后添加,因为这里已经证明不存在冲突
    • 更新余额
  4. 若有冲突将b的分叉之前的交易(包括分叉点)存储在切片mergeList中,分叉后的交易(包括分叉点)存储在waitForMerge
  5. 合并分叉前的所有数据func (book *UTXOBook) appendOrMergeEntries(entries []*UTXOEntry)
  6. 若从第一条记录就开始不一样当前book在 (这里存在两个问题...

合并一组 同一账户的数据

func GroupMerge(books []*UTXOBook) *UTXOBook {...}函数

  1. reduced := make([]*UTXOBook, 0, len(books))存在这样一个数组存储最长book
  2. 遍历books和reduced
    1. 如果两个book返回的关系是 0合并两个book的 validators MergeBook
    2. 如果两个book返回的关系是 1合并并将更长链添加到 reduced 中,注意: 短链并没有移出,这里应该移出短链
    3. 若books中的元素与reduced中的任意一个元素都存在冲突没有一个r与b是不存在冲突的那么添加 b 到 reduced 中
  3. 最后将reduced中的元素都合并取最优链func (book *UTXOBook) MergeBook(b *UTXOBook) (bool, bool)

validatorentrybook 的权重计算

  • book的权重累加所有entry的权重

  • entry的权重累加validators在当前交易的权重

  • validator 的权重计算 func (worker *PoWv1) GetWeight(userID, transactionID, pow []byte) uint64**注意:**这里的pow是验证人计算出pow时随机数据的hash值这个hash值得右边0的个数大于等于难度值才能够算挖矿成功

    • 通过 UserID 和 transactionID 计算相似度 func computeSimilarity(userID, transactionID []byte) uint64

      • 将 userID 和 transactionID 拼接然后计算出拼接后的hash

      • 计算hash和transactionID的异或距离该距离值就为pow的难度值 func ComputeXORBitSimilarity(data1, data2 []byte) uint64

        • 获取data1 和 data2的最小length长了的取后边段的data

        • 进行运算,获得异或后的[]byte func XOR(a, b []byte) []byte

          func XOR(a, b []byte) []byte {
          	c := make([]byte, len(a))
          	for i := 0; i < len(a); i++ {
          		c[i] = a[i] ^ b[i]
          	}
          	return c
          }
          
        • 最后计算该切片的左边有几个0返回0的个数

    • 通过相似度计算难度值相似度越高难度值越小难度值最大为16目前测试用

  • 再次计算pow数据的hash值比较末尾0的个数若小于难度值则代表该验证人并未成功计算出pow权重值为0

  • 最后返回权重 return uint64(1) + uint64(similarity - SimilarityThreshold) + (simi - difficulty) * uint64(2)

    • 相似度越高,权重值越大
    • pow计算出的结果 0 的数量越大,权重值越高

获取node

在bitswap里获取不了node而是使用handle接口调用core.go中实现的方法

是如何控制频繁接收同一消息的?数据一样,发送时间不同算两个不同的消息吗?

需要捋清楚的模块

签名

被签名的数据,签名数据,公私钥对

签名和验签都要调用 获取被签名的数据 的方法;