Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dashboard #7

Merged
merged 13 commits into from
Oct 2, 2023
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This project started out as a fork of [Cosmos-IE](https://github.com/node-a-team

## Dashbord

### Grafana

<details close>

<summary>General</summary>
Expand All @@ -33,6 +35,32 @@ https://user-images.githubusercontent.com/31609693/200193555-2e5f6bc4-ecf4-4332-

</details>

### Local

<details close>

<summary>Missing Validators per Block</summary>

https://github.com/jim380/Cendermint/assets/31609693/1df264df-9c54-4a60-b3f0-5c9d0e98aa3c

</details>

<details close>

<summary>Node Info</summary>

![consensus](assets/node.jpg)

</details>

<details close>

<summary>Consensus</summary>

![consensus](assets/consensus.jpg)

</details>

## Supported chains

<details><summary>Click to view</summary>
Expand Down
Binary file added assets/consensus.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/node.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions component.block.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{{ define "GetBlockInfo" }}
<div
{{
state
.
}}
name="GetBlockInfo"
ssa:poll="Action(this, 'Reload Block')"
ssa:poll.interval="5" {{/* 5 seconds */}}
>
<div><b>Chain Id</b>: {{ .Block.Header.ChainID }}</div>
<div><b>Height</b>: {{ .Block.Header.Height }}</div>
<div><b>Timestamp</b>: {{ .Block.Header.Timestamp }}</div>
<div><b>Hash</b>: {{ .BlockId.Hash }}</div>
<div><b>Validator Signatures</b>: {{ len .Block.LastCommit.Signatures }}</div>
{{ if .MissingValidators }}
<b>Missing Validators</b>
<table>
<tr>
<th>Moniker</th>
<th>Consensus Hex Address</th>
</tr>
{{ range .MissingValidators }}
<tr>
<td>{{ .Moniker }}</td>
<td>{{ .ConsHexAddr }}</td>
</tr>
{{ end }}
</table>
{{ end }}
</div>
{{ end }}
34 changes: 34 additions & 0 deletions component.consensus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{ define "GetConsensusInfo" }}
<div
{{
state
.
}}
name="GetConsensusInfo"
ssa:poll="Action(this, 'Reload Consensus')"
ssa:poll.interval="5" {{/* 5 seconds */}}
>
<div><b>Height</b>: {{ .ConsensusState.Result.Height }}</div>
<div><b>Round</b>: {{ .ConsensusState.Result.Round }}</div>
<div><b>Step</b>: {{ .ConsensusState.Result.Step }}</div>
<div><b>Prevotes Bit Array</b>: {{ (index .ConsensusState.Result.Votes 0).PrevotesBitArray}}</div>
<div><b>Precommits Bit Array</b>: {{ (index .ConsensusState.Result.Votes 0).PrecommitsBitArray}}</div>
<div><b>Total Validators</b>: {{ len .Validatorsets}}</div>
<table>
<tr>
<th>Moniker</th>
<th>Voting Power</th>
<th>Prevote</th>
<th>Precommit</th>
</tr>
{{ range $key, $value := .Validatorsets }}
<tr>
<td>{{ index $value 5 }}</td>
<td>{{ index $value 1 }}</td>
<td>{{ index $value 3 }}</td>
<td>{{ index $value 4 }}</td>
</tr>
{{ end }}
</table>
</div>
{{ end }}
22 changes: 22 additions & 0 deletions component.node.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{{ define "GetNodeInfo" }}
<div
{{
state
.
}}
name="GetNodeInfo"
{{/* no refresh since these don't change by block */}}
{{/* ssa:poll="Action(this, 'Reload Node')"
ssa:poll.interval="5" */}}
>
<div><b>Node Id</b>: {{ .Default.NodeID }}</div>
<div><b>Tendermint Version</b>: {{ .Default.TMVersion }}</div>
<div><b>Moniker</b>: {{ .Default.Moniker }}</div>
<div><b>App Name</b>: {{ .Application.AppName }}</div>
<div><b>Name</b>: {{ .Application.Name }}</div>
<div><b>Version</b>: {{ .Application.Version }}</div>
<div><b>Git Commit</b>: {{ .Application.GitCommit }}</div>
<div><b>Go Version</b>: {{ .Application.GoVersion }}</div>
<div><b>SDK Version</b>: {{ .Application.SDKVersion }}</div>
</div>
{{ end }}
9 changes: 6 additions & 3 deletions config.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ CHAIN=
OPERATOR_ADDR=
REST_ADDR=http://localhost:1317
RPC_ADDR=http://localhost:1317
# Gravity Bridge
UMEE_ORCH_ADDR=umee1uyjwndy7kcrcs84n9l9pvheqwl8e3e526afq5y
ETH_ORCH_ADDR=0x182Ab18c41Bcf7f3dB018159A62900906f719649
LISTENING_PORT=26661
MISS_THRESHOLD=10
MISS_CONSECUTIVE=2
Expand All @@ -11,6 +14,6 @@ LOG_OUTPUT=console
POLL_INTERVAL=5
# debug, info, warn, error, dpanic, panic, fatal
LOG_LEVEL=info
# for Umee only
UMEE_ORCH_ADDR=umee1uyjwndy7kcrcs84n9l9pvheqwl8e3e526afq5y
ETH_ORCH_ADDR=0x182Ab18c41Bcf7f3dB018159A62900906f719649
# dashboard
DASHBOARD_ENABLED=true
DASHBOARD_PORT=8080
25 changes: 13 additions & 12 deletions config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ import (
)

type Config struct {
Chain Chain
ChainList map[string][]string
SDKVersion string
OperatorAddr string
RestAddr string
RpcAddr string
ListeningPort string
MissThreshold string
MissConsecutive string
LogOutput string
PollInterval string
LogLevel string
Chain Chain
ChainList map[string][]string
SDKVersion string
OperatorAddr string
RestAddr string
RpcAddr string
ListeningPort string
MissThreshold string
MissConsecutive string
LogOutput string
PollInterval string
LogLevel string
DashboardEnabled string
}

type Chain struct {
Expand Down
121 changes: 121 additions & 0 deletions dashboard/components/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package components

import (
"encoding/json"
"log"

"github.com/jim380/Cendermint/rest"
"github.com/jim380/Cendermint/utils"
"github.com/kyoto-framework/kyoto/v2"
"go.uber.org/zap"
)

/*
Component
- Each component is a context receiver, which returns its state
- Each component becomes a part of the page or top-level component,
which executes component asynchronously and gets a state future object
- Context holds common objects like http.ResponseWriter, *http.Request, etc
*/
func GetBlockInfo(ctx *kyoto.Context) (state rest.Blocks) {
route := "/cosmos/base/tendermint/v1beta1/blocks/latest" //TO-DO refactor this
fetchBlockInfo := func() rest.Blocks {
var state rest.Blocks
resp, err := rest.HttpQuery(rest.RESTAddr + route)
if err != nil {
zap.L().Fatal("Connection to REST failed", zap.Bool("Success", false), zap.String("err:", err.Error()))
return rest.Blocks{}
}

err = json.Unmarshal(resp, &state)
if err != nil {
zap.L().Fatal("Failed to unmarshal response", zap.Bool("Success", false), zap.String("err:", err.Error()))
return rest.Blocks{}
}

// convert block hash from base64 to hex
hashInHex, err := utils.Base64ToHex(state.BlockId.Hash)
if err != nil {
zap.L().Fatal("Failed to convert base64 to hex", zap.Bool("Success", false), zap.String("err:", err.Error()))
return rest.Blocks{}
}
state.BlockId.Hash = hashInHex

/*
Find validators with missing signatures in the block
*/
var cs rest.ConsensusState
var activeSet map[string][]string = make(map[string][]string)

resp, err = rest.HttpQuery(rest.RPCAddr + "/dump_consensus_state")
if err != nil {
zap.L().Fatal("Connection to REST failed", zap.Bool("Success", false), zap.String("err:", err.Error()))
return rest.Blocks{}
}

err = json.Unmarshal(resp, &cs)
if err != nil {
zap.L().Fatal("Failed to unmarshal response", zap.Bool("Success", false), zap.String("err:", err.Error()))
return rest.Blocks{}
}

conspubMonikerMap := GetConspubMonikerMap()
// range over all validators in the active set
for _, validator := range cs.Result.Validatorset.Validators {
// get moniker
validator.Moniker = conspubMonikerMap[validator.ConsPubKey.Key]
// populate the map => [ConsAddr]{consPubKey, moniker}; ConsAddr is in hex coming back from rpc
activeSet[validator.ConsAddr] = []string{validator.ConsPubKey.Key, validator.Moniker}
}

/*
- Create a map validatorConsAddrInHexSignedMap using allSignaturesInBlock for quick lookup
- validatorConsAddrInHexSignedMap gives all validators who signed on this block
*/
allSignaturesInBlock := state.Block.LastCommit.Signatures
validatorConsAddrInHexSignedMap := make(map[string]bool)
for _, signature := range allSignaturesInBlock {
// Validator_address could be in hex or base64; hex is legacy so using base64 here
validatorConsAddrInHexSignedMap[signature.Validator_address] = true
}

// Check if validator.ConsAddr in activeSet exists in validatorConsAddrInHexSignedMap
for consAddrInHex, props := range activeSet {
// convert consAddrInHex to base64
if _, exists := validatorConsAddrInHexSignedMap[utils.HexToBase64(consAddrInHex)]; !exists {
// If the Validator_address does not exist in allSignaturesInBlock, add it to MissingValidators
state.MissingValidators = append(state.MissingValidators, struct {
Moniker string
ConsHexAddr string
}{
Moniker: props[1],
ConsHexAddr: consAddrInHex,
// TO-DO add operator address
})
}
}

return state
}

/*
Handle Actions
- To call an action of parent component, use $ prefix in action name
- To call an action of component by id, use <id:action> as an action name
- To push multiple component UI updates during a single action call,
call kyoto.ActionFlush(ctx, state) to initiate an update
*/
handled := kyoto.Action(ctx, "Reload Block", func(args ...any) {
// add logic here
state = fetchBlockInfo()
log.Println("New block info fetched on block", state.Block.Header.Height)
})
// Prevent further execution if action handled
if handled {
return
}
// Default loading behavior if not handled
state = fetchBlockInfo()

return
}
Loading
Loading