Newer
Older
"os"
"path"
"sort"
"strconv"
"strings"
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
var (
url string
json bool
help bool
flags = flag.NewFlagSet("", flag.ExitOnError)
)
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() {
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.Votes(blockNumber)
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)
}
}
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
difficulty bool
balance bool
header bool
)
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.")
panic("El bloque génesis no tiene firmantes")
}
url = updateURL(url)
node, err := bfa.Dial(url)
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"))
}
var formatedTimestamp interface{}
s := sealers[sealer]
t := int64(s.Time)
formatedTimestamp = time.Unix(t, 0).Format(format)
formatedTimestamp = t
fmt.Printf(formatStr, sealer, lastBlockLen, s.LastBlock, timestampLen, formatedTimestamp, s.Difficulty, balanceLen, s.Balance)
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()
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.Votes(latest)
genesisSigners := node.SealersAtBlock(0)
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
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)
}
}
)
description = "Vota por una propuesta."
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.Votes(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 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"`
}{
Accounts: make(map[string]*big.Int),
}
description = "Muestra el estado del nodo."
setFlags()
parseFlags()
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
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()
url = updateURL(url)
node, err := bfa.Dial(url)
util.Check(err)
defer node.Close()
util.PrintJson(node.BlockByNumber(blockNumber))
}
commands = map[string]func(){
"proposals": proposals,
"sealers": sealers,
"status": status,
"block": block,
for command := range commands {
validCommands = append(validCommands, command)
}
sort.Strings(validCommands)
//defer func() {
// if err := recover(); err != nil {
// log.Printf("Error: %s", err)
// }
//}()
if len(os.Args) > 1 {
command = os.Args[1]
}
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]))