我们知道,在启动一个节点前,需要执行tendermint init
完成相关系统文件的生成,具体如何处理的我们查看下InitFilesCmd
查看源代码,发现本部分功能比较长
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
privValKeyFile := config.PrivValidatorKeyFile()
privValStateFile := config.PrivValidatorStateFile()
var pv *privval.FilePV
if tmos.FileExists(privValKeyFile) {
pv = privval.LoadFilePV(privValKeyFile, privValStateFile)
logger.Info("Found private validator", "keyFile", privValKeyFile,
"stateFile", privValStateFile)
} else {
pv = privval.GenFilePV(privValKeyFile, privValStateFile)
pv.Save()
logger.Info("Generated private validator", "keyFile", privValKeyFile,
"stateFile", privValStateFile)
}
nodeKeyFile := config.NodeKeyFile()
if tmos.FileExists(nodeKeyFile) {
logger.Info("Found node key", "path", nodeKeyFile)
} else {
if _, err := p2p.LoadOrGenNodeKey(nodeKeyFile); err != nil {
return err
}
logger.Info("Generated node key", "path", nodeKeyFile)
}
// genesis file
genFile := config.GenesisFile()
if tmos.FileExists(genFile) {
logger.Info("Found genesis file", "path", genFile)
} else {
genDoc := types.GenesisDoc{
ChainID: fmt.Sprintf("test-chain-%v", tmrand.Str(6)),
GenesisTime: tmtime.Now(),
ConsensusParams: types.DefaultConsensusParams(),
}
key := pv.GetPubKey()
genDoc.Validators = []types.GenesisValidator{{
Address: key.Address(),
PubKey: key,
Power: 10,
}}
if err := genDoc.SaveAs(genFile); err != nil {
return err
}
logger.Info("Generated genesis file", "path", genFile)
}
return nil
|
我们一步一步进行分析。
初始化分析
配置文件
初始化首先默认获取验证节点的秘钥配置文件和状态文件。
查看默认的配置文件,可以发现文件的默认位置按照如下规则生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
defaultConfigDir = "config"
...
defaultPrivValKeyName = "priv_validator_key.json"
defaultPrivValStateName = "priv_validator_state.json"
...
defaultPrivValKeyPath = filepath.Join(defaultConfigDir, defaultPrivValKeyName)
defaultPrivValStatePath = filepath.Join(defaultDataDir, defaultPrivValStateName)
...
rootify(cfg.PrivValidatorKey, cfg.RootDir)
...
func rootify(path, root string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(root, path)
}
|
上文已经分析,绝对路径root
在系统启动前已经配置了,是读取的HOME
中的设置,如果我们没有设置,则路径就是.tendermint
,所以获取的文件路径是.tendermint/config/priv_validator_key.json
和.tendermint/config/priv_validator_state.json
。如果文件存在,则读取文件内容,否则生成。
FilePV
一个默认验证者的信息、状态是通过FilePV
进行持久化存储的,具体的定义格式如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
type FilePV struct {
Key FilePVKey
LastSignState FilePVLastSignState
}
type FilePVKey struct {
Address types.Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
PrivKey crypto.PrivKey `json:"priv_key"`
filePath string
}
type FilePVLastSignState struct {
Height int64 `json:"height"`
Round int `json:"round"`
Step int8 `json:"step"`
Signature []byte `json:"signature,omitempty"`
SignBytes tmbytes.HexBytes `json:"signbytes,omitempty"`
filePath string
}
|
验证者信息包括验证者地址、公私钥,验证状态包括当前验证的高度、进行PBFT的轮次和所处的位置、当前进行的签名。
读取过程如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV {
keyJSONBytes, err := ioutil.ReadFile(keyFilePath)
if err != nil {
tmos.Exit(err.Error())
}
pvKey := FilePVKey{}
err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey)
if err != nil {
tmos.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err))
}
// overwrite pubkey and address for convenience
pvKey.PubKey = pvKey.PrivKey.PubKey()
pvKey.Address = pvKey.PubKey.Address()
pvKey.filePath = keyFilePath
pvState := FilePVLastSignState{}
if loadState {
stateJSONBytes, err := ioutil.ReadFile(stateFilePath)
if err != nil {
tmos.Exit(err.Error())
}
err = cdc.UnmarshalJSON(stateJSONBytes, &pvState)
if err != nil {
tmos.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err))
}
}
pvState.filePath = stateFilePath
return &FilePV{
Key: pvKey,
LastSignState: pvState,
}
}
|
本流程实际比较简单,直接读取文件,反序列化到结构体中即可。我们在读取FilePV
时有一个假设,即节点的公私钥信息存在,则默认节点的状态信息也存在。而当启动时节点信息不存在的情况下,需要生成节点信息和相应的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func GenFilePV(keyFilePath, stateFilePath string) *FilePV {
privKey := ed25519.GenPrivKey()
return &FilePV{
Key: FilePVKey{
Address: privKey.PubKey().Address(),
PubKey: privKey.PubKey(),
PrivKey: privKey,
filePath: keyFilePath,
},
LastSignState: FilePVLastSignState{
Step: stepNone,
filePath: stateFilePath,
},
}
}
|
在此处我们发现生成时并不对直接生成文件,而是在上层显示调用Save
函数保存的。
由于保存文件对于启动过程影响并不大,而且保存的代码比较长,我们暂时忽略。
到目前为止,验证者的信息和状态已经获取完毕。
NodeKey
读取完验证者的相关信息后,接下来是节点的信息
1
2
3
|
type NodeKey struct {
PrivKey crypto.PrivKey `json:"priv_key"` // our priv key
} |
其读取方式以及生成很简单,和验证者的处理方式一致,此处不再赘述。
但是,此处有一点问题,在上层调用时显示判断了一次文件是否存在,当文件不存在时,程序调用的方式为LoadOrGenNodeKey
,即又判断了一次,私认为此处有一些重复代码。
此外,还有一个需要注意的,在这个地方,node的私钥信息是和privval的私钥信息是不一致的。在次数是为什么呢?
通常一个节点,可以对应一个验证者身份,节点用私钥可以标识自己的唯一身份,所以用验证者的私钥生成地址即可,为何要再单独生成呢?
我们带着问题往后面看吧。
Genesis
生成了验证者身份和节点身份后,初始化还需要创建创世区块。
创世区块的格式如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
type GenesisDoc struct {
GenesisTime time.Time `json:"genesis_time"`
ChainID string `json:"chain_id"`
ConsensusParams *ConsensusParams `json:"consensus_params,omitempty"`
Validators []GenesisValidator `json:"validators,omitempty"`
AppHash tmbytes.HexBytes `json:"app_hash"`
AppState json.RawMessage `json:"app_state,omitempty"`
}
type ConsensusParams struct {
Block BlockParams `json:"block"` //定义生成区块的参数,包括区块最大大小,区块最大Gas以及区块最小生成间隔
Evidence EvidenceParams `json:"evidence"` // tendermint支持对错误交易的举证,此处指允许证据距离如今最长的时间
Validator ValidatorParams `json:"validator"` //指的是验证者的地址类型,主要包括ed25519,sr25519,secp256k1三种类型
}
type GenesisValidator struct {
Address Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
Power int64 `json:"power"`
Name string `json:"name"`
}
|
可以得知,生成的创世区块内容比较简单,包括整个链的配置和验证者,此外没有其他的信息了。然而,创世区块中的AppHash
和AppState
指的是什么呢?
我们后续继续探索。
结束
到此为止,一个节点的初始化就算结束了,而且创世过程也比较简单,就是尝试读取已有的文件,没有则创建新的,生成完毕创立区块链结束。