mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
cmd: delete six unshipped tools, make the shipped one honest and tested
Seven main packages sat at 0% coverage. Only cmd/consensus reaches a user: .hanzo/workflows/release.yml builds it into the release artifact. The other six were built by a Makefile CI never invokes, or by nothing at all, and nothing in or outside the module imports them. They did not merely go unused, they misreported. bench printed a 30k TPS figure that was the throughput of a failed map lookup, its chain half unable to start at all. checker exited 0 announcing "all health checks passed" while asserting nothing. sim drew from rand and formatted it like a quorum. server asserted its engine to an interface the engine does not implement, so health was a hardcoded true, and it was in no build recipe. conformance printed a 1-of-3 accept as normal. params duplicated cmd/consensus. cmd/consensus keeps only what it can prove. The engine "test" and "health" actions started an engine, printed a check mark and asserted nothing, so they would pass against a Start that does nothing; they are gone along with the stale "not yet implemented" arms for constructors that do exist. What remains is the parameter report, absorbing the -json flag from params. An unknown -network no longer falls back to mainnet. A typo used to print mainnet's K=21 while the operator read it as the network they asked for. Its old test never called a single symbol from main.go: it reimplemented a CLI in the test file, parsed flags main.go does not have, and passed with main.go deleted. Replaced with tests pinned to config, each verified to fail under a deliberate mutation of the code it covers. The .gitignore build-artifact patterns were unanchored, so `consensus` and `bench` matched the cmd/consensus and bench source directories and silently untracked new files in both. Anchored to the root artifacts they meant. Also drops the Makefile and scripts that drove the deleted binaries, the zmq-bench target chain building a cmd/ package absent for many commits, and the E2E server test that can now only ever skip. cmd/consensus: 0.0% -> 100.0% of statements, 1302 lines deleted. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+6
-6
@@ -35,14 +35,14 @@ go.work.sum
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Build artifacts
|
||||
# Build artifacts. Anchored: an unanchored `consensus` or `bench` also matches
|
||||
# the cmd/consensus and bench source directories, so new files there were
|
||||
# silently untracked. `make build` writes into bin/; release.yml writes
|
||||
# consensus-<os>-<arch> here.
|
||||
build/
|
||||
dist/
|
||||
consensus
|
||||
bench
|
||||
checker
|
||||
params
|
||||
sim
|
||||
/consensus
|
||||
/consensus-*
|
||||
|
||||
# Coverage
|
||||
coverage.html
|
||||
|
||||
@@ -2,29 +2,21 @@
|
||||
# See the file LICENSE for licensing terms.
|
||||
|
||||
.PHONY: all test build clean lint format check tools help benchmark \
|
||||
generate generate-mocks coverage coverage-html coverage-95 \
|
||||
install-tools remove-generated examples examples-go examples-c examples-cpp examples-rust
|
||||
coverage coverage-html coverage-95 \
|
||||
install-tools examples examples-go examples-c examples-cpp examples-rust
|
||||
|
||||
# Default target
|
||||
all: build test
|
||||
|
||||
# === BUILD TARGETS ===
|
||||
|
||||
# Build all tools and commands
|
||||
# Build the commands. A failure here fails the build: swallowing it with
|
||||
# `|| echo` is how this target came to report success while building nothing.
|
||||
build: ## Build all tools and commands
|
||||
@echo "🔨 Building all tools..."
|
||||
@echo "Building params..."
|
||||
@go build -o bin/params ./cmd/params || echo "ERROR: Failed to build params"
|
||||
@echo "Building checker..."
|
||||
@go build -o bin/checker ./cmd/checker || echo "ERROR: Failed to build checker"
|
||||
@echo "Building simulator..."
|
||||
@go build -o bin/sim ./cmd/sim || echo "ERROR: Failed to build sim"
|
||||
@echo "Building bench..."
|
||||
@go build -tags zmq -o bin/bench ./cmd/bench 2>/dev/null || go build -o bin/bench ./cmd/bench || echo "WARNING: Building without ZMQ support"
|
||||
@echo "Building consensus CLI..."
|
||||
@go build -o bin/consensus ./cmd/consensus || echo "ERROR: Failed to build consensus CLI"
|
||||
@echo "✅ Build complete! Tools built:"
|
||||
@ls -1 bin/ 2>/dev/null | grep -v '^$$' || echo "No tools built"
|
||||
@echo "🔨 Building..."
|
||||
@go build -o bin/consensus ./cmd/consensus
|
||||
@echo "✅ Built:"
|
||||
@ls -1 bin/
|
||||
|
||||
# === TEST TARGETS ===
|
||||
|
||||
@@ -108,27 +100,6 @@ benchmark: ## Run pure algorithm benchmarks without networking
|
||||
@echo "🚀 Running pure consensus benchmarks..."
|
||||
@go test -bench=. -benchmem ./config ./protocol/... ./engine/... ./photon ./core/... ./qzmq
|
||||
|
||||
# Build zmq-bench tool
|
||||
zmq-bench: ## Build the ZMQ benchmark tool
|
||||
@echo "🔨 Building zmq-bench tool..."
|
||||
@go build -tags zmq -o bin/zmq-bench ./cmd/zmq-bench || echo "ERROR: zmq-bench requires ZMQ"
|
||||
|
||||
# Run zmq-bench tool
|
||||
run-zmq-bench: zmq-bench ## Run ZMQ benchmark tool with configurable parameters
|
||||
@echo "🚀 Running benchmark..."
|
||||
@echo " Nodes: $(NODES)"
|
||||
@echo " Batch: $(BATCH)"
|
||||
@echo " Interval: $(INTERVAL)"
|
||||
@echo " Rounds: $(ROUNDS)"
|
||||
@echo ""
|
||||
./bin/zmq-bench -nodes $(NODES) -batch $(BATCH) -interval $(INTERVAL) -rounds $(ROUNDS)
|
||||
|
||||
# Configuration for benchmarks
|
||||
NODES ?= 10
|
||||
BATCH ?= 4096
|
||||
INTERVAL ?= 5ms
|
||||
ROUNDS ?= 100
|
||||
|
||||
# Run massively parallel ZMQ benchmarks with Ginkgo
|
||||
benchmark-zmq: check-ginkgo ## Run ZeroMQ transport benchmarks
|
||||
@echo "🌐 Running transport benchmarks..."
|
||||
@@ -139,53 +110,6 @@ test-parallel: check-ginkgo ## Run tests in parallel with Ginkgo
|
||||
@echo "⚡ Running tests in parallel..."
|
||||
ginkgo -p ./...
|
||||
|
||||
# Run CI benchmark suite (10, 100, 1000 nodes)
|
||||
ci-cluster: zmq-bench ## Run CI multi-node consensus benchmarks (10, 100, 1000 nodes)
|
||||
@echo "🌟 Running CI benchmark suite..."
|
||||
@echo ""
|
||||
@echo "### 10 nodes (default)"
|
||||
@./bin/zmq-bench -nodes 10 -batch 8192 -interval 1ms -rounds 500 -quiet
|
||||
@echo ""
|
||||
@echo "### 100 nodes"
|
||||
@./bin/zmq-bench -nodes 100 -batch 8192 -interval 1ms -rounds 100 -quiet
|
||||
@echo ""
|
||||
@echo "### 500 nodes"
|
||||
@./bin/zmq-bench -nodes 1000 -batch 8192 -interval 1ms -rounds 50 -quiet
|
||||
|
||||
# Run quick benchmark
|
||||
benchmark-quick: zmq-bench ## Quick benchmark with 10 nodes
|
||||
@./bin/zmq-bench -nodes 10 -batch 8192 -interval 1ms -rounds 20
|
||||
|
||||
# Run maximum TPS benchmark
|
||||
benchmark-max-tps: zmq-bench ## Run benchmark optimized for maximum TPS
|
||||
@echo "🚀 Running maximum TPS benchmark..."
|
||||
@CPU_COUNT=$$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4); \
|
||||
echo " CPU cores: $$CPU_COUNT"; \
|
||||
echo " Nodes: $$((CPU_COUNT * 2))"; \
|
||||
echo " Batch: 16384"; \
|
||||
echo " Interval: 1ms"; \
|
||||
echo ""; \
|
||||
./bin/zmq-bench -nodes $$((CPU_COUNT * 2)) -batch 16384 -interval 1ms -rounds 100
|
||||
|
||||
# === CODE GENERATION TARGETS ===
|
||||
|
||||
# Generate all code (mocks)
|
||||
generate: generate-mocks ## Generate all code (mocks)
|
||||
@echo "✅ All code generation complete"
|
||||
|
||||
# Generate mock files
|
||||
generate-mocks: check-mockgen ## Generate mock files
|
||||
@echo "🔧 Generating mocks..."
|
||||
@go generate ./...
|
||||
@echo "✅ Mocks generated"
|
||||
|
||||
# Remove all generated files
|
||||
remove-generated: ## Remove all generated files (*_mock.go, mock_*.go)
|
||||
@echo "🧹 Removing generated files..."
|
||||
@find . -name "*_mock.go" -type f -delete
|
||||
@find . -name "mock_*.go" -type f -delete
|
||||
@echo "✅ Generated files removed"
|
||||
|
||||
# === CODE QUALITY TARGETS ===
|
||||
|
||||
# Run linters
|
||||
@@ -258,7 +182,6 @@ install-tools: ## Install development tools
|
||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
@go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
@go install golang.org/x/tools/cmd/goimports@latest
|
||||
@go install github.com/golang/mock/mockgen@latest
|
||||
@go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
@$(MAKE) check-ginkgo
|
||||
@echo "✅ Development tools installed"
|
||||
@@ -296,10 +219,6 @@ clean: ## Clean build artifacts
|
||||
@cd pkg/rust && cargo clean 2>/dev/null || true
|
||||
@echo "✅ Clean complete"
|
||||
|
||||
# Clean everything including generated files
|
||||
clean-all: clean remove-generated ## Clean everything including generated files
|
||||
@echo "✅ Complete clean finished"
|
||||
|
||||
# === PAPER TARGETS ===
|
||||
|
||||
# Build the PDF white paper
|
||||
@@ -340,12 +259,6 @@ docs-clean: ## Clean documentation build artifacts
|
||||
@cd docs && rm -rf .next out node_modules
|
||||
@echo "✅ Documentation cleaned"
|
||||
|
||||
# === UTILITY TARGETS ===
|
||||
|
||||
# Run params tool
|
||||
run-params: build ## Build and run params tool
|
||||
@./bin/params
|
||||
|
||||
# Show comprehensive help
|
||||
help: ## Show this help
|
||||
@echo "🔧 Lux Consensus Makefile"
|
||||
@@ -354,7 +267,7 @@ help: ## Show this help
|
||||
@echo ""
|
||||
@echo "📋 Main Targets:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
grep -E '^(all|build|test|coverage-95|generate|clean|help):' | \
|
||||
grep -E '^(all|build|test|coverage-95|clean|help):' | \
|
||||
sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
@echo "🧪 Test Targets:"
|
||||
@@ -367,11 +280,6 @@ help: ## Show this help
|
||||
grep -E '^(bench|benchmark)' | \
|
||||
sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
@echo "🔧 Code Generation:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
grep -E '^generate' | \
|
||||
sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
@echo "🔍 Code Quality:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
grep -E '^(lint|format|security|pre-commit)' | \
|
||||
@@ -381,11 +289,8 @@ help: ## Show this help
|
||||
@echo " # Achieve 95% test coverage"
|
||||
@echo " make coverage-95"
|
||||
@echo ""
|
||||
@echo " # Generate all code and run tests"
|
||||
@echo " make generate test"
|
||||
@echo ""
|
||||
@echo " # Clean and rebuild everything"
|
||||
@echo " make clean-all generate build test"
|
||||
@echo " make clean build test"
|
||||
@echo ""
|
||||
@echo " # Build integration examples"
|
||||
@echo " make examples"
|
||||
@@ -396,10 +301,6 @@ help: ## Show this help
|
||||
check-ginkgo:
|
||||
@which ginkgo > /dev/null || (echo "📦 Installing Ginkgo..."; go install github.com/onsi/ginkgo/v2/ginkgo@latest)
|
||||
|
||||
# Check if mockgen is installed
|
||||
check-mockgen:
|
||||
@which mockgen > /dev/null || (echo "📦 Installing mockgen..."; go install github.com/golang/mock/mockgen@latest)
|
||||
|
||||
# Check if LaTeX is installed
|
||||
check-latex:
|
||||
@which pdflatex > /dev/null || (echo "❌ pdflatex not found. Install LaTeX (e.g., brew install --cask mactex)"; exit 1)
|
||||
|
||||
+54
-259
@@ -1,285 +1,80 @@
|
||||
# Lux Consensus Testing Guide
|
||||
# Testing
|
||||
|
||||
## Quick Start
|
||||
Always `GOWORK=off` in this repo.
|
||||
|
||||
## The suite
|
||||
|
||||
```bash
|
||||
# Build everything
|
||||
make build
|
||||
|
||||
# Run quick test
|
||||
./bin/consensus -action test
|
||||
|
||||
# Run simulation
|
||||
./bin/sim -nodes 5 -rounds 10
|
||||
GOWORK=off go test ./... # everything
|
||||
GOWORK=off go test -race ./... # what CI runs
|
||||
GOWORK=off go test -run TestName ./config # one test
|
||||
```
|
||||
|
||||
## Manual Testing with cURL
|
||||
`protocol/quasar` is the long pole: ~11600 SLH-DSA tests, which the race
|
||||
detector multiplies by roughly 22. Give it `-timeout=40m` under `-race`, or it
|
||||
gets killed mid-run and reports FAIL at exactly the timeout.
|
||||
|
||||
## Coverage
|
||||
|
||||
### 1. Start the test server
|
||||
```bash
|
||||
./bin/server -port 9090 -network local &
|
||||
GOWORK=off go test -coverprofile=coverage.out ./...
|
||||
GOWORK=off go tool cover -func=coverage.out # per function
|
||||
GOWORK=off go tool cover -html=coverage.out # annotated source
|
||||
```
|
||||
|
||||
### 2. Test endpoints
|
||||
`make coverage-95` fails the build under 95%.
|
||||
|
||||
A coverage number only means something if the tests can fail. Before trusting
|
||||
one, break the code on purpose and confirm the test that covers it goes red —
|
||||
a test that passes against a mutilated implementation is measuring nothing.
|
||||
|
||||
## Integration
|
||||
|
||||
`test/integration` drives the engines through the public API. Nothing there
|
||||
needs a server or a fixture process; `go test ./test/integration` is the whole
|
||||
story.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
#### Health Check
|
||||
```bash
|
||||
curl http://localhost:9090/health
|
||||
# Output: OK
|
||||
GOWORK=off go test -bench=. -benchmem ./...
|
||||
make benchmark # config, protocol, engine, core
|
||||
make benchmark-zmq # ZAP transport, needs ginkgo
|
||||
```
|
||||
|
||||
#### Status Check
|
||||
Increase `-benchtime=30s` when results scatter. Numbers in `BENCHMARKS.md` were
|
||||
taken on an M1 Max; treat anything measured elsewhere as a different data set,
|
||||
not a regression.
|
||||
|
||||
## Profiling
|
||||
|
||||
```bash
|
||||
curl http://localhost:9090/status | jq
|
||||
# Returns engine status and parameters
|
||||
GOWORK=off go test -cpuprofile=cpu.prof -bench=.
|
||||
GOWORK=off go tool pprof cpu.prof
|
||||
|
||||
GOWORK=off go test -memprofile=mem.prof -bench=.
|
||||
GOWORK=off go tool pprof mem.prof
|
||||
```
|
||||
|
||||
#### Run Consensus Test
|
||||
## The CLI
|
||||
|
||||
`make build` produces `bin/consensus`, which reports the parameters this build
|
||||
ships for a network:
|
||||
|
||||
```bash
|
||||
# Simple test (GET)
|
||||
curl http://localhost:9090/test | jq
|
||||
|
||||
# Custom test (POST)
|
||||
curl -X POST http://localhost:9090/test \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"rounds": 20, "nodes": 7}' | jq
|
||||
./bin/consensus # mainnet, as a table
|
||||
./bin/consensus -network local -json # local, as JSON
|
||||
```
|
||||
|
||||
#### Process Consensus Round
|
||||
```bash
|
||||
curl -X POST http://localhost:9090/consensus \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"block_id": "test-block-001",
|
||||
"votes": {
|
||||
"node1": 1,
|
||||
"node2": 1,
|
||||
"node3": 1,
|
||||
"node4": 1,
|
||||
"node5": 0
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
It refuses a network it does not know rather than defaulting, so a typo exits
|
||||
non-zero instead of printing mainnet's numbers under the name you asked for.
|
||||
|
||||
## CLI Tools
|
||||
## CI
|
||||
|
||||
### Consensus CLI
|
||||
```bash
|
||||
# Test different engines
|
||||
./bin/consensus -engine chain -action test
|
||||
./bin/consensus -engine dag -action test
|
||||
./bin/consensus -engine pq -action test
|
||||
Native runners via `.hanzo/workflows` — `ci.yml` for the suite, lint and the C
|
||||
and Rust wrappers, `release.yml` for the cross-compiled `consensus` binaries.
|
||||
|
||||
# Check health
|
||||
./bin/consensus -action health
|
||||
|
||||
# Get info
|
||||
./bin/consensus -action info
|
||||
```
|
||||
|
||||
### Simulator
|
||||
```bash
|
||||
# Basic simulation
|
||||
./bin/sim -nodes 5 -rounds 10
|
||||
|
||||
# Advanced simulation
|
||||
./bin/sim -nodes 21 -rounds 100 -failure 0.2 -latency 100ms -verbose
|
||||
|
||||
# Different networks
|
||||
./bin/sim -network mainnet -nodes 21 -rounds 50
|
||||
./bin/sim -network testnet -nodes 11 -rounds 30
|
||||
./bin/sim -network local -nodes 5 -rounds 20
|
||||
```
|
||||
|
||||
### Benchmarks
|
||||
```bash
|
||||
# Quick benchmark
|
||||
./bin/bench -engine chain -blocks 1000
|
||||
|
||||
# Full benchmark
|
||||
./bin/bench -engine all -duration 30s -parallel 8
|
||||
|
||||
# Verbose output
|
||||
./bin/bench -engine pq -blocks 10000 -verbose
|
||||
```
|
||||
|
||||
## E2E Testing
|
||||
|
||||
### Run all E2E tests
|
||||
```bash
|
||||
go test -v ./... -run TestE2E
|
||||
```
|
||||
|
||||
### Specific E2E tests
|
||||
```bash
|
||||
# Test consensus engines
|
||||
go test -v -run TestE2EConsensusEngine
|
||||
|
||||
# Test server endpoints (requires server running)
|
||||
go test -v -run TestE2EConsensusServer
|
||||
|
||||
# Test performance
|
||||
go test -v -run TestE2EPerformance
|
||||
|
||||
# Test simulation
|
||||
go test -v -run TestE2ESimulation
|
||||
```
|
||||
|
||||
## Unit Testing
|
||||
|
||||
### Run all tests
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
### Run with race detection
|
||||
```bash
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
### Run with coverage
|
||||
```bash
|
||||
go test -cover ./...
|
||||
go test -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out
|
||||
```
|
||||
|
||||
### Run benchmarks
|
||||
```bash
|
||||
go test -bench=. ./...
|
||||
go test -bench=. -benchtime=10s ./...
|
||||
go test -bench=. -benchmem ./...
|
||||
```
|
||||
|
||||
## Load Testing
|
||||
|
||||
### Simple load test
|
||||
```bash
|
||||
# 100 concurrent health checks
|
||||
for i in {1..100}; do
|
||||
curl -s http://localhost:9090/health &
|
||||
done
|
||||
wait
|
||||
```
|
||||
|
||||
### Stress test with Apache Bench (ab)
|
||||
```bash
|
||||
# Install ab if needed
|
||||
# macOS: brew install httpd
|
||||
# Linux: apt-get install apache2-utils
|
||||
|
||||
# 1000 requests, 10 concurrent
|
||||
ab -n 1000 -c 10 http://localhost:9090/health
|
||||
|
||||
# POST requests
|
||||
ab -n 100 -c 5 -p consensus.json -T application/json \
|
||||
http://localhost:9090/consensus
|
||||
```
|
||||
|
||||
### Stress test with hey
|
||||
```bash
|
||||
# Install hey
|
||||
go install github.com/rakyll/hey@latest
|
||||
|
||||
# Basic test
|
||||
hey -n 1000 -c 10 http://localhost:9090/health
|
||||
|
||||
# POST with payload
|
||||
hey -n 100 -c 5 -m POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"block_id":"test","votes":{"n1":1,"n2":1,"n3":1}}' \
|
||||
http://localhost:9090/consensus
|
||||
```
|
||||
|
||||
## Automated Test Script
|
||||
|
||||
Run the comprehensive test suite:
|
||||
```bash
|
||||
./test_consensus.sh
|
||||
```
|
||||
|
||||
This script runs:
|
||||
- Manual HTTP tests
|
||||
- CLI tool tests
|
||||
- E2E tests
|
||||
- Load tests
|
||||
- Unit tests
|
||||
- Benchmarks
|
||||
|
||||
## CI/CD Testing
|
||||
|
||||
The GitHub Actions CI runs:
|
||||
- Unit tests with race detection
|
||||
- Cross-platform builds (Linux, macOS, Windows)
|
||||
- Benchmarks
|
||||
- Linting
|
||||
|
||||
Check CI status:
|
||||
```bash
|
||||
gh run list --repo luxfi/consensus --limit 5
|
||||
gh run view <run-id> --repo luxfi/consensus
|
||||
```
|
||||
|
||||
## Performance Requirements
|
||||
|
||||
Expected performance metrics:
|
||||
- **Consensus finality**: < 100ms for local network
|
||||
- **Throughput**: > 10,000 consensus rounds/sec
|
||||
- **Concurrency**: Handle 1000+ concurrent requests
|
||||
- **Memory**: < 100MB for basic operations
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable verbose logging
|
||||
```bash
|
||||
./bin/sim -verbose
|
||||
./bin/bench -verbose
|
||||
```
|
||||
|
||||
### Check server logs
|
||||
```bash
|
||||
# If running in background
|
||||
tail -f consensus-server.log
|
||||
|
||||
# Or run in foreground
|
||||
./bin/server -port 9090 -network local
|
||||
```
|
||||
|
||||
### Profile performance
|
||||
```bash
|
||||
# CPU profiling
|
||||
go test -cpuprofile=cpu.prof -bench=.
|
||||
go tool pprof cpu.prof
|
||||
|
||||
# Memory profiling
|
||||
go test -memprofile=mem.prof -bench=.
|
||||
go tool pprof mem.prof
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Port already in use
|
||||
```bash
|
||||
# Find process using port
|
||||
lsof -i :9090
|
||||
# Kill process
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### Tests failing
|
||||
```bash
|
||||
# Check for race conditions
|
||||
go test -race ./...
|
||||
|
||||
# Run with verbose output
|
||||
go test -v ./...
|
||||
|
||||
# Run specific test
|
||||
go test -v -run TestName ./...
|
||||
```
|
||||
|
||||
### Benchmark variations
|
||||
If benchmarks show high variation, increase duration:
|
||||
```bash
|
||||
go test -bench=. -benchtime=30s ./...
|
||||
```
|
||||
@@ -1,181 +0,0 @@
|
||||
// Package main provides the bench CLI tool for consensus benchmarking
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/consensus/config"
|
||||
"github.com/luxfi/consensus/engine/chain"
|
||||
"github.com/luxfi/consensus/engine/dag"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
engine = flag.String("engine", "all", "Engine to benchmark (chain, dag, all)")
|
||||
network = flag.String("network", "local", "Network configuration (mainnet, testnet, local)")
|
||||
duration = flag.Duration("duration", 10*time.Second, "Benchmark duration")
|
||||
blocks = flag.Int("blocks", 1000, "Number of blocks to process")
|
||||
parallel = flag.Int("parallel", 1, "Number of parallel workers")
|
||||
useZMQ = flag.Bool("zmq", false, "Use ZMQ transport (if available)")
|
||||
verbose = flag.Bool("verbose", false, "Verbose output")
|
||||
help = flag.Bool("help", false, "Show help message")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Get network configuration
|
||||
params := getNetworkParams(*network)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *duration)
|
||||
defer cancel()
|
||||
|
||||
fmt.Printf("Benchmarking %s engine(s) with %s configuration\n", *engine, *network)
|
||||
fmt.Printf("Duration: %s, Blocks: %d, Parallel: %d, ZMQ: %v\n\n", *duration, *blocks, *parallel, *useZMQ)
|
||||
|
||||
switch *engine {
|
||||
case "chain":
|
||||
benchmarkChain(ctx, params, *blocks, *parallel, *verbose)
|
||||
case "dag":
|
||||
benchmarkDAG(ctx, params, *blocks, *parallel, *verbose)
|
||||
case "all":
|
||||
benchmarkChain(ctx, params, *blocks, *parallel, *verbose)
|
||||
fmt.Println()
|
||||
benchmarkDAG(ctx, params, *blocks, *parallel, *verbose)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown engine: %s\n", *engine)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Println("Consensus Benchmark Tool")
|
||||
fmt.Println("\nUsage: bench [options]")
|
||||
fmt.Println("\nOptions:")
|
||||
fmt.Println(" -engine string Engine to benchmark (default: all)")
|
||||
fmt.Println(" Options: chain, dag, all")
|
||||
fmt.Println(" -network string Network configuration (default: local)")
|
||||
fmt.Println(" Options: mainnet, testnet, local")
|
||||
fmt.Println(" -duration time Benchmark duration (default: 10s)")
|
||||
fmt.Println(" -blocks int Number of blocks to process (default: 1000)")
|
||||
fmt.Println(" -parallel int Number of parallel workers (default: 1)")
|
||||
fmt.Println(" -zmq Use ZMQ transport if available")
|
||||
fmt.Println(" -verbose Verbose output")
|
||||
fmt.Println(" -help Show this help message")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" bench # Benchmark all engines")
|
||||
fmt.Println(" bench -engine chain -blocks 5000 # Benchmark chain engine with 5000 blocks")
|
||||
fmt.Println(" bench -engine dag -parallel 4 # Benchmark DAG with 4 workers")
|
||||
fmt.Println(" bench -network mainnet -duration 30s # Use mainnet config for 30s")
|
||||
}
|
||||
|
||||
func getNetworkParams(network string) config.Parameters {
|
||||
switch network {
|
||||
case "mainnet":
|
||||
return config.MainnetParams()
|
||||
case "testnet":
|
||||
return config.TestnetParams()
|
||||
case "local":
|
||||
return config.LocalParams()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown network: %s, using local\n", network)
|
||||
return config.LocalParams()
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkChain(ctx context.Context, params config.Parameters, blocks int, parallel int, verbose bool) {
|
||||
fmt.Println("=== Chain Engine Benchmark ===")
|
||||
engine := chain.New()
|
||||
|
||||
start := time.Now()
|
||||
if err := engine.Start(ctx, true); err != nil {
|
||||
fmt.Printf("Failed to start chain engine: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
processed := 0
|
||||
errors := 0
|
||||
|
||||
for i := 0; i < blocks && ctx.Err() == nil; i++ {
|
||||
blockID := ids.GenerateTestID()
|
||||
err := engine.GetBlock(ctx, ids.EmptyNodeID, 0, blockID)
|
||||
if err != nil {
|
||||
errors++
|
||||
if verbose {
|
||||
fmt.Printf("Error processing block %d: %v\n", i, err)
|
||||
}
|
||||
} else {
|
||||
processed++
|
||||
}
|
||||
|
||||
if verbose && i%100 == 0 {
|
||||
fmt.Printf("Processed %d blocks...\n", i)
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
tps := float64(processed) / elapsed.Seconds()
|
||||
|
||||
fmt.Printf("Results:\n")
|
||||
fmt.Printf(" Processed: %d blocks\n", processed)
|
||||
fmt.Printf(" Errors: %d\n", errors)
|
||||
fmt.Printf(" Time: %s\n", elapsed)
|
||||
fmt.Printf(" TPS: %.2f blocks/sec\n", tps)
|
||||
|
||||
_ = engine.Stop(ctx)
|
||||
}
|
||||
|
||||
func benchmarkDAG(ctx context.Context, params config.Parameters, blocks int, parallel int, verbose bool) {
|
||||
fmt.Println("=== DAG Engine Benchmark ===")
|
||||
engine := dag.New()
|
||||
|
||||
start := time.Now()
|
||||
if err := engine.Start(ctx, 1); err != nil {
|
||||
fmt.Printf("Failed to start DAG engine: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
processed := 0
|
||||
errors := 0
|
||||
|
||||
for i := 0; i < blocks && ctx.Err() == nil; i++ {
|
||||
vertexID := ids.GenerateTestID()
|
||||
_, err := engine.GetVtx(ctx, vertexID)
|
||||
if err != nil {
|
||||
errors++
|
||||
if verbose {
|
||||
fmt.Printf("Error processing vertex %d: %v\n", i, err)
|
||||
}
|
||||
} else {
|
||||
processed++
|
||||
}
|
||||
|
||||
if verbose && i%100 == 0 {
|
||||
fmt.Printf("Processed %d vertices...\n", i)
|
||||
}
|
||||
}
|
||||
|
||||
elapsed := time.Since(start)
|
||||
tps := float64(processed) / elapsed.Seconds()
|
||||
|
||||
fmt.Printf("Results:\n")
|
||||
fmt.Printf(" Processed: %d vertices\n", processed)
|
||||
fmt.Printf(" Errors: %d\n", errors)
|
||||
fmt.Printf(" Time: %s\n", elapsed)
|
||||
fmt.Printf(" TPS: %.2f vertices/sec\n", tps)
|
||||
|
||||
_ = engine.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// As of Go 1.20, rand.Seed is deprecated - random seeding is automatic
|
||||
// No manual seeding required for better randomness
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// Package main provides the checker CLI tool for consensus health checking
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/consensus"
|
||||
"github.com/luxfi/consensus/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
engine = flag.String("engine", "all", "Engine to check (chain, dag, pq, all)")
|
||||
timeout = flag.Duration("timeout", 5*time.Second, "Health check timeout")
|
||||
verbose = flag.Bool("verbose", false, "Verbose output")
|
||||
help = flag.Bool("help", false, "Show help message")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
||||
defer cancel()
|
||||
|
||||
exitCode := 0
|
||||
|
||||
switch *engine {
|
||||
case "chain":
|
||||
chain := consensus.NewChain(consensus.DefaultConfig())
|
||||
if !checkEngine(ctx, "chain", chain, *verbose) {
|
||||
exitCode = 1
|
||||
}
|
||||
case "dag":
|
||||
// DAG engine not yet implemented in new API
|
||||
fmt.Println("DAG engine check not yet available in new API")
|
||||
case "pq":
|
||||
// PQ engine not yet implemented in new API
|
||||
fmt.Println("PQ engine check not yet available in new API")
|
||||
case "all":
|
||||
chain := consensus.NewChain(consensus.DefaultConfig())
|
||||
if !checkEngine(ctx, "chain", chain, *verbose) {
|
||||
exitCode = 1
|
||||
}
|
||||
fmt.Println("DAG and PQ engine checks not yet available in new API")
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown engine: %s\n", *engine)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Check configurations
|
||||
if !checkConfigurations(*verbose) {
|
||||
exitCode = 1
|
||||
}
|
||||
|
||||
if exitCode == 0 {
|
||||
fmt.Println("\n✓ All health checks passed")
|
||||
} else {
|
||||
fmt.Println("\n✗ Some health checks failed")
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Println("Consensus Health Checker")
|
||||
fmt.Println("\nUsage: checker [options]")
|
||||
fmt.Println("\nOptions:")
|
||||
fmt.Println(" -engine string Engine to check (default: all)")
|
||||
fmt.Println(" Options: chain, dag, pq, all")
|
||||
fmt.Println(" -timeout duration Health check timeout (default: 5s)")
|
||||
fmt.Println(" -verbose Verbose output")
|
||||
fmt.Println(" -help Show this help message")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" checker # Check all engines")
|
||||
fmt.Println(" checker -engine chain # Check only chain engine")
|
||||
fmt.Println(" checker -verbose # Verbose output for debugging")
|
||||
}
|
||||
|
||||
func checkEngine(ctx context.Context, name string, engine consensus.Engine, verbose bool) bool {
|
||||
fmt.Printf("Checking %s engine... ", name)
|
||||
|
||||
// Start engine
|
||||
if err := engine.Start(ctx); err != nil {
|
||||
fmt.Printf("✗ Failed to start: %v\n", err)
|
||||
return false
|
||||
}
|
||||
defer func() { _ = engine.Stop() }()
|
||||
|
||||
// Basic functionality check - just verify it started
|
||||
fmt.Println("✓")
|
||||
|
||||
if verbose {
|
||||
fmt.Printf(" Engine started successfully\n")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func checkConfigurations(verbose bool) bool {
|
||||
fmt.Println("\nChecking configurations...")
|
||||
|
||||
configs := map[string]config.Parameters{
|
||||
"mainnet": config.MainnetParams(),
|
||||
"testnet": config.TestnetParams(),
|
||||
"local": config.LocalParams(),
|
||||
"xchain": config.XChainParams(),
|
||||
}
|
||||
|
||||
allValid := true
|
||||
|
||||
for name, cfg := range configs {
|
||||
fmt.Printf(" %s: ", name)
|
||||
if err := cfg.Valid(); err != nil {
|
||||
fmt.Printf("✗ %v\n", err)
|
||||
allValid = false
|
||||
} else {
|
||||
fmt.Println("✓")
|
||||
if verbose {
|
||||
fmt.Printf(" K=%d, Alpha=%.2f, Beta=%d, BlockTime=%s\n",
|
||||
cfg.K, cfg.Alpha, cfg.Beta, cfg.BlockTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allValid
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/luxfi/consensus"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
// run a deterministic scenario: 3 validators, `accept` of them vote Preference.
|
||||
func run(accept int) bool {
|
||||
cfg := consensus.DefaultConfig()
|
||||
cfg.K = 3
|
||||
cfg.Alpha = 2 // quorum: 2 of 3
|
||||
chain := consensus.NewChain(cfg)
|
||||
ctx := context.Background()
|
||||
_ = chain.Start(ctx)
|
||||
var bid ids.ID
|
||||
bid[0] = 42
|
||||
_ = chain.Add(ctx, consensus.NewBlock(bid, consensus.GenesisID, 1, []byte("conf")))
|
||||
for round := 0; round < 8; round++ {
|
||||
for i := 0; i < accept; i++ {
|
||||
var nid ids.NodeID
|
||||
nid[0] = byte(i + 1)
|
||||
_ = chain.RecordVote(ctx, consensus.NewVote(bid, consensus.VotePreference, nid))
|
||||
}
|
||||
}
|
||||
return chain.IsAccepted(bid)
|
||||
}
|
||||
|
||||
func main() {
|
||||
for _, v := range []int{3, 2, 1, 0} {
|
||||
fmt.Printf("GO accept=%d/3 -> accepted=%t\n", v, run(v))
|
||||
}
|
||||
}
|
||||
+92
-129
@@ -1,156 +1,119 @@
|
||||
// Package main provides the main consensus CLI tool
|
||||
// Command consensus reports the consensus parameters a build ships for a
|
||||
// named network, as a table or as JSON. Operators use it to see what a
|
||||
// binary will actually run with, and to diff that between builds.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/consensus"
|
||||
"github.com/luxfi/consensus/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
engine = flag.String("engine", "chain", "Consensus engine (chain, dag, pq)")
|
||||
network = flag.String("network", "mainnet", "Network configuration")
|
||||
action = flag.String("action", "info", "Action to perform (info, test, health)")
|
||||
help = flag.Bool("help", false, "Show help message")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
|
||||
}
|
||||
|
||||
switch *action {
|
||||
case "info":
|
||||
showInfo(*engine, *network)
|
||||
case "test":
|
||||
testEngine(*engine, *network)
|
||||
case "health":
|
||||
checkHealth(*engine)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown action: %s\n", *action)
|
||||
os.Exit(1)
|
||||
func run(args []string, out, errOut io.Writer) int {
|
||||
fs := flag.NewFlagSet("consensus", flag.ContinueOnError)
|
||||
fs.SetOutput(errOut)
|
||||
network := fs.String("network", "mainnet", "network to report: mainnet, testnet, local")
|
||||
asJSON := fs.Bool("json", false, "report as JSON instead of a table")
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return 0
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Println("Lux Consensus CLI")
|
||||
fmt.Println("\nUsage: consensus [options]")
|
||||
fmt.Println("\nOptions:")
|
||||
fmt.Println(" -engine string Consensus engine (default: chain)")
|
||||
fmt.Println(" Options: chain, dag, pq")
|
||||
fmt.Println(" -network string Network configuration (default: mainnet)")
|
||||
fmt.Println(" Options: mainnet, testnet, local")
|
||||
fmt.Println(" -action string Action to perform (default: info)")
|
||||
fmt.Println(" Options: info, test, health")
|
||||
fmt.Println(" -help Show this help message")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" consensus # Show chain engine info")
|
||||
fmt.Println(" consensus -engine dag -action test # Test DAG engine")
|
||||
fmt.Println(" consensus -action health # Check engine health")
|
||||
p, ok := paramsFor(*network)
|
||||
if !ok {
|
||||
fmt.Fprintf(errOut, "unknown network %q: want mainnet, testnet or local\n", *network)
|
||||
return 1
|
||||
}
|
||||
|
||||
func showInfo(engineType, network string) {
|
||||
fmt.Printf("=== Consensus Engine Info ===\n")
|
||||
fmt.Printf("Engine: %s\n", engineType)
|
||||
fmt.Printf("Network: %s\n", network)
|
||||
|
||||
params := getNetworkParams(network)
|
||||
fmt.Printf("\nParameters:\n")
|
||||
fmt.Printf(" K (sample size): %d\n", params.K)
|
||||
fmt.Printf(" Alpha (quorum): %.2f\n", params.Alpha)
|
||||
fmt.Printf(" Beta (decision rounds): %d\n", params.Beta)
|
||||
fmt.Printf(" Block Time: %s\n", params.BlockTime)
|
||||
fmt.Printf(" Round Timeout: %s\n", params.RoundTO)
|
||||
|
||||
switch engineType {
|
||||
case "chain":
|
||||
fmt.Println("\nChain Engine:")
|
||||
fmt.Println(" - Linear blockchain consensus")
|
||||
fmt.Println(" - Sequential block processing")
|
||||
fmt.Println(" - Optimized for ordered transactions")
|
||||
case "dag":
|
||||
fmt.Println("\nDAG Engine:")
|
||||
fmt.Println(" - Directed Acyclic Graph consensus")
|
||||
fmt.Println(" - Parallel transaction processing")
|
||||
fmt.Println(" - High throughput optimization")
|
||||
case "pq":
|
||||
fmt.Println("\nPost-Quantum Engine:")
|
||||
fmt.Println(" - Quantum-resistant cryptography")
|
||||
fmt.Println(" - ML-DSA-65 algorithm")
|
||||
fmt.Println(" - Future-proof security")
|
||||
write := writeTable
|
||||
if *asJSON {
|
||||
write = writeJSON
|
||||
}
|
||||
if err := write(out, fields(p)); err != nil {
|
||||
fmt.Fprintln(errOut, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func testEngine(engineType, network string) {
|
||||
fmt.Printf("Testing %s engine with %s configuration...\n", engineType, network)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
switch engineType {
|
||||
case "chain":
|
||||
engine := consensus.NewChain(consensus.DefaultConfig())
|
||||
// Start engine
|
||||
if err := engine.Start(ctx); err != nil {
|
||||
fmt.Printf("✗ Failed to start: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() { _ = engine.Stop() }()
|
||||
fmt.Println("✓ Engine test passed")
|
||||
fmt.Printf(" Engine started successfully\n")
|
||||
case "dag":
|
||||
fmt.Println("DAG engine not yet implemented in new API")
|
||||
case "pq":
|
||||
fmt.Println("PQ engine not yet implemented in new API")
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown engine: %s\n", engineType)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func checkHealth(engineType string) {
|
||||
fmt.Printf("Checking %s engine health...\n", engineType)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
switch engineType {
|
||||
case "chain":
|
||||
engine := consensus.NewChain(consensus.DefaultConfig())
|
||||
// Start engine
|
||||
if err := engine.Start(ctx); err != nil {
|
||||
fmt.Printf("✗ Failed to start: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() { _ = engine.Stop() }()
|
||||
fmt.Println("✓ Healthy")
|
||||
fmt.Printf(" Engine started successfully\n")
|
||||
case "dag":
|
||||
fmt.Println("DAG engine not yet implemented in new API")
|
||||
case "pq":
|
||||
fmt.Println("PQ engine not yet implemented in new API")
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown engine: %s\n", engineType)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getNetworkParams(network string) config.Parameters {
|
||||
// paramsFor reports whether the network is one this build knows. An unknown
|
||||
// name is refused rather than defaulted, so a typo cannot quietly print some
|
||||
// other network's parameters while the operator reads them as the one asked for.
|
||||
//
|
||||
// config.XChainParams is deliberately absent: it returns a value identical to
|
||||
// LocalParams, so offering it as a fourth name would answer the same question
|
||||
// twice and no test could tell the two apart.
|
||||
func paramsFor(network string) (config.Parameters, bool) {
|
||||
switch network {
|
||||
case "mainnet":
|
||||
return config.MainnetParams()
|
||||
return config.MainnetParams(), true
|
||||
case "testnet":
|
||||
return config.TestnetParams()
|
||||
return config.TestnetParams(), true
|
||||
case "local":
|
||||
return config.LocalParams()
|
||||
default:
|
||||
return config.MainnetParams()
|
||||
return config.LocalParams(), true
|
||||
}
|
||||
return config.Parameters{}, false
|
||||
}
|
||||
|
||||
type field struct {
|
||||
name string
|
||||
value any
|
||||
}
|
||||
|
||||
// fields is the single ordered description of a report, so the table and the
|
||||
// JSON cannot drift apart in content. Durations and PQMode render through
|
||||
// String so both formats carry the same human-readable value.
|
||||
func fields(p config.Parameters) []field {
|
||||
return []field{
|
||||
{"k", p.K},
|
||||
{"alpha", p.Alpha},
|
||||
{"beta", p.Beta},
|
||||
{"alphaPreference", p.AlphaPreference},
|
||||
{"alphaConfidence", p.AlphaConfidence},
|
||||
{"betaVirtuous", p.BetaVirtuous},
|
||||
{"betaRogue", p.BetaRogue},
|
||||
{"concurrentPolls", p.ConcurrentPolls},
|
||||
{"concurrentRepolls", p.ConcurrentRepolls},
|
||||
{"optimalProcessing", p.OptimalProcessing},
|
||||
{"maxOutstandingItems", p.MaxOutstandingItems},
|
||||
{"maxItemProcessingTime", p.MaxItemProcessingTime.String()},
|
||||
{"parents", p.Parents},
|
||||
{"batchSize", p.BatchSize},
|
||||
{"blockTime", p.BlockTime.String()},
|
||||
{"roundTimeout", p.RoundTO.String()},
|
||||
{"convergenceSettleWindow", p.ConvergenceSettleWindow.String()},
|
||||
{"gasLimit", p.GasLimit},
|
||||
{"pqMode", p.PQMode.String()},
|
||||
}
|
||||
}
|
||||
|
||||
func writeTable(w io.Writer, fs []field) error {
|
||||
for _, f := range fs {
|
||||
if _, err := fmt.Fprintf(w, "%-24s %v\n", f.name, f.value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeJSON(w io.Writer, fs []field) error {
|
||||
m := make(map[string]any, len(fs))
|
||||
for _, f := range fs {
|
||||
m[f.name] = f.value
|
||||
}
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", " ")
|
||||
return e.Encode(m)
|
||||
}
|
||||
|
||||
+157
-82
@@ -2,105 +2,180 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/consensus/config"
|
||||
)
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "default run",
|
||||
args: []string{},
|
||||
want: "Starting consensus simulation",
|
||||
},
|
||||
{
|
||||
name: "with nodes",
|
||||
args: []string{"-nodes", "5"},
|
||||
want: "nodes=5",
|
||||
},
|
||||
{
|
||||
name: "with rounds",
|
||||
args: []string{"-rounds", "100"},
|
||||
want: "rounds=100",
|
||||
},
|
||||
{
|
||||
name: "with alpha",
|
||||
args: []string{"-alpha", "0.7"},
|
||||
want: "alpha=0.7",
|
||||
},
|
||||
{
|
||||
name: "with beta",
|
||||
args: []string{"-beta", "10"},
|
||||
want: "beta=10",
|
||||
},
|
||||
{
|
||||
name: "all params",
|
||||
args: []string{"-nodes", "21", "-rounds", "50", "-alpha", "0.8", "-beta", "15"},
|
||||
want: "nodes=21",
|
||||
},
|
||||
// An unknown network must be refused, not silently answered with some other
|
||||
// network's numbers. Breaks if paramsFor grows a default arm.
|
||||
func TestUnknownNetworkIsRefused(t *testing.T) {
|
||||
var out, errOut bytes.Buffer
|
||||
if code := run([]string{"-network", "mianet"}, &out, &errOut); code != 1 {
|
||||
t.Fatalf("exit = %d, want 1", code)
|
||||
}
|
||||
if out.Len() != 0 {
|
||||
t.Errorf("stdout = %q, want empty: a refused network must report no parameters", out.String())
|
||||
}
|
||||
if !strings.Contains(errOut.String(), "mianet") {
|
||||
t.Errorf("stderr = %q, want it to name the rejected network", errOut.String())
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Capture output
|
||||
var buf bytes.Buffer
|
||||
oldArgs := make([]string, len(tt.args))
|
||||
copy(oldArgs, tt.args)
|
||||
|
||||
// Mock the run (since we can't actually execute main)
|
||||
output := simulateRun(tt.args)
|
||||
buf.WriteString(output)
|
||||
|
||||
got := buf.String()
|
||||
if !strings.Contains(got, tt.want) && !tt.wantErr {
|
||||
t.Errorf("run() output = %v, want substring %v", got, tt.want)
|
||||
// Each name must reach its own constructor. Breaks if a network is wired to
|
||||
// the wrong one, because the three differ in K, Beta and BlockTime.
|
||||
func TestNetworkReachesItsOwnParameters(t *testing.T) {
|
||||
for name, want := range map[string]config.Parameters{
|
||||
"mainnet": config.MainnetParams(),
|
||||
"testnet": config.TestnetParams(),
|
||||
"local": config.LocalParams(),
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
var out, errOut bytes.Buffer
|
||||
if code := run([]string{"-network", name, "-json"}, &out, &errOut); code != 0 {
|
||||
t.Fatalf("exit = %d (stderr %q), want 0", code, errOut.String())
|
||||
}
|
||||
got := decode(t, out.Bytes())
|
||||
for _, f := range fields(want) {
|
||||
if got[f.name] != fmt.Sprint(f.value) {
|
||||
t.Errorf("%s = %q, want %q", f.name, got[f.name], fmt.Sprint(f.value))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// simulateRun simulates the main function behavior
|
||||
func simulateRun(args []string) string {
|
||||
var output strings.Builder
|
||||
output.WriteString("Starting consensus simulation\n")
|
||||
// The two formats are rendered by different code from one description, so this
|
||||
// pins that they cannot drift: same names, same values, nothing dropped.
|
||||
func TestTableAndJSONAgree(t *testing.T) {
|
||||
var tbl, js, errOut bytes.Buffer
|
||||
if code := run([]string{"-network", "mainnet"}, &tbl, &errOut); code != 0 {
|
||||
t.Fatalf("table exit = %d", code)
|
||||
}
|
||||
if code := run([]string{"-network", "mainnet", "-json"}, &js, &errOut); code != 0 {
|
||||
t.Fatalf("json exit = %d", code)
|
||||
}
|
||||
|
||||
// Parse args
|
||||
nodes := 10
|
||||
rounds := 20
|
||||
alpha := 0.8
|
||||
beta := uint32(15)
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "-nodes":
|
||||
if i+1 < len(args) {
|
||||
output.WriteString("nodes=" + args[i+1] + "\n")
|
||||
fromJSON := decode(t, js.Bytes())
|
||||
lines := strings.Split(strings.TrimRight(tbl.String(), "\n"), "\n")
|
||||
if len(lines) != len(fromJSON) {
|
||||
t.Fatalf("table has %d rows, JSON has %d keys", len(lines), len(fromJSON))
|
||||
}
|
||||
case "-rounds":
|
||||
if i+1 < len(args) {
|
||||
output.WriteString("rounds=" + args[i+1] + "\n")
|
||||
for _, line := range lines {
|
||||
name, value, ok := strings.Cut(strings.TrimSpace(line), " ")
|
||||
if !ok {
|
||||
t.Fatalf("row %q has no value", line)
|
||||
}
|
||||
case "-alpha":
|
||||
if i+1 < len(args) {
|
||||
output.WriteString("alpha=" + args[i+1] + "\n")
|
||||
}
|
||||
case "-beta":
|
||||
if i+1 < len(args) {
|
||||
output.WriteString("beta=" + args[i+1] + "\n")
|
||||
if got, want := strings.TrimSpace(value), fromJSON[name]; got != want {
|
||||
t.Errorf("%s: table %q, JSON %q", name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.WriteString("Configuration:\n")
|
||||
output.WriteString(" Nodes: " + string(rune(nodes)) + "\n")
|
||||
output.WriteString(" Rounds: " + string(rune(rounds)) + "\n")
|
||||
output.WriteString(" Alpha: " + string(rune(int(alpha*10))) + "\n")
|
||||
output.WriteString(" Beta: " + string(rune(beta)) + "\n")
|
||||
// The exit code is the tool's contract with a shell. Breaks if -h starts
|
||||
// reporting failure, or a bad flag starts reporting success.
|
||||
func TestExitCodes(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want int
|
||||
}{
|
||||
{"default is mainnet", nil, 0},
|
||||
{"help", []string{"-h"}, 0},
|
||||
{"unknown network", []string{"-network", "nope"}, 1},
|
||||
{"unknown flag", []string{"-verbose"}, 2},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var out, errOut bytes.Buffer
|
||||
if code := run(tt.args, &out, &errOut); code != tt.want {
|
||||
t.Errorf("exit = %d, want %d", code, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return output.String()
|
||||
// A report that could not be written is a failure, not a silent success —
|
||||
// otherwise `consensus > /full/disk` exits 0 having produced nothing.
|
||||
func TestWriteFailureIsReported(t *testing.T) {
|
||||
for _, args := range [][]string{{}, {"-json"}} {
|
||||
var errOut bytes.Buffer
|
||||
if code := run(args, brokenWriter{}, &errOut); code != 1 {
|
||||
t.Errorf("run(%v) exit = %d, want 1", args, code)
|
||||
}
|
||||
if !strings.Contains(errOut.String(), "disk full") {
|
||||
t.Errorf("run(%v) stderr = %q, want the write error", args, errOut.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type brokenWriter struct{}
|
||||
|
||||
func (brokenWriter) Write([]byte) (int, error) { return 0, errors.New("disk full") }
|
||||
|
||||
// main is what a shell actually runs, so it is exercised as a process: the
|
||||
// test binary re-executes itself and reads the code it exits with. Breaks if
|
||||
// os.Exit is dropped (a refused network would then look successful to a
|
||||
// script) or if os.Args reaches run unsliced (the program name would parse
|
||||
// as a flag and every invocation would fail).
|
||||
func TestMainExitsWithRunsCode(t *testing.T) {
|
||||
if args, child := os.LookupEnv(childArgs); child {
|
||||
os.Args = append([]string{"consensus"}, strings.Fields(args)...)
|
||||
main()
|
||||
return
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
args string
|
||||
want int
|
||||
}{
|
||||
{"known network", "-network local", 0},
|
||||
{"unknown network", "-network nope", 1},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := exec.Command(os.Args[0], "-test.run=TestMainExitsWithRunsCode")
|
||||
cmd.Env = append(os.Environ(), childArgs+"="+tt.args)
|
||||
out, err := cmd.Output()
|
||||
|
||||
got := 0
|
||||
var exit *exec.ExitError
|
||||
if errors.As(err, &exit) {
|
||||
got = exit.ExitCode()
|
||||
} else if err != nil {
|
||||
t.Fatalf("running child: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("exit = %d, want %d (stdout %q)", got, tt.want, out)
|
||||
}
|
||||
if wantReport := tt.want == 0; wantReport != strings.Contains(string(out), "k ") {
|
||||
t.Errorf("stdout = %q, report on stdout should be %v", out, wantReport)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// childArgs carries the command line into the re-executed test binary.
|
||||
const childArgs = "CONSENSUS_TEST_ARGS"
|
||||
|
||||
// decode renders the JSON report as name -> literal text, so values compare
|
||||
// against fmt.Sprint of the source field without float round-tripping.
|
||||
func decode(t *testing.T, b []byte) map[string]string {
|
||||
t.Helper()
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.UseNumber()
|
||||
var raw map[string]any
|
||||
if err := d.Decode(&raw); err != nil {
|
||||
t.Fatalf("report is not JSON: %v", err)
|
||||
}
|
||||
out := make(map[string]string, len(raw))
|
||||
for k, v := range raw {
|
||||
out[k] = fmt.Sprint(v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
// Package main provides the params CLI tool for viewing consensus network parameters
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/luxfi/consensus/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
network = flag.String("network", "mainnet", "Network to show parameters for (mainnet, testnet, local, xchain)")
|
||||
json = flag.Bool("json", false, "Output in JSON format")
|
||||
help = flag.Bool("help", false, "Show help message")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var params config.Parameters
|
||||
switch *network {
|
||||
case "mainnet":
|
||||
params = config.MainnetParams()
|
||||
case "testnet":
|
||||
params = config.TestnetParams()
|
||||
case "local":
|
||||
params = config.LocalParams()
|
||||
case "xchain":
|
||||
params = config.XChainParams()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown network: %s\n", *network)
|
||||
fmt.Fprintln(os.Stderr, "Valid networks: mainnet, testnet, local, xchain")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *json {
|
||||
printJSON(params)
|
||||
} else {
|
||||
printTable(params)
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Println("Consensus Parameters Viewer")
|
||||
fmt.Println("\nUsage: params [options]")
|
||||
fmt.Println("\nOptions:")
|
||||
fmt.Println(" -network string Network to show parameters for (default: mainnet)")
|
||||
fmt.Println(" Options: mainnet, testnet, local, xchain")
|
||||
fmt.Println(" -json Output in JSON format")
|
||||
fmt.Println(" -help Show this help message")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" params # Show mainnet parameters")
|
||||
fmt.Println(" params -network testnet # Show testnet parameters")
|
||||
fmt.Println(" params -network local -json # Show local parameters in JSON")
|
||||
}
|
||||
|
||||
func printTable(p config.Parameters) {
|
||||
fmt.Printf("K (sample size): %d\n", p.K)
|
||||
fmt.Printf("Alpha (quorum): %.2f\n", p.Alpha)
|
||||
fmt.Printf("Beta (decision rounds): %d\n", p.Beta)
|
||||
fmt.Printf("Block Time: %s\n", p.BlockTime)
|
||||
fmt.Printf("Round Timeout: %s\n", p.RoundTO)
|
||||
|
||||
if p.AlphaPreference > 0 {
|
||||
fmt.Printf("Alpha Preference: %d\n", p.AlphaPreference)
|
||||
}
|
||||
if p.AlphaConfidence > 0 {
|
||||
fmt.Printf("Alpha Confidence: %d\n", p.AlphaConfidence)
|
||||
}
|
||||
if p.BetaVirtuous > 0 {
|
||||
fmt.Printf("Beta Virtuous: %d\n", p.BetaVirtuous)
|
||||
}
|
||||
if p.BetaRogue > 0 {
|
||||
fmt.Printf("Beta Rogue: %d\n", p.BetaRogue)
|
||||
}
|
||||
if p.ConcurrentPolls > 0 {
|
||||
fmt.Printf("Concurrent Polls: %d\n", p.ConcurrentPolls)
|
||||
}
|
||||
if p.ConcurrentRepolls > 0 {
|
||||
fmt.Printf("Concurrent Repolls: %d\n", p.ConcurrentRepolls)
|
||||
}
|
||||
if p.OptimalProcessing > 0 {
|
||||
fmt.Printf("Optimal Processing: %d\n", p.OptimalProcessing)
|
||||
}
|
||||
if p.MaxOutstandingItems > 0 {
|
||||
fmt.Printf("Max Outstanding Items: %d\n", p.MaxOutstandingItems)
|
||||
}
|
||||
if p.Parents > 0 {
|
||||
fmt.Printf("Parents: %d\n", p.Parents)
|
||||
}
|
||||
if p.BatchSize > 0 {
|
||||
fmt.Printf("Batch Size: %d\n", p.BatchSize)
|
||||
}
|
||||
}
|
||||
|
||||
func printJSON(p config.Parameters) {
|
||||
fmt.Printf(`{
|
||||
"k": %d,
|
||||
"alpha": %.2f,
|
||||
"beta": %d,
|
||||
"blockTime": "%s",
|
||||
"roundTimeout": "%s",
|
||||
"alphaPreference": %d,
|
||||
"alphaConfidence": %d,
|
||||
"betaVirtuous": %d,
|
||||
"betaRogue": %d,
|
||||
"concurrentPolls": %d,
|
||||
"concurrentRepolls": %d,
|
||||
"optimalProcessing": %d,
|
||||
"maxOutstandingItems": %d,
|
||||
"parents": %d,
|
||||
"batchSize": %d
|
||||
}
|
||||
`, p.K, p.Alpha, p.Beta, p.BlockTime, p.RoundTO,
|
||||
p.AlphaPreference, p.AlphaConfidence, p.BetaVirtuous, p.BetaRogue,
|
||||
p.ConcurrentPolls, p.ConcurrentRepolls, p.OptimalProcessing,
|
||||
p.MaxOutstandingItems, p.Parents, p.BatchSize)
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/consensus"
|
||||
"github.com/luxfi/consensus/config"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
type ConsensusServer struct {
|
||||
engine consensus.Engine
|
||||
config config.Parameters
|
||||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
Engine string `json:"engine"`
|
||||
Network string `json:"network"`
|
||||
Healthy bool `json:"healthy"`
|
||||
Parameters config.Parameters `json:"parameters"`
|
||||
Health interface{} `json:"health,omitempty"`
|
||||
}
|
||||
|
||||
type TestRequest struct {
|
||||
Rounds int `json:"rounds"`
|
||||
Nodes int `json:"nodes"`
|
||||
}
|
||||
|
||||
type TestResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Rounds int `json:"rounds"`
|
||||
Accepts int `json:"accepts"`
|
||||
Rejects int `json:"rejects"`
|
||||
AvgTime string `json:"avg_time"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
}
|
||||
|
||||
func (s *ConsensusServer) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
health := make(map[string]interface{})
|
||||
health["healthy"] = true
|
||||
|
||||
if adapter, ok := s.engine.(interface {
|
||||
HealthCheck(context.Context) (interface{}, error)
|
||||
}); ok {
|
||||
if h, err := adapter.HealthCheck(ctx); err == nil {
|
||||
if healthMap, ok := h.(map[string]interface{}); ok {
|
||||
health = healthMap
|
||||
} else if healthBoolMap, ok := h.(map[string]bool); ok {
|
||||
for k, v := range healthBoolMap {
|
||||
health[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
healthy := true
|
||||
if val, ok := health["healthy"].(bool); ok {
|
||||
healthy = val
|
||||
}
|
||||
|
||||
resp := StatusResponse{
|
||||
Engine: "chain",
|
||||
Network: "mainnet",
|
||||
Healthy: healthy,
|
||||
Parameters: s.config,
|
||||
Health: health,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error encoding response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConsensusServer) handleTest(w http.ResponseWriter, r *http.Request) {
|
||||
var req TestRequest
|
||||
if r.Method == http.MethodPost {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
req.Rounds = 10
|
||||
req.Nodes = 5
|
||||
}
|
||||
|
||||
// Run simple consensus test
|
||||
accepts := 0
|
||||
rejects := 0
|
||||
totalTime := time.Duration(0)
|
||||
|
||||
for i := 0; i < req.Rounds; i++ {
|
||||
start := time.Now()
|
||||
|
||||
// Simulate consensus round
|
||||
blockID := ids.GenerateTestID()
|
||||
|
||||
// Simulate votes from nodes
|
||||
acceptVotes := 0
|
||||
for j := 0; j < req.Nodes; j++ {
|
||||
if j < (req.Nodes * 8 / 10) { // 80% vote to accept
|
||||
acceptVotes++
|
||||
}
|
||||
}
|
||||
|
||||
if float64(acceptVotes)/float64(req.Nodes) >= s.config.Alpha {
|
||||
accepts++
|
||||
} else {
|
||||
rejects++
|
||||
}
|
||||
|
||||
totalTime += time.Since(start)
|
||||
|
||||
// Log the vote
|
||||
log.Printf("Round %d: BlockID=%s, Accept=%d/%d\n", i+1, blockID, acceptVotes, req.Nodes)
|
||||
}
|
||||
|
||||
avgTime := totalTime / time.Duration(req.Rounds)
|
||||
confidence := float64(accepts) / float64(req.Rounds)
|
||||
|
||||
resp := TestResponse{
|
||||
Success: true,
|
||||
Rounds: req.Rounds,
|
||||
Accepts: accepts,
|
||||
Rejects: rejects,
|
||||
AvgTime: avgTime.String(),
|
||||
Confidence: confidence * 100,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error encoding response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConsensusServer) handleConsensus(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
BlockID string `json:"block_id"`
|
||||
Votes map[string]int `json:"votes"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Process consensus
|
||||
blockID, err := ids.FromString(req.BlockID)
|
||||
if err != nil {
|
||||
blockID = ids.GenerateTestID()
|
||||
}
|
||||
|
||||
// Calculate if consensus reached
|
||||
totalVotes := 0
|
||||
acceptVotes := 0
|
||||
for _, count := range req.Votes {
|
||||
totalVotes += count
|
||||
if count > 0 {
|
||||
acceptVotes += count
|
||||
}
|
||||
}
|
||||
|
||||
finalized := false
|
||||
if totalVotes > 0 {
|
||||
confidence := float64(acceptVotes) / float64(totalVotes)
|
||||
finalized = confidence >= s.config.Alpha
|
||||
}
|
||||
|
||||
resp := map[string]interface{}{
|
||||
"block_id": blockID.String(),
|
||||
"finalized": finalized,
|
||||
"votes": req.Votes,
|
||||
"confidence": float64(acceptVotes) / float64(totalVotes) * 100,
|
||||
"alpha": s.config.Alpha * 100,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error encoding response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
port = flag.String("port", "8080", "Server port")
|
||||
network = flag.String("network", "mainnet", "Network configuration")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
// Initialize consensus engine
|
||||
engine := consensus.NewChainEngine()
|
||||
var params config.Parameters
|
||||
|
||||
switch *network {
|
||||
case "testnet":
|
||||
params = config.TestnetParams()
|
||||
case "local":
|
||||
params = config.LocalParams()
|
||||
default:
|
||||
params = config.MainnetParams()
|
||||
}
|
||||
|
||||
server := &ConsensusServer{
|
||||
engine: engine,
|
||||
config: params,
|
||||
}
|
||||
|
||||
// Set up routes
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/status", server.handleStatus)
|
||||
mux.HandleFunc("/test", server.handleTest)
|
||||
mux.HandleFunc("/consensus", server.handleConsensus)
|
||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if _, err := w.Write([]byte("OK")); err != nil {
|
||||
log.Printf("Error writing response: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
log.Printf("Starting consensus server on port %s with %s config", *port, *network)
|
||||
log.Printf("Endpoints:")
|
||||
log.Printf(" GET /status - Get engine status")
|
||||
log.Printf(" GET /health - Health check")
|
||||
log.Printf(" GET /test - Run consensus test")
|
||||
log.Printf(" POST /test - Run consensus test with custom params")
|
||||
log.Printf(" POST /consensus - Process consensus round")
|
||||
|
||||
// Create server with timeouts to avoid G114 warning
|
||||
srv := &http.Server{
|
||||
Addr: ":" + *port,
|
||||
Handler: mux,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
-212
@@ -1,212 +0,0 @@
|
||||
// Package main provides the sim CLI tool for consensus simulation
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/consensus/config"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
nodes = flag.Int("nodes", 100, "Number of nodes in the network")
|
||||
rounds = flag.Int("rounds", 10, "Number of consensus rounds to simulate")
|
||||
network = flag.String("network", "mainnet", "Network configuration (mainnet, testnet, local)")
|
||||
failure = flag.Float64("failure", 0.1, "Node failure rate (0.0-1.0)")
|
||||
latency = flag.Duration("latency", 50*time.Millisecond, "Network latency")
|
||||
verbose = flag.Bool("verbose", false, "Verbose output")
|
||||
help = flag.Bool("help", false, "Show help message")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
printHelp()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *failure < 0 || *failure > 1 {
|
||||
fmt.Fprintf(os.Stderr, "Failure rate must be between 0.0 and 1.0\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Get network configuration
|
||||
params := getNetworkParams(*network)
|
||||
|
||||
fmt.Println("=== Consensus Simulation ===")
|
||||
fmt.Printf("Network: %s\n", *network)
|
||||
fmt.Printf("Nodes: %d\n", *nodes)
|
||||
fmt.Printf("Rounds: %d\n", *rounds)
|
||||
fmt.Printf("Failure: %.1f%%\n", *failure*100)
|
||||
fmt.Printf("Latency: %s\n", *latency)
|
||||
fmt.Printf("Parameters: K=%d, Alpha=%.2f, Beta=%d\n\n", params.K, params.Alpha, params.Beta)
|
||||
|
||||
// Run simulation
|
||||
results := runSimulation(*nodes, *rounds, params, *failure, *latency, *verbose)
|
||||
|
||||
// Print results
|
||||
printResults(results, params)
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Println("Consensus Simulator")
|
||||
fmt.Println("\nUsage: sim [options]")
|
||||
fmt.Println("\nOptions:")
|
||||
fmt.Println(" -nodes int Number of nodes in the network (default: 100)")
|
||||
fmt.Println(" -rounds int Number of consensus rounds (default: 10)")
|
||||
fmt.Println(" -network string Network configuration (default: mainnet)")
|
||||
fmt.Println(" Options: mainnet, testnet, local")
|
||||
fmt.Println(" -failure float Node failure rate 0.0-1.0 (default: 0.1)")
|
||||
fmt.Println(" -latency duration Network latency (default: 50ms)")
|
||||
fmt.Println(" -verbose Verbose output")
|
||||
fmt.Println(" -help Show this help message")
|
||||
fmt.Println("\nExamples:")
|
||||
fmt.Println(" sim # Run default simulation")
|
||||
fmt.Println(" sim -nodes 1000 -rounds 100 # Large scale simulation")
|
||||
fmt.Println(" sim -failure 0.3 -latency 200ms # High failure, slow network")
|
||||
fmt.Println(" sim -network testnet -verbose # Testnet config with details")
|
||||
}
|
||||
|
||||
func getNetworkParams(network string) config.Parameters {
|
||||
switch network {
|
||||
case "mainnet":
|
||||
return config.MainnetParams()
|
||||
case "testnet":
|
||||
return config.TestnetParams()
|
||||
case "local":
|
||||
return config.LocalParams()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown network: %s, using mainnet\n", network)
|
||||
return config.MainnetParams()
|
||||
}
|
||||
}
|
||||
|
||||
type SimulationResult struct {
|
||||
Round int
|
||||
VotesReceived int
|
||||
Confidence float64
|
||||
Decision string
|
||||
TimeToConsensus time.Duration
|
||||
FailedNodes int
|
||||
}
|
||||
|
||||
func runSimulation(nodes int, rounds int, params config.Parameters, failureRate float64, latency time.Duration, verbose bool) []SimulationResult {
|
||||
results := make([]SimulationResult, 0, rounds)
|
||||
ctx := context.Background()
|
||||
|
||||
for round := 1; round <= rounds; round++ {
|
||||
if verbose {
|
||||
fmt.Printf("Round %d: ", round)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
result := simulateRound(ctx, nodes, params, failureRate, latency)
|
||||
result.Round = round
|
||||
result.TimeToConsensus = time.Since(start)
|
||||
|
||||
if verbose {
|
||||
fmt.Printf("%s (confidence: %.2f%%, time: %s)\n",
|
||||
result.Decision, result.Confidence*100, result.TimeToConsensus)
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
|
||||
// Simulate inter-round delay
|
||||
time.Sleep(latency)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func simulateRound(ctx context.Context, nodes int, params config.Parameters, failureRate float64, latency time.Duration) SimulationResult {
|
||||
// Calculate failed nodes
|
||||
failedNodes := int(float64(nodes) * failureRate)
|
||||
activeNodes := nodes - failedNodes
|
||||
|
||||
// Sample K nodes randomly
|
||||
k := params.K
|
||||
if k > activeNodes {
|
||||
k = activeNodes
|
||||
}
|
||||
|
||||
// Simulate voting
|
||||
votes := 0
|
||||
for i := 0; i < k; i++ {
|
||||
// Simulate network latency
|
||||
time.Sleep(latency / time.Duration(k))
|
||||
|
||||
// Random vote with Byzantine behavior (simulation only, not security-critical)
|
||||
if rand.Float64() > 0.2 { // 80% honest nodes //nolint:gosec // simulation randomness
|
||||
votes++
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate confidence
|
||||
confidence := float64(votes) / float64(k)
|
||||
|
||||
// Determine decision based on alpha threshold
|
||||
decision := "REJECT"
|
||||
if confidence >= params.Alpha {
|
||||
decision = "ACCEPT"
|
||||
}
|
||||
|
||||
return SimulationResult{
|
||||
VotesReceived: votes,
|
||||
Confidence: confidence,
|
||||
Decision: decision,
|
||||
FailedNodes: failedNodes,
|
||||
}
|
||||
}
|
||||
|
||||
func printResults(results []SimulationResult, params config.Parameters) {
|
||||
fmt.Println("=== Simulation Results ===")
|
||||
|
||||
accepts := 0
|
||||
rejects := 0
|
||||
totalTime := time.Duration(0)
|
||||
totalConfidence := 0.0
|
||||
|
||||
for _, r := range results {
|
||||
if r.Decision == "ACCEPT" {
|
||||
accepts++
|
||||
} else {
|
||||
rejects++
|
||||
}
|
||||
totalTime += r.TimeToConsensus
|
||||
totalConfidence += r.Confidence
|
||||
}
|
||||
|
||||
fmt.Printf("\nConsensus Decisions:\n")
|
||||
fmt.Printf(" Accepts: %d (%.1f%%)\n", accepts, float64(accepts)/float64(len(results))*100)
|
||||
fmt.Printf(" Rejects: %d (%.1f%%)\n", rejects, float64(rejects)/float64(len(results))*100)
|
||||
|
||||
fmt.Printf("\nPerformance:\n")
|
||||
fmt.Printf(" Avg Time: %s\n", totalTime/time.Duration(len(results)))
|
||||
fmt.Printf(" Avg Confidence: %.2f%%\n", totalConfidence/float64(len(results))*100)
|
||||
fmt.Printf(" Alpha Required: %.2f%%\n", params.Alpha*100)
|
||||
|
||||
// Calculate finality probability
|
||||
finalityProb := calculateFinalityProbability(params.Alpha, params.Beta, totalConfidence/float64(len(results)))
|
||||
fmt.Printf("\nFinality:\n")
|
||||
fmt.Printf(" Probability: %.4f%%\n", finalityProb*100)
|
||||
fmt.Printf(" Beta Rounds: %d\n", params.Beta)
|
||||
}
|
||||
|
||||
func calculateFinalityProbability(alpha float64, beta uint32, avgConfidence float64) float64 {
|
||||
// Simplified finality calculation
|
||||
// P(finality) = confidence^beta
|
||||
prob := 1.0
|
||||
for i := uint32(0); i < beta; i++ {
|
||||
prob *= avgConfidence
|
||||
}
|
||||
return prob
|
||||
}
|
||||
|
||||
func init() {
|
||||
// As of Go 1.20, rand.Seed is deprecated - random seeding is automatic
|
||||
// No manual seeding required for better randomness
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Temporary build script to work around node package issues
|
||||
|
||||
echo "Building consensus tools..."
|
||||
|
||||
# Build params (should work)
|
||||
echo "Building params..."
|
||||
go build -o bin/params ./cmd/params || echo "❌ Failed to build params"
|
||||
|
||||
# Build checker (should work)
|
||||
echo "Building checker..."
|
||||
go build -o bin/checker ./cmd/checker || echo "❌ Failed to build checker"
|
||||
|
||||
# Build sim (should work)
|
||||
echo "Building simulator..."
|
||||
go build -o bin/sim ./cmd/sim || echo "❌ Failed to build sim"
|
||||
|
||||
# Try to build zmq-bench standalone
|
||||
echo "Building zmq-bench..."
|
||||
cd cmd/zmq-bench
|
||||
go build -tags zmq -o ../../bin/zmq-bench . || echo "⚠️ Failed to build zmq-bench"
|
||||
cd ../..
|
||||
|
||||
# Build consensus CLI
|
||||
echo "Building consensus CLI..."
|
||||
cd cmd/consensus
|
||||
go mod tidy
|
||||
go build -o ../../bin/consensus . || echo "⚠️ Failed to build consensus CLI"
|
||||
cd ../..
|
||||
|
||||
echo "✅ Build complete! Successfully built tools are in bin/"
|
||||
ls -la bin/ 2>/dev/null || echo "No binaries built successfully"
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Distributed multi-machine benchmark setup
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
MODE=${1:-coordinator} # coordinator or worker
|
||||
COORDINATOR_IP=${2:-localhost}
|
||||
COORDINATOR_PORT=${3:-5555}
|
||||
NODES=${4:-100}
|
||||
ROUNDS=${5:-1000}
|
||||
|
||||
if [ "$MODE" = "coordinator" ]; then
|
||||
echo "🎯 Starting as COORDINATOR"
|
||||
echo " Bind address: tcp://*:$COORDINATOR_PORT"
|
||||
echo " Total nodes: $NODES"
|
||||
echo " Rounds: $ROUNDS"
|
||||
echo ""
|
||||
echo "Workers should connect to: tcp://$(hostname -I | awk '{print $1}'):$COORDINATOR_PORT"
|
||||
echo ""
|
||||
|
||||
./bin/consensus benchmark \
|
||||
--transport zmq \
|
||||
--zmq-bind "tcp://*:$COORDINATOR_PORT" \
|
||||
--nodes $NODES \
|
||||
--rounds $ROUNDS
|
||||
else
|
||||
echo "🔗 Starting as WORKER"
|
||||
echo " Connecting to: tcp://$COORDINATOR_IP:$COORDINATOR_PORT"
|
||||
echo ""
|
||||
|
||||
./bin/consensus benchmark \
|
||||
--transport zmq \
|
||||
--zmq-bind "tcp://$COORDINATOR_IP:$COORDINATOR_PORT"
|
||||
fi
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Multi-node benchmark setup script
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
NODES=${1:-10}
|
||||
BASE_PORT=${2:-30000}
|
||||
BATCH_SIZE=${3:-4096}
|
||||
INTERVAL=${4:-5ms}
|
||||
ROUNDS=${5:-100}
|
||||
|
||||
echo "🚀 Starting $NODES-node consensus benchmark network"
|
||||
echo " Base port: $BASE_PORT"
|
||||
echo " Batch size: $BATCH_SIZE"
|
||||
echo " Interval: $INTERVAL"
|
||||
echo " Rounds: $ROUNDS"
|
||||
echo ""
|
||||
|
||||
# Build the benchmark tool if needed
|
||||
if [ ! -f bin/zmq-bench ]; then
|
||||
echo "Building zmq-bench..."
|
||||
make zmq-bench
|
||||
fi
|
||||
|
||||
# Kill any existing zmq-bench processes
|
||||
echo "Cleaning up any existing processes..."
|
||||
pkill -f zmq-bench || true
|
||||
sleep 1
|
||||
|
||||
# Start the benchmark
|
||||
echo "Starting benchmark with $NODES nodes..."
|
||||
./bin/zmq-bench \
|
||||
-nodes $NODES \
|
||||
-port $BASE_PORT \
|
||||
-batch $BATCH_SIZE \
|
||||
-interval $INTERVAL \
|
||||
-rounds $ROUNDS
|
||||
|
||||
echo ""
|
||||
echo "Benchmark complete!"
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Run consensus benchmarks with different node counts
|
||||
|
||||
echo "Lux Consensus Benchmarks"
|
||||
echo "========================"
|
||||
echo ""
|
||||
|
||||
# Test with different node counts
|
||||
for nodes in 5 10 15 20; do
|
||||
echo "Testing with $nodes nodes:"
|
||||
echo "-------------------------"
|
||||
|
||||
# Run simulation with local parameters
|
||||
echo "Simulation test:"
|
||||
./bin/sim -network local -nodes $nodes -rounds 100 -sims 10
|
||||
|
||||
echo ""
|
||||
echo "Parameter analysis for $nodes nodes:"
|
||||
./bin/params -nodes $nodes -summary
|
||||
|
||||
echo ""
|
||||
echo "Safety check for $nodes nodes:"
|
||||
./bin/checker -network local -nodes $nodes -analyze
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Test high TPS configuration
|
||||
echo "High TPS Configuration Test:"
|
||||
echo "---------------------------"
|
||||
./bin/params -preset hightps -check
|
||||
echo ""
|
||||
./bin/sim -network hightps -nodes 10 -rounds 100 -sims 10
|
||||
|
||||
echo ""
|
||||
echo "Benchmark complete!"
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test all CLI tools comprehensively
|
||||
|
||||
set -e
|
||||
|
||||
echo "==========================================="
|
||||
echo " Lux Consensus Tools Test Suite"
|
||||
echo "==========================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Test counter
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
# Test function
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
local command="$2"
|
||||
|
||||
echo -n "Testing $test_name... "
|
||||
if $command > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓${NC}"
|
||||
((TESTS_PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗${NC}"
|
||||
echo " Failed command: $command"
|
||||
((TESTS_FAILED++))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "1. Testing bench tool"
|
||||
echo "------------------------"
|
||||
run_test "bench help" "./bin/bench -help"
|
||||
run_test "bench chain engine" "./bin/bench -engine chain -blocks 10 -duration 1s"
|
||||
run_test "bench dag engine" "./bin/bench -engine dag -blocks 10 -duration 1s"
|
||||
run_test "bench pq engine" "./bin/bench -engine pq -blocks 10 -duration 1s"
|
||||
run_test "bench all engines" "./bin/bench -engine all -blocks 5 -duration 1s"
|
||||
run_test "bench with parallel" "./bin/bench -engine chain -parallel 2 -blocks 10"
|
||||
run_test "bench mainnet config" "./bin/bench -network mainnet -blocks 5"
|
||||
run_test "bench testnet config" "./bin/bench -network testnet -blocks 5"
|
||||
run_test "bench local config" "./bin/bench -network local -blocks 5"
|
||||
echo ""
|
||||
|
||||
echo "2. Testing checker tool"
|
||||
echo "------------------------"
|
||||
run_test "checker help" "./bin/checker -help"
|
||||
run_test "checker all engines" "./bin/checker -engine all"
|
||||
run_test "checker chain engine" "./bin/checker -engine chain"
|
||||
run_test "checker dag engine" "./bin/checker -engine dag"
|
||||
run_test "checker pq engine" "./bin/checker -engine pq"
|
||||
run_test "checker with timeout" "./bin/checker -timeout 10s"
|
||||
run_test "checker verbose" "./bin/checker -verbose"
|
||||
echo ""
|
||||
|
||||
echo "3. Testing sim tool"
|
||||
echo "------------------------"
|
||||
run_test "sim help" "./bin/sim -help"
|
||||
run_test "sim default" "./bin/sim -nodes 10 -rounds 2"
|
||||
run_test "sim local network" "./bin/sim -nodes 5 -rounds 2 -network local"
|
||||
run_test "sim testnet" "./bin/sim -nodes 11 -rounds 2 -network testnet"
|
||||
run_test "sim mainnet" "./bin/sim -nodes 21 -rounds 2 -network mainnet"
|
||||
run_test "sim with failure" "./bin/sim -nodes 10 -rounds 2 -failure 0.3"
|
||||
run_test "sim with latency" "./bin/sim -nodes 10 -rounds 2 -latency 100ms"
|
||||
run_test "sim large scale" "./bin/sim -nodes 100 -rounds 1"
|
||||
run_test "sim verbose" "./bin/sim -nodes 5 -rounds 1 -verbose"
|
||||
echo ""
|
||||
|
||||
echo "4. Testing consensus tool"
|
||||
echo "------------------------"
|
||||
run_test "consensus help" "./bin/consensus -help"
|
||||
run_test "consensus info chain" "./bin/consensus -engine chain -action info"
|
||||
run_test "consensus info dag" "./bin/consensus -engine dag -action info"
|
||||
run_test "consensus info pq" "./bin/consensus -engine pq -action info"
|
||||
run_test "consensus test chain" "./bin/consensus -engine chain -action test"
|
||||
run_test "consensus test dag" "./bin/consensus -engine dag -action test"
|
||||
run_test "consensus test pq" "./bin/consensus -engine pq -action test"
|
||||
run_test "consensus health chain" "./bin/consensus -engine chain -action health"
|
||||
run_test "consensus health dag" "./bin/consensus -engine dag -action health"
|
||||
run_test "consensus health pq" "./bin/consensus -engine pq -action health"
|
||||
run_test "consensus mainnet" "./bin/consensus -network mainnet -action info"
|
||||
run_test "consensus testnet" "./bin/consensus -network testnet -action info"
|
||||
run_test "consensus local" "./bin/consensus -network local -action info"
|
||||
echo ""
|
||||
|
||||
echo "5. Testing params tool"
|
||||
echo "------------------------"
|
||||
run_test "params help" "./bin/params -help"
|
||||
run_test "params mainnet" "./bin/params -network mainnet"
|
||||
run_test "params testnet" "./bin/params -network testnet"
|
||||
run_test "params local" "./bin/params -network local"
|
||||
run_test "params xchain" "./bin/params -network xchain"
|
||||
run_test "params json mainnet" "./bin/params -network mainnet -json"
|
||||
run_test "params json testnet" "./bin/params -network testnet -json"
|
||||
run_test "params json local" "./bin/params -network local -json"
|
||||
run_test "params json xchain" "./bin/params -network xchain -json"
|
||||
echo ""
|
||||
|
||||
echo "==========================================="
|
||||
echo " TEST SUMMARY"
|
||||
echo "==========================================="
|
||||
echo -e "Tests Passed: ${GREEN}$TESTS_PASSED${NC}"
|
||||
echo -e "Tests Failed: ${RED}$TESTS_FAILED${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TESTS_FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}All tests passed successfully!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}Some tests failed. Please review the output above.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Comprehensive validation script for Lux Consensus
|
||||
# Ensures 100% passing status across all components
|
||||
|
||||
set -e
|
||||
|
||||
echo "======================================"
|
||||
echo " Lux Consensus Validation Suite "
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# Color codes
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Track overall status
|
||||
ALL_PASS=true
|
||||
|
||||
# Function to check status
|
||||
check_status() {
|
||||
local name="$1"
|
||||
local command="$2"
|
||||
|
||||
printf "%-30s" "$name"
|
||||
if eval "$command" > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ PASS${NC}"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗ FAIL${NC}"
|
||||
ALL_PASS=false
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "1. GO TESTS"
|
||||
echo "----------------------------------------"
|
||||
check_status "Running all tests..." "go test ./..."
|
||||
TOTAL_PACKAGES=$(go list ./... | wc -l)
|
||||
TESTED_PACKAGES=$(go test ./... 2>&1 | grep -c "^ok" || true)
|
||||
echo " Packages tested: $TESTED_PACKAGES"
|
||||
echo " Total packages: $TOTAL_PACKAGES"
|
||||
echo ""
|
||||
|
||||
echo "2. LINTING"
|
||||
echo "----------------------------------------"
|
||||
check_status "Running golangci-lint..." "golangci-lint run --timeout=10m 2>&1 | grep -v '^level' | grep -v '^$' | test \$(wc -l) -eq 0"
|
||||
echo ""
|
||||
|
||||
echo "3. BENCHMARKS"
|
||||
echo "----------------------------------------"
|
||||
check_status "Config benchmarks..." "go test -bench=. -benchtime=100ms -run=XXX ./config"
|
||||
check_status "Engine PQ benchmarks..." "go test -bench=. -benchtime=100ms -run=XXX ./engine/pq"
|
||||
check_status "Protocol benchmarks..." "go test -bench=. -benchtime=100ms -run=XXX ./protocol/field ./protocol/quasar"
|
||||
check_status "QZMQ benchmarks..." "go test -bench=. -benchtime=100ms -run=XXX ./qzmq"
|
||||
echo ""
|
||||
|
||||
echo "4. CLI TOOLS"
|
||||
echo "----------------------------------------"
|
||||
check_status "bench tool..." "./bin/bench -help"
|
||||
check_status "checker tool..." "./bin/checker -help"
|
||||
check_status "sim tool..." "./bin/sim -help"
|
||||
check_status "consensus tool..." "./bin/consensus -help"
|
||||
check_status "params tool..." "./bin/params -help"
|
||||
echo ""
|
||||
|
||||
echo "5. TOOL FUNCTIONALITY"
|
||||
echo "----------------------------------------"
|
||||
check_status "bench chain engine..." "./bin/bench -engine chain -blocks 10 -duration 1s"
|
||||
check_status "checker all engines..." "./bin/checker -engine all"
|
||||
check_status "sim network..." "./bin/sim -nodes 5 -rounds 2"
|
||||
check_status "consensus info..." "./bin/consensus -action info"
|
||||
check_status "params mainnet..." "./bin/params -network mainnet"
|
||||
echo ""
|
||||
|
||||
echo "6. BUILD VERIFICATION"
|
||||
echo "----------------------------------------"
|
||||
check_status "Clean build..." "make clean && make build"
|
||||
echo ""
|
||||
|
||||
echo "======================================"
|
||||
echo " VALIDATION SUMMARY "
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
if [ "$ALL_PASS" = true ]; then
|
||||
echo -e "${GREEN}✓ 100% PASSING${NC}"
|
||||
echo ""
|
||||
echo "All components validated successfully:"
|
||||
echo " • All Go tests pass"
|
||||
echo " • No linting errors"
|
||||
echo " • All benchmarks run"
|
||||
echo " • All CLI tools functional"
|
||||
echo " • Build system operational"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}✗ VALIDATION FAILED${NC}"
|
||||
echo ""
|
||||
echo "Some components failed validation."
|
||||
echo "Please review the output above."
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,11 +1,8 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -52,91 +49,6 @@ func TestE2EConsensusEngine(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestE2EConsensusServer tests the HTTP server endpoints
|
||||
func TestE2EConsensusServer(t *testing.T) {
|
||||
serverURL := "http://localhost:9090"
|
||||
|
||||
// Probe once. If port :9090 doesn't host our consensus server
|
||||
// (no listener, or a different service answering), skip the
|
||||
// whole suite — these tests require `consensus serve` running
|
||||
// alongside. They are NOT a unit test of the consensus engine.
|
||||
probe, probeErr := http.Get(serverURL + "/health")
|
||||
if probeErr != nil {
|
||||
t.Skipf("Consensus server not running at %s: %v", serverURL, probeErr)
|
||||
}
|
||||
probe.Body.Close()
|
||||
if probe.StatusCode != http.StatusOK {
|
||||
t.Skipf("Port %s answered with status %d (not our consensus server)", serverURL, probe.StatusCode)
|
||||
}
|
||||
|
||||
// Test health endpoint
|
||||
t.Run("HealthCheck", func(t *testing.T) {
|
||||
resp, err := http.Get(serverURL + "/health")
|
||||
if err != nil {
|
||||
t.Skipf("Server not running: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Health check failed: status %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
// Test status endpoint
|
||||
t.Run("Status", func(t *testing.T) {
|
||||
resp, err := http.Get(serverURL + "/status")
|
||||
if err != nil {
|
||||
t.Skipf("Server not running: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var status map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
|
||||
t.Errorf("Failed to decode status: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
healthy, ok := status["healthy"].(bool)
|
||||
if !ok {
|
||||
t.Skipf("Server response missing 'healthy' field (not our consensus server): %+v", status)
|
||||
}
|
||||
if !healthy {
|
||||
t.Error("Engine not healthy")
|
||||
}
|
||||
|
||||
t.Logf("Status: %+v", status)
|
||||
})
|
||||
|
||||
// Test consensus processing
|
||||
t.Run("ConsensusRound", func(t *testing.T) {
|
||||
payload := map[string]interface{}{
|
||||
"block_id": "test-block-123",
|
||||
"votes": map[string]int{
|
||||
"node1": 1,
|
||||
"node2": 1,
|
||||
"node3": 1,
|
||||
"node4": 0,
|
||||
},
|
||||
}
|
||||
|
||||
body, _ := json.Marshal(payload)
|
||||
resp, err := http.Post(serverURL+"/consensus", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
t.Skipf("Server not running: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Errorf("Failed to decode result: %v", err)
|
||||
}
|
||||
|
||||
// With 75% votes (3/4), consensus should be reached (alpha=0.8 for local)
|
||||
// Actually with local params alpha might be lower
|
||||
t.Logf("Consensus result: %+v", result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestE2ESimulation runs a full simulation
|
||||
func TestE2ESimulation(t *testing.T) {
|
||||
params := config.LocalParams()
|
||||
|
||||
Reference in New Issue
Block a user