Skip to content
Snippets Groups Projects
bfa_client.go 7.93 KiB
Newer Older
package main

import (
	"../bfa"
	"../util"
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	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
	}
	// 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
		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(&timestamp, "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)
	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 (
		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)
		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]()