diff --git a/bfa_client/src/client/bfa_client.go b/bfa_client/src/client/bfa_client.go index 76ba39e9b991a9b9124b067c06cebece7ecd73ae..44b8bfff4bd10faf78eae4f8510ab2f6bd0eb3e1 100644 --- a/bfa_client/src/client/bfa_client.go +++ b/bfa_client/src/client/bfa_client.go @@ -15,8 +15,9 @@ import ( ) const ( - latest = -1 - minSigners = 5 + latest = -1 + minSigners = 5 // We don't want to have less sealers than this number + voteThreshold int = 3 // Number of manual votes needed to enable autovote ) type Node = bfa.Node @@ -59,13 +60,14 @@ func updateURL(url string) (updated string) { } func usage(errorCode int) { - fmt.Fprintf(os.Stderr, "Uso: %v %v [opciones] %v\n%v\n", os.Args[0], command, otherArgs, description) + _, _ = 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:]) + err := flags.Parse(os.Args[2:]) + util.Check(err) if help { usage(0) } @@ -205,63 +207,107 @@ func sealers() { } } +func autovote() { + var ( + removedSealers = 0 + threshold = voteThreshold + voted = make(map[string]bool) + ) + description = fmt.Sprintf(`Vota automáticamente en todas las propuestas activas, en el sentido que corresponda, con ciertas restricciones. + - No deja votar si el voto puede reducir la cantidad de selladores a menos de %v. + - No permite eliminar a los selladores establecidos en el bloque génesis. + - Requiere que la propuesta tenga al menos %v votos. + - No permite votar para eliminarse a uno mismo de la lista de selladores.`, minSigners, threshold) + flags.IntVar(&threshold, "threshold", voteThreshold, "Cantidad mÃnima de votos en una propuesta para habilitar el voto automático.") + setFlags() + parseFlags() + util.PanicIf(threshold < voteThreshold, "No se puede especificar una cantidad de votos inferior a %v.", voteThreshold) + url = updateURL(url) + node, err := bfa.Dial(url) + util.Check(err) + defer node.Close() + util.PanicIf(!node.IsSealer(bfa.Self), "Solo los selladores pueden votar") + votes := node.GetVotes(latest) + genesisSigners := node.GetSignersAtBlock(0) + self, err := node.Coinbase() + util.Check(err) + for _, tally := range votes.Tally { + if tally.False >= threshold { // We are trying to remove a sealer + removedSealers += 1 + } + } + util.PanicIf(len(votes.Signers)-removedSealers < minSigners, "No se puede emitir un voto automático que reduzca la cantidad de selladores por debajo de %v.", minSigners) + for _, proposal := range votes.Proposals { + isSealer := util.Contains(votes.Signers, proposal) + switch { + case votes.Votes[proposal][self] != nil: + continue // Already voted + case isSealer && votes.Tally[proposal].False < threshold: + continue // There aren't enough votes to enable autovote + case !isSealer && votes.Tally[proposal].True < threshold: + continue // There aren't enough votes to enable autovote + case util.Contains(genesisSigners, proposal) && isSealer: + log.Printf("No se puede quitar en forma automática a un sellador del bloque génesis: %v.\n", proposal) + continue + case proposal == self: + log.Println("No se puede votar para eliminarse uno mismo de la lista de selladores.") + continue + } + node.Propose(proposal, !isSealer) + if json { + voted[proposal] = !isSealer + } else { + fmt.Printf("Voto por %v: %v\n", proposal, !isSealer) + } + } + if json { + util.PrintJson(voted) + } +} + func propose() { var ( - auto bool authorize bool - proposals []string + voted = make(map[string]bool) ) 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 { + 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 flags.NArg() != 0 { - fmt.Fprintf(os.Stderr, "Se especificó -auto. Ignorando argumentos adicionales.") + if _, ok := votes.Tally[address]; ok { + continue // address is in a proposal, so we allow voting either way } - } else { - if flags.NArg() == 0 { - panic("No se especificaron candidatos por los cuales votar") + isSealer := util.Contains(votes.Signers, 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)) } - 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)) + node.Propose(address, authorize) + if json { + voted[address] = authorize + } else { + fmt.Printf("Voto por %v: %v\n", address, authorize) } } - 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) + if json { + util.PrintJson(voted) } } @@ -271,6 +317,7 @@ func main() { "proposals": proposals, "sealers": sealers, "vote": propose, + "autovote": autovote, } validCommands []string ) @@ -287,8 +334,8 @@ func main() { 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])) + log.Printf("Uso: %v <%v> [opciones]\n", path.Base(os.Args[0]), strings.Join(validCommands, "|")) + log.Printf("Para ayuda: %v <command> -h\n", path.Base(os.Args[0])) os.Exit(1) } commands[command]()