LLLeon / Blog

LLLeon 的部落格
15 stars 4 forks source link

Tendermint 中验证人节点的添加 #6

Open LLLeon opened 6 years ago

LLLeon commented 6 years ago

添加验证人的两种方式

添加 Tendermint 验证人有两种方式:

前面的文档已经对第一种方式进行了说明,这里讨论第二种方式,即如何在运行中的 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 应用的 BeginBlockDeliverTxEndBlock 方法。

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)))
}

References