package main import ( "../bfa" "../util" "flag" "fmt" "log" "math/big" "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.NewFlagSet("", flag.ExitOnError) 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) { util.Require(len(os.Args) > 1, "not enough arguments") _, _ = fmt.Fprintf(os.Stderr, "Uso: %v %v [opciones] %v\n%v\n", os.Args[0], os.Args[1], otherArgs, description) flags.PrintDefaults() os.Exit(errorCode) } func parseFlags(allowAdditionalArgs bool) { err := flags.Parse(os.Args[2:]) util.Check(err) if help { usage(0) } if flags.NArg() > 0 && !allowAdditionalArgs { _, _ = fmt.Fprintln(os.Stderr, "Demasiados argumentos") usage(1) } } 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(false) url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() blockNumber = node.BlockNumberInRange(blockNumber) votes := node.Votes(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 lastBlock bool timestamp bool difficulty bool balance bool header bool format string ) 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(&lastBlock, "last-block", false, "Muestra 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.") flags.BoolVar(&difficulty, "difficulty", false, "Muestra la dificultad del sellado (1: fuera de turno, 2: en turno).") flags.BoolVar(&balance, "balance", false, "Muestra el saldo del sellador (en Wei).") flags.BoolVar(&header, "header", true, "Muestra un encabezado en cada columna.") 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(false) if blockNumber == 0 { util.Error("El bloque génesis no tiene firmantes") } url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() blockNumber = node.BlockNumberInRange(blockNumber) extended := lastBlock || timestamp || difficulty || balance if extended { sealers := node.SealersStatus(blockNumber) if json { util.PrintJson(sealers) return } var ( list []string lastBlockLen int64 timestampLen int64 balanceLen int64 ) for sealer := range sealers { list = append(list, sealer) } switch { case lastBlock: sort.Slice(list, func(i, j int) bool { return sealers[list[i]].LastBlock > sealers[list[j]].LastBlock }) case timestamp: sort.Slice(list, func(i, j int) bool { return sealers[list[i]].Time > sealers[list[j]].Time }) case difficulty: sort.Slice(list, func(i, j int) bool { return sealers[list[i]].Difficulty > sealers[list[j]].Difficulty }) case balance: sort.Slice(list, func(i, j int) bool { return sealers[list[i]].Balance.Cmp(sealers[list[j]].Balance) > 0 }) } formatStr := "%42v" if lastBlock { formatStr += " %[2]*[3]v" lastBlockLen = util.Max(2, int64(len(strconv.FormatInt(sealers[list[0]].LastBlock, 10)))) } if timestamp { formatStr += " %[4]*[5]v" if len(format) > 0 { format = parseFormatString(format) timestampLen = int64(len(format)) } else { timestampLen = 10 } } if difficulty { formatStr += " %2[6]v" } if balance { formatStr += " %[7]*[8]v" balanceLen = 0 for _, s := range sealers { balanceLen = util.Max(balanceLen, int64(len(s.Balance.String()))) } } formatStr += "\n" if header { fmt.Printf(formatStr, fmt.Sprintf("%-24v", "Sealer"), lastBlockLen, fmt.Sprintf("%*v", -lastBlockLen/2-2, "Last"), timestampLen, fmt.Sprintf("%*v", -timestampLen/2-2, "Time"), "Dif", balanceLen, fmt.Sprintf("%*v", -balanceLen/2-4, "Balance")) } for _, sealer := range list { var formatedTimestamp interface{} s := sealers[sealer] if timestamp { t := int64(s.Time) if len(format) > 0 && t > 0 { formatedTimestamp = time.Unix(t, 0).Format(format) } else { formatedTimestamp = t } } fmt.Printf(formatStr, sealer, lastBlockLen, s.LastBlock, timestampLen, formatedTimestamp, s.Difficulty, balanceLen, s.Balance) } } else { sealers := node.SealersAtBlock(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(false) util.Ensure(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.Ensure(node.IsSealer(bfa.Self), "Solo los selladores pueden votar") votes := node.Votes(latest) genesisSigners := node.SealersAtBlock(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.Ensure(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(true) url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() util.Ensure(node.IsSealer(bfa.Self), "Solo los selladores pueden votar") votes := node.Votes(latest) util.Ensure(flags.NArg() > 0, "No se especificaron candidatos por los cuales votar") for i := 0; i < flags.NArg(); i++ { address := flags.Arg(i) util.Ensure(util.IsAddress(address), "'%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: util.Error("'%v' ya es un sellador", address) case !isSealer && !authorize: util.Error("'%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 status() { nodeStatus := struct { Enode string `json:"enode"` Network uint64 `json:"network"` Genesis string `json:"genesis"` BFANetwork bool `json:"bfaNetwork"` BFAGenesis bool `json:"bfaGenesis"` Accounts map[string]*big.Int `json:"accounts"` Coinbase string `json:"coinbase"` IsSealer bool `json:"isSealer"` IsMining bool `json:"isMining"` BlockNumber int64 `json:"blockNumber"` PeerCount int64 `json:"peerCount"` }{ Accounts: make(map[string]*big.Int), } description = "Muestra el estado del nodo." setFlags() parseFlags(false) url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() for _, account := range node.Accounts() { nodeStatus.Accounts[account] = node.Balance(account) } nodeStatus.Coinbase, _ = node.Coinbase() nodeStatus.BlockNumber = node.BlockNumber() nodeStatus.IsSealer = node.IsSealer(bfa.Self) nodeStatus.IsMining = node.IsMining() nodeInfo := node.NodeInfo() nodeStatus.Enode = nodeInfo.Enode ethInfo := nodeInfo.Protocols["eth"].(map[string]interface{}) nodeStatus.Genesis = ethInfo["genesis"].(string) nodeStatus.Network = uint64(ethInfo["network"].(float64)) nodeStatus.BFAGenesis = nodeStatus.Genesis == bfa.Genesis nodeStatus.BFANetwork = nodeStatus.Network == bfa.Network nodeStatus.PeerCount = node.PeerCount() util.PrintJson(nodeStatus) } func block() { var ( blockNumber int64 ) 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)") parseFlags(false) url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() blockNumber = node.BlockNumberInRange(blockNumber) block := node.BlockByNumber(blockNumber) util.PrintJson(block) } func snapshot() { var blockNumber int64 description = "Muestra el snapshot en un bloque" 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)") parseFlags(false) url = updateURL(url) node, err := bfa.Dial(url) util.Check(err) defer node.Close() blockNumber = node.BlockNumberInRange(blockNumber) snapshot := node.SnapshotAtBlock(blockNumber) util.PrintJson(snapshot) } func main() { var ( commands = map[string]func(){ "proposals": proposals, "sealers": sealers, "vote": propose, "autovote": autovote, "status": status, "block": block, "snapshot": snapshot, } validCommands []string command func() ) for cmd := range commands { validCommands = append(validCommands, cmd) } sort.Strings(validCommands) //defer func() { // if err := recover(); err != nil { // log.Printf("Error: %s", err) // } //}() if len(os.Args) > 1 { command = commands[os.Args[1]] } util.Ensure(command != nil, "Uso: %v <%v> [opciones]\nPara ayuda: %v <command> -h\n", path.Base(os.Args[0]), strings.Join(validCommands, "|"), path.Base(os.Args[0])) command() }