mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-04 02:52:07 +00:00
This pull requests adds the protocol buffer field for the `ABCI.VoteExtensionsEnableHeight` parameter. This proto field is threaded throughout all of the relevant places where consensus params are used and referenced. This PR also adds validation of the consensus param updates. Previous consensus param changes didn't depend on _previous_ versions of the params, so this change adds a method for validating against the old params as well. closes: #8453
339 lines
10 KiB
Go
339 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
|
|
"github.com/tendermint/tendermint/config"
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
"github.com/tendermint/tendermint/privval"
|
|
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
|
"github.com/tendermint/tendermint/test/e2e/pkg/infra"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
const (
|
|
AppAddressTCP = "tcp://127.0.0.1:30000"
|
|
AppAddressUNIX = "unix:///var/run/app.sock"
|
|
|
|
PrivvalAddressTCP = "tcp://0.0.0.0:27559"
|
|
PrivvalAddressUNIX = "unix:///var/run/privval.sock"
|
|
PrivvalKeyFile = "config/priv_validator_key.json"
|
|
PrivvalStateFile = "data/priv_validator_state.json"
|
|
PrivvalDummyKeyFile = "config/dummy_validator_key.json"
|
|
PrivvalDummyStateFile = "data/dummy_validator_state.json"
|
|
)
|
|
|
|
// Setup sets up the testnet configuration.
|
|
func Setup(testnet *e2e.Testnet, infp infra.Provider) error {
|
|
logger.Info("setup", "msg", log.NewLazySprintf("Generating testnet files in %q", testnet.Dir))
|
|
|
|
err := os.MkdirAll(testnet.Dir, os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = infp.Setup()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
genesis, err := MakeGenesis(testnet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, node := range testnet.Nodes {
|
|
nodeDir := filepath.Join(testnet.Dir, node.Name)
|
|
|
|
dirs := []string{
|
|
filepath.Join(nodeDir, "config"),
|
|
filepath.Join(nodeDir, "data"),
|
|
filepath.Join(nodeDir, "data", "app"),
|
|
}
|
|
for _, dir := range dirs {
|
|
// light clients don't need an app directory
|
|
if node.Mode == e2e.ModeLight && strings.Contains(dir, "app") {
|
|
continue
|
|
}
|
|
err := os.MkdirAll(dir, 0o755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cfg, err := MakeConfig(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics
|
|
|
|
appCfg, err := MakeAppConfig(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = os.WriteFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg, 0o644) //nolint:gosec
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if node.Mode == e2e.ModeLight {
|
|
// stop early if a light client
|
|
continue
|
|
}
|
|
|
|
err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
(privval.NewFilePV(node.PrivvalKey,
|
|
filepath.Join(nodeDir, PrivvalKeyFile),
|
|
filepath.Join(nodeDir, PrivvalStateFile),
|
|
)).Save()
|
|
|
|
// Set up a dummy validator. Tendermint requires a file PV even when not used, so we
|
|
// give it a dummy such that it will fail if it actually tries to use it.
|
|
(privval.NewFilePV(ed25519.GenPrivKey(),
|
|
filepath.Join(nodeDir, PrivvalDummyKeyFile),
|
|
filepath.Join(nodeDir, PrivvalDummyStateFile),
|
|
)).Save()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MakeGenesis generates a genesis document.
|
|
func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
|
|
genesis := types.GenesisDoc{
|
|
GenesisTime: time.Now(),
|
|
ChainID: testnet.Name,
|
|
ConsensusParams: types.DefaultConsensusParams(),
|
|
InitialHeight: testnet.InitialHeight,
|
|
}
|
|
// set the app version to 1
|
|
genesis.ConsensusParams.Version.App = 1
|
|
genesis.ConsensusParams.Evidence.MaxAgeNumBlocks = e2e.EvidenceAgeHeight
|
|
genesis.ConsensusParams.Evidence.MaxAgeDuration = e2e.EvidenceAgeTime
|
|
genesis.ConsensusParams.ABCI.VoteExtensionsEnableHeight = testnet.VoteExtensionsEnableHeight
|
|
for validator, power := range testnet.Validators {
|
|
genesis.Validators = append(genesis.Validators, types.GenesisValidator{
|
|
Name: validator.Name,
|
|
Address: validator.PrivvalKey.PubKey().Address(),
|
|
PubKey: validator.PrivvalKey.PubKey(),
|
|
Power: power,
|
|
})
|
|
}
|
|
// The validator set will be sorted internally by Tendermint ranked by power,
|
|
// but we sort it here as well so that all genesis files are identical.
|
|
sort.Slice(genesis.Validators, func(i, j int) bool {
|
|
return strings.Compare(genesis.Validators[i].Name, genesis.Validators[j].Name) == -1
|
|
})
|
|
if len(testnet.InitialState) > 0 {
|
|
appState, err := json.Marshal(testnet.InitialState)
|
|
if err != nil {
|
|
return genesis, err
|
|
}
|
|
genesis.AppState = appState
|
|
}
|
|
return genesis, genesis.ValidateAndComplete()
|
|
}
|
|
|
|
// MakeConfig generates a Tendermint config for a node.
|
|
func MakeConfig(node *e2e.Node) (*config.Config, error) {
|
|
cfg := config.DefaultConfig()
|
|
cfg.Moniker = node.Name
|
|
cfg.ProxyApp = AppAddressTCP
|
|
cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657"
|
|
cfg.RPC.PprofListenAddress = ":6060"
|
|
cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false))
|
|
cfg.P2P.AddrBookStrict = false
|
|
cfg.DBBackend = node.Database
|
|
cfg.StateSync.DiscoveryTime = 5 * time.Second
|
|
|
|
switch node.ABCIProtocol {
|
|
case e2e.ProtocolUNIX:
|
|
cfg.ProxyApp = AppAddressUNIX
|
|
case e2e.ProtocolTCP:
|
|
cfg.ProxyApp = AppAddressTCP
|
|
case e2e.ProtocolGRPC:
|
|
cfg.ProxyApp = AppAddressTCP
|
|
cfg.ABCI = "grpc"
|
|
case e2e.ProtocolBuiltin:
|
|
cfg.ProxyApp = ""
|
|
cfg.ABCI = ""
|
|
default:
|
|
return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
|
|
}
|
|
|
|
// Tendermint errors if it does not have a privval key set up, regardless of whether
|
|
// it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy
|
|
// key here by default, and use the real key for actual validators that should use
|
|
// the file privval.
|
|
cfg.PrivValidatorListenAddr = ""
|
|
cfg.PrivValidatorKey = PrivvalDummyKeyFile
|
|
cfg.PrivValidatorState = PrivvalDummyStateFile
|
|
|
|
switch node.Mode {
|
|
case e2e.ModeValidator:
|
|
switch node.PrivvalProtocol {
|
|
case e2e.ProtocolFile:
|
|
cfg.PrivValidatorKey = PrivvalKeyFile
|
|
cfg.PrivValidatorState = PrivvalStateFile
|
|
case e2e.ProtocolUNIX:
|
|
cfg.PrivValidatorListenAddr = PrivvalAddressUNIX
|
|
case e2e.ProtocolTCP:
|
|
cfg.PrivValidatorListenAddr = PrivvalAddressTCP
|
|
default:
|
|
return nil, fmt.Errorf("invalid privval protocol setting %q", node.PrivvalProtocol)
|
|
}
|
|
case e2e.ModeSeed:
|
|
cfg.P2P.SeedMode = true
|
|
cfg.P2P.PexReactor = true
|
|
case e2e.ModeFull, e2e.ModeLight:
|
|
// Don't need to do anything, since we're using a dummy privval key by default.
|
|
default:
|
|
return nil, fmt.Errorf("unexpected mode %q", node.Mode)
|
|
}
|
|
if node.Mempool != "" {
|
|
cfg.Mempool.Version = node.Mempool
|
|
}
|
|
|
|
if node.BlockSync == "" {
|
|
cfg.BlockSyncMode = false
|
|
} else {
|
|
cfg.BlockSync.Version = node.BlockSync
|
|
}
|
|
|
|
if node.StateSync {
|
|
cfg.StateSync.Enable = true
|
|
cfg.StateSync.RPCServers = []string{}
|
|
for _, peer := range node.Testnet.ArchiveNodes() {
|
|
if peer.Name == node.Name {
|
|
continue
|
|
}
|
|
cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC())
|
|
}
|
|
if len(cfg.StateSync.RPCServers) < 2 {
|
|
return nil, errors.New("unable to find 2 suitable state sync RPC servers")
|
|
}
|
|
}
|
|
|
|
cfg.P2P.Seeds = ""
|
|
for _, seed := range node.Seeds {
|
|
if len(cfg.P2P.Seeds) > 0 {
|
|
cfg.P2P.Seeds += ","
|
|
}
|
|
cfg.P2P.Seeds += seed.AddressP2P(true)
|
|
}
|
|
cfg.P2P.PersistentPeers = ""
|
|
for _, peer := range node.PersistentPeers {
|
|
if len(cfg.P2P.PersistentPeers) > 0 {
|
|
cfg.P2P.PersistentPeers += ","
|
|
}
|
|
cfg.P2P.PersistentPeers += peer.AddressP2P(true)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// MakeAppConfig generates an ABCI application config for a node.
|
|
func MakeAppConfig(node *e2e.Node) ([]byte, error) {
|
|
cfg := map[string]interface{}{
|
|
"chain_id": node.Testnet.Name,
|
|
"dir": "data/app",
|
|
"listen": AppAddressUNIX,
|
|
"mode": node.Mode,
|
|
"sync_app": node.SyncApp,
|
|
"proxy_port": node.ProxyPort,
|
|
"protocol": "socket",
|
|
"persist_interval": node.PersistInterval,
|
|
"snapshot_interval": node.SnapshotInterval,
|
|
"retain_blocks": node.RetainBlocks,
|
|
"key_type": node.PrivvalKey.Type(),
|
|
"prepare_proposal_delay": node.Testnet.PrepareProposalDelay,
|
|
"process_proposal_delay": node.Testnet.ProcessProposalDelay,
|
|
"check_tx_delay": node.Testnet.CheckTxDelay,
|
|
}
|
|
switch node.ABCIProtocol {
|
|
case e2e.ProtocolUNIX:
|
|
cfg["listen"] = AppAddressUNIX
|
|
case e2e.ProtocolTCP:
|
|
cfg["listen"] = AppAddressTCP
|
|
case e2e.ProtocolGRPC:
|
|
cfg["listen"] = AppAddressTCP
|
|
cfg["protocol"] = "grpc"
|
|
case e2e.ProtocolBuiltin:
|
|
delete(cfg, "listen")
|
|
cfg["protocol"] = "builtin"
|
|
default:
|
|
return nil, fmt.Errorf("unexpected ABCI protocol setting %q", node.ABCIProtocol)
|
|
}
|
|
if node.Mode == e2e.ModeValidator {
|
|
switch node.PrivvalProtocol {
|
|
case e2e.ProtocolFile:
|
|
case e2e.ProtocolTCP:
|
|
cfg["privval_server"] = PrivvalAddressTCP
|
|
cfg["privval_key"] = PrivvalKeyFile
|
|
cfg["privval_state"] = PrivvalStateFile
|
|
case e2e.ProtocolUNIX:
|
|
cfg["privval_server"] = PrivvalAddressUNIX
|
|
cfg["privval_key"] = PrivvalKeyFile
|
|
cfg["privval_state"] = PrivvalStateFile
|
|
default:
|
|
return nil, fmt.Errorf("unexpected privval protocol setting %q", node.PrivvalProtocol)
|
|
}
|
|
}
|
|
|
|
if len(node.Testnet.ValidatorUpdates) > 0 {
|
|
validatorUpdates := map[string]map[string]int64{}
|
|
for height, validators := range node.Testnet.ValidatorUpdates {
|
|
updateVals := map[string]int64{}
|
|
for node, power := range validators {
|
|
updateVals[base64.StdEncoding.EncodeToString(node.PrivvalKey.PubKey().Bytes())] = power
|
|
}
|
|
validatorUpdates[fmt.Sprintf("%v", height)] = updateVals
|
|
}
|
|
cfg["validator_update"] = validatorUpdates
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err := toml.NewEncoder(&buf).Encode(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate app config: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// UpdateConfigStateSync updates the state sync config for a node.
|
|
func UpdateConfigStateSync(node *e2e.Node, height int64, hash []byte) error {
|
|
cfgPath := filepath.Join(node.Testnet.Dir, node.Name, "config", "config.toml")
|
|
|
|
// FIXME Apparently there's no function to simply load a config file without
|
|
// involving the entire Viper apparatus, so we'll just resort to regexps.
|
|
bz, err := os.ReadFile(cfgPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height)))
|
|
bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash)))
|
|
return os.WriteFile(cfgPath, bz, 0o644) //nolint:gosec
|
|
}
|