package main import ( "../bfa" "../util" "flag" "fmt" "log" "os" "path" "sort" "strconv" "strings" "time" ) const ( 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 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() { err := flags.Parse(os.Args[2:]) util.Check(err) 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 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 ( authorize bool voted = make(map[string]bool) ) description = "Vota por una propuesta." otherArgs = "[propuesta...]" setFlags() 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() util.PanicIf(!node.IsSealer(bfa.Self), "Solo los selladores pueden votar") votes := node.GetVotes(latest) 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 := 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)) } node.Propose(address, authorize) if json { voted[address] = authorize } else { fmt.Printf("Voto por %v: %v\n", address, authorize) } } if json { util.PrintJson(voted) } } func main() { var ( commands = map[string]func(){ "proposals": proposals, "sealers": sealers, "vote": propose, "autovote": autovote, } 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 { 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]() }