Skip to content

Conversation

@tclemos
Copy link
Contributor

@tclemos tclemos commented Dec 29, 2025

closes https://github.com/0xPolygon/devtools/issues/479

Adds a new report command to generate comprehensive blockchain analysis reports for Ethereum-compatible chains. Supports JSON, HTML, and PDF output formats with statistics, visualizations, and performance optimizations.

Features

  • Multiple output formats: JSON, HTML and PDF
  • Comprehensive metrics: Block/transaction counts, gas usage patterns, unique addresses, base fee analysis
  • Top 10 analysis: Highest transaction/gas blocks, most used gas prices/limits
  • Performance optimizations:
    • Concurrent RPC requests (configurable, default: 10)
    • Rate limiting (default: 4 req/s)
    • Batch receipt fetching per block

Usage

JSON

polycli report --rpc-url http://localhost:8545 --start-block 1000 --end-block 2000

HTML

polycli report --rpc-url http://localhost:8545 --start-block 1000 --end-block 2000 --format html

PDF

polycli report --rpc-url http://localhost:8545 --start-block 1000 --end-block 2000 --format pdf -o report.pdf

@tclemos tclemos self-assigned this Dec 29, 2025
@tclemos tclemos changed the title Thiago/cmd report feat: add new report cmd Dec 29, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new report command to generate comprehensive blockchain analysis reports for Ethereum-compatible chains. The command fetches block and transaction data via RPC, calculates statistics, and outputs results in JSON, HTML, or PDF format with support for concurrent requests and rate limiting.

Key changes:

  • New report command with JSON/HTML/PDF output formats
  • Concurrent block fetching (configurable, default 10 workers) with rate limiting (default 4 req/s)
  • Comprehensive metrics including transaction counts, gas usage, unique addresses, and top-10 analyses
  • HTML visualizations with SVG charts and interactive tooltips
  • PDF generation using chromedp for headless browser rendering

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
go.mod / go.sum Added chromedp dependencies for PDF generation and viper v1.19.0 (moved from direct to indirect dependency) with related transitive dependency version adjustments
cmd/root.go Registered the new report command in the CLI
cmd/report/report.go Core implementation: flag validation, concurrent RPC fetching with rate limiting, block/transaction data processing, and output generation
cmd/report/types.go Data structures for report, summary statistics, block info, transaction info, and top-10 analyses
cmd/report/html.go HTML generation functions including stat cards, SVG line charts for transactions and gas usage, and top-10 tables
cmd/report/pdf.go PDF generation using chromedp to render HTML to PDF with page settings
cmd/report/template.html HTML template with embedded CSS for styling and JavaScript for chart tooltips
cmd/report/usage.md Documentation covering features, usage examples, and report contents

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +500 to +534
<td><code>%s</code></td>
<td>%s</td>
<td>%s</td>
<td>%s</td>
</tr>`, i+1, tx.Hash, formatNumber(tx.BlockNumber), formatNumberWithUnits(tx.GasLimit), formatNumber(tx.GasUsed)))
}

sb.WriteString(`
</tbody>
</table>
</div>`)
}

// Top 10 transactions by gas limit
if len(report.Top10.TransactionsByGasLimit) > 0 {
sb.WriteString(`
<div class="subsection">
<h3>Top 10 Transactions by Gas Limit</h3>
<table>
<thead>
<tr>
<th>Rank</th>
<th>Transaction Hash</th>
<th>Block Number</th>
<th>Gas Limit</th>
<th>Gas Used (Wei)</th>
</tr>
</thead>
<tbody>`)

for i, tx := range report.Top10.TransactionsByGasLimit {
sb.WriteString(fmt.Sprintf(`
<tr>
<td>%d</td>
<td><code>%s</code></td>
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transaction hashes from the blockchain are directly embedded into HTML without escaping at lines 500 and 534. While transaction hashes are typically hexadecimal strings, if the RPC endpoint returns unexpected data, this could lead to HTML injection. Use html.EscapeString() to safely escape all dynamic content before embedding in HTML.

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +27
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Allocate a new browser context
ctx, cancel = chromedp.NewContext(ctx)
defer cancel()
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancel function returned from chromedp.NewContext is assigned to the same variable as the previous cancel function from context.WithTimeout, overwriting it. This means the first cancel is never called, potentially leaking the timeout context. Store the second cancel in a different variable (e.g., cancel2) and defer it separately, or ensure both cancels are invoked.

Suggested change
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Allocate a new browser context
ctx, cancel = chromedp.NewContext(ctx)
defer cancel()
ctx, cancelTimeout := context.WithTimeout(context.Background(), 30*time.Second)
defer cancelTimeout()
// Allocate a new browser context
ctx, cancelBrowser := chromedp.NewContext(ctx)
defer cancelBrowser()

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +153
padding, height-padding+15, report.Blocks[0].Number,
width-padding, height-padding+15, report.Blocks[len(report.Blocks)-1].Number,
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The axis labels at lines 152-153 display report.Blocks[0].Number and report.Blocks[len(report.Blocks)-1].Number, assuming these are the first and last blocks in the requested range. However, because blocks are added in the order they arrive from concurrent workers (not sorted by block number), these labels may not correspond to the actual start and end of the block range specified by the user. This will create misleading charts where the axis labels don't match the displayed data. This depends on fixing the block ordering issue at line 207 of report.go.

Copilot uses AI. Check for mistakes.
Comment on lines +242 to +243
padding, height-padding+15, report.Blocks[0].Number,
width-padding, height-padding+15, report.Blocks[len(report.Blocks)-1].Number,
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The axis labels at lines 242-243 have the same issue as the transaction count chart: they display report.Blocks[0].Number and report.Blocks[len(report.Blocks)-1].Number assuming these correspond to the start and end of the user-specified range. Because blocks are added in arrival order (not sorted by block number), these labels will be incorrect and misleading. This depends on fixing the block ordering issue at line 207 of report.go.

Copilot uses AI. Check for mistakes.
numPoints := 0
for i := 0; i < len(report.Blocks); i += step {
block := report.Blocks[i]
x := padding + (float64(numPoints) / float64((len(report.Blocks)-1)/step)) * chartWidth
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there is only one block (len(report.Blocks) == 1), the expression float64((len(report.Blocks)-1)/step) at line 121 evaluates to 0, causing division by zero which results in +Inf for the x coordinate. This will produce invalid SVG coordinates. Add a check for single-block edge case or ensure the denominator is at least 1.

Copilot uses AI. Check for mistakes.

## Notes

- The command queries blocks sequentially to avoid overwhelming the RPC endpoint
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states "The command queries blocks sequentially to avoid overwhelming the RPC endpoint" but the actual implementation uses concurrent workers (default: 10) as specified in the PR description and implemented in report.go. This is misleading. Update the documentation to accurately reflect that blocks are fetched concurrently with configurable concurrency and rate limiting.

Suggested change
- The command queries blocks sequentially to avoid overwhelming the RPC endpoint
- The command queries blocks concurrently using a worker pool (default: 10 workers), with configurable concurrency and rate limiting to avoid overwhelming the RPC endpoint

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +176
blockInfo, err := fetchBlockInfo(ctx, ec, blockNum, rateLimiter)
if err != nil {
errorChan <- fmt.Errorf("failed to fetch block %d: %w", blockNum, err)
return
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a worker encounters an error at line 174, it sends one error to errorChan and returns immediately. However, other workers continue processing blocks and may send results to resultChan. The main loop will return on the first error (line 235), but remaining goroutines are not canceled, potentially causing resource leaks or hanging goroutines. Consider using a context cancellation mechanism to stop all workers when the first error occurs, or draining the channels properly before returning.

Copilot uses AI. Check for mistakes.

// Replace metadata placeholders
html = strings.ReplaceAll(html, "{{CHAIN_ID}}", fmt.Sprintf("%d", report.ChainID))
html = strings.ReplaceAll(html, "{{RPC_URL}}", report.RpcUrl)
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RPC URL from user input is directly embedded into HTML without escaping at line 19. If the RPC URL contains HTML special characters (e.g., <, >, &, quotes), it could break the HTML structure or potentially lead to XSS if the URL contains malicious content. Use html.EscapeString() to properly escape the RPC URL and other user-controlled data before inserting into HTML.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +45
// Wait a bit for any dynamic content to settle
time.Sleep(500 * time.Millisecond)
return nil
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using time.Sleep for a fixed 500ms delay is a blocking operation that doesn't respect context cancellation. If the context is canceled during this sleep, the function will still wait the full duration. Use a timer with context cancellation instead, such as select { case &lt;-time.After(500*time.Millisecond): case &lt;-ctx.Done(): return ctx.Err() }.

Suggested change
// Wait a bit for any dynamic content to settle
time.Sleep(500 * time.Millisecond)
return nil
// Wait a bit for any dynamic content to settle, respecting context cancellation
select {
case <-time.After(500 * time.Millisecond):
return nil
case <-ctx.Done():
return ctx.Err()
}

Copilot uses AI. Check for mistakes.
numPoints := 0
for i := 0; i < len(report.Blocks); i += step {
block := report.Blocks[i]
x := padding + (float64(numPoints) / float64((len(report.Blocks)-1)/step)) * chartWidth
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there is only one block (len(report.Blocks) == 1), the expression float64((len(report.Blocks)-1)/step) at line 211 evaluates to 0, causing division by zero which results in +Inf for the x coordinate. This will produce invalid SVG coordinates. Add a check for single-block edge case or ensure the denominator is at least 1.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants