Tendermint 中验证人节点的添加
添加验证人的两种方式
添加 Tendermint 验证人有两种方式:
- 在启动 Tendermint 网络前,在
genesis.json中进行操作。可以创建一个新的priv_validator.json文件,然后把里面的pub_key拷贝到genesis.json文件中。 - 在一个运行中的 Tendermint 网络中,通过 ABCI 应用的
EndBlock方法添加验证人。
前面的文档已经对第一种方式进行了说明,这里讨论第二种方式,即如何在运行中的 Tendermint 网络中添加验证人节点。
启动 Tendermint 前添加
最简单的方式是在启动 Tendermint 网络前,在 genesis.json 中进行操作。可以创建一个新的 priv_validator.json 文件,然后把里面的 pub_key 拷贝到 genesis.json 文件中。
执行这条命令来生成 priv_validator.json:
tendermint gen_validator
现在可以更新 genesis 文件了。比如新的 priv_validator.json 长这样:
{
"address" : "5AF49D2A2D4F5AD4C7C8C4CC2FB020131E9C4902",
"pub_key" : {
"value" : "l9X9+fjkeBzDfPGbUM7AMIRE6uJN78zN5+lk5OYotek=",
"type" : "AC26791624DE60"
},
"priv_key" : {
"value" : "EDJY9W6zlAw+su6ITgTKg2nTZcHAH1NMTW5iwlgmNDuX1f35+OR4HMN88ZtQzsAwhETq4k3vzM3n6WTk5ii16Q==",
"type" : "954568A3288910"
},
"last_step" : 0,
"last_round" : 0,
"last_height" : 0
}
然后新的 genesis.json 将长这样:
{
"validators" : [
{
"pub_key" : {
"value" : "h3hk+QE8c6QLTySp8TcfzclJw/BG79ziGB/pIA+DfPE=",
"type" : "AC26791624DE60"
},
"power" : 10,
"name" : ""
},
{
"pub_key" : {
"value" : "l9X9+fjkeBzDfPGbUM7AMIRE6uJN78zN5+lk5OYotek=",
"type" : "AC26791624DE60"
},
"power" : 10,
"name" : ""
}
],
"app_hash" : "",
"chain_id" : "test-chain-rDlYSN",
"genesis_time" : "0001-01-01T00:00:00Z"
}
更新本机 ~/.tendermint/config 目录中的 genesis.json。把 genesis 文件和新的 priv_validator.json 拷贝到新机器上的 ~/.tendermint/config 目录中。
现在在所有机器上执行 tendermint node,使用 --p2p.persistent_peers 或 /dial_peers 来让它们互为 peer。他们应该开始生成区块,只要他们都在线就会继续生成区块。
要让 Tendermint 网络可以容忍其中一个验证人失败,至少需要四个验证人节点(> 2/3)。
启动 Tendermint 后添加
EndBlock 方法的定义
EndBlock 是 abci 中 Application 接口中定义的一个方法:
type Application interface {
// Info/Query Connection
Info(RequestInfo) ResponseInfo // Return application info
SetOption(RequestSetOption) ResponseSetOption // Set application option
Query(RequestQuery) ResponseQuery // Query for state
// Mempool Connection
CheckTx(tx []byte) ResponseCheckTx // Validate a tx for the mempool
// Consensus Connection
InitChain(RequestInitChain) ResponseInitChain // Initialize blockchain with validators and other info from TendermintCore
BeginBlock(RequestBeginBlock) ResponseBeginBlock // Signals the beginning of a block
DeliverTx(tx []byte) ResponseDeliverTx // Deliver a tx for full processing
EndBlock(RequestEndBlock) ResponseEndBlock // Signals the end of a block, returns changes to the validator set
Commit() ResponseCommit // Commit the state and return the application Merkle root hash
}
它可以用来在每个区块结束时运行一些代码,此外,应答可以包含验证人列表,可以用来更新验证人集合。要添加新验证人或更新现有验证人,只需将它们包括在 EndBlock 应答返回的列表中即可。要移除一个验证人,将其 power 设为 0 并放入此列表。Tendermint 将负责更新验证人集合。
注意,如果希望轻客户端能够从外部证明状态的转换,则投票权重的变化必须严格小于每个区块的 1/3。参考 这篇文档 来查看它如何追踪验证人。
Tendermint 源码中如何进行验证人的更新
相关代码在 tendermint/state/execution.go 第 315 行。
执行 ApplyBlock 方法时会执行 updateState 方法,它会根据执行 execBlockOnProxyApp 返回的应答来更新状态:
// update the validator set with the latest abciResponses
lastHeightValsChanged := state.LastHeightValidatorsChanged
if len(abciResponses.EndBlock.ValidatorUpdates) > 0 {
err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates)
if err != nil {
return state, fmt.Errorf("Error changing validator set: %v", err)
}
// change results from this height but only applies to the next height
lastHeightValsChanged = header.Height + 1
}
execBlockOnProxyApp 方法内部会调用开发者定义的 ABCI 应用的 BeginBlock、DeliverTx 和 EndBlock 方法。
ABCI 应用中如何实现验证人的更新
处理逻辑就是在由客户端向 ABCI 应用提交交易时,在 DeliverTx 方法中进行验证人的更新。当 Tendermint 在 ApplyBlock 方法中应用区块时,会调用此方法。
这里更新验证人的交易格式为 val:pubkey/power,看一下它的具体实现:
// tx is either "val:pubkey/power" or "key=value" or just arbitrary bytes
func (app *PersistentKVStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {
// if it starts with "val:", update the validator set
// format is "val:pubkey/power"
if isValidatorTx(tx) {
// update validators in the merkle tree
// and in app.ValUpdates
return app.execValidatorTx(tx)
}
// otherwise, update the key-value store
return app.app.DeliverTx(tx)
}
execValidatorTx 方法会在数据库及 app 的 ValUpdates 字段中更新验证人:
func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.ResponseDeliverTx {
tx = tx[len(ValidatorSetChangePrefix):]
//get the pubkey and power
pubKeyAndPower := strings.Split(string(tx), "/")
if len(pubKeyAndPower) != 2 {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Expected 'pubkey/power'. Got %v", pubKeyAndPower)}
}
pubkeyS, powerS := pubKeyAndPower[0], pubKeyAndPower[1]
// decode the pubkey
pubkey, err := hex.DecodeString(pubkeyS)
if err != nil {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Pubkey (%s) is invalid hex", pubkeyS)}
}
// decode the power
power, err := strconv.ParseInt(powerS, 10, 64)
if err != nil {
return types.ResponseDeliverTx{
Code: code.CodeTypeEncodingError,
Log: fmt.Sprintf("Power (%s) is not an int", powerS)}
}
// update
return app.updateValidator(types.Ed25519Validator(pubkey, int64(power)))
}