diff --git a/bfa_client/src/client/bfa_client.go b/bfa_client/src/client/bfa_client.go index 2b11e791afb3f13dbe54bb6c871a4cedf18c85f1..76ba39e9b991a9b9124b067c06cebece7ecd73ae 100644 --- a/bfa_client/src/client/bfa_client.go +++ b/bfa_client/src/client/bfa_client.go @@ -11,10 +11,12 @@ import ( "sort" "strconv" "strings" + "time" ) const ( - latest = -1 + latest = -1 + minSigners = 5 ) type Node = bfa.Node @@ -42,7 +44,7 @@ func updateURL(url string) (updated string) { defaultHTTP = "http://localhost:8545" ) if url != "" { // We accept the user selected URL - return + return url } // First, we try IPC updated = defaultIPC @@ -102,11 +104,43 @@ func proposals() { } } +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." @@ -114,11 +148,13 @@ func sealers() { 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() @@ -134,16 +170,27 @@ func sealers() { } 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 { - output = sealers[sealer].Time + 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: %*d\n", sealer, length, output) + fmt.Printf("%v: %*v\n", sealer, length, output) } } else { sealers := node.GetSignersAtBlock(blockNumber) @@ -160,28 +207,30 @@ func sealers() { func propose() { var ( - all bool + auto bool authorize bool proposals []string ) description = "Vota por una propuesta." otherArgs = "[propuesta...]" setFlags() - flags.BoolVar(&all, "all", false, "Vota en todas las propuestas activas") - flags.BoolVar(&authorize, "authorize", true, "Sentido del voto (true: a favor, false: en contra)") + 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") - if all { - votes := node.GetVotes(latest) + 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ó -all. Ignorando argumentos adicionales.") + fmt.Fprintf(os.Stderr, "Se especificó -auto. Ignorando argumentos adicionales.") } } else { if flags.NArg() == 0 { @@ -192,8 +241,11 @@ func propose() { 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 { + 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: @@ -202,7 +254,12 @@ func propose() { 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) }