13 KiB
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
:处理本地数据和网络数据的函数
-
先注销 callback2 相关的 EventLoop 事件;
-
从本地提取最新20个块组成的链,每一个区块都包含当轮次的核心组所有成员;
- 先从数据库中提取全链数据(lightBlock)
- 再截取前20个block
- 再通过lightBlock的ID获取全量块,用来补全lightBlock的签名
-
将网络获取的chain和本地的chain数据中的所有 base块 和 核心成员都缓存到map中,再将 base块的ID和核心成员的ID存储在切片中
baseBlocks
validators
; -
两线程中的公钥线程:
-
获取所有核心成员的公钥;
-
遍历blList中的每条chain,利用获取的公钥验证,将验证通过的append到 list 中,最终将blList更新为 list ;
-
-
两线程中的base块线程:
- 通过前边记录的base块ID,将其中所有的base块取出,先从本地获取,本地没有从网络中获取,存储在 key为blockId value为block 的map中,最终将这个map返回;
- 将map中的最新的两个base块中的核心成员取出,并且记录在 peerStore 中,peerStore 是sectionManager 中的全局变量,这些核心成员需要做一些验签之类的事情。
-
计算 多次从网络中收到的链 和 本地的链的权重,这里同样计算了下一轮block的坐标和核心成员的坐标,并且对核心成员的签名排序。计算权重的流程:
- 根据 所有核心成员的Sign的Seed 取hash,得到下一轮(下一区块)的坐标coord;
- 再通过 核心成员的ID和下一区块的坐标coord ,求出每个核心成员的坐标;
- 然后再求 每个核心成员与下一区块的坐标coord 的距离值;
- 每个字符分开取异或,因为pow这个系数一定会是越来越小,而高位值能够使pow提前变得更小,所以还是存在高位影响大于低位
- 距离值越大,权重就越小。
**问题:**Seed是怎么计算出来的?
所以就可以知道,为什么需要计算 下一轮block的坐标和核心成员的坐标 了。
-
合并数据
-
首先,遍历所有链的所有block,将所有block按照轮次存储在 lists
map[string][]*LightBlock
中,如果每一轮存在 id 相同的block,那么合并两个block的签名,并且更新权重; -
再利用链的连续性,找出每条链的叶节点,这里存在几个不同的叶节点,就有几条链可能分叉;
-
用迭代的方式,从叶节点开始往前遍历,准备好需要比较的所有链的数据;
-
再将上面准备好的链,以“与哪条链分叉”为基准进行分组
- gid为1,代表该链与第一条链分叉,gid为0,说明该链不分叉(不分叉就可能是 平行或无重叠)
-
通过循环,将每一组中的最优链选出
- 优先比较:权重->链长度->验证人数->最新块的签名数
-
获得每组的最优链后,再从中选出一条链:
- 获得权重,取权重最大的
- 计算平均权重,若权重是最大的,而平均权重不是最大,意味着该链只是块数量多,所以不足以证明该链是最优链,也许那条平均值最大的链才是,所以返回
ErrNoMostConsChain
错误。
-
如何解决分叉?
- 三种情况:
- Y型分叉
- 平行
- 轮次不相交
- 平行和轮次不相交的情况都可能需要获取全链的数据,来解决分叉
- (全链数据太大的问题)处理方案:
- 冷热库分离,该方案需要超级节点来做
- 临时从本地取10个块,若全网认证这10个块,就不以这10个块为准,不再需要获取全链数据
- (全链数据太大的问题)处理方案:
- 三种情况:
-
若合并成功,正常获取了核心主链,先存入数据库,再返回数据;若收到
ErrNoMostConsChain
错误,需要重新从网络中获取最新全量块数据:- 先从本地获取20个块;
- 再通过
requestBlockList
函数,从网络中获取以上20个块往后的 全链信息: 问题:对端接收到请求信号,发送的是全链信息,还是某一部分的数据? **回答:**若requestBlockList
方法传入的第二个参数为空,或者全网找不到其中的任何一个基准块,那么可能收到的就是全链信息,只有上述两种情况是全链,其他都是部分链数据。 流程:- 这里的获取流程和上边的流程很类似,不同的在于合并数据的函数
CompareBlockList
- 不同之处在于,这里接收到的链的数据有一部分可能是整链数据
- 遍历链集合,以链的头块来区分是否是整链,将整链和非整链分别存储在不同的map中
fromGenesisBlock
fromGivenBlock
- 分别对整链和非整链数据,按照 权重->总长度->总验证次数 的优先级,取出最优链
- 若以上三个依据都相等,则调用
compareTwoList
函数进行比较- 首先确定两链的重叠轮次区间,以该区间为遍历范围
- 在区间内,遍历出不同ID的block,表示出现分叉,分别记录分叉区块(block1 block2),跳出遍历循环
- 若记录分叉块的变量block1 block2有一个为空,则意味着不存在分叉,返回最长链
- 若有分叉,取出两链的权重,长链的权重还要加上delta(较长链多出的长度是短链的几倍,作为权重的附加值)参数,
- 比较优先级:权重 -> 签名数 -> 时间戳 -> 区块ID
- 若既有整链数据,又有非整链数据,同样使用
compareTwoList
函数进行截取比较 - 最终将比较的数据存入数据库,并返回比较的数据,注意:这里因为有全链数据的存在,所以一定能够确认最优链,和上边的
requestLatestLightBlockList
函数不一样。
- 这里的获取流程和上边的流程很类似,不同的在于合并数据的函数
-
-
合并数据后,入库并使用链的数据更新全量block的数据
-
将最终的chain返回
getLocalCoreChainWithBlocks
函数,若从网络中获取的为空,则调用这个函数从本地获取
- 先从数据库中提取全链数据(lightBlock)
- 再截取前20个block
- 再通过lightBlock的ID获取全量块,用来补全lightBlock的签名
接收到 EventRequestBlockList (获取全量块链)信号处理
接收到的entry中,包含对方发送的基准块数据,以及基准块数量。
**问题:**为什么要传入多个基准块,这里是否会产生冲突?
**答:**因为如果只传入一个基准块,接收者本地不存在该块的可能性就非常大,传入多个就增加了接收者找到相同基准块的可能性;接收该信号的一方不做冲突处理。
流程:
- 先解析基准块的长度,和数据。若解析失败,给信号发送者回复nil;
- 为了使监听信号的Go程不阻塞,启动Go程做数据库相关的工作:
- 通过
getLocalCoreChainWithBlocks(0)
函数获取主链数据,若获取失败给信号发送者回复nil;- 获取整链数据,链中存储的是lightBlock
- 若传入的参数count>0,截取最新count个块
- 再从数据库中取出每一个全量块,并更新lightBlock中的签名
- 最终返回取出的链数据
- 再根据对端发送的基准块,来截取需要回复的块
- 若对端发送的基准块数量为0,那么需要回复 整条链的全量块 给对方
- 若对端发送的基准块中没有一个在我方链中,同样需要回复 整条链的全量块
- 找到需要回复的块,从数据库中取全量块,打包回复
- 通过
账户比对
比较同一账户两个不同版本的关系
func (book *UTXOBook) RelationWith(b *UTXOBook) int8 {...}
函数,返回 0(相同或参数较短),1(参数较长),-1(有冲突)
- 若两book相同,返回0
- 若book为空,b不为空,返回1
- 遍历b,若已经检测到b存在book的最后一笔交易,并且b中的交易book中没有,返回1
- 遍历b,若还未检测到b存在book的最后一笔交易,就已经发现b中的交易book中没有,返回-1,表明存在冲突
- 若b中不存在book的最后一笔交易,b中的交易book中全有,返回0,表明b的数据不足
注意:这里之比较RID,不会对validator进行比较,即使返回0,entry的验证人也会不一样。
合并同一账户的两份数据
func (book *UTXOBook) MergeBook(b *UTXOBook) (bool, bool) {...}
函数
- 判断是否是同一账户的数据
- 找到分叉的起始点
func (book *UTXOBook) findForkEntry(entries []*UTXOEntry) (*UTXOEntry, bool)
- 若 entries 存在 book 中不存在的交易,并且这时候还未检测到 entries中包含book的最后一笔交易,则表明有冲突,返回分叉点交易(分叉前的第一笔交易)
- 若先检测到 entries 中包含book的最后一笔交易,那么表明不存在冲突
- 若发现冲突,判断是否是第一笔交易就发现了冲突,如果是,第二个参数返回true,第一个参数返回book的第一笔交易
- 若没有检测到冲突,合并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
- 遍历validators,合并验证人
- 如果不存在,就往后添加,因为这里已经证明不存在冲突
- 更新余额
- 遍历entries,如果是已存在的entry就合并
- 若有冲突,将b的分叉之前的交易(包括分叉点)存储在切片
mergeList
中,分叉后的交易(包括分叉点)存储在waitForMerge
中 - 合并分叉前的所有数据
func (book *UTXOBook) appendOrMergeEntries(entries []*UTXOEntry)
- 若从第一条记录就开始不一样,当前book在 (这里存在两个问题...)
合并一组 同一账户的数据
func GroupMerge(books []*UTXOBook) *UTXOBook {...}
函数
reduced := make([]*UTXOBook, 0, len(books))
存在这样一个数组,存储最长book- 遍历books和reduced
- 如果两个book返回的关系是 0,合并两个book的 validators
MergeBook
- 如果两个book返回的关系是 1,合并,并将更长链添加到 reduced 中,注意: 短链并没有移出,这里应该移出短链
- 若books中的元素与reduced中的任意一个元素都存在冲突(没有一个r与b是不存在冲突的),那么添加 b 到 reduced 中
- 如果两个book返回的关系是 0,合并两个book的 validators
- 最后将reduced中的元素都合并,取最优链
func (book *UTXOBook) MergeBook(b *UTXOBook) (bool, bool)
validator,entry,book 的权重计算
-
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中实现的方法
是如何控制频繁接收同一消息的?数据一样,发送时间不同算两个不同的消息吗?
需要捋清楚的模块
签名
被签名的数据,签名数据,公私钥对
签名和验签都要调用 获取被签名的数据 的方法;