2021-06-25 09:18:37 +00:00
|
|
|
package network
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
jsonrpc "github.com/ethereum/go-ethereum/rpc"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/improbable-eng/grpc-web/go/grpcweb"
|
|
|
|
"github.com/rs/cors"
|
|
|
|
|
|
|
|
tmos "github.com/tendermint/tendermint/libs/os"
|
|
|
|
"github.com/tendermint/tendermint/node"
|
|
|
|
"github.com/tendermint/tendermint/p2p"
|
|
|
|
pvm "github.com/tendermint/tendermint/privval"
|
|
|
|
"github.com/tendermint/tendermint/proxy"
|
|
|
|
"github.com/tendermint/tendermint/rpc/client/local"
|
|
|
|
"github.com/tendermint/tendermint/types"
|
|
|
|
tmtime "github.com/tendermint/tendermint/types/time"
|
|
|
|
|
|
|
|
"github.com/cosmos/cosmos-sdk/server/api"
|
|
|
|
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
|
|
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/x/genutil"
|
|
|
|
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
|
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
|
|
|
|
|
|
"github.com/tharsis/ethermint/ethereum/rpc"
|
|
|
|
ethsrv "github.com/tharsis/ethermint/server"
|
|
|
|
ethermint "github.com/tharsis/ethermint/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
func startInProcess(cfg Config, val *Validator) error {
|
|
|
|
logger := val.Ctx.Logger
|
|
|
|
tmCfg := val.Ctx.Config
|
|
|
|
tmCfg.Instrumentation.Prometheus = false
|
|
|
|
|
|
|
|
nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile())
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to load or generate node key: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
app := cfg.AppConstructor(*val)
|
|
|
|
|
|
|
|
genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg)
|
|
|
|
tmNode, err := node.NewNode(
|
|
|
|
tmCfg,
|
|
|
|
pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()),
|
|
|
|
nodeKey,
|
|
|
|
proxy.NewLocalClientCreator(app),
|
|
|
|
genDocProvider,
|
|
|
|
node.DefaultDBProvider,
|
|
|
|
node.DefaultMetricsProvider(tmCfg.Instrumentation),
|
|
|
|
logger.With("module", val.Moniker),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create node: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := tmNode.Start(); err != nil {
|
|
|
|
return fmt.Errorf("failed to start node: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
val.tmNode = tmNode
|
|
|
|
|
|
|
|
if val.RPCAddress != "" {
|
|
|
|
val.RPCClient = local.New(tmNode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll need a RPC client if the validator exposes a gRPC or REST endpoint.
|
|
|
|
if val.APIAddress != "" || val.AppConfig.GRPC.Enable {
|
|
|
|
val.ClientCtx = val.ClientCtx.
|
|
|
|
WithClient(val.RPCClient)
|
|
|
|
|
|
|
|
// Add the tx service in the gRPC router.
|
|
|
|
app.RegisterTxService(val.ClientCtx)
|
|
|
|
|
|
|
|
// Add the tendermint queries service in the gRPC router.
|
|
|
|
app.RegisterTendermintService(val.ClientCtx)
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.APIAddress != "" {
|
|
|
|
apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server"))
|
|
|
|
app.RegisterAPIRoutes(apiSrv, val.AppConfig.API)
|
|
|
|
|
|
|
|
errCh := make(chan error)
|
|
|
|
|
|
|
|
go func() {
|
2021-06-29 17:02:21 +00:00
|
|
|
if err := apiSrv.Start(val.AppConfig.Config); err != nil {
|
2021-06-25 09:18:37 +00:00
|
|
|
errCh <- err
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-errCh:
|
|
|
|
return fmt.Errorf("failed to start API server: %w", err)
|
|
|
|
case <-time.After(5 * time.Second): // assume server started successfully
|
|
|
|
}
|
|
|
|
|
|
|
|
val.api = apiSrv
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.AppConfig.GRPC.Enable {
|
|
|
|
grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC.Address)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to start gRPC server: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
val.grpc = grpcSrv
|
|
|
|
}
|
|
|
|
|
2021-08-16 09:45:10 +00:00
|
|
|
if val.AppConfig.JSONRPC.Enable {
|
2021-06-25 09:18:37 +00:00
|
|
|
tmEndpoint := "/websocket"
|
|
|
|
tmRPCAddr := val.Ctx.Config.RPC.ListenAddress
|
2021-08-19 16:55:13 +00:00
|
|
|
tmWsClient := ethsrv.ConnectTmWS(tmRPCAddr, tmEndpoint, val.Ctx.Logger)
|
2021-06-25 09:18:37 +00:00
|
|
|
|
|
|
|
val.jsonRPC = jsonrpc.NewServer()
|
|
|
|
|
2021-08-16 09:45:10 +00:00
|
|
|
rpcAPIArr := val.AppConfig.JSONRPC.API
|
2021-07-26 11:15:55 +00:00
|
|
|
apis := rpc.GetRPCAPIs(val.Ctx, val.ClientCtx, tmWsClient, rpcAPIArr)
|
|
|
|
|
2021-06-25 09:18:37 +00:00
|
|
|
for _, api := range apis {
|
|
|
|
if err := val.jsonRPC.RegisterName(api.Namespace, api.Service); err != nil {
|
|
|
|
return fmt.Errorf("failed to register JSON-RPC namespace %s: %w", api.Namespace, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r := mux.NewRouter()
|
|
|
|
r.HandleFunc("/", val.jsonRPC.ServeHTTP).Methods("POST")
|
|
|
|
if val.grpc != nil {
|
|
|
|
grpcWeb := grpcweb.WrapServer(val.grpc)
|
2021-08-19 16:55:13 +00:00
|
|
|
ethsrv.MountGRPCWebServices(r, grpcWeb, grpcweb.ListGRPCResources(val.grpc), val.Ctx.Logger)
|
2021-06-25 09:18:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
handlerWithCors := cors.New(cors.Options{
|
|
|
|
AllowedOrigins: []string{"*"},
|
|
|
|
AllowedMethods: []string{
|
|
|
|
http.MethodHead,
|
|
|
|
http.MethodGet,
|
|
|
|
http.MethodPost,
|
|
|
|
http.MethodPut,
|
|
|
|
http.MethodPatch,
|
|
|
|
http.MethodDelete,
|
|
|
|
},
|
|
|
|
AllowedHeaders: []string{"*"},
|
|
|
|
AllowCredentials: false,
|
|
|
|
OptionsPassthrough: false,
|
|
|
|
})
|
|
|
|
|
|
|
|
httpSrv := &http.Server{
|
2021-08-16 09:45:10 +00:00
|
|
|
Addr: strings.TrimPrefix(val.AppConfig.JSONRPC.Address, "tcp://"), // FIXME: timeouts
|
|
|
|
// Addr: val.AppConfig.JSONRPC.RPCAddress, // FIXME: address has too many colons
|
2021-06-25 09:18:37 +00:00
|
|
|
Handler: handlerWithCors.Handler(r),
|
|
|
|
}
|
|
|
|
|
|
|
|
httpSrvDone := make(chan struct{}, 1)
|
|
|
|
|
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
if err := httpSrv.ListenAndServe(); err != nil {
|
|
|
|
if err == http.ErrServerClosed {
|
|
|
|
close(httpSrvDone)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
errCh <- err
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-errCh:
|
|
|
|
return fmt.Errorf("JSON-RPC go routine failed to start: %w", err)
|
|
|
|
case <-time.After(1 * time.Second): // assume EVM RPC server started successfully
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error {
|
|
|
|
genTime := tmtime.Now()
|
|
|
|
|
|
|
|
for i := 0; i < cfg.NumValidators; i++ {
|
|
|
|
tmCfg := vals[i].Ctx.Config
|
|
|
|
|
|
|
|
nodeDir := filepath.Join(outputDir, vals[i].Moniker, "ethermintd")
|
|
|
|
gentxsDir := filepath.Join(outputDir, "gentxs")
|
|
|
|
|
|
|
|
tmCfg.Moniker = vals[i].Moniker
|
|
|
|
tmCfg.SetRoot(nodeDir)
|
|
|
|
|
|
|
|
initCfg := genutiltypes.NewInitConfig(cfg.ChainID, gentxsDir, vals[i].NodeID, vals[i].PubKey)
|
|
|
|
|
|
|
|
genFile := tmCfg.GenesisFile()
|
|
|
|
genDoc, err := types.GenesisDocFromFile(genFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create genesis doc: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig,
|
|
|
|
tmCfg, initCfg, *genDoc, banktypes.GenesisBalancesIterator{})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to create app state: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// overwrite each validator's genesis file to have a canonical genesis time
|
|
|
|
if err := genutil.ExportGenesisFileWithTime(genFile, cfg.ChainID, nil, appState, genTime); err != nil {
|
|
|
|
return fmt.Errorf("failed to export genesis: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string) error {
|
|
|
|
|
|
|
|
// set the accounts in the genesis state
|
|
|
|
var authGenState authtypes.GenesisState
|
|
|
|
cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[authtypes.ModuleName], &authGenState)
|
|
|
|
|
|
|
|
accounts, err := authtypes.PackAccounts(genAccounts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
authGenState.Accounts = accounts
|
|
|
|
cfg.GenesisState[authtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&authGenState)
|
|
|
|
|
|
|
|
// set the balances in the genesis state
|
|
|
|
var bankGenState banktypes.GenesisState
|
|
|
|
cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[banktypes.ModuleName], &bankGenState)
|
|
|
|
|
|
|
|
bankGenState.Balances = genBalances
|
|
|
|
cfg.GenesisState[banktypes.ModuleName] = cfg.Codec.MustMarshalJSON(&bankGenState)
|
|
|
|
|
|
|
|
var stakingGenState stakingtypes.GenesisState
|
|
|
|
cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[stakingtypes.ModuleName], &stakingGenState)
|
|
|
|
|
|
|
|
stakingGenState.Params.BondDenom = ethermint.AttoPhoton
|
|
|
|
cfg.GenesisState[stakingtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&stakingGenState)
|
|
|
|
|
|
|
|
appGenStateJSON, err := json.MarshalIndent(cfg.GenesisState, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
genDoc := types.GenesisDoc{
|
|
|
|
ChainID: cfg.ChainID,
|
|
|
|
AppState: appGenStateJSON,
|
|
|
|
Validators: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate empty genesis files for each validator and save
|
|
|
|
for i := 0; i < cfg.NumValidators; i++ {
|
|
|
|
if err := genDoc.SaveAs(genFiles[i]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeFile(name string, dir string, contents []byte) error {
|
|
|
|
writePath := filepath.Join(dir)
|
|
|
|
file := filepath.Join(writePath, name)
|
|
|
|
|
|
|
|
err := tmos.EnsureDir(writePath, 0755)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmos.WriteFile(file, contents, 0644)
|
|
|
|
}
|