Newer
Older
"github.com/ethereum/go-ethereum/common"
"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)
wei = new(big.Float).SetFloat64(1)
kilowei = new(big.Float).SetFloat64(1e3)
megawei = new(big.Float).SetFloat64(1e6)
gigawei = new(big.Float).SetFloat64(1e9)
microether = new(big.Float).SetFloat64(1e12)
milliether = new(big.Float).SetFloat64(1e15)
ether = new(big.Float).SetFloat64(1e18)
units = map[string]*big.Float{
"wei": wei,
"kwei": kilowei,
"kilowei": kilowei,
"babbage": kilowei,
"mwei": megawei,
"megawei": megawei,
"lovelace": megawei,
"gwei": gigawei,
"gigawei": gigawei,
"shannon": gigawei,
"microether": microether,
"szabo": microether,
"milliether": milliether,
"finney": milliether,
"ether": ether,
}
)
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) {
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 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)")
url = updateURL(url)
node, err := bfa.Dial(url)
util.Check(err)
defer node.Close()
blockNumber = node.BlockNumberInRange(blockNumber)
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)
}
}
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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 printSealers(sealers []string) {
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 weiTo(wei *big.Int, unit string) (value *big.Float, ok bool) {
divisor, ok := units[unit]
if ok {
w := new(big.Float).SetInt(wei)
value = w.Quo(w, divisor)
}
return
}
blockNumber int64
lastBlock bool
timestamp bool
difficulty bool
balance bool
header bool
format string
formatStr string
unit string
list []string
lastBlockLen int64
timestampLen int64
balanceLen int64
Miguel Montes
committed
description = "Presenta la lista de selladores. Opcionalmente presenta información sobre los selladores."
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).")
Miguel Montes
committed
flags.BoolVar(&header, "header", false, "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.")
Miguel Montes
committed
flags.StringVar(&formatStr, "output-format", "", "Formato de la salida. Ignorado en formato json. Es una string en la que se reemplazan las ocurrencias de {sealer}, {last-block}, {timestamp}, {difficulty} y {balance} por sus respectivos valores.")
flags.StringVar(&unit, "unit", "wei", "Unidades en la que se expresa el balance. Posibles valores: wei, Kwei, kilowei, Mwei, megawei, Gwei, gigawei, microether, milliether, ether, babbage, lovelace, shannon, szabo, finney.")
util.Error("El bloque génesis no tiene firmantes\n")
}
unit = strings.ToLower(unit)
if _, ok := units[unit]; !ok {
util.Error("Unidad '%v' desconocida\n", unit)
}
url = updateURL(url)
node, err := bfa.Dial(url)
blockNumber = node.BlockNumberInRange(blockNumber)
extended := lastBlock || timestamp || difficulty || balance || len(formatStr) > 0
if !extended {
printSealers(node.SealersAtBlock(blockNumber))
Miguel Montes
committed
return
}
Miguel Montes
committed
sealers := node.SealersStatus(blockNumber)
if json {
util.PrintJson(sealers)
return
}
if len(format) > 0 {
format = parseFormatString(format)
timestampLen = int64(len(format))
} else {
timestampLen = 10
}
Miguel Montes
committed
for sealer := range sealers {
list = append(list, sealer)
}
if formatStr != "" {
sort.Strings(list)
formatStr = strings.Replace(formatStr, "%", "%%", -1)
formatStr = strings.Replace(formatStr, "{sealer}", "%[1]v", -1)
formatStr = strings.Replace(formatStr, "{last-block}", "%[3]v", -1)
formatStr = strings.Replace(formatStr, "{timestamp}", "%[5]v", -1)
formatStr = strings.Replace(formatStr, "{difficulty}", "%[6]v", -1)
formatStr = strings.Replace(formatStr, "{balance}", "%[8]v", -1)
Miguel Montes
committed
} else {
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 })
}
Miguel Montes
committed
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 difficulty {
formatStr += " %2[6]v"
}
formatStr += " %#12.6[8]e"
headerFmt += " %[7]*[8]v"
balanceLen = 12
}
if header {
headerFmt += "\n"
fmt.Printf(headerFmt,
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", -(int(balanceLen)+len(unit))/2-1, strings.Title(unit)))
Miguel Montes
committed
}
formatStr += "\n"
for _, sealer := range list {
var formatedTimestamp interface{}
Miguel Montes
committed
s := sealers[sealer]
t := int64(s.Time)
formatedTimestamp = t
if len(format) > 0 && t > 0 {
Miguel Montes
committed
formatedTimestamp = time.Unix(t, 0).Format(format)
convertedBalance, _ := weiTo(s.Balance, unit)
fmt.Printf(formatStr, sealer, lastBlockLen, s.LastBlock, timestampLen, formatedTimestamp, s.Difficulty, balanceLen, convertedBalance)
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()
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)
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
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).")
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\n")
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 {
isSealer := util.Contains(votes.Signers, address)
switch { // address is not in a proposal, we only allow removing signers or adding non signers
case isSealer && authorize:
util.Error("'%v' ya es un sellador\n", address)
case !isSealer && !authorize:
util.Error("'%v' no es un sellador\n", 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"`
Time int64 `json:"timestamp"`
DateTime string `json:"datetime"`
LastBlockSigned int64 `json:"lastBlockSigned,omitempty"`
LastTimeSigned int64 `json:"lastTimeSigned,omitempty"`
DateTimeLastBlockSigned string `json:"datetimeLastSigned,omitempty"`
PeerCount uint64 `json:"peerCount"`
}{
Accounts: make(map[string]*big.Int),
}
description = "Muestra el estado del nodo."
setFlags()
url = updateURL(url)
node, err := bfa.Dial(url)
util.Check(err)
defer node.Close()
for _, account := range node.Accounts() {
nodeStatus.Accounts[account] = node.Balance(common.HexToAddress(account))
}
nodeStatus.Coinbase, _ = node.Coinbase()
nodeStatus.BlockNumber = node.BlockNumber()
header := node.HeaderByNumber(nodeStatus.BlockNumber)
nodeStatus.Time = header.Time.Int64()
nodeStatus.DateTime = time.Unix(nodeStatus.Time, 0).Format("2006-01-02 15:04:05")
nodeStatus.IsSealer = node.IsSealer(bfa.Self)
nodeStatus.IsMining = node.IsMining()
if nodeStatus.IsSealer {
nodeStatus.LastBlockSigned = node.LastBlockSignedBy(nodeStatus.Coinbase, nodeStatus.BlockNumber)
if nodeStatus.LastBlockSigned != 0 {
header := node.HeaderByNumber(nodeStatus.LastBlockSigned)
nodeStatus.LastTimeSigned = header.Time.Int64()
nodeStatus.DateTimeLastBlockSigned = time.Unix(nodeStatus.LastTimeSigned, 0).Format("2006-01-02 15:04:05")
}
}
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)")
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)")
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 transfers() {
type Transfer struct {
From bfa.Address `json:"from"`
To bfa.Address `json:"to"`
Amount *big.Int `json:"amount"`
BlockNumber int64 `json:"blockNumber"`
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
}
var (
first, last, end int64
)
description = "Muestra transferencias en un rango de bloques, y opcionalmente, restringidas a ciertas direcciones."
otherArgs = "[dirección...]"
setFlags()
flags.Int64Var(&first, "first-block", latest, "Primer bloque del rango (-1 para especificar el último bloque)")
flags.Int64Var(&last, "last-block", latest, "Último bloque del rango (-1 para especificar el último bloque)")
parseFlags(true)
url = updateURL(url)
node, err := bfa.Dial(url)
util.Check(err)
defer node.Close()
set := make(map[common.Address]bool)
txs := make([]Transfer, 0)
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)
set[common.HexToAddress(address)] = true
}
latest := node.BlockNumber()
if first < 0 {
first = latest
}
if last < 0 || last > latest {
end = latest + 1
} else {
end = last + 1
}
for first < end {
for i := first; i < end; i++ {
if node.BlockTransactionCount(i) > 0 {
block := node.BlockByNumber(i)
for _, transaction := range block.Transactions {
if !transaction.Value.IsZero() {
src := transaction.From
dst := transaction.To
if len(set) == 0 || set[src] || set[dst] {
if json {
txs = append(txs, Transfer{bfa.Address{src}, bfa.Address{dst}, (*big.Int)(transaction.Value), i})
} else {
fmt.Printf("%v -> %v: %v (%v)\n", src.Hex(), dst.Hex(), transaction.Value, transaction.BlockNumber)
}
}
}
}
}
}
first = end
if last < 0 || last > end {
latest = node.BlockNumber()
if latest < last {
end = latest + 1
} else {
end = last + 1
}
}
}
if json {
util.PrintJson(txs)
}
}
commands = map[string]func(){
"proposals": proposals,
"sealers": sealers,
"status": status,
"block": block,
"snapshot": snapshot,
for cmd := range commands {
validCommands = append(validCommands, cmd)
sort.Strings(validCommands)
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()