diff --git a/badgerdb-analyzer/Makefile b/badgerdb-analyzer/Makefile new file mode 100644 index 0000000..20bfe9f --- /dev/null +++ b/badgerdb-analyzer/Makefile @@ -0,0 +1,43 @@ +# Makefile for badgerdb-analyzer + +.PHONY: all build-all build-v2 build-v3 build-v4 clean tidy-all + +# Default target +all: build-all + +# Build all versions +build-all: build-v2 build-v3 build-v4 + +# Build BadgerDB v2 version +build-v2: + @echo "Building badgerdb-analyzer-v2..." + go build -modfile=go_v2.mod -tags badgerv2 -o bin/badgerdb-analyzer-v2 + @echo "✓ Built: bin/badgerdb-analyzer-v2" + +# Build BadgerDB v3 version +build-v3: + @echo "Building badgerdb-analyzer-v3..." + go build -modfile=go_v3.mod -tags badgerv3 -o bin/badgerdb-analyzer-v3 + @echo "✓ Built: bin/badgerdb-analyzer-v3" + +# Build BadgerDB v4 version +build-v4: + @echo "Building badgerdb-analyzer-v4..." + go build -modfile=go_v4.mod -tags badgerv4 -o bin/badgerdb-analyzer-v4 + @echo "✓ Built: bin/badgerdb-analyzer-v4" + +# Clean build artifacts +clean: + @echo "Cleaning bin/..." + rm -f bin/badgerdb-sampler-v2 bin/badgerdb-sampler-v3 bin/badgerdb-sampler-v4 + @echo "Cleaning outputs/..." + rm -rf outputs/* + @echo "✓ Cleaned" + +# Tidy all module files +tidy-all: + @echo "Tidying all module files..." + GOFLAGS="-tags=badgerv2" go mod tidy -modfile=go_v2.mod + GOFLAGS="-tags=badgerv3" go mod tidy -modfile=go_v3.mod + GOFLAGS="-tags=badgerv4" go mod tidy -modfile=go_v4.mod + @echo "✓ All module files tidied" diff --git a/badgerdb-analyzer/README.md b/badgerdb-analyzer/README.md new file mode 100644 index 0000000..ccb72e4 --- /dev/null +++ b/badgerdb-analyzer/README.md @@ -0,0 +1,75 @@ +# BadgerDB Analyzer + +This **badgerdb-analyzer** tool is for analyzing and checking the consistency of BadgerDB databases from Oasis node snapshots. It performs a full database scan, analysis with statistics, consistency validation, and integrity checks. It handles multiple BadgerDB versions (v2-v4) and data representations use by Oasis nodes (v20.x-v25.x). The decoding logic is from the **badgerdb-sampler** tool. + +**Warning:** For experimental purposes only. Decoded data might be incorrect. + +## Features + +- Full database analysis in single pass +- Comprehensive statistics (key/value sizes, block ranges, transaction counts) +- Consistency checks (block sequences, timestamp validation, referential integrity) +- Multi-version support (BadgerDB v2, v3, v4) +- Six database types (consensus-blockstore, consensus-evidence, consensus-mkvs, consensus-state, runtime-history, runtime-mkvs) +- JSON output for programmatic analysis + +## Supported Database Types + +| Database Type | Description | Key Statistics | +|------------------------|-------------------------------------|----------------| +| `consensus-blockstore` | Block metadata and commit info | Consensus block sequences, timestamps, transactions, events | +| `consensus-evidence` | Byzantine validator evidence | Evidence heights, gap detection | +| `consensus-mkvs` | Consensus state Merkle tree | Node types, write log versions | +| `consensus-state` | Tendermint/CometBFT consensus state | ABCI responses completeness | +| `runtime-history` | Runtime block history | Runtime/consensus heights, timestamps, intervals | +| `runtime-mkvs` | Runtime state Merkle tree | Node types, module distribution, write log versions | + +## Installation + +```bash +make build-all +``` + +This builds three binaries: +- `./bin/badgerdb-analyzer-v2` - For BadgerDB v2 databases +- `./bin/badgerdb-analyzer-v3` - For BadgerDB v3 databases +- `./bin/badgerdb-analyzer-v4` - For BadgerDB v4 databases + +## Usage + +```bash +badgerdb-analyzer-vX [output-json] +``` + +**Arguments:** +- `database-type`: One of: `consensus-blockstore`, `consensus-evidence`, `consensus-mkvs`, `consensus-state`, `runtime-mkvs`, `runtime-history` +- `database-path`: Path to the BadgerDB directory +- `output-json`: (Optional) Path to JSON output file. If omitted, outputs to stdout + +### Examples + +**Analyze consensus blockstore:** +```bash +./bin/badgerdb-analyzer-v3 consensus-blockstore \ + /snapshots/mainnet/consensus/tendermint/data/blockstore.badger.db \ + blockstore-analysis.json +``` + +**Analyze runtime history:** +```bash +./bin/badgerdb-analyzer-v3 runtime-history \ + /snapshots/mainnet/runtimes//history.db \ + runtime-history-analysis.json +``` + +## Exit Codes + +- `0` - Success, all checks passed +- `1` - Issues found (checks failed) +- `2` - Database open failed +- `3` - Invalid arguments +- `4` - Analysis error + +## Related Tools + +- **badgerdb-sampler**: Sample-based database exploration diff --git a/badgerdb-analyzer/analyzer.go b/badgerdb-analyzer/analyzer.go new file mode 100644 index 0000000..a35c322 --- /dev/null +++ b/badgerdb-analyzer/analyzer.go @@ -0,0 +1,1615 @@ +package main + +import ( + "encoding/binary" + "fmt" + "math" + "sort" + "time" + + "github.com/fxamacker/cbor/v2" +) + +// AnalysisResult contains all analysis results for a database +type AnalysisResult struct { + // Database metadata + DatabasePath string `json:"database_path"` + DatabaseType string `json:"database_type"` + BadgerDBVersion string `json:"badgerdb_version"` + DatabaseSize int64 `json:"database_size"` + + // Statistics + Keys KeyStats `json:"keys"` + Values ValueStats `json:"values"` + ConsensusBlocks *ConsensusBlockStats `json:"consensus_blocks,omitempty"` + RuntimeBlocks *RuntimeBlockStats `json:"runtime_blocks,omitempty"` + MKVSStats *MKVSStats `json:"mkvs_stats,omitempty"` + ABCIAnalysis *ABCIAnalysisStats `json:"abci_analysis,omitempty"` + + // Quality and checks + Consistency []CheckResult `json:"consistency_checks"` + ErrorCounts map[string]int `json:"error_counts"` + Anomalies []string `json:"anomalies,omitempty"` +} + +// ConsensusBlockStats contains statistics for consensus blockstore +type ConsensusBlockStats struct { + OldestConsensusHeight int64 `json:"oldest_consensus_height"` + NewestConsensusHeight int64 `json:"newest_consensus_height"` + TotalBlocks int64 `json:"total_blocks"` + IsComplete bool `json:"is_complete"` + MissingBlocks []int64 `json:"missing_blocks,omitempty"` + OldestTimestamp string `json:"oldest_timestamp,omitempty"` // RFC3339 + NewestTimestamp string `json:"newest_timestamp,omitempty"` // RFC3339 + IntervalStats *SizeStats `json:"interval_stats,omitempty"` + TransactionStats *ConsensusTransactionStats `json:"transaction_stats,omitempty"` + EventStats *ConsensusEventStats `json:"event_stats,omitempty"` +} + +// ConsensusTransactionStats contains transaction statistics for consensus blocks +type ConsensusTransactionStats struct { + Total int64 `json:"total"` + PerBlockStats *SizeStats `json:"per_block_stats,omitempty"` + SizeStats *SizeStats `json:"size_stats,omitempty"` + TypeDistribution map[string]int64 `json:"type_distribution"` +} + +// ConsensusEventStats contains event statistics for consensus blocks +type ConsensusEventStats struct { + Total int64 `json:"total"` + PerBlockStats *SizeStats `json:"per_block_stats,omitempty"` + TypeDistribution map[string]int64 `json:"type_distribution"` +} + +// RuntimeBlockStats contains statistics for runtime history +type RuntimeBlockStats struct { + OldestConsensusHeight int64 `json:"oldest_consensus_height"` + NewestConsensusHeight int64 `json:"newest_consensus_height"` + OldestRuntimeHeight int64 `json:"oldest_runtime_height"` + NewestRuntimeHeight int64 `json:"newest_runtime_height"` + OldestTimestamp string `json:"oldest_timestamp,omitempty"` + NewestTimestamp string `json:"newest_timestamp,omitempty"` + IsComplete bool `json:"is_complete"` + TotalBlocks int64 `json:"total_blocks"` + IntervalStats *SizeStats `json:"interval_stats,omitempty"` + TransactionStats *RuntimeTransactionStats `json:"transaction_stats,omitempty"` + EventStats *RuntimeEventStats `json:"event_stats,omitempty"` +} + +// RuntimeTransactionStats contains transaction statistics for runtime blocks +type RuntimeTransactionStats struct { + Total int64 `json:"total"` + EVMTransactions int64 `json:"evm_transactions"` + PerBlockStats *SizeStats `json:"per_block_stats,omitempty"` + EVMTypeDistribution map[string]int64 `json:"evm_type_distribution"` + EVMSizeStats *SizeStats `json:"evm_size_stats,omitempty"` +} + +// RuntimeEventStats contains event statistics for runtime blocks +type RuntimeEventStats struct { + Total int64 `json:"total"` + EVMEvents int64 `json:"evm_events"` + PerBlockStats *SizeStats `json:"per_block_stats,omitempty"` + RuntimeEventTypes map[string]int64 `json:"runtime_event_types"` + EVMSignatures map[string]int64 `json:"evm_signatures"` +} + +// MKVSStats contains statistics for MKVS databases +type MKVSStats struct { + NodeTypeDistribution map[string]int64 `json:"node_type_distribution"` + ModuleDistribution map[string]int64 `json:"module_distribution,omitempty"` + TotalModule int64 `json:"total_module,omitempty"` + TotalNode int64 `json:"total_node"` + WriteLogStats *WriteLogStats `json:"write_log_stats,omitempty"` +} + +// WriteLogStats contains statistics about available write_log entries +type WriteLogStats struct { + OldestVersion uint64 `json:"oldest_version"` + NewestVersion uint64 `json:"newest_version"` + TotalEntries int64 `json:"total_entries"` + VersionRange uint64 `json:"version_range"` // NewestVersion - OldestVersion + 1 + Coverage float64 `json:"coverage_percent"` // (TotalEntries / VersionRange) * 100 +} + +// ABCIAnalysisStats contains aggregated statistics across all ABCI responses +type ABCIAnalysisStats struct { + TxMethodCounts map[string]int64 `json:"tx_method_counts"` + EventTypeCounts map[string]int64 `json:"event_type_counts"` + TotalTx int64 `json:"total_tx"` + TotalEvent int64 `json:"total_event"` +} + +// SizeStats contains statistical information about sizes +type SizeStats struct { + Min int64 `json:"min"` + Max int64 `json:"max"` + Avg float64 `json:"avg"` + Total int64 `json:"total"` + StdDev float64 `json:"std_dev"` +} + +// KeyStats contains statistics about keys +type KeyStats struct { + TotalCount int64 `json:"total_count"` + TypeDistribution map[string]int `json:"type_distribution"` + SizeStats SizeStats `json:"size_stats"` +} + +// ValueStats contains statistics about values +type ValueStats struct { + SizeStats SizeStats `json:"size_stats"` +} + +// CheckResult contains the result of a consistency check +type CheckResult struct { + Name string `json:"name"` + Passed bool `json:"passed"` + Issues []string `json:"issues,omitempty"` +} + +// SizeStatsAccumulator accumulates size statistics using Welford's online algorithm +type SizeStatsAccumulator struct { + count int64 + min int64 + max int64 + sum int64 + mean float64 + m2 float64 // For variance calculation +} + +// Add adds a new value to the accumulator +func (s *SizeStatsAccumulator) Add(value int64) { + s.count++ + s.sum += value + + if s.count == 1 { + s.min = value + s.max = value + s.mean = float64(value) + s.m2 = 0 + } else { + if value < s.min { + s.min = value + } + if value > s.max { + s.max = value + } + + // Welford's online algorithm for variance + delta := float64(value) - s.mean + s.mean += delta / float64(s.count) + delta2 := float64(value) - s.mean + s.m2 += delta * delta2 + } +} + +// Finalize returns the final SizeStats +func (s *SizeStatsAccumulator) Finalize() SizeStats { + if s.count == 0 { + return SizeStats{} + } + + stdDev := 0.0 + if s.count > 1 { + variance := s.m2 / float64(s.count) + stdDev = math.Sqrt(variance) + } + + return SizeStats{ + Min: s.min, + Max: s.max, + Avg: s.mean, + Total: s.sum, + StdDev: stdDev, + } +} + +// consensusBlockstoreCollector collects data for consensus-blockstore analysis +type consensusBlockstoreCollector struct { + keyTypes map[string]int + heights []int64 + timestamps []time.Time + blocks map[int64]bool + commits map[int64]bool + seenCommits map[int64]bool +} + +// runtimeHistoryCollector collects data for runtime-history analysis +type runtimeHistoryCollector struct { + keyTypes map[string]int + heights []int64 + timestamps []time.Time + consensusHeights []int64 +} + +// consensusMkvsCollector collects data for consensus-mkvs analysis +type consensusMkvsCollector struct { + keyTypes map[string]int + nodeTypes map[string]int64 + modules map[string]int64 + writeLogVersions []uint64 // For version completeness checks + totalLeaves int64 + totalInternal int64 + + // Consensus-specific height-based aggregation + leafCountsByHeight map[int64]int // height -> leaf count + txCountsByHeight map[int64]int // height -> tx count + eventCountsByHeight map[int64]int // height -> event count +} + +// runtimeMkvsCollector collects data for runtime-mkvs analysis +type runtimeMkvsCollector struct { + keyTypes map[string]int + nodeTypes map[string]int64 + modules map[string]int64 + writeLogVersions []uint64 // For version completeness checks + totalLeaves int64 + totalInternal int64 + + // Per-height transaction/event aggregation + txCountsByHeight map[uint64]int // height -> total tx count + evmTxCountsByHeight map[uint64]int // height -> EVM tx count + evmTxTypesByHeight map[uint64]map[string]int // height -> type -> count + eventCountsByHeight map[uint64]int // height -> total event count + evmEventCountsByHeight map[uint64]int // height -> EVM event count + evmEventSigsByHeight map[uint64]map[string]int // height -> signature -> count + runtimeEventsByHeight map[uint64]map[string]int // height -> event type -> count +} + +// consensusEvidenceCollector collects data for consensus-evidence analysis +type consensusEvidenceCollector struct { + keyTypes map[string]int + consensusHeights []int64 +} + +// consensusStateCollector collects data for consensus-state analysis +type consensusStateCollector struct { + keyTypes map[string]int + abciResponseHeights []int64 + + // Per-height aggregated data (NOT full objects) + txCountsByHeight map[int64]int // height -> tx count + txMethodsByHeight map[int64]map[string]int // height -> method -> count + eventTypesByHeight map[int64]map[string]int // height -> event type -> count +} + +// analyzeDatabase performs full analysis of a database +func analyzeDatabase(db *DB, dbType string, dbPath string, dbVersion string, dbSize int64) (*AnalysisResult, error) { + // Initialize result + result := &AnalysisResult{ + DatabasePath: dbPath, + DatabaseType: dbType, + BadgerDBVersion: dbVersion, + DatabaseSize: dbSize, + Keys: KeyStats{ + TotalCount: 0, + TypeDistribution: make(map[string]int), + SizeStats: SizeStats{}, + }, + Values: ValueStats{ + SizeStats: SizeStats{}, + }, + ErrorCounts: make(map[string]int), + Consistency: []CheckResult{}, + Anomalies: []string{}, + } + + // Initialize statistics accumulators + keySizeStats := &SizeStatsAccumulator{} + valueSizeStats := &SizeStatsAccumulator{} + + // Database-specific collectors + var consensusBlockstoreData *consensusBlockstoreCollector + var runtimeHistoryData *runtimeHistoryCollector + var consensusMkvsData *consensusMkvsCollector + var runtimeMkvsData *runtimeMkvsCollector + var consensusEvidenceData *consensusEvidenceCollector + var consensusStateData *consensusStateCollector + + switch dbType { + case "consensus-blockstore": + consensusBlockstoreData = &consensusBlockstoreCollector{ + keyTypes: make(map[string]int), + heights: []int64{}, + timestamps: []time.Time{}, + blocks: make(map[int64]bool), + commits: make(map[int64]bool), + seenCommits: make(map[int64]bool), + } + case "runtime-history": + runtimeHistoryData = &runtimeHistoryCollector{ + keyTypes: make(map[string]int), + heights: []int64{}, + timestamps: []time.Time{}, + consensusHeights: []int64{}, + } + case "consensus-mkvs": + consensusMkvsData = &consensusMkvsCollector{ + keyTypes: make(map[string]int), + nodeTypes: make(map[string]int64), + modules: make(map[string]int64), + writeLogVersions: []uint64{}, + totalLeaves: 0, + totalInternal: 0, + leafCountsByHeight: make(map[int64]int), + txCountsByHeight: make(map[int64]int), + eventCountsByHeight: make(map[int64]int), + } + case "runtime-mkvs": + runtimeMkvsData = &runtimeMkvsCollector{ + keyTypes: make(map[string]int), + nodeTypes: make(map[string]int64), + modules: make(map[string]int64), + writeLogVersions: []uint64{}, + totalLeaves: 0, + totalInternal: 0, + txCountsByHeight: make(map[uint64]int), + evmTxCountsByHeight: make(map[uint64]int), + evmTxTypesByHeight: make(map[uint64]map[string]int), + eventCountsByHeight: make(map[uint64]int), + evmEventCountsByHeight: make(map[uint64]int), + evmEventSigsByHeight: make(map[uint64]map[string]int), + runtimeEventsByHeight: make(map[uint64]map[string]int), + } + case "consensus-evidence": + consensusEvidenceData = &consensusEvidenceCollector{ + keyTypes: make(map[string]int), + consensusHeights: []int64{}, + } + case "consensus-state": + consensusStateData = &consensusStateCollector{ + keyTypes: make(map[string]int), + abciResponseHeights: []int64{}, + txCountsByHeight: make(map[int64]int), + txMethodsByHeight: make(map[int64]map[string]int), + eventTypesByHeight: make(map[int64]map[string]int), + } + } + + // Single-pass iteration + logProgress("Phase 1: Collecting statistics...") + keyCount := int64(0) + + err := db.View(func(txn *Txn) error { + opts := DefaultIteratorOptions + opts.PrefetchValues = true + opts.PrefetchSize = 100 + opts.AllVersions = false + it := txn.NewIterator(opts) + defer it.Close() + + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + key := item.Key() + keySizeStats.Add(int64(len(key))) + + value, _ := item.ValueCopy(nil) + if value != nil { + valueSizeStats.Add(int64(len(value))) + } + + // Process based on database type + switch dbType { + case "consensus-blockstore": + processConsensusBlockstoreEntry(key, value, consensusBlockstoreData, result.ErrorCounts) + case "runtime-history": + processRuntimeHistoryEntry(key, value, runtimeHistoryData, result.ErrorCounts) + case "consensus-mkvs": + processConsensusMkvsEntry(key, value, consensusMkvsData, result.ErrorCounts) + case "runtime-mkvs": + processRuntimeMkvsEntry(key, value, runtimeMkvsData, result.ErrorCounts) + case "consensus-evidence": + processConsensusEvidenceEntry(key, value, consensusEvidenceData, result.ErrorCounts) + case "consensus-state": + processConsensusStateEntry(key, value, consensusStateData, result.ErrorCounts) + } + + keyCount++ + if keyCount%100000 == 0 { + logProgress(" Processed %d keys...", keyCount) + } + } + return nil + }) + + if err != nil { + return nil, err + } + + // Finalize statistics + result.Keys.TotalCount = keyCount + result.Keys.SizeStats = keySizeStats.Finalize() + result.Values.SizeStats = valueSizeStats.Finalize() + + logProgress("Phase 2: Building statistics and running checks...") + + // Build database-specific stats and checks + switch dbType { + case "consensus-blockstore": + result.ConsensusBlocks = buildConsensusBlockStats(consensusBlockstoreData) + result.Consistency = runConsensusBlockstoreChecks(consensusBlockstoreData) + result.Keys.TypeDistribution = consensusBlockstoreData.keyTypes + case "runtime-history": + result.RuntimeBlocks = buildRuntimeBlockStats(runtimeHistoryData) + result.Consistency = runRuntimeHistoryChecks(runtimeHistoryData) + result.Keys.TypeDistribution = runtimeHistoryData.keyTypes + case "consensus-mkvs": + result.MKVSStats = buildConsensusMkvsStats(consensusMkvsData) + result.Consistency = runConsensusMkvsChecks(consensusMkvsData) + result.Keys.TypeDistribution = consensusMkvsData.keyTypes + case "runtime-mkvs": + result.MKVSStats = buildRuntimeMkvsStats(runtimeMkvsData) + // Populate transaction/event stats if RuntimeBlockStats exists + if result.RuntimeBlocks != nil { + populateRuntimeTransactionEventStats(result.RuntimeBlocks, runtimeMkvsData) + } + result.Consistency = runRuntimeMkvsChecks(runtimeMkvsData) + result.Keys.TypeDistribution = runtimeMkvsData.keyTypes + case "consensus-evidence": + result.Consistency = runConsensusEvidenceChecks(consensusEvidenceData) + result.Keys.TypeDistribution = consensusEvidenceData.keyTypes + case "consensus-state": + result.ABCIAnalysis = buildABCIAnalysisStats(consensusStateData) + result.Consistency = runConsensusStateChecks(consensusStateData) + result.Keys.TypeDistribution = consensusStateData.keyTypes + } + + return result, nil +} + +// processConsensusBlockstoreEntry processes a single consensus-blockstore entry +func processConsensusBlockstoreEntry(key, value []byte, collector *consensusBlockstoreCollector, errorCounts map[string]int) { + keyInfo := decodeConsensusBlockstoreKey(key) + collector.keyTypes[keyInfo.KeyType]++ + + if value == nil { + return + } + + valueInfo := decodeConsensusBlockstoreValue(keyInfo.KeyType, value) + + // Extract heights and timestamps from block metadata + if keyInfo.KeyType == "block_meta" { + if keyInfo.ConsensusHeight > 0 { + collector.heights = append(collector.heights, keyInfo.ConsensusHeight) + collector.blocks[keyInfo.ConsensusHeight] = true + } + if valueInfo.Timestamp > 0 { + collector.timestamps = append(collector.timestamps, time.Unix(valueInfo.Timestamp, 0)) + } + } + + // Track commits + if keyInfo.KeyType == "block_commit" { + if keyInfo.ConsensusHeight > 0 { + collector.commits[keyInfo.ConsensusHeight] = true + } + } + + // Track seen commits + if keyInfo.KeyType == "seen_commit" { + if keyInfo.ConsensusHeight > 0 { + collector.seenCommits[keyInfo.ConsensusHeight] = true + } + } + + // Count decode errors + if valueInfo.RawError != "" { + errorCounts[valueInfo.RawError]++ + } +} + +// buildConsensusBlockStats builds consensus block statistics +func buildConsensusBlockStats(collector *consensusBlockstoreCollector) *ConsensusBlockStats { + if len(collector.heights) == 0 { + return &ConsensusBlockStats{} + } + + // Sort heights + sort.Slice(collector.heights, func(i, j int) bool { + return collector.heights[i] < collector.heights[j] + }) + + // Sort timestamps + sort.Slice(collector.timestamps, func(i, j int) bool { + return collector.timestamps[i].Before(collector.timestamps[j]) + }) + + stats := &ConsensusBlockStats{ + OldestConsensusHeight: collector.heights[0], + NewestConsensusHeight: collector.heights[len(collector.heights)-1], + TotalBlocks: int64(len(collector.heights)), + } + + // Check if complete + expectedBlocks := stats.NewestConsensusHeight - stats.OldestConsensusHeight + 1 + stats.IsComplete = (stats.TotalBlocks == expectedBlocks) + + // Find missing blocks + if !stats.IsComplete { + heightSet := make(map[int64]bool) + for _, h := range collector.heights { + heightSet[h] = true + } + for h := stats.OldestConsensusHeight; h <= stats.NewestConsensusHeight; h++ { + if !heightSet[h] { + stats.MissingBlocks = append(stats.MissingBlocks, h) + } + } + } + + // Timestamps + if len(collector.timestamps) > 0 { + stats.OldestTimestamp = collector.timestamps[0].Format(time.RFC3339) + stats.NewestTimestamp = collector.timestamps[len(collector.timestamps)-1].Format(time.RFC3339) + + // Calculate interval statistics + if len(collector.timestamps) > 1 { + intervalStats := &SizeStatsAccumulator{} + for i := 1; i < len(collector.timestamps); i++ { + interval := collector.timestamps[i].Sub(collector.timestamps[i-1]).Milliseconds() + if interval >= 0 { + intervalStats.Add(interval) + } + } + finalStats := intervalStats.Finalize() + stats.IntervalStats = &finalStats + } + } + + return stats +} + +// runConsensusBlockstoreChecks runs all consistency checks for consensus-blockstore +func runConsensusBlockstoreChecks(collector *consensusBlockstoreCollector) []CheckResult { + checks := []CheckResult{} + + // block_sequence_complete + checks = append(checks, checkBlockSequence(collector.heights)) + + // timestamp_monotonicity + checks = append(checks, checkTimestampMonotonicity(collector.timestamps)) + + // block_interval_anomalies + checks = append(checks, checkBlockIntervalAnomalies(collector.heights, collector.timestamps)) + + // referential_integrity + var oldestHeight, newestHeight int64 + if len(collector.heights) > 0 { + oldestHeight = collector.heights[0] + newestHeight = collector.heights[len(collector.heights)-1] + } + checks = append(checks, checkReferentialIntegrity(collector.blocks, collector.commits, oldestHeight, newestHeight)) + + // block_size_anomalies (placeholder for now) + checks = append(checks, CheckResult{Name: "block_size_anomalies", Passed: true}) + + return checks +} + +// checkBlockSequence checks if blocks are sequential with no gaps +func checkBlockSequence(heights []int64) CheckResult { + result := CheckResult{ + Name: "block_sequence_complete", + Passed: true, + } + + if len(heights) == 0 { + return result + } + + // Heights should already be sorted + minHeight := heights[0] + maxHeight := heights[len(heights)-1] + expected := maxHeight - minHeight + 1 + + if int64(len(heights)) != expected { + result.Passed = false + + // Find missing blocks + heightSet := make(map[int64]bool) + for _, h := range heights { + heightSet[h] = true + } + + missingCount := 0 + for h := minHeight; h <= maxHeight && missingCount < 100; h++ { + if !heightSet[h] { + result.Issues = append(result.Issues, fmt.Sprintf("Missing block at consensus height: %d", h)) + missingCount++ + } + } + if expected-int64(len(heights)) > 100 { + result.Issues = append(result.Issues, fmt.Sprintf("... and %d more missing blocks", expected-int64(len(heights))-100)) + } + } + + return result +} + +// checkTimestampMonotonicity checks if timestamps increase monotonically +func checkTimestampMonotonicity(timestamps []time.Time) CheckResult { + result := CheckResult{ + Name: "timestamp_monotonicity", + Passed: true, + } + + if len(timestamps) < 2 { + return result + } + + issueCount := 0 + for i := 1; i < len(timestamps) && issueCount < 10; i++ { + if timestamps[i].Before(timestamps[i-1]) { + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Timestamp at index %d (%s) is before index %d (%s)", + i, timestamps[i].Format(time.RFC3339), + i-1, timestamps[i-1].Format(time.RFC3339))) + issueCount++ + } + } + + return result +} + +// checkBlockIntervalAnomalies checks if block intervals are within 3σ of mean +func checkBlockIntervalAnomalies(heights []int64, timestamps []time.Time) CheckResult { + result := CheckResult{ + Name: "block_interval_anomalies", + Passed: true, + } + + if len(timestamps) < 3 || len(heights) != len(timestamps) { + return result + } + + // Calculate intervals + intervals := make([]float64, 0, len(timestamps)-1) + for i := 1; i < len(timestamps); i++ { + interval := timestamps[i].Sub(timestamps[i-1]).Seconds() + if interval >= 0 { + intervals = append(intervals, interval) + } + } + + if len(intervals) < 3 { + return result + } + + // Calculate mean and stddev + sum := 0.0 + for _, v := range intervals { + sum += v + } + mean := sum / float64(len(intervals)) + + variance := 0.0 + for _, v := range intervals { + variance += (v - mean) * (v - mean) + } + stddev := math.Sqrt(variance / float64(len(intervals))) + + // Find anomalies (more than 3σ from mean) + threshold := 3.0 * stddev + issueCount := 0 + for i, interval := range intervals { + if issueCount >= 10 { + break + } + if math.Abs(interval-mean) > threshold { + result.Passed = false + // Report consensus heights where the interval spans (intervals[i] is between heights[i] and heights[i+1]) + result.Issues = append(result.Issues, + fmt.Sprintf("Anomalous interval at consensus heights %d-%d: %.1fs", + heights[i], heights[i+1], interval)) + issueCount++ + } + } + + return result +} + +// checkReferentialIntegrity checks if every block has a commit and vice versa +func checkReferentialIntegrity(blocks, commits map[int64]bool, oldestHeight, newestHeight int64) CheckResult { + result := CheckResult{ + Name: "referential_integrity", + Passed: true, + } + + // Check if every block has a commit + for height := range blocks { + if !commits[height] { + // Skip expected edge case: newest block may not have commit yet + if height == newestHeight { + result.Issues = append(result.Issues, + fmt.Sprintf("Block at consensus height %d has no commit (expected: last height)", height)) + continue + } + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Block at consensus height %d has no commit", height)) + if len(result.Issues) >= 10 { + break + } + } + } + + // Check if every commit has a block + for height := range commits { + if !blocks[height] { + // Skip expected edge case: oldest commit may reference previous block + if height == oldestHeight-1 { + result.Issues = append(result.Issues, + fmt.Sprintf("Commit at consensus height %d has no block (expected: first height-1)", height)) + continue + } + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Commit at consensus height %d has no block", height)) + if len(result.Issues) >= 10 { + break + } + } + } + + return result +} + +// processRuntimeHistoryEntry processes a single runtime-history entry +func processRuntimeHistoryEntry(key, value []byte, collector *runtimeHistoryCollector, errorCounts map[string]int) { + keyInfo := decodeRuntimeHistoryKey(key) + collector.keyTypes[keyInfo.KeyType]++ + + if value == nil { + return + } + + // Only process block entries for height/timestamp/consensus height extraction + if keyInfo.KeyType != "block" { + return + } + + valueInfo := decodeRuntimeHistoryValue(keyInfo.KeyType, value) + + // Count decode errors + if valueInfo.RawError != "" { + errorCounts[valueInfo.RawError]++ + return + } + + // Extract data from block info + if valueInfo.Block != nil && !valueInfo.Block.BlockNil { + // Add runtime height + if valueInfo.Block.RuntimeHeight > 0 { + collector.heights = append(collector.heights, int64(valueInfo.Block.RuntimeHeight)) + } + + // Add consensus height + if valueInfo.Block.ConsensusHeight > 0 { + collector.consensusHeights = append(collector.consensusHeights, valueInfo.Block.ConsensusHeight) + } + + // Add timestamp + if valueInfo.Block.Timestamp != "" { + ts, err := time.Parse(time.RFC3339, valueInfo.Block.Timestamp) + if err == nil { + collector.timestamps = append(collector.timestamps, ts) + } else { + errorCounts["timestamp_parse_error"]++ + } + } + } +} + +func buildRuntimeBlockStats(collector *runtimeHistoryCollector) *RuntimeBlockStats { + if len(collector.heights) == 0 { + return &RuntimeBlockStats{} + } + + // Sort runtime heights + sort.Slice(collector.heights, func(i, j int) bool { + return collector.heights[i] < collector.heights[j] + }) + + // Sort consensus heights + sort.Slice(collector.consensusHeights, func(i, j int) bool { + return collector.consensusHeights[i] < collector.consensusHeights[j] + }) + + // Sort timestamps + sort.Slice(collector.timestamps, func(i, j int) bool { + return collector.timestamps[i].Before(collector.timestamps[j]) + }) + + stats := &RuntimeBlockStats{ + OldestRuntimeHeight: collector.heights[0], + NewestRuntimeHeight: collector.heights[len(collector.heights)-1], + TotalBlocks: int64(len(collector.heights)), + } + + // Set consensus heights + if len(collector.consensusHeights) > 0 { + stats.OldestConsensusHeight = collector.consensusHeights[0] + stats.NewestConsensusHeight = collector.consensusHeights[len(collector.consensusHeights)-1] + } + + // Check if runtime block sequence is complete + expectedBlocks := stats.NewestRuntimeHeight - stats.OldestRuntimeHeight + 1 + stats.IsComplete = (stats.TotalBlocks == expectedBlocks) + + // Timestamps + if len(collector.timestamps) > 0 { + stats.OldestTimestamp = collector.timestamps[0].Format(time.RFC3339) + stats.NewestTimestamp = collector.timestamps[len(collector.timestamps)-1].Format(time.RFC3339) + + // Calculate interval statistics + if len(collector.timestamps) > 1 { + intervalStats := &SizeStatsAccumulator{} + for i := 1; i < len(collector.timestamps); i++ { + interval := collector.timestamps[i].Sub(collector.timestamps[i-1]).Milliseconds() + if interval >= 0 { + intervalStats.Add(interval) + } + } + finalStats := intervalStats.Finalize() + stats.IntervalStats = &finalStats + } + } + + // Note: Transaction and event stats would be populated from runtime-mkvs analysis + // For now, these remain nil as runtime-history only contains block metadata + + return stats +} + +// runRuntimeHistoryChecks runs all consistency checks for runtime-history +func runRuntimeHistoryChecks(collector *runtimeHistoryCollector) []CheckResult { + checks := []CheckResult{} + + // runtime_block_sequence_complete + checks = append(checks, checkRuntimeBlockSequence(collector.heights)) + + // consensus_height_mapping + checks = append(checks, checkConsensusHeightMapping(collector.consensusHeights, collector.heights)) + + // timestamp_monotonicity + checks = append(checks, checkTimestampMonotonicity(collector.timestamps)) + + return checks +} + +// checkRuntimeBlockSequence checks if runtime blocks are sequential with no gaps +func checkRuntimeBlockSequence(heights []int64) CheckResult { + result := CheckResult{ + Name: "runtime_block_sequence_complete", + Passed: true, + } + + if len(heights) == 0 { + return result + } + + // Heights should already be sorted + minHeight := heights[0] + maxHeight := heights[len(heights)-1] + expected := maxHeight - minHeight + 1 + + if int64(len(heights)) != expected { + result.Passed = false + + // Find missing blocks + heightSet := make(map[int64]bool) + for _, h := range heights { + heightSet[h] = true + } + + missingCount := 0 + for h := minHeight; h <= maxHeight && missingCount < 100; h++ { + if !heightSet[h] { + result.Issues = append(result.Issues, fmt.Sprintf("Missing runtime block at height: %d", h)) + missingCount++ + } + } + if expected-int64(len(heights)) > 100 { + result.Issues = append(result.Issues, fmt.Sprintf("... and %d more missing blocks", expected-int64(len(heights))-100)) + } + } + + return result +} + +// checkConsensusHeightMapping checks if consensus height mapping is valid +func checkConsensusHeightMapping(consensusHeights []int64, runtimeHeights []int64) CheckResult { + result := CheckResult{ + Name: "consensus_height_mapping", + Passed: true, + } + + if len(consensusHeights) == 0 { + return result + } + + // Check for monotonicity in consensus heights + issueCount := 0 + for i := 1; i < len(consensusHeights) && issueCount < 10; i++ { + if consensusHeights[i] < consensusHeights[i-1] { + result.Passed = false + runtimeRange := "" + if i < len(runtimeHeights) { + runtimeRange = fmt.Sprintf(", runtime: %d-%d", runtimeHeights[i-1], runtimeHeights[i]) + } + result.Issues = append(result.Issues, + fmt.Sprintf("Consensus height decreased: %d to %d (consensus: %d-%d%s)", + consensusHeights[i-1], consensusHeights[i], + consensusHeights[i-1], consensusHeights[i], runtimeRange)) + issueCount++ + } + } + + // Check for large gaps in consensus heights (>10000 blocks) + const maxGap = 10000 + for i := 1; i < len(consensusHeights) && issueCount < 10; i++ { + gap := consensusHeights[i] - consensusHeights[i-1] + if gap > maxGap { + result.Passed = false + runtimeRange := "" + if i < len(runtimeHeights) { + runtimeRange = fmt.Sprintf(", runtime: %d-%d", runtimeHeights[i-1], runtimeHeights[i]) + } + result.Issues = append(result.Issues, + fmt.Sprintf("Large consensus height gap: %d blocks (consensus: %d-%d%s)", + gap, consensusHeights[i-1], consensusHeights[i], runtimeRange)) + issueCount++ + } + } + + return result +} + +// processConsensusMkvsEntry processes a single consensus-mkvs entry +func processConsensusMkvsEntry(key, value []byte, collector *consensusMkvsCollector, errorCounts map[string]int) { + keyInfo := decodeConsensusMkvsKey(key) + collector.keyTypes[keyInfo.KeyType]++ + + // Track write_log versions for consistency checks + if keyInfo.KeyType == "write_log" && keyInfo.ConsensusHeight > 0 { + collector.writeLogVersions = append(collector.writeLogVersions, uint64(keyInfo.ConsensusHeight)) + } + + // Process node values to extract node types + if keyInfo.KeyType == "node" && value != nil { + valueInfo := decodeConsensusMkvsValue(keyInfo.KeyType, value) + + if valueInfo.RawError != "" { + errorCounts[valueInfo.RawError]++ + } + + // Count node types + if valueInfo.NodeType != "" { + collector.nodeTypes[valueInfo.NodeType]++ + + if valueInfo.NodeType == "leaf" { + collector.totalLeaves++ + + // Inline height extraction (module-specific) + var height int64 + if valueInfo.Leaf != nil && valueInfo.Leaf.Module == "roothash" && len(key) >= 41 { + height = int64(binary.BigEndian.Uint64(key[len(key)-8:])) + if height <= 0 || height >= 100000000 { + height = 0 // sanity check + } + } + + if height > 0 { + collector.leafCountsByHeight[height]++ + + // Try to count transactions/events from CBOR value + if valueInfo.Leaf != nil && valueInfo.Leaf.ValueType == "cbor" && len(value) > 0 { + // Try to decode CBOR and look for transaction/event arrays + var decoded map[string]interface{} + if err := cbor.Unmarshal(value, &decoded); err == nil { + if txArray, ok := decoded["transactions"].([]interface{}); ok { + collector.txCountsByHeight[height] += len(txArray) + } + if evArray, ok := decoded["events"].([]interface{}); ok { + collector.eventCountsByHeight[height] += len(evArray) + } + } + } + } + + // Track module distribution + if valueInfo.Leaf != nil && valueInfo.Leaf.Module != "" { + collector.modules[valueInfo.Leaf.Module]++ + } + } else if valueInfo.NodeType == "internal" { + collector.totalInternal++ + } + } + } +} + +// processRuntimeMkvsEntry processes a single runtime-mkvs entry +func processRuntimeMkvsEntry(key, value []byte, collector *runtimeMkvsCollector, errorCounts map[string]int) { + keyInfo := decodeRuntimeMkvsKey(key) + collector.keyTypes[keyInfo.KeyType]++ + + // Track write_log versions and count transactions/events + if keyInfo.KeyType == "write_log" && keyInfo.RuntimeHeight > 0 { + collector.writeLogVersions = append(collector.writeLogVersions, keyInfo.RuntimeHeight) + + // Decode write_log and count transactions/events + if value != nil { + valueInfo := decodeRuntimeMkvsValue(keyInfo.KeyType, value) + + if valueInfo.WriteLog != nil { + height := keyInfo.RuntimeHeight + + for _, entry := range valueInfo.WriteLog { + if len(entry.Key) < 1 || entry.Value == nil { + continue + } + + module := decodeRuntimeModuleKey(entry.Key) + leafValue := decodeRuntimeLeafValue(module, entry.Key, entry.Value) + if leafValue == nil { + continue + } + + // Count transactions + if leafValue.EVMTxInput != nil { + collector.txCountsByHeight[height]++ + collector.evmTxCountsByHeight[height]++ + if leafValue.EVMTxInput.EVMTx != nil && leafValue.EVMTxInput.EVMTx.Type != "" { + if collector.evmTxTypesByHeight[height] == nil { + collector.evmTxTypesByHeight[height] = make(map[string]int) + } + collector.evmTxTypesByHeight[height][leafValue.EVMTxInput.EVMTx.Type]++ + } + } + + // Count events + if leafValue.EVMEvent != nil { + collector.eventCountsByHeight[height]++ + collector.evmEventCountsByHeight[height]++ + if leafValue.EVMEvent.EventSignature != "" { + if collector.evmEventSigsByHeight[height] == nil { + collector.evmEventSigsByHeight[height] = make(map[string]int) + } + collector.evmEventSigsByHeight[height][leafValue.EVMEvent.EventSignature]++ + } + } else if leafValue.RuntimeConsensusEvent != nil || leafValue.ValueType == "io_event" { + collector.eventCountsByHeight[height]++ + if collector.runtimeEventsByHeight[height] == nil { + collector.runtimeEventsByHeight[height] = make(map[string]int) + } + collector.runtimeEventsByHeight[height][module]++ + } + } + } + } + } + + // Process node values to extract node types and modules + if keyInfo.KeyType == "node" && value != nil { + valueInfo := decodeRuntimeMkvsValue(keyInfo.KeyType, value) + + if valueInfo.RawError != "" { + errorCounts[valueInfo.RawError]++ + } + + // Count node types + if valueInfo.NodeType != "" { + collector.nodeTypes[valueInfo.NodeType]++ + + if valueInfo.NodeType == "leaf" { + collector.totalLeaves++ + // Extract module from leaf + if valueInfo.Leaf != nil && valueInfo.Leaf.Module != "" { + collector.modules[valueInfo.Leaf.Module]++ + } + } else if valueInfo.NodeType == "internal" { + collector.totalInternal++ + } + } + } +} + +func buildConsensusMkvsStats(collector *consensusMkvsCollector) *MKVSStats { + stats := &MKVSStats{ + NodeTypeDistribution: collector.nodeTypes, + TotalNode: collector.totalLeaves + collector.totalInternal, + } + + // Include module distribution for consensus MKVS + if len(collector.modules) > 0 { + stats.ModuleDistribution = collector.modules + // Calculate total across all modules + for _, count := range collector.modules { + stats.TotalModule += count + } + } + + // Add write_log statistics + if len(collector.writeLogVersions) > 0 { + stats.WriteLogStats = calculateWriteLogStats(collector.writeLogVersions) + } + + return stats +} + +func buildRuntimeMkvsStats(collector *runtimeMkvsCollector) *MKVSStats { + stats := &MKVSStats{ + NodeTypeDistribution: collector.nodeTypes, + TotalNode: collector.totalLeaves + collector.totalInternal, + } + + // Include module distribution for runtime MKVS + if len(collector.modules) > 0 { + stats.ModuleDistribution = collector.modules + // Calculate total across all modules + for _, count := range collector.modules { + stats.TotalModule += count + } + } + + // Add write_log statistics + if len(collector.writeLogVersions) > 0 { + stats.WriteLogStats = calculateWriteLogStats(collector.writeLogVersions) + } + + return stats +} + +// calculateWriteLogStats computes statistics about available write_log entries +func calculateWriteLogStats(versions []uint64) *WriteLogStats { + if len(versions) == 0 { + return nil + } + + // Sort to find range + sortedVersions := make([]uint64, len(versions)) + copy(sortedVersions, versions) + sort.Slice(sortedVersions, func(i, j int) bool { + return sortedVersions[i] < sortedVersions[j] + }) + + oldest := sortedVersions[0] + newest := sortedVersions[len(sortedVersions)-1] + versionRange := newest - oldest + 1 + + // Calculate coverage (what percentage of the range is present) + coverage := 0.0 + if versionRange > 0 { + coverage = (float64(len(versions)) / float64(versionRange)) * 100.0 + } + + return &WriteLogStats{ + OldestVersion: oldest, + NewestVersion: newest, + TotalEntries: int64(len(versions)), + VersionRange: versionRange, + Coverage: coverage, + } +} + +// populateRuntimeTransactionEventStats builds transaction and event stats from write_log data +func populateRuntimeTransactionEventStats(stats *RuntimeBlockStats, collector *runtimeMkvsCollector) { + // Build transaction stats + if len(collector.txCountsByHeight) > 0 { + txStats := &RuntimeTransactionStats{ + EVMTypeDistribution: make(map[string]int64), + } + + // Calculate per-block stats + perBlockStats := &SizeStatsAccumulator{} + evmPerBlockStats := &SizeStatsAccumulator{} + + for height, count := range collector.txCountsByHeight { + txStats.Total += int64(count) + perBlockStats.Add(int64(count)) + + // EVM transaction counts + if evmCount, exists := collector.evmTxCountsByHeight[height]; exists { + txStats.EVMTransactions += int64(evmCount) + evmPerBlockStats.Add(int64(evmCount)) + } + + // Aggregate EVM type distribution + if types, exists := collector.evmTxTypesByHeight[height]; exists { + for txType, typeCount := range types { + txStats.EVMTypeDistribution[txType] += int64(typeCount) + } + } + } + + finalPerBlock := perBlockStats.Finalize() + txStats.PerBlockStats = &finalPerBlock + + if evmPerBlockStats.count > 0 { + finalEVM := evmPerBlockStats.Finalize() + txStats.EVMSizeStats = &finalEVM + } + + stats.TransactionStats = txStats + } + + // Build event stats + if len(collector.eventCountsByHeight) > 0 { + eventStats := &RuntimeEventStats{ + RuntimeEventTypes: make(map[string]int64), + EVMSignatures: make(map[string]int64), + } + + perBlockStats := &SizeStatsAccumulator{} + + for height, count := range collector.eventCountsByHeight { + eventStats.Total += int64(count) + perBlockStats.Add(int64(count)) + + // EVM event counts + if evmCount, exists := collector.evmEventCountsByHeight[height]; exists { + eventStats.EVMEvents += int64(evmCount) + } + + // Aggregate EVM signatures + if sigs, exists := collector.evmEventSigsByHeight[height]; exists { + for sig, sigCount := range sigs { + eventStats.EVMSignatures[sig] += int64(sigCount) + } + } + + // Aggregate runtime event types + if types, exists := collector.runtimeEventsByHeight[height]; exists { + for eventType, typeCount := range types { + eventStats.RuntimeEventTypes[eventType] += int64(typeCount) + } + } + } + + finalPerBlock := perBlockStats.Finalize() + eventStats.PerBlockStats = &finalPerBlock + + stats.EventStats = eventStats + } +} + +// checkConsensusMKVSHeightAnomalies detects unusual patterns in leaf counts per height +func checkConsensusMKVSHeightAnomalies(leafCountsByHeight map[int64]int) CheckResult { + result := CheckResult{Name: "consensus_mkvs_height_anomalies", Passed: true} + if len(leafCountsByHeight) < 3 { + return result + } + + // Calculate mean and stddev of leaf counts + sum := 0.0 + for _, count := range leafCountsByHeight { + sum += float64(count) + } + mean := sum / float64(len(leafCountsByHeight)) + + variance := 0.0 + for _, count := range leafCountsByHeight { + variance += (float64(count) - mean) * (float64(count) - mean) + } + stddev := math.Sqrt(variance / float64(len(leafCountsByHeight))) + + // Find anomalies (more than 3σ from mean) + threshold := 3.0 * stddev + if stddev == 0 { + return result + } + + // Sort heights for reporting + heights := make([]int64, 0, len(leafCountsByHeight)) + for height := range leafCountsByHeight { + heights = append(heights, height) + } + sort.Slice(heights, func(i, j int) bool { + return heights[i] < heights[j] + }) + + issueCount := 0 + for _, height := range heights { + count := float64(leafCountsByHeight[height]) + if math.Abs(count-mean) > threshold { + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Anomalous leaf count at height %d: %d nodes", height, int(count))) + issueCount++ + if issueCount >= 10 { + break + } + } + } + + return result +} + +// runConsensusMkvsChecks runs all consistency checks for consensus-mkvs databases +func runConsensusMkvsChecks(collector *consensusMkvsCollector) []CheckResult { + checks := []CheckResult{} + + // version_monotonicity + checks = append(checks, checkVersionMonotonicity(collector.writeLogVersions)) + + // consensus_mkvs_height_anomalies - run if height data available + if len(collector.leafCountsByHeight) > 0 { + checks = append(checks, checkConsensusMKVSHeightAnomalies(collector.leafCountsByHeight)) + } + + return checks +} + +// runRuntimeMkvsChecks runs all consistency checks for runtime-mkvs databases +func runRuntimeMkvsChecks(collector *runtimeMkvsCollector) []CheckResult { + checks := []CheckResult{} + + // version_monotonicity + checks = append(checks, checkVersionMonotonicity(collector.writeLogVersions)) + + // No height anomaly check for runtime MKVS (no height-based aggregation) + + return checks +} + +// checkVersionMonotonicity checks if versions increase monotonically (allowing duplicates) +func checkVersionMonotonicity(versions []uint64) CheckResult { + result := CheckResult{ + Name: "version_monotonicity", + Passed: true, + } + + if len(versions) < 2 { + return result + } + + // Check if versions are non-decreasing + issueCount := 0 + for i := 1; i < len(versions) && issueCount < 10; i++ { + if versions[i] < versions[i-1] { + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Version decreased: %d to %d at index %d", + versions[i-1], versions[i], i)) + issueCount++ + } + } + + return result +} + +func processConsensusEvidenceEntry(key, value []byte, collector *consensusEvidenceCollector, errorCounts map[string]int) { + keyInfo := decodeConsensusEvidenceKey(key) + collector.keyTypes[keyInfo.KeyType]++ + + // Collect consensus heights for gap checking + if keyInfo.ConsensusHeight > 0 { + collector.consensusHeights = append(collector.consensusHeights, keyInfo.ConsensusHeight) + } +} + +func processConsensusStateEntry(key, value []byte, collector *consensusStateCollector, errorCounts map[string]int) { + keyInfo := decodeConsensusStateKey(key) + collector.keyTypes[keyInfo.KeyType]++ + + // Collect ABCI response heights and data for analysis + if keyInfo.KeyType == "abci_responses" && keyInfo.ConsensusHeight > 0 { + height := keyInfo.ConsensusHeight + collector.abciResponseHeights = append(collector.abciResponseHeights, height) + + // Decode ABCI response (reuses existing logic from decode_consensus.go) + valueInfo := decodeConsensusStateValue(keyInfo.KeyType, value) + if valueInfo.ABCIResponse != nil { + resp := valueInfo.ABCIResponse + + // Extract tx count + collector.txCountsByHeight[height] = resp.TxResultCount + + // Extract transaction method counts + if resp.TransactionSummary != nil { + collector.txMethodsByHeight[height] = resp.TransactionSummary.MethodCounts + } + + // Extract event type counts + if resp.EventSummary != nil { + collector.eventTypesByHeight[height] = resp.EventSummary.EventTypeCounts + } + } + } +} + +// runConsensusEvidenceChecks runs consistency checks for consensus-evidence +func runConsensusEvidenceChecks(collector *consensusEvidenceCollector) []CheckResult { + checks := []CheckResult{} + + // consensus_height_sequence - check for large gaps + checks = append(checks, checkConsensusHeightSequence(collector.consensusHeights)) + + return checks +} + +// buildABCIAnalysisStats builds aggregated stats from collected data +func buildABCIAnalysisStats(collector *consensusStateCollector) *ABCIAnalysisStats { + if len(collector.txCountsByHeight) == 0 { + return nil + } + + stats := &ABCIAnalysisStats{ + TxMethodCounts: make(map[string]int64), + EventTypeCounts: make(map[string]int64), + } + + // Aggregate across all heights + for _, txCounts := range collector.txMethodsByHeight { + for method, count := range txCounts { + stats.TxMethodCounts[method] += int64(count) + } + } + + for _, eventCounts := range collector.eventTypesByHeight { + for eventType, count := range eventCounts { + stats.EventTypeCounts[eventType] += int64(count) + } + } + + // Calculate totals + for _, count := range stats.TxMethodCounts { + stats.TotalTx += count + } + + for _, count := range stats.EventTypeCounts { + stats.TotalEvent += count + } + + return stats +} + +// checkABCIResponseAnomalies detects unusual patterns using pre-computed tx counts +func checkABCIResponseAnomalies(txCountsByHeight map[int64]int) CheckResult { + result := CheckResult{Name: "abci_response_anomalies", Passed: true} + + if len(txCountsByHeight) < 3 { + return result + } + + // Calculate mean and stddev of tx counts + sum := 0.0 + for _, count := range txCountsByHeight { + sum += float64(count) + } + mean := sum / float64(len(txCountsByHeight)) + + variance := 0.0 + for _, count := range txCountsByHeight { + variance += (float64(count) - mean) * (float64(count) - mean) + } + stddev := math.Sqrt(variance / float64(len(txCountsByHeight))) + + // Find anomalies (more than 3σ from mean) + threshold := 3.0 * stddev + if stddev == 0 { + return result + } + + // Sort heights for reporting + heights := make([]int64, 0, len(txCountsByHeight)) + for height := range txCountsByHeight { + heights = append(heights, height) + } + sort.Slice(heights, func(i, j int) bool { + return heights[i] < heights[j] + }) + + issueCount := 0 + for _, height := range heights { + count := float64(txCountsByHeight[height]) + if math.Abs(count-mean) > threshold { + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Anomalous tx count at height %d: %d transactions", height, int(count))) + issueCount++ + if issueCount >= 10 { + break + } + } + } + + return result +} + +// runConsensusStateChecks runs consistency checks for consensus-state +func runConsensusStateChecks(collector *consensusStateCollector) []CheckResult { + checks := []CheckResult{} + + // abci_response_complete - check that all heights have ABCI responses + checks = append(checks, checkABCIResponseComplete(collector.abciResponseHeights)) + + // abci_response_anomalies - check for unusual tx count patterns + checks = append(checks, checkABCIResponseAnomalies(collector.txCountsByHeight)) + + return checks +} + +// checkConsensusHeightSequence checks for large gaps in consensus heights (>1000 blocks) +func checkConsensusHeightSequence(consensusHeights []int64) CheckResult { + result := CheckResult{ + Name: "consensus_height_sequence", + Passed: true, + } + + if len(consensusHeights) < 2 { + return result + } + + // Sort heights + sorted := make([]int64, len(consensusHeights)) + copy(sorted, consensusHeights) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i] < sorted[j] + }) + + // Check for large gaps (>1000 blocks) + const maxGap = 1000 + issueCount := 0 + for i := 1; i < len(sorted) && issueCount < 10; i++ { + gap := sorted[i] - sorted[i-1] + if gap > maxGap { + result.Passed = false + result.Issues = append(result.Issues, + fmt.Sprintf("Large consensus height gap: %d blocks (consensus: %d-%d)", + gap, sorted[i-1], sorted[i])) + issueCount++ + } + } + + return result +} + +// checkABCIResponseComplete checks if ABCI responses form a complete sequence +func checkABCIResponseComplete(abciHeights []int64) CheckResult { + result := CheckResult{ + Name: "abci_response_complete", + Passed: true, + } + + if len(abciHeights) == 0 { + return result + } + + // Sort heights + sorted := make([]int64, len(abciHeights)) + copy(sorted, abciHeights) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i] < sorted[j] + }) + + minHeight := sorted[0] + maxHeight := sorted[len(sorted)-1] + expected := maxHeight - minHeight + 1 + + if int64(len(sorted)) != expected { + result.Passed = false + + // Find missing heights + heightSet := make(map[int64]bool) + for _, h := range sorted { + heightSet[h] = true + } + + missingCount := 0 + for h := minHeight; h <= maxHeight && missingCount < 100; h++ { + if !heightSet[h] { + result.Issues = append(result.Issues, fmt.Sprintf("Missing ABCI response at consensus height: %d", h)) + missingCount++ + } + } + if expected-int64(len(sorted)) > 100 { + result.Issues = append(result.Issues, fmt.Sprintf("... and %d more missing ABCI responses", expected-int64(len(sorted))-100)) + } + } + + return result +} diff --git a/badgerdb-analyzer/db_common.go b/badgerdb-analyzer/db_common.go new file mode 100644 index 0000000..97b1805 --- /dev/null +++ b/badgerdb-analyzer/db_common.go @@ -0,0 +1,144 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" +) + +// createLocalMirror creates a temporary local directory with database files copied/symlinked +// to avoid mmap issues on FUSE filesystems. Returns temp path and cleanup function. +func createLocalMirror(fusePath string) (string, func(), error) { + // Create temp directory on local filesystem (not FUSE) + tmpDir, err := os.MkdirTemp("", "badgerdb-temp-*") + if err != nil { + return "", nil, fmt.Errorf("failed to create temp directory: %w", err) + } + + cleanup := func() { + if err := os.RemoveAll(tmpDir); err != nil { + fmt.Fprintf(os.Stderr, "Warning: Failed to clean up temp directory %s: %v\n", tmpDir, err) + } + } + + fmt.Fprintf(os.Stderr, " Creating local mirror in: %s\n", tmpDir) + + entries, err := os.ReadDir(fusePath) + if err != nil { + cleanup() + return "", nil, fmt.Errorf("failed to read database directory: %w", err) + } + + copiedCount := 0 + linkedCount := 0 + skippedCount := 0 + + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + srcPath := filepath.Join(fusePath, name) + dstPath := filepath.Join(tmpDir, name) + + // Skip memtable files - these will be created fresh + if strings.HasSuffix(name, ".mem") { + fmt.Fprintf(os.Stderr, " Skipping memtable file: %s\n", name) + skippedCount++ + continue + } + + // Copy small metadata files (MANIFEST, DISCARD, etc.) + // Symlink large data files (*.sst, *.vlog) + if strings.HasSuffix(name, ".sst") || strings.HasSuffix(name, ".vlog") { + // Symlink large data files + if err := os.Symlink(srcPath, dstPath); err != nil { + cleanup() + return "", nil, fmt.Errorf("failed to symlink %s: %w", name, err) + } + linkedCount++ + } else { + // Copy small metadata files + if err := copyFile(srcPath, dstPath); err != nil { + cleanup() + return "", nil, fmt.Errorf("failed to copy %s: %w", name, err) + } + copiedCount++ + } + } + + fmt.Fprintf(os.Stderr, " Mirror created: %d files copied, %d files symlinked, %d files skipped\n", + copiedCount, linkedCount, skippedCount) + + return tmpDir, cleanup, nil +} + +// copyFile copies a file from src to dst +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return err + } + + return dstFile.Sync() +} + +// removeMemtableFiles renames .mem files by appending .bak suffix and returns a restore function. +// This allows opening databases with corrupted memtables by letting BadgerDB create fresh memtable files. +func removeMemtableFiles(dbPath string) (backupDir string, restore func() error, err error) { + entries, err := os.ReadDir(dbPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read database directory: %w", err) + } + + // Rename all .mem files to .mem.bak + count := 0 + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".mem") { + oldPath := filepath.Join(dbPath, entry.Name()) + newPath := oldPath + ".bak" + if err := os.Rename(oldPath, newPath); err != nil { + return "", nil, fmt.Errorf("failed to rename %s: %w", entry.Name(), err) + } + count++ + } + } + + fmt.Fprintf(os.Stderr, " Renamed %d memtable file(s) to *.mem.bak\n", count) + + // Restore function renames .mem.bak files back to .mem + restore = func() error { + entries, err := os.ReadDir(dbPath) + if err != nil { + return fmt.Errorf("failed to read database directory: %w", err) + } + count := 0 + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".mem.bak") { + oldPath := filepath.Join(dbPath, entry.Name()) + newPath := strings.TrimSuffix(oldPath, ".bak") + if err := os.Rename(oldPath, newPath); err != nil { + return fmt.Errorf("failed to restore %s: %w", entry.Name(), err) + } + count++ + } + } + fmt.Fprintf(os.Stderr, " Restored %d memtable file(s) from *.mem.bak\n", count) + return nil + } + + return dbPath, restore, nil +} diff --git a/badgerdb-analyzer/db_v2.go b/badgerdb-analyzer/db_v2.go new file mode 100644 index 0000000..7a4451b --- /dev/null +++ b/badgerdb-analyzer/db_v2.go @@ -0,0 +1,154 @@ +//go:build badgerv2 + +package main + +import ( + "fmt" + "os" + "strings" + + badger "github.com/dgraph-io/badger/v2" +) + +type ( + DB = badger.DB + Txn = badger.Txn + Item = badger.Item + Iterator = badger.Iterator + IteratorOptions = badger.IteratorOptions +) + +var DefaultIteratorOptions = badger.DefaultIteratorOptions + +func init() { + BadgerVersion = "v2.2007.2" +} + +// openDatabase tries three strategies in order: +// 1. ReadOnly mode (cleanly closed databases) +// 2. Minimal read-write mode (allows log replay on original path) +// 3. Local mirror mode (FUSE workaround with mmap-safe copy) +func openDatabase(path string) (*DB, error) { + // Stage 1: Try read-only mode first (clean databases) + fmt.Fprintf(os.Stderr, "Opening in read-only mode...\n") + opts := badger.DefaultOptions(path) + opts.ReadOnly = true + // Keep default logger enabled for diagnostics + + db, err := badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened in read-only mode\n") + return db, nil + } + + // Check for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open in read-only mode: %v\n", err) + + // Stage 2: Try minimal read-write mode (allows log replay) + fmt.Fprintf(os.Stderr, "Opening in minimal read-write mode (allows log replay)...\n") + opts = badger.DefaultOptions(path) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened in minimal read-write mode\n") + return db, nil + } + + // Check again for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open in minimal read-write mode: %v\n", err) + + // Stage 3: Try removing corrupted memtable files and retry + fmt.Fprintf(os.Stderr, "Opening without memtable files...\n") + if backupDir, restoreMemFiles, err := removeMemtableFiles(path); backupDir != "" { + // Memtable files were removed, retry opening + opts = badger.DefaultOptions(path) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened without memtable files\n") + return db, nil + } + + // Check for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + // Restore memtable files before continuing + fmt.Fprintf(os.Stderr, "Restoring memtable files...\n") + if restoreErr := restoreMemFiles(); restoreErr != nil { + fmt.Fprintf(os.Stderr, "Warning: Failed to restore memtable files: %v\n", restoreErr) + } else { + fmt.Fprintf(os.Stderr, "Memtable files restored\n") + } + } + + fmt.Fprintf(os.Stderr, "Failed to open without memtable files: %v\n", err) + + // Stage 4: Try local mirror workaround (FUSE compatibility) + fmt.Fprintf(os.Stderr, "Opening with local mirror (workaround for SIGBUS error on FUSE)...\n") + + // Create local mirror with symlinks to avoid mmap SIGBUS errors on FUSE + tmpDir, cleanup, err := createLocalMirror(path) + if err != nil { + return nil, fmt.Errorf("failed to create local mirror: %w", err) + } + // Note: cleanup is not called here - tmp files remain in /tmp and will be cleaned by OS + // This is acceptable for read-only analysis tools. For long-running services, use defer cleanup() + _ = cleanup + + // Open from local mirror with same minimal read-write settings + opts = badger.DefaultOptions(tmpDir) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened with local mirror (tmp dir: %s)\n", tmpDir) + return db, nil + } + + // Check again for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open with local mirror (tmp dir: %s): %v\n", tmpDir, err) + return nil, fmt.Errorf("failure: all opening strategies failed") +} diff --git a/badgerdb-analyzer/db_v3.go b/badgerdb-analyzer/db_v3.go new file mode 100644 index 0000000..edcf0e5 --- /dev/null +++ b/badgerdb-analyzer/db_v3.go @@ -0,0 +1,155 @@ +//go:build badgerv3 + +package main + +import ( + "fmt" + "os" + "strings" + + badger "github.com/dgraph-io/badger/v3" +) + +type ( + DB = badger.DB + Txn = badger.Txn + Item = badger.Item + Iterator = badger.Iterator + IteratorOptions = badger.IteratorOptions +) + +var DefaultIteratorOptions = badger.DefaultIteratorOptions + +func init() { + BadgerVersion = "v3.2103.5" +} + +// openDatabase tries three strategies in order: +// 1. ReadOnly mode (cleanly closed databases) +// 2. Minimal read-write mode (allows log replay on original path) +// 3. Local mirror mode (FUSE workaround with mmap-safe copy) +func openDatabase(path string) (*DB, error) { + // Stage 1: Try read-only mode first (clean databases) + fmt.Fprintf(os.Stderr, "Opening in read-only mode...\n") + opts := badger.DefaultOptions(path) + opts.ReadOnly = true + // Keep default logger enabled for diagnostics + + db, err := badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened in read-only mode\n") + return db, nil + } + + // Check for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open in read-only mode: %v\n", err) + + // Stage 2: Try minimal read-write mode (allows log replay) + fmt.Fprintf(os.Stderr, "Opening in minimal read-write mode (allows log replay)...\n") + opts = badger.DefaultOptions(path) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened in minimal read-write mode\n") + return db, nil + } + + // Check again for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open in minimal read-write mode: %v\n", err) + + // Stage 3: Try removing corrupted memtable files and retry + fmt.Fprintf(os.Stderr, "Opening without memtable files...\n") + if backupDir, restoreMemFiles, err := removeMemtableFiles(path); backupDir != "" { + // Memtable files were removed, retry opening + opts = badger.DefaultOptions(path) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened without memtable files\n") + return db, nil + } + + // Check for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + // Restore memtable files before continuing + fmt.Fprintf(os.Stderr, "Restoring memtable files...\n") + if restoreErr := restoreMemFiles(); restoreErr != nil { + fmt.Fprintf(os.Stderr, "Warning: Failed to restore memtable files: %v\n", restoreErr) + } else { + fmt.Fprintf(os.Stderr, "Memtable files restored\n") + } + } + + fmt.Fprintf(os.Stderr, "Failed to open without memtable files: %v\n", err) + + // Stage 4: Try local mirror workaround (FUSE compatibility) + // BadgerDB v3 always uses mmap for memtables, which doesn't work on FUSE filesystems + fmt.Fprintf(os.Stderr, "Opening with local mirror (workaround for SIGBUS error on FUSE)...\n") + + // Create local mirror with symlinks to avoid mmap SIGBUS errors on FUSE + tmpDir, cleanup, err := createLocalMirror(path) + if err != nil { + return nil, fmt.Errorf("failed to create local mirror: %w", err) + } + // Note: cleanup is not called here - tmp files remain in /tmp and will be cleaned by OS + // This is acceptable for read-only analysis tools. For long-running services, use defer cleanup() + _ = cleanup + + // Open from local mirror with same minimal read-write settings + opts = badger.DefaultOptions(tmpDir) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened with local mirror (tmp dir: %s)\n", tmpDir) + return db, nil + } + + // Check again for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open with local mirror (tmp dir: %s): %v\n", tmpDir, err) + return nil, fmt.Errorf("failure: all opening strategies failed") +} diff --git a/badgerdb-analyzer/db_v4.go b/badgerdb-analyzer/db_v4.go new file mode 100644 index 0000000..57db30f --- /dev/null +++ b/badgerdb-analyzer/db_v4.go @@ -0,0 +1,155 @@ +//go:build badgerv4 + +package main + +import ( + "fmt" + "os" + "strings" + + badger "github.com/dgraph-io/badger/v4" +) + +type ( + DB = badger.DB + Txn = badger.Txn + Item = badger.Item + Iterator = badger.Iterator + IteratorOptions = badger.IteratorOptions +) + +var DefaultIteratorOptions = badger.DefaultIteratorOptions + +func init() { + BadgerVersion = "v4.x.x" +} + +// openDatabase tries three strategies in order: +// 1. ReadOnly mode (cleanly closed databases) +// 2. Minimal read-write mode (allows log replay on original path) +// 3. Local mirror mode (FUSE workaround with mmap-safe copy) +func openDatabase(path string) (*DB, error) { + // Stage 1: Try read-only mode first (clean databases) + fmt.Fprintf(os.Stderr, "Opening in read-only mode...\n") + opts := badger.DefaultOptions(path) + opts.ReadOnly = true + // Keep default logger enabled for diagnostics + + db, err := badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened in read-only mode\n") + return db, nil + } + + // Check for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open in read-only mode: %v\n", err) + + // Stage 2: Try minimal read-write mode (allows log replay) + fmt.Fprintf(os.Stderr, "Opening in minimal read-write mode (allows log replay)...\n") + opts = badger.DefaultOptions(path) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened in minimal read-write mode\n") + return db, nil + } + + // Check again for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open in minimal read-write mode: %v\n", err) + + // Stage 3: Try removing corrupted memtable files and retry + fmt.Fprintf(os.Stderr, "Opening without memtable files...\n") + if backupDir, restoreMemFiles, err := removeMemtableFiles(path); backupDir != "" { + // Memtable files were removed, retry opening + opts = badger.DefaultOptions(path) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened without memtable files\n") + return db, nil + } + + // Check for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + // Restore memtable files before continuing + fmt.Fprintf(os.Stderr, "Restoring memtable files...\n") + if restoreErr := restoreMemFiles(); restoreErr != nil { + fmt.Fprintf(os.Stderr, "Warning: Failed to restore memtable files: %v\n", restoreErr) + } else { + fmt.Fprintf(os.Stderr, "Memtable files restored\n") + } + } + + fmt.Fprintf(os.Stderr, "Failed to open without memtable files: %v\n", err) + + // Stage 4: Try local mirror workaround (FUSE compatibility) + // BadgerDB v4 uses mmap for memtables similar to v3, which doesn't work on FUSE filesystems + fmt.Fprintf(os.Stderr, "Opening with local mirror (workaround for SIGBUS error on FUSE)...\n") + + // Create local mirror with symlinks to avoid mmap SIGBUS errors on FUSE + tmpDir, cleanup, err := createLocalMirror(path) + if err != nil { + return nil, fmt.Errorf("failed to create local mirror: %w", err) + } + // Note: cleanup is not called here - tmp files remain in /tmp and will be cleaned by OS + // This is acceptable for read-only analysis tools. For long-running services, use defer cleanup() + _ = cleanup + + // Open from local mirror with same minimal read-write settings + opts = badger.DefaultOptions(tmpDir) + opts.ReadOnly = false + opts.SyncWrites = false + opts.NumMemtables = 1 // Minimize memtable usage + opts.NumLevelZeroTables = 100 + opts.NumLevelZeroTablesStall = 200 + opts.NumCompactors = 0 + opts.CompactL0OnClose = false + opts.BypassLockGuard = true // Allow concurrent access + opts.DetectConflicts = false // Reduce overhead + // Keep default logger enabled for diagnostics + + db, err = badger.Open(opts) + if err == nil { + fmt.Fprintf(os.Stderr, "Successfully opened with local mirror (tmp dir: %s)\n", tmpDir) + return db, nil + } + + // Check again for version incompatibility + if strings.Contains(err.Error(), "unsupported version") { + return nil, fmt.Errorf("database version incompatible with this tool. Try a different badgerdb-sampler version.\nOriginal error: %v", err) + } + + fmt.Fprintf(os.Stderr, "Failed to open with local mirror (tmp dir: %s): %v\n", tmpDir, err) + return nil, fmt.Errorf("failure: all opening strategies failed") +} diff --git a/badgerdb-analyzer/decode_consensus.go b/badgerdb-analyzer/decode_consensus.go new file mode 100644 index 0000000..6172df1 --- /dev/null +++ b/badgerdb-analyzer/decode_consensus.go @@ -0,0 +1,1164 @@ +package main + +import ( + "encoding/base64" + "encoding/binary" + "fmt" + "strconv" + "strings" + "time" + + "github.com/fxamacker/cbor/v2" + "github.com/gogo/protobuf/proto" +) + +// decodeConsensusBlockstoreKey parses consensus-blockstore key and returns structured info. +// See: tendermint/store/store.go for key formats (H:, P:, C:, SC:, BH:) +func decodeConsensusBlockstoreKey(key []byte) *ConsensusBlockstoreKeyInfo { + info := &ConsensusBlockstoreKeyInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: len(key), + } + + if len(key) < 2 { + info.KeyType = "unknown" + return info + } + + // All keys start with 0x01 (dbVersion from Oasis BadgerDB wrapper) + if key[0] != 0x01 { + info.KeyType = "unknown" + return info + } + + // Parse ASCII key after 0x01 prefix + asciiKey := string(key[1:]) + + // blockStore state key + if asciiKey == "blockStore" { + info.KeyType = "blockstore_state" + return info + } + + // Parse structured keys: "H:{height}", "P:{height}:{part}", "C:{height}", "SC:{height}", "BH:{hash}" + if strings.HasPrefix(asciiKey, "H:") { + info.KeyType = "block_meta" + if h, err := strconv.ParseInt(asciiKey[2:], 10, 64); err == nil { + info.ConsensusHeight = h + } + return info + } + if strings.HasPrefix(asciiKey, "P:") { + info.KeyType = "block_part" + // Format: P:{height}:{part} + parts := strings.Split(asciiKey[2:], ":") + if len(parts) >= 1 { + if h, err := strconv.ParseInt(parts[0], 10, 64); err == nil { + info.ConsensusHeight = h + } + } + if len(parts) >= 2 { + if p, err := strconv.Atoi(parts[1]); err == nil { + info.PartIndex = p + } + } + return info + } + if strings.HasPrefix(asciiKey, "C:") { + info.KeyType = "block_commit" + if h, err := strconv.ParseInt(asciiKey[2:], 10, 64); err == nil { + info.ConsensusHeight = h + } + return info + } + if strings.HasPrefix(asciiKey, "SC:") { + info.KeyType = "seen_commit" + if h, err := strconv.ParseInt(asciiKey[3:], 10, 64); err == nil { + info.ConsensusHeight = h + } + return info + } + if strings.HasPrefix(asciiKey, "BH:") { + info.KeyType = "block_hash" + info.Hash = asciiKey[3:] + return info + } + + info.KeyType = "unknown" + return info +} + +// decodeConsensusBlockstoreValue decodes protobuf value and returns structured info. +// See: tendermint/proto/tendermint/store and tendermint/proto/tendermint/types +func decodeConsensusBlockstoreValue(keyType string, value []byte) *ConsensusBlockstoreValueInfo { + info := &ConsensusBlockstoreValueInfo{ + RawDump: formatRawValue(value, TruncateLongLen), + RawSize: len(value), + } + + switch keyType { + case "blockstore_state": + var state tmBlockStoreState + if err := proto.Unmarshal(value, &state); err == nil { + info.State = &ConsensusBlockStoreState{ + Base: state.Base, + ConsensusHeight: state.Height, + } + } else { + info.RawError = err.Error() + } + + case "block_meta": + var meta tmBlockMeta + if err := proto.Unmarshal(value, &meta); err == nil { + blockMeta := &ConsensusBlockMetaInfo{ + ConsensusHeight: meta.Header.Height, + NumTxs: meta.NumTxs, + } + + // Extract timestamp + if meta.Header.Time.Unix() > 0 { + info.Timestamp = meta.Header.Time.Unix() + blockMeta.Time = meta.Header.Time.Format(time.RFC3339) + } + + // Format AppHash (truncate to 16 hex chars) + blockMeta.AppHash = formatRawValue(meta.Header.AppHash, TruncateHashLen) + + // Truncate ChainID + chainID := meta.Header.ChainID + if len(chainID) > 20 { + chainID = chainID[:20] + "..." + } + blockMeta.ChainID = chainID + + info.BlockMeta = blockMeta + } else { + info.RawError = err.Error() + } + + case "block_part": + var part tmPart + if err := proto.Unmarshal(value, &part); err == nil { + info.Part = &ConsensusPartInfo{ + Index: part.Index, + PartSize: len(part.Bytes), + ProofTotal: part.Proof.Total, + } + } else { + info.RawError = err.Error() + } + + case "block_commit", "seen_commit": + var commit tmCommit + if err := proto.Unmarshal(value, &commit); err == nil { + info.Commit = &ConsensusCommitInfo{ + ConsensusHeight: commit.Height, + Round: commit.Round, + Signatures: len(commit.Signatures), + } + } else { + info.RawError = err.Error() + } + + case "block_hash": + // Block hash values are plain strings containing height numbers + if height, err := strconv.ParseInt(string(value), 10, 64); err == nil { + info.HashHeight = height + } + } + + return info +} + +// decodeConsensusEvidenceKey parses consensus-evidence key and returns structured info. +// Key format: 0x01 (dbVersion) + 0x00/0x01 (committed/pending) + "HEIGHT_HEX/HASH_HEX" +// See: cometbft/evidence/pool.go (keyCommitted, keyPending functions) +func decodeConsensusEvidenceKey(key []byte) *ConsensusEvidenceKeyInfo { + info := &ConsensusEvidenceKeyInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: len(key), + } + + if len(key) < 2 { + info.KeyType = "unknown" + return info + } + + // All keys start with 0x01 (dbVersion from Oasis BadgerDB wrapper) + if key[0] != 0x01 { + info.KeyType = "unknown" + return info + } + + // Second byte is the evidence state prefix + prefixByte := key[1] + info.PrefixByte = prefixByte + + switch prefixByte { + case 0x00: + info.KeyType = "committed" + case 0x01: + info.KeyType = "pending" + default: + info.KeyType = fmt.Sprintf("type_%02x", prefixByte) + return info + } + + // Parse key suffix: "HEIGHT_HEX/HASH_HEX" + if len(key) > 2 { + keySuffix := string(key[2:]) + parts := strings.Split(keySuffix, "/") + if len(parts) == 2 { + // Parse height from hex string (big-endian padded) + if h, err := strconv.ParseInt(parts[0], 16, 64); err == nil { + info.ConsensusHeight = h + } + // Store evidence hash + info.Hash = parts[1] + } + } + + return info +} + +// decodeConsensusEvidenceValue decodes evidence value and returns structured info. +// Committed evidence stores only Int64Value (height), pending evidence stores full Evidence protobuf. +// See: cometbft/evidence/pool.go (addPendingEvidence, markEvidenceAsCommitted) +func decodeConsensusEvidenceValue(keyType string, value []byte) (info *ConsensusEvidenceValueInfo) { + info = &ConsensusEvidenceValueInfo{ + RawDump: formatRawValue(value, TruncateLongLen), + RawSize: len(value), + } + + if len(value) == 0 { + return info + } + + // Recover from protobuf panics that occur with schema mismatches + defer func() { + if r := recover(); r != nil { + info.RawError = fmt.Sprintf("protobuf panic: %v", r) + } + }() + + // Committed evidence only stores the block height as Int64Value + if keyType == "committed" { + var heightValue tmInt64Value + if err := proto.Unmarshal(value, &heightValue); err == nil { + info.EvidenceType = "committed_marker" + info.CommittedHeight = heightValue.Value + return info + } else { + info.RawError = fmt.Sprintf("failed to decode committed evidence Int64Value: %v", err) + info.EvidenceType = "unknown" + return info + } + } + + // Pending evidence stores full evidence protobuf - try CometBFT DuplicateVoteEvidence first + cbSuccess := false + var cbDve cbDuplicateVoteEvidence + func() { + defer func() { + recover() // Silently catch panics from CometBFT unmarshal + }() + if err := proto.Unmarshal(value, &cbDve); err == nil && cbDve.VoteA != nil && cbDve.VoteB != nil { + cbSuccess = true + } + }() + + if cbSuccess { + info.SchemaVersion = "cb-v0.37" + info.EvidenceType = "duplicate_vote" + info.VoteAHeight = cbDve.VoteA.Height + info.VoteBHeight = cbDve.VoteB.Height + info.TotalVotingPower = cbDve.TotalVotingPower + info.ValidatorPower = cbDve.ValidatorPower + if cbDve.Timestamp.Unix() > 0 { + info.Timestamp = cbDve.Timestamp.Format(time.RFC3339) + } + return info + } + + // Fall back to Tendermint v0.34 DuplicateVoteEvidence + var dve tmDuplicateVoteEvidence + if err := proto.Unmarshal(value, &dve); err == nil && dve.VoteA != nil { + info.SchemaVersion = "tm-v0.34" + info.EvidenceType = "duplicate_vote" + info.VoteAHeight = dve.VoteA.Height + if dve.VoteB != nil { + info.VoteBHeight = dve.VoteB.Height + } + info.TotalVotingPower = dve.TotalVotingPower + info.ValidatorPower = dve.ValidatorPower + if dve.Timestamp.Unix() > 0 { + info.Timestamp = dve.Timestamp.Format(time.RFC3339) + } + return info + } + + // Try to decode as LightClientAttackEvidence (same for both versions) + var lca tmLightClientAttackEvidence + if err := proto.Unmarshal(value, &lca); err == nil && lca.ConflictingBlock != nil { + info.EvidenceType = "light_client_attack" + info.TotalVotingPower = lca.TotalVotingPower + if lca.Timestamp.Unix() > 0 { + info.Timestamp = lca.Timestamp.Format(time.RFC3339) + } + return info + } + + // If all parsing failed, show raw value + info.EvidenceType = "unknown" + if info.RawError == "" { + info.RawError = "failed to decode as any known evidence type" + } + return info +} + +// decodeConsensusMkvsKey parses consensus-mkvs key and returns structured info. +// Keys use keyformat encoding: [type_byte][data...] +// See: _oasis-core/go/storage/mkvs/db/badger/badger.go:31-66 +func decodeConsensusMkvsKey(key []byte) *ConsensusMkvsKeyInfo { + info := &ConsensusMkvsKeyInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: len(key), + } + + if len(key) < 1 { + info.KeyType = "unknown" + return info + } + + // First byte is the key type prefix + prefixByte := key[0] + data := key[1:] + + switch prefixByte { + case 0x00: + info.KeyType = "node" + info.Hash = formatRawValue(data, TruncateHashLen) + + case 0x01: + info.KeyType = "write_log" + if len(data) >= 8 { + info.ConsensusHeight = int64(binary.BigEndian.Uint64(data[0:8])) + if len(data) >= 8+33 { + info.RootType = fmt.Sprintf("%d", data[8]) + info.Hash = formatRawValue(data[9:9+32], TruncateHashLen) + } + } + + case 0x02: + info.KeyType = "roots_metadata" + if len(data) >= 8 { + info.ConsensusHeight = int64(binary.BigEndian.Uint64(data[0:8])) + } + + case 0x03: + info.KeyType = "root_updated_nodes" + if len(data) >= 8 { + info.ConsensusHeight = int64(binary.BigEndian.Uint64(data[0:8])) + if len(data) >= 8+33 { + info.RootType = fmt.Sprintf("%d", data[8]) + info.Hash = formatRawValue(data[9:9+32], TruncateHashLen) + } + } + + case 0x04: + info.KeyType = "metadata" + + case 0x05: + info.KeyType = "multipart_restore_log" + if len(data) >= 33 { + info.RootType = fmt.Sprintf("%d", data[0]) + info.Hash = formatRawValue(data[1:33], TruncateHashLen) + } else { + info.Hash = formatRawValue(data, TruncateHashLen) + } + + case 0x06: + info.KeyType = "root_node" + if len(data) >= 33 { + info.RootType = fmt.Sprintf("%d", data[0]) + info.Hash = formatRawValue(data[1:33], TruncateHashLen) + } else { + info.Hash = formatRawValue(data, TruncateHashLen) + } + + default: + info.KeyType = fmt.Sprintf("unknown_%02x", prefixByte) + } + + return info +} + +// decodeConsensusMkvsValue decodes consensus MKVS value and returns structured info. +// See: _oasis-core/go/storage/mkvs/node/node.go:26-32 (prefixes), 294-309 (InternalNode), 531-537 (LeafNode) +func decodeConsensusMkvsValue(keyType string, value []byte) *ConsensusMkvsValueInfo { + info := &ConsensusMkvsValueInfo{ + RawDump: formatRawValue(value, TruncateLongLen), + RawSize: len(value), + } + + if len(value) == 0 { + info.NodeType = "empty" + return info + } + + if keyType != "node" { + info.NodeType = "non_node" + return info + } + + // Parse MKVS node: 0x00=leaf, 0x01=internal, 0x02=nil + switch value[0] { + case 0x00: // LeafNode + info.NodeType = "leaf" + data := value[1:] + + // Try pre-v21.1.0 format (skip 8-byte Version field) + if len(data) >= 10 { + testKeySize := int(binary.LittleEndian.Uint16(data[8:10])) + if testKeySize > 0 && testKeySize <= 10000 && len(data) >= 10+testKeySize+4 { + data = data[8:] + } + } + + if len(data) < 2 { + info.RawError = "key length missing" + return info + } + + keySize := int(binary.LittleEndian.Uint16(data[0:2])) + data = data[2:] + + if len(data) < keySize { + info.RawError = fmt.Sprintf("key truncated (expected %s, got %s)", + formatApproxSize(keySize), formatApproxSize(len(data))) + return info + } + + key := data[:keySize] + data = data[keySize:] + + // Decode consensus module key prefix + module := decodeConsensusModuleKey(key) + + leaf := &ConsensusMkvsLeafInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: keySize, + Module: module, + } + + // Extract Oasis address for staking-related modules + // Staking keys have format: module_prefix (1 byte) + 21-byte Oasis address + if keySize == 22 { + switch key[0] { + case 0x50, 0x53, 0x54: // staking accounts, delegations, debonding_delegations + leaf.OasisAddress = (*(*OasisAddress)(key[1:22])).String() + } + } + // Entity/node keys also contain addresses + if keySize >= 22 { + switch key[0] { + case 0x10, 0x11: // registry entities, nodes + leaf.OasisAddress = (*(*OasisAddress)(key[1:22])).String() + } + } + + if len(data) < 4 { + info.RawError = "value length missing" + info.Leaf = leaf + return info + } + + valueSize := int(binary.LittleEndian.Uint32(data[0:4])) + data = data[4:] + leaf.ValueSize = valueSize + + if len(data) < valueSize { + info.RawError = fmt.Sprintf("value truncated (expected %s, got %s)", + formatApproxSize(valueSize), formatApproxSize(len(data))) + info.Leaf = leaf + return info + } + + leafValue := data[:valueSize] + leaf.ValueDump = formatRawValue(leafValue, TruncateLongLen) + + // Deterministic format lookup based on key prefix + format, exists := GetConsensusMKVSFormat(key) + if !exists { + // Unknown key prefix - try CBOR as fallback + var decoded interface{} + if err := cbor.Unmarshal(leafValue, &decoded); err == nil { + leaf.Value = formatCBORDetailed(decoded) + leaf.ValueType = "cbor" + } else { + leaf.ValueError = fmt.Sprintf("unknown key prefix 0x%02x: %s", key[0], err.Error()) + leaf.ValueType = "unknown" + } + } else { + // Known format - decode deterministically + switch format.Format { + case "cbor": + var decoded interface{} + if err := cbor.Unmarshal(leafValue, &decoded); err == nil { + leaf.Value = formatCBORDetailed(decoded) + leaf.ValueType = "cbor" + leaf.CBOR = format.Type // Store expected type + } else { + leaf.ValueError = err.Error() + leaf.ValueType = "cbor_error" + } + + case "binary": + // Raw binary data + leaf.ValueType = "raw_binary" + switch len(leafValue) { + case 32: + leaf.CBOR = fmt.Sprintf("%s (32-byte hash)", format.Description) + case 64: + leaf.CBOR = fmt.Sprintf("%s (64-byte signature)", format.Description) + case 65: + leaf.CBOR = fmt.Sprintf("%s (65-byte pubkey)", format.Description) + default: + leaf.CBOR = fmt.Sprintf("%s (%d bytes)", format.Description, len(leafValue)) + } + + case "empty": + // Index entry with empty value + leaf.ValueType = "empty" + leaf.CBOR = fmt.Sprintf("index entry: %s", format.Description) + } + } + + info.Leaf = leaf + return info + + case 0x01: // InternalNode + info.NodeType = "internal" + data := value[1:] + + // Try pre-v21.1.0 format (skip 8-byte Version field) + if len(data) >= 10 { + testLabelBits := binary.LittleEndian.Uint16(data[8:10]) + if testLabelBits <= 2048 && len(data) >= 10+(int(testLabelBits)+7)/8+1 { + data = data[8:] + } + } + + if len(data) < 2 { + info.RawError = "label bits missing" + return info + } + + labelBits := binary.LittleEndian.Uint16(data[0:2]) + data = data[2:] + + labelBytes := (int(labelBits) + 7) / 8 + if len(data) < labelBytes+1 { + info.RawError = fmt.Sprintf("label truncated (expected %s, got %s)", + formatApproxSize(labelBytes+1), formatApproxSize(len(data))) + info.Internal = &ConsensusMkvsInternalInfo{LabelBits: labelBits} + return info + } + + data = data[labelBytes:] // skip label + + internal := &ConsensusMkvsInternalInfo{LabelBits: labelBits} + + // Check for embedded leaf node or nil marker + if len(data) < 1 { + info.RawError = "missing leaf/nil marker" + info.Internal = internal + return info + } + + if data[0] == 0x02 { // NilNode marker - no embedded leaf + internal.HasLeaf = false + data = data[1:] // skip nil marker + } else if data[0] == 0x00 { // LeafNode prefix - embedded leaf present + internal.HasLeaf = true + // Skip embedded leaf: prefix(1) + keyLen(2) + key + valueLen(4) + value + data = data[1:] // skip prefix + if len(data) < 2 { + info.RawError = "embedded leaf key length missing" + info.Internal = internal + return info + } + keyLen := binary.LittleEndian.Uint16(data[0:2]) + data = data[2:] + if len(data) < int(keyLen)+4 { + info.RawError = fmt.Sprintf("embedded leaf truncated (expected %s, got %s)", + formatApproxSize(int(keyLen)+4), formatApproxSize(len(data))) + info.Internal = internal + return info + } + data = data[keyLen:] // skip key + valueLen := binary.LittleEndian.Uint32(data[0:4]) + data = data[4:] + if len(data) < int(valueLen) { + info.RawError = fmt.Sprintf("embedded leaf value truncated (expected %s, got %s)", + formatApproxSize(int(valueLen)), formatApproxSize(len(data))) + info.Internal = internal + return info + } + data = data[valueLen:] // skip value + } else { + info.RawError = fmt.Sprintf("unexpected marker 0x%02x", data[0]) + info.Internal = internal + return info + } + + // Read left and right hashes + if len(data) >= 32 { + internal.LeftHash = formatRawValue(data[:32], TruncateHashLen) + data = data[32:] + } + if len(data) >= 32 { + internal.RightHash = formatRawValue(data[:32], TruncateHashLen) + } + + info.Internal = internal + return info + + case 0x02: // NilNode + info.NodeType = "nil" + return info + + default: + info.NodeType = "unknown" + info.RawError = fmt.Sprintf("unknown prefix 0x%02x", value[0]) + return info + } +} + +// decodeConsensusStateKey parses consensus-state key and returns structured info. +func decodeConsensusStateKey(key []byte) *ConsensusStateKeyInfo { + info := &ConsensusStateKeyInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: len(key), + } + + if len(key) < 2 { + info.KeyType = "unknown" + return info + } + + // All keys start with 0x01 (dbVersion from Oasis BadgerDB wrapper) + if key[0] != 0x01 { + info.KeyType = "unknown" + return info + } + + // Parse ASCII key after 0x01 prefix + decoded := string(key[1:]) + + // Check if it's printable ASCII + if !isPrintableASCII(decoded) { + info.KeyType = "binary" + info.IsBinary = true + return info + } + + // Extract key type prefix before colon + colonIdx := strings.IndexByte(decoded, ':') + if colonIdx != -1 { + prefix := decoded[:colonIdx] + switch prefix { + case "abciResponsesKey": + info.KeyType = "abci_responses" + // Extract height from key like "abciResponsesKey:10000000" + if colonIdx < len(decoded)-1 { + if h, err := strconv.ParseInt(decoded[colonIdx+1:], 10, 64); err == nil { + info.ConsensusHeight = h + } + } + case "consensusParamsKey": + info.KeyType = "consensus_params" + // Extract height if present + if colonIdx < len(decoded)-1 { + if h, err := strconv.ParseInt(decoded[colonIdx+1:], 10, 64); err == nil { + info.ConsensusHeight = h + } + } + case "validatorsKey": + info.KeyType = "validators" + // Extract height if present + if colonIdx < len(decoded)-1 { + if h, err := strconv.ParseInt(decoded[colonIdx+1:], 10, 64); err == nil { + info.ConsensusHeight = h + } + } + case "stateKey": + info.KeyType = "state" + case "genesisDoc": + info.KeyType = "genesis" + default: + info.KeyType = prefix + } + return info + } + + info.KeyType = "text_key" + return info +} + +// decodeConsensusStateValue decodes state value and returns structured info. +func decodeConsensusStateValue(keyType string, value []byte) (info *ConsensusStateValueInfo) { + info = &ConsensusStateValueInfo{ + RawDump: formatRawValue(value, TruncateLongLen), + RawSize: len(value), + } + + if len(value) == 0 { + return info + } + + // Recover from protobuf panics that occur with schema mismatches + defer func() { + if r := recover(); r != nil { + info.RawError = fmt.Sprintf("protobuf panic: %v", r) + } + }() + + switch keyType { + case "abci_responses": + // Try CometBFT FinalizeBlock format first + cbSuccess := false + var cbResp cbResponseFinalizeBlock + func() { + defer func() { + recover() // Silently catch panics from CometBFT unmarshal + }() + if err := proto.Unmarshal(value, &cbResp); err == nil && (len(cbResp.TxResults) > 0 || len(cbResp.Events) > 0 || len(cbResp.ValidatorUpdates) > 0) { + cbSuccess = true + } + }() + + if cbSuccess { + info.SchemaVersion = "cb-v0.37" + abciResponseInfo := &ConsensusABCIResponseInfo{} + + // Count tx results + if cbResp.TxResults != nil { + abciResponseInfo.TxResultCount = len(cbResp.TxResults) + } + + // Count validator updates + if cbResp.ValidatorUpdates != nil { + abciResponseInfo.ValidatorUpdates = len(cbResp.ValidatorUpdates) + } + + // Count event types from all sources + eventSummary := &ConsensusEventSummary{ + EventTypeCounts: make(map[string]int), + } + + // Decode transactions and collect events from TxResults + txSummary := &ConsensusTransactionSummary{ + MethodCounts: make(map[string]int), + } + + if cbResp.TxResults != nil { + for _, txResult := range cbResp.TxResults { + // Count and decode events + for _, event := range txResult.Events { + eventSummary.EventTypeCounts[event.Type]++ + + // Decode event if sample limit not reached + if decodedEvents, err := decodeConsensusEvent(event); err == nil { + for _, decodedEvent := range decodedEvents { + if len(eventSummary.Events) < 10 { + eventSummary.Events = append(eventSummary.Events, decodedEvent) + } + } + } + } + + // Decode transaction if present + if len(txResult.Data) > 0 { + if decodedTx, err := decodeConsensusTransaction(txResult.Data); err == nil { + txSummary.MethodCounts[decodedTx.Method]++ + // Only include first few transactions for sample + if len(txSummary.Transactions) < 10 { + txSummary.Transactions = append(txSummary.Transactions, decodedTx) + } + } + } + } + } + + // Events from root Events field (BeginBlock-style events in CometBFT) + if cbResp.Events != nil { + for _, event := range cbResp.Events { + eventSummary.EventTypeCounts[event.Type]++ + + // Decode event if sample limit not reached + if decodedEvents, err := decodeConsensusEvent(event); err == nil { + for _, decodedEvent := range decodedEvents { + if len(eventSummary.Events) < 10 { + eventSummary.Events = append(eventSummary.Events, decodedEvent) + } + } + } + } + } + + abciResponseInfo.EventCount = len(cbResp.Events) + + if len(txSummary.MethodCounts) > 0 { + abciResponseInfo.TransactionSummary = txSummary + } + + if len(eventSummary.EventTypeCounts) > 0 { + abciResponseInfo.EventSummary = eventSummary + } + + info.ABCIResponse = abciResponseInfo + + } else { + // Fall back to Tendermint v0.34 format + var resp tmABCIResponses + if err := proto.Unmarshal(value, &resp); err == nil { + // Validate that unmarshal actually decoded meaningful data + if resp.DeliverTxs == nil && resp.BeginBlock == nil && resp.EndBlock == nil { + info.RawError = "protobuf unmarshal succeeded but all fields are nil (schema mismatch)" + } else { + info.SchemaVersion = "tm-v0.34" + abciResponseInfo := &ConsensusABCIResponseInfo{} + + // Count deliver_tx results (transaction results) + if resp.DeliverTxs != nil { + abciResponseInfo.TxResultCount = len(resp.DeliverTxs) + } + + // Count events from end_block + if resp.EndBlock != nil { + abciResponseInfo.EventCount = len(resp.EndBlock.Events) + if resp.EndBlock.ValidatorUpdates != nil { + abciResponseInfo.ValidatorUpdates = len(resp.EndBlock.ValidatorUpdates) + } + } + + // Count event types from all sources + eventSummary := &ConsensusEventSummary{ + EventTypeCounts: make(map[string]int), + } + + // Decode transactions and collect events from DeliverTxs + txSummary := &ConsensusTransactionSummary{ + MethodCounts: make(map[string]int), + } + + if resp.DeliverTxs != nil { + for _, txResult := range resp.DeliverTxs { + // Count and decode events + for _, event := range txResult.Events { + eventSummary.EventTypeCounts[event.Type]++ + + // Decode event if sample limit not reached + if decodedEvents, err := decodeConsensusEvent(event); err == nil { + for _, decodedEvent := range decodedEvents { + if len(eventSummary.Events) < 10 { + eventSummary.Events = append(eventSummary.Events, decodedEvent) + } + } + } + } + + // Decode transaction if present + if len(txResult.Data) > 0 { + if decodedTx, err := decodeConsensusTransaction(txResult.Data); err == nil { + txSummary.MethodCounts[decodedTx.Method]++ + // Only include first few transactions for sample + if len(txSummary.Transactions) < 10 { + txSummary.Transactions = append(txSummary.Transactions, decodedTx) + } + } + } + } + } + + // Events from BeginBlock + if resp.BeginBlock != nil { + for _, event := range resp.BeginBlock.Events { + eventSummary.EventTypeCounts[event.Type]++ + + // Decode event if sample limit not reached + if decodedEvents, err := decodeConsensusEvent(event); err == nil { + for _, decodedEvent := range decodedEvents { + if len(eventSummary.Events) < 10 { + eventSummary.Events = append(eventSummary.Events, decodedEvent) + } + } + } + } + } + + // Events from EndBlock + if resp.EndBlock != nil { + for _, event := range resp.EndBlock.Events { + eventSummary.EventTypeCounts[event.Type]++ + + // Decode event if sample limit not reached + if decodedEvents, err := decodeConsensusEvent(event); err == nil { + for _, decodedEvent := range decodedEvents { + if len(eventSummary.Events) < 10 { + eventSummary.Events = append(eventSummary.Events, decodedEvent) + } + } + } + } + } + + + if len(txSummary.MethodCounts) > 0 { + abciResponseInfo.TransactionSummary = txSummary + } + + if len(eventSummary.EventTypeCounts) > 0 { + abciResponseInfo.EventSummary = eventSummary + } + + info.ABCIResponse = abciResponseInfo + } + } else { + info.RawError = fmt.Sprintf("failed to decode as CometBFT or Tendermint v0.34 schema: %v", err) + } + } + + case "consensus_params": + var params tmConsensusParams + if err := proto.Unmarshal(value, ¶ms); err == nil { + // Fields are non-nullable structs, so always initialized + info.ConsensusParams = ¶ms + } else { + info.RawError = fmt.Sprintf("failed to decode consensus params: %v", err) + } + + case "validators": + var valSet tmValidatorSet + if err := proto.Unmarshal(value, &valSet); err == nil { + // Validate that unmarshal decoded meaningful data + if len(valSet.Validators) == 0 && valSet.Proposer == nil && valSet.TotalVotingPower == 0 { + info.RawError = "protobuf unmarshal succeeded but all fields are empty/nil (schema mismatch)" + } else { + info.ConsensusValidators = &valSet + } + } else { + info.RawError = fmt.Sprintf("failed to decode validator set: %v", err) + } + + case "state": + var state tmState + if err := proto.Unmarshal(value, &state); err == nil { + // Validate that unmarshal decoded meaningful data + if state.ChainID == "" && state.LastBlockHeight == 0 && state.InitialHeight == 0 { + info.RawError = "protobuf unmarshal succeeded but all fields are empty/zero (schema mismatch)" + } else { + info.ConsensusState = &state + } + } else { + info.RawError = fmt.Sprintf("failed to decode state: %v", err) + } + + case "genesis": + if value[0] != '{' { + info.RawError = "genesis document not in expected JSON format" + } + // Genesis is JSON, RawDump shows hex preview (already set above) + } + + return info +} + +// decodeConsensusTransaction decodes a raw CBOR-encoded transaction bytes into ConsensusTransactionInfo. +// Returns (decoded, error) where error is a parsing error. +// See: _oasis-core/go/consensus/api/transaction/transaction.go:42-54 +// See: _oasis-core/go/common/crypto/signature/signature.go:415-421 +// See: _oasis-core/go/staking/api/api.go for transaction body types +func decodeConsensusTransaction(rawTx []byte) (ConsensusTransactionInfo, error) { + info := ConsensusTransactionInfo{} + + // Decode SignedTransaction envelope + var signedTx cborConsensusSignedTransaction + if err := cbor.Unmarshal(rawTx, &signedTx); err != nil { + return info, fmt.Errorf("failed to decode SignedTransaction: %w", err) + } + + // Extract signer public key + info.Signer = formatRawValue(signedTx.Signature.PublicKey[:], TruncateLongLen) + + // Decode inner Transaction from the blob + var tx cborConsensusInnerTransaction + if err := cbor.Unmarshal(signedTx.Blob, &tx); err != nil { + return info, fmt.Errorf("failed to decode Transaction: %w", err) + } + + info.Nonce = tx.Nonce + info.Method = tx.Method + if tx.Fee != nil { + info.Fee = &ConsensusFee{ + Amount: tx.Fee.Amount, + Gas: tx.Fee.Gas, + } + } + + // Decode body based on method + if len(tx.Body) > 0 { + info.BodyHex = formatRawValue(tx.Body, TruncateLongLen) + info.BodySize = len(tx.Body) + + switch tx.Method { + case "staking.Transfer": + var transfer cborConsensusTransfer + if err := cbor.Unmarshal(tx.Body, &transfer); err == nil { + info.Body = transfer + } else { + info.BodyError = fmt.Sprintf("failed to decode transfer body: %v", err) + } + + case "staking.Burn": + var burn cborConsensusBurn + if err := cbor.Unmarshal(tx.Body, &burn); err == nil { + info.Body = burn + } else { + info.BodyError = fmt.Sprintf("failed to decode burn body: %v", err) + } + + case "staking.AddEscrow": + var escrow cborConsensusAddEscrow + if err := cbor.Unmarshal(tx.Body, &escrow); err == nil { + info.Body = escrow + } else { + info.BodyError = fmt.Sprintf("failed to decode add_escrow body: %v", err) + } + + case "staking.ReclaimEscrow": + var reclaim cborConsensusReclaimEscrow + if err := cbor.Unmarshal(tx.Body, &reclaim); err == nil { + info.Body = reclaim + } else { + info.BodyError = fmt.Sprintf("failed to decode reclaim_escrow body: %v", err) + } + + case "registry.RegisterEntity": + var regEntity cborConsensusRegisterEntity + if err := cbor.Unmarshal(tx.Body, ®Entity); err == nil { + info.Body = regEntity + } else { + info.BodyError = fmt.Sprintf("failed to decode register_entity body: %v", err) + } + + case "registry.RegisterNode": + var regNode cborConsensusRegisterNode + if err := cbor.Unmarshal(tx.Body, ®Node); err == nil { + info.Body = regNode + } else { + info.BodyError = fmt.Sprintf("failed to decode register_node body: %v", err) + } + + case "roothash.ExecutorCommit": + var execCommit cborConsensusExecutorCommit + if err := cbor.Unmarshal(tx.Body, &execCommit); err == nil { + info.Body = execCommit + } else { + info.BodyError = fmt.Sprintf("failed to decode executor_commit body: %v", err) + } + + case "governance.SubmitProposal": + var proposal cborConsensusSubmitProposal + if err := cbor.Unmarshal(tx.Body, &proposal); err == nil { + info.Body = proposal + } else { + info.BodyError = fmt.Sprintf("failed to decode submit_proposal body: %v", err) + } + + case "governance.CastVote": + var vote cborConsensusCastVote + if err := cbor.Unmarshal(tx.Body, &vote); err == nil { + info.Body = vote + } else { + info.BodyError = fmt.Sprintf("failed to decode cast_vote body: %v", err) + } + } + } + + return info, nil +} + +// decodeConsensusEvent decodes a Tendermint event into ConsensusEventInfo. +// Events use base64-encoded CBOR-marshaled bodies in the Value field. +// Returns all decoded attributes. On success returns (results, nil). +// See: _oasis-core/go/consensus/api/events/events.go (TypedAttribute pattern) +// See: _oasis-core/go/staking/api/api.go (event type definitions) +func decodeConsensusEvent(event tmEvent) ([]ConsensusEventInfo, error) { + if len(event.Attributes) == 0 { + return nil, fmt.Errorf("event has no attributes") + } + + var results []ConsensusEventInfo + + // Process all attributes + for _, attr := range event.Attributes { + info := ConsensusEventInfo{ + EventType: event.Type, + EventKind: string(attr.Key), + } + + // Base64 decode the attribute value + cborData, err := base64.StdEncoding.DecodeString(string(attr.Value)) + if err != nil { + info.BodyError = fmt.Sprintf("base64 decode failed: %v", err) + results = append(results, info) + continue + } + + // Populate raw body fields + info.BodyHex = formatRawValue(cborData, TruncateLongLen) + info.BodySize = len(cborData) + + // Decode body based on event kind + switch info.EventKind { + case "transfer": + var transfer cborConsensusTransferEvent + if err := cbor.Unmarshal(cborData, &transfer); err == nil { + info.Body = transfer + } else { + info.BodyError = fmt.Sprintf("cbor unmarshal failed: %v", err) + } + + case "burn": + var burn cborConsensusBurnEvent + if err := cbor.Unmarshal(cborData, &burn); err == nil { + info.Body = burn + } else { + info.BodyError = fmt.Sprintf("cbor unmarshal failed: %v", err) + } + + case "add_escrow": + var addEscrow cborConsensusAddEscrowEvent + if err := cbor.Unmarshal(cborData, &addEscrow); err == nil { + info.Body = addEscrow + } else { + info.BodyError = fmt.Sprintf("cbor unmarshal failed: %v", err) + } + + case "reclaim_escrow": + var reclaimEscrow cborConsensusReclaimEscrowEvent + if err := cbor.Unmarshal(cborData, &reclaimEscrow); err == nil { + info.Body = reclaimEscrow + } else { + info.BodyError = fmt.Sprintf("cbor unmarshal failed: %v", err) + } + } + + results = append(results, info) + } + + return results, nil +} diff --git a/badgerdb-analyzer/decode_evm.go b/badgerdb-analyzer/decode_evm.go new file mode 100644 index 0000000..e4145ff --- /dev/null +++ b/badgerdb-analyzer/decode_evm.go @@ -0,0 +1,436 @@ + +package main + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + + "github.com/fxamacker/cbor/v2" +) + +// decodeEVMData decodes EVM module storage data. +// See: _oasis-sdk/runtime-sdk/modules/evm/src/state.rs +func decodeEVMData(module string, key []byte, value []byte) *EVMDataInfo { + info := &EVMDataInfo{} + + // Module format: "evm:subtype" where subtype is extracted from key prefix + // The full key after module name starts with the storage type prefix + + // Find where module name ends in the key + moduleNameEnd := 0 + for i, b := range key { + if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' { + moduleNameEnd = i + 1 + } else { + break + } + } + + if moduleNameEnd >= len(key) { + info.RawError = "no data after module name" + return info + } + + subKey := key[moduleNameEnd:] + if len(subKey) < 1 { + info.RawError = "insufficient subkey data" + return info + } + + storagePrefix := subKey[0] + data := subKey[1:] + + switch storagePrefix { + case 0x01: // CODES: evm + 0x01 + H160 (address) + info.StorageType = "code" + if len(data) >= 20 { + info.Address = "0x" + hex.EncodeToString(data[:20]) + data = data[20:] + } + // Value is contract bytecode + info.CodeSize = len(value) + if len(value) > 0 { + info.CodeHex = formatRawValue(value, TruncateLongLen) + } + + case 0x02: // STORAGES: evm + 0x02 + H160 (address) + H256 (slot) + info.StorageType = "storage" + if len(data) >= 20 { + info.Address = "0x" + hex.EncodeToString(data[:20]) + data = data[20:] + if len(data) >= 32 { + info.StorageSlot = formatRawValue(data[:32], TruncateHashLen) + } + } + // Value is H256 storage value + if len(value) == 32 { + info.StorageValue = formatRawValue(value, TruncateLongLen) + } + + case 0x03: // BLOCK_HASHES: evm + 0x03 + RuntimeHeight (uint64 BE) + info.StorageType = "block_hash" + if len(data) >= 8 { + info.RuntimeHeight = binary.BigEndian.Uint64(data[:8]) + } + // Value is H256 block hash + if len(value) == 32 { + info.BlockHash = formatRawValue(value, TruncateHashLen) + } + + case 0x04: // CONFIDENTIAL_STORAGES: evm + 0x04 + H160 (address) + H256 (slot) + info.StorageType = "confidential_storage" + if len(data) >= 20 { + info.Address = "0x" + hex.EncodeToString(data[:20]) + data = data[20:] + if len(data) >= 32 { + info.StorageSlot = formatRawValue(data[:32], TruncateHashLen) + } + } + // Value is encrypted - we can only show size + if len(value) > 0 { + info.StorageValue = fmt.Sprintf("", len(value)) + } + + default: + info.RawError = fmt.Sprintf("unknown EVM storage type: 0x%02x", storagePrefix) + return info + } + + return info +} + +// decodeEVMEvent decodes an EVM Log event from CBOR-encoded value. +// See: _oasis-sdk/runtime-sdk/modules/evm/src/lib.rs:253-263 +// Returns (info, error) where error is a parsing error, not an execution error. +func decodeEVMEvent(value []byte) *EVMEventInfo { + info := &EVMEventInfo{} + + // Value is CBOR: [event_code, {address, topics, data}] + var eventWrapper []interface{} + if err := cbor.Unmarshal(value, &eventWrapper); err != nil { + info.RawError = fmt.Sprintf("cbor unmarshal failed: %v", err) + return info + } + if len(eventWrapper) < 2 { + info.RawError = "invalid format: insufficient fields" + return info + } + + eventData, ok := eventWrapper[1].(map[interface{}]interface{}) + if !ok { + info.RawError = "invalid format: element[1] not a map" + return info + } + + // Extract address (H160) + if addrBytes, ok := eventData["address"].([]byte); ok && len(addrBytes) == 20 { + info.Address = "0x" + hex.EncodeToString(addrBytes) + } else { + info.RawError = "invalid address" + return info + } + + // Extract topics (Vec) + if topicsArray, ok := eventData["topics"].([]interface{}); ok { + info.TopicCount = len(topicsArray) + for i, topic := range topicsArray { + if topicBytes, ok := topic.([]byte); ok && len(topicBytes) == 32 { + topicHex := "0x" + hex.EncodeToString(topicBytes) + info.Topics = append(info.Topics, topicHex) + if i == 0 { + info.EventHash = topicHex + if sig, found := EVMEventSignatures[topicHex]; found { + info.EventSignature = sig + } + } + } + } + } + + // Extract data + if dataBytes, ok := eventData["data"].([]byte); ok { + info.DataSize = len(dataBytes) + info.DataHex = formatRawValue(dataBytes, TruncateLongLen) + } + + return info +} + +// decodeEVMTxInput decodes EVM transaction input artifacts. +func decodeEVMTxInput(key []byte, value []byte) *EVMTxInputInfo { + info := &EVMTxInputInfo{} + + if len(key) >= 33 { + info.TxHash = formatRawValue(key[1:33], TruncateHashLen) + } + + var ia cborRuntimeInputArtifacts + if err := cbor.Unmarshal(value, &ia); err != nil { + info.RawError = fmt.Sprintf("failed to unmarshal RuntimeInputArtifacts: %v", err) + return info + } + info.BatchOrder = ia.BatchOrder + + // Attempt 0: Variant wrapper format [version, [variant_tag, data]] + var wrapperArray []interface{} + if err := cbor.Unmarshal(ia.Input, &wrapperArray); err == nil && len(wrapperArray) >= 2 { + if nestedArray, ok := wrapperArray[1].([]interface{}); ok && len(nestedArray) >= 2 { + // Extract the actual transaction data (second element of nested array) + if txDataBytes, err := cbor.Marshal(nestedArray[1]); err == nil { + // Recursively decode the unwrapped transaction + ia.Input = txDataBytes + return decodeEVMTxInput(key, value) + } + } + } + + // Try decoding as array format first (old runtime version) + var callArray cborRuntimeCallArrayFormat + if err := cbor.Unmarshal(ia.Input, &callArray); err == nil { + info.Method = callArray.Method + // Decode EVM call methods that have body structure + if callArray.Method == "evm.Call" || callArray.Method == "evm.Create" || callArray.Method == "evm.SimulateCall" || callArray.Method == "evm.EstimateGas" { + isCreate := callArray.Method == "evm.Create" + info.EVMTx = decodeEVMTransactionFromCBOR(callArray.Body, isCreate) + } + return info + } + + // Try decoding as map format (newer runtime version) + var callMap cborRuntimeCallMapFormat + if err := cbor.Unmarshal(ia.Input, &callMap); err == nil { + info.Method = callMap.Method + // Decode EVM call methods that have body structure + if callMap.Method == "evm.Call" || callMap.Method == "evm.Create" || callMap.Method == "evm.SimulateCall" || callMap.Method == "evm.EstimateGas" { + isCreate := callMap.Method == "evm.Create" + info.EVMTx = decodeEVMTransactionFromCBOR(callMap.Body, isCreate) + } + return info + } + + // Fallback: try generic array decode (for compatibility with other runtime versions) + var callArrayGeneric []interface{} + if err := cbor.Unmarshal(ia.Input, &callArrayGeneric); err == nil { + // Array format: [format, method, body, ...] or [format, [method, body, ...]] + if len(callArrayGeneric) < 2 { + info.RawError = "array format: insufficient fields" + return info + } + + // Extract method and body - handle both flat and nested array formats + var method string + var bodyBytes []byte + + if methodVal, ok := callArrayGeneric[1].(string); ok { + // Flat format: [format, method, body, ...] + method = methodVal + if len(callArrayGeneric) >= 3 { + bodyBytes, _ = callArrayGeneric[2].([]byte) + } + } else if nestedArray, ok := callArrayGeneric[1].([]interface{}); ok { + // Nested format: [format, [method, body, ...]] or [format, [{method: "...", body: ...}]] + if len(nestedArray) < 1 { + info.RawError = "nested array format: empty" + return info + } + if methodVal, ok := nestedArray[0].(string); ok { + // Nested array format: [format, [method, body, ...]] + method = methodVal + if len(nestedArray) >= 2 { + bodyBytes, _ = nestedArray[1].([]byte) + } + } else if nestedMap, ok := nestedArray[0].(map[interface{}]interface{}); ok { + // Nested map format: [format, [{method: "...", body: ...}]] + if methodVal, ok := nestedMap["method"].(string); ok { + method = methodVal + if bodyVal, ok := nestedMap["body"].([]byte); ok { + bodyBytes = bodyVal + } + } else { + // Fallback A: Re-marshal map and try decoding as cborRuntimeCallMapFormat + if marshaled, err := cbor.Marshal(nestedMap); err == nil { + var callMap cborRuntimeCallMapFormat + if err := cbor.Unmarshal(marshaled, &callMap); err == nil { + method = callMap.Method + bodyBytes = callMap.Body + } else { + info.RawError = "nested map format: method missing" + return info + } + } else { + info.RawError = "nested map format: method missing" + return info + } + } + } else if nestedBytes, ok := nestedArray[0].([]byte); ok { + // Fallback B: Nested bytes - unwrap and decode + var callMap cborRuntimeCallMapFormat + if err := cbor.Unmarshal(nestedBytes, &callMap); err == nil { + method = callMap.Method + bodyBytes = callMap.Body + } else { + // Try array format as second attempt + var callArray cborRuntimeCallArrayFormat + if err := cbor.Unmarshal(nestedBytes, &callArray); err == nil { + method = callArray.Method + bodyBytes = callArray.Body + } else { + info.RawError = "nested array format: unable to decode nested bytes" + return info + } + } + } else { + info.RawError = "nested array format: invalid element type" + return info + } + } else { + info.RawError = "array format: invalid element[1] type" + return info + } + + info.Method = method + // Decode EVM call methods that have body structure + if bodyBytes != nil && (method == "evm.Call" || method == "evm.Create" || method == "evm.SimulateCall" || method == "evm.EstimateGas") { + info.EVMTx = decodeEVMTransactionFromCBOR(bodyBytes, method == "evm.Create") + } + return info + } + + // Final fallback: try legacy map[interface{}]interface{} decode + var call map[interface{}]interface{} + if err := cbor.Unmarshal(ia.Input, &call); err == nil { + if methodVal, ok := call["method"].(string); ok { + info.Method = methodVal + // Decode EVM call methods that have body structure + if methodVal == "evm.Call" || methodVal == "evm.Create" || methodVal == "evm.SimulateCall" || methodVal == "evm.EstimateGas" { + isCreate := methodVal == "evm.Create" + // Extract body bytes and decode + if bodyBytes, ok := call["body"].([]byte); ok { + info.EVMTx = decodeEVMTransactionFromCBOR(bodyBytes, isCreate) + } + } + return info + } + info.RawError = "map format: method missing" + return info + } + + // All decode attempts failed + info.RawError = "all formats failed" + return info +} + +// decodeEVMTransactionFromCBOR decodes EVM transaction from raw CBOR bytes. +// See: _oasis-sdk/runtime-sdk/modules/evm/src/types.rs for transaction structure +func decodeEVMTransactionFromCBOR(bodyBytes []byte, isCreate bool) *EVMTransactionInfo { + info := &EVMTransactionInfo{} + + if isCreate { + info.Type = "create" + } else { + info.Type = "call" + } + + // Try to unmarshal as map + var bodyMap map[interface{}]interface{} + if err := cbor.Unmarshal(bodyBytes, &bodyMap); err != nil { + info.RawError = fmt.Sprintf("cbor unmarshal failed: %v", err) + return info + } + + // Extract from address (20 bytes) + if fromBytes, ok := bodyMap["from"].([]byte); ok && len(fromBytes) == 20 { + info.From = "0x" + hex.EncodeToString(fromBytes) + } + + // Extract to address (20 bytes, calls only) + if !isCreate { + if toBytes, ok := bodyMap["address"].([]byte); ok && len(toBytes) == 20 { + info.To = "0x" + hex.EncodeToString(toBytes) + } else if toBytes, ok := bodyMap["to"].([]byte); ok && len(toBytes) == 20 { + info.To = "0x" + hex.EncodeToString(toBytes) + } + } + + // Extract value (U256) + if valueBytes, ok := bodyMap["value"].([]byte); ok { + info.Value = formatU256(valueBytes) + } + + // Extract gas_limit (u64) + if gasLimit, ok := bodyMap["gas_limit"].(uint64); ok { + info.GasLimit = gasLimit + } else if gasLimit, ok := bodyMap["gas"].(uint64); ok { + info.GasLimit = gasLimit + } + + // Extract gas_price (U256) + if gasPriceBytes, ok := bodyMap["gas_price"].([]byte); ok { + info.GasPrice = formatU256(gasPriceBytes) + } + + // Extract nonce (u64) + if nonce, ok := bodyMap["nonce"].(uint64); ok { + info.Nonce = nonce + } + + // Extract data/init_code + dataKey := "data" + if isCreate { + dataKey = "init_code" + } + if dataBytes, ok := bodyMap[dataKey].([]byte); ok { + info.DataSize = len(dataBytes) + info.DataDump = formatRawValue(dataBytes, TruncateLongLen) + } + + return info +} + +// decodeEVMTxOutput decodes EVM transaction output artifacts. +// Execution errors (transaction failures) are stored in info.ErrorExecution field. +func decodeEVMTxOutput(value []byte) *EVMTxOutputInfo { + info := &EVMTxOutputInfo{} + + var oa cborRuntimeOutputArtifacts + if err := cbor.Unmarshal(value, &oa); err != nil { + info.RawError = fmt.Sprintf("failed to unmarshal RuntimeOutputArtifacts: %v", err) + return info + } + + if len(oa.Output) == 0 { + info.SuccessExecution = true + return info + } + + // Try to decode as CBOR CallResult + var result map[interface{}]interface{} + if err := cbor.Unmarshal(oa.Output, &result); err != nil { + // Raw bytes - success + info.SuccessExecution = true + info.ResultSize = len(oa.Output) + info.ResultDump = formatRawValue(oa.Output, TruncateLongLen) + return info + } + + // Check success/failure + if okVal, exists := result["ok"]; exists { + info.SuccessExecution = true + if okBytes, ok := okVal.([]byte); ok { + info.ResultSize = len(okBytes) + info.ResultDump = formatRawValue(okBytes, TruncateLongLen) + } + } else if failVal, exists := result["fail"]; exists { + info.SuccessExecution = false + if failMap, ok := failVal.(map[interface{}]interface{}); ok { + if msgVal, ok := failMap["message"].(string); ok { + info.ErrorExecution = msgVal // Execution error, keep in struct + } else { + info.ErrorExecution = fmt.Sprintf("%v", failMap) + } + } + } + + return info +} diff --git a/badgerdb-analyzer/decode_runtime.go b/badgerdb-analyzer/decode_runtime.go new file mode 100644 index 0000000..52cedaf --- /dev/null +++ b/badgerdb-analyzer/decode_runtime.go @@ -0,0 +1,589 @@ +package main + +import ( + "encoding/binary" + "fmt" + "math/big" + "strings" + "time" + + "github.com/fxamacker/cbor/v2" +) + +// decodeRuntimeMkvsKey parses runtime-mkvs key and returns structured info. +// Keys use keyformat encoding: [type_byte][data...] +// See: _oasis-core/go/storage/mkvs/db/badger/badger.go:31-66 +func decodeRuntimeMkvsKey(key []byte) *RuntimeMkvsKeyInfo { + info := &RuntimeMkvsKeyInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: len(key), + } + + if len(key) < 1 { + info.KeyType = "unknown" + return info + } + + // First byte is the key type prefix + prefixByte := key[0] + data := key[1:] + + switch prefixByte { + case 0x00: + info.KeyType = "node" + info.Hash = formatRawValue(data, TruncateHashLen) + case 0x01: + info.KeyType = "write_log" + if len(data) >= 8 { + info.RuntimeHeight = binary.BigEndian.Uint64(data[0:8]) + } + case 0x02: + info.KeyType = "roots_metadata" + if len(data) >= 8 { + info.RuntimeHeight = binary.BigEndian.Uint64(data[0:8]) + } + case 0x03: + info.KeyType = "root_updated_nodes" + if len(data) >= 8 { + info.RuntimeHeight = binary.BigEndian.Uint64(data[0:8]) + } + case 0x04: + info.KeyType = "metadata" + default: + info.KeyType = fmt.Sprintf("unknown_%02x", prefixByte) + } + + return info +} + +// decodeRuntimeMkvsValue decodes runtime MKVS value and returns structured info. +// See: _oasis-core/go/storage/mkvs/node/node.go:26-32 (prefixes), 294-309 (InternalNode), 531-537 (LeafNode) +func decodeRuntimeMkvsValue(keyType string, value []byte) *RuntimeMkvsValueInfo { + info := &RuntimeMkvsValueInfo{ + RawDump: formatRawValue(value, TruncateLongLen), + RawSize: len(value), + } + + if len(value) == 0 { + info.NodeType = "empty" + return info + } + + if keyType != "node" { + // Decode write_log values + if keyType == "write_log" { + var writeLog WriteLog + if err := cbor.Unmarshal(value, &writeLog); err != nil { + info.RawError = err.Error() + } else { + info.WriteLog = writeLog + } + info.NodeType = "write_log" + return info + } + + info.NodeType = "non_node" + return info + } + + // Parse MKVS node: 0x00=leaf, 0x01=internal, 0x02=nil + switch value[0] { + case 0x00: // LeafNode: [2-byte keySize LE][key][4-byte valueSize LE][value] + info.NodeType = "leaf" + data := value[1:] + + // Try pre-v21.1.0 format (skip 8-byte Version field) + if len(data) >= 10 { + testKeySize := int(binary.LittleEndian.Uint16(data[8:10])) + if testKeySize > 0 && testKeySize <= 10000 && len(data) >= 10+testKeySize+4 { + data = data[8:] + } + } + + if len(data) < 2 { + info.RawError = "key length missing" + return info + } + + keySize := int(binary.LittleEndian.Uint16(data[0:2])) + data = data[2:] + + if len(data) < keySize { + info.RawError = fmt.Sprintf("key truncated (expected %s, got %s)", + formatApproxSize(keySize), formatApproxSize(len(data))) + return info + } + + key := data[:keySize] + data = data[keySize:] + + // Extract module name from key + module := decodeRuntimeModuleKey(key) + + leaf := &RuntimeMkvsLeafInfo{ + Module: module, + KeySize: keySize, + KeyDump: formatRawValue(key, TruncateLongLen), + } + + if len(data) < 4 { + info.RawError = "value length missing" + info.Leaf = leaf + return info + } + + valueSize := int(binary.LittleEndian.Uint32(data[0:4])) + data = data[4:] + + if len(data) < valueSize { + info.RawError = fmt.Sprintf("value truncated (expected %s, got %s)", + formatApproxSize(valueSize), formatApproxSize(len(data))) + info.Leaf = leaf + return info + } + + leafValue := data[:valueSize] + leaf.Value = decodeRuntimeLeafValue(module, key, leafValue) + info.Leaf = leaf + return info + + case 0x01: // InternalNode: [2-byte labelBits LE][label][leaf/nil marker][hashes] + info.NodeType = "internal" + data := value[1:] + + // Try pre-v21.1.0 format (skip 8-byte Version field) + if len(data) >= 10 { + testLabelBits := binary.LittleEndian.Uint16(data[8:10]) + if testLabelBits <= 2048 && len(data) >= 10+(int(testLabelBits)+7)/8+1 { + data = data[8:] + } + } + + if len(data) < 2 { + info.RawError = "label bits missing" + return info + } + + labelBits := binary.LittleEndian.Uint16(data[0:2]) + data = data[2:] + + labelBytes := (int(labelBits) + 7) / 8 + if len(data) < labelBytes+1 { + info.RawError = fmt.Sprintf("label truncated (expected %s, got %s)", + formatApproxSize(labelBytes+1), formatApproxSize(len(data))) + info.Internal = &RuntimeMkvsInternalInfo{LabelBits: labelBits} + return info + } + + data = data[labelBytes:] // skip label + + internal := &RuntimeMkvsInternalInfo{LabelBits: labelBits} + + // Check for embedded leaf node or nil marker + if len(data) < 1 { + info.RawError = "missing leaf/nil marker" + info.Internal = internal + return info + } + + if data[0] == 0x02 { // NilNode marker - no embedded leaf + internal.HasLeaf = false + data = data[1:] // skip nil marker + } else if data[0] == 0x00 { // LeafNode prefix - embedded leaf present + internal.HasLeaf = true + // Skip embedded leaf: prefix(1) + keyLen(2) + key + valueLen(4) + value + data = data[1:] // skip prefix + if len(data) < 2 { + info.RawError = "embedded leaf key length missing" + info.Internal = internal + return info + } + keyLen := binary.LittleEndian.Uint16(data[0:2]) + data = data[2:] + if len(data) < int(keyLen)+4 { + info.RawError = fmt.Sprintf("embedded leaf truncated (expected %s, got %s)", + formatApproxSize(int(keyLen)+4), formatApproxSize(len(data))) + info.Internal = internal + return info + } + data = data[keyLen:] // skip key + valueLen := binary.LittleEndian.Uint32(data[0:4]) + data = data[4:] + if len(data) < int(valueLen) { + info.RawError = fmt.Sprintf("embedded leaf value truncated (expected %s, got %s)", + formatApproxSize(int(valueLen)), formatApproxSize(len(data))) + info.Internal = internal + return info + } + data = data[valueLen:] // skip value + } else { + info.RawError = fmt.Sprintf("unexpected marker 0x%02x", data[0]) + info.Internal = internal + return info + } + + // Read left and right hashes + if len(data) >= 32 { + internal.LeftHash = formatRawValue(data[:32], TruncateHashLen) + data = data[32:] + } + if len(data) >= 32 { + internal.RightHash = formatRawValue(data[:32], TruncateHashLen) + } + + info.Internal = internal + return info + + case 0x02: // NilNode + info.NodeType = "nil" + return info + + default: + info.NodeType = "unknown" + info.RawError = fmt.Sprintf("unknown prefix 0x%02x", value[0]) + return info + } +} + +// decodeRuntimeHistoryKey parses runtime-history key and returns structured info. +// See: _oasis-core/go/runtime/history/db.go:19-31 +// Key formats: +// - 0x01: metadata key (1 byte) +// - 0x02 + uint64: block key (9 bytes) - round number in big-endian +// - 0x03 + uint64: round_results key (9 bytes) - round number in big-endian +func decodeRuntimeHistoryKey(key []byte) *RuntimeHistoryKeyInfo { + info := &RuntimeHistoryKeyInfo{ + KeyDump: formatRawValue(key, TruncateLongLen), + KeySize: len(key), + } + + if len(key) == 0 { + info.KeyType = "unknown" + return info + } + + switch key[0] { + case 0x01: + info.KeyType = "metadata" + if len(key) > 1 { + info.ExtraData = formatRawValue(key[1:], TruncateLongLen) + } + + case 0x02: + info.KeyType = "block" + if len(key) == 9 { + info.RuntimeHeight = binary.BigEndian.Uint64(key[1:9]) + } + + case 0x03: + info.KeyType = "round_results" + if len(key) == 9 { + info.RuntimeHeight = binary.BigEndian.Uint64(key[1:9]) + } + + default: + info.KeyType = "unknown" + } + + return info +} + +// decodeRuntimeHistoryValue decodes CBOR-encoded runtime history value and returns structured info. +// See: _oasis-core/go/runtime/history/db.go:34-44 (metadata) +// See: _oasis-core/go/roothash/api/api.go:402-409 (AnnotatedBlock) +// See: _oasis-core/go/roothash/api/results.go:5-17 (RoundResults) +func decodeRuntimeHistoryValue(keyType string, value []byte) *RuntimeHistoryValueInfo { + info := &RuntimeHistoryValueInfo{ + RawDump: formatRawValue(value, TruncateLongLen), + RawSize: len(value), + } + + if len(value) == 0 { + return info + } + + switch keyType { + case "metadata": + var meta cborRuntimeHistoryMetadata + if err := cbor.Unmarshal(value, &meta); err != nil { + info.RawError = err.Error() + return info + } + info.Metadata = &RuntimeHistoryMetadataInfo{ + Version: meta.Version, + RuntimeID: formatRawValue(meta.RuntimeID, TruncateLongLen), + LastRuntimeHeight: meta.LastRound, + LastConsensusHeight: meta.LastConsensusHeight, + } + + case "block": + var block cborRuntimeHistoryAnnotatedBlock + if err := cbor.Unmarshal(value, &block); err != nil { + info.RawError = err.Error() + return info + } + blockInfo := &RuntimeHistoryBlockInfo{ + ConsensusHeight: block.Height, + } + if block.Block == nil { + blockInfo.BlockNil = true + } else { + h := block.Block.Header + blockInfo.RuntimeHeight = h.Round + blockInfo.Timestamp = time.Unix(int64(h.Timestamp), 0).UTC().Format(time.RFC3339) + blockInfo.HeaderType = headerTypeName(h.HeaderType) + blockInfo.StateRoot = formatRawValue(h.StateRoot, TruncateHashLen) + } + info.Block = blockInfo + + case "round_results": + var results cborRuntimeHistoryRoundResults + if err := cbor.Unmarshal(value, &results); err != nil { + info.RawError = err.Error() + return info + } + info.RoundResults = &RuntimeHistoryRoundResultsInfo{ + MessageCount: len(results.Messages), + GoodComputeEntities: len(results.GoodComputeEntities), + BadComputeEntities: len(results.BadComputeEntities), + } + } + + return info +} + +// headerTypeName converts header type byte to string +func headerTypeName(headerType uint8) string { + switch headerType { + case 0: + return "Invalid" + case 1: + return "Normal" + case 2: + return "RoundFailed" + case 3: + return "EpochTransition" + case 4: + return "Suspended" + default: + return fmt.Sprintf("Unknown_%d", headerType) + } +} + +// decodeRuntimeLeafValue decodes MKVS leaf value and returns structured info with valueType classification. +// See: _oasis-core/go/runtime/transaction/transaction.go:129-150 (artifacts) +func decodeRuntimeLeafValue(module string, key []byte, value []byte) *RuntimeLeafValueInfo { + info := &RuntimeLeafValueInfo{ + ValueDump: formatRawValue(value, TruncateLongLen), + ValueSize: len(value), + ValueType: "unknown", // default + } + + // Extract module name and subprefix for format lookup + modName := module + if idx := strings.Index(module, ":"); idx > 0 { + modName = module[:idx] + } + var subPrefix byte + if len(modName) < len(key) { + subPrefix = key[len(modName)] + } + + // Check lookup table for deterministic format handling + format, exists := GetRuntimeModuleStateFormat(modName, subPrefix) + if exists { + info.ValueType = format.Description + switch format.Format { + case "binary": + info.CBOR = fmt.Sprintf("%s (%d bytes)", format.Type, len(value)) + return info + case "wasm": + info.CBOR = fmt.Sprintf("wasm bytecode (%d bytes)", len(value)) + return info + // case "cbor": fall through to existing CBOR decode logic below + } + } + + // Check for EVM module data + if len(module) >= 3 && module[:3] == "evm" { + evmInfo := decodeEVMData(module, key, value) + if evmInfo != nil { + info.EVM = evmInfo + // No error return from decodeEVMData (returns nil on failure) + // Set value_type based on EVM storage type + switch evmInfo.StorageType { + case "code": + info.ValueType = "evm_code" + case "storage", "confidential_storage": + info.ValueType = "evm_storage" + case "block_hash": + info.ValueType = "evm_block_hash" + default: + info.ValueType = "evm_unknown" + } + return info + } + } + + // Check for IO transaction artifacts + if len(module) > 5 && module[:5] == "io_tx" { + // Determine artifact kind from key + if len(key) >= 34 { + kind := key[33] + if kind == 1 { + // Input artifact + info.EVMTxInput = decodeEVMTxInput(key, value) + info.ValueType = "io_input" + return info + } else if kind == 2 { + // Output artifact + info.EVMTxOutput = decodeEVMTxOutput(value) + info.ValueType = "io_output" + return info + } + } + } + + // Check for IO event tags + if len(module) > 8 && module[:8] == "io_event" { + // Special handling for EVM events + if len(module) >= 12 && module[9:12] == "evm" { + info.EVMEvent = decodeEVMEvent(value) + info.ValueType = "evm_event" + return info + } + // Special handling for consensus_accounts events + if len(module) >= 25 && module[9:25] == "consensus_accounts" { + if evt := decodeRuntimeConsensusEvent(value); evt != nil { + info.RuntimeConsensusEvent = evt + info.ValueType = "runtime_consensus_accounts_event" + return info + } + } + + // Generic event decoding for non-EVM events + var decoded interface{} + if err := cbor.Unmarshal(value, &decoded); err == nil { + // Use detailed formatter to expand arrays and maps + info.CBOR = formatCBORDetailed(decoded) + info.ValueType = "io_event" + } else { + info.ValueError = err.Error() + } + return info + } + + // Detect raw binary data based on module + switch module { + case "contracts:code": + // WASM bytecode - starts with magic bytes 0xFF060000734e615070 ("sNaPpY" Snappy compression) + if len(value) > 10 && value[0] == 0xFF { + info.ValueType = "wasm_bytecode" + info.CBOR = fmt.Sprintf("snappy_compressed_wasm (%d bytes)", len(value)) + return info + } + case "contracts:instance_state": + // Check if it's raw hash/encrypted data (typically 32-33 bytes, non-CBOR) + if len(value) >= 32 && len(value) <= 128 { + // Try CBOR first, but if it fails with "extraneous data" it's likely raw binary + var decoded interface{} + if err := cbor.Unmarshal(value, &decoded); err != nil { + // Check for characteristic CBOR errors indicating raw binary + if strings.Contains(err.Error(), "extraneous data") || + strings.Contains(err.Error(), "unexpected \"break\"") || + strings.Contains(err.Error(), "exceeded max number of elements") { + info.ValueType = "raw_binary" + info.CBOR = fmt.Sprintf("raw_state_data (%d bytes)", len(value)) + return info + } + info.ValueError = err.Error() + return info + } + // CBOR decode succeeded + info.CBOR = formatCBOR(decoded, len(value)) + info.ValueType = "cbor" + return info + } + } + + // Try CBOR decode for regular state keys + var decoded interface{} + if err := cbor.Unmarshal(value, &decoded); err == nil { + info.CBOR = formatCBOR(decoded, len(value)) + info.ValueType = "cbor" + } else { + info.ValueError = err.Error() + } + + return info +} + +// convertRuntimeConsensusError converts CBOR error to output error. +func convertRuntimeConsensusError(err *cborRuntimeConsensusError) *RuntimeConsensusEventError { + if err == nil { + return nil + } + return &RuntimeConsensusEventError{Module: err.Module, Code: err.Code} +} + +// decodeRuntimeConsensusEvent decodes consensus_accounts events. +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/mod.rs:106-157 +func decodeRuntimeConsensusEvent(value []byte) *RuntimeConsensusEventInfo { + var eventArray []interface{} + if err := cbor.Unmarshal(value, &eventArray); err != nil || len(eventArray) < 2 { + return nil + } + eventCode, ok := eventArray[0].(uint64) + if !ok { + return nil + } + + info := &RuntimeConsensusEventInfo{} + switch eventCode { + case 1, 2, 3: // Deposit, Withdraw, Delegate + var event cborRuntimeConsensusTransferEvent + if err := cbor.Unmarshal(value, &event); err != nil { + return nil + } + if eventCode == 1 { + info.EventType = "deposit" + } else if eventCode == 2 { + info.EventType = "withdraw" + } else { + info.EventType = "delegate" + } + info.From = event.From.String() + info.Nonce = event.Nonce + info.To = event.To.String() + info.Amount = event.Amount.String() + info.Error = convertRuntimeConsensusError(event.Error) + + case 4: // UndelegateStart + var event cborRuntimeConsensusUndelegateStartEvent + if err := cbor.Unmarshal(value, &event); err != nil { + return nil + } + info.EventType = "undelegate_start" + info.From = event.From.String() + info.Nonce = event.Nonce + info.To = event.To.String() + info.Shares = new(big.Int).SetBytes(event.Shares).String() + info.DebondEndTime = event.DebondEndTime + info.Error = convertRuntimeConsensusError(event.Error) + + case 5: // UndelegateDone + var event cborRuntimeConsensusUndelegateDoneEvent + if err := cbor.Unmarshal(value, &event); err != nil { + return nil + } + info.EventType = "undelegate_done" + info.From = event.From.String() + info.To = event.To.String() + info.Shares = new(big.Int).SetBytes(event.Shares).String() + info.Amount = event.Amount.String() + + default: + return nil + } + return info +} diff --git a/badgerdb-analyzer/evm_signatures.go b/badgerdb-analyzer/evm_signatures.go new file mode 100644 index 0000000..a819753 --- /dev/null +++ b/badgerdb-analyzer/evm_signatures.go @@ -0,0 +1,63 @@ +package main + +// EVMEventSignatures maps Keccak256 event signature hashes to human-readable signatures. +// The hash is already computed and stored in topics[0] by the EVM - we just look it up. +// Hashes are lowercase hex strings without "0x" prefix. +// +// This is a curated list focused on protocols deployed on Oasis Network (Sapphire and Emerald). +// For comprehensive coverage, consider integrating with external signature databases like +// 4byte.directory or Etherscan. Users can extend this map with project-specific signatures. +var EVMEventSignatures = map[string]string{ + // ERC-20 Token Standard (Used by all tokens on Oasis) + "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": "Transfer(address,address,uint256)", + "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": "Approval(address,address,uint256)", + + // ERC-721 NFT Standard + "17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31": "ApprovalForAll(address,address,bool)", + + // ERC-1155 Multi-Token Standard + "c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62": "TransferSingle(address,address,address,uint256,uint256)", + "4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb": "TransferBatch(address,address,address,uint256[],uint256[])", + + // Ownable / Access Control (Common pattern in Oasis contracts) + "8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0": "OwnershipTransferred(address,address)", + "2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d": "RoleGranted(bytes32,address,address)", + "f6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b": "RoleRevoked(bytes32,address,address)", + + // Pausable (Common security pattern) + "62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258": "Paused(address)", + "5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa": "Unpaused(address)", + + // Wrapped ETH/ROSE (wROSE: 0x8Bc2B030b299964eEfb5e1e0b36991352E56D2D3) + "e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c": "Deposit(address,uint256)", + "7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65": "Withdrawal(address,uint256)", + + // Uniswap V2 / DEX Events (YuzuSwap, ValleySwap, LilacSwap on Emerald) + "1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1": "Sync(uint112,uint112)", + "d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822": "Swap(address,uint256,uint256,uint256,uint256,address)", + "0d3648bd0f6ba80134a33ba9275ac585d9d315f0ad8355cddefde31afa28d0e9": "PairCreated(address,address,address,uint256)", + "4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f": "Mint(address,uint256,uint256)", + "dccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496": "Burn(address,uint256,uint256,address)", + + // Proxy Patterns (EIP-1967) - Common upgrade pattern + "bc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b": "Upgraded(address)", + "7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f": "AdminChanged(address,address)", + "101b8081ff3b56bbf45deb824d86a3b0fd38b7e3dd42421105cf8abe9106db0b": "BeaconUpgraded(address)", + + // Bridge Events (Celer MessageBus: 0x9Bb46D5100d2Db4608112026951c9C965b233f4D, Wormhole) + "1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05": "TokensBridged(address,address,uint256,uint256,uint256)", + + // Oasis Network Specific Notes: + // - Sapphire and Emerald ParaTimes are EVM-compatible and use standard EVM events + // - wROSE (Wrapped ROSE) uses standard WETH9 events listed above + // - YuzuSwap: First DEX on Emerald (Uniswap V2 fork) - 0x250d48C5E78f1E85F7AB07FEC61E93ba703aE668 + // - ValleySwap: DEX on Emerald - 0x7C0b0a525fc6A2caDf7AE37198119025C6feA28a + // - LilacSwap: DEX on Emerald - 0x5ca7e4301C9ac05B94e4c5b0C1812015f0A0be5f + // - Celer MessageBus: Cross-chain bridge - 0x9Bb46D5100d2Db4608112026951c9C965b233f4D + // - Band Oracle: Price feeds on Sapphire - 0xDA7a001b254CD22e46d3eAB04d937489c93174C3 + // - Sapphire DeFi: Midas, Neby, Thorn, Accumulated Finance, BitProtocol + // + // To add more signatures, examine contract ABIs on: + // - Sapphire: https://explorer.oasis.io/mainnet/sapphire + // - Emerald: https://explorer.emerald.oasis.dev +} diff --git a/badgerdb-analyzer/go.mod b/badgerdb-analyzer/go.mod new file mode 100644 index 0000000..ecb16d1 --- /dev/null +++ b/badgerdb-analyzer/go.mod @@ -0,0 +1,36 @@ +module github.com/oasisprotocol/badgerdb-analyzer + +go 1.23.0 + +require ( + // BadgerDB dependency filled by go_*.mod + github.com/fxamacker/cbor/v2 v2.9.0 + github.com/gogo/protobuf v1.3.2 +) + +require ( + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.34.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/badgerdb-analyzer/go.sum b/badgerdb-analyzer/go.sum new file mode 100644 index 0000000..982e58c --- /dev/null +++ b/badgerdb-analyzer/go.sum @@ -0,0 +1,225 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= +github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/badgerdb-analyzer/go_v2.mod b/badgerdb-analyzer/go_v2.mod new file mode 100644 index 0000000..a3c0be1 --- /dev/null +++ b/badgerdb-analyzer/go_v2.mod @@ -0,0 +1,39 @@ +module github.com/oasisprotocol/badgerdb-sampler + +go 1.23.0 + +require ( + github.com/dgraph-io/badger/v2 v2.2007.2 + github.com/dgraph-io/badger/v3 v3.2103.5 + github.com/dgraph-io/badger/v4 v4.8.0 + github.com/fxamacker/cbor/v2 v2.9.0 + github.com/gogo/protobuf v1.3.2 +) + +require ( + github.com/DataDog/zstd v1.4.1 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.34.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/badgerdb-analyzer/go_v2.sum b/badgerdb-analyzer/go_v2.sum new file mode 100644 index 0000000..9bfb170 --- /dev/null +++ b/badgerdb-analyzer/go_v2.sum @@ -0,0 +1,228 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= +github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= +github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/badgerdb-analyzer/go_v3.mod b/badgerdb-analyzer/go_v3.mod new file mode 100644 index 0000000..6d1777f --- /dev/null +++ b/badgerdb-analyzer/go_v3.mod @@ -0,0 +1,38 @@ +module github.com/oasisprotocol/badgerdb-sampler + +go 1.23.0 + +require ( + github.com/dgraph-io/badger/v2 v2.2007.4 + github.com/dgraph-io/badger/v3 v3.2103.5 + github.com/dgraph-io/badger/v4 v4.8.0 + github.com/fxamacker/cbor/v2 v2.9.0 + github.com/gogo/protobuf v1.3.2 +) + +require ( + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect + github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.34.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect +) diff --git a/badgerdb-analyzer/go_v3.sum b/badgerdb-analyzer/go_v3.sum new file mode 100644 index 0000000..982e58c --- /dev/null +++ b/badgerdb-analyzer/go_v3.sum @@ -0,0 +1,225 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= +github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/badgerdb-analyzer/go_v4.mod b/badgerdb-analyzer/go_v4.mod new file mode 100644 index 0000000..7b2272b --- /dev/null +++ b/badgerdb-analyzer/go_v4.mod @@ -0,0 +1,36 @@ +module github.com/oasisprotocol/badgerdb-sampler + +go 1.25.4 + +require ( + github.com/dgraph-io/badger/v2 v2.2007.4 + github.com/dgraph-io/badger/v3 v3.2103.5 + github.com/dgraph-io/badger/v4 v4.2.0 + github.com/fxamacker/cbor/v2 v2.9.0 + github.com/gogo/protobuf v1.3.2 +) + +require ( + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/stretchr/testify v1.8.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.5.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + +// Note: Dependencies will be filled in when v4 support is implemented +// Run 'go mod tidy' with -modfile=go.mod.v4 to populate diff --git a/badgerdb-analyzer/go_v4.sum b/badgerdb-analyzer/go_v4.sum new file mode 100644 index 0000000..49ec425 --- /dev/null +++ b/badgerdb-analyzer/go_v4.sum @@ -0,0 +1,210 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/badgerdb-analyzer/main.go b/badgerdb-analyzer/main.go new file mode 100644 index 0000000..df2308f --- /dev/null +++ b/badgerdb-analyzer/main.go @@ -0,0 +1,194 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" +) + +// BadgerVersion is set by build tags +var BadgerVersion string + +func main() { + // Parse arguments + dbType, dbPath, outputPath, err := parseArgs() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n\n", err) + printUsage() + os.Exit(3) // Invalid args + } + + // Calculate database directory size + dbSize, err := getDatabaseSize(dbPath) + if err != nil { + logProgress("Warning: Could not calculate database size: %v", err) + dbSize = 0 + } + + // Log initial info + logProgress("Database: %s (type: %s, size: %s)", dbPath, dbType, formatBytes(dbSize)) + logProgress("Opening database...") + + // Open database with multi-stage strategy (ReadOnly → Minimal RW → Local Mirror) + db, err := openDatabase(dbPath) + if err != nil { + logProgress("Failed to open database: %v", err) + os.Exit(2) // Open failed + } + defer db.Close() + + // Analyze database + logProgress("Starting analysis...") + result, err := analyzeDatabase(db, dbType, dbPath, BadgerVersion, dbSize) + if err != nil { + logProgress("Analysis failed: %v", err) + os.Exit(4) // Error + } + + // Output JSON + if err := outputJSON(result, outputPath); err != nil { + logProgress("Failed to output JSON: %v", err) + os.Exit(4) // Error + } + + // Determine exit code based on consistency checks + exitCode := 0 + if result.Consistency != nil { + for _, check := range result.Consistency { + if !check.Passed { + exitCode = 1 // Issues found + break + } + } + } + + logProgress("Analysis complete") + os.Exit(exitCode) +} + +// parseArgs parses command-line arguments +func parseArgs() (dbType, dbPath, outputPath string, err error) { + if len(os.Args) < 3 { + return "", "", "", fmt.Errorf("insufficient arguments") + } + + dbType = normalizeDBType(os.Args[1]) + dbPath = os.Args[2] + + // Validate database type + validTypes := []string{ + "consensus-blockstore", + "consensus-evidence", + "consensus-mkvs", + "consensus-state", + "runtime-mkvs", + "runtime-history", + } + valid := false + for _, validType := range validTypes { + if dbType == validType { + valid = true + break + } + } + if !valid { + return "", "", "", fmt.Errorf("invalid database type: %s (valid types: %s)", + dbType, strings.Join(validTypes, ", ")) + } + + // Optional output path + if len(os.Args) > 3 { + outputPath = os.Args[3] + } + + return dbType, dbPath, outputPath, nil +} + +// normalizeDBType removes version suffix from database type +func normalizeDBType(dbType string) string { + for _, suffix := range []string{"-v2", "-v3", "-v4"} { + if strings.HasSuffix(dbType, suffix) { + return strings.TrimSuffix(dbType, suffix) + } + } + return dbType +} + +// getDatabaseSize calculates the total size of all files in the database directory +func getDatabaseSize(dbPath string) (int64, error) { + var totalSize int64 + err := filepath.Walk(dbPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + totalSize += info.Size() + } + return nil + }) + return totalSize, err +} + +// outputJSON outputs the analysis result as JSON to stdout or file +func outputJSON(result *AnalysisResult, outputPath string) error { + output, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + if outputPath == "" { + // Output to stdout + fmt.Println(string(output)) + } else { + // Create directory if needed + dir := filepath.Dir(outputPath) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", dir, err) + } + + // Write to file + if err := os.WriteFile(outputPath, output, 0644); err != nil { + return fmt.Errorf("failed to write file %s: %w", outputPath, err) + } + + logProgress("Results saved to: %s", outputPath) + } + + return nil +} + +// logProgress logs a progress message to stderr +func logProgress(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", args...) +} + +// printUsage prints usage information +func printUsage() { + fmt.Fprintf(os.Stderr, "Usage: %s [output-json]\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "\nDatabase types:\n") + fmt.Fprintf(os.Stderr, " consensus-blockstore - Block metadata and commit info\n") + fmt.Fprintf(os.Stderr, " consensus-evidence - Byzantine validator evidence\n") + fmt.Fprintf(os.Stderr, " consensus-mkvs - Consensus state Merkle tree\n") + fmt.Fprintf(os.Stderr, " consensus-state - Tendermint consensus state\n") + fmt.Fprintf(os.Stderr, " runtime-mkvs - Runtime state Merkle tree\n") + fmt.Fprintf(os.Stderr, " runtime-history - Runtime block history\n") + fmt.Fprintf(os.Stderr, "\nExamples:\n") + fmt.Fprintf(os.Stderr, " %s consensus-blockstore /path/to/blockstore.badger.db\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s runtime-history /path/to/history.badger.db analysis.json\n", os.Args[0]) +} + +// formatBytes formats a byte count as a human-readable string +func formatBytes(bytes int64) string { + const unit = 1024 + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} diff --git a/badgerdb-analyzer/scripts/download-mainnet.sh b/badgerdb-analyzer/scripts/download-mainnet.sh new file mode 100755 index 0000000..7f0fc8a --- /dev/null +++ b/badgerdb-analyzer/scripts/download-mainnet.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Script to download Oasis node snapshots for Mainnet +set -euo pipefail + +curl -Lo mainnet/20201001-20201118/README.md https://snapshots.oasis.io/node/mainnet/20201001-20201118/README.md +curl -Lo mainnet/20201001-20201118/consensus.tar.zst.list https://snapshots.oasis.io/node/mainnet/20201001-20201118/consensus.tar.zst.list +curl -Lo mainnet/20201001-20201118/consensus.tar.zst https://snapshots.oasis.io/node/mainnet/20201001-20201118/consensus.tar.zst + +curl -Lo mainnet/20201118-20210428/README.md https://snapshots.oasis.io/node/mainnet/20201118-20210428/README.md +curl -Lo mainnet/20201118-20210428/consensus.tar.zst.list https://snapshots.oasis.io/node/mainnet/20201118-20210428/consensus.tar.zst.list +curl -Lo mainnet/20201118-20210428/consensus.tar.zst https://snapshots.oasis.io/node/mainnet/20201118-20210428/consensus.tar.zst + +curl -Lo mainnet/20210428-20220411/README.md https://snapshots.oasis.io/node/mainnet/20210428-20220411/README.md +curl -Lo mainnet/20210428-20220411/consensus.tar.zst.list https://snapshots.oasis.io/node/mainnet/20210428-20220411/consensus.tar.zst.list +curl -Lo mainnet/20210428-20220411/consensus.tar.zst https://snapshots.oasis.io/node/mainnet/20210428-20220411/consensus.tar.zst +curl -Lo mainnet/20210428-20220411/cipher.tar.zst.list https://snapshots.oasis.io/node/mainnet/20210428-20220411/cipher.tar.zst.list +curl -Lo mainnet/20210428-20220411/cipher.tar.zst https://snapshots.oasis.io/node/mainnet/20210428-20220411/cipher.tar.zst +curl -Lo mainnet/20210428-20220411/emerald.tar.zst.list https://snapshots.oasis.io/node/mainnet/20210428-20220411/emerald.tar.zst.list +curl -Lo mainnet/20210428-20220411/emerald.tar.zst https://snapshots.oasis.io/node/mainnet/20210428-20220411/emerald.tar.zst + +curl -Lo mainnet/20220411-20231129/README.md https://snapshots.oasis.io/node/mainnet/20220411-20231129/README.md +curl -Lo mainnet/20220411-20231129/consensus.tar.zst.list https://snapshots.oasis.io/node/mainnet/20220411-20231129/consensus.tar.zst.list +curl -Lo mainnet/20220411-20231129/consensus.tar.zst https://snapshots.oasis.io/node/mainnet/20220411-20231129/consensus.tar.zst +curl -Lo mainnet/20220411-20231129/cipher.tar.zst.list https://snapshots.oasis.io/node/mainnet/20220411-20231129/cipher.tar.zst.list +curl -Lo mainnet/20220411-20231129/cipher.tar.zst https://snapshots.oasis.io/node/mainnet/20220411-20231129/cipher.tar.zst +curl -Lo mainnet/20220411-20231129/emerald.tar.zst.list https://snapshots.oasis.io/node/mainnet/20220411-20231129/emerald.tar.zst.list +curl -Lo mainnet/20220411-20231129/emerald.tar.zst https://snapshots.oasis.io/node/mainnet/20220411-20231129/emerald.tar.zst +curl -Lo mainnet/20220411-20231129/sapphire.tar.zst.list https://snapshots.oasis.io/node/mainnet/20220411-20231129/sapphire.tar.zst.list +curl -Lo mainnet/20220411-20231129/sapphire.tar.zst https://snapshots.oasis.io/node/mainnet/20220411-20231129/sapphire.tar.zst diff --git a/badgerdb-analyzer/scripts/download-testnet.sh b/badgerdb-analyzer/scripts/download-testnet.sh new file mode 100755 index 0000000..1e31bcb --- /dev/null +++ b/badgerdb-analyzer/scripts/download-testnet.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Script to download Oasis node snapshots for Testnet +set -euo pipefail + +curl -Lo testnet/20200915-20201104/README.md https://snapshots.oasis.io/node/testnet/20200915-20201104/README.md +curl -Lo testnet/20200915-20201104/consensus_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20200915-20201104/consensus_testnet.tar.zst.list +curl -Lo testnet/20200915-20201104/consensus_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20200915-20201104/consensus_testnet.tar.zst + +curl -Lo testnet/20201104-20210203/README.md https://snapshots.oasis.io/node/testnet/20201104-20210203/README.md +curl -Lo testnet/20201104-20210203/consensus_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20201104-20210203/consensus_testnet.tar.zst.list +curl -Lo testnet/20201104-20210203/consensus_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20201104-20210203/consensus_testnet.tar.zst + +curl -Lo testnet/20210203-20210324/README.md https://snapshots.oasis.io/node/testnet/20210203-20210324/README.md +curl -Lo testnet/20210203-20210324/consensus_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20210203-20210324/consensus_testnet.tar.zst.list +curl -Lo testnet/20210203-20210324/consensus_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20210203-20210324/consensus_testnet.tar.zst + +curl -Lo testnet/20210324-20210413/README.md https://snapshots.oasis.io/node/testnet/20210324-20210413/README.md +curl -Lo testnet/20210324-20210413/consensus_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20210324-20210413/consensus_testnet.tar.zst.list +curl -Lo testnet/20210324-20210413/consensus_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20210324-20210413/consensus_testnet.tar.zst + +curl -Lo testnet/20210413-20220303/README.md https://snapshots.oasis.io/node/testnet/20210413-20220303/README.md +curl -Lo testnet/20210413-20220303/consensus_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20210413-20220303/consensus_testnet.tar.zst.list +curl -Lo testnet/20210413-20220303/consensus_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20210413-20220303/consensus_testnet.tar.zst + +curl -Lo testnet/20220303-20231012/README.md https://snapshots.oasis.io/node/testnet/20220303-20231012/README.md +curl -Lo testnet/20220303-20231012/consensus_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20220303-20231012/consensus_testnet.tar.zst.list +curl -Lo testnet/20220303-20231012/consensus_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20220303-20231012/consensus_testnet.tar.zst +curl -Lo testnet/20220303-20231012/cipher_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20220303-20231012/cipher_testnet.tar.zst.list +curl -Lo testnet/20220303-20231012/cipher_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20220303-20231012/cipher_testnet.tar.zst +curl -Lo testnet/20220303-20231012/emerald_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20220303-20231012/emerald_testnet.tar.zst.list +curl -Lo testnet/20220303-20231012/emerald_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20220303-20231012/emerald_testnet.tar.zst +curl -Lo testnet/20220303-20231012/sapphire_testnet.tar.zst.list https://snapshots.oasis.io/node/testnet/20220303-20231012/sapphire_testnet.tar.zst.list +curl -Lo testnet/20220303-20231012/sapphire_testnet.tar.zst https://snapshots.oasis.io/node/testnet/20220303-20231012/sapphire_testnet.tar.zst diff --git a/badgerdb-analyzer/scripts/run-mainnet-analyzers.sh b/badgerdb-analyzer/scripts/run-mainnet-analyzers.sh new file mode 100755 index 0000000..8b7a54c --- /dev/null +++ b/badgerdb-analyzer/scripts/run-mainnet-analyzers.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# Script to run all badgerdb-analyzers on Mainnet snapshots +set -euo pipefail + +ANALYZER_V2="./bin/badgerdb-analyzer-v2" +ANALYZER_V3="./bin/badgerdb-analyzer-v3" +pre_run() { + mkdir -p "${OUTPUT_DIR}" + "$(dirname "${0}")"/snapshots-extract.sh --extract "${DATA_DIR%.dir}.tar.zst" + echo "Processing: ${DATA_DIR}..." +} +post_run() { + "$(dirname "${0}")"/snapshots-extract.sh --clean "${DATA_DIR%.dir}.tar.zst" + echo "Processing done." + echo +} + + +# # mainnet-20201001-20201118 (BadgerDB v2) +# ( +# DATA_DIR="/snapshots/mainnet/20201001-20201118/consensus.dir" +# OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +# pre_run +# $ANALYZER_V2 consensus-blockstore-v2 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v2.json > $OUTPUT_DIR/consensus-blockstore-v2.log 2>&1 || true +# $ANALYZER_V2 consensus-evidence-v2 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v2.json > $OUTPUT_DIR/consensus-evidence-v2.log 2>&1 || true +# $ANALYZER_V2 consensus-state-v2 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v2.json > $OUTPUT_DIR/consensus-state-v2.log 2>&1 || true +# $ANALYZER_V2 consensus-mkvs-v2 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v2.json > $OUTPUT_DIR/consensus-mkvs-v2.log 2>&1 || true +# post_run +# ) + +# # mainnet-20201118-20210428 (BadgerDB v2) +# ( +# DATA_DIR="/snapshots/mainnet/20201118-20210428/consensus.dir" +# OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +# pre_run +# $ANALYZER_V2 consensus-blockstore-v2 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v2.json > $OUTPUT_DIR/consensus-blockstore-v2.log 2>&1 || true +# $ANALYZER_V2 consensus-evidence-v2 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v2.json > $OUTPUT_DIR/consensus-evidence-v2.log 2>&1 || true +# $ANALYZER_V2 consensus-state-v2 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v2.json > $OUTPUT_DIR/consensus-state-v2.log 2>&1 || true +# $ANALYZER_V2 consensus-mkvs-v2 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v2.json > $OUTPUT_DIR/consensus-mkvs-v2.log 2>&1 || true +# post_run +# ) + +# # mainnet-20210428-20220411 (BadgerDB v3) +# ( +# DATA_DIR="/snapshots/mainnet/20210428-20220411/consensus.dir" +# OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +# pre_run +# $ANALYZER_V3 consensus-blockstore-v3 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v3.json > $OUTPUT_DIR/consensus-blockstore-v3.log 2>&1 || true +# $ANALYZER_V3 consensus-evidence-v3 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v3.json > $OUTPUT_DIR/consensus-evidence-v3.log 2>&1 || true +# $ANALYZER_V3 consensus-state-v3 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v3.json > $OUTPUT_DIR/consensus-state-v3.log 2>&1 || true +# $ANALYZER_V3 consensus-mkvs-v3 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v3.json > $OUTPUT_DIR/consensus-mkvs-v3.log 2>&1 || true +# post_run +# ) +# ( +# DATA_DIR="/snapshots/mainnet/20210428-20220411/cipher.dir" +# OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +# pre_run +# $ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e199119c992377cb/mkvs_storage.badger.db $OUTPUT_DIR/cipher-runtime-mkvs-v3.json > $OUTPUT_DIR/cipher-runtime-mkvs-v3.log 2>&1 || true +# $ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e199119c992377cb/history.db $OUTPUT_DIR/cipher-runtime-history-v3.json > $OUTPUT_DIR/cipher-runtime-history-v3.log 2>&1 || true +# post_run +# ) +# ( +# DATA_DIR="/snapshots/mainnet/20210428-20220411/emerald.dir" +# OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +# pre_run +# $ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e2eaa99fc008f87f/history.db $OUTPUT_DIR/emerald-runtime-history-v3.json > $OUTPUT_DIR/emerald-runtime-history-v3.log 2>&1 || true +# $ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e2eaa99fc008f87f/mkvs_storage.badger.db $OUTPUT_DIR/emerald-runtime-mkvs-v3.json > $OUTPUT_DIR/emerald-runtime-mkvs-v3.log 2>&1 || true +# post_run +# ) + +# mainnet-20220411-20231129 (BadgerDB v3) +( +DATA_DIR="/snapshots/mainnet/20220411-20231129/consensus.dir" +OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 consensus-blockstore-v3 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v3.json > $OUTPUT_DIR/consensus-blockstore-v3.log 2>&1 || true +$ANALYZER_V3 consensus-evidence-v3 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v3.json > $OUTPUT_DIR/consensus-evidence-v3.log 2>&1 || true +$ANALYZER_V3 consensus-state-v3 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v3.json > $OUTPUT_DIR/consensus-state-v3.log 2>&1 || true +$ANALYZER_V3 consensus-mkvs-v3 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v3.json > $OUTPUT_DIR/consensus-mkvs-v3.log 2>&1 || true +post_run +) +( +DATA_DIR="/snapshots/mainnet/20220411-20231129/cipher.dir" +OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e199119c992377cb/mkvs_storage.badger.db $OUTPUT_DIR/cipher-runtime-mkvs-v3.json > $OUTPUT_DIR/cipher-runtime-mkvs-v3.log 2>&1 || true +$ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e199119c992377cb/history.db $OUTPUT_DIR/cipher-runtime-history-v3.json > $OUTPUT_DIR/cipher-runtime-history-v3.log 2>&1 || true +post_run +) +( +DATA_DIR="/snapshots/mainnet/20220411-20231129/emerald.dir" +OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e2eaa99fc008f87f/mkvs_storage.badger.db $OUTPUT_DIR/emerald-runtime-mkvs-v3.json > $OUTPUT_DIR/emerald-runtime-mkvs-v3.log 2>&1 || true +$ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000e2eaa99fc008f87f/history.db $OUTPUT_DIR/emerald-runtime-history-v3.json > $OUTPUT_DIR/emerald-runtime-history-v3.log 2>&1 || true +post_run +) +( +DATA_DIR="/snapshots/mainnet/20220411-20231129/sapphire.dir" +OUTPUT_DIR="./outputs/mainnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000f80306c9858e7279/mkvs_storage.badger.db $OUTPUT_DIR/sapphire-runtime-mkvs-v3.json > $OUTPUT_DIR/sapphire-runtime-mkvs-v3.log 2>&1 || true +$ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000f80306c9858e7279/history.db $OUTPUT_DIR/sapphire-runtime-history-v3.json > $OUTPUT_DIR/sapphire-runtime-history-v3.log 2>&1 || true +post_run +) + +echo "All samplers finished!" + +ls -ald outputs/*/* || true diff --git a/badgerdb-analyzer/scripts/run-testnet-analyzers.sh b/badgerdb-analyzer/scripts/run-testnet-analyzers.sh new file mode 100755 index 0000000..474754a --- /dev/null +++ b/badgerdb-analyzer/scripts/run-testnet-analyzers.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# Script to run all badgerdb-analyzers on Testnet snapshots +set -euo pipefail + +ANALYZER_V2="./bin/badgerdb-analyzer-v2" +ANALYZER_V3="./bin/badgerdb-analyzer-v3" +ANALYZER_V4="./bin/badgerdb-analyzer-v4" +pre_run() { + mkdir -p "${OUTPUT_DIR}" + "$(dirname "${0}")"/snapshots-extract.sh --extract "${DATA_DIR%.dir}.tar.zst" + echo "Processing: ${DATA_DIR}..." +} +post_run() { + "$(dirname "${0}")"/snapshots-extract.sh --clean "${DATA_DIR%.dir}.tar.zst" + echo "Processing done." + echo +} + + +# testnet-20200915-20201104 (BadgerDB v2) +( +DATA_DIR="/snapshots/testnet/20200915-20201104/consensus_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V2 consensus-blockstore-v2 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v2.json > $OUTPUT_DIR/consensus-blockstore-v2.log 2>&1 || true +$ANALYZER_V2 consensus-evidence-v2 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v2.json > $OUTPUT_DIR/consensus-evidence-v2.log 2>&1 || true +$ANALYZER_V2 consensus-state-v2 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v2.json > $OUTPUT_DIR/consensus-state-v2.log 2>&1 || true +$ANALYZER_V2 consensus-mkvs-v2 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v2.json > $OUTPUT_DIR/consensus-mkvs-v2.log 2>&1 || true +post_run +) + +# testnet-20201104-20210203 (BadgerDB v2) +( +DATA_DIR="/snapshots/testnet/20201104-20210203/consensus_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V2 consensus-blockstore-v2 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v2.json > $OUTPUT_DIR/consensus-blockstore-v2.log 2>&1 || true +$ANALYZER_V2 consensus-evidence-v2 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v2.json > $OUTPUT_DIR/consensus-evidence-v2.log 2>&1 || true +$ANALYZER_V2 consensus-state-v2 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v2.json > $OUTPUT_DIR/consensus-state-v2.log 2>&1 || true +$ANALYZER_V2 consensus-mkvs-v2 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v2.json > $OUTPUT_DIR/consensus-mkvs-v2.log 2>&1 || true +post_run +) + +# testnet-20210203-20210324 (BadgerDB v2) +( +DATA_DIR="/snapshots/testnet/20210203-20210324/consensus_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V2 consensus-blockstore-v2 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v2.json > $OUTPUT_DIR/consensus-blockstore-v2.log 2>&1 || true +$ANALYZER_V2 consensus-evidence-v2 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v2.json > $OUTPUT_DIR/consensus-evidence-v2.log 2>&1 || true +$ANALYZER_V2 consensus-state-v2 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v2.json > $OUTPUT_DIR/consensus-state-v2.log 2>&1 || true +$ANALYZER_V2 consensus-mkvs-v2 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v2.json > $OUTPUT_DIR/consensus-mkvs-v2.log 2>&1 || true +post_run +) + +# testnet-20210324-20210413 (BadgerDB v2) +( +DATA_DIR="/snapshots/testnet/20210324-20210413/consensus_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V2 consensus-blockstore-v2 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v2.json > $OUTPUT_DIR/consensus-blockstore-v2.log 2>&1 || true +$ANALYZER_V2 consensus-evidence-v2 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v2.json > $OUTPUT_DIR/consensus-evidence-v2.log 2>&1 || true +$ANALYZER_V2 consensus-state-v2 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v2.json > $OUTPUT_DIR/consensus-state-v2.log 2>&1 || true +$ANALYZER_V2 consensus-mkvs-v2 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v2.json > $OUTPUT_DIR/consensus-mkvs-v2.log 2>&1 || true +post_run +) + +# testnet-20210413-20220303 (BadgerDB v3) +( +DATA_DIR="/snapshots/testnet/20210413-20220303/consensus_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 consensus-blockstore-v3 $DATA_DIR/tendermint/data/blockstore.badger.db _DIR-20220303/consensus-blockstore-v3.json > $OUTPUT_DIR/consensus-blockstore-v3.log 2>&1 || true +$ANALYZER_V3 consensus-evidence-v3 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v3.json > $OUTPUT_DIR/consensus-evidence-v3.log 2>&1 || true +$ANALYZER_V3 consensus-state-v3 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v3.json > $OUTPUT_DIR/consensus-state-v3.log 2>&1 || true +$ANALYZER_V3 consensus-mkvs-v3 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v3.json > $OUTPUT_DIR/consensus-mkvs-v3.log 2>&1 || true +post_run +) + +# testnet-20220303-20231012 (BadgerDB v3) +( +DATA_DIR="/snapshots/testnet/20220303-20231012/consensus_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 consensus-blockstore-v3 $DATA_DIR/tendermint/data/blockstore.badger.db $OUTPUT_DIR/consensus-blockstore-v3.json > $OUTPUT_DIR/consensus-blockstore-v3.log 2>&1 || true +$ANALYZER_V3 consensus-evidence-v3 $DATA_DIR/tendermint/data/evidence.badger.db $OUTPUT_DIR/consensus-evidence-v3.json > $OUTPUT_DIR/consensus-evidence-v3.log 2>&1 || true +$ANALYZER_V3 consensus-state-v3 $DATA_DIR/tendermint/data/state.badger.db $OUTPUT_DIR/consensus-state-v3.json > $OUTPUT_DIR/consensus-state-v3.log 2>&1 || true +$ANALYZER_V3 consensus-mkvs-v3 $DATA_DIR/tendermint/abci-state/mkvs_storage.badger.db $OUTPUT_DIR/consensus-mkvs-v3.json > $OUTPUT_DIR/consensus-mkvs-v3.log 2>&1 || true +post_run +) +( +DATA_DIR="/snapshots/testnet/20220303-20231012/cipher_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/0000000000000000000000000000000000000000000000000000000000000000/mkvs_storage.badger.db $OUTPUT_DIR/cipher-runtime-mkvs-v3.json > $OUTPUT_DIR/cipher-runtime-mkvs-v3.log 2>&1 || true +$ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/0000000000000000000000000000000000000000000000000000000000000000/history.db $OUTPUT_DIR/cipher-runtime-history-v3.json > $OUTPUT_DIR/cipher-runtime-history-v3.log 2>&1 || true +post_run +) +( +DATA_DIR="/snapshots/testnet/20220303-20231012/emerald_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/00000000000000000000000000000000000000000000000072c8215e60d5bca7/mkvs_storage.badger.db $OUTPUT_DIR/emerald-runtime-mkvs-v3.json > $OUTPUT_DIR/emerald-runtime-mkvs-v3.log 2>&1 || true +$ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/00000000000000000000000000000000000000000000000072c8215e60d5bca7/history.db $OUTPUT_DIR/emerald-runtime-history-v3.json > $OUTPUT_DIR/emerald-runtime-history-v3.log 2>&1 || true +post_run +) +( +DATA_DIR="/snapshots/testnet/20220303-20231012/sapphire_testnet.dir" +OUTPUT_DIR="./outputs/testnet-$(basename $(dirname "${DATA_DIR}"))" +pre_run +$ANALYZER_V3 runtime-mkvs-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c/mkvs_storage.badger.db $OUTPUT_DIR/sapphire-runtime-mkvs-v3.json > $OUTPUT_DIR/sapphire-runtime-mkvs-v3.log 2>&1 || true +$ANALYZER_V3 runtime-history-v3 $DATA_DIR/runtimes/000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c/history.db $OUTPUT_DIR/sapphire-runtime-history-v3.json > $OUTPUT_DIR/sapphire-runtime-history-v3.log 2>&1 || true +post_run +) + +echo "All samplers finished!" + +ls -ald outputs/*/* || true diff --git a/badgerdb-analyzer/scripts/snapshots-extract.sh b/badgerdb-analyzer/scripts/snapshots-extract.sh new file mode 100755 index 0000000..79db016 --- /dev/null +++ b/badgerdb-analyzer/scripts/snapshots-extract.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Script to extract or mount .tar.zst snapshot archives +# +# Usage: +# ./snapshots-extract.sh [--extract|--mount|--clean] [...] +# +# Examples: +# ./snapshots-extract.sh --extract testnet/*/*.tar.zst +# ./snapshots-extract.sh --mount testnet/*/*.tar.zst +# ./snapshots-extract.sh --clean testnet/*/*.tar.zst +set -euo pipefail + +usage() { + echo "Usage: $0 [--extract|--mount|--clean] [...]" + exit 1 +} + +mode="${1:-}" +if [[ "$mode" != "--extract" && "$mode" != "--mount" && "$mode" != "--clean" ]]; then + usage +fi +shift + +if [[ $# -eq 0 ]]; then + echo "Error: No snapshot files specified" + usage +fi + +for archive in "$@"; do + basename="${archive%.tar.zst}" + rootdir="${basename}.dir" + overlaydir="${basename}.overlay" + + if [[ "$mode" == "--extract" ]]; then + # Extract mode + if [[ -d "$rootdir" ]]; then + echo "Already extracted: $rootdir" + continue + fi + echo "Extracting: $archive -> $rootdir" + mkdir -p "$rootdir" + tar --zstd -C "$rootdir" -xf "$archive" + + elif [[ "$mode" == "--mount" ]]; then + # Mount mode + if mountpoint -q "$rootdir" 2>/dev/null; then + echo "Already mounted: $rootdir" + continue + fi + echo "Mounting: $archive -> $rootdir" + mkdir -p "$rootdir" + ./ratarmount -o allow_other,uid=1000,gid=1000 -w "$overlaydir" "$archive" "$rootdir" + + elif [[ "$mode" == "--clean" ]]; then + # Cleanup mode + echo "Cleaning: $basename" + if mountpoint -q "$rootdir" 2>/dev/null; then + timeout 120 fusermount -u "$rootdir" || sudo umount -f "$rootdir" + fi + rm -rf "$rootdir" "$overlaydir" + fi +done + +echo "Extracting done." diff --git a/badgerdb-analyzer/types_consensus.go b/badgerdb-analyzer/types_consensus.go new file mode 100644 index 0000000..cc16ea0 --- /dev/null +++ b/badgerdb-analyzer/types_consensus.go @@ -0,0 +1,911 @@ +package main + +import ( + "encoding/json" + "math/big" + "time" + + "github.com/fxamacker/cbor/v2" +) + +// Consensus decode output types - structured representations of decoded consensus database entries. +// These types separate decoding logic from string formatting, enabling flexible output formats. + +// ============================================================================= +// Blockstore Types +// ============================================================================= + +// ConsensusBlockstoreKeyInfo represents a decoded consensus-blockstore key. +// See: tendermint/store/store.go for key formats (H:, P:, C:, SC:, BH:) +type ConsensusBlockstoreKeyInfo struct { + // Raw fields + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + KeyError string `json:"key_error,omitempty"` + + // Decoded fields + KeyType string `json:"key_type"` // "blockstore_state", "block_meta", "block_part", "block_commit", "seen_commit", "block_hash", "unknown" + ConsensusHeight int64 `json:"consensus_height,omitempty"` // For H:, C:, SC:, P: keys + PartIndex int `json:"part_index,omitempty"` // For P: keys + Hash string `json:"hash,omitempty"` // For BH: keys +} + +// ConsensusBlockstoreValueInfo represents a decoded consensus-blockstore value. +// See: tendermint/proto/tendermint/store/types.proto (BlockStoreState) +// See: tendermint/proto/tendermint/types/types.proto (BlockMeta, Part, Commit) +type ConsensusBlockstoreValueInfo struct { + // Raw fields + RawDump string `json:"raw_dump,omitempty"` + RawSize int `json:"raw_size"` + RawError string `json:"raw_error,omitempty"` + + // Decoded fields + Timestamp int64 `json:"timestamp,omitempty"` // Unix timestamp from block meta + State *ConsensusBlockStoreState `json:"state,omitempty"` + BlockMeta *ConsensusBlockMetaInfo `json:"block_meta,omitempty"` + Part *ConsensusPartInfo `json:"part,omitempty"` + Commit *ConsensusCommitInfo `json:"commit,omitempty"` + HashHeight int64 `json:"hash_height,omitempty"` // For block_hash type +} + +// ConsensusBlockStoreState represents BlockStoreState from tendermint. +// See: tendermint/proto/tendermint/store/types.proto (BlockStoreState message) +type ConsensusBlockStoreState struct { + Base int64 `json:"base"` + ConsensusHeight int64 `json:"consensus_height"` +} + +// ConsensusBlockMetaInfo represents decoded block metadata. +// See: tendermint/proto/tendermint/types/types.proto (BlockMeta, Header messages) +type ConsensusBlockMetaInfo struct { + ConsensusHeight int64 `json:"consensus_height"` + Time string `json:"time"` // RFC3339 format + ChainID string `json:"chain_id"` + NumTxs int64 `json:"num_txs"` + AppHash string `json:"app_hash,omitempty"` // hex, truncated +} + +// ConsensusPartInfo represents decoded block part. +// See: tendermint/proto/tendermint/types/types.proto (Part message) +type ConsensusPartInfo struct { + Index uint32 `json:"index"` + PartSize int `json:"part_size"` + ProofTotal int64 `json:"proof_total"` +} + +// ConsensusCommitInfo represents decoded commit. +// See: tendermint/proto/tendermint/types/types.proto (Commit message) +type ConsensusCommitInfo struct { + ConsensusHeight int64 `json:"consensus_height"` + Round int32 `json:"round"` // Tendermint commit round (not runtime height) + Signatures int `json:"signatures"` +} + +// ============================================================================= +// Evidence Types +// ============================================================================= + +// ConsensusEvidenceKeyInfo represents a decoded consensus-evidence key. +// Key format: dbVersion (0x01) + prefix (0x00=committed, 0x01=pending) + "HEIGHT_HEX/HASH_HEX" +// See: cometbft/evidence/pool.go for key formats (keyCommitted, keyPending) +type ConsensusEvidenceKeyInfo struct { + // Raw fields + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + KeyError string `json:"key_error,omitempty"` + + // Decoded fields + KeyType string `json:"key_type"` // "committed", "pending", "type_XX" + PrefixByte byte `json:"prefix_byte,omitempty"` + ConsensusHeight int64 `json:"consensus_height,omitempty"` // Extracted from key suffix + Hash string `json:"hash,omitempty"` // Evidence hash from key suffix +} + +// ConsensusEvidenceValueInfo represents a decoded consensus-evidence value. +// Committed evidence stores Int64Value (height only), pending stores full Evidence protobuf. +// See: cometbft/evidence/pool.go (addPendingEvidence stores full evidence, markEvidenceAsCommitted stores height) +// See: tendermint/proto/tendermint/types/evidence.proto (DuplicateVoteEvidence, LightClientAttackEvidence) +type ConsensusEvidenceValueInfo struct { + // Raw fields + RawDump string `json:"raw_dump,omitempty"` + RawSize int `json:"raw_size"` + RawError string `json:"raw_error,omitempty"` + SchemaVersion string `json:"schema_version,omitempty"` // "cb-v0.37", "tm-v0.34", "unknown" + + // Decoded fields + EvidenceType string `json:"evidence_type,omitempty"` // "committed_marker", "duplicate_vote", "light_client_attack", "unknown" + CommittedHeight int64 `json:"committed_height,omitempty"` // For committed evidence (Int64Value) + VoteAHeight int64 `json:"vote_a_height,omitempty"` // For pending DuplicateVoteEvidence + VoteBHeight int64 `json:"vote_b_height,omitempty"` // For pending DuplicateVoteEvidence + TotalVotingPower int64 `json:"total_voting_power,omitempty"` // For pending evidence + ValidatorPower int64 `json:"validator_power,omitempty"` // For pending evidence + Timestamp string `json:"timestamp,omitempty"` // RFC3339 format (for pending evidence) +} + +// ============================================================================= +// MKVS Types +// ============================================================================= + +// ConsensusMkvsKeyInfo represents a decoded consensus-mkvs key. +// See: _oasis-core/go/storage/mkvs/db/badger/badger.go:31-66 +type ConsensusMkvsKeyInfo struct { + // Raw fields + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + KeyError string `json:"key_error,omitempty"` + + // Decoded fields + KeyType string `json:"key_type"` // "node", "write_log", "roots_metadata", "root_updated_nodes", "metadata", "multipart_restore_log", "root_node", "unknown" + ConsensusHeight int64 `json:"consensus_height,omitempty"` // For write_log, roots_metadata, root_updated_nodes + Hash string `json:"hash,omitempty"` // hex, truncated (partial key data) + RootType string `json:"root_type,omitempty"` +} + +// ConsensusMkvsValueInfo represents a decoded consensus-mkvs value (node). +// See: _oasis-core/go/storage/mkvs/node/node.go:26-32 (prefixes), 294-309 (InternalNode), 531-537 (LeafNode) +type ConsensusMkvsValueInfo struct { + // Raw fields + RawDump string `json:"raw_dump,omitempty"` + RawSize int `json:"raw_size"` + RawError string `json:"raw_error,omitempty"` + + // Node info + NodeType string `json:"node_type"` // "leaf", "internal", "nil", "non_node", "unknown" + Leaf *ConsensusMkvsLeafInfo `json:"leaf,omitempty"` + Internal *ConsensusMkvsInternalInfo `json:"internal,omitempty"` +} + +// ConsensusMkvsLeafInfo represents a decoded MKVS LeafNode for consensus. +// See: _oasis-core/go/storage/mkvs/node/node.go:531-537 (LeafNode) +// See: _oasis-core/go/consensus/tendermint/apps/*/state/state.go (module prefixes) +type ConsensusMkvsLeafInfo struct { + // Leaf key (extracted from MKVS node) + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + + // Decoded key fields + Module string `json:"module"` + KeyType string `json:"key_type,omitempty"` + OasisAddress string `json:"oasis_address,omitempty"` // bech32 oasis1... address if applicable + + // Leaf value raw data + ValueDump string `json:"value_dump,omitempty"` + ValueSize int `json:"value_size"` + ValueError string `json:"value_error,omitempty"` + + // Decoded value + ValueType string `json:"value_type,omitempty"` + Value interface{} `json:"value,omitempty"` + CBOR string `json:"cbor,omitempty"` // CBOR type description or format hint +} + +// ConsensusMkvsInternalInfo represents a decoded MKVS InternalNode. +// See: _oasis-core/go/storage/mkvs/node/node.go:294-309 (InternalNode) +type ConsensusMkvsInternalInfo struct { + LabelBits uint16 `json:"label_bits"` + HasLeaf bool `json:"has_leaf"` + LeftHash string `json:"left_hash,omitempty"` // hex, truncated + RightHash string `json:"right_hash,omitempty"` // hex, truncated +} + +// ============================================================================= +// State Types +// ============================================================================= + +// ConsensusStateKeyInfo represents a decoded consensus-state key. +// See: tendermint/state/store.go for key formats (abciResponsesKey, validatorsKey, etc.) +type ConsensusStateKeyInfo struct { + // Raw fields + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + KeyError string `json:"key_error,omitempty"` + + // Decoded fields + KeyType string `json:"key_type"` // "abci_responses", "consensus_params", "validators", "state", "genesis", "text_key", "binary", "unknown" + ConsensusHeight int64 `json:"consensus_height,omitempty"` // For abci_responses (extracted from key) + IsBinary bool `json:"is_binary,omitempty"` +} + +// ConsensusStateValueInfo represents a decoded consensus-state value. +// See: tendermint/proto/tendermint/state/types.proto (various state types) +type ConsensusStateValueInfo struct { + // Raw fields + RawDump string `json:"raw_dump,omitempty"` + RawSize int `json:"raw_size"` + RawError string `json:"raw_error,omitempty"` + SchemaVersion string `json:"schema_version,omitempty"` // "cometbft", "tendermint-v0.34", "unknown" + + // Decoded content - only ONE populated + ABCIResponse *ConsensusABCIResponseInfo `json:"abci_response,omitempty"` // For abci_responses + ConsensusParams *tmConsensusParams `json:"consensus_params,omitempty"` // For consensus_params + ConsensusValidators *tmValidatorSet `json:"consensus_validators,omitempty"` // For validators + ConsensusState *tmState `json:"consensus_state,omitempty"` // For state +} + +// ConsensusABCIResponseInfo represents decoded ABCI response summary. +// See: tendermint/proto/tendermint/abci/types.proto (ResponseFinalizeBlock) +type ConsensusABCIResponseInfo struct { + TxResultCount int `json:"tx_result_count"` + ValidatorUpdates int `json:"validator_updates"` + EventCount int `json:"event_count"` + TransactionSummary *ConsensusTransactionSummary `json:"transaction_summary,omitempty"` + EventSummary *ConsensusEventSummary `json:"event_summary,omitempty"` +} + +// ConsensusTransactionSummary summarizes decoded transactions from ABCI responses. +type ConsensusTransactionSummary struct { + MethodCounts map[string]int `json:"method_counts"` // Count by method name + Transactions []ConsensusTransactionInfo `json:"transactions,omitempty"` // Decoded transaction details +} + +// ConsensusTransactionInfo represents a decoded Oasis consensus transaction. +// See: _oasis-core/go/consensus/api/transaction/transaction.go:42-54 +type ConsensusTransactionInfo struct { + Nonce uint64 `json:"nonce"` + Method string `json:"method"` + Fee *ConsensusFee `json:"fee,omitempty"` + Signer string `json:"signer,omitempty"` // hex-encoded public key + TxHash string `json:"tx_hash,omitempty"` // hex-encoded transaction hash + BodyHex string `json:"body_hex,omitempty"` // Truncated hex dump of raw body + BodySize int `json:"body_size,omitempty"` // Size of body in bytes + Body interface{} `json:"body,omitempty"` // Decoded body for known types + BodyError string `json:"body_error,omitempty"` // Error decoding body +} + +// ConsensusFee represents transaction fee. +// See: _oasis-core/go/consensus/api/transaction/gas.go:30-35 +type ConsensusFee struct { + Amount string `json:"amount"` // string representation of quantity.Quantity + Gas uint64 `json:"gas"` +} + +// ConsensusEventSummary summarizes decoded events from ABCI responses. +type ConsensusEventSummary struct { + EventTypeCounts map[string]int `json:"event_type_counts"` // Count by event type + Events []ConsensusEventInfo `json:"events,omitempty"` // Sample of decoded events +} + +// ConsensusEventInfo represents a decoded consensus event (unified type for all event kinds). +type ConsensusEventInfo struct { + EventType string `json:"event_type"` // "staking.transfer", "staking.burn", etc. + EventKind string `json:"event_kind"` // "transfer", "burn", "add_escrow", "reclaim_escrow" + BodyHex string `json:"body_hex,omitempty"` // Truncated hex dump of raw body + BodySize int `json:"body_size,omitempty"` // Size of body in bytes + Body interface{} `json:"body,omitempty"` // Decoded body for known types + BodyError string `json:"body_error,omitempty"` // Error decoding body +} + + +// ============================================================================= +// Consensus CBOR Deserialization Types +// ============================================================================= + +// QuantityBytes wraps big.Int and implements encoding.BinaryUnmarshaler to automatically +// decode CBOR byte strings (big-endian) into arbitrary-precision unsigned integers. +// See: _oasis-core/go/common/quantity/quantity.go:28-50 (MarshalBinary/UnmarshalBinary) +type QuantityBytes big.Int + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +// Decodes a byte slice (big-endian) into a big.Int. +func (q *QuantityBytes) UnmarshalBinary(data []byte) error { + if q == nil { + return nil + } + (*big.Int)(q).SetBytes(data) + return nil +} + +// String returns the decimal string representation of the quantity. +func (q *QuantityBytes) String() string { + if q == nil { + return "0" + } + return (*big.Int)(q).String() +} + +// OasisAddress represents a 21-byte Oasis address with automatic bech32 encoding. +// See: _oasis-core/go/common/crypto/address/address.go (ADDRESS_SIZE = 21) +type OasisAddress [21]byte + +// MarshalJSON implements json.Marshaler to automatically encode as bech32 string. +func (a OasisAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(bech32Encode("oasis", a[:])) +} + +// String returns the bech32-encoded address (e.g., "oasis1..."). +func (a OasisAddress) String() string { + return bech32Encode("oasis", a[:]) +} + +// cborConsensusSignedTransaction represents the signature envelope. +// See: _oasis-core/go/common/crypto/signature/signature.go:415-421 +type cborConsensusSignedTransaction struct { + Blob []byte `json:"untrusted_raw_value"` + Signature cborConsensusSignature `json:"signature"` +} + +// cborConsensusInnerTransaction represents an unsigned consensus transaction. +// See: _oasis-core/go/consensus/api/transaction/transaction.go:43-54 +type cborConsensusInnerTransaction struct { + Nonce uint64 `json:"nonce"` + Fee *cborConsensusFee `json:"fee"` + Method string `json:"method"` + Body cbor.RawMessage `json:"body"` +} + +// cborConsensusFee represents transaction fee. +// See: _oasis-core/go/consensus/api/transaction/gas.go:30-35 +type cborConsensusFee struct { + Amount string `json:"amount"` // string representation of quantity.Quantity + Gas uint64 `json:"gas"` +} + +// cborConsensusSignature represents a signature with public key. +// See: _oasis-core/go/common/crypto/signature/signature.go:313-318 +type cborConsensusSignature struct { + PublicKey [32]byte `json:"public_key"` // ED25519 public key + Signature [64]byte `json:"signature"` // ED25519 signature +} + +// cborConsensusTransfer represents a staking transfer transaction body. +// See: _oasis-core/go/staking/api/api.go:342-345 +type cborConsensusTransfer struct { + To OasisAddress `json:"to"` // Oasis address (bech32-encoded in JSON) + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusBurn represents a staking burn transaction body. +// See: _oasis-core/go/staking/api/api.go:368-370 +type cborConsensusBurn struct { + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusAddEscrow represents an add escrow transaction body. +// See: _oasis-core/go/staking/api/api.go:403-406 +type cborConsensusAddEscrow struct { + Account OasisAddress `json:"account"` // Oasis address (bech32-encoded in JSON) + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusReclaimEscrow represents a reclaim escrow transaction body. +// See: _oasis-core/go/staking/api/api.go:444-447 +type cborConsensusReclaimEscrow struct { + Account OasisAddress `json:"account"` // Oasis address (bech32-encoded in JSON) + Shares *QuantityBytes `json:"shares"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusRegisterEntity represents a registry entity registration transaction body. +// See: _oasis-core/go/registry/api/api.go +type cborConsensusRegisterEntity struct { + Signature []byte `json:"signature,omitempty"` + Descriptor []byte `json:"descriptor,omitempty"` // CBOR-encoded entity descriptor +} + +// cborConsensusRegisterNode represents a registry node registration transaction body. +// See: _oasis-core/go/registry/api/api.go +type cborConsensusRegisterNode struct { + Signature []byte `json:"signature,omitempty"` + Descriptor []byte `json:"descriptor,omitempty"` // CBOR-encoded node descriptor +} + +// cborConsensusExecutorCommit represents a roothash executor commit transaction body. +// See: _oasis-core/go/roothash/api/commitment/executor.go +type cborConsensusExecutorCommit struct { + ID []byte `json:"runtime_id,omitempty"` // Runtime ID + Commits []byte `json:"commits,omitempty"` // CBOR-encoded commits +} + +// cborConsensusSubmitProposal represents a governance proposal submission transaction body. +// See: _oasis-core/go/governance/api/api.go +type cborConsensusSubmitProposal struct { + Content []byte `json:"content,omitempty"` // CBOR-encoded proposal content + Deposit *QuantityBytes `json:"deposit,omitempty"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusCastVote represents a governance vote transaction body. +// See: _oasis-core/go/governance/api/api.go +type cborConsensusCastVote struct { + ProposalID uint64 `json:"proposal_id"` + Vote uint8 `json:"vote"` // 0=invalid, 1=yes, 2=no, 3=abstain +} + +// cborConsensusTransferEvent represents a staking transfer event. +// See: _oasis-core/go/staking/api/api.go:223-229 +type cborConsensusTransferEvent struct { + From OasisAddress `json:"from"` // Oasis address (bech32-encoded in JSON) + To OasisAddress `json:"to"` // Oasis address (bech32-encoded in JSON) + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusBurnEvent represents a staking burn event. +// See: _oasis-core/go/staking/api/api.go:236-240 +type cborConsensusBurnEvent struct { + Owner OasisAddress `json:"owner"` // Oasis address (bech32-encoded in JSON) + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusAddEscrowEvent represents an add escrow event. +// See: _oasis-core/go/staking/api/api.go:266-273 +type cborConsensusAddEscrowEvent struct { + Owner OasisAddress `json:"owner"` // Oasis address (bech32-encoded in JSON) + Escrow OasisAddress `json:"escrow"` // Oasis address (bech32-encoded in JSON) + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string + NewShares *QuantityBytes `json:"new_shares"` // QuantityBytes automatically unmarshals from CBOR byte string +} + +// cborConsensusReclaimEscrowEvent represents a reclaim escrow event. +// See: _oasis-core/go/staking/api/api.go:313-320 +type cborConsensusReclaimEscrowEvent struct { + Owner OasisAddress `json:"owner"` // Oasis address (bech32-encoded in JSON) + Escrow OasisAddress `json:"escrow"` // Oasis address (bech32-encoded in JSON) + Amount *QuantityBytes `json:"amount"` // QuantityBytes automatically unmarshals from CBOR byte string + Shares *QuantityBytes `json:"shares"` // QuantityBytes automatically unmarshals from CBOR byte string +} + + +// ============================================================================= +// Tendermint Protobuf Deserialization Types +// ============================================================================= +// Source: github.com/tendermint/tendermint v0.34.21 +// These types are used only for protobuf deserialization - no tendermint business logic is needed. +// For proto.Unmarshal() to work types need to implement proto.Message interface. + +// tmBlockStoreState represents BlockStoreState from tendermint/proto/tendermint/store/types.proto +type tmBlockStoreState struct { + Base int64 `protobuf:"varint,1,opt,name=base,proto3"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3"` +} +func (m *tmBlockStoreState) Reset() { *m = tmBlockStoreState{} } +func (m *tmBlockStoreState) String() string { return "" } +func (*tmBlockStoreState) ProtoMessage() {} + +// tmBlockMeta represents BlockMeta from tendermint/proto/tendermint/types/types.proto +type tmBlockMeta struct { + BlockID tmBlockID `protobuf:"bytes,1,opt,name=block_id,json=blockId,proto3"` + BlockSize int64 `protobuf:"varint,2,opt,name=block_size,json=blockSize,proto3"` + Header tmHeader `protobuf:"bytes,3,opt,name=header,proto3"` + NumTxs int64 `protobuf:"varint,4,opt,name=num_txs,json=numTxs,proto3"` +} +func (m *tmBlockMeta) Reset() { *m = tmBlockMeta{} } +func (m *tmBlockMeta) String() string { return "" } +func (*tmBlockMeta) ProtoMessage() {} + +// tmBlockID from tendermint/proto/tendermint/types/types.proto +type tmBlockID struct { + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3"` + PartSetHeader tmPartSetHeader `protobuf:"bytes,2,opt,name=part_set_header,json=partSetHeader,proto3"` +} + + +// tmPartSetHeader from tendermint/proto/tendermint/types/types.proto +type tmPartSetHeader struct { + Total uint32 `protobuf:"varint,1,opt,name=total,proto3"` + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3"` +} + + +// tmHeader from tendermint/proto/tendermint/types/types.proto +type tmHeader struct { + Version tmConsensus `protobuf:"bytes,1,opt,name=version,proto3"` + ChainID string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3"` + Height int64 `protobuf:"varint,3,opt,name=height,proto3"` + Time time.Time `protobuf:"bytes,4,opt,name=time,proto3,stdtime"` + LastBlockID tmBlockID `protobuf:"bytes,5,opt,name=last_block_id,json=lastBlockId,proto3"` + LastCommitHash []byte `protobuf:"bytes,6,opt,name=last_commit_hash,json=lastCommitHash,proto3"` + DataHash []byte `protobuf:"bytes,7,opt,name=data_hash,json=dataHash,proto3"` + ValidatorsHash []byte `protobuf:"bytes,8,opt,name=validators_hash,json=validatorsHash,proto3"` + NextValidatorsHash []byte `protobuf:"bytes,9,opt,name=next_validators_hash,json=nextValidatorsHash,proto3"` + ConsensusHash []byte `protobuf:"bytes,10,opt,name=consensus_hash,json=consensusHash,proto3"` + AppHash []byte `protobuf:"bytes,11,opt,name=app_hash,json=appHash,proto3"` + LastResultsHash []byte `protobuf:"bytes,12,opt,name=last_results_hash,json=lastResultsHash,proto3"` + EvidenceHash []byte `protobuf:"bytes,13,opt,name=evidence_hash,json=evidenceHash,proto3"` + ProposerAddress []byte `protobuf:"bytes,14,opt,name=proposer_address,json=proposerAddress,proto3"` +} + + +// tmConsensus from tendermint/proto/tendermint/types/types.proto +type tmConsensus struct { + Block uint64 `protobuf:"varint,1,opt,name=block,proto3"` + App uint64 `protobuf:"varint,2,opt,name=app,proto3"` +} + + +// tmPart from tendermint/proto/tendermint/types/types.proto +type tmPart struct { + Index uint32 `protobuf:"varint,1,opt,name=index,proto3"` + Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3"` + Proof tmProof `protobuf:"bytes,3,opt,name=proof,proto3"` +} +func (m *tmPart) Reset() { *m = tmPart{} } +func (m *tmPart) String() string { return "" } +func (*tmPart) ProtoMessage() {} + + +// tmProof from tendermint/proto/tendermint/types/types.proto +type tmProof struct { + Total int64 `protobuf:"varint,1,opt,name=total,proto3"` + Index int64 `protobuf:"varint,2,opt,name=index,proto3"` + LeafHash []byte `protobuf:"bytes,3,opt,name=leaf_hash,json=leafHash,proto3"` + Aunts [][]byte `protobuf:"bytes,4,rep,name=aunts,proto3"` +} + + +// tmCommit from tendermint/proto/tendermint/types/types.proto +type tmCommit struct { + Height int64 `protobuf:"varint,1,opt,name=height,proto3"` + Round int32 `protobuf:"varint,2,opt,name=round,proto3"` + BlockID tmBlockID `protobuf:"bytes,3,opt,name=block_id,json=blockId,proto3"` + Signatures []tmCommitSig `protobuf:"bytes,4,rep,name=signatures,proto3"` +} +func (m *tmCommit) Reset() { *m = tmCommit{} } +func (m *tmCommit) String() string { return "" } +func (*tmCommit) ProtoMessage() {} + + +// tmCommitSig from tendermint/proto/tendermint/types/types.proto +type tmCommitSig struct { + BlockIDFlag int32 `protobuf:"varint,1,opt,name=block_id_flag,json=blockIdFlag,proto3"` + ValidatorAddress []byte `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3"` + Timestamp time.Time `protobuf:"bytes,3,opt,name=timestamp,proto3,stdtime"` + Signature []byte `protobuf:"bytes,4,opt,name=signature,proto3"` +} + + +// tmInt64Value from gogoproto/protobuf/wrappers.proto +// Used by Tendermint to store committed evidence (just the height) +type tmInt64Value struct { + Value int64 `protobuf:"varint,1,opt,name=value,proto3"` +} +func (m *tmInt64Value) Reset() { *m = tmInt64Value{} } +func (m *tmInt64Value) String() string { return "" } +func (*tmInt64Value) ProtoMessage() {} + + +// tmDuplicateVoteEvidence from tendermint/proto/tendermint/types/evidence.proto +type tmDuplicateVoteEvidence struct { + VoteA *tmVote `protobuf:"bytes,1,opt,name=vote_a,json=voteA,proto3"` + VoteB *tmVote `protobuf:"bytes,2,opt,name=vote_b,json=voteB,proto3"` + TotalVotingPower int64 `protobuf:"varint,3,opt,name=total_voting_power,json=totalVotingPower,proto3"` + ValidatorPower int64 `protobuf:"varint,4,opt,name=validator_power,json=validatorPower,proto3"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime"` +} +func (m *tmDuplicateVoteEvidence) Reset() { *m = tmDuplicateVoteEvidence{} } +func (m *tmDuplicateVoteEvidence) String() string { return "" } +func (*tmDuplicateVoteEvidence) ProtoMessage() {} + + +// tmVote from tendermint/proto/tendermint/types/types.proto +type tmVote struct { + Type int32 `protobuf:"varint,1,opt,name=type,proto3"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3"` + Round int32 `protobuf:"varint,3,opt,name=round,proto3"` + BlockID tmBlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime"` + ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3"` + ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3"` + Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3"` +} + + +// tmLightClientAttackEvidence from tendermint/proto/tendermint/types/evidence.proto +type tmLightClientAttackEvidence struct { + ConflictingBlock *tmLightBlock `protobuf:"bytes,1,opt,name=conflicting_block,json=conflictingBlock,proto3"` + CommonHeight int64 `protobuf:"varint,2,opt,name=common_height,json=commonHeight,proto3"` + ByzantineValidators []*tmValidator `protobuf:"bytes,3,rep,name=byzantine_validators,json=byzantineValidators,proto3"` + TotalVotingPower int64 `protobuf:"varint,4,opt,name=total_voting_power,json=totalVotingPower,proto3"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime"` +} +func (m *tmLightClientAttackEvidence) Reset() { *m = tmLightClientAttackEvidence{} } +func (m *tmLightClientAttackEvidence) String() string { return "" } +func (*tmLightClientAttackEvidence) ProtoMessage() {} + + +// tmLightBlock from tendermint/proto/tendermint/types/types.proto +type tmLightBlock struct { + SignedHeader *tmSignedHeader `protobuf:"bytes,1,opt,name=signed_header,json=signedHeader,proto3"` + ValidatorSet *tmValidatorSet `protobuf:"bytes,2,opt,name=validator_set,json=validatorSet,proto3"` +} + + +// tmSignedHeader from tendermint/proto/tendermint/types/types.proto +type tmSignedHeader struct { + Header *tmHeader `protobuf:"bytes,1,opt,name=header,proto3"` + Commit *tmCommit `protobuf:"bytes,2,opt,name=commit,proto3"` +} + + +// tmValidatorSet from tendermint/proto/tendermint/types/validator.proto +type tmValidatorSet struct { + Validators []*tmValidator `protobuf:"bytes,1,rep,name=validators,proto3"` + Proposer *tmValidator `protobuf:"bytes,2,opt,name=proposer,proto3"` + TotalVotingPower int64 `protobuf:"varint,3,opt,name=total_voting_power,json=totalVotingPower,proto3"` +} +func (m *tmValidatorSet) Reset() { *m = tmValidatorSet{} } +func (m *tmValidatorSet) String() string { return "" } +func (*tmValidatorSet) ProtoMessage() {} + + +// tmValidator from tendermint/proto/tendermint/types/validator.proto +type tmValidator struct { + Address []byte `protobuf:"bytes,1,opt,name=address,proto3"` + PubKey []byte `protobuf:"bytes,2,opt,name=pub_key,json=pubKey,proto3"` + VotingPower int64 `protobuf:"varint,3,opt,name=voting_power,json=votingPower,proto3"` + ProposerPriority int64 `protobuf:"varint,4,opt,name=proposer_priority,json=proposerPriority,proto3"` +} + + +// tmABCIResponses from tendermint/proto/tendermint/state/types.proto +type tmABCIResponses struct { + DeliverTxs []*tmResponseDeliverTx `protobuf:"bytes,1,rep,name=deliver_txs,json=deliverTxs,proto3"` + EndBlock *tmResponseEndBlock `protobuf:"bytes,2,opt,name=end_block,json=endBlock,proto3"` + BeginBlock *tmResponseBeginBlock `protobuf:"bytes,3,opt,name=begin_block,json=beginBlock,proto3"` +} +func (m *tmABCIResponses) Reset() { *m = tmABCIResponses{} } +func (m *tmABCIResponses) String() string { return "" } +func (*tmABCIResponses) ProtoMessage() {} + +// tmResponseDeliverTx from tendermint/proto/tendermint/abci/types.proto +type tmResponseDeliverTx struct { + Code uint32 `protobuf:"varint,1,opt,name=code,proto3"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3"` + Log string `protobuf:"bytes,3,opt,name=log,proto3"` + Info string `protobuf:"bytes,4,opt,name=info,proto3"` + GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,json=gasWanted,proto3"` + GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3"` + Events []tmEvent `protobuf:"bytes,7,rep,name=events,proto3"` + Codespace string `protobuf:"bytes,8,opt,name=codespace,proto3"` +} + +// tmResponseBeginBlock from tendermint/proto/tendermint/abci/types.proto +type tmResponseBeginBlock struct { + Events []tmEvent `protobuf:"bytes,1,rep,name=events,proto3"` +} + + +// tmResponseEndBlock from tendermint/proto/tendermint/abci/types.proto +type tmResponseEndBlock struct { + ValidatorUpdates []tmValidatorUpdate `protobuf:"bytes,1,rep,name=validator_updates,json=validatorUpdates,proto3"` + ConsensusParamUpdates *tmConsensusParams `protobuf:"bytes,2,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3"` + Events []tmEvent `protobuf:"bytes,3,rep,name=events,proto3"` +} + + +// tmEvent from tendermint/proto/tendermint/abci/types.proto +type tmEvent struct { + Type string `protobuf:"bytes,1,opt,name=type,proto3"` + Attributes []tmEventAttribute `protobuf:"bytes,2,rep,name=attributes,proto3"` +} + + +// tmEventAttribute from tendermint/proto/tendermint/abci/types.proto +type tmEventAttribute struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3"` + Index bool `protobuf:"varint,3,opt,name=index,proto3"` +} + + +// tmValidatorUpdate from tendermint/proto/tendermint/abci/types.proto +type tmValidatorUpdate struct { + PubKey []byte `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3"` + Power int64 `protobuf:"varint,2,opt,name=power,proto3"` +} + + +// tmConsensusParams from tendermint/proto/tendermint/types/params.proto +type tmConsensusParams struct { + Block tmBlockParams `protobuf:"bytes,1,opt,name=block,proto3"` + Evidence tmEvidenceParams `protobuf:"bytes,2,opt,name=evidence,proto3"` + Validator tmValidatorParams `protobuf:"bytes,3,opt,name=validator,proto3"` + Version tmVersionParams `protobuf:"bytes,4,opt,name=version,proto3"` +} +func (m *tmConsensusParams) Reset() { *m = tmConsensusParams{} } +func (m *tmConsensusParams) String() string { return "" } +func (*tmConsensusParams) ProtoMessage() {} + + +// tmBlockParams from tendermint/proto/tendermint/types/params.proto +type tmBlockParams struct { + MaxBytes int64 `protobuf:"varint,1,opt,name=max_bytes,json=maxBytes,proto3"` + MaxGas int64 `protobuf:"varint,2,opt,name=max_gas,json=maxGas,proto3"` +} + + +// tmEvidenceParams from tendermint/proto/tendermint/types/params.proto +type tmEvidenceParams struct { + MaxAgeNumBlocks int64 `protobuf:"varint,1,opt,name=max_age_num_blocks,json=maxAgeNumBlocks,proto3"` + MaxAgeDuration int64 `protobuf:"varint,2,opt,name=max_age_duration,json=maxAgeDuration,proto3"` + MaxBytes int64 `protobuf:"varint,3,opt,name=max_bytes,json=maxBytes,proto3"` +} + + +// tmValidatorParams from tendermint/proto/tendermint/types/params.proto +type tmValidatorParams struct { + PubKeyTypes []string `protobuf:"bytes,1,rep,name=pub_key_types,json=pubKeyTypes,proto3"` +} + + +// tmVersionParams from tendermint/proto/tendermint/types/params.proto +type tmVersionParams struct { + AppVersion uint64 `protobuf:"varint,1,opt,name=app_version,json=appVersion,proto3"` +} + + +// tmState from tendermint/proto/tendermint/state/types.proto +type tmState struct { + Version uint64 `protobuf:"varint,1,opt,name=version,proto3"` + ChainID string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3"` + InitialHeight int64 `protobuf:"varint,3,opt,name=initial_height,json=initialHeight,proto3"` + LastBlockHeight int64 `protobuf:"varint,4,opt,name=last_block_height,json=lastBlockHeight,proto3"` +} +func (m *tmState) Reset() { *m = tmState{} } +func (m *tmState) String() string { return "" } +func (*tmState) ProtoMessage() {} + + +// ============================================================================= +// CometBFT Protobuf Deserialization Types +// ============================================================================= +// Source: github.com/cometbft/cometbft v0.37.x / v0.38.x +// https://github.com/cometbft/cometbft/blob/main/proto/cometbft/abci/v1/types.proto +// https://github.com/cometbft/cometbft/blob/main/proto/cometbft/types/v1/evidence.proto +// For proto.Unmarshal() to work types need to implement proto.Message interface. + +// Type aliases for cb* types identical to tm* types +// Source: cometbft/proto/cometbft/abci/v1/types.proto +type cbEvent = tmEvent +type cbEventAttribute = tmEventAttribute +type cbValidatorUpdate = tmValidatorUpdate +type cbConsensusParams = tmConsensusParams +type cbBlockParams = tmBlockParams +type cbEvidenceParams = tmEvidenceParams +type cbValidatorParams = tmValidatorParams +type cbVersionParams = tmVersionParams +type cbBlockID = tmBlockID +type cbPartSetHeader = tmPartSetHeader +type cbInt64Value = tmInt64Value + +// cbResponseFinalizeBlock from CometBFT ABCI++ (v0.37+) +// Source: cometbft/proto/cometbft/abci/v1/types.proto +type cbResponseFinalizeBlock struct { + Events []cbEvent `protobuf:"bytes,1,rep,name=events,proto3"` + TxResults []*cbExecTxResult `protobuf:"bytes,2,rep,name=tx_results,json=txResults,proto3"` + ValidatorUpdates []cbValidatorUpdate `protobuf:"bytes,3,rep,name=validator_updates,json=validatorUpdates,proto3"` + ConsensusParamUpdates *cbConsensusParams `protobuf:"bytes,4,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3"` + AppHash []byte `protobuf:"bytes,5,opt,name=app_hash,json=appHash,proto3"` +} +func (m *cbResponseFinalizeBlock) Reset() { *m = cbResponseFinalizeBlock{} } +func (m *cbResponseFinalizeBlock) String() string { return "" } +func (*cbResponseFinalizeBlock) ProtoMessage() {} + +// cbExecTxResult from CometBFT ABCI++ (v0.37+) +// Source: cometbft/proto/cometbft/abci/v1/types.proto +type cbExecTxResult struct { + Code uint32 `protobuf:"varint,1,opt,name=code,proto3"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3"` + Log string `protobuf:"bytes,3,opt,name=log,proto3"` + Info string `protobuf:"bytes,4,opt,name=info,proto3"` + GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,json=gasWanted,proto3"` + GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3"` + Events []cbEvent `protobuf:"bytes,7,rep,name=events,proto3"` + Codespace string `protobuf:"bytes,8,opt,name=codespace,proto3"` +} + +// cbDuplicateVoteEvidence from CometBFT +// Source: cometbft/proto/cometbft/types/v1/evidence.proto +type cbDuplicateVoteEvidence struct { + VoteA *cbVote `protobuf:"bytes,1,opt,name=vote_a,json=voteA,proto3"` + VoteB *cbVote `protobuf:"bytes,2,opt,name=vote_b,json=voteB,proto3"` + TotalVotingPower int64 `protobuf:"varint,3,opt,name=total_voting_power,json=totalVotingPower,proto3"` + ValidatorPower int64 `protobuf:"varint,4,opt,name=validator_power,json=validatorPower,proto3"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime"` +} +func (m *cbDuplicateVoteEvidence) Reset() { *m = cbDuplicateVoteEvidence{} } +func (m *cbDuplicateVoteEvidence) String() string { return "" } +func (*cbDuplicateVoteEvidence) ProtoMessage() {} + +// cbVote from CometBFT +// Source: cometbft/proto/cometbft/types/v1/types.proto +type cbVote struct { + Type int32 `protobuf:"varint,1,opt,name=type,proto3"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3"` + Round int32 `protobuf:"varint,3,opt,name=round,proto3"` + BlockID *cbBlockID `protobuf:"bytes,4,opt,name=block_id,json=blockId,proto3"` + Timestamp time.Time `protobuf:"bytes,5,opt,name=timestamp,proto3,stdtime"` + ValidatorAddress []byte `protobuf:"bytes,6,opt,name=validator_address,json=validatorAddress,proto3"` + ValidatorIndex int32 `protobuf:"varint,7,opt,name=validator_index,json=validatorIndex,proto3"` + Signature []byte `protobuf:"bytes,8,opt,name=signature,proto3"` +} + + +// ============================================================================= +// Consensus MKVS Format Mappings +// ============================================================================= + +// ConsensusMKVSFormat describes the value format for a consensus MKVS key prefix. +type ConsensusMKVSFormat struct { + Format string // "cbor", "binary", "empty" + Type string // Go type name or description + Description string // Human-readable description +} + +// consensusMKVSFormats maps consensus MKVS key prefixes to their value formats. +// Extracted from _oasis-core/go/consensus/tendermint/apps/*/state/state.go +// This provides deterministic decoding without heuristics. +var consensusMKVSFormats = map[byte]ConsensusMKVSFormat{ + // Registry Module (0x10-0x19) + // See: _oasis-core/go/consensus/tendermint/apps/registry/state/state.go + 0x10: {Format: "cbor", Type: "entity.SignedEntity", Description: "signed entity"}, + 0x11: {Format: "cbor", Type: "node.MultiSignedNode", Description: "signed node"}, + 0x12: {Format: "empty", Type: "index", Description: "node by entity index"}, + 0x13: {Format: "cbor", Type: "registry.Runtime", Description: "runtime"}, + 0x14: {Format: "binary", Type: "signature.PublicKey", Description: "node by consensus address"}, + 0x15: {Format: "cbor", Type: "registry.NodeStatus", Description: "node status"}, + 0x16: {Format: "cbor", Type: "registry.ConsensusParameters", Description: "registry parameters"}, + 0x17: {Format: "binary", Type: "signature.PublicKey", Description: "key map"}, + 0x18: {Format: "cbor", Type: "registry.Runtime", Description: "suspended runtime"}, + 0x19: {Format: "empty", Type: "index", Description: "runtime by entity index"}, + + // Roothash Module (0x20-0x29) + // See: _oasis-core/go/consensus/tendermint/apps/roothash/state/state.go + 0x20: {Format: "cbor", Type: "roothash.RuntimeState", Description: "runtime state"}, + 0x21: {Format: "cbor", Type: "roothash.ConsensusParameters", Description: "roothash parameters"}, + 0x22: {Format: "binary", Type: "common.Namespace", Description: "round timeout queue"}, + 0x24: {Format: "empty", Type: "index", Description: "evidence"}, + 0x25: {Format: "binary", Type: "hash.Hash", Description: "state root"}, + 0x26: {Format: "binary", Type: "hash.Hash", Description: "io root"}, + 0x27: {Format: "cbor", Type: "roothash.RoundResults", Description: "last round results"}, + 0x28: {Format: "cbor", Type: "message.IncomingMessageQueueMeta", Description: "incoming message queue meta"}, + 0x29: {Format: "cbor", Type: "message.IncomingMessage", Description: "incoming message queue"}, + + // Beacon Module (0x40-0x45) + // See: _oasis-core/go/consensus/tendermint/apps/beacon/state/state.go + 0x40: {Format: "cbor", Type: "beacon.EpochTimeState", Description: "epoch current"}, + 0x41: {Format: "cbor", Type: "beacon.EpochTimeState", Description: "epoch future"}, + 0x42: {Format: "binary", Type: "beacon-32bytes", Description: "beacon value"}, + 0x43: {Format: "cbor", Type: "beacon.ConsensusParameters", Description: "beacon parameters"}, + 0x45: {Format: "cbor", Type: "beacon.EpochTime", Description: "pending mock epoch"}, + + // Staking Module (0x50-0x59) + // See: _oasis-core/go/consensus/tendermint/apps/staking/state/state.go + 0x50: {Format: "cbor", Type: "staking.Account", Description: "account"}, + 0x51: {Format: "cbor", Type: "quantity.Quantity", Description: "total supply"}, + 0x52: {Format: "cbor", Type: "quantity.Quantity", Description: "common pool"}, + 0x53: {Format: "cbor", Type: "staking.Delegation", Description: "delegation"}, + 0x54: {Format: "cbor", Type: "staking.DebondingDelegation", Description: "debonding delegation"}, + 0x55: {Format: "empty", Type: "index", Description: "debonding queue"}, + 0x56: {Format: "cbor", Type: "staking.ConsensusParameters", Description: "staking parameters"}, + 0x57: {Format: "cbor", Type: "quantity.Quantity", Description: "last block fees"}, + 0x58: {Format: "cbor", Type: "EpochSigning", Description: "epoch signing"}, + 0x59: {Format: "cbor", Type: "quantity.Quantity", Description: "governance deposits"}, + + // Scheduler Module (0x60-0x63) + // See: _oasis-core/go/consensus/tendermint/apps/scheduler/state/state.go + 0x60: {Format: "cbor", Type: "api.Committee", Description: "committee"}, + 0x61: {Format: "cbor", Type: "map[signature.PublicKey]int64", Description: "current validators"}, + 0x62: {Format: "cbor", Type: "map[signature.PublicKey]int64", Description: "pending validators"}, + 0x63: {Format: "cbor", Type: "api.ConsensusParameters", Description: "scheduler parameters"}, + + // Keymanager Module (0x70) + // See: _oasis-core/go/consensus/tendermint/apps/keymanager/state/state.go + 0x70: {Format: "cbor", Type: "api.Status", Description: "keymanager status"}, + + // Governance Module (0x80-0x85) + // See: _oasis-core/go/consensus/tendermint/apps/governance/state/state.go + 0x80: {Format: "cbor", Type: "uint64", Description: "next proposal identifier"}, + 0x81: {Format: "cbor", Type: "governance.Proposal", Description: "proposals"}, + 0x82: {Format: "empty", Type: "index", Description: "active proposals"}, + 0x83: {Format: "cbor", Type: "governance.Vote", Description: "votes"}, + 0x84: {Format: "empty", Type: "index", Description: "pending upgrades"}, + 0x85: {Format: "cbor", Type: "governance.ConsensusParameters", Description: "governance parameters"}, +} + +// GetConsensusMKVSFormat returns the format metadata for a consensus MKVS key. +// Returns (format, true) if the key prefix is known, (empty, false) otherwise. +func GetConsensusMKVSFormat(key []byte) (ConsensusMKVSFormat, bool) { + if len(key) == 0 { + return ConsensusMKVSFormat{}, false + } + format, exists := consensusMKVSFormats[key[0]] + return format, exists +} diff --git a/badgerdb-analyzer/types_evm.go b/badgerdb-analyzer/types_evm.go new file mode 100644 index 0000000..3c693c9 --- /dev/null +++ b/badgerdb-analyzer/types_evm.go @@ -0,0 +1,63 @@ +package main + +// EVMDataInfo represents decoded EVM storage data. +// See: _oasis-sdk/runtime-sdk/modules/evm/src/state.rs +type EVMDataInfo struct { + RawError string `json:"raw_error,omitempty"` + StorageType string `json:"storage_type"` // "code", "storage", "block_hash", "confidential_storage" + Address string `json:"address,omitempty"` // H160 (20 bytes) - contract address (0x-prefixed hex) + StorageSlot string `json:"storage_slot,omitempty"` // H256 (32 bytes) - storage slot (0x-prefixed hex, truncated) + StorageValue string `json:"storage_value,omitempty"` // H256 (32 bytes) - storage value (0x-prefixed hex) + RuntimeHeight uint64 `json:"runtime_height,omitempty"` // For block_hash type (runtime block height) + BlockHash string `json:"block_hash,omitempty"` // H256 (32 bytes) - block hash (0x-prefixed hex, truncated) + CodeSize int `json:"code_size,omitempty"` // Size of contract bytecode + CodeHex string `json:"code_hex,omitempty"` // Truncated bytecode (hex, no 0x prefix) +} + +// EVMEventInfo represents a decoded EVM Log event. +// See: _oasis-sdk/runtime-sdk/modules/evm/src/lib.rs:253-263 (Event::Log) +// See: _oasis-sdk/client-sdk/go/modules/evm/types.go:56-61 (Event) +type EVMEventInfo struct { + RawError string `json:"raw_error,omitempty"` + Address string `json:"address"` // H160 contract address (0x-prefixed hex) + TopicCount int `json:"topic_count"` // Number of topics (0-4) + Topics []string `json:"topics,omitempty"` // H256 topics array (0x-prefixed hex) + EventSignature string `json:"event_signature,omitempty"` // Human-readable signature (if known) + EventHash string `json:"event_hash,omitempty"` // topic[0] keccak256 hash (0x-prefixed) + DataSize int `json:"data_size"` // Size of non-indexed data + DataHex string `json:"data_hex,omitempty"` // Truncated raw data (0x-prefixed hex) +} + +// EVMTxInputInfo represents decoded EVM transaction input artifacts. +// See: _oasis-core/go/runtime/transaction/transaction.go:126-140 (inputArtifacts) +type EVMTxInputInfo struct { + RawError string `json:"raw_error,omitempty"` + TxHash string `json:"tx_hash"` // Transaction hash (0x-prefixed hex) + BatchOrder uint32 `json:"batch_order"` // Order within batch + Method string `json:"method,omitempty"` // SDK method (e.g., "evm.Call") + EVMTx *EVMTransactionInfo `json:"evm_tx,omitempty"` // Decoded EVM transaction +} + +// EVMTransactionInfo represents a decoded EVM transaction. +type EVMTransactionInfo struct { + RawError string `json:"raw_error,omitempty"` + Type string `json:"type"` // "call" or "create" + From string `json:"from,omitempty"` // 0x prefix + To string `json:"to,omitempty"` // 0x prefix + Value string `json:"value,omitempty"` // Wei as decimal string + GasLimit uint64 `json:"gas_limit,omitempty"` + GasPrice string `json:"gas_price,omitempty"` // Wei as decimal string + Nonce uint64 `json:"nonce,omitempty"` + DataSize int `json:"data_size"` + DataDump string `json:"data_dump,omitempty"` // 0x-hex +} + +// EVMTxOutputInfo represents decoded EVM transaction output artifacts. +// See: _oasis-core/go/runtime/transaction/transaction.go:142-150 (outputArtifacts) +type EVMTxOutputInfo struct { + RawError string `json:"raw_error,omitempty"` + SuccessExecution bool `json:"success"` + ResultSize int `json:"result_size,omitempty"` + ResultDump string `json:"result_dump,omitempty"` + ErrorExecution string `json:"error,omitempty"` // Execution error (application data, not decoding error) +} diff --git a/badgerdb-analyzer/types_runtime.go b/badgerdb-analyzer/types_runtime.go new file mode 100644 index 0000000..90336c9 --- /dev/null +++ b/badgerdb-analyzer/types_runtime.go @@ -0,0 +1,402 @@ +package main + +import ( + "math/big" + + "github.com/fxamacker/cbor/v2" +) + +// ============================================================================= +// Decoded Output Types (structured representations for JSON output) +// ============================================================================= + +// RuntimeMkvsKeyInfo represents a decoded runtime-mkvs key. +// See: _oasis-core/go/storage/mkvs/db/badger/badger.go:31-66 +type RuntimeMkvsKeyInfo struct { + // Raw fields + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + KeyError string `json:"key_error,omitempty"` + + // Decoded fields + KeyType string `json:"key_type"` // "node", "write_log", "roots_metadata", "root_updated_nodes", "metadata", "unknown" + RuntimeHeight uint64 `json:"runtime_height,omitempty"` // For write_log, roots_metadata, root_updated_nodes + Hash string `json:"hash,omitempty"` // For node (hex, truncated) +} + +// RuntimeMkvsValueInfo represents a decoded runtime-mkvs value (node). +// See: _oasis-core/go/storage/mkvs/node/node.go:26-32 (prefixes), 294-309 (InternalNode), 531-537 (LeafNode) +type RuntimeMkvsValueInfo struct { + // Raw fields + RawDump string `json:"raw_dump,omitempty"` + RawSize int `json:"raw_size"` + RawError string `json:"raw_error,omitempty"` + + // Node info + NodeType string `json:"node_type"` // "leaf", "internal", "nil", "non_node", "write_log", "unknown" + Leaf *RuntimeMkvsLeafInfo `json:"leaf,omitempty"` + Internal *RuntimeMkvsInternalInfo `json:"internal,omitempty"` + WriteLog WriteLog `json:"write_log,omitempty"` +} + +// RuntimeMkvsLeafInfo represents a decoded MKVS LeafNode. +// See: _oasis-core/go/storage/mkvs/node/node.go:531-537 +type RuntimeMkvsLeafInfo struct { + // Leaf key (extracted from MKVS node) + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + + // Decoded key fields + Module string `json:"module"` + KeyType string `json:"key_type,omitempty"` + + // Nested value (has its own raw representation) + Value *RuntimeLeafValueInfo `json:"value,omitempty"` +} + +// RuntimeMkvsInternalInfo represents a decoded MKVS InternalNode. +// See: _oasis-core/go/storage/mkvs/node/node.go:294-309 +type RuntimeMkvsInternalInfo struct { + LabelBits uint16 `json:"label_bits"` + HasLeaf bool `json:"has_leaf"` + LeftHash string `json:"left_hash,omitempty"` // hex, truncated + RightHash string `json:"right_hash,omitempty"` // hex, truncated +} + +// WriteLog represents MKVS write log (application-level changes). +// See: _oasis-core/go/storage/mkvs/writelog/writelog.go +type WriteLog []LogEntry + +// LogEntry is a write log entry. +type LogEntry struct { + _ struct{} `cbor:",toarray"` + Key []byte + Value []byte // nil if deleted +} + +// RuntimeHistoryKeyInfo represents a decoded runtime-history key. +// See: _oasis-core/go/runtime/history/db.go:19-31 +type RuntimeHistoryKeyInfo struct { + // Raw fields + KeyDump string `json:"key_dump,omitempty"` + KeySize int `json:"key_size"` + KeyError string `json:"key_error,omitempty"` + + // Decoded fields + KeyType string `json:"key_type"` // "metadata", "block", "round_results", "unknown" + RuntimeHeight uint64 `json:"runtime_height,omitempty"` // For block, round_results (runtime block height) + ExtraData string `json:"extra,omitempty"` // Unexpected extra bytes (hex) +} + +// RuntimeHistoryValueInfo represents a decoded runtime-history value. +type RuntimeHistoryValueInfo struct { + // Raw fields + RawDump string `json:"raw_dump,omitempty"` + RawSize int `json:"raw_size"` + RawError string `json:"raw_error,omitempty"` + + // Decoded content - only ONE populated + Metadata *RuntimeHistoryMetadataInfo `json:"metadata,omitempty"` + Block *RuntimeHistoryBlockInfo `json:"block,omitempty"` + RoundResults *RuntimeHistoryRoundResultsInfo `json:"round_results,omitempty"` +} + +// RuntimeHistoryMetadataInfo represents decoded runtime history metadata. +// See: _oasis-core/go/runtime/history/db.go:34-44 +type RuntimeHistoryMetadataInfo struct { + Version uint64 `json:"version"` + RuntimeID string `json:"runtime_id"` // hex, truncated + LastRuntimeHeight uint64 `json:"last_runtime_height"` // last runtime block height + LastConsensusHeight int64 `json:"last_consensus_height"` // last consensus block height +} + +// RuntimeHistoryBlockInfo represents a decoded runtime history block. +// See: _oasis-core/go/roothash/api/api.go:402-409 (AnnotatedBlock) +// See: _oasis-core/go/roothash/api/block/header.go:69-99 (Header) +type RuntimeHistoryBlockInfo struct { + ConsensusHeight int64 `json:"consensus_height"` // consensus block height + RuntimeHeight uint64 `json:"runtime_height"` // runtime block height (formerly "round") + Timestamp string `json:"timestamp"` // RFC3339 format + HeaderType string `json:"header_type"` + StateRoot string `json:"state_root,omitempty"` // hex, truncated + BlockNil bool `json:"block_nil,omitempty"` // true if block was nil +} + +// RuntimeHistoryRoundResultsInfo represents decoded runtime history round results. +// See: _oasis-core/go/roothash/api/results.go:5-17 +type RuntimeHistoryRoundResultsInfo struct { + MessageCount int `json:"message_count"` + GoodComputeEntities int `json:"good_compute_entities"` + BadComputeEntities int `json:"bad_compute_entities"` +} + +// RuntimeLeafValueInfo represents a decoded MKVS leaf node value. +// See: _oasis-core/go/runtime/transaction/transaction.go:129-150 (artifacts) +type RuntimeLeafValueInfo struct { + // Raw leaf value bytes (use Value* prefix for leaf-specific fields) + ValueDump string `json:"value_dump,omitempty"` + ValueSize int `json:"value_size"` + ValueError string `json:"value_error,omitempty"` + + // Classification + ValueType string `json:"value_type,omitempty"` + + // Decoded content - only ONE populated + CBOR interface{} `json:"cbor,omitempty"` + EVM *EVMDataInfo `json:"evm,omitempty"` + EVMEvent *EVMEventInfo `json:"evm_event,omitempty"` + EVMTxInput *EVMTxInputInfo `json:"evm_tx_input,omitempty"` + EVMTxOutput *EVMTxOutputInfo `json:"evm_tx_output,omitempty"` + RuntimeConsensusEvent *RuntimeConsensusEventInfo `json:"runtime_consensus_accounts_event,omitempty"` +} + +// RuntimeConsensusEventError represents consensus error. +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/types.rs:207-215 +type RuntimeConsensusEventError struct { + Module string `json:"module,omitempty"` + Code uint32 `json:"code,omitempty"` +} + +// RuntimeConsensusEventInfo is decoded consensus_accounts event. +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/mod.rs:105-157 +type RuntimeConsensusEventInfo struct { + EventType string `json:"event_type"` + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` + Nonce uint64 `json:"nonce,omitempty"` + Amount string `json:"amount,omitempty"` + Shares string `json:"shares,omitempty"` + DebondEndTime uint64 `json:"debond_end_time,omitempty"` + Error *RuntimeConsensusEventError `json:"error,omitempty"` +} + +// ============================================================================= +// Runtime CBOR Deserialization Types +// ============================================================================= + +// cborRuntimeHistoryMetadata represents the metadata stored in runtime history DB. +// See: _oasis-core/go/runtime/history/db.go:34-44 (dbMetadata) +type cborRuntimeHistoryMetadata struct { + RuntimeID []byte `cbor:"runtime_id"` + Version uint64 `cbor:"version"` + LastConsensusHeight int64 `cbor:"last_consensus_height"` + LastRound uint64 `cbor:"last_round"` +} + +// cborRuntimeHistoryAnnotatedBlock represents an annotated block in runtime history. +// See: _oasis-core/go/roothash/api/api.go:401-409 (AnnotatedBlock) +type cborRuntimeHistoryAnnotatedBlock struct { + Height int64 `cbor:"consensus_height"` + Block *cborRuntimeHistoryBlock `cbor:"block"` +} + +// cborRuntimeHistoryBlock represents a runtime block. +// See: _oasis-core/go/roothash/api/block/block.go:7-12 (Block) +type cborRuntimeHistoryBlock struct { + Header cborRuntimeHistoryBlockHeader `cbor:"header"` +} + +// cborRuntimeHistoryBlockHeader represents a runtime block header. +// See: _oasis-core/go/roothash/api/block/header.go:69-99 (Header) +type cborRuntimeHistoryBlockHeader struct { + Version uint16 `cbor:"version"` + Namespace []byte `cbor:"namespace"` + Round uint64 `cbor:"round"` + Timestamp uint64 `cbor:"timestamp"` // POSIX time (Unix seconds) + HeaderType uint8 `cbor:"header_type"` + PreviousHash []byte `cbor:"previous_hash"` + IORoot []byte `cbor:"io_root"` + StateRoot []byte `cbor:"state_root"` + MessagesHash []byte `cbor:"messages_hash"` + InMessagesHash []byte `cbor:"in_msgs_hash"` +} + +// cborRuntimeHistoryRoundResults represents round results in runtime history. +// See: _oasis-core/go/roothash/api/results.go:5-17 (RoundResults) +type cborRuntimeHistoryRoundResults struct { + Messages []cborRuntimeHistoryMessageEvent `cbor:"messages,omitempty"` + GoodComputeEntities [][]byte `cbor:"good_compute_entities,omitempty"` + BadComputeEntities [][]byte `cbor:"bad_compute_entities,omitempty"` +} + +// cborRuntimeHistoryMessageEvent represents a message event. +// See: _oasis-core/go/roothash/api/api.go:492-499 (MessageEvent) +type cborRuntimeHistoryMessageEvent struct { + Module string `cbor:"module,omitempty"` + Code uint32 `cbor:"code,omitempty"` + Index uint32 `cbor:"index,omitempty"` + Result cbor.RawMessage `cbor:"result,omitempty"` +} + +// cborRuntimeInputArtifacts represents input transaction artifacts stored in IO tree. +// See: _oasis-core/go/runtime/transaction/transaction.go:129-140 (inputArtifacts) +type cborRuntimeInputArtifacts struct { + _ struct{} `cbor:",toarray"` + Input []byte + BatchOrder uint32 +} + +// cborRuntimeOutputArtifacts represents output transaction artifacts stored in IO tree. +// See: _oasis-core/go/runtime/transaction/transaction.go:145-150 (outputArtifacts) +type cborRuntimeOutputArtifacts struct { + _ struct{} `cbor:",toarray"` + Output []byte +} + +// cborRuntimeCallArrayFormat represents runtime Call structure encoded as CBOR array (old format). +// See: _oasis-sdk/runtime-sdk/src/types/transaction.rs:124-141 (Call) +// Used in older runtime versions before map-based encoding +type cborRuntimeCallArrayFormat struct { + _ struct{} `cbor:",toarray"` + Format uint8 // CallFormat: 0=Plain, 1=EncryptedX25519DeoxysII + Method string // Method name + Body cbor.RawMessage // Method body (CBOR-encoded) + ReadOnly bool // Read-only flag +} + +// cborRuntimeCallMapFormat represents runtime Call structure encoded as CBOR map (new format). +// See: _oasis-sdk/runtime-sdk/src/types/transaction.rs:124-141 (Call) +// Used in newer runtime versions +type cborRuntimeCallMapFormat struct { + Format uint8 `cbor:"format,omitempty"` + Method string `cbor:"method,omitempty"` + Body cbor.RawMessage `cbor:"body"` + ReadOnly bool `cbor:"ro,omitempty"` +} + +// ============================================================================= +// Runtime Event CBOR Deserialization Types +// ============================================================================= + +// RuntimeBaseUnits represents token::BaseUnits encoded as CBOR array [amount_bytes, denomination_bytes]. +// See: _oasis-sdk/runtime-sdk/src/types/token.rs:90 +// pub struct BaseUnits(pub u128, pub Denomination); +type RuntimeBaseUnits struct { + _ struct{} `cbor:",toarray"` + Amount []byte // u128 as big-endian bytes + Denomination []byte // Denomination as bytes (empty for native token) +} + +func (b *RuntimeBaseUnits) String() string { + if len(b.Amount) == 0 { + return "0" + } + return new(big.Int).SetBytes(b.Amount).String() +} + +// RuntimeAddress represents a 21-byte runtime address with bech32 encoding. +// See: _oasis-sdk/runtime-sdk/src/types/address.rs:79-80 +// pub struct Address([u8; ADDRESS_SIZE]); // ADDRESS_SIZE = 21 +// CBOR: Encodes as ByteString +// Display: Bech32 with HRP "oasis" +type RuntimeAddress [21]byte + +func (a RuntimeAddress) String() string { + return bech32Encode("oasis", a[:]) +} + +// cborRuntimeConsensusError represents error details from consensus layer. +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/types.rs:207-215 +type cborRuntimeConsensusError struct { + Module string `cbor:"module,omitempty"` + Code uint32 `cbor:"code,omitempty"` +} + +// Deposit/Withdraw/Delegate events (codes 1-3). +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/mod.rs:109-137 +type cborRuntimeConsensusTransferEvent struct { + _ struct{} `cbor:",toarray"` + From RuntimeAddress + Nonce uint64 + To RuntimeAddress + Amount RuntimeBaseUnits + Error *cborRuntimeConsensusError `cbor:"error,omitempty"` +} + +// UndelegateStart event (code 4). +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/mod.rs:139-148 +type cborRuntimeConsensusUndelegateStartEvent struct { + _ struct{} `cbor:",toarray"` + From RuntimeAddress + Nonce uint64 + To RuntimeAddress + Shares []byte + DebondEndTime uint64 + Error *cborRuntimeConsensusError `cbor:"error,omitempty"` +} + +// UndelegateDone event (code 5). +// See: _oasis-sdk/runtime-sdk/src/modules/consensus_accounts/mod.rs:150-156 +type cborRuntimeConsensusUndelegateDoneEvent struct { + _ struct{} `cbor:",toarray"` + From RuntimeAddress + To RuntimeAddress + Shares []byte + Amount RuntimeBaseUnits +} + + +// ============================================================================= +// Runtime Module State Format Mappings +// ============================================================================= + +// RuntimeModuleStateFormat describes runtime module state value formats. +type RuntimeModuleStateFormat struct { + Format string // "cbor", "binary", "wasm" + Type string // Type name or description + Description string +} + +// runtimeModuleStateFormats maps module state keys to expected formats. +// Extracted from _oasis-sdk/runtime-sdk/modules/*/src/state.rs +var runtimeModuleStateFormats = map[string]map[byte]RuntimeModuleStateFormat{ + "evm": { + // _oasis-sdk/runtime-sdk/modules/evm/src/state.rs:10 + 0x01: {Format: "binary", Type: "Vec", Description: "contract code"}, + // _oasis-sdk/runtime-sdk/modules/evm/src/state.rs:12 + 0x02: {Format: "binary", Type: "H256", Description: "storage slot"}, + // _oasis-sdk/runtime-sdk/modules/evm/src/state.rs:14 + 0x03: {Format: "binary", Type: "H256", Description: "block hash"}, + // _oasis-sdk/runtime-sdk/modules/evm/src/state.rs:17 + 0x04: {Format: "binary", Type: "ConfidentialValue", Description: "confidential storage"}, + }, + "accounts": { + // _oasis-sdk/runtime-sdk/modules/accounts/src/state.rs + 0x01: {Format: "cbor", Type: "types.AccountInfo", Description: "account"}, + 0x02: {Format: "cbor", Type: "types.AccountBalances", Description: "balances"}, + 0x03: {Format: "cbor", Type: "Quantity", Description: "total supply"}, + }, + "contracts": { + // _oasis-sdk/runtime-sdk/modules/contracts/src/state.rs + 0x01: {Format: "cbor", Type: "u64", Description: "next code ID"}, + 0x02: {Format: "cbor", Type: "u64", Description: "next instance ID"}, + 0x03: {Format: "cbor", Type: "types.Code", Description: "code info"}, + 0x04: {Format: "cbor", Type: "types.Instance", Description: "instance info"}, + 0x05: {Format: "cbor", Type: "store.Value", Description: "instance state"}, + 0xFF: {Format: "wasm", Type: "Vec", Description: "WASM bytecode"}, + }, + "core": { + // _oasis-sdk/runtime-sdk/src/modules/core/state.rs + 0x01: {Format: "cbor", Type: "types.Metadata", Description: "metadata"}, + 0x02: {Format: "cbor", Type: "MessageHandlers", Description: "message handlers"}, + 0x03: {Format: "cbor", Type: "EpochTime", Description: "last epoch"}, + 0x04: {Format: "cbor", Type: "Quantity", Description: "min gas price"}, + }, + "consensus_accounts": { + // _oasis-sdk/runtime-sdk/modules/consensus_accounts/src/state.rs + 0x01: {Format: "cbor", Type: "types.Delegation", Description: "delegations"}, + 0x02: {Format: "cbor", Type: "types.UndelegationReceipt", Description: "undelegations"}, + 0x03: {Format: "cbor", Type: "QueueEntry", Description: "undelegation queue"}, + 0x04: {Format: "cbor", Type: "types.Receipt", Description: "receipts"}, + }, +} + +// GetRuntimeModuleStateFormat returns format for a module state key. +func GetRuntimeModuleStateFormat(module string, subPrefix byte) (RuntimeModuleStateFormat, bool) { + if moduleFmts, exists := runtimeModuleStateFormats[module]; exists { + if format, exists := moduleFmts[subPrefix]; exists { + return format, true + } + } + return RuntimeModuleStateFormat{}, false +} diff --git a/badgerdb-analyzer/utils.go b/badgerdb-analyzer/utils.go new file mode 100644 index 0000000..d4ff88a --- /dev/null +++ b/badgerdb-analyzer/utils.go @@ -0,0 +1,611 @@ +package main + +import ( + "encoding/hex" + "fmt" + "math/big" + "reflect" + "strings" + "unicode" +) + +const ( + // TruncateHashLen is the default hex character limit for hash truncation + TruncateHashLen = 16 + // TruncateLongLen is the default hex character limit for long data truncation + TruncateLongLen = 1000 +) + +// decodeRuntimeModuleKey decodes runtime state key to "module:subtype" description. +// Runtime keys use raw ASCII module names (never CBOR-encoded). +// Format: [module_name_bytes] + [0x01-0xFF subprefix] + [data] +// Special prefixes: 'T' (transaction artifacts), 'E' (events) +func decodeRuntimeModuleKey(key []byte) string { + if len(key) == 0 { + return "" + } + + // Skip leading 0x00 from MKVS leaf encoding if present + if key[0] == 0x00 && len(key) > 1 { + key = key[1:] + } + + // Handle IO tree prefixes + switch key[0] { + case 'T': // Transaction artifact (0x54) + if len(key) >= 34 { + kind := key[33] + switch kind { + case 1: + return "io_tx:input" + case 2: + return "io_tx:output" + default: + return "io_tx:unknown" + } + } + return "io_tx" + + case 'E': // Event tag (0x45) + if len(key) > 33 { + // Extract module name from event tag + for i, b := range key[1 : len(key)-32] { + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_') { + if i > 0 { + return fmt.Sprintf("io_event:%s", string(key[1:1+i])) + } + break + } + } + } + return "io_event" + } + + // Extract raw ASCII module name + end := 0 + for i, b := range key { + if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' { + end = i + 1 + } else { + break + } + } + + if end == 0 { + return fmt.Sprintf("unknown_%02x", key[0]) + } + + moduleName := string(key[:end]) + if end >= len(key) { + return moduleName + } + + // Add subtype description based on module and subprefix + subPrefix := key[end] + var subType string + switch moduleName { + case "evm": + switch subPrefix { + case 0x01: + subType = "code" + case 0x02: + subType = "storage" + case 0x03: + subType = "block_hash" + case 0x04: + subType = "confidential_storage" + } + case "accounts": + switch subPrefix { + case 0x01: + subType = "account" + case 0x02: + subType = "balance" + case 0x03: + subType = "total_supply" + } + case "contracts": + switch subPrefix { + case 0x01: + subType = "next_code_id" + case 0x02: + subType = "next_instance_id" + case 0x03: + subType = "code_info" + case 0x04: + subType = "instance_info" + case 0x05: + subType = "instance_state" + case 0xFF: + subType = "code" + } + case "core": + switch subPrefix { + case 0x01: + subType = "metadata" + case 0x02: + subType = "message_handlers" + case 0x03: + subType = "last_epoch" + case 0x04: + subType = "dynamic_min_gas_price" + } + case "consensus_accounts": + switch subPrefix { + case 0x01: + subType = "delegations" + case 0x02: + subType = "undelegations" + case 0x03: + subType = "undelegation_queue" + case 0x04: + subType = "receipts" + } + } + + if subType != "" { + return fmt.Sprintf("%s:%s", moduleName, subType) + } + return moduleName +} + +// decodeConsensusModuleKey decodes consensus state key prefix to "module/subtype" description. +// Consensus keys use numeric byte prefixes (never ASCII module names). +// +// Prefix ranges based on Oasis Core v22.2.13: +// 0x10-0x19: Registry module +// 0x20-0x29: Roothash module +// 0x40-0x46: Beacon module +// 0x50-0x59: Staking module +// 0x60-0x63: Scheduler module +// 0x70: Keymanager module +// 0x80-0x85: Governance module +// 0xF1: Consensus parameters +// +// Source files in Oasis Core repository: +// go/consensus/tendermint/apps/registry/state/state.go (0x10-0x19) +// go/consensus/tendermint/apps/roothash/state/state.go (0x20-0x29) +// go/consensus/tendermint/apps/beacon/state/state.go (0x40-0x43, 0x45) +// go/consensus/tendermint/apps/beacon/state/state_vrf.go (0x46) +// go/consensus/tendermint/apps/staking/state/state.go (0x50-0x59) +// go/consensus/tendermint/apps/scheduler/state/state.go (0x60-0x63) +// go/consensus/tendermint/apps/keymanager/state/state.go (0x70) +// go/consensus/tendermint/apps/governance/state/state.go (0x80-0x85) +// go/consensus/tendermint/abci/state/state.go (0xF1) +// +func decodeConsensusModuleKey(key []byte) string { + if len(key) == 0 { + return "" + } + + switch key[0] { + // Registry module (0x10-0x19) + // Source: _oasis-core/go/consensus/tendermint/apps/registry/state/state.go + case 0x10: + return "registry/entities" + case 0x11: + return "registry/nodes" + case 0x12: + return "registry/node_by_entity" + case 0x13: + return "registry/runtimes" + case 0x14: + return "registry/node_by_consensus_address" + case 0x15: + return "registry/node_status" + case 0x16: + return "registry/params" + case 0x17: + return "registry/key_map" + case 0x18: + return "registry/suspended_runtimes" + case 0x19: + return "registry/runtime_by_entity" + + // Roothash module (0x20-0x29) + // Source: _oasis-core/go/consensus/tendermint/apps/roothash/state/state.go + case 0x20: + return "roothash/runtime_state" + case 0x21: + return "roothash/params" + case 0x22: + return "roothash/round_timeout" + case 0x24: + return "roothash/evidence" + case 0x25: + return "roothash/state_root" + case 0x26: + return "roothash/io_root" + case 0x27: + return "roothash/last_round_results" + case 0x28: + return "roothash/incoming_msg_queue_meta" + case 0x29: + return "roothash/incoming_msg_queue" + + // Beacon module (0x40-0x46) + // Source: _oasis-core/go/consensus/tendermint/apps/beacon/state/*.go + case 0x40: + return "beacon/epoch_current" + case 0x41: + return "beacon/epoch_future" + case 0x42: + return "beacon/beacon" + case 0x43: + return "beacon/params" + case 0x44: + return "beacon/pvss_state_deprecated" + case 0x45: + return "beacon/epoch_pending_mock" + case 0x46: + return "beacon/vrf_state" + + // Staking module (0x50-0x59) + // Source: _oasis-core/go/consensus/tendermint/apps/staking/state/state.go + case 0x50: + return "staking/accounts" + case 0x51: + return "staking/total_supply" + case 0x52: + return "staking/common_pool" + case 0x53: + return "staking/delegations" + case 0x54: + return "staking/debonding_delegations" + case 0x55: + return "staking/debonding_queue" + case 0x56: + return "staking/params" + case 0x57: + return "staking/last_block_fees" + case 0x58: + return "staking/epoch_signing" + case 0x59: + return "staking/governance_deposits" + + // Scheduler module (0x60-0x63) + // Source: _oasis-core/go/consensus/tendermint/apps/scheduler/state/state.go + case 0x60: + return "scheduler/committees" + case 0x61: + return "scheduler/validators_current" + case 0x62: + return "scheduler/validators_pending" + case 0x63: + return "scheduler/params" + + // Keymanager module (0x70) + // Source: _oasis-core/go/consensus/tendermint/apps/keymanager/state/state.go + case 0x70: + return "keymanager/status" + + // Governance module (0x80-0x85) + // Source: _oasis-core/go/consensus/tendermint/apps/governance/state/state.go + case 0x80: + return "governance/next_proposal_id" + case 0x81: + return "governance/proposals" + case 0x82: + return "governance/active_proposals" + case 0x83: + return "governance/votes" + case 0x84: + return "governance/pending_upgrades" + case 0x85: + return "governance/params" + + // Consensus parameters (0xF1) + // Source: _oasis-core/go/consensus/tendermint/abci/state/state.go + case 0xF1: + return "consensus/params" + + default: + return fmt.Sprintf("unknown:0x%02x", key[0]) + } +} + +// formatCBOR formats decoded CBOR value concisely +func formatCBOR(v interface{}, rawLen int) string { + switch val := v.(type) { + case map[interface{}]interface{}: + keys := make([]string, 0, len(val)) + for k := range val { + keys = append(keys, fmt.Sprintf("%v", k)) + } + if len(keys) > 3 { + return fmt.Sprintf("map{%s, +%d}", strings.Join(keys[:3], ","), len(keys)-3) + } + return fmt.Sprintf("map{%s}", strings.Join(keys, ",")) + case []interface{}: + return fmt.Sprintf("array[%d]", len(val)) + case []byte: + return fmt.Sprintf("bytes(%d)", len(val)) + case string: + if len(val) > 20 { + return fmt.Sprintf("%q...", val[:20]) + } + return fmt.Sprintf("%q", val) + case uint64: + return fmt.Sprintf("%d", val) + case int64: + return fmt.Sprintf("%d", val) + case bool: + return fmt.Sprintf("%v", val) + case nil: + return "null" + default: + return fmt.Sprintf("%T(%d bytes)", v, rawLen) + } +} + +// formatCBORDetailed formats decoded CBOR value with detailed structure +func formatCBORDetailed(v interface{}) interface{} { + switch val := v.(type) { + case map[interface{}]interface{}: + result := make(map[string]interface{}) + for k, v := range val { + keyStr := fmt.Sprintf("%v", k) + result[keyStr] = formatCBORDetailed(v) + } + return result + case []interface{}: + result := make([]interface{}, len(val)) + for i, elem := range val { + result[i] = formatCBORDetailed(elem) + } + return result + case []byte: + // Format as hex string with size info + if len(val) <= 32 { + return truncateHex0x(val, 64) + } + return truncateHex0x(val, 64) + fmt.Sprintf(" (%d bytes)", len(val)) + case string: + return val + case uint64: + return val + case int64: + return val + case bool: + return val + case nil: + return nil + default: + return fmt.Sprintf("%v", val) + } +} + +// isPrintableASCII checks if a string contains only printable ASCII characters +func isPrintableASCII(s string) bool { + if len(s) == 0 { + return false + } + for _, r := range s { + if r < 32 || r > 126 { + return false + } + } + return true +} + +// formatRawValue formats data as ASCII or 0x-prefixed hex with truncation +// Returns ASCII string if printable, otherwise 0x-prefixed hex +func formatRawValue(data []byte, maxLen int) string { + if isPrintableASCII(string(data)) { + if len(data) > maxLen { + return string(data[:maxLen]) + "..." + } + return string(data) + } + // Hex formatting with 0x prefix + hexStr := hex.EncodeToString(data) + if len(hexStr) > maxLen { + return "0x" + hexStr[:maxLen] + "..." + } + return "0x" + hexStr +} + +// truncateHex converts bytes to hex and truncates if too long. +func truncateHex(data []byte, maxLen int) string { + hex := fmt.Sprintf("%x", data) + if len(hex) > maxLen { + return hex[:maxLen] + "..." + } + return hex +} + +// truncateHex0x converts bytes to 0x-prefixed hex and truncates if too long. +func truncateHex0x(data []byte, maxLen int) string { + hex := fmt.Sprintf("%x", data) + if len(hex) > maxLen { + return "0x" + hex[:maxLen] + "..." + } + return "0x" + hex +} + +// formatU256 converts big-endian U256 bytes to decimal string. +func formatU256(b []byte) string { + if len(b) == 0 { + return "0" + } + n := new(big.Int).SetBytes(b) + return n.String() +} + +// bech32Charset is the character set for bech32 encoding +const bech32Charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +// bech32Encode encodes data with bech32 using the given human-readable part +func bech32Encode(hrp string, data []byte) string { + // Convert 8-bit data to 5-bit groups + var values []int + acc := 0 + bits := 0 + for _, b := range data { + acc = (acc << 8) | int(b) + bits += 8 + for bits >= 5 { + bits -= 5 + values = append(values, (acc>>bits)&31) + } + } + if bits > 0 { + values = append(values, (acc<<(5-bits))&31) + } + + // Create checksum + checksum := bech32CreateChecksum(hrp, values) + + // Build result efficiently using strings.Builder + var result strings.Builder + result.Grow(len(hrp) + 1 + len(values) + len(checksum)) + result.WriteString(hrp) + result.WriteString("1") + for _, v := range values { + result.WriteByte(bech32Charset[v]) + } + for _, v := range checksum { + result.WriteByte(bech32Charset[v]) + } + return result.String() +} + +// bech32Polymod computes the bech32 checksum polynomial +func bech32Polymod(values []int) int { + gen := []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + chk := 1 + for _, v := range values { + b := chk >> 25 + chk = ((chk & 0x1ffffff) << 5) ^ v + for i := 0; i < 5; i++ { + if (b>>i)&1 == 1 { + chk ^= gen[i] + } + } + } + return chk +} + +// bech32HRPExpand expands the human-readable part for checksum calculation +func bech32HRPExpand(hrp string) []int { + result := make([]int, 0, len(hrp)*2+1) + for _, c := range hrp { + result = append(result, int(c)>>5) + } + result = append(result, 0) + for _, c := range hrp { + result = append(result, int(c)&31) + } + return result +} + +// bech32CreateChecksum creates the bech32 checksum +func bech32CreateChecksum(hrp string, data []int) []int { + values := append(bech32HRPExpand(hrp), data...) + values = append(values, 0, 0, 0, 0, 0, 0) + polymod := bech32Polymod(values) ^ 1 + checksum := make([]int, 6) + for i := 0; i < 6; i++ { + checksum[i] = (polymod >> (5 * (5 - i))) & 31 + } + return checksum +} + +// extractErrors recursively extracts all non-empty fields ending with "Error" from a value. +func extractErrors(v interface{}, prefix string, maxDepth int) []string { + if maxDepth <= 0 || v == nil { + return []string{} + } + + val := reflect.ValueOf(v) + if !val.IsValid() { + return []string{} + } + + // Dereference pointers + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return []string{} + } + val = val.Elem() + } + + results := []string{} + switch val.Kind() { + case reflect.Struct: + typ := val.Type() + for i := 0; i < val.NumField(); i++ { + field, fieldType := val.Field(i), typ.Field(i) + if !field.CanInterface() { + continue + } + jsonName := getJSONFieldName(fieldType) + // Collect error if field ends with "Error" and is non-empty string + if strings.HasSuffix(fieldType.Name, "Error") && field.Kind() == reflect.String { + if errStr := field.String(); errStr != "" { + results = append(results, fmt.Sprintf("%s.%s: %s", prefix, jsonName, errStr)) + } + } + results = append(results, extractErrors(field.Interface(), prefix+"."+jsonName, maxDepth-1)...) + } + case reflect.Interface: + if !val.IsNil() { + results = extractErrors(val.Interface(), prefix, maxDepth-1) + } + case reflect.Slice, reflect.Array: + for i := 0; i < val.Len(); i++ { + if elem := val.Index(i); elem.CanInterface() { + results = append(results, extractErrors(elem.Interface(), prefix+"[]", maxDepth-1)...) + } + } + } + return results +} + +// getJSONFieldName extracts JSON field name from struct tag, falls back to snake_case +func getJSONFieldName(field reflect.StructField) string { + if tag := field.Tag.Get("json"); tag != "" { + if name := strings.Split(tag, ",")[0]; name != "" { + return name + } + } + return toSnakeCase(field.Name) +} + +// toSnakeCase converts PascalCase to snake_case +func toSnakeCase(s string) string { + var result strings.Builder + for i, r := range s { + if i > 0 && unicode.IsUpper(r) { + result.WriteRune('_') + } + result.WriteRune(unicode.ToLower(r)) + } + return result.String() +} + +// formatApproxSize returns approximate size string for error messages. +// Rounds to first significant digit: 23→~20, 234→~200, 2345→~2K, 23456→~20K +func formatApproxSize(size int) string { + if size < 10 { + return fmt.Sprintf("~%d", size) + } else if size < 100 { + return fmt.Sprintf("~%d", (size/10)*10) + } else if size < 1000 { + return fmt.Sprintf("~%d", (size/100)*100) + } else if size < 10000 { + return fmt.Sprintf("~%dK", size/1000) + } else if size < 100000 { + return fmt.Sprintf("~%dK", (size/10000)*10) + } else if size < 1000000 { + return fmt.Sprintf("~%dK", (size/100000)*100) + } else if size < 10000000 { + return fmt.Sprintf("~%dM", size/1000000) + } else if size < 100000000 { + return fmt.Sprintf("~%dM", (size/10000000)*10) + } else if size < 1000000000 { + return fmt.Sprintf("~%dM", (size/100000000)*100) + } else { + return fmt.Sprintf("~%dG", size/1000000000) + } +}