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(monitor): add zkevm batch counters #373

Merged
merged 9 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 55 additions & 18 deletions cmd/monitor/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ type (
HeadBlock *big.Int
PeerCount uint64
GasPrice *big.Int
PendingCount uint64
QueuedCount uint64
TxPoolStatus txPoolStatus
ZkEVMBatches zkEVMBatches
SelectedBlock rpctypes.PolyBlock
SelectedTransaction rpctypes.PolyTransaction
BlockCache *lru.Cache `json:"-"`
Expand All @@ -76,8 +76,17 @@ type (
ChainID *big.Int
PeerCount uint64
GasPrice *big.Int
PendingCount uint64
QueuedCount uint64
TxPoolStatus txPoolStatus
ZkEVMBatches zkEVMBatches
}
txPoolStatus struct {
pending uint64
queued uint64
}
zkEVMBatches struct {
trusted uint64
virtual uint64
verified uint64
}
historicalDataPoint struct {
SampleTime time.Time
Expand Down Expand Up @@ -111,6 +120,22 @@ func monitor(ctx context.Context) error {
return errBatchRequestsNotSupported
}

// Check if tx pool status is supported.
txPoolStatusSupported := false
if _, _, err = util.GetTxPoolStatus(rpc); err != nil {
log.Debug().Err(err).Msg("Unable to get tx pool status")
} else {
txPoolStatusSupported = true
}

// Check if zkevm batches are supported.
zkEVMBatchesSupported := false
if _, _, _, err = util.GetZkEVMBatches(rpc); err != nil {
log.Debug().Err(err).Msg("Unable to get zkevm batches")
} else {
zkEVMBatchesSupported = true
}

ms := new(monitorStatus)
ms.BlocksLock.Lock()
ms.BlockCache, err = lru.New(blockCacheLimit)
Expand All @@ -121,8 +146,8 @@ func monitor(ctx context.Context) error {
ms.BlocksLock.Unlock()

ms.ChainID = big.NewInt(0)
ms.PendingCount = 0
ms.QueuedCount = 0
ms.TxPoolStatus = txPoolStatus{}
ms.ZkEVMBatches = zkEVMBatches{}

observedPendingTxs = make(historicalRange, 0)

Expand All @@ -148,7 +173,7 @@ func monitor(ctx context.Context) error {
}
if !isUiRendered {
go func() {
errChan <- renderMonitorUI(ctx, ec, ms, rpc)
errChan <- renderMonitorUI(ctx, ec, ms, rpc, txPoolStatusSupported, zkEVMBatchesSupported)
}()
isUiRendered = true
}
Expand Down Expand Up @@ -186,9 +211,14 @@ func getChainState(ctx context.Context, ec *ethclient.Client) (*chainState, erro
return nil, fmt.Errorf("couldn't estimate gas: %s", err.Error())
}

cs.PendingCount, cs.QueuedCount, err = util.GetTxPoolStatus(ec.Client())
cs.TxPoolStatus.pending, cs.TxPoolStatus.queued, err = util.GetTxPoolStatus(ec.Client())
if err != nil {
log.Debug().Err(err).Msg("Unable to get pending and queued transaction count")
log.Debug().Err(err).Msg("Unable to get tx pool status")
}

cs.ZkEVMBatches.trusted, cs.ZkEVMBatches.virtual, cs.ZkEVMBatches.verified, err = util.GetZkEVMBatches(ec.Client())
if err != nil {
log.Debug().Err(err).Msg("Unable to get zkevm batches")
}

return cs, nil
Expand All @@ -214,7 +244,7 @@ func fetchCurrentBlockData(ctx context.Context, ec *ethclient.Client, ms *monito
time.Sleep(interval)
return err
}
observedPendingTxs = append(observedPendingTxs, historicalDataPoint{SampleTime: time.Now(), SampleValue: float64(cs.PendingCount)})
observedPendingTxs = append(observedPendingTxs, historicalDataPoint{SampleTime: time.Now(), SampleValue: float64(cs.TxPoolStatus.pending)})
if len(observedPendingTxs) > maxDataPoints {
observedPendingTxs = observedPendingTxs[len(observedPendingTxs)-maxDataPoints:]
}
Expand All @@ -231,8 +261,8 @@ func fetchCurrentBlockData(ctx context.Context, ec *ethclient.Client, ms *monito
ms.ChainID = cs.ChainID
ms.PeerCount = cs.PeerCount
ms.GasPrice = cs.GasPrice
ms.PendingCount = cs.PendingCount
ms.QueuedCount = cs.QueuedCount
ms.TxPoolStatus = cs.TxPoolStatus
ms.ZkEVMBatches = cs.ZkEVMBatches

return
}
Expand Down Expand Up @@ -366,7 +396,7 @@ func (ms *monitorStatus) processBatchesConcurrently(ctx context.Context, rpc *et
return errors.Join(errs...)
}

func renderMonitorUI(ctx context.Context, ec *ethclient.Client, ms *monitorStatus, rpc *ethrpc.Client) error {
func renderMonitorUI(ctx context.Context, ec *ethclient.Client, ms *monitorStatus, rpc *ethrpc.Client, txPoolStatusSupported, zkEVMBatchesSupported bool) error {
if err := termui.Init(); err != nil {
log.Error().Err(err).Msg("Failed to initialize UI")
return err
Expand All @@ -375,7 +405,7 @@ func renderMonitorUI(ctx context.Context, ec *ethclient.Client, ms *monitorStatu

currentMode := monitorModeExplorer

blockTable, blockInfo, transactionList, transactionInformationList, transactionInfo, grid, selectGrid, blockGrid, transactionGrid, skeleton := ui.SetUISkeleton()
blockTable, blockInfo, transactionList, transactionInformationList, transactionInfo, grid, selectGrid, blockGrid, transactionGrid, skeleton := ui.SetUISkeleton(txPoolStatusSupported, zkEVMBatchesSupported)

termWidth, termHeight := termui.TerminalDimensions()
windowSize = termHeight/2 - 4
Expand Down Expand Up @@ -476,8 +506,8 @@ func renderMonitorUI(ctx context.Context, ec *ethclient.Client, ms *monitorStatu
Str("HeadBlock", ms.HeadBlock.String()).
Uint64("PeerCount", ms.PeerCount).
Str("GasPrice", ms.GasPrice.String()).
Uint64("PendingCount", ms.PendingCount).
Uint64("QueuedCount", ms.QueuedCount).
Interface("TxPoolStatus", ms.TxPoolStatus).
Interface("ZkEVMBatches", ms.ZkEVMBatches).
Msg("Redrawing")

if blockTable.SelectedRow == 0 {
Expand Down Expand Up @@ -509,8 +539,15 @@ func renderMonitorUI(ctx context.Context, ec *ethclient.Client, ms *monitorStatu
ms.BlocksLock.RUnlock()
renderedBlocks = renderedBlocksTemp

log.Debug().Int("skeleton.Current.Inner.Dy()", skeleton.Current.Inner.Dy()).Int("skeleton.Current.Inner.Dx()", skeleton.Current.Inner.Dx()).Msg("the dimension of the current box")
skeleton.Current.Text = ui.GetCurrentBlockInfo(ms.HeadBlock, ms.GasPrice, ms.PeerCount, ms.PendingCount, ms.QueuedCount, ms.ChainID, rpcUrl, renderedBlocks, skeleton.Current.Inner.Dx(), skeleton.Current.Inner.Dy())
skeleton.Current.Text = ui.GetCurrentText(skeleton.Current, ms.HeadBlock, ms.GasPrice, ms.PeerCount, ms.ChainID, rpcUrl)
if txPoolStatusSupported {
skeleton.TxPool.Text = ui.GetTxPoolText(skeleton.TxPool, ms.TxPoolStatus.pending, ms.TxPoolStatus.queued)
}

if zkEVMBatchesSupported {
skeleton.ZkEVM.Text = ui.GetZkEVMText(skeleton.ZkEVM, ms.ZkEVMBatches.trusted, ms.ZkEVMBatches.virtual, ms.ZkEVMBatches.verified)
}

skeleton.TxPerBlockChart.Data = metrics.GetTxsPerBlock(renderedBlocks)
skeleton.GasPriceChart.Data = metrics.GetMeanGasPricePerBlock(renderedBlocks)
skeleton.BlockSizeChart.Data = metrics.GetSizePerBlock(renderedBlocks)
Expand Down
103 changes: 75 additions & 28 deletions cmd/monitor/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,58 @@ import (
)

type UiSkeleton struct {
Current *widgets.Paragraph
TxPerBlockChart *widgets.Sparkline
GasPriceChart *widgets.Sparkline
BlockSizeChart *widgets.Sparkline
PendingTxChart *widgets.Sparkline
GasChart *widgets.Sparkline
BlockInfo *widgets.List
TxInfo *widgets.List
Receipts *widgets.List
Current, TxPool, ZkEVM *widgets.Paragraph
TxPerBlockChart *widgets.Sparkline
GasPriceChart *widgets.Sparkline
BlockSizeChart *widgets.Sparkline
PendingTxChart *widgets.Sparkline
GasChart *widgets.Sparkline
BlockInfo *widgets.List
TxInfo *widgets.List
Receipts *widgets.List
}

func GetCurrentBlockInfo(headBlock *big.Int, gasPrice *big.Int, peerCount uint64, pendingCount uint64, queuedCount uint64, chainID *big.Int, rpcURL string, blocks []rpctypes.PolyBlock, dx int, dy int) string {
// Return an appropriate message if dy is 0 or less.
if dy <= 0 {
return "Invalid display configuration."
}

func GetCurrentText(widget *widgets.Paragraph, headBlock, gasPrice *big.Int, peerCount uint64, chainID *big.Int, rpcURL string) string {
// First column
height := fmt.Sprintf("Height: %s", headBlock.String())
timeInfo := fmt.Sprintf("Time: %s", time.Now().Format("02 Jan 06 15:04:05 MST"))
gasPriceString := fmt.Sprintf("Gas Price: %s gwei", new(big.Int).Div(gasPrice, metrics.UnitShannon).String())
peers := fmt.Sprintf("Peers: %d", peerCount)
pendingTx := fmt.Sprintf("Pending Tx: %d", pendingCount)
queuedTx := fmt.Sprintf("Queued Tx: %d", queuedCount)
chainIdString := fmt.Sprintf("Chain ID: %s", chainID.String())

// Second column
rpcURLString := fmt.Sprintf("RPC URL: %s", rpcURL)
chainIdString := fmt.Sprintf("Chain ID: %s", chainID.String())

return formatParagraph(widget, []string{height, timeInfo, gasPriceString, peers, chainIdString, rpcURLString})
}

func GetTxPoolText(widget *widgets.Paragraph, pendingTxCount, queuedTxCount uint64) string {
pendingTx := fmt.Sprintf("Pending Tx: %d", pendingTxCount)
queuedTx := fmt.Sprintf("Queued Tx: %d", queuedTxCount)
return formatParagraph(widget, []string{pendingTx, queuedTx})
}

func GetZkEVMText(widget *widgets.Paragraph, trustedBatchesCount, virtualBatchesCount, verifiedBatchesCount uint64) string {
trustedBatches := fmt.Sprintf("Trusted: %d", trustedBatchesCount)
trustedVirtualBatchesGap := trustedBatchesCount - virtualBatchesCount
virtualBatches := fmt.Sprintf("Virtual: %d (%d)", virtualBatchesCount, trustedVirtualBatchesGap)

trustedVerifiedBatchesGap := trustedBatchesCount - verifiedBatchesCount
verifiedBatches := fmt.Sprintf("Verified: %d (%d)", verifiedBatchesCount, trustedVerifiedBatchesGap)
return formatParagraph(widget, []string{trustedBatches, virtualBatches, verifiedBatches})
}

info := []string{height, timeInfo, gasPriceString, peers, pendingTx, queuedTx, chainIdString, rpcURLString}
columns := len(info) / dy
if len(info)%dy != 0 {
func formatParagraph(widget *widgets.Paragraph, content []string) string {
dx := widget.Inner.Dx()
dy := widget.Inner.Dy()

// Return an appropriate message if dy is 0 or less.
if dy <= 0 {
return "Invalid display configuration."
}

columns := len(content) / dy
if len(content)%dy != 0 {
columns += 1 // Add an extra column for the remaining items
}

Expand All @@ -57,8 +80,8 @@ func GetCurrentBlockInfo(headBlock *big.Int, gasPrice *big.Int, peerCount uint64
for i := 0; i < columns; i++ {
for j := 0; j < dy; j++ {
index := i*dy + j
if index < len(info) && len(info[index]) > columnWidths[i] {
columnWidths[i] = len(info[index])
if index < len(content) && len(content[index]) > columnWidths[i] {
columnWidths[i] = len(content[index])
}
}
// Add padding and ensure it doesn't exceed 'dx'
Expand All @@ -72,9 +95,9 @@ func GetCurrentBlockInfo(headBlock *big.Int, gasPrice *big.Int, peerCount uint64
for i := 0; i < dy; i++ {
for j := 0; j < columns; j++ {
index := j*dy + i
if index < len(info) {
if index < len(content) {
formatString := fmt.Sprintf("%%-%ds", columnWidths[j])
formattedInfo.WriteString(fmt.Sprintf(formatString, info[index]))
formattedInfo.WriteString(fmt.Sprintf(formatString, content[index]))
}
}
formattedInfo.WriteString("\n")
Expand Down Expand Up @@ -449,7 +472,7 @@ func GetSimpleReceipt(ctx context.Context, rpc *ethrpc.Client, tx rpctypes.PolyT
return fields
}

func SetUISkeleton() (blockList *widgets.List, blockInfo *widgets.List, transactionList *widgets.List, transactionInformationList *widgets.List, transactionInfo *widgets.Table, grid *ui.Grid, selectGrid *ui.Grid, blockGrid *ui.Grid, transactionGrid *ui.Grid, termUi UiSkeleton) {
func SetUISkeleton(txPoolStatusSupported, zkEVMBatchesSupported bool) (blockList *widgets.List, blockInfo *widgets.List, transactionList *widgets.List, transactionInformationList *widgets.List, transactionInfo *widgets.Table, grid *ui.Grid, selectGrid *ui.Grid, blockGrid *ui.Grid, transactionGrid *ui.Grid, termUi UiSkeleton) {
// help := widgets.NewParagraph()
// help.Title = "Block Headers"
// help.Text = "Use the arrow keys to scroll through the transactions. Press <Esc> to go back to the explorer view"
Expand All @@ -470,8 +493,32 @@ func SetUISkeleton() (blockList *widgets.List, blockInfo *widgets.List, transact

termUi = UiSkeleton{}

// Top row
termUi.Current = widgets.NewParagraph()
termUi.Current.Title = "Current"
totalWidgets := 1

if txPoolStatusSupported {
termUi.TxPool = widgets.NewParagraph()
termUi.TxPool.Title = "TxPool"
totalWidgets++
}

if zkEVMBatchesSupported {
termUi.ZkEVM = widgets.NewParagraph()
termUi.ZkEVM.Title = "ZkEVM Batch No."
totalWidgets++
}

topRowBlocks := []interface{}{
ui.NewCol((5.0-float64(totalWidgets-1))/5.0, termUi.Current),
}
if txPoolStatusSupported {
topRowBlocks = append(topRowBlocks, ui.NewCol(1.0/5.0, termUi.TxPool))
}
if zkEVMBatchesSupported {
topRowBlocks = append(topRowBlocks, ui.NewCol(1.0/5.0, termUi.ZkEVM))
}

termUi.TxPerBlockChart = widgets.NewSparkline()
termUi.TxPerBlockChart.LineColor = ui.ColorRed
Expand Down Expand Up @@ -538,7 +585,7 @@ func SetUISkeleton() (blockList *widgets.List, blockInfo *widgets.List, transact
termUi.Receipts.WrapText = true

grid.Set(
ui.NewRow(1.0/10, termUi.Current),
ui.NewRow(1.0/10, topRowBlocks...),

ui.NewRow(2.0/10,
ui.NewCol(1.0/5, slg0),
Expand All @@ -558,7 +605,7 @@ func SetUISkeleton() (blockList *widgets.List, blockInfo *widgets.List, transact
)

selectGrid.Set(
ui.NewRow(1.0/10, termUi.Current),
ui.NewRow(1.0/10, topRowBlocks...),

ui.NewRow(2.0/10,
ui.NewCol(1.0/5, slg0),
Expand Down
40 changes: 40 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/cenkalti/backoff"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/clique"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
Expand Down Expand Up @@ -203,6 +204,45 @@ func GetTxPoolStatus(rpc *ethrpc.Client) (uint64, uint64, error) {
return pendingCount, queuedCount, nil
}

func GetZkEVMBatches(rpc *ethrpc.Client) (uint64, uint64, uint64, error) {
trustedBatches, err := getZkEVMBatch(rpc, trusted)
if err != nil {
return 0, 0, 0, err
}

virtualBatches, err := getZkEVMBatch(rpc, virtual)
if err != nil {
return trustedBatches, 0, 0, err
}

verifiedBatches, err := getZkEVMBatch(rpc, verified)
if err != nil {
return trustedBatches, virtualBatches, 0, err
}

return trustedBatches, virtualBatches, verifiedBatches, nil
}

type batch string

const (
trusted batch = "zkevm_batchNumber"
virtual batch = "zkevm_virtualBatchNumber"
verified batch = "zkevm_verifiedBatchNumber"
)

func getZkEVMBatch(rpc *ethrpc.Client, batchType batch) (uint64, error) {
var raw interface{}
if err := rpc.Call(&raw, string(batchType)); err != nil {
return 0, err
}
batch, err := hexutil.DecodeUint64(fmt.Sprintf("%v", raw))
if err != nil {
return 0, err
}
return batch, nil
}

func tryCastToUint64(val any) (uint64, error) {
switch t := val.(type) {
case float64:
Expand Down