200 lines
5.2 KiB
Go
200 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"time"
|
|
|
|
internalSigner "tendermint-signer/internal/signer"
|
|
|
|
tmlog "github.com/tendermint/tendermint/libs/log"
|
|
tmOS "github.com/tendermint/tendermint/libs/os"
|
|
tmService "github.com/tendermint/tendermint/libs/service"
|
|
"github.com/tendermint/tendermint/privval"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
func fileExists(filename string) bool {
|
|
info, err := os.Stat(filename)
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return !info.IsDir()
|
|
}
|
|
|
|
func main() {
|
|
logger := tmlog.NewTMLogger(
|
|
tmlog.NewSyncWriter(os.Stdout),
|
|
).With("module", "validator")
|
|
|
|
var configFile = flag.String("config", "", "path to configuration file")
|
|
flag.Parse()
|
|
|
|
if *configFile == "" {
|
|
panic("--config flag is required")
|
|
}
|
|
|
|
config, err := internalSigner.LoadConfigFromFile(*configFile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
logger.Info(
|
|
"Tendermint Validator",
|
|
"mode", config.Mode,
|
|
"priv-key", config.PrivValKeyFile,
|
|
"priv-state-dir", config.PrivValStateDir,
|
|
)
|
|
|
|
// services to stop on shutdown
|
|
var services []tmService.Service
|
|
|
|
var pv types.PrivValidator
|
|
|
|
chainID := config.ChainID
|
|
if chainID == "" {
|
|
log.Fatal("chain_id option is required")
|
|
}
|
|
|
|
if config.Mode == "single" {
|
|
logger.Info("Mode: single")
|
|
stateFile := path.Join(config.PrivValStateDir, fmt.Sprintf("%s_priv_validator_state.json", chainID))
|
|
|
|
var val types.PrivValidator
|
|
if fileExists(stateFile) {
|
|
val = privval.LoadFilePV(config.PrivValKeyFile, stateFile)
|
|
} else {
|
|
logger.Info("Initializing empty state file", "file", stateFile)
|
|
val = privval.LoadFilePVEmptyState(config.PrivValKeyFile, stateFile)
|
|
}
|
|
|
|
pv = &internalSigner.PvGuard{PrivValidator: val}
|
|
} else if config.Mode == "mpc" {
|
|
logger.Info("Mode: mpc")
|
|
if config.CosignerThreshold == 0 {
|
|
log.Fatal("The `cosigner_threshold` option is required in `threshold` mode")
|
|
}
|
|
|
|
if config.ListenAddress == "" {
|
|
log.Fatal("The cosigner_listen_address option is required in `threshold` mode")
|
|
}
|
|
|
|
key, err := internalSigner.LoadCosignerKey(config.PrivValKeyFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// ok to auto initialize on disk since the cosigner share is the one that actually
|
|
// protects against double sign - this exists as a cache for the final signature
|
|
stateFile := path.Join(config.PrivValStateDir, fmt.Sprintf("%s_priv_validator_state.json", chainID))
|
|
signState, err := internalSigner.LoadOrCreateSignState(stateFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// state for our cosigner share
|
|
// Not automatically initialized on disk to avoid double sign risk
|
|
shareStateFile := path.Join(config.PrivValStateDir, fmt.Sprintf("%s_share_sign_state.json", chainID))
|
|
shareSignState, err := internalSigner.LoadSignState(shareStateFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
cosigners := []internalSigner.Cosigner{}
|
|
remoteCosigners := []internalSigner.RemoteCosigner{}
|
|
|
|
// add ourselves as a peer so localcosigner can handle GetEphSecPart requests
|
|
peers := []internalSigner.CosignerPeer{{
|
|
ID: key.ID,
|
|
PublicKey: key.RSAKey.PublicKey,
|
|
}}
|
|
|
|
for _, cosignerConfig := range config.Cosigners {
|
|
cosigner := internalSigner.NewRemoteCosigner(cosignerConfig.ID, cosignerConfig.Address)
|
|
cosigners = append(cosigners, cosigner)
|
|
remoteCosigners = append(remoteCosigners, *cosigner)
|
|
|
|
if cosignerConfig.ID < 1 || cosignerConfig.ID > len(key.CosignerKeys) {
|
|
log.Fatalf("Unexpected cosigner ID %d", cosignerConfig.ID)
|
|
}
|
|
|
|
pubKey := key.CosignerKeys[cosignerConfig.ID-1]
|
|
peers = append(peers, internalSigner.CosignerPeer{
|
|
ID: cosigner.GetID(),
|
|
PublicKey: *pubKey,
|
|
})
|
|
}
|
|
|
|
total := len(config.Cosigners) + 1
|
|
localCosignerConfig := internalSigner.LocalCosignerConfig{
|
|
CosignerKey: key,
|
|
SignState: &shareSignState,
|
|
RsaKey: key.RSAKey,
|
|
Peers: peers,
|
|
Total: uint8(total),
|
|
Threshold: uint8(config.CosignerThreshold),
|
|
}
|
|
|
|
localCosigner := internalSigner.NewLocalCosigner(localCosignerConfig)
|
|
|
|
val := internalSigner.NewThresholdValidator(&internalSigner.ThresholdValidatorOpt{
|
|
Pubkey: key.PubKey,
|
|
Threshold: config.CosignerThreshold,
|
|
SignState: signState,
|
|
Cosigner: localCosigner,
|
|
Peers: cosigners,
|
|
})
|
|
|
|
rpcServerConfig := internalSigner.CosignerRpcServerConfig{
|
|
Logger: logger,
|
|
ListenAddress: config.ListenAddress,
|
|
Cosigner: localCosigner,
|
|
Peers: remoteCosigners,
|
|
}
|
|
|
|
rpcServer := internalSigner.NewCosignerRpcServer(&rpcServerConfig)
|
|
rpcServer.Start()
|
|
services = append(services, rpcServer)
|
|
|
|
pv = &internalSigner.PvGuard{PrivValidator: val}
|
|
} else {
|
|
log.Fatalf("Unsupported mode: %s", config.Mode)
|
|
}
|
|
|
|
pubkey, err := pv.GetPubKey()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
logger.Info("Signer", "pubkey", pubkey)
|
|
|
|
for _, node := range config.Nodes {
|
|
dialer := net.Dialer{Timeout: 30 * time.Second}
|
|
signer := internalSigner.NewReconnRemoteSigner(node.Address, logger, config.ChainID, pv, dialer)
|
|
|
|
err := signer.Start()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
services = append(services, signer)
|
|
}
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
tmOS.TrapSignal(logger, func() {
|
|
for _, service := range services {
|
|
err := service.Stop()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
wg.Done()
|
|
})
|
|
wg.Wait()
|
|
}
|