github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/cmd/burrow/commands/configure.go (about)

     1  package commands
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"strings"
     8  
     9  	"github.com/hyperledger/burrow/config/deployment"
    10  	"github.com/hyperledger/burrow/config/source"
    11  	"github.com/hyperledger/burrow/consensus/tendermint"
    12  	"github.com/hyperledger/burrow/crypto"
    13  	"github.com/hyperledger/burrow/dump"
    14  	"github.com/hyperledger/burrow/execution"
    15  	"github.com/hyperledger/burrow/execution/state"
    16  	"github.com/hyperledger/burrow/genesis/spec"
    17  	"github.com/hyperledger/burrow/keys"
    18  	"github.com/hyperledger/burrow/logging"
    19  	"github.com/hyperledger/burrow/logging/logconfig"
    20  	"github.com/hyperledger/burrow/logging/logconfig/presets"
    21  	"github.com/hyperledger/burrow/rpc"
    22  	cli "github.com/jawher/mow.cli"
    23  	tmjson "github.com/tendermint/tendermint/libs/json"
    24  	dbm "github.com/tendermint/tm-db"
    25  )
    26  
    27  // Configure generates burrow configuration(s)
    28  func Configure(output Output) func(cmd *cli.Cmd) {
    29  	return func(cmd *cli.Cmd) {
    30  
    31  		genesisSpecOpt := cmd.StringOpt("s genesis-spec", "",
    32  			"A GenesisSpec to use as a template for a GenesisDoc that will be created along with keys")
    33  
    34  		jsonOutOpt := cmd.BoolOpt("j json", false, "Emit config in JSON rather than TOML "+
    35  			"suitable for further processing")
    36  
    37  		keysURLOpt := cmd.StringOpt("k keys-url", "", fmt.Sprintf("Provide keys GRPC address, default: %s",
    38  			keys.DefaultKeysConfig().RemoteAddress))
    39  
    40  		keysDir := cmd.StringOpt("keys-dir", "", "Directory where keys are stored")
    41  
    42  		curveType := cmd.StringOpt("curve-type", crypto.CurveTypeEd25519.String(), "Curve type for realising keys")
    43  
    44  		configTemplateIn := cmd.StringsOpt("config-template-in", nil,
    45  			"Go text/template input filename to generate config file specified with --config-out")
    46  
    47  		configTemplateOut := cmd.StringsOpt("config-out", nil,
    48  			"Go text/template output filename. Template filename specified with --config-template-in")
    49  
    50  		separateGenesisDoc := cmd.StringOpt("w separate-genesis-doc", "", "Emit a separate genesis doc as JSON or TOML")
    51  
    52  		loggingOpt := cmd.StringOpt("l logging", "",
    53  			"Comma separated list of logging instructions which form a 'program' which is a depth-first "+
    54  				"pre-order of instructions that will build the root logging sink. See 'burrow help' for more information.")
    55  
    56  		describeLoggingOpt := cmd.BoolOpt("describe-logging", false,
    57  			"Print an exhaustive list of logging instructions available with the --logging option")
    58  
    59  		debugOpt := cmd.BoolOpt("d debug", false, "Include maximal debug options in config "+
    60  			"including logging opcodes and dumping EVM tokens to disk these can be later pruned from the "+
    61  			"generated config.")
    62  
    63  		chainNameOpt := cmd.StringOpt("n chain-name", "", "Default chain name")
    64  
    65  		emptyBlocksOpt := cmd.StringOpt("e empty-blocks", "",
    66  			"Whether to create empty blocks, one of: 'never' (always wait for transactions before proposing a "+
    67  				"block, 'always' (at end of each consensus round), or a duration like '1s', '5m', or '6h'")
    68  
    69  		restoreDumpOpt := cmd.StringOpt("restore-dump", "", "Including AppHash for restored file")
    70  
    71  		pool := cmd.BoolOpt("pool", false, "Write config files for all the validators called burrowNNN.toml")
    72  
    73  		cmd.Spec = "[--keys-url=<keys URL> | --keys-dir=<keys directory>] [--curve-type=<name>]" +
    74  			"[ --config-template-in=<text template> --config-out=<output file>]... " +
    75  			"[--genesis-spec=<GenesisSpec file>] [--separate-genesis-doc=<genesis JSON file>] " +
    76  			"[--chain-name=<chain name>] [--restore-dump=<dump file>] [--json] [--debug] [--pool] " +
    77  			"[--logging=<logging program>] [--describe-logging] [--empty-blocks=<'always','never',duration>]"
    78  
    79  		// no sourcing logs
    80  		source.LogWriter = ioutil.Discard
    81  		// TODO: this has default, only set if explicit?
    82  		configOpts := addConfigOptions(cmd)
    83  
    84  		cmd.Action = func() {
    85  			conf, err := configOpts.obtainBurrowConfig()
    86  			if err != nil {
    87  				output.Fatalf("could not obtain config: %v", err)
    88  			}
    89  
    90  			if *describeLoggingOpt {
    91  				output.Logf("Usage:\n  burrow configure -l INSTRUCTION[,...]\n\nBuilds a logging " +
    92  					"configuration by constructing a tree of logging sinks assembled from preset instructions " +
    93  					"that generate the tree while traversing it.\n\nLogging Instructions:\n")
    94  				for _, instruction := range presets.Instructons() {
    95  					output.Logf("  %-15s\t%s\n", instruction.Name(), instruction.Description())
    96  				}
    97  				output.Logf("\nExample Usage:\n  burrow configure -l include-any,info,stderr\n")
    98  				return
    99  			}
   100  
   101  			if *keysURLOpt != "" {
   102  				conf.Keys.RemoteAddress = *keysURLOpt
   103  			}
   104  
   105  			if len(*configTemplateIn) != len(*configTemplateOut) {
   106  				output.Fatalf("--config-template-in and --config-out must be specified the same number of times")
   107  			}
   108  
   109  			pkg := deployment.Config{Keys: make(map[crypto.Address]deployment.Key)}
   110  
   111  			ct, err := crypto.CurveTypeFromString(*curveType)
   112  			if err != nil {
   113  				output.Fatalf("could not realise curve type: %v", err)
   114  			}
   115  
   116  			// Genesis Spec
   117  			if *genesisSpecOpt != "" {
   118  				genesisSpec := new(spec.GenesisSpec)
   119  				err := source.FromFile(*genesisSpecOpt, genesisSpec)
   120  				if err != nil {
   121  					output.Fatalf("Could not read GenesisSpec: %v", err)
   122  				}
   123  				if conf.Keys.RemoteAddress == "" {
   124  					dir := conf.Keys.KeysDirectory
   125  					if *keysDir != "" {
   126  						dir = *keysDir
   127  					}
   128  					keyStore := keys.NewFilesystemKeyStore(dir, conf.Keys.AllowBadFilePermissions)
   129  
   130  					keyClient := keys.NewLocalKeyClient(keyStore, logging.NewNoopLogger())
   131  					conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient, ct)
   132  					if err != nil {
   133  						output.Fatalf("could not generate GenesisDoc from GenesisSpec using MockKeyClient: %v", err)
   134  					}
   135  
   136  					allNames, err := keyStore.GetAllNames()
   137  					if err != nil {
   138  						output.Fatalf("could get all keys: %v", err)
   139  					}
   140  
   141  					for k := range allNames {
   142  						addr, err := crypto.AddressFromHexString(allNames[k])
   143  						if err != nil {
   144  							output.Fatalf("address %s not valid: %v", k, err)
   145  						}
   146  						key, err := keyStore.GetKey("", addr[:])
   147  						if err != nil {
   148  							output.Fatalf("failed to get key: %s: %v", k, err)
   149  						}
   150  						bs, err := json.Marshal(key)
   151  						if err != nil {
   152  							output.Fatalf("failed to json marshal key: %s: %v", k, err)
   153  						}
   154  						pkg.Keys[addr] = deployment.Key{Name: k, Address: addr, KeyJSON: bs}
   155  					}
   156  				} else {
   157  					keyClient, err := keys.NewRemoteKeyClient(conf.Keys.RemoteAddress, logging.NewNoopLogger())
   158  					if err != nil {
   159  						output.Fatalf("could not create remote key client: %v", err)
   160  					}
   161  					conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient, ct)
   162  					if err != nil {
   163  						output.Fatalf("could not realise GenesisSpec: %v", err)
   164  					}
   165  				}
   166  
   167  			}
   168  
   169  			if *chainNameOpt != "" {
   170  				if conf.GenesisDoc == nil {
   171  					output.Fatalf("unable to set ChainName since no GenesisDoc/GenesisSpec provided.")
   172  				}
   173  				conf.GenesisDoc.ChainName = *chainNameOpt
   174  			}
   175  
   176  			if *restoreDumpOpt != "" {
   177  				if conf.GenesisDoc == nil {
   178  					output.Fatalf("no GenesisDoc provided, cannot restore dump")
   179  				}
   180  
   181  				if len(conf.GenesisDoc.Validators) == 0 {
   182  					output.Fatalf("on restore, validators must be provided in GenesisDoc or GenesisSpec")
   183  				}
   184  
   185  				reader, err := dump.NewFileReader(*restoreDumpOpt)
   186  				if err != nil {
   187  					output.Fatalf("failed to read restore dump: %v", err)
   188  				}
   189  
   190  				st, err := state.MakeGenesisState(dbm.NewMemDB(), conf.GenesisDoc)
   191  				if err != nil {
   192  					output.Fatalf("could not generate state from genesis: %v", err)
   193  				}
   194  
   195  				err = dump.Load(reader, st)
   196  				if err != nil {
   197  					output.Fatalf("could not restore dump %s: %v", *restoreDumpOpt, err)
   198  				}
   199  
   200  				conf.GenesisDoc.AppHash = st.Hash()
   201  			}
   202  
   203  			// Logging
   204  			if *loggingOpt != "" {
   205  				ops := strings.Split(*loggingOpt, ",")
   206  				sinkConfig, err := presets.BuildSinkConfig(ops...)
   207  				if err != nil {
   208  					output.Fatalf("could not build logging configuration: %v\n\nTo see possible logging "+
   209  						"instructions run:\n  burrow configure --describe-logging", err)
   210  				}
   211  				conf.Logging = &logconfig.LoggingConfig{
   212  					RootSink: sinkConfig,
   213  				}
   214  			}
   215  
   216  			if *debugOpt {
   217  				conf.Execution = &execution.ExecutionConfig{
   218  					VMOptions: []execution.VMOption{execution.DumpTokens, execution.DebugOpcodes},
   219  				}
   220  			}
   221  
   222  			if *emptyBlocksOpt != "" {
   223  				conf.Tendermint.CreateEmptyBlocks = *emptyBlocksOpt
   224  			}
   225  
   226  			peers := make([]string, 0)
   227  			if conf.GenesisDoc != nil {
   228  				pkg.GenesisDoc = conf.GenesisDoc
   229  
   230  				for _, val := range conf.GenesisDoc.Validators {
   231  					nodeKey := tendermint.NewNodeKey()
   232  					nodeAddress, _ := crypto.AddressFromHexString(string(nodeKey.ID()))
   233  
   234  					bs, err := tmjson.Marshal(nodeKey)
   235  					if err != nil {
   236  						output.Fatalf("failed to json marshal private key: %v", err)
   237  					}
   238  					pkg.Keys[nodeAddress] = deployment.Key{Name: val.Name, Address: nodeAddress, KeyJSON: bs}
   239  
   240  					pkg.Validators = append(pkg.Validators, deployment.Validator{
   241  						Name:        val.Name,
   242  						Address:     val.Address,
   243  						NodeAddress: nodeAddress,
   244  					})
   245  				}
   246  
   247  				for ind := range *configTemplateIn {
   248  					err := processTemplate(&pkg, (*configTemplateIn)[ind], (*configTemplateOut)[ind])
   249  					if err != nil {
   250  						output.Fatalf("could not template from %s to %s: %v", (*configTemplateIn)[ind], (*configTemplateOut)[ind], err)
   251  					}
   252  				}
   253  			}
   254  
   255  			// Store this for use in pool
   256  			genesisDoc := conf.GenesisDoc
   257  			if *separateGenesisDoc != "" {
   258  				if conf.GenesisDoc == nil {
   259  					output.Fatalf("cannot write separate genesis doc since no GenesisDoc/GenesisSpec was provided")
   260  				}
   261  				genesisDocJSON, err := conf.GenesisDoc.JSONBytes()
   262  				if err != nil {
   263  					output.Fatalf("could not form GenesisDoc JSON: %v", err)
   264  				}
   265  				err = ioutil.WriteFile(*separateGenesisDoc, genesisDocJSON, 0644)
   266  				if err != nil {
   267  					output.Fatalf("could not write GenesisDoc JSON: %v", err)
   268  				}
   269  				conf.GenesisDoc = nil
   270  			}
   271  
   272  			if *pool {
   273  				for i, val := range pkg.Validators {
   274  					tmConf, err := conf.Tendermint.Config(fmt.Sprintf(".burrow%03d", i), conf.Execution.TimeoutFactor)
   275  					if err != nil {
   276  						output.Fatalf("could not obtain config for %03d: %v", i, err)
   277  					}
   278  					nodeKey := pkg.Keys[val.NodeAddress]
   279  					err = tendermint.WriteNodeKey(tmConf.NodeKeyFile(), nodeKey.KeyJSON)
   280  					if err != nil {
   281  						output.Fatalf("failed to create node key for %03d: %v", i, err)
   282  					}
   283  					peers = append(peers, fmt.Sprintf("tcp://%s@127.0.0.1:%d",
   284  						strings.ToLower(nodeKey.Address.String()), 26656+i))
   285  				}
   286  				for i, acc := range genesisDoc.Accounts {
   287  					// set stuff
   288  					conf.ValidatorAddress = &acc.Address
   289  					conf.Tendermint.PersistentPeers = strings.Join(peers, ",")
   290  					conf.BurrowDir = fmt.Sprintf(".burrow%03d", i)
   291  					conf.Tendermint.ListenHost = rpc.LocalHost
   292  					conf.Tendermint.ListenPort = fmt.Sprint(26656 + i)
   293  					conf.RPC.Info.ListenHost = rpc.LocalHost
   294  					conf.RPC.Info.ListenPort = fmt.Sprint(26758 + i)
   295  					conf.RPC.Web3.ListenHost = rpc.LocalHost
   296  					conf.RPC.Web3.ListenPort = fmt.Sprint(26860 + i)
   297  					conf.RPC.GRPC.ListenHost = rpc.LocalHost
   298  					conf.RPC.GRPC.ListenPort = fmt.Sprint(10997 + i)
   299  					conf.RPC.Metrics.ListenHost = rpc.LocalHost
   300  					conf.RPC.Metrics.ListenPort = fmt.Sprint(9102 + i)
   301  					conf.Logging.RootSink.Output.OutputType = "file"
   302  					conf.Logging.RootSink.Output.FileConfig = &logconfig.FileConfig{Path: fmt.Sprintf("burrow%03d.log", i)}
   303  
   304  					if *jsonOutOpt {
   305  						err = ioutil.WriteFile(fmt.Sprintf("burrow%03d.json", i), []byte(conf.JSONString()), 0644)
   306  					} else {
   307  						err = ioutil.WriteFile(fmt.Sprintf("burrow%03d.toml", i), []byte(conf.TOMLString()), 0644)
   308  					}
   309  					if err != nil {
   310  						output.Fatalf("Could not write Burrow config file: %v", err)
   311  					}
   312  				}
   313  			} else if *jsonOutOpt {
   314  				output.Printf(conf.JSONString())
   315  			} else {
   316  				output.Printf(conf.TOMLString())
   317  			}
   318  
   319  		}
   320  	}
   321  }
   322  
   323  func processTemplate(pkg *deployment.Config, templateIn, templateOut string) error {
   324  	data, err := ioutil.ReadFile(templateIn)
   325  	if err != nil {
   326  		return err
   327  	}
   328  	output, err := pkg.Dump(templateIn, string(data))
   329  	if err != nil {
   330  		return err
   331  	}
   332  	return ioutil.WriteFile(templateOut, []byte(output), 0644)
   333  }