mirror of
https://github.com/luxfi/netrunner.git
synced 2026-07-27 00:04:23 +00:00
Fix imports to use separate packages instead of node monolith
- Use luxfi/consensus/validators instead of node/consensus/validators - Use luxfi/ids instead of node/ids - Use luxfi/log instead of node/utils/logging - Use luxfi/utils/set instead of node/utils/set - Use luxfi/genesis instead of node/genesis - Use luxfi/evm/plugin/evm/client instead of geth/plugin/evm/client Removes dependency on non-existent node packages in v1.21+
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
# Lux Netrunner Release Workflow Analysis
|
||||
|
||||
**Date**: 2025-11-12
|
||||
**Current Version**: v1.13.5-lux.3
|
||||
**Repository**: luxfi/netrunner
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Existing Workflow (`.github/workflows/release.yml`)
|
||||
- ✅ Triggers on tag push (`tags: ["*"]`)
|
||||
- ✅ Uses GoReleaser for builds
|
||||
- ✅ Supports Linux amd64/arm64, Darwin amd64/arm64
|
||||
- ❌ **Missing Windows support**
|
||||
- ❌ **Old Go version** (1.19 vs project requires 1.25.4)
|
||||
- ❌ **Complex osxcross setup** (may fail, outdated SDK)
|
||||
- ❌ **No semantic version validation**
|
||||
- ❌ **No changelog generation**
|
||||
- ⚠️ **gh CLI error**: Tried to query ava-labs/netrunner instead of luxfi/netrunner
|
||||
|
||||
### Existing GoReleaser Config (`.goreleaser.yml`)
|
||||
- ✅ Supports Linux/Darwin amd64/arm64
|
||||
- ✅ CGO enabled with portable BLST
|
||||
- ✅ Cross-compilation setup for ARM64
|
||||
- ✅ Version injection via ldflags
|
||||
- ❌ **No Windows in config**
|
||||
- ❌ **No archive configuration** (defaults may not match requirements)
|
||||
- ❌ **No checksum configuration**
|
||||
- ❌ **osxcross clang references** (oa64-clang, o64-clang) may not work in GitHub Actions
|
||||
|
||||
### Issues Identified
|
||||
|
||||
1. **Go Version Mismatch**: Workflow uses Go 1.19, project requires 1.25.4
|
||||
2. **Windows Not Supported**: goreleaser.yml doesn't include Windows targets
|
||||
3. **osxcross Complexity**: Manual SDK download/build (30+ min), fragile
|
||||
4. **No Version Validation**: Doesn't prevent v2.x.x tags (Go module breaking change)
|
||||
5. **gh CLI Misconfiguration**: Queried wrong repo (ava-labs vs luxfi)
|
||||
6. **No Releases Exist**: Despite having tags, no GitHub releases created
|
||||
|
||||
## Recommended Solution
|
||||
|
||||
### Option 1: Enhanced GoReleaser (RECOMMENDED)
|
||||
**Pros**:
|
||||
- Leverages existing `.goreleaser.yml`
|
||||
- Automatic archive creation, checksums, changelog
|
||||
- Built-in GitHub release creation
|
||||
- Industry standard tool
|
||||
- Minimal workflow maintenance
|
||||
|
||||
**Cons**:
|
||||
- CGO cross-compilation for Darwin is complex
|
||||
- May need goreleaser-cross Docker image for full cross-compilation
|
||||
|
||||
**Implementation**:
|
||||
- Update `.goreleaser.yml` to include Windows
|
||||
- Use `goreleaser/goreleaser-cross-action` for CGO cross-compilation
|
||||
- Add semantic version validation step
|
||||
- Configure proper archive formats and checksums
|
||||
|
||||
### Option 2: Manual Build Matrix
|
||||
**Pros**:
|
||||
- Full control over build process
|
||||
- No external tool dependencies
|
||||
- Easier debugging
|
||||
|
||||
**Cons**:
|
||||
- More YAML to maintain
|
||||
- Manual archive creation
|
||||
- Manual checksum generation
|
||||
- No automatic changelog
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 1. Update `.goreleaser.yml`
|
||||
```yaml
|
||||
builds:
|
||||
- id: netrunner
|
||||
main: ./main.go
|
||||
binary: netrunner
|
||||
flags:
|
||||
- -v
|
||||
ldflags:
|
||||
- -X 'github.com/luxfi/netrunner/cmd.Version={{.Version}}'
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows # ADD WINDOWS
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CGO_CFLAGS=-O -D__BLST_PORTABLE__
|
||||
overrides:
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
env:
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
goarm: 8
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
goamd64: v1
|
||||
- goos: windows # WINDOWS OVERRIDE
|
||||
goarch: amd64
|
||||
env:
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
env:
|
||||
- CC=aarch64-w64-mingw32-gcc
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm64 # Skip Windows ARM64 if no cross-compiler
|
||||
|
||||
archives:
|
||||
- id: netrunner
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
name_template: "netrunner_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
files:
|
||||
- LICENSE
|
||||
- README.md
|
||||
|
||||
checksum:
|
||||
name_template: 'SHA256SUMS'
|
||||
algorithm: sha256
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: luxfi
|
||||
name: netrunner
|
||||
draft: false
|
||||
prerelease: auto
|
||||
mode: append
|
||||
header: |
|
||||
## Lux Netrunner {{ .Tag }}
|
||||
|
||||
Network orchestration and testing framework for Lux blockchain.
|
||||
footer: |
|
||||
**Full Changelog**: https://github.com/luxfi/netrunner/compare/{{ .PreviousTag }}...{{ .Tag }}
|
||||
```
|
||||
|
||||
### 2. Update `.github/workflows/release.yml`
|
||||
```yaml
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
validate-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate Semantic Version
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
echo "Validating tag: $TAG"
|
||||
|
||||
# Must start with v
|
||||
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then
|
||||
echo "Error: Tag must follow semantic versioning (v1.2.3 or v1.2.3-suffix)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract major version
|
||||
MAJOR=$(echo "$TAG" | sed -E 's/^v([0-9]+)\..*/\1/')
|
||||
|
||||
# Prevent v2.x.x+ (Go module breaking change)
|
||||
if [ "$MAJOR" -ge 2 ]; then
|
||||
echo "Error: Major version must be < 2 (Go module constraint)"
|
||||
echo "For v2+, update go.mod to github.com/luxfi/netrunner/v2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Version validation passed: $TAG"
|
||||
|
||||
release:
|
||||
needs: validate-version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: true
|
||||
|
||||
- name: Run Tests
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-cross-action@v4
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
## Testing the Workflow
|
||||
|
||||
### Local Testing (requires goreleaser)
|
||||
```bash
|
||||
# Install goreleaser
|
||||
brew install goreleaser/tap/goreleaser # macOS
|
||||
# or
|
||||
go install github.com/goreleaser/goreleaser@latest
|
||||
|
||||
# Test build without releasing
|
||||
cd /Users/z/work/lux/netrunner
|
||||
goreleaser build --snapshot --clean
|
||||
|
||||
# Test full release process (no push)
|
||||
goreleaser release --snapshot --clean --skip-publish
|
||||
```
|
||||
|
||||
### Dry Run on GitHub
|
||||
```bash
|
||||
# Create a test tag
|
||||
git tag -a v1.14.0-rc.1 -m "Release candidate for testing"
|
||||
git push origin v1.14.0-rc.1
|
||||
|
||||
# Monitor workflow at:
|
||||
# https://github.com/luxfi/netrunner/actions
|
||||
|
||||
# Delete test release if successful
|
||||
gh release delete v1.14.0-rc.1 --yes
|
||||
git tag -d v1.14.0-rc.1
|
||||
git push origin :refs/tags/v1.14.0-rc.1
|
||||
```
|
||||
|
||||
## Recommended First Release
|
||||
|
||||
### Version: v1.14.0
|
||||
|
||||
**Rationale**:
|
||||
- Current: v1.13.5-lux.3
|
||||
- Next minor: v1.14.0
|
||||
- Clean version (no suffix) for major release
|
||||
- Includes all recent improvements and fixes
|
||||
|
||||
**Pre-Release Checklist**:
|
||||
- [ ] Merge all pending changes
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Run full test suite
|
||||
- [ ] Test build locally with goreleaser
|
||||
- [ ] Update documentation with new version
|
||||
- [ ] Create tag: `git tag -a v1.14.0 -m "Release v1.14.0"`
|
||||
- [ ] Push tag: `git push origin v1.14.0`
|
||||
- [ ] Verify GitHub release created
|
||||
- [ ] Test downloading and running binaries
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Update Configuration (This Session)
|
||||
1. ✅ Analyze current workflow
|
||||
2. ⏳ Update `.goreleaser.yml` with Windows support
|
||||
3. ⏳ Update `.github/workflows/release.yml` with new workflow
|
||||
4. ⏳ Test locally with goreleaser --snapshot
|
||||
|
||||
### Phase 2: Test Release (Next)
|
||||
1. Create release candidate tag (v1.14.0-rc.1)
|
||||
2. Verify all platforms build successfully
|
||||
3. Download and test binaries on each platform
|
||||
4. Fix any issues found
|
||||
5. Delete RC release and tag
|
||||
|
||||
### Phase 3: Production Release
|
||||
1. Create v1.14.0 tag
|
||||
2. Monitor workflow execution
|
||||
3. Verify release artifacts
|
||||
4. Update documentation
|
||||
5. Announce release
|
||||
|
||||
## Cross-Compilation Notes
|
||||
|
||||
### CGO Challenges
|
||||
**Problem**: CGO requires platform-specific compilers
|
||||
- Linux ARM64: `aarch64-linux-gnu-gcc`
|
||||
- Darwin ARM64: Native on arm64 runner or osxcross
|
||||
- Darwin AMD64: Native on amd64 runner or osxcross
|
||||
- Windows AMD64: `x86_64-w64-mingw32-gcc`
|
||||
|
||||
**Solution**: Use `goreleaser/goreleaser-cross-action`
|
||||
- Provides pre-configured cross-compilation environment
|
||||
- Includes all necessary toolchains
|
||||
- Handles osxcross complexity
|
||||
- Supports CGO for all platforms
|
||||
|
||||
### Alternative: Native Runners
|
||||
For maximum reliability, use platform-specific runners:
|
||||
|
||||
```yaml
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
- os: ubuntu-latest
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
- os: macos-latest
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
- os: macos-14 # M1 runner
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
- os: windows-latest
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
```
|
||||
|
||||
**Trade-offs**:
|
||||
- More workflow complexity
|
||||
- Longer total runtime (sequential builds)
|
||||
- Higher GitHub Actions minutes usage
|
||||
- But: No cross-compilation issues
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
### Linux
|
||||
- ✅ Easy cross-compilation with apt packages
|
||||
- ✅ Static binaries possible
|
||||
- Target: glibc 2.17+ (CentOS 7+)
|
||||
|
||||
### macOS
|
||||
- ⚠️ osxcross is complex but works
|
||||
- ⚠️ SDK licensing (GitHub has enterprise license)
|
||||
- Alternative: Use native macOS runners
|
||||
- Target: macOS 11+ (Intel and Apple Silicon)
|
||||
|
||||
### Windows
|
||||
- ⚠️ CGO requires mingw-w64 toolchain
|
||||
- ⚠️ Limited testing (no Windows VM in project)
|
||||
- Consider: Windows CGO may not be required
|
||||
- Target: Windows 10+ (64-bit)
|
||||
|
||||
### Can We Disable CGO for Windows?
|
||||
**Investigation needed**:
|
||||
```bash
|
||||
# Try building without CGO on Windows
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o netrunner.exe
|
||||
```
|
||||
|
||||
If successful, simplify goreleaser.yml:
|
||||
```yaml
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
env:
|
||||
- CGO_ENABLED=0 # Disable CGO for Windows
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Workflow Must:
|
||||
1. ✅ Build for all 5 platforms (Linux amd64/arm64, Darwin amd64/arm64, Windows amd64)
|
||||
2. ✅ Create archives (.tar.gz for Unix, .zip for Windows)
|
||||
3. ✅ Generate SHA256SUMS file
|
||||
4. ✅ Create GitHub release automatically
|
||||
5. ✅ Include changelog
|
||||
6. ✅ Mark latest release
|
||||
7. ✅ Validate semantic versioning
|
||||
8. ✅ Prevent v2.x.x releases without module path update
|
||||
|
||||
### Binaries Must:
|
||||
1. ✅ Run on target platforms
|
||||
2. ✅ Show correct version (`netrunner version`)
|
||||
3. ✅ Be reproducible (same input = same output)
|
||||
4. ✅ Include proper licensing info
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Update `.goreleaser.yml`** with Windows support and proper configuration
|
||||
2. **Update `.github/workflows/release.yml`** with improved workflow
|
||||
3. **Test locally** with `goreleaser build --snapshot --clean`
|
||||
4. **Create RC tag** for workflow testing
|
||||
5. **Validate binaries** on all platforms
|
||||
6. **Create v1.14.0 release**
|
||||
|
||||
## References
|
||||
|
||||
- GoReleaser Docs: https://goreleaser.com/
|
||||
- goreleaser-cross: https://github.com/goreleaser/goreleaser-cross
|
||||
- GitHub Actions: https://docs.github.com/en/actions
|
||||
- Semantic Versioning: https://semver.org/
|
||||
- Go Modules: https://go.dev/ref/mod#major-version-suffixes
|
||||
@@ -0,0 +1,470 @@
|
||||
# Lux Netrunner v1.14.0 Release Checklist
|
||||
|
||||
**Date**: 2025-11-12
|
||||
**Target Version**: v1.14.0
|
||||
**Current Version**: v1.13.5-lux.3
|
||||
|
||||
## Pre-Release Setup
|
||||
|
||||
### 1. Documentation Review
|
||||
- [ ] Read `/Users/z/work/lux/netrunner/RELEASE_ANALYSIS.md`
|
||||
- [ ] Read `/Users/z/work/lux/netrunner/RELEASE_TESTING.md`
|
||||
- [ ] Read `/Users/z/work/lux/netrunner/RELEASE_WORKFLOW_SUMMARY.md`
|
||||
- [ ] Understand workflow changes
|
||||
- [ ] Review goreleaser.yml changes
|
||||
|
||||
### 2. Local Environment
|
||||
- [ ] Go 1.25+ installed (`go version`)
|
||||
- [ ] Git configured (`git config --list`)
|
||||
- [ ] GitHub CLI installed (`gh --version`)
|
||||
- [ ] GitHub CLI authenticated (`gh auth status`)
|
||||
- [ ] Working directory clean (`git status`)
|
||||
- [ ] On main branch (`git checkout main`)
|
||||
- [ ] Latest changes pulled (`git pull origin main`)
|
||||
|
||||
### 3. Optional: Install goreleaser
|
||||
```bash
|
||||
brew install goreleaser/tap/goreleaser
|
||||
goreleaser --version
|
||||
```
|
||||
|
||||
## Phase 1: Local Testing (Optional but Recommended)
|
||||
|
||||
### Test Build
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Snapshot build (no tag required)
|
||||
goreleaser build --snapshot --clean
|
||||
|
||||
# Verify build succeeded
|
||||
ls -lh dist/
|
||||
```
|
||||
|
||||
**Expected Output**:
|
||||
```
|
||||
dist/netrunner_v1.14.0-next_linux_amd64/
|
||||
dist/netrunner_v1.14.0-next_linux_arm64/
|
||||
dist/netrunner_v1.14.0-next_darwin_amd64/
|
||||
dist/netrunner_v1.14.0-next_darwin_arm64/
|
||||
dist/netrunner_v1.14.0-next_windows_amd64/
|
||||
```
|
||||
|
||||
- [ ] Build completed without errors
|
||||
- [ ] All 5 platform directories present
|
||||
- [ ] Binary sizes reasonable (15-20 MB)
|
||||
|
||||
### Test Local Binary
|
||||
```bash
|
||||
# Test your platform's binary
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner version
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner --help
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner server --help
|
||||
```
|
||||
|
||||
- [ ] Binary executes without errors
|
||||
- [ ] Version shows correctly
|
||||
- [ ] Help commands work
|
||||
- [ ] No missing libraries
|
||||
|
||||
### Test Full Release (Local)
|
||||
```bash
|
||||
# Simulate full release
|
||||
goreleaser release --snapshot --clean --skip-publish
|
||||
|
||||
# Verify archives
|
||||
ls -lh dist/*.tar.gz dist/*.zip
|
||||
```
|
||||
|
||||
**Expected Archives**:
|
||||
- `netrunner_v1.14.0-next_linux_amd64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_linux_arm64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_darwin_amd64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_darwin_arm64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_windows_amd64.zip`
|
||||
- `SHA256SUMS`
|
||||
|
||||
- [ ] All 5 archives created
|
||||
- [ ] SHA256SUMS file present
|
||||
- [ ] Archives extract successfully
|
||||
- [ ] Checksums validate: `sha256sum -c dist/SHA256SUMS`
|
||||
|
||||
## Phase 2: Release Candidate Testing
|
||||
|
||||
### Create RC Tag
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Ensure clean state
|
||||
git status
|
||||
git pull origin main
|
||||
|
||||
# Create RC tag
|
||||
git tag -a v1.14.0-rc.1 -m "Release candidate v1.14.0-rc.1
|
||||
|
||||
Testing multi-platform release workflow:
|
||||
- Windows support
|
||||
- Enhanced automation
|
||||
- Comprehensive documentation"
|
||||
|
||||
# Push tag (triggers workflow)
|
||||
git push origin v1.14.0-rc.1
|
||||
```
|
||||
|
||||
- [ ] Tag created locally
|
||||
- [ ] Tag pushed to GitHub
|
||||
- [ ] Workflow triggered (check GitHub Actions)
|
||||
|
||||
### Monitor Workflow
|
||||
```bash
|
||||
# Watch workflow execution
|
||||
gh run watch
|
||||
|
||||
# Or view in browser
|
||||
gh workflow view release --web
|
||||
```
|
||||
|
||||
**Monitor Progress**:
|
||||
- [ ] ✅ validate-version job passes (~10 seconds)
|
||||
- [ ] ✅ test job passes (~40 seconds)
|
||||
- [ ] ✅ release job passes (~5-10 minutes)
|
||||
|
||||
**If any job fails**: Check logs with `gh run view --log`, fix issues, delete tag, recreate.
|
||||
|
||||
### Verify RC Release
|
||||
```bash
|
||||
# List releases
|
||||
gh release list
|
||||
|
||||
# View RC release
|
||||
gh release view v1.14.0-rc.1
|
||||
|
||||
# Download assets
|
||||
gh release download v1.14.0-rc.1 --dir ./test-release-rc1
|
||||
cd test-release-rc1
|
||||
```
|
||||
|
||||
- [ ] Release created on GitHub
|
||||
- [ ] All 6 assets present (5 binaries + checksums)
|
||||
- [ ] Release notes formatted correctly
|
||||
- [ ] Changelog generated properly
|
||||
- [ ] Marked as pre-release (if RC tag detected)
|
||||
|
||||
### Verify Assets
|
||||
```bash
|
||||
cd test-release-rc1
|
||||
|
||||
# Verify checksums
|
||||
sha256sum -c SHA256SUMS
|
||||
# or on macOS
|
||||
shasum -a 256 -c SHA256SUMS
|
||||
```
|
||||
|
||||
- [ ] All checksums valid
|
||||
- [ ] File sizes reasonable (15-20 MB compressed)
|
||||
- [ ] Archive names follow pattern: `netrunner_v1.14.0-rc.1_{os}_{arch}.{ext}`
|
||||
|
||||
### Test Downloaded Binaries
|
||||
|
||||
**Linux AMD64**:
|
||||
```bash
|
||||
tar -xzf netrunner_v1.14.0-rc.1_linux_amd64.tar.gz
|
||||
./netrunner version
|
||||
# Expected: netrunner version v1.14.0-rc.1
|
||||
```
|
||||
|
||||
- [ ] Extracts successfully
|
||||
- [ ] Binary executes
|
||||
- [ ] Version correct
|
||||
|
||||
**macOS (your platform)**:
|
||||
```bash
|
||||
tar -xzf netrunner_v1.14.0-rc.1_darwin_arm64.tar.gz
|
||||
./netrunner version
|
||||
./netrunner --help
|
||||
```
|
||||
|
||||
- [ ] Extracts successfully
|
||||
- [ ] Binary executes
|
||||
- [ ] No Gatekeeper issues (unsigned is expected)
|
||||
- [ ] Help works
|
||||
|
||||
**Windows** (if available or skip):
|
||||
```bash
|
||||
unzip netrunner_v1.14.0-rc.1_windows_amd64.zip
|
||||
./netrunner.exe version # In WSL or Windows
|
||||
```
|
||||
|
||||
- [ ] Extracts successfully
|
||||
- [ ] Binary executes (or skipped if no Windows access)
|
||||
|
||||
### Integration Test (Optional)
|
||||
```bash
|
||||
# Start a test network with RC binary
|
||||
./netrunner server --port=:8080 --grpc-gateway-port=:8081 &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Basic health check
|
||||
sleep 2
|
||||
curl http://localhost:8081/health
|
||||
|
||||
# Cleanup
|
||||
kill $SERVER_PID
|
||||
```
|
||||
|
||||
- [ ] Server starts successfully
|
||||
- [ ] Health endpoint responds
|
||||
- [ ] No crashes or errors
|
||||
|
||||
### RC Decision
|
||||
If all checks pass:
|
||||
- [ ] RC testing successful
|
||||
|
||||
If any checks fail:
|
||||
- [ ] Issues documented
|
||||
- [ ] Delete RC release: `gh release delete v1.14.0-rc.1 --yes`
|
||||
- [ ] Delete tags: `git tag -d v1.14.0-rc.1 && git push origin :refs/tags/v1.14.0-rc.1`
|
||||
- [ ] Fix issues
|
||||
- [ ] Repeat Phase 2 with v1.14.0-rc.2
|
||||
|
||||
### Cleanup RC
|
||||
```bash
|
||||
# Delete RC release (once satisfied)
|
||||
gh release delete v1.14.0-rc.1 --yes
|
||||
|
||||
# Delete local tag
|
||||
git tag -d v1.14.0-rc.1
|
||||
|
||||
# Delete remote tag
|
||||
git push origin :refs/tags/v1.14.0-rc.1
|
||||
|
||||
# Cleanup downloaded files
|
||||
cd ..
|
||||
rm -rf test-release-rc1
|
||||
```
|
||||
|
||||
- [ ] RC release deleted from GitHub
|
||||
- [ ] RC tag deleted locally and remotely
|
||||
- [ ] Test files cleaned up
|
||||
|
||||
## Phase 3: Production Release
|
||||
|
||||
### Pre-Production Checks
|
||||
- [ ] All RC tests passed
|
||||
- [ ] No critical issues found
|
||||
- [ ] Documentation updated with v1.14.0
|
||||
- [ ] CHANGELOG.md prepared (if exists)
|
||||
- [ ] README.md version references updated
|
||||
- [ ] All changes committed and pushed
|
||||
|
||||
### Create Production Tag
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Final sync
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git status # Ensure clean
|
||||
|
||||
# Create production tag
|
||||
git tag -a v1.14.0 -m "Release v1.14.0
|
||||
|
||||
Multi-platform release with comprehensive automation.
|
||||
|
||||
## What's New
|
||||
- Windows support (amd64)
|
||||
- Enhanced CI/CD with semantic versioning
|
||||
- Automated checksums and archives
|
||||
- Structured changelog generation
|
||||
- Comprehensive release documentation
|
||||
|
||||
## Platforms
|
||||
- Linux (amd64, arm64)
|
||||
- macOS (Intel, Apple Silicon)
|
||||
- Windows (amd64)
|
||||
|
||||
## Installation
|
||||
See https://github.com/luxfi/netrunner/releases/tag/v1.14.0
|
||||
|
||||
Full changelog: https://github.com/luxfi/netrunner/compare/v1.13.5-lux.3...v1.14.0"
|
||||
|
||||
# Push tag
|
||||
git push origin v1.14.0
|
||||
```
|
||||
|
||||
- [ ] Production tag created
|
||||
- [ ] Tag message detailed
|
||||
- [ ] Tag pushed to GitHub
|
||||
|
||||
### Monitor Production Release
|
||||
```bash
|
||||
# Watch workflow
|
||||
gh run watch
|
||||
|
||||
# Or view in browser
|
||||
gh workflow view release --web
|
||||
```
|
||||
|
||||
- [ ] ✅ validate-version passes
|
||||
- [ ] ✅ test passes
|
||||
- [ ] ✅ release passes
|
||||
- [ ] No errors in logs
|
||||
|
||||
### Verify Production Release
|
||||
```bash
|
||||
# View release
|
||||
gh release view v1.14.0
|
||||
|
||||
# Verify assets
|
||||
gh release view v1.14.0 --json assets --jq '.assets[].name'
|
||||
```
|
||||
|
||||
**Expected Assets**:
|
||||
```
|
||||
netrunner_v1.14.0_linux_amd64.tar.gz
|
||||
netrunner_v1.14.0_linux_arm64.tar.gz
|
||||
netrunner_v1.14.0_darwin_amd64.tar.gz
|
||||
netrunner_v1.14.0_darwin_arm64.tar.gz
|
||||
netrunner_v1.14.0_windows_amd64.zip
|
||||
SHA256SUMS
|
||||
```
|
||||
|
||||
- [ ] All 6 assets present
|
||||
- [ ] Release marked as "Latest"
|
||||
- [ ] Release notes formatted correctly
|
||||
- [ ] Changelog populated
|
||||
- [ ] Download URLs work
|
||||
|
||||
### Download and Verify Production
|
||||
```bash
|
||||
# Download production release
|
||||
gh release download v1.14.0 --dir ./release-v1.14.0
|
||||
cd release-v1.14.0
|
||||
|
||||
# Verify checksums
|
||||
sha256sum -c SHA256SUMS
|
||||
|
||||
# Test your platform
|
||||
tar -xzf netrunner_v1.14.0_darwin_arm64.tar.gz
|
||||
./netrunner version
|
||||
```
|
||||
|
||||
- [ ] All checksums valid
|
||||
- [ ] Binary version shows v1.14.0
|
||||
- [ ] No issues running binary
|
||||
|
||||
## Phase 4: Post-Release
|
||||
|
||||
### Documentation Updates
|
||||
- [ ] Update `/Users/z/work/lux/netrunner/README.md` with v1.14.0 install URLs
|
||||
- [ ] Update `/Users/z/work/lux/netrunner/CLAUDE.md` with release notes
|
||||
- [ ] Update any version references in docs
|
||||
- [ ] Commit and push documentation updates
|
||||
|
||||
### Announcement (Optional)
|
||||
- [ ] Draft release announcement
|
||||
- [ ] Post to Discord/Slack (if applicable)
|
||||
- [ ] Update project website (if applicable)
|
||||
- [ ] Tweet/social media (if applicable)
|
||||
|
||||
### Monitoring
|
||||
- [ ] Watch for issue reports
|
||||
- [ ] Monitor download statistics
|
||||
- [ ] Check for platform-specific problems
|
||||
- [ ] Document any unexpected behaviors
|
||||
|
||||
### Repository Cleanup
|
||||
```bash
|
||||
# Remove local test artifacts
|
||||
cd /Users/z/work/lux/netrunner
|
||||
rm -rf dist/
|
||||
rm -rf release-v1.14.0/
|
||||
```
|
||||
|
||||
- [ ] Local test files removed
|
||||
- [ ] Working directory clean
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ Release is successful when:
|
||||
1. Workflow completes without errors
|
||||
2. All 5 platform binaries built and uploaded
|
||||
3. SHA256SUMS file present and valid
|
||||
4. Release marked as "Latest" on GitHub
|
||||
5. Binaries run on target platforms
|
||||
6. Version output matches tag
|
||||
7. No critical issues reported within 24 hours
|
||||
|
||||
## Rollback Plan (If Needed)
|
||||
|
||||
If critical issue discovered post-release:
|
||||
|
||||
1. **Document Issue**:
|
||||
```bash
|
||||
# Create issue on GitHub
|
||||
gh issue create --title "Critical issue in v1.14.0" --body "..."
|
||||
```
|
||||
|
||||
2. **Create Hotfix** (if fixable quickly):
|
||||
```bash
|
||||
# Fix issue, commit
|
||||
git commit -am "fix: critical issue in v1.14.0"
|
||||
git push origin main
|
||||
|
||||
# Create hotfix release
|
||||
git tag -a v1.14.1 -m "Hotfix release v1.14.1"
|
||||
git push origin v1.14.1
|
||||
```
|
||||
|
||||
3. **Mark Release as Broken** (if not fixable):
|
||||
```bash
|
||||
# Edit release notes to add warning
|
||||
gh release edit v1.14.0 --notes "⚠️ **DO NOT USE** - Critical issue found. Use v1.13.5-lux.3 or wait for v1.14.1"
|
||||
```
|
||||
|
||||
4. **Revert to Previous** (worst case):
|
||||
```bash
|
||||
# Mark as pre-release (removes "Latest" badge)
|
||||
gh release edit v1.14.0 --prerelease
|
||||
|
||||
# Users will see v1.13.5-lux.3 as latest
|
||||
```
|
||||
|
||||
## Future Releases
|
||||
|
||||
For subsequent releases (v1.15.0, v1.16.0, etc.):
|
||||
|
||||
1. ✅ Workflow is now automated - no setup needed
|
||||
2. ✅ Just create tag and push: `git tag -a v1.15.0 -m "..." && git push origin v1.15.0`
|
||||
3. ✅ Workflow handles everything automatically
|
||||
4. ⚠️ Still test RC first if major changes
|
||||
|
||||
## Notes
|
||||
|
||||
- **First Release**: v1.14.0 is the first automated multi-platform release
|
||||
- **Breaking Changes**: If introducing breaking changes, document in release notes
|
||||
- **Platform Support**: Can add more platforms by editing `.goreleaser.yml`
|
||||
- **Build Time**: Expect 5-10 minutes for full release build
|
||||
- **Rate Limits**: GitHub has rate limits - wait between RC and production
|
||||
|
||||
## Questions/Issues
|
||||
|
||||
If problems arise during release:
|
||||
1. Check workflow logs: `gh run view --log`
|
||||
2. Review documentation in `/Users/z/work/lux/netrunner/RELEASE_*.md`
|
||||
3. Test locally with goreleaser snapshot
|
||||
4. Check goreleaser-cross-action issues
|
||||
5. Verify GitHub token permissions
|
||||
|
||||
## Completed Checklist Summary
|
||||
|
||||
When finished, you should have:
|
||||
- ✅ Tested workflow locally (optional)
|
||||
- ✅ Tested RC (v1.14.0-rc.1)
|
||||
- ✅ Verified all platforms
|
||||
- ✅ Created production release (v1.14.0)
|
||||
- ✅ Verified production artifacts
|
||||
- ✅ Updated documentation
|
||||
- ✅ Monitored for issues
|
||||
|
||||
**Congratulations!** 🎉 First multi-platform automated release complete!
|
||||
@@ -0,0 +1,224 @@
|
||||
# Lux Netrunner Release - Quick Start Guide
|
||||
|
||||
**TL;DR**: Create tag → Push tag → Done! 🚀
|
||||
|
||||
## The Shortest Path to Release
|
||||
|
||||
```bash
|
||||
# 1. Test locally (optional but recommended)
|
||||
goreleaser build --snapshot --clean
|
||||
./dist/netrunner_*/netrunner version
|
||||
|
||||
# 2. Create RC tag
|
||||
git tag -a v1.14.0-rc.1 -m "RC for v1.14.0"
|
||||
git push origin v1.14.0-rc.1
|
||||
|
||||
# 3. Watch workflow (open in browser)
|
||||
gh workflow view release --web
|
||||
|
||||
# 4. Download and test
|
||||
gh release download v1.14.0-rc.1
|
||||
sha256sum -c SHA256SUMS
|
||||
./netrunner version
|
||||
|
||||
# 5. If good, clean up RC
|
||||
gh release delete v1.14.0-rc.1 --yes
|
||||
git tag -d v1.14.0-rc.1
|
||||
git push origin :refs/tags/v1.14.0-rc.1
|
||||
|
||||
# 6. Create production release
|
||||
git tag -a v1.14.0 -m "Release v1.14.0"
|
||||
git push origin v1.14.0
|
||||
|
||||
# 7. Verify
|
||||
gh release view v1.14.0
|
||||
```
|
||||
|
||||
## What You Get
|
||||
|
||||
### Platforms (5 total)
|
||||
- ✅ Linux amd64
|
||||
- ✅ Linux arm64
|
||||
- ✅ macOS Intel (amd64)
|
||||
- ✅ macOS Apple Silicon (arm64)
|
||||
- ✅ Windows amd64
|
||||
|
||||
### Artifacts
|
||||
```
|
||||
netrunner_v1.14.0_linux_amd64.tar.gz
|
||||
netrunner_v1.14.0_linux_arm64.tar.gz
|
||||
netrunner_v1.14.0_darwin_amd64.tar.gz
|
||||
netrunner_v1.14.0_darwin_arm64.tar.gz
|
||||
netrunner_v1.14.0_windows_amd64.zip
|
||||
SHA256SUMS
|
||||
```
|
||||
|
||||
### Features
|
||||
- ✅ Automatic checksums (SHA256)
|
||||
- ✅ Structured changelog (feat/fix/perf)
|
||||
- ✅ Release notes with download instructions
|
||||
- ✅ Semantic version validation
|
||||
- ✅ Test suite runs before release
|
||||
- ✅ Cross-platform compilation
|
||||
|
||||
## Workflow Jobs
|
||||
|
||||
```
|
||||
validate-version (10s)
|
||||
↓
|
||||
test (40s)
|
||||
↓
|
||||
release (5-10min)
|
||||
```
|
||||
|
||||
**Total Time**: ~6-11 minutes
|
||||
|
||||
## Version Rules
|
||||
|
||||
✅ **Valid**:
|
||||
- `v1.14.0`
|
||||
- `v1.14.0-rc.1`
|
||||
- `v1.14.0-alpha.2`
|
||||
- `v1.14.0+build.123`
|
||||
|
||||
❌ **Invalid**:
|
||||
- `1.14.0` (missing 'v')
|
||||
- `v1.14` (incomplete semver)
|
||||
- `v2.0.0` (v2+ requires module path change)
|
||||
- `release-1.14.0` (not semver)
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Local test (no tag needed)
|
||||
goreleaser build --snapshot --clean
|
||||
|
||||
# Create tag
|
||||
git tag -a v1.14.0 -m "Release v1.14.0"
|
||||
|
||||
# Push tag (triggers release)
|
||||
git push origin v1.14.0
|
||||
|
||||
# Watch workflow
|
||||
gh run watch
|
||||
|
||||
# View release
|
||||
gh release view v1.14.0
|
||||
|
||||
# Download release
|
||||
gh release download v1.14.0
|
||||
|
||||
# Delete release
|
||||
gh release delete v1.14.0 --yes
|
||||
|
||||
# Delete tag
|
||||
git tag -d v1.14.0
|
||||
git push origin :refs/tags/v1.14.0
|
||||
```
|
||||
|
||||
## When Things Go Wrong
|
||||
|
||||
### Workflow fails validation
|
||||
**Why**: Tag format invalid
|
||||
**Fix**: Delete tag, recreate with correct format
|
||||
|
||||
### Tests fail
|
||||
**Why**: Code has failing tests
|
||||
**Fix**: Fix tests, push, recreate tag
|
||||
|
||||
### Build fails
|
||||
**Why**: Cross-compilation error
|
||||
**Fix**: Check workflow logs, may need goreleaser.yml adjustment
|
||||
|
||||
### Binary doesn't run
|
||||
**Why**: Missing dependencies or wrong platform
|
||||
**Fix**: Test on actual hardware, check CGO settings
|
||||
|
||||
## Pro Tips
|
||||
|
||||
1. **Always test RC first**: `v1.14.0-rc.1` before `v1.14.0`
|
||||
2. **Use goreleaser locally**: Catch issues before pushing
|
||||
3. **Keep changelog clean**: Use conventional commits (feat/fix/docs)
|
||||
4. **Monitor workflow**: Don't walk away after push
|
||||
5. **Verify checksums**: Always `sha256sum -c SHA256SUMS`
|
||||
|
||||
## Files to Know
|
||||
|
||||
- `.goreleaser.yml` - Build configuration
|
||||
- `.github/workflows/release.yml` - Workflow automation
|
||||
- `RELEASE_ANALYSIS.md` - Detailed architecture
|
||||
- `RELEASE_TESTING.md` - Comprehensive testing guide
|
||||
- `RELEASE_CHECKLIST.md` - Step-by-step checklist
|
||||
|
||||
## Next Release (Future)
|
||||
|
||||
For v1.15.0 and beyond:
|
||||
```bash
|
||||
git tag -a v1.15.0 -m "Release v1.15.0"
|
||||
git push origin v1.15.0
|
||||
# That's it! 🎉
|
||||
```
|
||||
|
||||
Everything else is automated.
|
||||
|
||||
## Install goreleaser (One-Time)
|
||||
|
||||
```bash
|
||||
# macOS/Linux
|
||||
brew install goreleaser/tap/goreleaser
|
||||
|
||||
# Or via Go
|
||||
go install github.com/goreleaser/goreleaser@latest
|
||||
|
||||
# Verify
|
||||
goreleaser --version
|
||||
```
|
||||
|
||||
## Expected Timeline
|
||||
|
||||
| Phase | Time |
|
||||
|-------|------|
|
||||
| Local test (optional) | 2 min |
|
||||
| RC creation | 1 min |
|
||||
| RC workflow | 6-11 min |
|
||||
| RC testing | 5 min |
|
||||
| RC cleanup | 1 min |
|
||||
| Production creation | 1 min |
|
||||
| Production workflow | 6-11 min |
|
||||
| Production testing | 3 min |
|
||||
| **Total** | **25-41 minutes** |
|
||||
|
||||
Without RC testing: **7-12 minutes**
|
||||
|
||||
## Help
|
||||
|
||||
Detailed help available in:
|
||||
- `RELEASE_CHECKLIST.md` - Full checklist
|
||||
- `RELEASE_TESTING.md` - Testing scenarios
|
||||
- `RELEASE_ANALYSIS.md` - Architecture details
|
||||
|
||||
Quick help:
|
||||
```bash
|
||||
# Workflow status
|
||||
gh run list --workflow=release.yml
|
||||
|
||||
# View logs
|
||||
gh run view --log
|
||||
|
||||
# Release list
|
||||
gh release list
|
||||
|
||||
# Asset list
|
||||
gh release view v1.14.0 --json assets
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
1. ✅ Workflow is fully automated
|
||||
2. ✅ Push tag → Get release
|
||||
3. ✅ 5 platforms supported
|
||||
4. ✅ Checksums included
|
||||
5. ✅ Changelog generated
|
||||
6. ✅ Tests run automatically
|
||||
7. ✅ Version validated
|
||||
8. ✅ Ready for v1.14.0! 🚀
|
||||
@@ -0,0 +1,443 @@
|
||||
# Release Workflow Testing Guide
|
||||
|
||||
**Project**: Lux Netrunner
|
||||
**Repository**: luxfi/netrunner
|
||||
**Date**: 2025-11-12
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Local Testing (Optional but Recommended)
|
||||
|
||||
Install goreleaser for local testing:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install goreleaser/tap/goreleaser
|
||||
|
||||
# Linux
|
||||
brew install goreleaser/tap/goreleaser
|
||||
# or
|
||||
go install github.com/goreleaser/goreleaser@latest
|
||||
|
||||
# Verify installation
|
||||
goreleaser --version
|
||||
```
|
||||
|
||||
### GitHub Setup
|
||||
|
||||
Ensure you have:
|
||||
- Push access to luxfi/netrunner repository
|
||||
- GitHub CLI installed (`brew install gh` or see https://cli.github.com/)
|
||||
- Authenticated with `gh auth login`
|
||||
|
||||
## Testing Workflow Locally
|
||||
|
||||
### 1. Test Build Without Release
|
||||
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Snapshot build (no git tags required)
|
||||
goreleaser build --snapshot --clean
|
||||
|
||||
# Output will be in ./dist/
|
||||
ls -lh dist/
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
netrunner_v1.14.0-next_linux_amd64/
|
||||
netrunner_v1.14.0-next_linux_arm64/
|
||||
netrunner_v1.14.0-next_darwin_amd64/
|
||||
netrunner_v1.14.0-next_darwin_arm64/
|
||||
netrunner_v1.14.0-next_windows_amd64/
|
||||
```
|
||||
|
||||
### 2. Test Full Release Process (Local)
|
||||
|
||||
```bash
|
||||
# Simulate full release without publishing
|
||||
goreleaser release --snapshot --clean --skip-publish
|
||||
|
||||
# Check generated artifacts
|
||||
ls -lh dist/
|
||||
```
|
||||
|
||||
Expected artifacts:
|
||||
- `netrunner_v1.14.0-next_linux_amd64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_linux_arm64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_darwin_amd64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_darwin_arm64.tar.gz`
|
||||
- `netrunner_v1.14.0-next_windows_amd64.zip`
|
||||
- `SHA256SUMS`
|
||||
|
||||
### 3. Test Binary Functionality
|
||||
|
||||
```bash
|
||||
# Test current platform binary (example for macOS arm64)
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner version
|
||||
|
||||
# Expected output:
|
||||
# netrunner version v1.14.0-next
|
||||
|
||||
# Test help command
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner --help
|
||||
|
||||
# Test server start (quick test)
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner server --help
|
||||
```
|
||||
|
||||
### 4. Verify Checksums
|
||||
|
||||
```bash
|
||||
cd dist/
|
||||
|
||||
# Verify all checksums
|
||||
sha256sum -c SHA256SUMS
|
||||
|
||||
# Or verify specific file
|
||||
sha256sum netrunner_v1.14.0-next_linux_amd64.tar.gz
|
||||
grep "netrunner_v1.14.0-next_linux_amd64.tar.gz" SHA256SUMS
|
||||
```
|
||||
|
||||
## Testing on GitHub (Release Candidate)
|
||||
|
||||
### Step 1: Create RC Tag
|
||||
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Ensure you're on latest main
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Create release candidate tag
|
||||
git tag -a v1.14.0-rc.1 -m "Release candidate v1.14.0-rc.1 for testing"
|
||||
|
||||
# Push tag to trigger workflow
|
||||
git push origin v1.14.0-rc.1
|
||||
```
|
||||
|
||||
### Step 2: Monitor Workflow
|
||||
|
||||
```bash
|
||||
# Watch workflow execution
|
||||
gh workflow view release
|
||||
|
||||
# Or open in browser
|
||||
gh workflow view release --web
|
||||
|
||||
# Check run status
|
||||
gh run list --workflow=release.yml --limit 1
|
||||
|
||||
# View logs if needed
|
||||
gh run view --log
|
||||
```
|
||||
|
||||
### Step 3: Verify Release
|
||||
|
||||
```bash
|
||||
# List releases
|
||||
gh release list
|
||||
|
||||
# View specific release
|
||||
gh release view v1.14.0-rc.1
|
||||
|
||||
# Download all assets
|
||||
gh release download v1.14.0-rc.1 --dir ./test-release
|
||||
|
||||
# Verify checksums
|
||||
cd test-release
|
||||
sha256sum -c SHA256SUMS
|
||||
```
|
||||
|
||||
### Step 4: Test Downloaded Binaries
|
||||
|
||||
```bash
|
||||
cd test-release
|
||||
|
||||
# Extract Linux binary
|
||||
tar -xzf netrunner_v1.14.0-rc.1_linux_amd64.tar.gz
|
||||
./netrunner version
|
||||
|
||||
# Extract macOS binary (if on macOS)
|
||||
tar -xzf netrunner_v1.14.0-rc.1_darwin_arm64.tar.gz
|
||||
./netrunner version
|
||||
|
||||
# Extract Windows binary (if on Windows or WSL)
|
||||
unzip netrunner_v1.14.0-rc.1_windows_amd64.zip
|
||||
./netrunner.exe version
|
||||
```
|
||||
|
||||
### Step 5: Clean Up RC Release
|
||||
|
||||
If testing successful:
|
||||
|
||||
```bash
|
||||
# Delete release
|
||||
gh release delete v1.14.0-rc.1 --yes
|
||||
|
||||
# Delete local tag
|
||||
git tag -d v1.14.0-rc.1
|
||||
|
||||
# Delete remote tag
|
||||
git push origin :refs/tags/v1.14.0-rc.1
|
||||
|
||||
# Clean up downloaded files
|
||||
rm -rf test-release
|
||||
```
|
||||
|
||||
## Production Release Process
|
||||
|
||||
### Pre-Release Checklist
|
||||
|
||||
- [ ] All tests passing locally (`go test ./...`)
|
||||
- [ ] All tests passing on CI
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] Version number decided (v1.14.0)
|
||||
- [ ] RC testing completed successfully
|
||||
- [ ] Documentation updated with new version
|
||||
|
||||
### Create Production Release
|
||||
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Ensure clean working directory
|
||||
git status
|
||||
|
||||
# Ensure on latest main
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Create production tag
|
||||
git tag -a v1.14.0 -m "Release v1.14.0
|
||||
|
||||
Features:
|
||||
- Windows support added
|
||||
- Enhanced cross-platform builds
|
||||
- Improved release automation
|
||||
|
||||
See CHANGELOG.md for full details."
|
||||
|
||||
# Push tag (this triggers release workflow)
|
||||
git push origin v1.14.0
|
||||
```
|
||||
|
||||
### Monitor Release
|
||||
|
||||
```bash
|
||||
# Watch workflow
|
||||
gh run watch
|
||||
|
||||
# Or view in browser
|
||||
gh workflow view release --web
|
||||
```
|
||||
|
||||
### Verify Production Release
|
||||
|
||||
```bash
|
||||
# View release
|
||||
gh release view v1.14.0
|
||||
|
||||
# Verify all assets present
|
||||
gh release view v1.14.0 --json assets --jq '.assets[].name'
|
||||
```
|
||||
|
||||
Expected assets:
|
||||
- `netrunner_v1.14.0_linux_amd64.tar.gz`
|
||||
- `netrunner_v1.14.0_linux_arm64.tar.gz`
|
||||
- `netrunner_v1.14.0_darwin_amd64.tar.gz`
|
||||
- `netrunner_v1.14.0_darwin_arm64.tar.gz`
|
||||
- `netrunner_v1.14.0_windows_amd64.zip`
|
||||
- `SHA256SUMS`
|
||||
|
||||
### Post-Release Checklist
|
||||
|
||||
- [ ] Release appears on GitHub releases page
|
||||
- [ ] All 5 platform binaries present
|
||||
- [ ] SHA256SUMS file present
|
||||
- [ ] Release notes generated correctly
|
||||
- [ ] Changelog formatted properly
|
||||
- [ ] Download links work
|
||||
- [ ] Checksums verify correctly
|
||||
- [ ] Binaries run on target platforms
|
||||
|
||||
## Platform-Specific Testing
|
||||
|
||||
### Linux (amd64)
|
||||
|
||||
```bash
|
||||
# Download and test
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/netrunner_v1.14.0_linux_amd64.tar.gz
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/SHA256SUMS
|
||||
sha256sum -c SHA256SUMS --ignore-missing
|
||||
tar -xzf netrunner_v1.14.0_linux_amd64.tar.gz
|
||||
./netrunner version
|
||||
./netrunner --help
|
||||
```
|
||||
|
||||
### Linux (arm64)
|
||||
|
||||
```bash
|
||||
# On ARM64 system or emulator
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/netrunner_v1.14.0_linux_arm64.tar.gz
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/SHA256SUMS
|
||||
sha256sum -c SHA256SUMS --ignore-missing
|
||||
tar -xzf netrunner_v1.14.0_linux_arm64.tar.gz
|
||||
./netrunner version
|
||||
```
|
||||
|
||||
### macOS (Intel)
|
||||
|
||||
```bash
|
||||
# On Intel Mac
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/netrunner_v1.14.0_darwin_amd64.tar.gz
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/SHA256SUMS
|
||||
shasum -a 256 -c SHA256SUMS --ignore-missing
|
||||
tar -xzf netrunner_v1.14.0_darwin_amd64.tar.gz
|
||||
./netrunner version
|
||||
```
|
||||
|
||||
### macOS (Apple Silicon)
|
||||
|
||||
```bash
|
||||
# On M1/M2/M3 Mac
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/netrunner_v1.14.0_darwin_arm64.tar.gz
|
||||
curl -LO https://github.com/luxfi/netrunner/releases/download/v1.14.0/SHA256SUMS
|
||||
shasum -a 256 -c SHA256SUMS --ignore-missing
|
||||
tar -xzf netrunner_v1.14.0_darwin_arm64.tar.gz
|
||||
./netrunner version
|
||||
```
|
||||
|
||||
### Windows (amd64)
|
||||
|
||||
```powershell
|
||||
# PowerShell
|
||||
Invoke-WebRequest -Uri https://github.com/luxfi/netrunner/releases/download/v1.14.0/netrunner_v1.14.0_windows_amd64.zip -OutFile netrunner.zip
|
||||
Invoke-WebRequest -Uri https://github.com/luxfi/netrunner/releases/download/v1.14.0/SHA256SUMS -OutFile SHA256SUMS
|
||||
Expand-Archive netrunner.zip
|
||||
.\netrunner\netrunner.exe version
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Workflow fails at validation step
|
||||
|
||||
**Cause**: Tag doesn't match semantic versioning pattern
|
||||
|
||||
**Solution**: Delete tag and recreate with proper format
|
||||
```bash
|
||||
git tag -d v1.14.0-invalid
|
||||
git push origin :refs/tags/v1.14.0-invalid
|
||||
git tag -a v1.14.0 -m "Release v1.14.0"
|
||||
git push origin v1.14.0
|
||||
```
|
||||
|
||||
### Issue: Workflow fails at test step
|
||||
|
||||
**Cause**: Tests failing
|
||||
|
||||
**Solution**: Fix tests before creating tag
|
||||
```bash
|
||||
go test ./... # Run locally first
|
||||
git commit -am "fix: resolve test failures"
|
||||
git push origin main
|
||||
git tag -a v1.14.0 -m "Release v1.14.0"
|
||||
git push origin v1.14.0
|
||||
```
|
||||
|
||||
### Issue: GoReleaser fails on cross-compilation
|
||||
|
||||
**Cause**: Missing cross-compiler or CGO issue
|
||||
|
||||
**Solution**: Check goreleaser-cross-action logs, may need to disable CGO for problematic platform
|
||||
|
||||
### Issue: Binary doesn't run on target platform
|
||||
|
||||
**Cause**: Missing dynamic libraries or incompatible build
|
||||
|
||||
**Solution**:
|
||||
- Verify CGO_ENABLED setting
|
||||
- Check ldflags for static linking
|
||||
- Test on actual target platform, not emulator
|
||||
|
||||
### Issue: Checksums don't match
|
||||
|
||||
**Cause**: File corruption during download or generation
|
||||
|
||||
**Solution**: Re-download from GitHub release, verify no proxy/CDN caching issues
|
||||
|
||||
## Success Criteria
|
||||
|
||||
A release is successful when:
|
||||
|
||||
1. ✅ **Workflow completes** without errors
|
||||
2. ✅ **All platforms built** (5 binaries present)
|
||||
3. ✅ **Release created** on GitHub
|
||||
4. ✅ **Checksums valid** for all artifacts
|
||||
5. ✅ **Binaries run** on target platforms
|
||||
6. ✅ **Version correct** in binary output
|
||||
7. ✅ **Archives extract** without errors
|
||||
8. ✅ **Changelog generated** properly
|
||||
9. ✅ **Release marked latest** (if not pre-release)
|
||||
10. ✅ **Download links work** from GitHub
|
||||
|
||||
## Automation Improvements (Future)
|
||||
|
||||
Consider adding:
|
||||
|
||||
1. **Binary smoke tests** in workflow (run `--version` on each platform)
|
||||
2. **Automated platform testing** via matrix of runners
|
||||
3. **Integration test** execution before release
|
||||
4. **Docker image** builds alongside binaries
|
||||
5. **Homebrew tap** update automation
|
||||
6. **Announcement posting** to Discord/Slack
|
||||
7. **Documentation versioning** automation
|
||||
|
||||
## Useful Commands Reference
|
||||
|
||||
```bash
|
||||
# Local testing
|
||||
goreleaser build --snapshot --clean
|
||||
goreleaser release --snapshot --clean --skip-publish
|
||||
|
||||
# Tag management
|
||||
git tag -a v1.14.0 -m "Release v1.14.0"
|
||||
git push origin v1.14.0
|
||||
git tag -d v1.14.0
|
||||
git push origin :refs/tags/v1.14.0
|
||||
|
||||
# GitHub CLI
|
||||
gh release list
|
||||
gh release view v1.14.0
|
||||
gh release download v1.14.0
|
||||
gh release delete v1.14.0 --yes
|
||||
|
||||
# Workflow monitoring
|
||||
gh workflow list
|
||||
gh workflow view release
|
||||
gh run list --workflow=release.yml
|
||||
gh run watch
|
||||
|
||||
# Checksum verification
|
||||
sha256sum -c SHA256SUMS
|
||||
sha256sum netrunner_v1.14.0_linux_amd64.tar.gz
|
||||
shasum -a 256 -c SHA256SUMS # macOS
|
||||
```
|
||||
|
||||
## Next Steps After First Release
|
||||
|
||||
1. Update installation documentation with release URLs
|
||||
2. Test installation instructions on clean systems
|
||||
3. Gather feedback on binary compatibility
|
||||
4. Monitor issue tracker for platform-specific problems
|
||||
5. Document any platform-specific quirks discovered
|
||||
6. Plan next release with improvements
|
||||
|
||||
## References
|
||||
|
||||
- GoReleaser Docs: https://goreleaser.com/
|
||||
- GitHub Actions Docs: https://docs.github.com/en/actions
|
||||
- GitHub CLI Manual: https://cli.github.com/manual/
|
||||
- Semantic Versioning: https://semver.org/
|
||||
@@ -0,0 +1,328 @@
|
||||
# Lux Netrunner Release Workflow - Implementation Summary
|
||||
|
||||
**Date**: 2025-11-12
|
||||
**Status**: ✅ Complete - Ready for Testing
|
||||
**Next Version**: v1.14.0
|
||||
|
||||
## Overview
|
||||
|
||||
Created comprehensive multi-platform release workflow for Lux Netrunner with full automation, semantic version validation, and support for all major platforms.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### 1. `.goreleaser.yml` (Updated)
|
||||
**Changes**:
|
||||
- ✅ Added Windows amd64 support
|
||||
- ✅ Added archives configuration (tar.gz for Unix, zip for Windows)
|
||||
- ✅ Added SHA256SUMS checksum generation
|
||||
- ✅ Added structured changelog generation
|
||||
- ✅ Enhanced release notes template
|
||||
- ✅ Added `-s -w` ldflags for smaller binaries
|
||||
- ✅ Configured proper archive naming: `netrunner_v1.14.0_linux_amd64.tar.gz`
|
||||
|
||||
**Platforms Supported**:
|
||||
- Linux (amd64, arm64)
|
||||
- Darwin/macOS (amd64, arm64)
|
||||
- Windows (amd64)
|
||||
|
||||
**Total**: 5 platform variants
|
||||
|
||||
### 2. `.github/workflows/release.yml` (Updated)
|
||||
**Changes**:
|
||||
- ✅ Updated trigger to semantic version pattern (`v*.*.*`)
|
||||
- ✅ Added version validation job (prevents v2.x.x without module update)
|
||||
- ✅ Added test job (runs before release)
|
||||
- ✅ Updated Go version from 1.19 to 1.25
|
||||
- ✅ Replaced osxcross setup with goreleaser-cross-action
|
||||
- ✅ Added coverage artifact upload
|
||||
- ✅ Added GitHub Actions summary output
|
||||
|
||||
**Jobs**:
|
||||
1. **validate-version**: Semantic version check, major version constraint
|
||||
2. **test**: Run unit tests with race detection and coverage
|
||||
3. **release**: Build all platforms and create GitHub release
|
||||
|
||||
### 3. `RELEASE_ANALYSIS.md` (Created)
|
||||
**Contents**:
|
||||
- Current state analysis
|
||||
- Issues identified
|
||||
- Recommended solutions
|
||||
- Detailed implementation plan
|
||||
- Cross-compilation notes
|
||||
- Platform considerations
|
||||
- Success criteria
|
||||
- Migration plan
|
||||
|
||||
### 4. `RELEASE_TESTING.md` (Created)
|
||||
**Contents**:
|
||||
- Local testing instructions
|
||||
- RC (release candidate) testing process
|
||||
- Production release checklist
|
||||
- Platform-specific testing commands
|
||||
- Troubleshooting guide
|
||||
- Success criteria
|
||||
- Useful commands reference
|
||||
|
||||
### 5. `RELEASE_WORKFLOW_SUMMARY.md` (This File)
|
||||
**Contents**:
|
||||
- Implementation summary
|
||||
- Quick start guide
|
||||
- File changes overview
|
||||
- Testing recommendations
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### Workflow Enhancements
|
||||
1. **Semantic Version Validation**: Prevents invalid version tags
|
||||
2. **Major Version Protection**: Blocks v2.x.x without module path update
|
||||
3. **Pre-Release Testing**: Runs full test suite before building
|
||||
4. **Coverage Tracking**: Uploads test coverage reports
|
||||
5. **Clean Builds**: Uses `--clean` instead of deprecated `--rm-dist`
|
||||
6. **Modern Actions**: Updated to latest GitHub Actions versions
|
||||
|
||||
### Build Improvements
|
||||
1. **Windows Support**: Added Windows amd64 builds (.zip archives)
|
||||
2. **Smaller Binaries**: Added `-s -w` ldflags to strip debug info
|
||||
3. **Proper Archives**: Configured format per platform
|
||||
4. **Checksums**: Automatic SHA256SUMS generation
|
||||
5. **Structured Changelog**: Groups commits by type (feat/fix/perf)
|
||||
6. **Rich Release Notes**: Templated headers and footers
|
||||
|
||||
### Developer Experience
|
||||
1. **Local Testing**: Full goreleaser snapshot support
|
||||
2. **RC Testing**: Documented release candidate workflow
|
||||
3. **Clear Errors**: Helpful validation error messages
|
||||
4. **Monitoring**: GitHub Actions summary with release link
|
||||
5. **Documentation**: Comprehensive guides for all scenarios
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### Why GoReleaser?
|
||||
- Industry standard for Go project releases
|
||||
- Automatic archive creation and checksums
|
||||
- Built-in changelog generation
|
||||
- Supports complex cross-compilation scenarios
|
||||
- Minimal workflow YAML to maintain
|
||||
|
||||
### Why goreleaser-cross-action?
|
||||
- Eliminates fragile osxcross setup (30+ min build)
|
||||
- Provides pre-configured cross-compilation toolchains
|
||||
- Supports CGO for all platforms
|
||||
- Maintained by goreleaser team
|
||||
- Faster builds (uses Docker image)
|
||||
|
||||
### Why Semantic Version Validation?
|
||||
- Prevents accidental v2.x.x tags (Go module breaking change)
|
||||
- Enforces consistent version format
|
||||
- Catches typos before release
|
||||
- Documents versioning requirements
|
||||
|
||||
### Why Separate Test Job?
|
||||
- Fail fast if tests don't pass
|
||||
- Avoid wasting time on builds if code is broken
|
||||
- Generate coverage reports before release
|
||||
- Can be expanded with integration tests
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Local Testing (Recommended First Step)
|
||||
|
||||
```bash
|
||||
cd /Users/z/work/lux/netrunner
|
||||
|
||||
# Install goreleaser (one-time)
|
||||
brew install goreleaser/tap/goreleaser
|
||||
|
||||
# Test build without releasing
|
||||
goreleaser build --snapshot --clean
|
||||
|
||||
# Test on your platform
|
||||
./dist/netrunner_v1.14.0-next_darwin_arm64/netrunner version
|
||||
```
|
||||
|
||||
### Create Release Candidate
|
||||
|
||||
```bash
|
||||
# Create RC tag
|
||||
git tag -a v1.14.0-rc.1 -m "Release candidate v1.14.0-rc.1"
|
||||
git push origin v1.14.0-rc.1
|
||||
|
||||
# Monitor workflow
|
||||
gh workflow view release --web
|
||||
|
||||
# Download and test
|
||||
gh release download v1.14.0-rc.1 --dir ./test-release
|
||||
cd test-release
|
||||
sha256sum -c SHA256SUMS
|
||||
```
|
||||
|
||||
### Create Production Release
|
||||
|
||||
```bash
|
||||
# After RC testing successful
|
||||
gh release delete v1.14.0-rc.1 --yes
|
||||
git tag -d v1.14.0-rc.1
|
||||
git push origin :refs/tags/v1.14.0-rc.1
|
||||
|
||||
# Create production tag
|
||||
git tag -a v1.14.0 -m "Release v1.14.0"
|
||||
git push origin v1.14.0
|
||||
|
||||
# Monitor and verify
|
||||
gh run watch
|
||||
gh release view v1.14.0
|
||||
```
|
||||
|
||||
## Version Recommendation
|
||||
|
||||
### Recommended First Release: v1.14.0
|
||||
|
||||
**Rationale**:
|
||||
- Current version: v1.13.5-lux.3
|
||||
- Next minor bump: v1.14.0
|
||||
- Clean version (no suffix)
|
||||
- Major milestone (Windows support + automation)
|
||||
|
||||
**What's Included**:
|
||||
- All recent bug fixes and improvements
|
||||
- Windows platform support
|
||||
- Enhanced CI/CD automation
|
||||
- Comprehensive documentation
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before creating v1.14.0:
|
||||
|
||||
- [ ] Run local snapshot build: `goreleaser build --snapshot --clean`
|
||||
- [ ] Test local binary: `./dist/.../netrunner version`
|
||||
- [ ] Run all tests: `go test ./...`
|
||||
- [ ] Create RC tag: `v1.14.0-rc.1`
|
||||
- [ ] Verify workflow passes all jobs
|
||||
- [ ] Download all 5 platform binaries
|
||||
- [ ] Verify checksums: `sha256sum -c SHA256SUMS`
|
||||
- [ ] Test binary on Linux (amd64 or arm64)
|
||||
- [ ] Test binary on macOS (Intel or Apple Silicon)
|
||||
- [ ] Test binary on Windows (or WSL)
|
||||
- [ ] Verify release notes formatting
|
||||
- [ ] Verify changelog grouped correctly
|
||||
- [ ] Clean up RC: delete release and tag
|
||||
- [ ] Create production tag: `v1.14.0`
|
||||
- [ ] Verify final release
|
||||
- [ ] Update installation docs with v1.14.0 URLs
|
||||
|
||||
## Expected Output
|
||||
|
||||
### Workflow Execution
|
||||
```
|
||||
✅ validate-version
|
||||
└─ Check semantic version format (1s)
|
||||
|
||||
✅ test
|
||||
├─ Checkout (2s)
|
||||
├─ Set up Go (5s)
|
||||
├─ Run unit tests (30s)
|
||||
└─ Upload coverage (2s)
|
||||
|
||||
✅ release
|
||||
├─ Checkout (2s)
|
||||
├─ Set up Go (5s)
|
||||
├─ Run GoReleaser (5-10min)
|
||||
└─ Summary (1s)
|
||||
```
|
||||
|
||||
### GitHub Release Assets
|
||||
```
|
||||
netrunner_v1.14.0_linux_amd64.tar.gz (15 MB)
|
||||
netrunner_v1.14.0_linux_arm64.tar.gz (14 MB)
|
||||
netrunner_v1.14.0_darwin_amd64.tar.gz (15 MB)
|
||||
netrunner_v1.14.0_darwin_arm64.tar.gz (14 MB)
|
||||
netrunner_v1.14.0_windows_amd64.zip (15 MB)
|
||||
SHA256SUMS (400 B)
|
||||
```
|
||||
|
||||
### Release Notes
|
||||
```markdown
|
||||
## Lux Netrunner v1.14.0
|
||||
|
||||
Network orchestration and testing framework for Lux blockchain.
|
||||
|
||||
### Downloads
|
||||
[Links to all platform binaries]
|
||||
|
||||
### Verifying Checksums
|
||||
[Instructions with examples]
|
||||
|
||||
## Features
|
||||
- feat: add Windows support
|
||||
- feat: enhanced CI/CD automation
|
||||
|
||||
## Bug fixes
|
||||
- fix: ...
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/luxfi/netrunner/compare/v1.13.5-lux.3...v1.14.0
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **Windows ARM64**: Not included (no stable cross-compiler yet)
|
||||
2. **FreeBSD/OpenBSD**: Not included (can be added if needed)
|
||||
3. **CGO Dependency**: Requires cross-compilers, increases build time
|
||||
4. **Docker Image**: Not built automatically (future enhancement)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Consider adding:
|
||||
1. **Docker Images**: Multi-arch Docker builds
|
||||
2. **Homebrew Tap**: Auto-update homebrew formula
|
||||
3. **Snap/AppImage**: Linux package formats
|
||||
4. **Binary Signing**: Code signing for macOS/Windows
|
||||
5. **Notarization**: macOS notarization for Gatekeeper
|
||||
6. **SBOM Generation**: Software Bill of Materials
|
||||
7. **Vulnerability Scanning**: Trivy/Grype in workflow
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workflow fails with "tag must follow semantic versioning"
|
||||
**Fix**: Use `v1.14.0` format, not `1.14.0` or `v1.14` or `release-1.14.0`
|
||||
|
||||
### Workflow fails with "Major version must be < 2"
|
||||
**Fix**: Don't create v2.x.x tags. For v2, update go.mod module path first.
|
||||
|
||||
### Tests fail in workflow
|
||||
**Fix**: Run `go test ./...` locally and fix before pushing tag
|
||||
|
||||
### GoReleaser cross-compilation fails
|
||||
**Fix**: Check goreleaser-cross-action logs, may need to adjust CC env vars
|
||||
|
||||
### Binary doesn't run on target platform
|
||||
**Fix**: Test on actual hardware, not emulator. Check CGO settings.
|
||||
|
||||
## Support
|
||||
|
||||
For issues with the release workflow:
|
||||
1. Check `/Users/z/work/lux/netrunner/RELEASE_TESTING.md` for detailed testing
|
||||
2. Check `/Users/z/work/lux/netrunner/RELEASE_ANALYSIS.md` for architecture details
|
||||
3. Review workflow logs: `gh run view --log`
|
||||
4. Test locally first: `goreleaser build --snapshot --clean`
|
||||
|
||||
## References
|
||||
|
||||
- GoReleaser: https://goreleaser.com/
|
||||
- goreleaser-cross: https://github.com/goreleaser/goreleaser-cross
|
||||
- GitHub Actions: https://docs.github.com/en/actions
|
||||
- Semantic Versioning: https://semver.org/
|
||||
|
||||
## Conclusion
|
||||
|
||||
The release workflow is now complete and ready for testing. Follow the testing checklist to create v1.14.0-rc.1, verify all platforms work, then create the production v1.14.0 release.
|
||||
|
||||
All documentation is in place, workflow is tested with goreleaser's validation, and the process is fully automated. Once the first release is successful, future releases will be as simple as:
|
||||
|
||||
```bash
|
||||
git tag -a v1.15.0 -m "Release v1.15.0"
|
||||
git push origin v1.15.0
|
||||
```
|
||||
|
||||
The workflow will handle everything else automatically.
|
||||
@@ -1,187 +0,0 @@
|
||||
// Code generated by mockery v2.10.6. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
api "github.com/luxfi/netrunner/api"
|
||||
admin "github.com/luxfi/node/api/admin"
|
||||
|
||||
xvm "github.com/luxfi/node/vms/xvm"
|
||||
|
||||
evmclient "github.com/luxfi/evm/plugin/evm/client"
|
||||
|
||||
health "github.com/luxfi/node/api/health"
|
||||
|
||||
indexer "github.com/luxfi/node/indexer"
|
||||
|
||||
info "github.com/luxfi/node/api/info"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
platformvm "github.com/luxfi/node/vms/platformvm"
|
||||
)
|
||||
|
||||
// Client is an autogenerated mock type for the Client type
|
||||
type Client struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// AdminAPI provides a mock function with given fields:
|
||||
func (_m *Client) AdminAPI() *admin.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *admin.Client
|
||||
if rf, ok := ret.Get(0).(func() *admin.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*admin.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// CChainAPI provides a mock function with given fields:
|
||||
func (_m *Client) CChainAPI() evmclient.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 evmclient.Client
|
||||
if rf, ok := ret.Get(0).(func() evmclient.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(evmclient.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// CChainEthAPI provides a mock function with given fields:
|
||||
func (_m *Client) CChainEthAPI() api.EthClient {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 api.EthClient
|
||||
if rf, ok := ret.Get(0).(func() api.EthClient); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(api.EthClient)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// CChainIndexAPI provides a mock function with given fields:
|
||||
func (_m *Client) CChainIndexAPI() *indexer.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *indexer.Client
|
||||
if rf, ok := ret.Get(0).(func() *indexer.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*indexer.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// HealthAPI provides a mock function with given fields:
|
||||
func (_m *Client) HealthAPI() *health.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *health.Client
|
||||
if rf, ok := ret.Get(0).(func() *health.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*health.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// InfoAPI provides a mock function with given fields:
|
||||
func (_m *Client) InfoAPI() *info.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *info.Client
|
||||
if rf, ok := ret.Get(0).(func() *info.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*info.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PChainAPI provides a mock function with given fields:
|
||||
func (_m *Client) PChainAPI() *platformvm.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *platformvm.Client
|
||||
if rf, ok := ret.Get(0).(func() *platformvm.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*platformvm.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PChainIndexAPI provides a mock function with given fields:
|
||||
func (_m *Client) PChainIndexAPI() *indexer.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *indexer.Client
|
||||
if rf, ok := ret.Get(0).(func() *indexer.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*indexer.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// XChainAPI provides a mock function with given fields:
|
||||
func (_m *Client) XChainAPI() *xvm.Client {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *xvm.Client
|
||||
if rf, ok := ret.Get(0).(func() *xvm.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*xvm.Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// XChainWalletAPI provides a mock function with given fields:
|
||||
func (_m *Client) XChainWalletAPI() *xvm.WalletClient {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *xvm.WalletClient
|
||||
if rf, ok := ret.Get(0).(func() *xvm.WalletClient); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*xvm.WalletClient)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# Next.js
|
||||
.next/
|
||||
out/
|
||||
.turbo/
|
||||
|
||||
# Build
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Fumadocs
|
||||
.source/
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,42 @@
|
||||
import { source } from "@/lib/source"
|
||||
import type { Metadata } from "next"
|
||||
import { DocsPage, DocsBody, DocsTitle, DocsDescription } from "fumadocs-ui/page"
|
||||
import { notFound } from "next/navigation"
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx"
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
if (!page) notFound()
|
||||
|
||||
const MDX = page.data.body
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.toc} full={page.data.full}>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<MDX components={{ ...defaultMdxComponents }} />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
)
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return source.generateParams()
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[] }>
|
||||
}): Promise<Metadata> {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
if (!page) notFound()
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { source } from "@/lib/source"
|
||||
import type { ReactNode } from "react"
|
||||
import { DocsLayout } from "fumadocs-ui/layouts/docs"
|
||||
|
||||
export default async function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<DocsLayout tree={source.pageTree} sidebar={{ defaultOpenLevel: 0 }}>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
@import "tailwindcss";
|
||||
@import "fumadocs-ui/style.css";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme inline {
|
||||
--breakpoint-3xl: 1600px;
|
||||
--breakpoint-4xl: 2000px;
|
||||
--font-sans: var(--font-geist-sans), system-ui, sans-serif;
|
||||
--font-mono: var(--font-geist-mono), monospace;
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import "./global.css"
|
||||
import { RootProvider } from "fumadocs-ui/provider/next"
|
||||
import { Inter } from "next/font/google"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-geist-sans",
|
||||
display: "swap",
|
||||
})
|
||||
|
||||
const interMono = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-geist-mono",
|
||||
display: "swap",
|
||||
})
|
||||
|
||||
export const metadata = {
|
||||
title: {
|
||||
default: "Network Runner Documentation",
|
||||
template: "%s | Network Runner",
|
||||
},
|
||||
description: "Network orchestration and testing tool",
|
||||
}
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${inter.variable} ${interMono.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body className="min-h-svh bg-background font-sans antialiased">
|
||||
<RootProvider
|
||||
search={{
|
||||
enabled: true,
|
||||
}}
|
||||
theme={{
|
||||
enabled: true,
|
||||
defaultTheme: "dark",
|
||||
}}
|
||||
>
|
||||
<div className="relative flex min-h-svh flex-col bg-background">
|
||||
{children}
|
||||
</div>
|
||||
</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import Link from "next/link"
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="flex flex-1 flex-col items-center justify-center px-4">
|
||||
<div className="container flex flex-col items-center gap-12 py-24 sm:gap-16 sm:py-32">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl">
|
||||
Documentation
|
||||
</h1>
|
||||
<p className="max-w-2xl text-lg text-muted-foreground">
|
||||
Get started with our comprehensive documentation
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-8 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
---
|
||||
title: Configuration
|
||||
description: Complete configuration reference for Lux Netrunner
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
This guide provides a comprehensive reference for configuring Lux Netrunner, including network configuration, node configuration, and blockchain specifications.
|
||||
|
||||
## Configuration Hierarchy
|
||||
|
||||
Netrunner uses a hierarchical configuration system with the following precedence (highest to lowest):
|
||||
|
||||
1. **Node-specific flags** - Individual node configurations
|
||||
2. **Network-wide flags** - Global network configurations
|
||||
3. **Configuration files** - JSON config files
|
||||
4. **Default values** - Built-in defaults
|
||||
|
||||
## Network Configuration
|
||||
|
||||
### Network Config Structure
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
// Required: Genesis configuration
|
||||
Genesis string `json:"genesis"`
|
||||
|
||||
// Node configurations
|
||||
NodeConfigs []node.Config `json:"nodeConfigs"`
|
||||
|
||||
// Global flags applied to all nodes
|
||||
Flags map[string]interface{} `json:"flags"`
|
||||
|
||||
// Default binary path for nodes
|
||||
BinaryPath string `json:"binaryPath"`
|
||||
|
||||
// Chain configuration files
|
||||
ChainConfigFiles map[string]string `json:"chainConfigFiles"`
|
||||
|
||||
// Upgrade configuration files
|
||||
UpgradeConfigFiles map[string]string `json:"upgradeConfigFiles"`
|
||||
|
||||
// Subnet configuration files
|
||||
SubnetConfigFiles map[string]string `json:"subnetConfigFiles"`
|
||||
}
|
||||
```
|
||||
|
||||
### Example Network Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"genesis": "/path/to/genesis.json",
|
||||
"binaryPath": "/path/to/luxd",
|
||||
"flags": {
|
||||
"network-peer-list-gossip-frequency": "250ms",
|
||||
"network-max-reconnect-delay": "1s",
|
||||
"public-ip": "127.0.0.1",
|
||||
"health-check-frequency": "2s",
|
||||
"api-admin-enabled": true,
|
||||
"index-enabled": true
|
||||
},
|
||||
"nodeConfigs": [
|
||||
{
|
||||
"name": "node1",
|
||||
"isBeacon": true,
|
||||
"flags": {
|
||||
"http-port": 9630,
|
||||
"staking-port": 9631
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "node2",
|
||||
"flags": {
|
||||
"http-port": 9632,
|
||||
"staking-port": 9633
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Node Configuration
|
||||
|
||||
### Node Config Structure
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
// Unique node name
|
||||
Name string `json:"name"`
|
||||
|
||||
// Bootstrap beacon node
|
||||
IsBeacon bool `json:"isBeacon"`
|
||||
|
||||
// Staking credentials
|
||||
StakingKey string `json:"stakingKey"`
|
||||
StakingCert string `json:"stakingCert"`
|
||||
StakingSigningKey string `json:"stakingSigningKey"`
|
||||
|
||||
// Configuration files
|
||||
ConfigFile string `json:"configFile"`
|
||||
ChainConfigFiles map[string]string `json:"chainConfigFiles"`
|
||||
UpgradeConfigFiles map[string]string `json:"upgradeConfigFiles"`
|
||||
SubnetConfigFiles map[string]string `json:"subnetConfigFiles"`
|
||||
|
||||
// Node-specific flags
|
||||
Flags map[string]interface{} `json:"flags"`
|
||||
|
||||
// Binary path
|
||||
BinaryPath string `json:"binaryPath"`
|
||||
|
||||
// Output redirection
|
||||
RedirectStdout bool `json:"redirectStdout"`
|
||||
RedirectStderr bool `json:"redirectStderr"`
|
||||
}
|
||||
```
|
||||
|
||||
### Common Node Flags
|
||||
|
||||
```json
|
||||
{
|
||||
// API Configuration
|
||||
"api-admin-enabled": true,
|
||||
"api-ipcs-enabled": true,
|
||||
"api-keystore-enabled": true,
|
||||
"api-metrics-enabled": true,
|
||||
"http-host": "0.0.0.0",
|
||||
"http-port": 9630,
|
||||
"http-tls-enabled": false,
|
||||
|
||||
// Network Configuration
|
||||
"public-ip": "127.0.0.1",
|
||||
"staking-port": 9631,
|
||||
"staking-enabled": true,
|
||||
"staking-ephemeral-cert-enabled": false,
|
||||
"network-peer-list-gossip-frequency": "250ms",
|
||||
"network-max-reconnect-delay": "1s",
|
||||
"network-initial-reconnect-delay": "250ms",
|
||||
"network-peer-read-buffer-size": 8192,
|
||||
"network-peer-write-buffer-size": 8192,
|
||||
|
||||
// Database Configuration
|
||||
"db-dir": "/path/to/db",
|
||||
"db-type": "leveldb",
|
||||
|
||||
// Logging Configuration
|
||||
"log-level": "info",
|
||||
"log-dir": "/path/to/logs",
|
||||
"log-format": "json",
|
||||
"log-display-level": "info",
|
||||
|
||||
// Chain Configuration
|
||||
"index-enabled": true,
|
||||
"index-allow-incomplete": false,
|
||||
|
||||
// Health Check
|
||||
"health-check-frequency": "2s",
|
||||
"health-check-averager-halflife": "10s",
|
||||
|
||||
// Bootstrap Configuration
|
||||
"bootstrap-ips": "127.0.0.1:9631",
|
||||
"bootstrap-ids": "NodeID-xxx",
|
||||
|
||||
// Performance Tuning
|
||||
"snow-sample-size": 20,
|
||||
"snow-quorum-size": 15,
|
||||
"snow-concurrent-repolls": 4,
|
||||
"snow-optimal-processing": 50,
|
||||
"snow-max-processing": 1024,
|
||||
"snow-max-time-processing": "2m",
|
||||
|
||||
// VM Configuration
|
||||
"vm-aliases": {
|
||||
"custom": "vmID"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Blockchain Configuration
|
||||
|
||||
### Blockchain Spec Structure
|
||||
|
||||
```go
|
||||
type BlockchainSpec struct {
|
||||
// VM name or ID
|
||||
VMName string `json:"vm_name"`
|
||||
|
||||
// Genesis data (file path or JSON string)
|
||||
Genesis []byte `json:"genesis"`
|
||||
|
||||
// Subnet ID (optional)
|
||||
SubnetID *string `json:"subnet_id"`
|
||||
|
||||
// Subnet specification
|
||||
SubnetSpec *SubnetSpec `json:"subnet_spec"`
|
||||
|
||||
// Chain configuration
|
||||
ChainConfig []byte `json:"chain_config"`
|
||||
|
||||
// Network upgrade configuration
|
||||
NetworkUpgrade []byte `json:"network_upgrade"`
|
||||
|
||||
// Blockchain alias
|
||||
BlockchainAlias string `json:"blockchain_alias"`
|
||||
|
||||
// Per-node chain configuration
|
||||
PerNodeChainConfig map[string][]byte `json:"per_node_chain_config"`
|
||||
}
|
||||
```
|
||||
|
||||
### Subnet-EVM Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"vm_name": "subnetevm",
|
||||
"genesis": {
|
||||
"config": {
|
||||
"chainId": 99999,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"subnetEVMTimestamp": 0,
|
||||
"feeConfig": {
|
||||
"gasLimit": 20000000,
|
||||
"minBaseFee": 1000000000,
|
||||
"targetGas": 100000000,
|
||||
"baseFeeChangeDenominator": 48,
|
||||
"minBlockGasCost": 0,
|
||||
"maxBlockGasCost": 10000000,
|
||||
"targetBlockRate": 2,
|
||||
"blockGasCostStep": 500000
|
||||
},
|
||||
"allowFeeRecipients": false
|
||||
},
|
||||
"alloc": {
|
||||
"0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
|
||||
"balance": "0x52B7D2DCC80CD2E4000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0",
|
||||
"timestamp": "0x0",
|
||||
"gasLimit": "0x1312D00",
|
||||
"difficulty": "0x0"
|
||||
},
|
||||
"chain_config": {
|
||||
"priority-regossip-frequency": "1m",
|
||||
"priority-regossip-max-txs": 16,
|
||||
"priority-regossip-addresses": ["0x..."],
|
||||
"local-txs-enabled": true,
|
||||
"api-max-duration": "30s",
|
||||
"ws-cpu-refill-rate": 100000,
|
||||
"ws-cpu-max-stored": 5000000,
|
||||
"api-max-blocks-per-request": 30,
|
||||
"continuous-profiler-frequency": "1h",
|
||||
"rpc-tx-fee-cap": 100
|
||||
},
|
||||
"network_upgrade": {
|
||||
"subnetEVMTimestamp": 0,
|
||||
"durango": 0
|
||||
},
|
||||
"subnet_config": {
|
||||
"proposer-min-block-delay": 0,
|
||||
"proposer-num-historical-blocks": 512
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### QuantumVM Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"vm_name": "quantumvm",
|
||||
"genesis": {
|
||||
"config": {
|
||||
"quantumEnabled": true,
|
||||
"coronaEnabled": true,
|
||||
"quantumAlgorithmVersion": 1,
|
||||
"coronaKeySize": 1024,
|
||||
"quantumStampWindow": 30,
|
||||
"parallelBatchSize": 10,
|
||||
"quantumSigCacheSize": 10000
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"nodeID": "NodeID-xxx",
|
||||
"weight": 1000000
|
||||
}
|
||||
],
|
||||
"timestamp": 0
|
||||
},
|
||||
"chain_config": {
|
||||
"tx-fee": 1000,
|
||||
"create-asset-tx-fee": 10000,
|
||||
"quantum-verification-fee": 500,
|
||||
"max-parallel-txs": 100,
|
||||
"quantum-time": "2025-01-01T00:00:00Z",
|
||||
"min-quantum-confirmations": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Subnet Configuration
|
||||
|
||||
### Subnet Spec Structure
|
||||
|
||||
```go
|
||||
type SubnetSpec struct {
|
||||
// Validator participants
|
||||
Participants []string `json:"participants"`
|
||||
|
||||
// Subnet configuration
|
||||
SubnetConfig []byte `json:"subnet_config"`
|
||||
}
|
||||
```
|
||||
|
||||
### Elastic Subnet Configuration
|
||||
|
||||
```go
|
||||
type ElasticSubnetSpec struct {
|
||||
SubnetID *string `json:"subnet_id"`
|
||||
AssetName string `json:"asset_name"`
|
||||
AssetSymbol string `json:"asset_symbol"`
|
||||
InitialSupply uint64 `json:"initial_supply"`
|
||||
MaxSupply uint64 `json:"max_supply"`
|
||||
MinConsumptionRate uint64 `json:"min_consumption_rate"`
|
||||
MaxConsumptionRate uint64 `json:"max_consumption_rate"`
|
||||
MinValidatorStake uint64 `json:"min_validator_stake"`
|
||||
MaxValidatorStake uint64 `json:"max_validator_stake"`
|
||||
MinStakeDuration time.Duration `json:"min_stake_duration"`
|
||||
MaxStakeDuration time.Duration `json:"max_stake_duration"`
|
||||
MinDelegationFee uint32 `json:"min_delegation_fee"`
|
||||
MinDelegatorStake uint64 `json:"min_delegator_stake"`
|
||||
MaxValidatorWeightFactor byte `json:"max_validator_weight_factor"`
|
||||
UptimeRequirement uint32 `json:"uptime_requirement"`
|
||||
}
|
||||
```
|
||||
|
||||
## Genesis Configuration
|
||||
|
||||
### Creating Custom Genesis
|
||||
|
||||
```go
|
||||
func NewLuxGenesis(
|
||||
log logging.Logger,
|
||||
networkID uint32,
|
||||
xChainBalances []AddrAndBalance,
|
||||
cChainBalances []AddrAndBalance,
|
||||
genesisVdrs []ids.ShortID,
|
||||
) ([]byte, error)
|
||||
```
|
||||
|
||||
### Example Genesis Generation
|
||||
|
||||
```go
|
||||
// Generate genesis with custom validators
|
||||
genesisBytes, err := network.NewLuxGenesis(
|
||||
logger,
|
||||
12345, // network ID
|
||||
[]network.AddrAndBalance{
|
||||
{
|
||||
Addr: xChainAddr1,
|
||||
Balance: big.NewInt(1000000000000),
|
||||
},
|
||||
},
|
||||
[]network.AddrAndBalance{
|
||||
{
|
||||
Addr: cChainAddr1,
|
||||
Balance: big.NewInt(1000000000000),
|
||||
},
|
||||
},
|
||||
[]ids.ShortID{validator1, validator2, validator3},
|
||||
)
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Network Config File
|
||||
|
||||
Save as `network-config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"genesis": "./genesis.json",
|
||||
"binaryPath": "/path/to/luxd",
|
||||
"flags": {
|
||||
"network-peer-list-gossip-frequency": "250ms",
|
||||
"api-admin-enabled": true
|
||||
},
|
||||
"nodeConfigs": [
|
||||
{
|
||||
"name": "node1",
|
||||
"isBeacon": true,
|
||||
"configFile": "./node1-config.json"
|
||||
},
|
||||
{
|
||||
"name": "node2",
|
||||
"configFile": "./node2-config.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Node Config File
|
||||
|
||||
Save as `node-config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"http-port": 9630,
|
||||
"staking-port": 9631,
|
||||
"db-type": "leveldb",
|
||||
"log-level": "info",
|
||||
"api-admin-enabled": true,
|
||||
"index-enabled": true,
|
||||
"network-peer-list-gossip-frequency": "250ms"
|
||||
}
|
||||
```
|
||||
|
||||
### Using Config Files
|
||||
|
||||
```bash
|
||||
# Start network with config file
|
||||
netrunner control start \
|
||||
--config-file network-config.json
|
||||
|
||||
# Start with node config override
|
||||
netrunner control start \
|
||||
--config-file network-config.json \
|
||||
--node-config-file node-override.json
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Supported Environment Variables
|
||||
|
||||
```bash
|
||||
# Network configuration
|
||||
export NETRUNNER_NETWORK_ID=12345
|
||||
export NETRUNNER_BINARY_PATH="/path/to/luxd"
|
||||
export NETRUNNER_PLUGIN_DIR="/path/to/plugins"
|
||||
|
||||
# Server configuration
|
||||
export NETRUNNER_SERVER_PORT=":8080"
|
||||
export NETRUNNER_GRPC_GATEWAY_PORT=":8081"
|
||||
export NETRUNNER_LOG_LEVEL="debug"
|
||||
|
||||
# Default node configuration
|
||||
export NETRUNNER_DEFAULT_NODE_CONFIG='{
|
||||
"api-admin-enabled": true,
|
||||
"index-enabled": true
|
||||
}'
|
||||
|
||||
# Snapshot directory
|
||||
export NETRUNNER_SNAPSHOT_DIR="/path/to/snapshots"
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Multi-Region Setup
|
||||
|
||||
Configure nodes across regions:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeConfigs": [
|
||||
{
|
||||
"name": "us-east-1",
|
||||
"flags": {
|
||||
"public-ip": "1.2.3.4",
|
||||
"http-host": "0.0.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eu-west-1",
|
||||
"flags": {
|
||||
"public-ip": "5.6.7.8",
|
||||
"http-host": "0.0.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ap-southeast-1",
|
||||
"flags": {
|
||||
"public-ip": "9.10.11.12",
|
||||
"http-host": "0.0.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
```json
|
||||
{
|
||||
"flags": {
|
||||
"snow-sample-size": 20,
|
||||
"snow-quorum-size": 15,
|
||||
"snow-concurrent-repolls": 4,
|
||||
"snow-optimal-processing": 100,
|
||||
"snow-max-processing": 2048,
|
||||
"consensus-app-concurrency": 4,
|
||||
"network-peer-read-buffer-size": 16384,
|
||||
"network-peer-write-buffer-size": 16384,
|
||||
"db-cache-size": 768,
|
||||
"plugin-mode-enabled": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"flags": {
|
||||
"api-auth-required": true,
|
||||
"api-auth-password": "secure-password",
|
||||
"http-tls-enabled": true,
|
||||
"http-tls-cert-file": "/path/to/cert.pem",
|
||||
"http-tls-key-file": "/path/to/key.pem",
|
||||
"staking-tls-cert-file": "/path/to/staking-cert.pem",
|
||||
"staking-tls-key-file": "/path/to/staking-key.pem",
|
||||
"api-ipcs-enabled": false,
|
||||
"api-keystore-enabled": false,
|
||||
"api-admin-enabled": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Validation
|
||||
|
||||
### Pre-flight Checks
|
||||
|
||||
Netrunner validates configurations before starting:
|
||||
|
||||
1. **Genesis validation** - Ensures genesis is valid
|
||||
2. **Port availability** - Checks for port conflicts
|
||||
3. **Binary validation** - Verifies binary exists and is executable
|
||||
4. **Plugin validation** - Checks required plugins are available
|
||||
5. **Network ID consistency** - Ensures all nodes use same network ID
|
||||
|
||||
### Validation Example
|
||||
|
||||
```go
|
||||
func (c *Config) Validate() error {
|
||||
// Check genesis
|
||||
if len(c.Genesis) == 0 {
|
||||
return errors.New("no genesis given")
|
||||
}
|
||||
|
||||
// Get network ID
|
||||
networkID, err := utils.NetworkIDFromGenesis([]byte(c.Genesis))
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get network ID: %w", err)
|
||||
}
|
||||
|
||||
// Validate each node
|
||||
for i, nodeConfig := range c.NodeConfigs {
|
||||
if err := nodeConfig.Validate(networkID); err != nil {
|
||||
return fmt.Errorf("node %d config invalid: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Configuration
|
||||
|
||||
### Runtime Configuration Updates
|
||||
|
||||
```go
|
||||
// Update node configuration at runtime
|
||||
func UpdateNodeConfig(network network.Network, nodeName string, newConfig map[string]interface{}) error {
|
||||
node, err := network.GetNode(nodeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Apply new configuration
|
||||
return node.UpdateConfig(newConfig)
|
||||
}
|
||||
|
||||
// Example: Enable profiling on a running node
|
||||
UpdateNodeConfig(network, "node1", map[string]interface{}{
|
||||
"profile-continuous-enabled": true,
|
||||
"profile-continuous-freq": "1m",
|
||||
})
|
||||
```
|
||||
|
||||
### Hot-Reload Configuration
|
||||
|
||||
```bash
|
||||
# Enable configuration hot-reload
|
||||
netrunner control start \
|
||||
--enable-hot-reload \
|
||||
--config-watch-interval=10s \
|
||||
--config-file=/path/to/config.json
|
||||
|
||||
# Trigger reload manually
|
||||
netrunner control reload-config --node=node1
|
||||
```
|
||||
|
||||
## Configuration Templates
|
||||
|
||||
### Template Variables
|
||||
|
||||
```yaml
|
||||
# config-template.yaml
|
||||
network:
|
||||
id: {{ .NetworkID }}
|
||||
nodes:
|
||||
{{- range $i, $node := .Nodes }}
|
||||
- name: node{{ $i }}
|
||||
http_port: {{ add 9630 (mul $i 2) }}
|
||||
staking_port: {{ add 9631 (mul $i 2) }}
|
||||
{{- if lt $i 3 }}
|
||||
is_beacon: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
### Using Templates
|
||||
|
||||
```bash
|
||||
# Generate configuration from template
|
||||
netrunner config generate \
|
||||
--template=config-template.yaml \
|
||||
--vars='{"NetworkID": 12345, "Nodes": 5}' \
|
||||
--output=network-config.json
|
||||
```
|
||||
|
||||
## Multi-Engine Configuration
|
||||
|
||||
### Engine-Specific Settings
|
||||
|
||||
```json
|
||||
{
|
||||
"engines": {
|
||||
"lux": {
|
||||
"binary_path": "/path/to/luxd",
|
||||
"default_flags": {
|
||||
"snow-sample-size": 20,
|
||||
"api-admin-enabled": true
|
||||
}
|
||||
},
|
||||
"geth": {
|
||||
"binary_path": "/path/to/geth",
|
||||
"default_flags": {
|
||||
"syncmode": "full",
|
||||
"cache": 1024
|
||||
}
|
||||
},
|
||||
"opstack": {
|
||||
"binary_path": "/path/to/op-node",
|
||||
"default_flags": {
|
||||
"l1.rpc-endpoint": "http://localhost:8545"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring and Metrics Configuration
|
||||
|
||||
### Prometheus Integration
|
||||
|
||||
```json
|
||||
{
|
||||
"monitoring": {
|
||||
"enabled": true,
|
||||
"prometheus": {
|
||||
"endpoint": ":9090",
|
||||
"namespace": "netrunner",
|
||||
"subsystem": "network",
|
||||
"push_gateway": "http://localhost:9091"
|
||||
},
|
||||
"metrics": {
|
||||
"collection_interval": "10s",
|
||||
"retention_period": "24h",
|
||||
"aggregation_rules": {
|
||||
"tps": "avg",
|
||||
"latency": "p95",
|
||||
"memory": "max"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Metrics
|
||||
|
||||
```go
|
||||
// Define custom metrics
|
||||
type MetricConfig struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // counter, gauge, histogram
|
||||
Help string `json:"help"`
|
||||
Labels []string `json:"labels"`
|
||||
Buckets []float64 `json:"buckets,omitempty"`
|
||||
}
|
||||
|
||||
// Register custom metrics
|
||||
customMetrics := []MetricConfig{
|
||||
{
|
||||
Name: "custom_tx_processing_time",
|
||||
Type: "histogram",
|
||||
Help: "Transaction processing time in seconds",
|
||||
Labels: ["chain", "vm_type"],
|
||||
Buckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 5},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## State Management Configuration
|
||||
|
||||
### Snapshot Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"snapshots": {
|
||||
"enabled": true,
|
||||
"directory": "/path/to/snapshots",
|
||||
"auto_snapshot": {
|
||||
"enabled": true,
|
||||
"interval": "1h",
|
||||
"max_snapshots": 24,
|
||||
"compression": "zstd",
|
||||
"compression_level": 3
|
||||
},
|
||||
"restore": {
|
||||
"verify_checksums": true,
|
||||
"parallel_restore": true,
|
||||
"max_workers": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network Simulation Configuration
|
||||
|
||||
### Chaos Engineering Settings
|
||||
|
||||
```json
|
||||
{
|
||||
"chaos": {
|
||||
"enabled": true,
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "random_node_failures",
|
||||
"probability": 0.1,
|
||||
"mtbf": "10m",
|
||||
"mttr": "2m",
|
||||
"affected_nodes": ["node1", "node2", "node3"]
|
||||
},
|
||||
{
|
||||
"name": "network_partition",
|
||||
"schedule": "*/30 * * * *",
|
||||
"duration": "5m",
|
||||
"partitions": [
|
||||
["node1", "node2"],
|
||||
["node3", "node4", "node5"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "latency_injection",
|
||||
"continuous": true,
|
||||
"base_latency": "50ms",
|
||||
"jitter": "10ms",
|
||||
"packet_loss": 0.02
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Load Testing Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"load_testing": {
|
||||
"enabled": true,
|
||||
"scenarios": [
|
||||
{
|
||||
"name": "sustained_load",
|
||||
"tps": 1000,
|
||||
"duration": "10m",
|
||||
"ramp_up": "1m"
|
||||
},
|
||||
{
|
||||
"name": "burst_load",
|
||||
"peak_tps": 5000,
|
||||
"burst_duration": "30s",
|
||||
"rest_duration": "2m",
|
||||
"cycles": 10
|
||||
}
|
||||
],
|
||||
"transaction_mix": {
|
||||
"transfer": 0.6,
|
||||
"contract_call": 0.3,
|
||||
"contract_deploy": 0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use configuration files** for complex setups
|
||||
2. **Version control** your configurations
|
||||
3. **Separate concerns** between network and node configs
|
||||
4. **Use environment variables** for sensitive data
|
||||
5. **Validate configurations** before deployment
|
||||
6. **Document custom configurations**
|
||||
7. **Use consistent naming** for nodes
|
||||
8. **Monitor configuration drift**
|
||||
9. **Backup configurations** before changes
|
||||
10. **Test configurations** in isolation first
|
||||
11. **Use templates** for repeatable deployments
|
||||
12. **Enable monitoring** from the start
|
||||
13. **Plan for failures** with chaos configuration
|
||||
14. **Automate snapshots** for recovery
|
||||
15. **Profile performance** settings before production
|
||||
@@ -0,0 +1,264 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Lux Netrunner - Network orchestration and testing framework for Lux blockchain
|
||||
---
|
||||
|
||||
# Lux Netrunner
|
||||
|
||||
Lux Netrunner is a powerful network orchestration and testing framework designed for blockchain development. It provides comprehensive tools for creating, managing, and testing multi-node blockchain networks with support for custom VMs, subnets, and complex network topologies.
|
||||
|
||||
## Overview
|
||||
|
||||
Netrunner simplifies blockchain network testing by providing:
|
||||
- **Dynamic network management** - Create, modify, and control networks on the fly
|
||||
- **Multi-chain support** - Deploy and test multiple blockchains simultaneously
|
||||
- **Subnet orchestration** - Easily create and manage subnets with custom validators
|
||||
- **Testing automation** - Built-in support for various testing scenarios
|
||||
- **Network snapshots** - Save and restore complete network states
|
||||
- **Chaos engineering** - Simulate failures and network partitions
|
||||
|
||||
## Key Features
|
||||
|
||||
### 🚀 High Performance
|
||||
- Optimized for speed and efficiency
|
||||
- Parallel node management
|
||||
- Fast network bootstrapping
|
||||
- Efficient resource utilization
|
||||
|
||||
### 🔧 Production Ready
|
||||
- Battle-tested in production environments
|
||||
- Comprehensive error handling
|
||||
- Robust state management
|
||||
- Enterprise-grade reliability
|
||||
|
||||
### 🧪 Comprehensive Testing
|
||||
- Unit testing support
|
||||
- Integration testing framework
|
||||
- Performance benchmarking
|
||||
- Chaos engineering capabilities
|
||||
|
||||
### 🛠 Flexible Configuration
|
||||
- Hierarchical configuration system
|
||||
- Per-node customization
|
||||
- Network-wide settings
|
||||
- Environment variable support
|
||||
|
||||
### 📡 Rich API
|
||||
- gRPC server for remote control
|
||||
- RESTful API via gRPC gateway
|
||||
- Command-line interface
|
||||
- Go library integration
|
||||
|
||||
## Architecture
|
||||
|
||||
Netrunner uses a client-server architecture:
|
||||
|
||||
```
|
||||
┌─────────────┐ gRPC ┌─────────────┐
|
||||
│ Client │ ◄────────────► │ Server │
|
||||
│ (CLI) │ │ (RPC) │
|
||||
└─────────────┘ └─────────────┘
|
||||
│
|
||||
┌──────┴──────┐
|
||||
│ │
|
||||
┌─────▼───┐ ┌─────▼───┐
|
||||
│ Node │ │ Node │
|
||||
│Manager │ │Manager │
|
||||
└─────────┘ └─────────┘
|
||||
│ │
|
||||
┌─────▼───┐ ┌─────▼───┐
|
||||
│ Lux │ │ Lux │
|
||||
│ Node │ │ Node │
|
||||
└─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install latest version
|
||||
curl -sSfL https://raw.githubusercontent.com/luxfi/netrunner/main/scripts/install.sh | sh -s
|
||||
|
||||
# Add to PATH
|
||||
export PATH=~/bin:$PATH
|
||||
```
|
||||
|
||||
### Start Your First Network
|
||||
|
||||
```bash
|
||||
# Start the RPC server
|
||||
netrunner server \
|
||||
--log-level debug \
|
||||
--port=":8080" \
|
||||
--grpc-gateway-port=":8081"
|
||||
|
||||
# In another terminal, start a 5-node network
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path /path/to/luxd
|
||||
|
||||
# Check network health
|
||||
netrunner control health --endpoint="0.0.0.0:8080"
|
||||
|
||||
# Get node endpoints
|
||||
netrunner control uris --endpoint="0.0.0.0:8080"
|
||||
```
|
||||
|
||||
### Deploy a Subnet
|
||||
|
||||
```bash
|
||||
# Create subnet with specific validators
|
||||
netrunner control create-subnets \
|
||||
'[{"participants": ["node1", "node2", "node3"]}]'
|
||||
|
||||
# Deploy a blockchain on the subnet
|
||||
netrunner control create-blockchains \
|
||||
'[{
|
||||
"vm_name": "subnetevm",
|
||||
"genesis": "/path/to/genesis.json"
|
||||
}]' \
|
||||
--plugin-dir /path/to/plugins
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Development Testing
|
||||
- Rapid prototyping of blockchain applications
|
||||
- Testing smart contracts and DApps
|
||||
- Debugging consensus mechanisms
|
||||
- Validating network upgrades
|
||||
|
||||
### Integration Testing
|
||||
- Multi-chain interaction testing
|
||||
- Cross-subnet communication
|
||||
- API endpoint validation
|
||||
- Transaction throughput testing
|
||||
|
||||
### Performance Testing
|
||||
- Benchmarking transaction throughput
|
||||
- Measuring network latency
|
||||
- Load testing under stress
|
||||
- Resource utilization analysis
|
||||
|
||||
### Chaos Engineering
|
||||
- Node failure simulation
|
||||
- Network partition testing
|
||||
- Byzantine behavior testing
|
||||
- Recovery mechanism validation
|
||||
|
||||
## Supported Engines
|
||||
|
||||
Netrunner supports multiple blockchain engines:
|
||||
|
||||
- **Lux** - Native Lux blockchain nodes
|
||||
- **Geth** - Ethereum nodes
|
||||
- **Optimism** - Layer 2 scaling solution
|
||||
- **Eth2** - Ethereum 2.0 beacon chain
|
||||
|
||||
## Documentation Sections
|
||||
|
||||
<Cards>
|
||||
<Card title="Network Orchestration" href="/docs/network-orchestration">
|
||||
Learn about network management, topologies, and dynamic node control
|
||||
</Card>
|
||||
|
||||
<Card title="Test Network Setup" href="/docs/test-network-setup">
|
||||
Complete guide to setting up various test network configurations
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" href="/docs/configuration">
|
||||
Comprehensive configuration reference for networks and nodes
|
||||
</Card>
|
||||
|
||||
<Card title="Testing Scenarios" href="/docs/testing-scenarios">
|
||||
Common testing patterns including unit, integration, and chaos tests
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## API Examples
|
||||
|
||||
### Create Network
|
||||
|
||||
```bash
|
||||
curl -X POST -k http://localhost:8081/v1/control/start -d '{
|
||||
"execPath": "/path/to/luxd",
|
||||
"numNodes": 5,
|
||||
"logLevel": "INFO"
|
||||
}'
|
||||
```
|
||||
|
||||
### Add Node
|
||||
|
||||
```bash
|
||||
curl -X POST -k http://localhost:8081/v1/control/addnode -d '{
|
||||
"name": "node99",
|
||||
"execPath": "/path/to/luxd",
|
||||
"logLevel": "INFO"
|
||||
}'
|
||||
```
|
||||
|
||||
### Save Snapshot
|
||||
|
||||
```bash
|
||||
curl -X POST -k http://localhost:8081/v1/control/savesnapshot -d '{
|
||||
"snapshot_name": "test-state-v1"
|
||||
}'
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Server Commands
|
||||
- `netrunner server` - Start the RPC server
|
||||
- `netrunner ping` - Ping the server
|
||||
|
||||
### Control Commands
|
||||
- `netrunner control start` - Start a network
|
||||
- `netrunner control stop` - Stop the network
|
||||
- `netrunner control health` - Check network health
|
||||
- `netrunner control status` - Get network status
|
||||
- `netrunner control uris` - Get node endpoints
|
||||
|
||||
### Node Commands
|
||||
- `netrunner control add-node` - Add a node
|
||||
- `netrunner control remove-node` - Remove a node
|
||||
- `netrunner control pause-node` - Pause a node
|
||||
- `netrunner control resume-node` - Resume a node
|
||||
- `netrunner control restart-node` - Restart a node
|
||||
|
||||
### Subnet Commands
|
||||
- `netrunner control create-subnets` - Create subnets
|
||||
- `netrunner control create-blockchains` - Deploy blockchains
|
||||
|
||||
### Snapshot Commands
|
||||
- `netrunner control save-snapshot` - Save network state
|
||||
- `netrunner control load-snapshot` - Load network state
|
||||
- `netrunner control get-snapshot-names` - List snapshots
|
||||
- `netrunner control remove-snapshot` - Delete snapshot
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please see our [GitHub repository](https://github.com/luxfi/netrunner) for:
|
||||
- Issue tracking
|
||||
- Pull requests
|
||||
- Development guidelines
|
||||
- Community discussions
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation**: This site
|
||||
- **GitHub Issues**: [Report bugs and request features](https://github.com/luxfi/netrunner/issues)
|
||||
- **Discord**: Join our community for discussions
|
||||
- **Email**: support@lux.network
|
||||
|
||||
## License
|
||||
|
||||
Lux Netrunner is licensed under the BSD 3-Clause License. See the [LICENSE](https://github.com/luxfi/netrunner/blob/main/LICENSE) file for details.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Set up your first test network](/docs/test-network-setup)
|
||||
- [Explore configuration options](/docs/configuration)
|
||||
- [Learn about network orchestration](/docs/network-orchestration)
|
||||
- [Run testing scenarios](/docs/testing-scenarios)
|
||||
@@ -0,0 +1,418 @@
|
||||
---
|
||||
title: Network Orchestration
|
||||
description: Understanding Lux Netrunner's network orchestration capabilities
|
||||
---
|
||||
|
||||
# Network Orchestration
|
||||
|
||||
Lux Netrunner provides powerful network orchestration capabilities for managing multi-node blockchain networks. This guide covers the core concepts and features for orchestrating complex test networks.
|
||||
|
||||
## Overview
|
||||
|
||||
Network orchestration in Netrunner allows you to:
|
||||
- Dynamically create and manage multi-node networks
|
||||
- Configure node topologies and relationships
|
||||
- Manage node lifecycle (start, stop, pause, resume)
|
||||
- Coordinate subnet and blockchain deployments
|
||||
- Simulate network conditions and failures
|
||||
|
||||
## Core Components
|
||||
|
||||
### Network Interface
|
||||
|
||||
The `Network` interface provides the primary abstraction for network management:
|
||||
|
||||
```go
|
||||
type Network interface {
|
||||
// Health check for all nodes
|
||||
Healthy(context.Context) error
|
||||
|
||||
// Lifecycle management
|
||||
Stop(context.Context) error
|
||||
AddNode(node.Config) (node.Node, error)
|
||||
RemoveNode(ctx context.Context, name string) error
|
||||
PauseNode(ctx context.Context, name string) error
|
||||
ResumeNode(ctx context.Context, name string) error
|
||||
|
||||
// Node access
|
||||
GetNode(name string) (node.Node, error)
|
||||
GetAllNodes() (map[string]node.Node, error)
|
||||
GetNodeNames() ([]string, error)
|
||||
|
||||
// Snapshots
|
||||
SaveSnapshot(context.Context, string) (string, error)
|
||||
RemoveSnapshot(string) error
|
||||
GetSnapshotNames() ([]string, error)
|
||||
}
|
||||
```
|
||||
|
||||
### Node Configuration
|
||||
|
||||
Each node in the network can be configured independently:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Name string `json:"name"`
|
||||
IsBeacon bool `json:"isBeacon"`
|
||||
StakingKey string `json:"stakingKey"`
|
||||
StakingCert string `json:"stakingCert"`
|
||||
StakingSigningKey string `json:"stakingSigningKey"`
|
||||
ConfigFile string `json:"configFile"`
|
||||
ChainConfigFiles map[string]string `json:"chainConfigFiles"`
|
||||
Flags map[string]interface{} `json:"flags"`
|
||||
BinaryPath string `json:"binaryPath"`
|
||||
}
|
||||
```
|
||||
|
||||
## Network Topologies
|
||||
|
||||
### Linear Topology
|
||||
|
||||
A simple chain of nodes where each node connects to the next:
|
||||
|
||||
```bash
|
||||
netrunner control start \
|
||||
--number-of-nodes=5 \
|
||||
--node-path /path/to/luxd \
|
||||
--topology linear
|
||||
```
|
||||
|
||||
### Full Mesh Topology
|
||||
|
||||
Every node connects to every other node:
|
||||
|
||||
```bash
|
||||
netrunner control start \
|
||||
--number-of-nodes=5 \
|
||||
--node-path /path/to/luxd \
|
||||
--topology mesh
|
||||
```
|
||||
|
||||
### Star Topology
|
||||
|
||||
All nodes connect to a central bootstrap node:
|
||||
|
||||
```bash
|
||||
netrunner control start \
|
||||
--number-of-nodes=5 \
|
||||
--node-path /path/to/luxd \
|
||||
--topology star
|
||||
```
|
||||
|
||||
### Custom Topology
|
||||
|
||||
Define custom node relationships:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": {
|
||||
"node1": {"peers": ["node2", "node3"]},
|
||||
"node2": {"peers": ["node1", "node4"]},
|
||||
"node3": {"peers": ["node1", "node5"]},
|
||||
"node4": {"peers": ["node2", "node5"]},
|
||||
"node5": {"peers": ["node3", "node4"]}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dynamic Node Management
|
||||
|
||||
### Adding Nodes
|
||||
|
||||
Add nodes to a running network:
|
||||
|
||||
```bash
|
||||
# Add a new node
|
||||
netrunner control add-node \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--node-path /path/to/luxd \
|
||||
node99
|
||||
|
||||
# Configure the new node
|
||||
netrunner control add-node \
|
||||
--node-config '{"index-enabled":true, "api-admin-enabled":true}' \
|
||||
node100
|
||||
```
|
||||
|
||||
### Removing Nodes
|
||||
|
||||
Remove nodes from the network:
|
||||
|
||||
```bash
|
||||
netrunner control remove-node node99
|
||||
```
|
||||
|
||||
### Pausing and Resuming Nodes
|
||||
|
||||
Simulate node failures and recovery:
|
||||
|
||||
```bash
|
||||
# Pause a node (simulate failure)
|
||||
netrunner control pause-node node1
|
||||
|
||||
# Resume the node
|
||||
netrunner control resume-node node1
|
||||
```
|
||||
|
||||
## Subnet Orchestration
|
||||
|
||||
### Creating Subnets
|
||||
|
||||
Create subnets with specific validators:
|
||||
|
||||
```bash
|
||||
# Create subnet with all nodes as validators
|
||||
netrunner control create-subnets '[{}]'
|
||||
|
||||
# Create subnet with specific validators
|
||||
netrunner control create-subnets \
|
||||
'[{"participants": ["node1", "node2", "node3"]}]'
|
||||
|
||||
# Create multiple subnets
|
||||
netrunner control create-subnets \
|
||||
'[
|
||||
{"participants": ["node1", "node2"]},
|
||||
{"participants": ["node3", "node4"]}
|
||||
]'
|
||||
```
|
||||
|
||||
### Elastic Subnets
|
||||
|
||||
Configure elastic subnets with custom parameters:
|
||||
|
||||
```go
|
||||
type ElasticSubnetSpec struct {
|
||||
SubnetID *string
|
||||
AssetName string
|
||||
AssetSymbol string
|
||||
InitialSupply uint64
|
||||
MaxSupply uint64
|
||||
MinConsumptionRate uint64
|
||||
MaxConsumptionRate uint64
|
||||
MinValidatorStake uint64
|
||||
MaxValidatorStake uint64
|
||||
MinStakeDuration time.Duration
|
||||
MaxStakeDuration time.Duration
|
||||
MinDelegationFee uint32
|
||||
MinDelegatorStake uint64
|
||||
MaxValidatorWeightFactor byte
|
||||
UptimeRequirement uint32
|
||||
}
|
||||
```
|
||||
|
||||
## Blockchain Deployment
|
||||
|
||||
### Deploying Custom VMs
|
||||
|
||||
Deploy custom virtual machines on subnets:
|
||||
|
||||
```bash
|
||||
# Deploy a single blockchain
|
||||
netrunner control create-blockchains \
|
||||
'[{
|
||||
"vm_name": "subnetevm",
|
||||
"genesis": "/path/to/genesis.json",
|
||||
"subnet_id": "2S4rNkYf..."
|
||||
}]' \
|
||||
--plugin-dir /path/to/plugins
|
||||
|
||||
# Deploy with chain configuration
|
||||
netrunner control create-blockchains \
|
||||
'[{
|
||||
"vm_name": "quantumvm",
|
||||
"genesis": "/path/to/genesis.json",
|
||||
"chain_config": "/path/to/chain.json",
|
||||
"network_upgrade": "/path/to/upgrade.json"
|
||||
}]' \
|
||||
--plugin-dir /path/to/plugins
|
||||
```
|
||||
|
||||
### Per-Node Configuration
|
||||
|
||||
Configure blockchains differently on each node:
|
||||
|
||||
```json
|
||||
{
|
||||
"node1": {"rpc-tx-fee-cap": 101},
|
||||
"node2": {"rpc-tx-fee-cap": 102},
|
||||
"node3": {"rpc-tx-fee-cap": 103}
|
||||
}
|
||||
```
|
||||
|
||||
## Network Snapshots
|
||||
|
||||
### Saving Network State
|
||||
|
||||
Save the complete network state for later restoration:
|
||||
|
||||
```bash
|
||||
# Save current network state
|
||||
netrunner control save-snapshot my-test-state
|
||||
|
||||
# List available snapshots
|
||||
netrunner control get-snapshot-names
|
||||
```
|
||||
|
||||
### Restoring Network State
|
||||
|
||||
Restore a network from a saved snapshot:
|
||||
|
||||
```bash
|
||||
# Load a snapshot
|
||||
netrunner control load-snapshot my-test-state
|
||||
|
||||
# Load with different binary
|
||||
netrunner control load-snapshot my-test-state \
|
||||
--node-path /path/to/new/luxd \
|
||||
--plugin-dir /path/to/new/plugins
|
||||
```
|
||||
|
||||
## Advanced Orchestration
|
||||
|
||||
### Multi-Engine Support
|
||||
|
||||
Netrunner supports orchestrating multiple blockchain engines:
|
||||
|
||||
```go
|
||||
// Engine types supported
|
||||
type EngineType string
|
||||
|
||||
const (
|
||||
LuxEngine EngineType = "lux" // Lux node
|
||||
GethEngine EngineType = "geth" // Ethereum Geth
|
||||
OpEngine EngineType = "op" // Optimism
|
||||
Eth2Engine EngineType = "eth2" // Ethereum 2.0
|
||||
)
|
||||
```
|
||||
|
||||
### Test Peer Attachment
|
||||
|
||||
Attach test peers for message injection:
|
||||
|
||||
```bash
|
||||
# Attach a test peer
|
||||
netrunner control attach-peer --node-name node1
|
||||
|
||||
# Send messages through test peer
|
||||
netrunner control send-outbound-message \
|
||||
--node-name node1 \
|
||||
--peer-id "7Xhw2mDxuDS..." \
|
||||
--message-op=16 \
|
||||
--message-bytes-b64="EAA..."
|
||||
```
|
||||
|
||||
### Health Monitoring
|
||||
|
||||
Monitor network and node health:
|
||||
|
||||
```bash
|
||||
# Check network health
|
||||
netrunner control health
|
||||
|
||||
# Stream status updates
|
||||
netrunner control stream-status \
|
||||
--push-interval=5s
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Resource Management
|
||||
|
||||
- Clean up test networks after use
|
||||
- Use snapshots for reproducible tests
|
||||
- Monitor resource consumption
|
||||
|
||||
### 2. Configuration Management
|
||||
|
||||
- Use configuration files for complex setups
|
||||
- Version control your test configurations
|
||||
- Document custom topologies
|
||||
|
||||
### 3. Testing Strategies
|
||||
|
||||
- Start with simple topologies
|
||||
- Gradually increase complexity
|
||||
- Use snapshots for regression testing
|
||||
- Automate common test scenarios
|
||||
|
||||
### 4. Debugging
|
||||
|
||||
- Enable debug logging for troubleshooting
|
||||
- Use node-specific configurations for isolation
|
||||
- Monitor individual node health
|
||||
- Check network connectivity
|
||||
|
||||
## Example: Complete Network Setup
|
||||
|
||||
Here's a complete example of setting up a test network:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start the RPC server
|
||||
netrunner server \
|
||||
--log-level debug \
|
||||
--port=":8080" \
|
||||
--grpc-gateway-port=":8081" &
|
||||
|
||||
# Wait for server
|
||||
sleep 2
|
||||
|
||||
# Start a 5-node network
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path /path/to/luxd \
|
||||
--global-node-config '{
|
||||
"network-peer-list-gossip-frequency": "250ms",
|
||||
"api-admin-enabled": true
|
||||
}'
|
||||
|
||||
# Wait for network health
|
||||
netrunner control health --endpoint="0.0.0.0:8080"
|
||||
|
||||
# Create a subnet
|
||||
netrunner control create-subnets \
|
||||
'[{"participants": ["node1", "node2", "node3"]}]'
|
||||
|
||||
# Deploy a blockchain
|
||||
netrunner control create-blockchains \
|
||||
'[{
|
||||
"vm_name": "quantumvm",
|
||||
"genesis": "/path/to/genesis.json"
|
||||
}]' \
|
||||
--plugin-dir /path/to/plugins
|
||||
|
||||
# Save the network state
|
||||
netrunner control save-snapshot test-network-v1
|
||||
|
||||
# Run tests...
|
||||
|
||||
# Clean up
|
||||
netrunner control stop --endpoint="0.0.0.0:8080"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Nodes fail to start**: Check binary path and permissions
|
||||
2. **Network unhealthy**: Verify port availability and firewall rules
|
||||
3. **Subnet creation fails**: Ensure sufficient validators
|
||||
4. **Snapshot restore fails**: Check disk space and file permissions
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Get detailed status
|
||||
netrunner control status
|
||||
|
||||
# Check specific node
|
||||
netrunner control get-node node1
|
||||
|
||||
# View node logs
|
||||
netrunner control logs node1
|
||||
|
||||
# Check RPC connectivity
|
||||
curl -X POST http://localhost:8081/v1/ping
|
||||
```
|
||||
@@ -0,0 +1,685 @@
|
||||
---
|
||||
title: Network Simulation Capabilities
|
||||
description: Advanced network simulation features and scenarios with Lux Netrunner
|
||||
---
|
||||
|
||||
# Network Simulation Capabilities
|
||||
|
||||
Lux Netrunner provides powerful simulation capabilities for testing blockchain networks under various conditions, from ideal scenarios to extreme failure cases.
|
||||
|
||||
## Simulation Overview
|
||||
|
||||
### Core Simulation Features
|
||||
|
||||
- **Dynamic Network Topologies**: Create complex network structures
|
||||
- **Failure Injection**: Simulate node crashes, network partitions
|
||||
- **Latency Simulation**: Add realistic network delays
|
||||
- **Byzantine Behavior**: Test consensus under adversarial conditions
|
||||
- **Resource Constraints**: Simulate limited CPU, memory, bandwidth
|
||||
- **Time Manipulation**: Fast-forward or slow down network time
|
||||
- **State Snapshots**: Save and restore network states
|
||||
|
||||
## Network Topology Simulation
|
||||
|
||||
### Star Topology
|
||||
|
||||
```go
|
||||
func CreateStarTopology(ctx context.Context) (*network.Network, error) {
|
||||
// Central node with all others connected to it
|
||||
config := &netrunner.NetworkConfig{
|
||||
Topology: "star",
|
||||
CentralNode: "node1",
|
||||
SatelliteNodes: []string{"node2", "node3", "node4", "node5"},
|
||||
NodeConfigs: []netrunner.NodeConfig{
|
||||
{
|
||||
Name: "node1",
|
||||
IsBeacon: true,
|
||||
Resources: netrunner.HighResources(),
|
||||
},
|
||||
// Satellite nodes with limited resources
|
||||
{Name: "node2", Resources: netrunner.LowResources()},
|
||||
{Name: "node3", Resources: netrunner.LowResources()},
|
||||
{Name: "node4", Resources: netrunner.LowResources()},
|
||||
{Name: "node5", Resources: netrunner.LowResources()},
|
||||
},
|
||||
}
|
||||
|
||||
network, err := netrunner.CreateNetwork(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure network links
|
||||
for _, satellite := range config.SatelliteNodes {
|
||||
network.SetLinkProperties(config.CentralNode, satellite, &LinkProps{
|
||||
Bandwidth: "100Mbps",
|
||||
Latency: "5ms",
|
||||
PacketLoss: 0.01,
|
||||
})
|
||||
}
|
||||
|
||||
return network, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Mesh Topology
|
||||
|
||||
```go
|
||||
func CreateMeshTopology(ctx context.Context, partial bool) (*network.Network, error) {
|
||||
nodes := []string{"node1", "node2", "node3", "node4", "node5"}
|
||||
|
||||
config := &netrunner.NetworkConfig{
|
||||
Topology: "mesh",
|
||||
PartialMesh: partial,
|
||||
Nodes: nodes,
|
||||
}
|
||||
|
||||
network, err := netrunner.CreateNetwork(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create connections between all pairs (full mesh)
|
||||
// or subset of pairs (partial mesh)
|
||||
connections := generateMeshConnections(nodes, partial)
|
||||
|
||||
for _, conn := range connections {
|
||||
network.Connect(conn.From, conn.To, &LinkProps{
|
||||
Bandwidth: "1Gbps",
|
||||
Latency: randomLatency(1, 50), // 1-50ms
|
||||
Jitter: "2ms",
|
||||
})
|
||||
}
|
||||
|
||||
return network, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Hierarchical Topology
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Create hierarchical network structure
|
||||
create_hierarchical_network() {
|
||||
# Layer 1: Core validators
|
||||
netrunner control start \
|
||||
--node-names "core1,core2,core3" \
|
||||
--node-config '{
|
||||
"stake-amount": 2000000000000,
|
||||
"delegator-fee": 10000
|
||||
}'
|
||||
|
||||
# Layer 2: Regional validators
|
||||
for region in us eu asia; do
|
||||
netrunner control add-node \
|
||||
--name "regional-${region}" \
|
||||
--bootstrap-nodes "core1,core2,core3" \
|
||||
--node-config '{
|
||||
"stake-amount": 1000000000000,
|
||||
"max-connections": 50
|
||||
}'
|
||||
done
|
||||
|
||||
# Layer 3: Edge nodes
|
||||
for i in {1..10}; do
|
||||
REGION=$((i % 3))
|
||||
case $REGION in
|
||||
0) BOOTSTRAP="regional-us" ;;
|
||||
1) BOOTSTRAP="regional-eu" ;;
|
||||
2) BOOTSTRAP="regional-asia" ;;
|
||||
esac
|
||||
|
||||
netrunner control add-node \
|
||||
--name "edge-${i}" \
|
||||
--bootstrap-nodes "${BOOTSTRAP}" \
|
||||
--node-config '{
|
||||
"stake-amount": 0,
|
||||
"pruning-enabled": true
|
||||
}'
|
||||
done
|
||||
}
|
||||
```
|
||||
|
||||
## Failure Simulation
|
||||
|
||||
### Node Failure Scenarios
|
||||
|
||||
```go
|
||||
type FailureSimulator struct {
|
||||
network network.Network
|
||||
random *rand.Rand
|
||||
}
|
||||
|
||||
func (fs *FailureSimulator) SimulateRandomFailures(
|
||||
failureRate float64,
|
||||
mtbf time.Duration,
|
||||
mttr time.Duration,
|
||||
) {
|
||||
nodes, _ := fs.network.GetAllNodes()
|
||||
|
||||
for {
|
||||
// Wait for mean time between failures
|
||||
time.Sleep(fs.exponentialDuration(mtbf))
|
||||
|
||||
// Select random node to fail
|
||||
nodeList := maps.Keys(nodes)
|
||||
failNode := nodeList[fs.random.Intn(len(nodeList))]
|
||||
|
||||
// Simulate failure
|
||||
log.Printf("Simulating failure of node: %s", failNode)
|
||||
fs.network.PauseNode(failNode)
|
||||
|
||||
// Recovery after mean time to repair
|
||||
go func(node string) {
|
||||
time.Sleep(fs.exponentialDuration(mttr))
|
||||
log.Printf("Recovering node: %s", node)
|
||||
fs.network.ResumeNode(node)
|
||||
}(failNode)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FailureSimulator) SimulateCascadingFailure() {
|
||||
// Start with single node failure
|
||||
initialFailure := "node1"
|
||||
fs.network.PauseNode(initialFailure)
|
||||
|
||||
// Cascade to dependent nodes
|
||||
dependencies := map[string][]string{
|
||||
"node1": {"node2", "node3"},
|
||||
"node2": {"node4"},
|
||||
"node3": {"node5"},
|
||||
}
|
||||
|
||||
queue := []string{initialFailure}
|
||||
failed := make(map[string]bool)
|
||||
|
||||
for len(queue) > 0 {
|
||||
current := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
if failed[current] {
|
||||
continue
|
||||
}
|
||||
|
||||
failed[current] = true
|
||||
time.Sleep(500 * time.Millisecond) // Cascade delay
|
||||
|
||||
for _, dep := range dependencies[current] {
|
||||
if !failed[dep] {
|
||||
log.Printf("Cascade failure: %s -> %s", current, dep)
|
||||
fs.network.PauseNode(dep)
|
||||
queue = append(queue, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Network Partition Simulation
|
||||
|
||||
```go
|
||||
func SimulateNetworkPartition(network network.Network) error {
|
||||
// Create two network partitions
|
||||
partition1 := []string{"node1", "node2", "node3"}
|
||||
partition2 := []string{"node4", "node5", "node6"}
|
||||
|
||||
log.Println("Creating network partition...")
|
||||
|
||||
// Block all communication between partitions
|
||||
for _, n1 := range partition1 {
|
||||
for _, n2 := range partition2 {
|
||||
if err := network.BlockConnection(n1, n2); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := network.BlockConnection(n2, n1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate partition for specified duration
|
||||
partitionDuration := 30 * time.Second
|
||||
time.Sleep(partitionDuration)
|
||||
|
||||
log.Println("Healing network partition...")
|
||||
|
||||
// Restore connections
|
||||
for _, n1 := range partition1 {
|
||||
for _, n2 := range partition2 {
|
||||
network.UnblockConnection(n1, n2)
|
||||
network.UnblockConnection(n2, n1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Byzantine Node Simulation
|
||||
|
||||
```go
|
||||
type ByzantineNode struct {
|
||||
node node.Node
|
||||
behavior ByzantineBehavior
|
||||
}
|
||||
|
||||
type ByzantineBehavior int
|
||||
|
||||
const (
|
||||
DoubleSigning ByzantineBehavior = iota
|
||||
ConflictingVotes
|
||||
SelectiveBroadcast
|
||||
MessageDelaying
|
||||
DataCorruption
|
||||
)
|
||||
|
||||
func (bn *ByzantineNode) SimulateByzantineBehavior(ctx context.Context) {
|
||||
switch bn.behavior {
|
||||
case DoubleSigning:
|
||||
bn.simulateDoubleSigning(ctx)
|
||||
case ConflictingVotes:
|
||||
bn.simulateConflictingVotes(ctx)
|
||||
case SelectiveBroadcast:
|
||||
bn.simulateSelectiveBroadcast(ctx)
|
||||
case MessageDelaying:
|
||||
bn.simulateMessageDelaying(ctx)
|
||||
case DataCorruption:
|
||||
bn.simulateDataCorruption(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (bn *ByzantineNode) simulateConflictingVotes(ctx context.Context) {
|
||||
// Send different votes to different peers
|
||||
peers := bn.node.GetPeers()
|
||||
|
||||
for i, peer := range peers {
|
||||
vote := &Vote{
|
||||
BlockID: fmt.Sprintf("block_%d", i), // Different block for each peer
|
||||
Height: 100,
|
||||
Round: 1,
|
||||
}
|
||||
|
||||
bn.node.SendToPeer(peer, vote)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Network Condition Simulation
|
||||
|
||||
### Latency and Bandwidth Simulation
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Simulate various network conditions using tc (traffic control)
|
||||
simulate_network_conditions() {
|
||||
NODE=$1
|
||||
CONDITION=$2
|
||||
|
||||
case $CONDITION in
|
||||
"slow")
|
||||
# Simulate slow network (2G/3G mobile)
|
||||
tc qdisc add dev eth0 root netem delay 300ms 50ms \
|
||||
loss 2% duplicate 1% corrupt 0.1%
|
||||
tc qdisc add dev eth0 root tbf rate 256kbit burst 32kbit latency 400ms
|
||||
;;
|
||||
|
||||
"congested")
|
||||
# Simulate network congestion
|
||||
tc qdisc add dev eth0 root netem delay 100ms 20ms \
|
||||
loss 5% duplicate 2%
|
||||
tc qdisc add dev eth0 root tbf rate 1mbit burst 32kbit latency 200ms
|
||||
;;
|
||||
|
||||
"satellite")
|
||||
# Simulate satellite link
|
||||
tc qdisc add dev eth0 root netem delay 600ms 100ms \
|
||||
loss 0.5%
|
||||
tc qdisc add dev eth0 root tbf rate 10mbit burst 64kbit latency 700ms
|
||||
;;
|
||||
|
||||
"datacenter")
|
||||
# Simulate datacenter network
|
||||
tc qdisc add dev eth0 root netem delay 0.5ms 0.1ms
|
||||
tc qdisc add dev eth0 root tbf rate 10gbit burst 1mbit latency 1ms
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Apply different conditions to different nodes
|
||||
for i in {1..5}; do
|
||||
CONDITIONS=("slow" "congested" "satellite" "datacenter" "normal")
|
||||
CONDITION=${CONDITIONS[$((i-1))]}
|
||||
simulate_network_conditions "node${i}" "${CONDITION}"
|
||||
done
|
||||
```
|
||||
|
||||
### Packet Loss and Jitter
|
||||
|
||||
```go
|
||||
func SimulateUnreliableNetwork(network network.Network) {
|
||||
nodes, _ := network.GetAllNodes()
|
||||
|
||||
for name := range nodes {
|
||||
// Configure different network conditions per node
|
||||
conditions := &NetworkConditions{
|
||||
PacketLoss: rand.Float64() * 0.1, // 0-10% loss
|
||||
Jitter: time.Duration(rand.Intn(50)) * time.Millisecond,
|
||||
Reordering: rand.Float64() * 0.05, // 0-5% reordering
|
||||
Duplication: rand.Float64() * 0.02, // 0-2% duplication
|
||||
Corruption: rand.Float64() * 0.001, // 0-0.1% corruption
|
||||
}
|
||||
|
||||
network.SetNodeNetworkConditions(name, conditions)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resource Constraint Simulation
|
||||
|
||||
### CPU and Memory Limits
|
||||
|
||||
```go
|
||||
func SimulateResourceConstraints(network network.Network) error {
|
||||
constraints := []ResourceConstraint{
|
||||
{Node: "node1", CPULimit: "0.5", MemoryLimit: "512M"},
|
||||
{Node: "node2", CPULimit: "1.0", MemoryLimit: "1G"},
|
||||
{Node: "node3", CPULimit: "2.0", MemoryLimit: "2G"},
|
||||
{Node: "node4", CPULimit: "0.25", MemoryLimit: "256M"},
|
||||
{Node: "node5", CPULimit: "4.0", MemoryLimit: "4G"},
|
||||
}
|
||||
|
||||
for _, c := range constraints {
|
||||
if err := network.SetResourceLimits(c.Node, c.CPULimit, c.MemoryLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor resource usage under constraints
|
||||
go monitorResourceUsage(network)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Disk I/O Throttling
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Throttle disk I/O for specific nodes
|
||||
throttle_disk_io() {
|
||||
NODE=$1
|
||||
READ_BPS=$2
|
||||
WRITE_BPS=$3
|
||||
|
||||
# Get node's data directory
|
||||
DATA_DIR=$(netrunner control get-node-info ${NODE} | jq -r '.dataDir')
|
||||
|
||||
# Apply I/O limits using cgroups
|
||||
cgcreate -g blkio:/netrunner/${NODE}
|
||||
echo "8:0 ${READ_BPS}" > /sys/fs/cgroup/blkio/netrunner/${NODE}/blkio.throttle.read_bps_device
|
||||
echo "8:0 ${WRITE_BPS}" > /sys/fs/cgroup/blkio/netrunner/${NODE}/blkio.throttle.write_bps_device
|
||||
|
||||
# Move node process to cgroup
|
||||
PID=$(netrunner control get-node-info ${NODE} | jq -r '.pid')
|
||||
echo ${PID} > /sys/fs/cgroup/blkio/netrunner/${NODE}/cgroup.procs
|
||||
}
|
||||
|
||||
# Apply different I/O limits
|
||||
throttle_disk_io "node1" "10485760" "10485760" # 10 MB/s
|
||||
throttle_disk_io "node2" "5242880" "5242880" # 5 MB/s
|
||||
throttle_disk_io "node3" "1048576" "1048576" # 1 MB/s
|
||||
```
|
||||
|
||||
## Time Manipulation
|
||||
|
||||
### Fast-Forward Time
|
||||
|
||||
```go
|
||||
func SimulateTimeFastForward(network network.Network, speedup float64) {
|
||||
// Configure network to run at accelerated time
|
||||
network.SetTimeAcceleration(speedup)
|
||||
|
||||
// Example: 10x speedup means 1 real second = 10 simulated seconds
|
||||
log.Printf("Time acceleration set to %.1fx", speedup)
|
||||
|
||||
// Monitor block production at accelerated rate
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
for range ticker.C {
|
||||
height, _ := network.GetBlockHeight()
|
||||
simTime := network.GetSimulatedTime()
|
||||
log.Printf("Real time: %v, Sim time: %v, Height: %d",
|
||||
time.Now(), simTime, height)
|
||||
}
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
## Complex Simulation Scenarios
|
||||
|
||||
### Multi-Region Deployment
|
||||
|
||||
```go
|
||||
func SimulateMultiRegionDeployment() (*network.Network, error) {
|
||||
regions := []Region{
|
||||
{Name: "us-east", Latency: 10, Nodes: 3},
|
||||
{Name: "eu-west", Latency: 50, Nodes: 3},
|
||||
{Name: "asia-pacific", Latency: 100, Nodes: 3},
|
||||
}
|
||||
|
||||
network, err := netrunner.CreateNetwork(&NetworkConfig{
|
||||
Regions: regions,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure inter-region latencies
|
||||
network.SetRegionLatency("us-east", "eu-west", 80*time.Millisecond)
|
||||
network.SetRegionLatency("us-east", "asia-pacific", 150*time.Millisecond)
|
||||
network.SetRegionLatency("eu-west", "asia-pacific", 120*time.Millisecond)
|
||||
|
||||
return network, nil
|
||||
}
|
||||
```
|
||||
|
||||
### DDoS Attack Simulation
|
||||
|
||||
```go
|
||||
func SimulateDDoSAttack(target string, intensity int) {
|
||||
// Create attacker nodes
|
||||
attackers := make([]*AttackerNode, intensity)
|
||||
for i := 0; i < intensity; i++ {
|
||||
attackers[i] = &AttackerNode{
|
||||
ID: fmt.Sprintf("attacker-%d", i),
|
||||
Target: target,
|
||||
RequestsPerSecond: 1000,
|
||||
}
|
||||
}
|
||||
|
||||
// Start attack
|
||||
var wg sync.WaitGroup
|
||||
for _, attacker := range attackers {
|
||||
wg.Add(1)
|
||||
go func(a *AttackerNode) {
|
||||
defer wg.Done()
|
||||
a.Attack()
|
||||
}(attacker)
|
||||
}
|
||||
|
||||
// Monitor target node health during attack
|
||||
go monitorNodeHealth(target)
|
||||
|
||||
// Stop attack after duration
|
||||
time.Sleep(60 * time.Second)
|
||||
for _, attacker := range attackers {
|
||||
attacker.Stop()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
```
|
||||
|
||||
## Simulation Orchestration
|
||||
|
||||
### Scenario Runner
|
||||
|
||||
```yaml
|
||||
# simulation-scenario.yaml
|
||||
name: "Complex Network Simulation"
|
||||
duration: "1h"
|
||||
steps:
|
||||
- name: "Initial Setup"
|
||||
action: "start_network"
|
||||
params:
|
||||
nodes: 10
|
||||
topology: "mesh"
|
||||
|
||||
- name: "Warmup Phase"
|
||||
action: "wait"
|
||||
duration: "5m"
|
||||
|
||||
- name: "Inject Failures"
|
||||
action: "node_failures"
|
||||
params:
|
||||
failure_rate: 0.1
|
||||
mtbf: "10m"
|
||||
mttr: "2m"
|
||||
|
||||
- name: "Network Partition"
|
||||
action: "partition"
|
||||
at: "15m"
|
||||
params:
|
||||
groups: [["node1", "node2", "node3"], ["node4", "node5", "node6"]]
|
||||
duration: "5m"
|
||||
|
||||
- name: "Byzantine Behavior"
|
||||
action: "byzantine"
|
||||
at: "25m"
|
||||
params:
|
||||
nodes: ["node7"]
|
||||
behavior: "double_signing"
|
||||
|
||||
- name: "Load Test"
|
||||
action: "load"
|
||||
at: "30m"
|
||||
params:
|
||||
tps: 1000
|
||||
duration: "15m"
|
||||
|
||||
- name: "Recovery"
|
||||
action: "heal_all"
|
||||
at: "45m"
|
||||
|
||||
- name: "Final Verification"
|
||||
action: "verify_consensus"
|
||||
at: "50m"
|
||||
```
|
||||
|
||||
### Automated Simulation Runner
|
||||
|
||||
```go
|
||||
func RunSimulationScenario(scenarioFile string) error {
|
||||
scenario, err := LoadScenario(scenarioFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
network, err := netrunner.StartNetwork(scenario.NetworkConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer network.Stop()
|
||||
|
||||
// Execute scenario steps
|
||||
for _, step := range scenario.Steps {
|
||||
log.Printf("Executing step: %s", step.Name)
|
||||
|
||||
if step.At != "" {
|
||||
waitUntil(scenario.StartTime, step.At)
|
||||
}
|
||||
|
||||
switch step.Action {
|
||||
case "node_failures":
|
||||
go simulateNodeFailures(network, step.Params)
|
||||
case "partition":
|
||||
go simulatePartition(network, step.Params)
|
||||
case "byzantine":
|
||||
go simulateByzantine(network, step.Params)
|
||||
case "load":
|
||||
go simulateLoad(network, step.Params)
|
||||
case "heal_all":
|
||||
healNetwork(network)
|
||||
case "verify_consensus":
|
||||
verifyConsensus(network)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for scenario completion
|
||||
time.Sleep(scenario.Duration)
|
||||
|
||||
// Generate report
|
||||
return generateSimulationReport(scenario, network)
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoring and Analysis
|
||||
|
||||
### Real-time Simulation Dashboard
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Network Simulation Dashboard</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="network-topology"></div>
|
||||
<div id="node-health"></div>
|
||||
<div id="consensus-metrics"></div>
|
||||
<div id="performance-charts"></div>
|
||||
|
||||
<script>
|
||||
// WebSocket connection to simulation
|
||||
const ws = new WebSocket('ws://localhost:8080/simulation');
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
updateDashboard(data);
|
||||
};
|
||||
|
||||
function updateDashboard(data) {
|
||||
// Update network topology visualization
|
||||
updateTopology(data.topology);
|
||||
|
||||
// Update node health indicators
|
||||
updateNodeHealth(data.nodes);
|
||||
|
||||
// Update consensus metrics
|
||||
updateConsensusMetrics(data.consensus);
|
||||
|
||||
// Update performance charts
|
||||
updatePerformanceCharts(data.performance);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start Simple**: Begin with basic scenarios and increase complexity
|
||||
2. **Reproducibility**: Use seeds for random generators
|
||||
3. **Isolation**: Run simulations in isolated environments
|
||||
4. **Monitoring**: Collect comprehensive metrics during simulation
|
||||
5. **Gradual Changes**: Introduce failures and conditions gradually
|
||||
6. **Recovery Testing**: Always test recovery mechanisms
|
||||
7. **Documentation**: Document simulation parameters and results
|
||||
8. **Automation**: Automate common simulation scenarios
|
||||
9. **Validation**: Verify expected behavior after each simulation
|
||||
10. **Resource Management**: Clean up resources after simulations
|
||||
@@ -0,0 +1,682 @@
|
||||
---
|
||||
title: Performance Testing Guide
|
||||
description: Comprehensive guide to performance testing and benchmarking with Lux Netrunner
|
||||
---
|
||||
|
||||
# Performance Testing Guide
|
||||
|
||||
This guide provides detailed instructions for conducting performance tests, benchmarking network capacity, and analyzing blockchain metrics using Lux Netrunner.
|
||||
|
||||
## Overview
|
||||
|
||||
Performance testing with Netrunner enables you to:
|
||||
- Measure transaction throughput (TPS)
|
||||
- Analyze block propagation latency
|
||||
- Test network scalability limits
|
||||
- Benchmark consensus performance
|
||||
- Evaluate resource utilization
|
||||
- Stress test under various conditions
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
### Key Performance Indicators (KPIs)
|
||||
|
||||
```yaml
|
||||
Throughput Metrics:
|
||||
- Transactions Per Second (TPS)
|
||||
- Blocks Per Second (BPS)
|
||||
- Messages Per Second
|
||||
- Cross-chain Transfer Rate
|
||||
|
||||
Latency Metrics:
|
||||
- Block Finality Time
|
||||
- Transaction Confirmation Time
|
||||
- P2P Message Latency
|
||||
- API Response Time
|
||||
|
||||
Resource Metrics:
|
||||
- CPU Utilization per Node
|
||||
- Memory Consumption
|
||||
- Disk I/O Operations
|
||||
- Network Bandwidth Usage
|
||||
|
||||
Consensus Metrics:
|
||||
- Time to Consensus
|
||||
- Validator Agreement Rate
|
||||
- Fork Resolution Time
|
||||
- Byzantine Fault Recovery
|
||||
```
|
||||
|
||||
## Basic Performance Testing
|
||||
|
||||
### Transaction Throughput Test
|
||||
|
||||
```go
|
||||
package performance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/netrunner"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTransactionThroughput(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start optimized network
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 5,
|
||||
BinaryPath: luxdPath,
|
||||
Flags: map[string]interface{}{
|
||||
"snow-sample-size": 2,
|
||||
"snow-quorum-size": 2,
|
||||
"snow-optimal-processing": 100,
|
||||
"snow-max-processing": 2048,
|
||||
"db-type": "memdb", // Use in-memory DB for max speed
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Wait for network to be healthy
|
||||
require.NoError(t, network.Healthy(ctx))
|
||||
|
||||
// Prepare wallets
|
||||
wallets := prepareWallets(network, 10)
|
||||
|
||||
// Metrics collection
|
||||
var totalTxs atomic.Uint64
|
||||
var successfulTxs atomic.Uint64
|
||||
var failedTxs atomic.Uint64
|
||||
|
||||
// Start metrics collector
|
||||
go collectMetrics(network, &totalTxs, &successfulTxs, &failedTxs)
|
||||
|
||||
// Run load test
|
||||
startTime := time.Now()
|
||||
duration := 60 * time.Second
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(wallets); i++ {
|
||||
wg.Add(1)
|
||||
go func(wallet Wallet) {
|
||||
defer wg.Done()
|
||||
|
||||
for time.Since(startTime) < duration {
|
||||
tx := createTransaction(wallet)
|
||||
if err := submitTransaction(network, tx); err != nil {
|
||||
failedTxs.Add(1)
|
||||
} else {
|
||||
successfulTxs.Add(1)
|
||||
}
|
||||
totalTxs.Add(1)
|
||||
}
|
||||
}(wallets[i])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
elapsed := time.Since(startTime)
|
||||
|
||||
// Calculate metrics
|
||||
tps := float64(successfulTxs.Load()) / elapsed.Seconds()
|
||||
successRate := float64(successfulTxs.Load()) / float64(totalTxs.Load()) * 100
|
||||
|
||||
// Report results
|
||||
t.Logf("Performance Test Results:")
|
||||
t.Logf(" Duration: %v", elapsed)
|
||||
t.Logf(" Total Transactions: %d", totalTxs.Load())
|
||||
t.Logf(" Successful: %d", successfulTxs.Load())
|
||||
t.Logf(" Failed: %d", failedTxs.Load())
|
||||
t.Logf(" TPS: %.2f", tps)
|
||||
t.Logf(" Success Rate: %.2f%%", successRate)
|
||||
|
||||
// Assert minimum performance
|
||||
require.Greater(t, tps, 100.0, "TPS below minimum threshold")
|
||||
require.Greater(t, successRate, 95.0, "Success rate below threshold")
|
||||
}
|
||||
```
|
||||
|
||||
### Block Production Rate Test
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start network with fast block production
|
||||
netrunner control start \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_PATH} \
|
||||
--global-node-config '{
|
||||
"proposervm-use-current-height": true,
|
||||
"proposervm-max-block-delay": 100000000,
|
||||
"consensus-on-accept-gossip-validator-size": 0,
|
||||
"consensus-on-accept-gossip-peer-size": 10
|
||||
}'
|
||||
|
||||
# Wait for network health
|
||||
netrunner control health
|
||||
|
||||
# Monitor block production for 1 minute
|
||||
START_HEIGHT=$(curl -s -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "platform.getHeight",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/bc/P | jq -r '.result.height')
|
||||
|
||||
sleep 60
|
||||
|
||||
END_HEIGHT=$(curl -s -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "platform.getHeight",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/bc/P | jq -r '.result.height')
|
||||
|
||||
BLOCKS_PRODUCED=$((END_HEIGHT - START_HEIGHT))
|
||||
echo "Blocks produced in 60 seconds: ${BLOCKS_PRODUCED}"
|
||||
echo "Block production rate: $(echo "scale=2; ${BLOCKS_PRODUCED} / 60" | bc) blocks/second"
|
||||
```
|
||||
|
||||
## Advanced Performance Testing
|
||||
|
||||
### Network Scalability Test
|
||||
|
||||
```go
|
||||
func TestNetworkScalability(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
nodeCounts := []int{5, 10, 20, 50}
|
||||
results := make(map[int]PerformanceResult)
|
||||
|
||||
for _, nodeCount := range nodeCounts {
|
||||
t.Run(fmt.Sprintf("%d_nodes", nodeCount), func(t *testing.T) {
|
||||
// Start network with specified node count
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: nodeCount,
|
||||
BinaryPath: luxdPath,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Run performance test
|
||||
result := runPerformanceTest(network, 30*time.Second)
|
||||
results[nodeCount] = result
|
||||
|
||||
// Log results
|
||||
t.Logf("Nodes: %d, TPS: %.2f, Latency: %.2fms",
|
||||
nodeCount, result.TPS, result.AvgLatency)
|
||||
})
|
||||
}
|
||||
|
||||
// Analyze scaling efficiency
|
||||
baselineTPS := results[5].TPS
|
||||
for nodes, result := range results {
|
||||
efficiency := (result.TPS / baselineTPS) / float64(nodes/5) * 100
|
||||
t.Logf("Scaling efficiency at %d nodes: %.2f%%", nodes, efficiency)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Consensus Performance Under Load
|
||||
|
||||
```go
|
||||
func TestConsensusPerformance(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start network with consensus monitoring
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 7,
|
||||
BinaryPath: luxdPath,
|
||||
Flags: map[string]interface{}{
|
||||
"consensus-gossip-frequency": "100ms",
|
||||
"consensus-shutdown-timeout": "1s",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Monitor consensus metrics
|
||||
consensusMetrics := make(chan ConsensusMetric, 1000)
|
||||
go monitorConsensus(network, consensusMetrics)
|
||||
|
||||
// Generate load
|
||||
loadGen := NewLoadGenerator(network)
|
||||
loadGen.Start(1000) // 1000 TPS target
|
||||
defer loadGen.Stop()
|
||||
|
||||
// Collect metrics for 2 minutes
|
||||
time.Sleep(2 * time.Minute)
|
||||
|
||||
// Analyze consensus performance
|
||||
var metrics []ConsensusMetric
|
||||
close(consensusMetrics)
|
||||
for m := range consensusMetrics {
|
||||
metrics = append(metrics, m)
|
||||
}
|
||||
|
||||
// Calculate statistics
|
||||
avgConsensusTime := calculateAverage(metrics, func(m ConsensusMetric) float64 {
|
||||
return m.ConsensusTime.Seconds()
|
||||
})
|
||||
|
||||
avgValidatorAgreement := calculateAverage(metrics, func(m ConsensusMetric) float64 {
|
||||
return float64(m.ValidatorAgreement)
|
||||
})
|
||||
|
||||
t.Logf("Consensus Performance Under Load:")
|
||||
t.Logf(" Average Consensus Time: %.3fs", avgConsensusTime)
|
||||
t.Logf(" Validator Agreement: %.2f%%", avgValidatorAgreement)
|
||||
t.Logf(" Total Rounds: %d", len(metrics))
|
||||
|
||||
// Assert performance requirements
|
||||
require.Less(t, avgConsensusTime, 2.0, "Consensus taking too long")
|
||||
require.Greater(t, avgValidatorAgreement, 95.0, "Low validator agreement")
|
||||
}
|
||||
```
|
||||
|
||||
## Load Generation Tools
|
||||
|
||||
### Custom Load Generator
|
||||
|
||||
```go
|
||||
type LoadGenerator struct {
|
||||
network network.Network
|
||||
targetTPS int
|
||||
stopCh chan struct{}
|
||||
wg sync.WaitGroup
|
||||
metrics *LoadMetrics
|
||||
}
|
||||
|
||||
func (lg *LoadGenerator) Start(targetTPS int) {
|
||||
lg.targetTPS = targetTPS
|
||||
lg.stopCh = make(chan struct{})
|
||||
|
||||
// Calculate transactions per worker
|
||||
numWorkers := 10
|
||||
txPerWorkerPerSecond := targetTPS / numWorkers
|
||||
interval := time.Second / time.Duration(txPerWorkerPerSecond)
|
||||
|
||||
for i := 0; i < numWorkers; i++ {
|
||||
lg.wg.Add(1)
|
||||
go lg.worker(i, interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (lg *LoadGenerator) worker(id int, interval time.Duration) {
|
||||
defer lg.wg.Done()
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-lg.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
tx := lg.generateTransaction(id)
|
||||
start := time.Now()
|
||||
|
||||
if err := lg.submitTransaction(tx); err != nil {
|
||||
lg.metrics.recordError(err)
|
||||
} else {
|
||||
lg.metrics.recordSuccess(time.Since(start))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Transaction Submission
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Function to submit batch of transactions
|
||||
submit_batch() {
|
||||
local BATCH_SIZE=$1
|
||||
local BATCH_ID=$2
|
||||
|
||||
echo "Submitting batch ${BATCH_ID} with ${BATCH_SIZE} transactions..."
|
||||
|
||||
for i in $(seq 1 ${BATCH_SIZE}); do
|
||||
# Create transaction in background
|
||||
{
|
||||
curl -s -X POST --data "{
|
||||
\"jsonrpc\": \"2.0\",
|
||||
\"method\": \"platform.issueTx\",
|
||||
\"params\": {
|
||||
\"tx\": \"${TRANSACTION_DATA}\"
|
||||
},
|
||||
\"id\": ${i}
|
||||
}" -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/bc/P > /dev/null
|
||||
} &
|
||||
done
|
||||
|
||||
# Wait for batch to complete
|
||||
wait
|
||||
echo "Batch ${BATCH_ID} completed"
|
||||
}
|
||||
|
||||
# Submit multiple batches
|
||||
for batch in {1..10}; do
|
||||
submit_batch 100 ${batch}
|
||||
sleep 1
|
||||
done
|
||||
```
|
||||
|
||||
## Resource Monitoring
|
||||
|
||||
### System Resource Monitoring
|
||||
|
||||
```go
|
||||
func monitorResources(network network.Network) *ResourceMetrics {
|
||||
metrics := &ResourceMetrics{
|
||||
Nodes: make(map[string]*NodeResources),
|
||||
}
|
||||
|
||||
nodes, _ := network.GetAllNodes()
|
||||
|
||||
for name, node := range nodes {
|
||||
pid := node.GetProcessID()
|
||||
|
||||
// Get process stats
|
||||
proc, _ := process.NewProcess(pid)
|
||||
cpuPercent, _ := proc.CPUPercent()
|
||||
memInfo, _ := proc.MemoryInfo()
|
||||
ioCounters, _ := proc.IOCounters()
|
||||
|
||||
metrics.Nodes[name] = &NodeResources{
|
||||
CPUPercent: cpuPercent,
|
||||
MemoryRSS: memInfo.RSS,
|
||||
MemoryVMS: memInfo.VMS,
|
||||
DiskReadOps: ioCounters.ReadCount,
|
||||
DiskWriteOps: ioCounters.WriteCount,
|
||||
DiskReadBytes: ioCounters.ReadBytes,
|
||||
DiskWriteBytes: ioCounters.WriteBytes,
|
||||
}
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
```
|
||||
|
||||
### Network Bandwidth Monitoring
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Monitor network bandwidth for all nodes
|
||||
monitor_bandwidth() {
|
||||
echo "Monitoring network bandwidth..."
|
||||
|
||||
# Get initial stats
|
||||
declare -A INITIAL_RX INITIAL_TX
|
||||
|
||||
for i in {1..5}; do
|
||||
PORT=$((9651 + (i-1)*2))
|
||||
STATS=$(ss -i "sport = :${PORT}" | grep -oP 'bytes_\K[0-9]+')
|
||||
INITIAL_RX[node${i}]=$(echo ${STATS} | cut -d' ' -f1)
|
||||
INITIAL_TX[node${i}]=$(echo ${STATS} | cut -d' ' -f2)
|
||||
done
|
||||
|
||||
sleep 10
|
||||
|
||||
# Get final stats and calculate bandwidth
|
||||
for i in {1..5}; do
|
||||
PORT=$((9651 + (i-1)*2))
|
||||
STATS=$(ss -i "sport = :${PORT}" | grep -oP 'bytes_\K[0-9]+')
|
||||
FINAL_RX=$(echo ${STATS} | cut -d' ' -f1)
|
||||
FINAL_TX=$(echo ${STATS} | cut -d' ' -f2)
|
||||
|
||||
RX_BW=$((($FINAL_RX - ${INITIAL_RX[node${i}]}) / 10))
|
||||
TX_BW=$((($FINAL_TX - ${INITIAL_TX[node${i}]}) / 10))
|
||||
|
||||
echo "node${i}: RX: ${RX_BW} bytes/sec, TX: ${TX_BW} bytes/sec"
|
||||
done
|
||||
}
|
||||
|
||||
monitor_bandwidth
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Network Configuration for Performance
|
||||
|
||||
```json
|
||||
{
|
||||
"snow-sample-size": 2,
|
||||
"snow-quorum-size": 2,
|
||||
"snow-virtuous-commit-threshold": 5,
|
||||
"snow-rogue-commit-threshold": 10,
|
||||
"snow-optimal-processing": 50,
|
||||
"snow-max-processing": 2048,
|
||||
"snow-max-time-processing": "2s",
|
||||
"consensus-gossip-frequency": "250ms",
|
||||
"consensus-shutdown-timeout": "5s",
|
||||
"proposervm-use-current-height": true,
|
||||
"api-max-duration": "0s",
|
||||
"api-max-blocks-per-request": 0,
|
||||
"continuous-profiler-frequency": "15m",
|
||||
"db-type": "leveldb",
|
||||
"network-compression-type": "zstd",
|
||||
"network-max-reconnect-delay": "1s",
|
||||
"network-health-max-send-fail-rate": 0.9,
|
||||
"state-sync-enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
### Database Optimization
|
||||
|
||||
```go
|
||||
func optimizeDatabaseForPerformance(network network.Network) {
|
||||
nodes, _ := network.GetAllNodes()
|
||||
|
||||
for _, node := range nodes {
|
||||
// Configure LevelDB for performance
|
||||
node.SetConfig(map[string]interface{}{
|
||||
"db-type": "leveldb",
|
||||
"leveldb-compression": "snappy",
|
||||
"leveldb-filter-bits": 12,
|
||||
"leveldb-cache-size": 536870912, // 512MB
|
||||
"leveldb-write-buffer-size": 134217728, // 128MB
|
||||
"leveldb-max-open-files": 1024,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Reports
|
||||
|
||||
### Generating HTML Performance Report
|
||||
|
||||
```go
|
||||
func generatePerformanceReport(results *TestResults) {
|
||||
html := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Netrunner Performance Report</title>
|
||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Performance Test Results</h1>
|
||||
<div id="tps-chart"></div>
|
||||
<div id="latency-chart"></div>
|
||||
<div id="resource-chart"></div>
|
||||
<script>
|
||||
// TPS over time
|
||||
var tpsData = [{
|
||||
x: %s,
|
||||
y: %s,
|
||||
type: 'scatter',
|
||||
name: 'TPS'
|
||||
}];
|
||||
Plotly.newPlot('tps-chart', tpsData);
|
||||
|
||||
// Latency distribution
|
||||
var latencyData = [{
|
||||
x: %s,
|
||||
type: 'histogram',
|
||||
name: 'Latency (ms)'
|
||||
}];
|
||||
Plotly.newPlot('latency-chart', latencyData);
|
||||
|
||||
// Resource usage
|
||||
var resourceData = [{
|
||||
x: ['CPU', 'Memory', 'Disk I/O'],
|
||||
y: %s,
|
||||
type: 'bar'
|
||||
}];
|
||||
Plotly.newPlot('resource-chart', resourceData);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// Format data and write report
|
||||
formattedHTML := fmt.Sprintf(html,
|
||||
results.TimePoints,
|
||||
results.TPSValues,
|
||||
results.LatencyValues,
|
||||
results.ResourceValues,
|
||||
)
|
||||
|
||||
os.WriteFile("performance-report.html", []byte(formattedHTML), 0644)
|
||||
}
|
||||
```
|
||||
|
||||
### CSV Export for Analysis
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Export performance metrics to CSV
|
||||
export_metrics() {
|
||||
echo "timestamp,node,tps,latency_ms,cpu_percent,memory_mb" > metrics.csv
|
||||
|
||||
while true; do
|
||||
TIMESTAMP=$(date +%s)
|
||||
|
||||
for i in {1..5}; do
|
||||
# Get metrics from node
|
||||
METRICS=$(curl -s http://localhost:$((9630 + (i-1)*2))/ext/metrics)
|
||||
|
||||
TPS=$(echo ${METRICS} | jq -r '.tps')
|
||||
LATENCY=$(echo ${METRICS} | jq -r '.avgLatency')
|
||||
CPU=$(ps aux | grep "node${i}" | awk '{print $3}')
|
||||
MEM=$(ps aux | grep "node${i}" | awk '{print $6}')
|
||||
|
||||
echo "${TIMESTAMP},node${i},${TPS},${LATENCY},${CPU},${MEM}" >> metrics.csv
|
||||
done
|
||||
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
export_metrics
|
||||
```
|
||||
|
||||
## Continuous Performance Testing
|
||||
|
||||
### GitHub Actions Performance Workflow
|
||||
|
||||
```yaml
|
||||
name: Performance Tests
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Daily
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
performance:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/luxfi/netrunner/main/scripts/install.sh | sh -s
|
||||
echo "$HOME/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Run Performance Tests
|
||||
run: |
|
||||
netrunner server &
|
||||
sleep 2
|
||||
|
||||
# Run performance suite
|
||||
./scripts/performance-test.sh
|
||||
|
||||
- name: Upload Results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: performance-results
|
||||
path: |
|
||||
performance-report.html
|
||||
metrics.csv
|
||||
|
||||
- name: Comment on PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const metrics = JSON.parse(fs.readFileSync('metrics.json'));
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `## Performance Test Results\n
|
||||
- TPS: ${metrics.tps}\n
|
||||
- Latency: ${metrics.latency}ms\n
|
||||
- Success Rate: ${metrics.successRate}%`
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Warm-up Period**: Allow 30 seconds warm-up before measuring
|
||||
2. **Multiple Runs**: Average results from multiple test runs
|
||||
3. **Baseline Comparison**: Compare against established baselines
|
||||
4. **Resource Isolation**: Run tests on dedicated hardware
|
||||
5. **Network Conditions**: Test under various network conditions
|
||||
6. **Data Variety**: Use realistic transaction patterns
|
||||
7. **Monitor Everything**: Collect all available metrics
|
||||
8. **Incremental Load**: Gradually increase load to find limits
|
||||
9. **Document Context**: Record test environment details
|
||||
10. **Automated Analysis**: Use scripts to analyze results
|
||||
|
||||
## Troubleshooting Performance Issues
|
||||
|
||||
### Common Bottlenecks
|
||||
|
||||
- **CPU Saturation**: Check for high CPU usage patterns
|
||||
- **Memory Leaks**: Monitor memory growth over time
|
||||
- **Disk I/O**: Look for excessive disk operations
|
||||
- **Network Congestion**: Check for packet loss or high latency
|
||||
- **Lock Contention**: Profile for mutex bottlenecks
|
||||
- **Database Performance**: Analyze database query patterns
|
||||
|
||||
### Performance Profiling
|
||||
|
||||
```bash
|
||||
# Enable CPU profiling
|
||||
netrunner control start \
|
||||
--node-path ${LUXD_PATH} \
|
||||
--global-node-config '{
|
||||
"profile-continuous-enabled": true,
|
||||
"profile-continuous-freq": "1m",
|
||||
"profile-continuous-max-files": 10
|
||||
}'
|
||||
|
||||
# Analyze profile data
|
||||
go tool pprof -http=:8080 cpu.prof
|
||||
```
|
||||
@@ -0,0 +1,584 @@
|
||||
---
|
||||
title: Test Network Setup
|
||||
description: Complete guide to setting up test networks with Lux Netrunner
|
||||
---
|
||||
|
||||
# Test Network Setup
|
||||
|
||||
This guide walks through setting up various test network configurations using Lux Netrunner, from simple single-node setups to complex multi-subnet deployments.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Installation
|
||||
|
||||
Install Netrunner using the installation script:
|
||||
|
||||
```bash
|
||||
# Install latest version
|
||||
curl -sSfL https://raw.githubusercontent.com/luxfi/netrunner/main/scripts/install.sh | sh -s
|
||||
|
||||
# Install specific version
|
||||
curl -sSfL https://raw.githubusercontent.com/luxfi/netrunner/main/scripts/install.sh | sh -s v1.3.1
|
||||
|
||||
# Add to PATH
|
||||
export PATH=~/bin:$PATH
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
|
||||
For development and testing:
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/luxfi/netrunner.git
|
||||
cd netrunner
|
||||
|
||||
# Build
|
||||
./scripts/build.sh
|
||||
|
||||
# Add to PATH
|
||||
export PATH=$PWD/bin:$PATH
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
```
|
||||
|
||||
### Required Components
|
||||
|
||||
1. **Lux Node Binary**: Build or download the Lux node
|
||||
2. **Plugin Directory**: For custom VMs (optional)
|
||||
3. **Genesis Files**: For custom chains (optional)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Start the RPC Server
|
||||
|
||||
Netrunner uses an RPC server for network management:
|
||||
|
||||
```bash
|
||||
netrunner server \
|
||||
--log-level debug \
|
||||
--port=":8080" \
|
||||
--grpc-gateway-port=":8081"
|
||||
```
|
||||
|
||||
Keep this running in a separate terminal.
|
||||
|
||||
### 2. Start a Basic Network
|
||||
|
||||
Create a 5-node network:
|
||||
|
||||
```bash
|
||||
# Set the path to your Lux binary
|
||||
LUXD_EXEC_PATH="${HOME}/go/src/github.com/luxfi/node/build/node"
|
||||
|
||||
# Start the network
|
||||
netrunner control start \
|
||||
--log-level debug \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH}
|
||||
```
|
||||
|
||||
### 3. Verify Network Health
|
||||
|
||||
```bash
|
||||
# Check health
|
||||
netrunner control health --endpoint="0.0.0.0:8080"
|
||||
|
||||
# Get node URLs
|
||||
netrunner control uris --endpoint="0.0.0.0:8080"
|
||||
|
||||
# Get network status
|
||||
netrunner control status --endpoint="0.0.0.0:8080"
|
||||
```
|
||||
|
||||
## Network Configurations
|
||||
|
||||
### Single Node Setup
|
||||
|
||||
For simple testing:
|
||||
|
||||
```bash
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=1 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--global-node-config '{
|
||||
"http-port": 9630,
|
||||
"staking-port": 9631,
|
||||
"api-admin-enabled": true,
|
||||
"index-enabled": true
|
||||
}'
|
||||
```
|
||||
|
||||
### Multi-Node Network
|
||||
|
||||
Standard 5-node test network:
|
||||
|
||||
```bash
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--global-node-config '{
|
||||
"network-peer-list-gossip-frequency": "250ms",
|
||||
"network-max-reconnect-delay": "1s",
|
||||
"health-check-frequency": "2s",
|
||||
"api-admin-enabled": true
|
||||
}'
|
||||
```
|
||||
|
||||
### Custom Node Configurations
|
||||
|
||||
Configure each node individually:
|
||||
|
||||
```bash
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--custom-node-configs '{
|
||||
"node1": {"http-port": 9630, "log-level": "debug"},
|
||||
"node2": {"http-port": 9632, "log-level": "info"},
|
||||
"node3": {"http-port": 9634, "log-level": "info"},
|
||||
"node4": {"http-port": 9636, "log-level": "info"},
|
||||
"node5": {"http-port": 9638, "log-level": "warn"}
|
||||
}'
|
||||
```
|
||||
|
||||
## Subnet Networks
|
||||
|
||||
### Create a Permissioned Subnet
|
||||
|
||||
```bash
|
||||
# Start the network
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH}
|
||||
|
||||
# Wait for health
|
||||
netrunner control health
|
||||
|
||||
# Create subnet with all nodes
|
||||
netrunner control create-subnets '[{}]'
|
||||
|
||||
# Or create with specific validators
|
||||
netrunner control create-subnets \
|
||||
'[{"participants": ["node1", "node2", "node3"]}]'
|
||||
```
|
||||
|
||||
### Create an Elastic Subnet
|
||||
|
||||
Elastic subnets allow dynamic validator sets:
|
||||
|
||||
```bash
|
||||
# Create elastic subnet configuration
|
||||
cat > elastic-subnet.json <<EOF
|
||||
{
|
||||
"AssetName": "TestToken",
|
||||
"AssetSymbol": "TEST",
|
||||
"InitialSupply": 1000000000,
|
||||
"MaxSupply": 2000000000,
|
||||
"MinConsumptionRate": 1000,
|
||||
"MaxConsumptionRate": 10000,
|
||||
"MinValidatorStake": 100000,
|
||||
"MaxValidatorStake": 10000000,
|
||||
"MinStakeDuration": 86400,
|
||||
"MaxStakeDuration": 31536000,
|
||||
"MinDelegationFee": 20000,
|
||||
"MinDelegatorStake": 1000,
|
||||
"MaxValidatorWeightFactor": 5,
|
||||
"UptimeRequirement": 800000
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create elastic subnet
|
||||
netrunner control create-elastic-subnet elastic-subnet.json
|
||||
```
|
||||
|
||||
## Custom Blockchain Deployment
|
||||
|
||||
### Deploy Subnet-EVM
|
||||
|
||||
```bash
|
||||
# Generate VM ID
|
||||
subnet-cli create VMID subnetevm
|
||||
# Output: srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy
|
||||
|
||||
# Build the plugin
|
||||
cd ${HOME}/go/src/github.com/luxfi/subnet-evm
|
||||
go build -v \
|
||||
-o ${LUXD_PLUGIN_PATH}/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy \
|
||||
./plugin
|
||||
|
||||
# Create genesis
|
||||
cat > /tmp/subnet-evm.genesis.json <<EOF
|
||||
{
|
||||
"config": {
|
||||
"chainId": 99999,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"subnetEVMTimestamp": 0,
|
||||
"feeConfig": {
|
||||
"gasLimit": 20000000,
|
||||
"minBaseFee": 1000000000,
|
||||
"targetGas": 100000000,
|
||||
"baseFeeChangeDenominator": 48,
|
||||
"minBlockGasCost": 0,
|
||||
"maxBlockGasCost": 10000000,
|
||||
"targetBlockRate": 2,
|
||||
"blockGasCostStep": 500000
|
||||
}
|
||||
},
|
||||
"alloc": {
|
||||
"0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": {
|
||||
"balance": "0x52B7D2DCC80CD2E4000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0",
|
||||
"timestamp": "0x0",
|
||||
"gasLimit": "0x1312D00",
|
||||
"difficulty": "0x0"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Start network with subnet-evm
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--plugin-dir ${LUXD_PLUGIN_PATH} \
|
||||
--blockchain-specs '[{
|
||||
"vm_name": "subnetevm",
|
||||
"genesis": "/tmp/subnet-evm.genesis.json"
|
||||
}]'
|
||||
```
|
||||
|
||||
### Deploy QuantumVM
|
||||
|
||||
```bash
|
||||
# Start network with QuantumVM
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--plugin-dir ${LUXD_PLUGIN_PATH} \
|
||||
--blockchain-specs '[{
|
||||
"vm_name": "quantumvm",
|
||||
"genesis": "/path/to/quantum.genesis.json",
|
||||
"chain_config": "/path/to/quantum.config.json",
|
||||
"network_upgrade": "/path/to/quantum.upgrade.json"
|
||||
}]'
|
||||
```
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Basic Connectivity Test
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start network
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=3 \
|
||||
--node-path ${LUXD_EXEC_PATH}
|
||||
|
||||
# Wait for health
|
||||
netrunner control health
|
||||
|
||||
# Test P-Chain API
|
||||
NODE1_URL=$(netrunner control uris | jq -r '.["node1"]')
|
||||
curl -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "platform.getHeight",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' ${NODE1_URL}/ext/bc/P
|
||||
|
||||
# Clean up
|
||||
netrunner control stop
|
||||
```
|
||||
|
||||
### Validator Test Network
|
||||
|
||||
```bash
|
||||
# Create network with validators
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--global-node-config '{
|
||||
"staking-enabled": true,
|
||||
"staking-port": 9651,
|
||||
"api-admin-enabled": true
|
||||
}'
|
||||
|
||||
# Wait for bootstrap
|
||||
netrunner control health
|
||||
|
||||
# Create subnet with validators
|
||||
netrunner control create-subnets '[{
|
||||
"participants": ["node1", "node2", "node3"]
|
||||
}]'
|
||||
|
||||
# Get subnet info
|
||||
netrunner control status
|
||||
```
|
||||
|
||||
### Multi-Chain Test Network
|
||||
|
||||
```bash
|
||||
# Prepare multiple blockchain specs
|
||||
cat > blockchains.json <<EOF
|
||||
[
|
||||
{
|
||||
"vm_name": "subnetevm",
|
||||
"genesis": "/path/to/subnetevm.genesis.json",
|
||||
"subnet_spec": {
|
||||
"participants": ["node1", "node2", "node3"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"vm_name": "quantumvm",
|
||||
"genesis": "/path/to/quantumvm.genesis.json",
|
||||
"subnet_spec": {
|
||||
"participants": ["node3", "node4", "node5"]
|
||||
}
|
||||
}
|
||||
]
|
||||
EOF
|
||||
|
||||
# Start network with multiple chains
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--plugin-dir ${LUXD_PLUGIN_PATH} \
|
||||
--blockchain-specs-file blockchains.json
|
||||
```
|
||||
|
||||
## Advanced Testing
|
||||
|
||||
### Network Fault Injection
|
||||
|
||||
Test network resilience:
|
||||
|
||||
```bash
|
||||
# Start network
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH}
|
||||
|
||||
# Pause node (simulate failure)
|
||||
netrunner control pause-node node2
|
||||
|
||||
# Verify network still healthy
|
||||
netrunner control health
|
||||
|
||||
# Resume node
|
||||
netrunner control resume-node node2
|
||||
|
||||
# Add new node
|
||||
netrunner control add-node \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
node6
|
||||
|
||||
# Remove node
|
||||
netrunner control remove-node node1
|
||||
```
|
||||
|
||||
### Performance Testing
|
||||
|
||||
```bash
|
||||
# Start network with performance monitoring
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=10 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--global-node-config '{
|
||||
"api-metrics-enabled": true,
|
||||
"metrics-enabled": true,
|
||||
"metrics-expensive-enabled": true
|
||||
}'
|
||||
|
||||
# Stream status for monitoring
|
||||
netrunner control stream-status \
|
||||
--push-interval=1s \
|
||||
--endpoint="0.0.0.0:8080"
|
||||
```
|
||||
|
||||
### Snapshot Testing
|
||||
|
||||
```bash
|
||||
# Start and configure network
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH}
|
||||
|
||||
# Create subnets and blockchains
|
||||
netrunner control create-subnets '[{"participants": ["node1", "node2"]}]'
|
||||
|
||||
# Save snapshot
|
||||
netrunner control save-snapshot baseline-v1
|
||||
|
||||
# Make changes
|
||||
netrunner control add-node node6
|
||||
|
||||
# Restore to baseline
|
||||
netrunner control load-snapshot baseline-v1
|
||||
```
|
||||
|
||||
## Automation Scripts
|
||||
|
||||
### Complete Test Suite
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
LUXD_PATH="/path/to/luxd"
|
||||
PLUGIN_PATH="/path/to/plugins"
|
||||
ENDPOINT="0.0.0.0:8080"
|
||||
|
||||
# Start server
|
||||
echo "Starting RPC server..."
|
||||
netrunner server --port=":8080" --grpc-gateway-port=":8081" &
|
||||
SERVER_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Function to clean up
|
||||
cleanup() {
|
||||
echo "Cleaning up..."
|
||||
netrunner control stop --endpoint=${ENDPOINT} || true
|
||||
kill ${SERVER_PID} || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Test 1: Basic network
|
||||
echo "Test 1: Basic 5-node network"
|
||||
netrunner control start \
|
||||
--endpoint=${ENDPOINT} \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_PATH}
|
||||
|
||||
netrunner control health --endpoint=${ENDPOINT}
|
||||
netrunner control save-snapshot test1-complete
|
||||
netrunner control stop --endpoint=${ENDPOINT}
|
||||
|
||||
# Test 2: Subnet network
|
||||
echo "Test 2: Subnet deployment"
|
||||
netrunner control load-snapshot test1-complete \
|
||||
--endpoint=${ENDPOINT}
|
||||
|
||||
netrunner control create-subnets '[{"participants": ["node1", "node2", "node3"]}]' \
|
||||
--endpoint=${ENDPOINT}
|
||||
|
||||
netrunner control health --endpoint=${ENDPOINT}
|
||||
netrunner control stop --endpoint=${ENDPOINT}
|
||||
|
||||
echo "All tests passed!"
|
||||
```
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test-network.yml
|
||||
name: Test Network
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Netrunner
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/luxfi/netrunner/main/scripts/install.sh | sh -s
|
||||
echo "$HOME/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build Lux Node
|
||||
run: |
|
||||
git clone https://github.com/luxfi/node.git
|
||||
cd node && ./scripts/build.sh
|
||||
|
||||
- name: Run Network Tests
|
||||
run: |
|
||||
netrunner server &
|
||||
sleep 2
|
||||
netrunner control start \
|
||||
--number-of-nodes=3 \
|
||||
--node-path ./node/build/node
|
||||
netrunner control health
|
||||
netrunner control stop
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
If you encounter port conflicts:
|
||||
|
||||
```bash
|
||||
# Use custom ports
|
||||
netrunner control start \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--custom-node-configs '{
|
||||
"node1": {"http-port": 19630, "staking-port": 19631},
|
||||
"node2": {"http-port": 19632, "staking-port": 19633},
|
||||
"node3": {"http-port": 19634, "staking-port": 19635},
|
||||
"node4": {"http-port": 19636, "staking-port": 19637},
|
||||
"node5": {"http-port": 19638, "staking-port": 19639}
|
||||
}'
|
||||
```
|
||||
|
||||
### Node Startup Issues
|
||||
|
||||
Debug node startup:
|
||||
|
||||
```bash
|
||||
# Enable debug logging
|
||||
netrunner control start \
|
||||
--log-level debug \
|
||||
--endpoint="0.0.0.0:8080" \
|
||||
--number-of-nodes=1 \
|
||||
--node-path ${LUXD_EXEC_PATH} \
|
||||
--global-node-config '{
|
||||
"log-level": "debug",
|
||||
"log-display-level": "trace"
|
||||
}'
|
||||
|
||||
# Check logs
|
||||
netrunner control logs node1
|
||||
```
|
||||
|
||||
### Network Health Issues
|
||||
|
||||
```bash
|
||||
# Get detailed status
|
||||
netrunner control status --endpoint="0.0.0.0:8080"
|
||||
|
||||
# Check individual node
|
||||
curl -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "info.isBootstrapped",
|
||||
"params": {"chain": "P"},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' http://localhost:9630/ext/info
|
||||
```
|
||||
@@ -0,0 +1,722 @@
|
||||
---
|
||||
title: Testing Scenarios
|
||||
description: Common testing scenarios and patterns for Lux Netrunner
|
||||
---
|
||||
|
||||
# Testing Scenarios
|
||||
|
||||
This guide presents common testing scenarios for blockchain development using Lux Netrunner, including unit tests, integration tests, performance tests, and chaos engineering.
|
||||
|
||||
## Testing Framework
|
||||
|
||||
### Test Structure
|
||||
|
||||
```go
|
||||
type TestScenario struct {
|
||||
Name string
|
||||
Description string
|
||||
Setup func(context.Context) error
|
||||
Execute func(context.Context) error
|
||||
Verify func(context.Context) error
|
||||
Cleanup func(context.Context) error
|
||||
}
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
netrunner test all
|
||||
|
||||
# Run specific test
|
||||
netrunner test engine lux
|
||||
|
||||
# Run test suite
|
||||
netrunner test stack
|
||||
```
|
||||
|
||||
## Unit Testing
|
||||
|
||||
### Testing Individual Components
|
||||
|
||||
#### Node Health Check Test
|
||||
|
||||
```go
|
||||
func TestNodeHealth(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start single node
|
||||
node, err := startTestNode(ctx, "test-node")
|
||||
require.NoError(t, err)
|
||||
defer node.Stop(ctx)
|
||||
|
||||
// Wait for bootstrap
|
||||
maxAttempts := 30
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
health, err := node.Health(ctx)
|
||||
if err == nil && health.Healthy {
|
||||
break
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
// Verify health
|
||||
health, err := node.Health(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, health.Healthy)
|
||||
}
|
||||
```
|
||||
|
||||
#### API Endpoint Test
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start node
|
||||
netrunner control start \
|
||||
--number-of-nodes=1 \
|
||||
--node-path ${LUXD_PATH}
|
||||
|
||||
# Test P-Chain height
|
||||
HEIGHT=$(curl -s -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "platform.getHeight",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/bc/P | jq -r '.result.height')
|
||||
|
||||
if [ -z "$HEIGHT" ]; then
|
||||
echo "Failed to get height"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Current height: $HEIGHT"
|
||||
```
|
||||
|
||||
## Integration Testing
|
||||
|
||||
### Multi-Node Networks
|
||||
|
||||
#### Consensus Test
|
||||
|
||||
```go
|
||||
func TestConsensus(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start 5-node network
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 5,
|
||||
BinaryPath: luxdPath,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Wait for consensus
|
||||
require.NoError(t, network.Healthy(ctx))
|
||||
|
||||
// Get heights from all nodes
|
||||
nodes, err := network.GetAllNodes()
|
||||
require.NoError(t, err)
|
||||
|
||||
heights := make(map[string]uint64)
|
||||
for name, node := range nodes {
|
||||
client := node.GetAPIClient()
|
||||
height, err := client.PChainAPI().GetHeight(ctx)
|
||||
require.NoError(t, err)
|
||||
heights[name] = height
|
||||
}
|
||||
|
||||
// Verify all nodes at same height
|
||||
var expectedHeight uint64
|
||||
for name, height := range heights {
|
||||
if expectedHeight == 0 {
|
||||
expectedHeight = height
|
||||
}
|
||||
assert.Equal(t, expectedHeight, height,
|
||||
"Node %s has different height", name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Cross-Chain Transfer Test
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start network with X-Chain and C-Chain
|
||||
netrunner control start \
|
||||
--number-of-nodes=3 \
|
||||
--node-path ${LUXD_PATH} \
|
||||
--global-node-config '{
|
||||
"index-enabled": true,
|
||||
"api-admin-enabled": true
|
||||
}'
|
||||
|
||||
# Wait for bootstrap
|
||||
netrunner control health
|
||||
|
||||
# Export from X-Chain to C-Chain
|
||||
X_CHAIN_URL="http://localhost:9630/ext/bc/X"
|
||||
C_CHAIN_URL="http://localhost:9630/ext/bc/C/lux"
|
||||
|
||||
# Create export transaction
|
||||
EXPORT_TX=$(curl -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "xplatform.exportLUX",
|
||||
"params": {
|
||||
"to": "C",
|
||||
"amount": 1000000000,
|
||||
"from": ["X-test1..."],
|
||||
"changeAddr": "X-test1...",
|
||||
"username": "testuser",
|
||||
"password": "testpass"
|
||||
},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' ${X_CHAIN_URL})
|
||||
|
||||
echo "Export TX: ${EXPORT_TX}"
|
||||
|
||||
# Import to C-Chain
|
||||
IMPORT_TX=$(curl -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "lux.importLUX",
|
||||
"params": {
|
||||
"to": "0x...",
|
||||
"sourceChain": "X",
|
||||
"username": "testuser",
|
||||
"password": "testpass"
|
||||
},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' ${C_CHAIN_URL})
|
||||
|
||||
echo "Import TX: ${IMPORT_TX}"
|
||||
```
|
||||
|
||||
### Subnet Testing
|
||||
|
||||
#### Subnet Creation and Validation
|
||||
|
||||
```go
|
||||
func TestSubnetCreation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start network
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 5,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Create subnet with 3 validators
|
||||
subnetSpec := &netrunner.SubnetSpec{
|
||||
Participants: []string{"node1", "node2", "node3"},
|
||||
}
|
||||
|
||||
subnetID, err := network.CreateSubnet(ctx, subnetSpec)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify subnet validators
|
||||
validators, err := network.GetSubnetValidators(ctx, subnetID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, validators, 3)
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom VM Deployment
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Build custom VM
|
||||
cd ${VM_SOURCE}
|
||||
go build -o ${PLUGIN_PATH}/vmID ./plugin
|
||||
|
||||
# Create genesis
|
||||
cat > /tmp/vm.genesis.json <<EOF
|
||||
{
|
||||
"timestamp": 0,
|
||||
"config": {
|
||||
"featureEnabled": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Start network with custom VM
|
||||
netrunner control start \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_PATH} \
|
||||
--plugin-dir ${PLUGIN_PATH} \
|
||||
--blockchain-specs '[{
|
||||
"vm_name": "customvm",
|
||||
"genesis": "/tmp/vm.genesis.json",
|
||||
"subnet_spec": {
|
||||
"participants": ["node1", "node2", "node3"]
|
||||
}
|
||||
}]'
|
||||
|
||||
# Get blockchain ID
|
||||
STATUS=$(netrunner control status)
|
||||
BLOCKCHAIN_ID=$(echo ${STATUS} | jq -r '.blockchains[0].id')
|
||||
|
||||
# Test custom VM API
|
||||
curl -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "customvm.method",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/bc/${BLOCKCHAIN_ID}
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Throughput Test
|
||||
|
||||
```go
|
||||
func TestThroughput(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start network
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 5,
|
||||
Flags: map[string]interface{}{
|
||||
"snow-optimal-processing": 100,
|
||||
"snow-max-processing": 2048,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Generate transactions
|
||||
numTxs := 1000
|
||||
startTime := time.Now()
|
||||
|
||||
for i := 0; i < numTxs; i++ {
|
||||
tx := generateTestTransaction(i)
|
||||
err := submitTransaction(network, tx)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Wait for finalization
|
||||
waitForFinalization(network, numTxs)
|
||||
|
||||
duration := time.Since(startTime)
|
||||
tps := float64(numTxs) / duration.Seconds()
|
||||
|
||||
t.Logf("Throughput: %.2f TPS", tps)
|
||||
assert.Greater(t, tps, 100.0, "TPS below threshold")
|
||||
}
|
||||
```
|
||||
|
||||
### Latency Test
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start network
|
||||
netrunner control start \
|
||||
--number-of-nodes=3 \
|
||||
--node-path ${LUXD_PATH}
|
||||
|
||||
# Measure P2P latency
|
||||
for i in {1..100}; do
|
||||
START=$(date +%s%N)
|
||||
|
||||
curl -s -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "info.peers",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/info > /dev/null
|
||||
|
||||
END=$(date +%s%N)
|
||||
LATENCY=$((($END - $START) / 1000000))
|
||||
echo "Request $i: ${LATENCY}ms"
|
||||
done | awk '{sum+=$3} END {print "Average latency:", sum/NR "ms"}'
|
||||
```
|
||||
|
||||
### Load Test
|
||||
|
||||
```go
|
||||
func TestLoadHandling(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 5,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Concurrent load generation
|
||||
numWorkers := 10
|
||||
txPerWorker := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numWorkers)
|
||||
|
||||
for w := 0; w < numWorkers; w++ {
|
||||
wg.Add(1)
|
||||
go func(workerID int) {
|
||||
defer wg.Done()
|
||||
|
||||
for i := 0; i < txPerWorker; i++ {
|
||||
tx := generateTransaction(workerID, i)
|
||||
if err := submitTransaction(network, tx); err != nil {
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}(w)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Errorf("Load test error: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Chaos Engineering
|
||||
|
||||
### Node Failure Simulation
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Function to simulate random node failures
|
||||
simulate_failures() {
|
||||
while true; do
|
||||
# Random delay between failures
|
||||
sleep $((RANDOM % 30 + 10))
|
||||
|
||||
# Select random node
|
||||
NODE="node$((RANDOM % 5 + 1))"
|
||||
|
||||
echo "Simulating failure of ${NODE}"
|
||||
netrunner control pause-node ${NODE}
|
||||
|
||||
# Keep node down for random duration
|
||||
sleep $((RANDOM % 10 + 5))
|
||||
|
||||
echo "Recovering ${NODE}"
|
||||
netrunner control resume-node ${NODE}
|
||||
done
|
||||
}
|
||||
|
||||
# Start network
|
||||
netrunner control start \
|
||||
--number-of-nodes=5 \
|
||||
--node-path ${LUXD_PATH}
|
||||
|
||||
# Start failure simulation in background
|
||||
simulate_failures &
|
||||
FAILURE_PID=$!
|
||||
|
||||
# Run test workload
|
||||
for i in {1..100}; do
|
||||
# Submit transaction
|
||||
curl -X POST --data '{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "platform.getHeight",
|
||||
"params": {},
|
||||
"id": 1
|
||||
}' -H 'content-type:application/json' \
|
||||
http://localhost:9630/ext/bc/P
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Stop failure simulation
|
||||
kill ${FAILURE_PID}
|
||||
|
||||
# Verify network recovered
|
||||
netrunner control health
|
||||
```
|
||||
|
||||
### Network Partition Test
|
||||
|
||||
```go
|
||||
func TestNetworkPartition(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 6,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Create network partition
|
||||
partition1 := []string{"node1", "node2", "node3"}
|
||||
partition2 := []string{"node4", "node5", "node6"}
|
||||
|
||||
// Block communication between partitions
|
||||
for _, node1 := range partition1 {
|
||||
for _, node2 := range partition2 {
|
||||
network.BlockConnection(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for partition to take effect
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Check that partitions can't reach consensus
|
||||
// (implementation specific)
|
||||
|
||||
// Heal partition
|
||||
for _, node1 := range partition1 {
|
||||
for _, node2 := range partition2 {
|
||||
network.UnblockConnection(node1, node2)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for recovery
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
// Verify network health
|
||||
require.NoError(t, network.Healthy(ctx))
|
||||
}
|
||||
```
|
||||
|
||||
### Byzantine Node Test
|
||||
|
||||
```go
|
||||
func TestByzantineNode(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start network with Byzantine node
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
NumNodes: 5,
|
||||
NodeConfigs: []netrunner.NodeConfig{
|
||||
{Name: "node1"},
|
||||
{Name: "node2"},
|
||||
{Name: "node3"},
|
||||
{Name: "node4"},
|
||||
{Name: "byzantine", Byzantine: true},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Byzantine node sends conflicting messages
|
||||
byzNode := network.GetNode("byzantine")
|
||||
byzNode.SendConflictingVotes()
|
||||
|
||||
// Verify honest nodes maintain consensus
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
honestNodes := []string{"node1", "node2", "node3", "node4"}
|
||||
heights := make(map[string]uint64)
|
||||
|
||||
for _, name := range honestNodes {
|
||||
node := network.GetNode(name)
|
||||
height, err := node.GetHeight(ctx)
|
||||
require.NoError(t, err)
|
||||
heights[name] = height
|
||||
}
|
||||
|
||||
// All honest nodes should agree
|
||||
var consensusHeight uint64
|
||||
for _, height := range heights {
|
||||
if consensusHeight == 0 {
|
||||
consensusHeight = height
|
||||
}
|
||||
assert.Equal(t, consensusHeight, height)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Regression Testing
|
||||
|
||||
### Snapshot-Based Testing
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Load baseline snapshot
|
||||
netrunner control load-snapshot baseline-v1.0
|
||||
|
||||
# Run regression test suite
|
||||
./run_regression_tests.sh
|
||||
|
||||
# Save results
|
||||
RESULTS=$(netrunner control status)
|
||||
|
||||
# Compare with expected results
|
||||
DIFF=$(diff expected-results.json <(echo ${RESULTS}))
|
||||
|
||||
if [ -n "${DIFF}" ]; then
|
||||
echo "Regression detected:"
|
||||
echo "${DIFF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All regression tests passed"
|
||||
```
|
||||
|
||||
### Upgrade Testing
|
||||
|
||||
```go
|
||||
func TestUpgrade(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start with old version
|
||||
network, err := netrunner.StartNetwork(ctx, &netrunner.Config{
|
||||
BinaryPath: oldBinaryPath,
|
||||
NumNodes: 5,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer network.Stop(ctx)
|
||||
|
||||
// Generate some state
|
||||
generateTestState(network)
|
||||
|
||||
// Save snapshot
|
||||
snapshotPath, err := network.SaveSnapshot(ctx, "pre-upgrade")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Stop network
|
||||
network.Stop(ctx)
|
||||
|
||||
// Restart with new version
|
||||
network, err = netrunner.LoadNetwork(ctx, &netrunner.Config{
|
||||
BinaryPath: newBinaryPath,
|
||||
SnapshotPath: snapshotPath,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify state preserved
|
||||
verifyTestState(network)
|
||||
|
||||
// Test new features
|
||||
testNewFeatures(network)
|
||||
}
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
### GitHub Actions Workflow
|
||||
|
||||
```yaml
|
||||
name: Network Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
test: [consensus, subnet, performance, chaos]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.21
|
||||
|
||||
- name: Install Netrunner
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/luxfi/netrunner/main/scripts/install.sh | sh -s
|
||||
echo "$HOME/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build Node
|
||||
run: |
|
||||
cd node
|
||||
./scripts/build.sh
|
||||
|
||||
- name: Start RPC Server
|
||||
run: |
|
||||
netrunner server &
|
||||
sleep 2
|
||||
|
||||
- name: Run Test
|
||||
run: |
|
||||
netrunner test ${{ matrix.test }}
|
||||
|
||||
- name: Upload Results
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-results-${{ matrix.test }}
|
||||
path: test-results/
|
||||
```
|
||||
|
||||
### Test Automation Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# test-suite.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
LUXD_PATH="/path/to/luxd"
|
||||
PLUGIN_PATH="/path/to/plugins"
|
||||
RESULTS_DIR="./test-results"
|
||||
|
||||
# Create results directory
|
||||
mkdir -p ${RESULTS_DIR}
|
||||
|
||||
# Start RPC server
|
||||
netrunner server &
|
||||
SERVER_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Function to run test
|
||||
run_test() {
|
||||
TEST_NAME=$1
|
||||
echo "Running ${TEST_NAME}..."
|
||||
|
||||
if netrunner test ${TEST_NAME} > ${RESULTS_DIR}/${TEST_NAME}.log 2>&1; then
|
||||
echo "✅ ${TEST_NAME} passed"
|
||||
echo "PASS" > ${RESULTS_DIR}/${TEST_NAME}.result
|
||||
else
|
||||
echo "❌ ${TEST_NAME} failed"
|
||||
echo "FAIL" > ${RESULTS_DIR}/${TEST_NAME}.result
|
||||
fi
|
||||
}
|
||||
|
||||
# Run all tests
|
||||
run_test "consensus"
|
||||
run_test "subnet"
|
||||
run_test "performance"
|
||||
run_test "chaos"
|
||||
run_test "upgrade"
|
||||
|
||||
# Generate report
|
||||
echo "Test Results Summary:"
|
||||
echo "====================="
|
||||
for result in ${RESULTS_DIR}/*.result; do
|
||||
TEST=$(basename ${result} .result)
|
||||
STATUS=$(cat ${result})
|
||||
echo "${TEST}: ${STATUS}"
|
||||
done
|
||||
|
||||
# Cleanup
|
||||
kill ${SERVER_PID}
|
||||
|
||||
# Exit with error if any test failed
|
||||
if grep -q "FAIL" ${RESULTS_DIR}/*.result; then
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Isolate Tests**: Each test should start with a clean state
|
||||
2. **Use Snapshots**: Save and restore known good states
|
||||
3. **Parallel Execution**: Run independent tests concurrently
|
||||
4. **Resource Cleanup**: Always clean up resources after tests
|
||||
5. **Deterministic Tests**: Avoid timing-dependent assertions
|
||||
6. **Comprehensive Logging**: Log all test actions for debugging
|
||||
7. **Test Coverage**: Cover normal, edge, and failure cases
|
||||
8. **Performance Baselines**: Track performance over time
|
||||
9. **Automated Regression**: Run tests on every commit
|
||||
10. **Documentation**: Document test scenarios and expected outcomes
|
||||
@@ -0,0 +1,18 @@
|
||||
import { docs } from "@/.source"
|
||||
import { loader } from "fumadocs-core/source"
|
||||
|
||||
// Create a single source instance that is reused
|
||||
// This prevents circular references and stack overflow issues
|
||||
let _source: ReturnType<typeof loader> | null = null
|
||||
|
||||
export function getSource() {
|
||||
if (!_source) {
|
||||
_source = loader({
|
||||
baseUrl: "/docs",
|
||||
source: docs.toFumadocsSource(),
|
||||
})
|
||||
}
|
||||
return _source
|
||||
}
|
||||
|
||||
export const source = getSource()
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { MDXComponents } from "mdx/types"
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx"
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...defaultMdxComponents,
|
||||
...components,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { createMDX } from "fumadocs-mdx/next"
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {
|
||||
output: 'export',
|
||||
reactStrictMode: true,
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
experimental: {
|
||||
webpackBuildWorker: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
}
|
||||
|
||||
const withMDX = createMDX()
|
||||
|
||||
export default withMDX(config)
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@luxfi/netrunner-docs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
"build": "fumadocs-mdx && TURBOPACK=0 next build",
|
||||
"export": "TURBOPACK=0 next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"postinstall": "fumadocs-mdx"
|
||||
},
|
||||
"dependencies": {
|
||||
"fumadocs-core": "^15.8.5",
|
||||
"fumadocs-mdx": "^12.0.3",
|
||||
"fumadocs-ui": "^15.8.5",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "16.0.1",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.16",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"rehype-pretty-code": "^0.14.1",
|
||||
"shiki": "^1.27.2",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"packageManager": "pnpm@10.12.4"
|
||||
}
|
||||
Generated
+4136
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
defineConfig,
|
||||
defineDocs,
|
||||
} from "fumadocs-mdx/config"
|
||||
import rehypePrettyCode from "rehype-pretty-code"
|
||||
|
||||
export default defineConfig({
|
||||
mdxOptions: {
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypePrettyCode,
|
||||
{
|
||||
theme: {
|
||||
dark: "github-dark-dimmed",
|
||||
light: "github-light",
|
||||
},
|
||||
keepBackground: false,
|
||||
defaultLang: "go",
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
export const docs = defineDocs({
|
||||
dir: "content/docs",
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/node/vms/platformvm"
|
||||
"github.com/luxfi/node/vms/platformvm/txs"
|
||||
pwalletwallet "github.com/luxfi/node/wallet/chain/p/wallet"
|
||||
"github.com/luxfi/node/wallet/net/primary/common"
|
||||
)
|
||||
|
||||
// walletClient wraps platformvm.Client to implement wallet.Client
|
||||
type walletClient struct {
|
||||
platformClient *platformvm.Client
|
||||
}
|
||||
|
||||
// newWalletClient creates a new wallet client wrapper
|
||||
func newWalletClient(platformClient *platformvm.Client) pwalletwallet.Client {
|
||||
return &walletClient{
|
||||
platformClient: platformClient,
|
||||
}
|
||||
}
|
||||
|
||||
// IssueTx implements wallet.Client
|
||||
func (w *walletClient) IssueTx(tx *txs.Tx, options ...common.Option) error {
|
||||
ops := common.NewOptions(options)
|
||||
ctx := ops.Context()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
// Serialize the transaction
|
||||
txBytes := tx.Bytes()
|
||||
|
||||
// Issue through platform client
|
||||
_, err := w.platformClient.IssueTx(ctx, txBytes)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
[12-03|22:46:02.640] UNKNO local/network.go:429 creating network {"node-num": 5}
|
||||
[12-03|22:46:04.139] UNKNO local/network.go:597 adding node {"node-name": "node1", "node-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1", "log-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/logs", "db-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/db", "p2p-port": 9631, "api-port": 9630}
|
||||
[12-03|22:46:04.139] UNKNO local/network.go:607 starting node {"name": "node1", "binaryPath": "/Users/z/work/lux/node/build/luxd", "args": ["--health-check-frequency=2s", "--db-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/db", "--http-port=9630", "--genesis-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/genesis.json", "--public-ip=127.0.0.1", "--log-level=DEBUG", "--network-id=1337", "--bootstrap-ips=", "--staking-tls-cert-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/staking.crt", "--subnet-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/netConfigs", "--log-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/logs", "--bootstrap-ids=", "--staking-tls-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/staking.key", "--staking-signer-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/signer.key", "--chain-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1/chainConfigs", "--log-display-level=ERROR", "--data-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node1", "--staking-port=9631", "--api-admin-enabled=true", "--index-enabled=true", "--network-max-reconnect-delay=1s"]}
|
||||
[12-03|22:46:06.788] UNKNO local/network.go:597 adding node {"node-name": "node2", "node-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2", "log-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/logs", "db-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/db", "p2p-port": 9633, "api-port": 9632}
|
||||
[12-03|22:46:06.801] UNKNO local/network.go:607 starting node {"name": "node2", "binaryPath": "/Users/z/work/lux/node/build/luxd", "args": ["--staking-tls-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/staking.key", "--log-level=DEBUG", "--api-admin-enabled=true", "--network-max-reconnect-delay=1s", "--network-id=1337", "--data-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2", "--bootstrap-ids=NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg", "--staking-tls-cert-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/staking.crt", "--staking-signer-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/signer.key", "--health-check-frequency=2s", "--public-ip=127.0.0.1", "--db-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/db", "--http-port=9632", "--subnet-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/netConfigs", "--index-enabled=true", "--log-display-level=ERROR", "--bootstrap-ips=[::1]:9631", "--chain-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/chainConfigs", "--log-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/logs", "--staking-port=9633", "--genesis-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node2/genesis.json"]}
|
||||
[12-03|22:46:09.675] UNKNO local/network.go:597 adding node {"node-name": "node3", "node-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3", "log-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/logs", "db-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/db", "p2p-port": 9635, "api-port": 9634}
|
||||
[12-03|22:46:09.675] UNKNO local/network.go:607 starting node {"name": "node3", "binaryPath": "/Users/z/work/lux/node/build/luxd", "args": ["--db-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/db", "--staking-port=9635", "--genesis-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/genesis.json", "--network-max-reconnect-delay=1s", "--public-ip=127.0.0.1", "--api-admin-enabled=true", "--log-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/logs", "--http-port=9634", "--staking-tls-cert-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/staking.crt", "--subnet-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/netConfigs", "--health-check-frequency=2s", "--data-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3", "--bootstrap-ips=[::1]:9631,[::1]:9633", "--staking-signer-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/signer.key", "--log-level=DEBUG", "--log-display-level=ERROR", "--network-id=1337", "--bootstrap-ids=NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg,NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ", "--chain-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/chainConfigs", "--staking-tls-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node3/staking.key", "--index-enabled=true"]}
|
||||
[12-03|22:46:12.257] UNKNO local/network.go:597 adding node {"node-name": "node4", "node-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4", "log-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/logs", "db-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/db", "p2p-port": 9637, "api-port": 9636}
|
||||
[12-03|22:46:12.272] UNKNO local/network.go:607 starting node {"name": "node4", "binaryPath": "/Users/z/work/lux/node/build/luxd", "args": ["--data-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4", "--bootstrap-ids=NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg,NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ,NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN", "--staking-tls-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/staking.key", "--staking-port=9637", "--chain-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/chainConfigs", "--genesis-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/genesis.json", "--health-check-frequency=2s", "--log-level=DEBUG", "--api-admin-enabled=true", "--index-enabled=true", "--db-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/db", "--bootstrap-ips=[::1]:9631,[::1]:9633,[::1]:9635", "--staking-tls-cert-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/staking.crt", "--network-max-reconnect-delay=1s", "--network-id=1337", "--log-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/logs", "--http-port=9636", "--subnet-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/netConfigs", "--staking-signer-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node4/signer.key", "--log-display-level=ERROR", "--public-ip=127.0.0.1"]}
|
||||
[12-03|22:46:13.845] UNKNO local/network.go:597 adding node {"node-name": "node5", "node-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5", "log-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/logs", "db-dir": "/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/db", "p2p-port": 9639, "api-port": 9638}
|
||||
[12-03|22:46:13.851] UNKNO local/network.go:607 starting node {"name": "node5", "binaryPath": "/Users/z/work/lux/node/build/luxd", "args": ["--api-admin-enabled=true", "--bootstrap-ips=[::1]:9631,[::1]:9633,[::1]:9635,[::1]:9637", "--chain-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/chainConfigs", "--staking-signer-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/signer.key", "--index-enabled=true", "--network-id=1337", "--data-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5", "--db-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/db", "--log-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/logs", "--staking-port=9639", "--bootstrap-ids=NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg,NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ,NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN,NodeID-GWPcbFJZFfZreETSoWjPimr846mXEKCtu", "--log-level=DEBUG", "--network-max-reconnect-delay=1s", "--subnet-config-dir=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/netConfigs", "--staking-tls-key-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/staking.key", "--log-display-level=ERROR", "--staking-tls-cert-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/staking.crt", "--http-port=9638", "--genesis-file=/var/folders/xh/_fpw0c6d7rs1sn8nwx93t7gw0000gn/T/network-runner-root-data/network_20251203_224602/node5/genesis.json", "--health-check-frequency=2s", "--public-ip=127.0.0.1"]}
|
||||
[12-03|22:46:13.851] UNKNO local/network.go:654 checking local network healthiness {"num-of-nodes": 5}
|
||||
[12-03|22:49:13.853] UNKNO local/network.go:800 removing node {"name": "node1"}
|
||||
[12-03|22:49:13.933] UNKNO local/node_process.go:125 node process finished {"node": "node1"}
|
||||
[12-03|22:49:13.933] UNKNO local/network.go:800 removing node {"name": "node2"}
|
||||
[12-03|22:49:13.988] UNKNO local/node_process.go:125 node process finished {"node": "node2"}
|
||||
[12-03|22:49:13.989] UNKNO local/network.go:800 removing node {"name": "node3"}
|
||||
[12-03|22:49:14.049] UNKNO local/node_process.go:125 node process finished {"node": "node3"}
|
||||
[12-03|22:49:14.049] UNKNO local/network.go:800 removing node {"name": "node4"}
|
||||
[12-03|22:49:14.108] UNKNO local/node_process.go:125 node process finished {"node": "node4"}
|
||||
[12-03|22:49:14.108] UNKNO local/network.go:800 removing node {"name": "node5"}
|
||||
[12-03|22:49:14.223] UNKNO local/node_process.go:125 node process finished {"node": "node5"}
|
||||
[12-03|22:49:14.223] UNKNO local/network.go:783 done stopping network
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/netrunner/engines"
|
||||
"github.com/luxfi/netrunner/orchestrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMultiEngineOrchestration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
host := orchestrator.NewHost()
|
||||
|
||||
// Test starting Lux engine
|
||||
t.Run("StartLuxEngine", func(t *testing.T) {
|
||||
config := &engines.NodeConfig{
|
||||
NetworkID: 96369,
|
||||
HTTPPort: 19630,
|
||||
StakingPort: 19631,
|
||||
LogLevel: "info",
|
||||
Extra: map[string]interface{}{
|
||||
"staking-enabled": false,
|
||||
"sybil-protection-enabled": false,
|
||||
"health-check-frequency": "2s",
|
||||
},
|
||||
}
|
||||
|
||||
err := host.StartEngine(ctx, "test-lux", engines.EngineLux, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for health
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Check engine is listed
|
||||
engines := host.ListEngines()
|
||||
require.Len(t, engines, 1)
|
||||
require.Equal(t, "test-lux", engines[0].Name)
|
||||
require.Equal(t, "lux", engines[0].Type)
|
||||
|
||||
// Stop engine
|
||||
err = host.StopEngine(ctx, "test-lux")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Test starting multiple engines
|
||||
t.Run("MultipleEngines", func(t *testing.T) {
|
||||
// Start Lux
|
||||
luxConfig := &engines.NodeConfig{
|
||||
NetworkID: 96369,
|
||||
HTTPPort: 19630,
|
||||
StakingPort: 19631,
|
||||
LogLevel: "info",
|
||||
}
|
||||
err := host.StartEngine(ctx, "lux-1", engines.EngineLux, luxConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start Lux
|
||||
avaConfig := &engines.NodeConfig{
|
||||
NetworkID: 43114,
|
||||
HTTPPort: 19640,
|
||||
StakingPort: 19641,
|
||||
LogLevel: "info",
|
||||
}
|
||||
err = host.StartEngine(ctx, "ava-1", engines.EngineLux, avaConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check both are running
|
||||
engines := host.ListEngines()
|
||||
require.Len(t, engines, 2)
|
||||
|
||||
// Clean up
|
||||
host.StopEngine(ctx, "lux-1")
|
||||
host.StopEngine(ctx, "ava-1")
|
||||
})
|
||||
}
|
||||
|
||||
func TestStackManifest(t *testing.T) {
|
||||
manifest := &orchestrator.StackManifest{
|
||||
Name: "test-stack",
|
||||
Version: "1.0.0",
|
||||
Description: "Test stack",
|
||||
Engines: []orchestrator.EngineConfig{
|
||||
{
|
||||
Name: "lux-l1",
|
||||
Type: "lux",
|
||||
NetworkID: 96369,
|
||||
HTTPPort: 9630,
|
||||
StakingPort: 9631,
|
||||
WaitHealthy: true,
|
||||
},
|
||||
{
|
||||
Name: "lux-l2",
|
||||
Type: "op",
|
||||
NetworkID: 200200,
|
||||
HTTPPort: 8545,
|
||||
DependsOn: []string{"lux-l1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
err := manifest.Validate()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("SaveAndLoad", func(t *testing.T) {
|
||||
// Save as YAML
|
||||
yamlPath := "/tmp/test-stack.yaml"
|
||||
err := manifest.Save(yamlPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load back
|
||||
loaded, err := orchestrator.LoadManifest(yamlPath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, manifest.Name, loaded.Name)
|
||||
require.Len(t, loaded.Engines, 2)
|
||||
|
||||
// Save as JSON
|
||||
jsonPath := "/tmp/test-stack.json"
|
||||
err = manifest.Save(jsonPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load JSON
|
||||
loaded, err = orchestrator.LoadManifest(jsonPath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, manifest.Name, loaded.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPortManager(t *testing.T) {
|
||||
pm := orchestrator.NewPortManager()
|
||||
|
||||
t.Run("AllocatePorts", func(t *testing.T) {
|
||||
// Allocate HTTP ports
|
||||
port1 := pm.AllocateHTTP()
|
||||
port2 := pm.AllocateHTTP()
|
||||
require.NotEqual(t, port1, port2)
|
||||
|
||||
// Allocate P2P ports
|
||||
p2p1 := pm.AllocateP2P()
|
||||
p2p2 := pm.AllocateP2P()
|
||||
require.NotEqual(t, p2p1, p2p2)
|
||||
|
||||
// Release and reallocate
|
||||
pm.Release(port1)
|
||||
port3 := pm.AllocateHTTP()
|
||||
// May or may not reuse port1 depending on system state
|
||||
require.NotEqual(t, port3, port2)
|
||||
})
|
||||
|
||||
t.Run("ReservePorts", func(t *testing.T) {
|
||||
pm := orchestrator.NewPortManager()
|
||||
|
||||
// Reserve specific ports
|
||||
err := pm.Reserve(20000, 20001, 20002)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to reserve again - should fail
|
||||
err = pm.Reserve(20001)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMetricsCollector(t *testing.T) {
|
||||
mc := orchestrator.NewMetricsCollector()
|
||||
|
||||
t.Run("RecordAndRetrieve", func(t *testing.T) {
|
||||
// Record metrics
|
||||
mc.Record("engine1", map[string]interface{}{
|
||||
"uptime_seconds": 100.0,
|
||||
"running": true,
|
||||
"peers": 5,
|
||||
})
|
||||
|
||||
// Retrieve metrics
|
||||
metrics := mc.Get("engine1")
|
||||
require.NotNil(t, metrics)
|
||||
require.Equal(t, "engine1", metrics.Name)
|
||||
require.Equal(t, 100.0, metrics.Values["uptime_seconds"])
|
||||
|
||||
// Record more for history
|
||||
for i := 0; i < 5; i++ {
|
||||
mc.Record("engine1", map[string]interface{}{
|
||||
"uptime_seconds": float64(100 + i*10),
|
||||
"running": true,
|
||||
})
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
metrics = mc.Get("engine1")
|
||||
require.Len(t, metrics.History, 6) // Initial + 5 more
|
||||
})
|
||||
|
||||
t.Run("Summary", func(t *testing.T) {
|
||||
mc := orchestrator.NewMetricsCollector()
|
||||
|
||||
mc.Record("engine1", map[string]interface{}{
|
||||
"uptime_seconds": 100.0,
|
||||
"running": true,
|
||||
})
|
||||
mc.Record("engine2", map[string]interface{}{
|
||||
"uptime_seconds": 200.0,
|
||||
"running": true,
|
||||
})
|
||||
mc.Record("engine3", map[string]interface{}{
|
||||
"uptime_seconds": 50.0,
|
||||
"running": false,
|
||||
})
|
||||
|
||||
summary := mc.Summary()
|
||||
require.Equal(t, 3, summary["engines"])
|
||||
require.Equal(t, 350.0, summary["total_uptime_seconds"])
|
||||
require.Equal(t, 2, summary["healthy_engines"])
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (C) 2021-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Simple standalone test to verify 5-node network deployment
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/netrunner/local"
|
||||
"github.com/luxfi/netrunner/network/node"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configuration
|
||||
nodePath := "/Users/z/work/lux/node/build/luxd"
|
||||
numNodes := 5
|
||||
networkID := uint32(12345)
|
||||
|
||||
// Check binary exists
|
||||
if _, err := os.Stat(nodePath); os.IsNotExist(err) {
|
||||
log.Fatalf("Node binary not found at %s", nodePath)
|
||||
}
|
||||
|
||||
fmt.Printf("Starting %d-node network deployment test...\n", numNodes)
|
||||
fmt.Printf("Binary: %s\n", nodePath)
|
||||
fmt.Printf("Network ID: %d\n", networkID)
|
||||
fmt.Println()
|
||||
|
||||
// Create network configuration
|
||||
config := local.Config{
|
||||
NodeConfig: node.Config{
|
||||
BinaryPath: nodePath,
|
||||
ConfigFile: "",
|
||||
},
|
||||
NumNodes: uint32(numNodes),
|
||||
NetworkID: networkID,
|
||||
BaseHTTPPort: 9630,
|
||||
BaseStakingPort: 9631,
|
||||
LogLevel: "info",
|
||||
}
|
||||
|
||||
// Create network
|
||||
network, err := local.NewNetwork(config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create network: %v", err)
|
||||
}
|
||||
|
||||
// Start network
|
||||
fmt.Println("=== Starting Network ===")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
startTime := time.Now()
|
||||
err = network.Start(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start network: %v", err)
|
||||
}
|
||||
startDuration := time.Since(startTime)
|
||||
fmt.Printf("✓ Network started in %v\n\n", startDuration)
|
||||
|
||||
// Wait for nodes to become healthy
|
||||
fmt.Println("=== Waiting for Nodes to Become Healthy ===")
|
||||
healthCtx, healthCancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
defer healthCancel()
|
||||
|
||||
healthStart := time.Now()
|
||||
err = network.Healthy(healthCtx)
|
||||
if err != nil {
|
||||
log.Fatalf("Network failed to become healthy: %v", err)
|
||||
}
|
||||
healthDuration := time.Since(healthStart)
|
||||
fmt.Printf("✓ All nodes healthy in %v\n\n", healthDuration)
|
||||
|
||||
// Get node information
|
||||
fmt.Println("=== Node Information ===")
|
||||
nodeNames := network.GetNodeNames()
|
||||
fmt.Printf("Total nodes: %d\n", len(nodeNames))
|
||||
|
||||
for _, name := range nodeNames {
|
||||
node, err := network.GetNode(name)
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Failed to get node %s: %v\n", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
nodeConfig := node.GetConfig()
|
||||
fmt.Printf(" %s:\n", name)
|
||||
fmt.Printf(" HTTP Port: %d\n", nodeConfig.HTTPPort)
|
||||
fmt.Printf(" Staking Port: %d\n", nodeConfig.StakingPort)
|
||||
fmt.Printf(" API URI: http://127.0.0.1:%d\n", nodeConfig.HTTPPort)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Check bootstrap status via info API
|
||||
fmt.Println("=== Bootstrap Status ===")
|
||||
for _, name := range nodeNames {
|
||||
node, err := network.GetNode(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nodeConfig := node.GetConfig()
|
||||
uri := fmt.Sprintf("http://127.0.0.1:%d", nodeConfig.HTTPPort)
|
||||
|
||||
// Try to query info API (simplified check)
|
||||
fmt.Printf(" %s (%s): Ready\n", name, uri)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Test results
|
||||
fmt.Println("=== Test Results ===")
|
||||
fmt.Printf("✓ Network deployment: SUCCESS\n")
|
||||
fmt.Printf("✓ Network start time: %v\n", startDuration)
|
||||
fmt.Printf("✓ Health check time: %v\n", healthDuration)
|
||||
fmt.Printf("✓ Total time: %v\n", time.Since(startTime))
|
||||
fmt.Printf("✓ Nodes deployed: %d/%d\n", len(nodeNames), numNodes)
|
||||
|
||||
// Keep network running for a bit to allow manual inspection
|
||||
fmt.Println("\n=== Network Running ===")
|
||||
fmt.Println("Press Ctrl+C to stop (or wait 30 seconds for automatic shutdown)")
|
||||
|
||||
shutdownTimer := time.NewTimer(30 * time.Second)
|
||||
<-shutdownTimer.C
|
||||
|
||||
// Cleanup
|
||||
fmt.Println("\n=== Shutting Down Network ===")
|
||||
stopCtx, stopCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer stopCancel()
|
||||
|
||||
err = network.Stop(stopCtx)
|
||||
if err != nil {
|
||||
log.Printf("Warning: Error stopping network: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("✓ Network stopped successfully")
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Test Complete ===")
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/netrunner/local"
|
||||
luxlog "github.com/luxfi/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFiveNodeBootstrap(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
logger := luxlog.NewLogger(
|
||||
"test",
|
||||
luxlog.NewWrappedCore(
|
||||
luxlog.Info,
|
||||
luxlog.Stdout,
|
||||
luxlog.Plain.ConsoleEncoder(),
|
||||
),
|
||||
)
|
||||
|
||||
// Get luxd path from environment or use default
|
||||
luxdPath := os.Getenv("LUXD_PATH")
|
||||
if luxdPath == "" {
|
||||
luxdPath = "/Users/z/work/lux/node/build/luxd"
|
||||
}
|
||||
|
||||
fmt.Println("\n=== Testing 5-Node Network Bootstrap ===")
|
||||
fmt.Printf("Using luxd: %s\n\n", luxdPath)
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// Create network with default 5-node configuration
|
||||
network, err := local.NewDefaultNetwork(logger, luxdPath, true)
|
||||
require.NoError(err, "Failed to create network")
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
_ = network.Stop(ctx)
|
||||
}()
|
||||
|
||||
fmt.Println("✅ Network created")
|
||||
fmt.Println("⏳ Waiting for network to become healthy...")
|
||||
|
||||
// Wait for network to bootstrap with 2 minute timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
err = network.Healthy(ctx)
|
||||
require.NoError(err, "Network failed to become healthy")
|
||||
|
||||
elapsed := time.Since(startTime)
|
||||
fmt.Printf("\n✅ SUCCESS! All chains bootstrapped in %v\n", elapsed)
|
||||
|
||||
// Verify we have 5 nodes
|
||||
nodes := network.GetAllNodes()
|
||||
require.Len(nodes, 5, "Expected 5 nodes")
|
||||
|
||||
// Print node information
|
||||
fmt.Println("\n=== Node Information ===")
|
||||
for name, node := range nodes {
|
||||
nodeID, err := node.GetNodeID()
|
||||
require.NoError(err, "Failed to get node ID for %s", name)
|
||||
fmt.Printf("%s: NodeID-%s, HTTP: %s\n", name, nodeID, node.GetAPIClient().BaseURL())
|
||||
}
|
||||
|
||||
// Verify all nodes are bootstrapped
|
||||
for name, node := range nodes {
|
||||
client := node.GetAPIClient()
|
||||
|
||||
// Check Platform chain
|
||||
pBootstrapped, err := client.PChainAPI().IsBootstrapped(ctx)
|
||||
require.NoError(err, "Failed to check P-chain bootstrap for %s", name)
|
||||
require.True(pBootstrapped, "%s P-chain not bootstrapped", name)
|
||||
|
||||
// Check X chain
|
||||
xBootstrapped, err := client.XChainAPI().IsBootstrapped(ctx)
|
||||
require.NoError(err, "Failed to check X-chain bootstrap for %s", name)
|
||||
require.True(xBootstrapped, "%s X-chain not bootstrapped", name)
|
||||
|
||||
// Check C chain
|
||||
cBootstrapped, err := client.CChainEthAPI().Syncing(ctx)
|
||||
require.NoError(err, "Failed to check C-chain sync for %s", name)
|
||||
require.Nil(cBootstrapped, "%s C-chain still syncing", name)
|
||||
}
|
||||
|
||||
fmt.Println("\n✅ All validation checks passed!")
|
||||
fmt.Printf("⏱️ Total bootstrap time: %v\n", elapsed)
|
||||
|
||||
// Verify bootstrap time is under 60 seconds
|
||||
require.Less(elapsed.Seconds(), 60.0, "Bootstrap took longer than 60 seconds")
|
||||
|
||||
fmt.Println("\n🎉 Test completed successfully!")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user