diff --git a/bfa_client/src/bfa/node.go b/bfa_client/src/bfa/node.go index 063bd6902fffc05add92355fbcd221e5cb077dda..89c37d4765af9b084be8322a625001b24255d085 100644 --- a/bfa_client/src/bfa/node.go +++ b/bfa_client/src/bfa/node.go @@ -7,7 +7,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" + "math/big" "sort" ) @@ -45,26 +47,61 @@ type SealerInfo struct { } type SealerStatus struct { - LastBlock int64 `json:"lastBlockSigned"` - Time uint64 `json:"timestamp"` - Difficulty uint64 `json:"difficulty"` + LastBlock int64 `json:"lastBlockSigned"` + Time uint64 `json:"timestamp"` + Difficulty uint64 `json:"difficulty"` + Balance *big.Int `json:"balance"` } const ( - Latest = "latest" - Self = "self" + Latest = "latest" + Self = "self" + Network = 47525974938 + Genesis = "0xe0e26f415af00fe32e1e8d94336355db43a78d9f59eb70910172b61fb670d99e" ) func (node *Node) Call(result interface{}, method string, args ...interface{}) { Check((*rpc.Client)(node).Call(result, method, args...)) } -func (node *Node) blockNumber() int64 { +func (node *Node) BlockNumber() int64 { var bn rpc.BlockNumber node.Call(&bn, "eth_blockNumber") return bn.Int64() } +func (node *Node) Accounts() (accounts []string) { + node.Call(&accounts, "eth_accounts") + return +} + +func (node *Node) IsMining() (mining bool) { + node.Call(&mining, "eth_mining") + return +} + +func (node *Node) PeerCount() (peerCount int64) { + node.Call(&peerCount, "net_peerCount") + return +} + +func (node *Node) BalanceAtBlock(account string, blockNumber int64) *big.Int { + var ( + balance string + block = Latest + ) + if blockNumber >= 0 { + block = Int64ToHex(blockNumber) + } + node.Call(&balance, "eth_getBalance", account, block) + n, _ := new(big.Int).SetString(balance, 0) + return n +} + +func (node *Node) Balance(account string) *big.Int { + return node.BalanceAtBlock(account, -1) +} + func (node *Node) Coinbase() (coinbase string, err error) { defer func() { if e := recover(); e != nil { @@ -84,7 +121,7 @@ func (node *Node) Coinbase() (coinbase string, err error) { return } -func (node *Node) GetBlockByNumber(blockNumber int64) types.Header { +func (node *Node) BlockByNumber(blockNumber int64) types.Header { var ( number string resp types.Header @@ -98,8 +135,8 @@ func (node *Node) GetBlockByNumber(blockNumber int64) types.Header { return resp } -func (node *Node) GetBlockSigner(blockNumber int64) (signer string) { - header := node.GetBlockByNumber(blockNumber) +func (node *Node) BlockSigner(blockNumber int64) (signer string) { + header := node.BlockByNumber(blockNumber) signer, err := GetSigner(&header) Check(err) return @@ -110,12 +147,12 @@ func (node *Node) GetSnapshot() (snapshot Snapshot) { return } -func (node *Node) GetSnapshotAtHash(hash common.Hash) (snapshot Snapshot) { +func (node *Node) SnapshotAtHash(hash common.Hash) (snapshot Snapshot) { node.Call(&snapshot, "clique_getSnapshotAtHash", hash) return } -func (node *Node) GetSnapshotAtBlock(blockNumber int64) (snapshot Snapshot) { +func (node *Node) SnapshotAtBlock(blockNumber int64) (snapshot Snapshot) { if blockNumber < 0 { return node.GetSnapshot() } @@ -123,7 +160,7 @@ func (node *Node) GetSnapshotAtBlock(blockNumber int64) (snapshot Snapshot) { return } -func (node *Node) GetSigners() (signers []string) { +func (node *Node) Sealers() (signers []string) { var s []common.Address node.Call(&s, "clique_getSigners", nil) for _, signer := range s { @@ -132,7 +169,12 @@ func (node *Node) GetSigners() (signers []string) { return } -func (node *Node) GetSignersAtHash(hash common.Hash) (signers []string) { +func (node *Node) NodeInfo() (nodeInfo p2p.NodeInfo) { + node.Call(&nodeInfo, "admin_nodeInfo", nil) + return +} + +func (node *Node) SealersAtHash(hash common.Hash) (signers []string) { var s []common.Address node.Call(&signers, "clique_getSignersAtHash", hash) for _, signer := range s { @@ -141,10 +183,10 @@ func (node *Node) GetSignersAtHash(hash common.Hash) (signers []string) { return } -func (node *Node) GetSignersAtBlock(blockNumber int64) (signers []string) { +func (node *Node) SealersAtBlock(blockNumber int64) (signers []string) { var s []common.Address if blockNumber < 0 { - return node.GetSigners() + return node.Sealers() } node.Call(&s, "clique_getSigners", Int64ToHex(blockNumber)) for _, signer := range s { @@ -160,7 +202,7 @@ func (node *Node) IsSealer(address string) bool { return false } } - return Contains(node.GetSigners(), address) + return Contains(node.Sealers(), address) } func (node *Node) Propose(address string, vote bool) { @@ -168,14 +210,14 @@ func (node *Node) Propose(address string, vote bool) { return } -func (node *Node) GetVotes(blockNumber int64) (votes Proposals) { +func (node *Node) Votes(blockNumber int64) (votes Proposals) { var ( snapshot Snapshot ) if blockNumber < 0 { snapshot = node.GetSnapshot() } else { - snapshot = node.GetSnapshotAtBlock(blockNumber) + snapshot = node.SnapshotAtBlock(blockNumber) } votes.BlockNumber = int64(snapshot.Number) for signer := range snapshot.Signers { @@ -213,11 +255,13 @@ func (node *Node) SealersStatus(blockNumber int64) (status map[string]*SealerSta return } status = make(map[string]*SealerStatus) - for _, address := range node.GetSignersAtBlock(blockNumber) { - status[address] = &SealerStatus{} + for _, address := range node.SealersAtBlock(blockNumber) { + status[address] = &SealerStatus{ + Balance: node.BalanceAtBlock(address, blockNumber), + } } notSeen := int64(len(status)) - block := node.GetBlockByNumber(blockNumber) + block := node.BlockByNumber(blockNumber) blockNumber = block.Number.Int64() until := Max(1, blockNumber-5*notSeen) for notSeen > 0 { @@ -232,21 +276,21 @@ func (node *Node) SealersStatus(blockNumber int64) (status map[string]*SealerSta break } blockNumber-- - block = node.GetBlockByNumber(blockNumber) + block = node.BlockByNumber(blockNumber) } return status } -func (node *Node) GetSealerInception(address string) (since int64) { - if signers := node.GetSigners(); !Contains(signers, address) { +func (node *Node) SealerInception(address string) (since int64) { + if signers := node.Sealers(); !Contains(signers, address) { return -1 } lo := int64(0) - hi := node.blockNumber() + hi := node.BlockNumber() for lo < hi { mid := lo + (hi-lo)/2 - signers := node.GetSignersAtBlock(mid) + signers := node.SealersAtBlock(mid) if Contains(signers, address) { hi = mid } else { @@ -256,11 +300,11 @@ func (node *Node) GetSealerInception(address string) (since int64) { return hi } -func (node *Node) getSignerFirstBlock(signer string, since int64, until int64) (blockNumber int64) { +func (node *Node) signerFirstBlock(signer string, since int64, until int64) (blockNumber int64) { if since < 0 { return -1 } - //snapshot := node.GetSnapshotAtBlock(since) + //snapshot := node.SnapshotAtBlock(since) var ( count int64 = 20 found int64 = -1 @@ -320,7 +364,7 @@ func (node *Node) getSignerFirstBlock(signer string, since int64, until int64) ( } } -func (node *Node) getSignerLastBlock(signer string, since int64, until int64) (blockNumber int64) { +func (node *Node) signerLastBlock(signer string, since int64, until int64) (blockNumber int64) { if since < 0 { return -1 } @@ -390,7 +434,7 @@ func (node *Node) searchSnapshotForward(blockNumber int64, signer string, count return } for count > 0 { - snapshot := node.GetSnapshotAtBlock(blockNumber + count - 1) + snapshot := node.SnapshotAtBlock(blockNumber + count - 1) recents := int64(len(snapshot.Recents)) visited += recents count -= recents @@ -410,7 +454,7 @@ func (node *Node) searchSnapshotBackward(blockNumber int64, signer string, count } count = Min(count, blockNumber) // Never search below block 1 for { - snapshot := node.GetSnapshotAtBlock(blockNumber - visited) + snapshot := node.SnapshotAtBlock(blockNumber - visited) visited += int64(len(snapshot.Recents)) if count == 0 { count = Min(blockNumber, int64(2*len(snapshot.Signers))) @@ -429,13 +473,13 @@ func (node *Node) searchSnapshotBackward(blockNumber int64, signer string, count func (node *Node) SealerInfo(sealer string) (info SealerInfo, err error) { info.Address = sealer - info.CurrentBlock = node.blockNumber() - info.Since = node.GetSealerInception(sealer) + info.CurrentBlock = node.BlockNumber() + info.Since = node.SealerInception(sealer) if info.Since == -1 { return info, fmt.Errorf("%q is not a sealer", sealer) } - info.FirstBlock = node.getSignerFirstBlock(sealer, info.Since+1, info.CurrentBlock) - info.LastBlock = node.getSignerLastBlock(sealer, info.FirstBlock, info.CurrentBlock) + info.FirstBlock = node.signerFirstBlock(sealer, info.Since+1, info.CurrentBlock) + info.LastBlock = node.signerLastBlock(sealer, info.FirstBlock, info.CurrentBlock) return } diff --git a/bfa_client/src/client/bfa_client.go b/bfa_client/src/client/bfa_client.go index d2715c001e00c3f05b43f3067c06da2ade818d68..d29ed66817379756c4df2f761fc37259b11745bd 100644 --- a/bfa_client/src/client/bfa_client.go +++ b/bfa_client/src/client/bfa_client.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "log" + "math/big" "os" "path" "sort" @@ -26,7 +27,7 @@ var ( url string json bool help bool - flags flag.FlagSet + flags = flag.NewFlagSet("", flag.ExitOnError) command string description string otherArgs string @@ -83,7 +84,7 @@ func proposals() { node, err := bfa.Dial(url) util.Check(err) defer node.Close() - votes := node.GetVotes(blockNumber) + votes := node.Votes(blockNumber) if json { util.PrintJson(votes) return @@ -140,16 +141,21 @@ func parseFormatString(s string) (format string) { func sealers() { var ( blockNumber int64 - status bool + lastBlock bool timestamp bool + difficulty bool + balance bool + header 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.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() if blockNumber == 0 { @@ -159,42 +165,80 @@ func sealers() { node, err := bfa.Dial(url) util.Check(err) defer node.Close() - if status { + extended := lastBlock || timestamp || difficulty || balance + if extended { sealers := node.SealersStatus(blockNumber) if json { util.PrintJson(sealers) return } - var list []string + var ( + list []string + lastBlockLen int64 + timestampLen int64 + balanceLen int64 + ) 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 { + 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) - length = int64(len(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 output interface{} + var formatedTimestamp interface{} + s := sealers[sealer] if timestamp { - t := int64(sealers[sealer].Time) + t := int64(s.Time) if len(format) > 0 && t > 0 { - output = time.Unix(t, 0).Format(format) + formatedTimestamp = time.Unix(t, 0).Format(format) } else { - output = t + formatedTimestamp = t } - } else { - output = sealers[sealer].LastBlock } - fmt.Printf("%v: %*v\n", sealer, length, output) + fmt.Printf(formatStr, sealer, lastBlockLen, s.LastBlock, timestampLen, formatedTimestamp, s.Difficulty, balanceLen, s.Balance) } } else { - sealers := node.GetSignersAtBlock(blockNumber) + sealers := node.SealersAtBlock(blockNumber) sort.Slice(sealers, func(i, j int) bool { return sealers[i] < sealers[j] }) if json { util.PrintJson(sealers) @@ -226,8 +270,8 @@ func autovote() { 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) + votes := node.Votes(latest) + genesisSigners := node.SealersAtBlock(0) self, err := node.Coinbase() util.Check(err) for _, tally := range votes.Tally { @@ -279,7 +323,7 @@ func propose() { util.Check(err) defer node.Close() util.PanicIf(!node.IsSealer(bfa.Self), "Solo los selladores pueden votar") - votes := node.GetVotes(latest) + votes := node.Votes(latest) if flags.NArg() == 0 { panic("No se especificaron candidatos por los cuales votar") } @@ -310,6 +354,45 @@ func propose() { } } +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 main() { var ( commands = map[string]func(){ @@ -317,6 +400,7 @@ func main() { "sealers": sealers, "vote": propose, "autovote": autovote, + "status": status, } validCommands []string ) @@ -326,7 +410,6 @@ func main() { defer func() { if err := recover(); err != nil { log.Printf("Error: %s", err) - usage(1) } }() if len(os.Args) > 1 {