package main import ( "../bfa" "../util" "flag" "fmt" "log" "os" "path" "sort" "strconv" "strings" "time" ) const ( latest = -1 minSigners = 5 ) type Node = bfa.Node var ( url string json bool help bool flags flag.FlagSet command string description string otherArgs string ) func setFlags() { flags.BoolVar(&json, "json", false, "Produce salida en formato json") flags.StringVar(&url, "url", "", "URL para conexión con geth. Ejemplo: '/home/bfa/bfa/network/node/geth.ipc' o 'http://localhost:8545'") flags.BoolVar(&help, "h", false, "") flags.BoolVar(&help, "help", false, "Muestra esta ayuda") } func updateURL(url string) (updated string) { const ( defaultIPC = "/home/bfa/bfa/network/node/geth.ipc" defaultHTTP = "http://localhost:8545" ) if url != "" { // We accept the user selected URL return url } // First, we try IPC updated = defaultIPC if bfaNetworkDir := os.Getenv("BFANETWORKDIR"); bfaNetworkDir != "" { updated = bfaNetworkDir + "/node/get.ipc" } if fileInfo, err := os.Stat(updated); err == nil && (fileInfo.Mode()&os.ModeSocket) != 0 { return } updated = defaultHTTP return } func usage(errorCode int) { fmt.Fprintf(os.Stderr, "Uso: %v %v [opciones] %v\n%v\n", os.Args[0], command, otherArgs, description) flags.PrintDefaults() os.Exit(errorCode) } func parseFlags() { flags.Parse(os.Args[2:]) if help { usage(0) } } func proposals() { var blockNumber int64 description = "Detalla el estado de las votaciones en curso" setFlags() flags.Int64Var(&blockNumber, "block-number", latest, "Número del bloque en el cual se quiere conocer el estado de la propuesta (-1 para el último)") parseFlags() url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() votes := node.GetVotes(blockNumber) if json { util.PrintJson(votes) return } fmt.Printf("Bloque: %d\nPropuestas en curso: %d\n", votes.BlockNumber, len(votes.Proposals)) for _, proposal := range votes.Proposals { fmt.Printf("Propuesta: %v\n", proposal) for _, signer := range votes.Signers { b := votes.Votes[proposal][signer] var v string if b == nil { v = "" } else { v = strconv.FormatBool(*b) } fmt.Printf("\t%v: %v\n", signer, v) } tally := votes.Tally[proposal] fmt.Printf("A favor: %v, en contra: %v, no votaron: %v\n", tally.True, tally.False, tally.Null) } } func parseFormatString(s string) (format string) { replacements := []struct { old string new string }{ {"YYYY", "2006"}, {"YYY", "006"}, {"YY", "06"}, {"MM", "01"}, {"DD", "02"}, {"hh", "15"}, {"mm", "04"}, {"ss", "05"}, } switch s { case "long": return "2006-01-02 15:04:05" case "short": return "15:04:05" case "unix": return time.UnixDate case "rfc3339": return time.RFC3339 } format = s for _, repl := range replacements { format = strings.Replace(format, repl.old, repl.new, 1) } return } func sealers() { var ( blockNumber int64 status bool timestamp bool format string length int64 = 10 // timestamp length ) description = "Presenta la lista de selladores. Opcionalmente indica el último bloque sellado por cada uno." setFlags() flags.Int64Var(&blockNumber, "block-number", latest, "Número del bloque en el cual se quiere conocer la lista de selladores (-1 para el último)") flags.BoolVar(&status, "status", false, "Indica el último bloque sellado por cada sellador, o 0 si un nodo no ha sellado en las últimas 5 rondas.") flags.BoolVar(×tamp, "timestamp", false, "Muestra el timestamp del sellado en lugar del número de bloque.") flags.StringVar(&format, "format", "", "Formato del timestamp. Ignorado en formato json. Opciones: 'unix', 'rfc3339', 'long' ('YYYY-MM-DD hh:mm:ss'), 'short' ('hh:mm:ss') o un formato específico. Ejemplo 'DD/MM/YY hh.mm.ss'. También se admite el formato del paquete 'time' de go.") parseFlags() if blockNumber == 0 { panic("El bloque génesis no tiene firmantes") } url = updateURL(url) fmt.Println(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() if status { sealers := node.SealersStatus(blockNumber) if json { util.PrintJson(sealers) return } var list []string for sealer := range sealers { list = append(list, sealer) } sort.Slice(list, func(i, j int) bool { return sealers[list[i]].LastBlock > sealers[list[j]].LastBlock }) if !timestamp { length = util.Max(2, int64(len(strconv.FormatInt(sealers[list[0]].LastBlock, 10)))) } else { if len(format) > 0 { format = parseFormatString(format) length = int64(len(format)) } } for _, sealer := range list { var output interface{} if timestamp { t := int64(sealers[sealer].Time) if len(format) > 0 && t > 0 { output = time.Unix(t, 0).Format(format) } else { output = t } } else { output = sealers[sealer].LastBlock } fmt.Printf("%v: %*v\n", sealer, length, output) } } else { sealers := node.GetSignersAtBlock(blockNumber) sort.Slice(sealers, func(i, j int) bool { return sealers[i] < sealers[j] }) if json { util.PrintJson(sealers) } else { for _, sealer := range sealers { fmt.Println(sealer) } } } } func propose() { var ( auto bool authorize bool proposals []string ) description = "Vota por una propuesta." otherArgs = "[propuesta...]" setFlags() flags.BoolVar(&auto, "auto", false, "Vota en todas las propuestas activas, en el sentido que corresponda.") flags.BoolVar(&authorize, "authorize", true, "Sentido del voto (true: a favor, false: en contra).") parseFlags() url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() genesisSigners := node.GetSignersAtBlock(0) util.PanicIf(!node.IsSealer(bfa.Self), "Solo los selladores pueden votar") votes := node.GetVotes(latest) if auto { for _, proposal := range votes.Proposals { util.PanicIf(util.Contains(genesisSigners, proposal) && node.IsSealer(proposal), "No se puede quitar en forma automática a un sellador del bloque génesis.") proposals = append(proposals, proposal) } if flags.NArg() != 0 { fmt.Fprintf(os.Stderr, "Se especificó -auto. Ignorando argumentos adicionales.") } } else { if flags.NArg() == 0 { panic("No se especificaron candidatos por los cuales votar") } for i := 0; i < flags.NArg(); i++ { address := flags.Arg(i) if !util.IsAddress(address) { panic(fmt.Sprintf("'%v' no es una dirección válida", address)) } if _, ok := votes.Tally[address]; ok { continue // address is in a proposal, so we allow voting either way } isSealer := node.IsSealer(address) switch { // address is not in a proposal, we allow removing signers or adding non signers case isSealer && authorize: panic(fmt.Sprintf("'%v' ya es un sellador", address)) case !isSealer && !authorize: panic(fmt.Sprintf("'%v' no es un sellador", address)) } proposals = append(proposals, flags.Arg(i)) } } self, err := node.Coinbase() util.Check(err) for _, proposal := range proposals { util.PanicIf(proposal == self, "No es válido votarse a sí mismo") authorize = !node.IsSealer(proposal) util.PanicIf(len(node.GetSigners()) <= minSigners && !authorize, "Hay sólo %v selladores. No se permite eliminar más selladores.", minSigners) node.Propose(proposal, authorize) fmt.Printf("Voto por %v: %v\n", proposal, authorize) } } func main() { var ( commands = map[string]func(){ "proposals": proposals, "sealers": sealers, "vote": propose, } validCommands []string ) for command := range commands { validCommands = append(validCommands, command) } defer func() { if err := recover(); err != nil { log.Printf("Error: %s", err) usage(1) } }() if len(os.Args) > 1 { command = os.Args[1] } if commands[command] == nil { fmt.Fprintf(os.Stderr, "Uso: %v <%v> [opciones]\n", path.Base(os.Args[0]), strings.Join(validCommands, "|")) fmt.Fprintf(os.Stderr, "Para ayuda: %v <command> -h\n", path.Base(os.Args[0])) os.Exit(1) } commands[command]() }