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 }