Documentation
¶
Overview ¶
Package shadow provides block-by-block comparison between a shadow chain node and a canonical chain node. The comparison is layered:
- Layer 0: Block header hashes (AppHash, LastResultsHash, gas). If these match, the block is identical and deeper layers are skipped.
- Layer 1: Transaction receipt comparison (status, gas, logs, etc.). Run only when Layer 0 fails, to isolate which transactions diverged.
- Layer 2: State diff comparison (future).
- Layer 3: Execution trace comparison (future).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RenderMarkdown ¶ added in v0.0.25
func RenderMarkdown(r *DivergenceReport) string
RenderMarkdown produces a human-readable investigation report from a DivergenceReport. The output is designed to be consumed by engineers or LLM agents analyzing why a shadow node diverged from the canonical chain.
Types ¶
type ChainSnapshot ¶ added in v0.0.25
type ChainSnapshot struct {
Block json.RawMessage `json:"block"`
BlockResults json.RawMessage `json:"blockResults"`
}
ChainSnapshot captures the raw RPC responses from one chain at a specific height. The JSON is preserved verbatim for offline analysis.
type Comparator ¶
type Comparator struct {
// contains filtered or unexported fields
}
Comparator performs block-by-block comparison between a shadow node and a canonical chain node via their RPC endpoints.
func NewComparator ¶
func NewComparator(shadowRPC, canonicalRPC string) *Comparator
NewComparator creates a Comparator that queries shadowRPC for the local shadow node and canonicalRPC for the reference chain.
func (*Comparator) BuildDivergenceReport ¶ added in v0.0.25
func (c *Comparator) BuildDivergenceReport(ctx context.Context, height int64, comparison CompareResult) (*DivergenceReport, error)
BuildDivergenceReport captures a complete investigation artifact at the given height. It pairs the comparison result with the full raw RPC responses from both chains so engineers can diagnose offline.
func (*Comparator) CompareBlock ¶
func (c *Comparator) CompareBlock(ctx context.Context, height int64) (*CompareResult, error)
CompareBlock performs a layered comparison for the given block height. Layer 0 (block headers) always runs. If Layer 0 detects a divergence, Layer 1 (transaction receipts) is run to provide diagnostic detail.
type CompareResult ¶
type CompareResult struct {
// Height is the block height that was compared.
Height int64 `json:"height"`
// Timestamp is the UTC time the comparison was performed.
Timestamp string `json:"timestamp"`
// Match is true when all checked layers agree between shadow and canonical.
Match bool `json:"match"`
// DivergenceLayer is the first layer that detected a mismatch.
// Nil when Match is true.
DivergenceLayer *int `json:"divergenceLayer,omitempty"`
// Layer0 holds the block-level hash comparison. Always populated.
Layer0 Layer0Result `json:"layer0"`
// Layer1 holds the transaction receipt comparison.
// Only populated when Layer 0 detected a divergence.
Layer1 *Layer1Result `json:"layer1,omitempty"`
}
CompareResult holds the comparison output for a single block.
func (*CompareResult) Diverged ¶
func (r *CompareResult) Diverged() bool
Diverged returns true when the comparison detected a mismatch at any layer.
type DivergenceReport ¶ added in v0.0.25
type DivergenceReport struct {
Height int64 `json:"height"`
Timestamp string `json:"timestamp"`
Comparison CompareResult `json:"comparison"`
Shadow ChainSnapshot `json:"shadow"`
Canonical ChainSnapshot `json:"canonical"`
}
DivergenceReport is a self-contained investigation artifact for a single app-hash divergence event. It includes the layered comparison result plus the full block and block_results from both chains, giving engineers all the context needed to diagnose why the shadow node diverged without querying external systems.
func FetchReport ¶ added in v0.0.30
func FetchReport(ctx context.Context, downloader seis3.Downloader, bucket, key string) (*DivergenceReport, error)
FetchReport downloads and decodes a DivergenceReport from S3.
type FieldDivergence ¶
type FieldDivergence struct {
Field string `json:"field"`
Shadow any `json:"shadow"`
Canonical any `json:"canonical"`
}
FieldDivergence records a single field-level mismatch in a tx receipt.
type Layer0Result ¶
type Layer0Result struct {
AppHashMatch bool `json:"appHashMatch"`
LastResultsHashMatch bool `json:"lastResultsHashMatch"`
GasUsedMatch bool `json:"gasUsedMatch"`
// Raw values are included when there is a mismatch, for diagnostics.
ShadowAppHash string `json:"shadowAppHash,omitempty"`
CanonicalAppHash string `json:"canonicalAppHash,omitempty"`
ShadowLastResultsHash string `json:"shadowLastResultsHash,omitempty"`
CanonicalLastResultsHash string `json:"canonicalLastResultsHash,omitempty"`
ShadowGasUsed int64 `json:"shadowGasUsed,omitempty"`
CanonicalGasUsed int64 `json:"canonicalGasUsed,omitempty"`
}
Layer0Result compares block-level hashes. This is the cheapest check; if all fields match, the block is identical and no further comparison is needed.
func (Layer0Result) Match ¶
func (r Layer0Result) Match() bool
Match returns true when all Layer 0 fields agree.
type Layer1Result ¶
type Layer1Result struct {
// TotalTxs is the number of transactions in the block.
TotalTxs int `json:"totalTxs"`
// TxCountMatch is true when both chains have the same number of txs.
TxCountMatch bool `json:"txCountMatch"`
// Divergences lists the per-transaction differences found.
Divergences []TxDivergence `json:"divergences,omitempty"`
}
Layer1Result compares individual transaction receipts within a block. Only populated when Layer 0 fails.
type TxDivergence ¶
type TxDivergence struct {
// TxIndex is the position of the transaction within the block.
TxIndex int `json:"txIndex"`
// Fields lists which receipt fields diverged.
Fields []FieldDivergence `json:"fields"`
}
TxDivergence records a mismatch for a single transaction within a block.