cosmos主网即将上线,对文档做了大量更新。特地翻译了一下,方便小伙伴们阅览, 之后会持续更新
第四章客户端:
规范
该规范描述了如何实现LCD。LCD支持模块化API。目前,还只支持ICS0(Tendermint API),ICS1(Key API)和ICS20(Token API)。以后,如有需要的话可以包含更多API。
构建并验证ABCI状态证明
众所周知,基于cosmos-sdk的应用程序的存储包含多个子存储。每个子存储由IAVL存储实现。这些子存储由简单的Merkle树组织。要构建树,我们需要从这些子存储中提取名称,高度和存储根哈希以构建一组简易的Merkle叶节点,然后计算从叶节点到根的哈希。简易Merkle树的根哈希是AppHash,它将包含在区块头中。
正如我们在LCD信任传播中所讨论的,可以通过检查可信验证人集合的投票权来验证AppHash。在这里,我们只需要建立从ABCI状态到AppHash的证明。证明包含两部分:
- IAVL证明
- 针对AppHash的子存储的证明
IAVL证明
证明有两种类型:存在证明和不存在证明。如果要查询键值是否存在于IAVL存储中,则它返回键值及其存在证明。另一方面,如果键值不存在,那么它只返回不存在证明,这可以证明键值肯定不存在。
IAVL存在证明
type CommitID struct {
Version int64
Hash []byte
}
type storeCore struct {
CommitID CommitID
}
type MultiStoreCommitID struct {
Name string
Core storeCore
}
type proofInnerNode struct {
Height int8
Size int64
Version int64
Left []byte
Right []byte
}
type KeyExistsProof struct {
MultiStoreCommitInfo []MultiStoreCommitID //All substore commitIDs
StoreName string //Current substore name
Height int64 //The commit height of current substore
RootHash cmn.HexBytes //The root hash of this IAVL tree
Version int64 //The version of the key-value in this IAVL tree
InnerNodes []proofInnerNode //The path from to root node to key-value leaf node
}
存在证据的数据结构如上所示。构建和验证存在证明的过程如下所示:
构建证明的步骤:
- 从根节点访问IAVL树。
- 记录InnerNodes中的访问节点,
- 找到目标叶子节点后,将叶子节点的版本赋值给证明版本
- 将当前IAVL树高度赋值给证明高度
- 将当前IAVL树rootHash赋值给证明rootHash
- 将当前的子存储的名称赋值给证明的子存储
- 从db按高度读取multistore的commitInfo并将其赋值给证明StoreCommitInfo
验证证明的步骤:
- 使用键,值和证明版本构建叶子节点。
- 计算叶子节点哈希
- 将hash值分配给第一个innerNode的rightHash,然后计算第一个innerNode的hash值
- 传播哈希计算过程。如果先前的innerNode是下一个innerNode的左子节点,则将先前的innerNode的hash分配给下一个innerNode的leftHash。否则,将先前的innerNode的hash值分配给下一个innerNode的rightHash。
- 最后一个innerNode的hash值应该等于此证明的rootHash。否则,证明无效。
IAVL不存在证明
众所周知,所有IAVL叶子节点都按每个叶子节点的key排序。因此,我们可以计算此IAVL树的整个key集合中目标key的位置。如下图所示,我们可以找到左key和右key。如果我们可以证明左key和右key肯定存在,并且它们是相邻的节点。因此目标key肯定不存在。
如果目标key大于最右边的叶子节点的或小于最左边节点的key,则目标key肯定不存在。
type proofLeafNode struct {
KeyBytes cmn.HexBytes
ValueBytes cmn.HexBytes
Version int64
}
type pathWithNode struct {
InnerNodes []proofInnerNode
Node proofLeafNode
}
type KeyAbsentProof struct {
MultiStoreCommitInfo []MultiStoreCommitID
StoreName string
Height int64
RootHash cmn.HexBytes
Left *pathWithNode // Proof the left key exist
Right *pathWithNode //Proof the right key exist
}
以上是不存在证明的数据结构。构建证明的步骤:
- 从根节点访问IAVL树。
- 获取在整个key集合中key的索引(标记为INDEX)。
- 如果返回的索引等于0,则右索引应为0且左节点不存在
- 如果返回的索引等于整个key集合的大小,则左节点索引应为INDEX-1且右节点不存在。
- 否则,右节点索引应为INDEX,左节点索引应为INDEX-1
- 将当前IAVL树高度赋值给证明高度
- 将当前IAVL树rootHash赋值给证明rootHash
- 将当前的子存储的名称赋值给证明的StoreName
- 从db按高度multistore的commitInfo并将其分配给证明的StoreCommitInfo
验证证明的步骤:
- 如果只存在正确的节点,请验证其存在的证明并验证它是否是最左侧的节点
- 如果仅存在左节点,请验证其存在的证明并验证它是否是最正确的节点。
- 如果右节点和左节点都存在,请验证它们是否相邻。
针对AppHash的子存储的证明
在验证了IAVL证明之后,我们就可以开始验证针对AppHash的子证明。首先,遍历MultiStoreCommitInfo并通过证明StoreName找到子存储的commitID。验证commitID中的哈希是否等于证明的RootHash。如果没有,证明无效。然后通过子存储的name的哈希对子存储的commitInfo数组进行排序。最后,使用所有子存储的commitInfo数组构建简单的Merkle树,并验证Merkle根哈希值是否等于appHash。
func SimpleHashFromTwoHashes(left []byte, right []byte) []byte {
var hasher = ripemd160.New()
err := encodeByteSlice(hasher, left)
if err != nil {
panic(err)
}
err = encodeByteSlice(hasher, right)
if err != nil {
panic(err)
}
return hasher.Sum(nil)
}
func SimpleHashFromHashes(hashes [][]byte) []byte {
// Recursive impl.
switch len(hashes) {
case 0:
return nil
case 1:
return hashes[0]
default:
left := SimpleHashFromHashes(hashes[:(len(hashes)+1)/2])
right := SimpleHashFromHashes(hashes[(len(hashes)+1)/2:])
return SimpleHashFromTwoHashes(left, right)
}
}
验证验证人集合的区块头
以上部分经常引用appHash。但值得信赖的appHash来自哪里?实际上,appHash存在于块头中,因此接下来我们需要验证特定高度的LCD可信验证人集合的区块头。验证流程如下所示:
当可信验证人集合与区块头不匹配时,我们需要尝试将可信验证人集合更新为此高度区块的验证人集合。LCD有一个规则,即每个验证人集合的变化不应超过1/3投票权。如果目标验证人集合的投票权变化超过1/3,则与可信验证人集合进行比较。我们必须验证在目标验证人集合之前是否存在隐藏的验证人集合变动。只有当所有验证人集合变更都遵循此规则时,才能完成验证人集合的更新。
例如:
- 更新至10000, tooMuchChangeErr
- 更新至5050, tooMuchChangeErr
- 更新至2575, Success
- 更新至5050, Success
- 更新至10000,tooMuchChangeErr
- 更新至7525, Success
- 更新至10000, Success