tendermint-mpc-validator/cmd/signer/main.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()
}