Skip to content

Development

Guide for contributing to txpark development.

Development Setup

Prerequisites

  • Go 1.22+: Install Go
  • kubectl 1.25+: Install kubectl
  • Docker (optional): For building container images
  • Make: For running build tasks
  • Git: For version control

Clone Repository

git clone https://gitlab.com/tezos-infra/evm/txpark.git
cd txpark

Install Dependencies

# Download Go modules
go mod download

# Verify dependencies
go mod verify

Build from Source

# Build binary
go build -o bin/txpark cmd/txpark/main.go

# Or use make
make build

# Install to $GOPATH/bin
go install cmd/txpark/main.go

Run Locally

# Run without installing
go run cmd/txpark/main.go --help

# Example: list testnets
go run cmd/txpark/main.go list

# With debugging
go run cmd/txpark/main.go --debug status my-testnet

Project Structure

txpark/
├── cmd/
│   └── txpark/
│       └── main.go           # Entry point
├── internal/
│   ├── cmd/                  # Command implementations
│   │   ├── root.go           # Root command & global flags
│   │   ├── deploy.go         # Deploy command
│   │   ├── list.go           # List command
│   │   ├── status.go         # Status command
│   │   ├── logs.go           # Logs command
│   │   ├── shell.go          # Shell command
│   │   ├── upgrade.go        # Upgrade command
│   │   ├── destroy.go        # Destroy command
│   │   └── version.go        # Version command
│   ├── k8s/                  # Kubernetes client
│   │   └── client.go         # K8s operations
│   ├── helm/                 # Helm client
│   │   └── helm.go           # Helm operations
│   ├── gitlab/               # GitLab client
│   │   └── client.go         # Pipeline triggering
│   ├── errors/               # Error handling
│   │   └── errors.go         # Custom error types
│   ├── logger/               # Logging
│   │   └── logger.go         # Structured logging
│   ├── profiles/             # Resource profiles
│   │   └── profiles.go       # Profile definitions
│   └── testnet/              # Testnet models
│       └── info.go           # Testnet metadata
├── helm/
│   └── txpark-testnet/       # Helm chart
│       ├── Chart.yaml
│       ├── values.yaml
│       └── templates/
├── scripts/                  # Helper scripts
│   ├── setup-sequencer.sh    # Generate sequencer config
│   └── ...
├── docs/                     # MkDocs documentation
│   ├── index.md
│   ├── getting-started.md
│   └── ...
├── mkdocs.yml                # MkDocs configuration
├── go.mod                    # Go module definition
├── go.sum                    # Go module checksums
├── Makefile                  # Build automation
└── README.md                 # Project overview

Key Directories

  • cmd/: Application entry points
  • internal/: Private application code (not importable by other projects)
  • internal/cmd/: CLI command implementations using Cobra
  • internal/k8s/: Kubernetes client wrapper
  • internal/helm/: Helm client wrapper
  • helm/: Helm charts for testnet deployment

Development Workflow

1. Create a Feature Branch

# Create branch from main
git checkout main
git pull origin main
git checkout -b feature/my-feature

2. Make Changes

Edit code, add tests, update documentation.

3. Test Locally

# Build
make build

# Test manually
./bin/txpark list
./bin/txpark status my-testnet

# Run tests
make test

# Run linters
make lint

4. Commit Changes

git add .
git commit -m "Add feature: description"

5. Push and Create MR

git push origin feature/my-feature

# Create merge request on GitLab
# https://gitlab.com/tezos-infra/evm/txpark/-/merge_requests/new

Adding a New Command

Example: Adding a restart command.

1. Create Command File

Create internal/cmd/restart.go:

package cmd

import (
    "context"
    "fmt"

    "github.com/spf13/cobra"
    "gitlab.com/tezos-infra/evm/txpark/internal/errors"
    "gitlab.com/tezos-infra/evm/txpark/internal/logger"
)

var (
    // Restart flags
    restartComponent string
)

// restartCmd represents the restart command
var restartCmd = &cobra.Command{
    Use:   "restart <testnet-name>",
    Short: "Restart testnet components",
    Long: `Restart one or more testnet components.

Examples:
  # Restart EVM sequencer
  txpark restart my-testnet --component evm-sequencer

  # Restart all components
  txpark restart my-testnet --all`,
    Args: cobra.ExactArgs(1),
    RunE: runRestart,
}

func init() {
    rootCmd.AddCommand(restartCmd)

    restartCmd.Flags().StringVarP(&restartComponent, "component", "c", "", "component to restart")
}

func runRestart(cmd *cobra.Command, args []string) error {
    log := GetLogger()
    ctx := context.Background()

    testnetName := args[0]

    log.Info("Restarting testnet component",
        logger.String("testnet", testnetName),
        logger.String("component", restartComponent))

    // Implementation here...
    // 1. Get K8s client
    // 2. Find pods by component
    // 3. Delete pods (triggers restart)
    // 4. Wait for new pods to be ready

    return nil
}

2. Update Documentation

Add command to docs/commands.md.

3. Test

go run cmd/txpark/main.go restart --help

Testing

Unit Tests

# Run all tests
go test ./...

# Run with coverage
go test -cover ./...

# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

Integration Tests

# Test against real cluster (requires kubectl access)
./scripts/integration-test.sh

Manual Testing

# Build
make build

# Deploy test testnet
./bin/txpark deploy test-dev --lifetime 1h

# Verify
./bin/txpark status test-dev
./bin/txpark logs test-dev evm-sequencer

# Cleanup
./bin/txpark destroy test-dev

Code Style

Go Guidelines

Follow Effective Go and these project-specific conventions:

Error handling:

// ✅ Use custom error types with suggestions
if err != nil {
    return errors.NewNotFoundError("testnet", name).
        WithSuggestion("run 'txpark list' to see available testnets")
}

// ❌ Don't use bare errors
if err != nil {
    return fmt.Errorf("testnet not found: %w", err)
}

Logging:

// ✅ Use structured logging
log.Info("Deploying testnet",
    logger.String("name", testnetName),
    logger.Int("lifetime", lifetime))

// ❌ Don't use fmt.Printf for logs
fmt.Printf("Deploying %s with lifetime %d\n", testnetName, lifetime)

Command structure:

// ✅ Keep RunE functions focused
func runDeploy(cmd *cobra.Command, args []string) error {
    log := GetLogger()
    ctx := context.Background()

    // 1. Validate inputs
    // 2. Perform operation
    // 3. Display results

    return nil
}

// ❌ Don't mix validation, execution, and display

Formatting

# Format code
go fmt ./...

# Or use goimports
goimports -w .

Linting

# Install golangci-lint
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Run linters
golangci-lint run

# Fix auto-fixable issues
golangci-lint run --fix

Documentation

Code Documentation

Add godoc comments to all exported functions:

// Client provides Kubernetes operations for testnet management.
// It wraps the kubernetes.Clientset with helper methods specific
// to txpark functionality.
type Client struct {
    clientset kubernetes.Interface
    config    *Config
    log       *logger.Logger
}

// GetTestnetNamespace converts a testnet name to its namespace name.
// It prepends "testnet-" to the provided name following the naming convention.
func (c *Client) GetTestnetNamespace(testnetName string) string {
    return fmt.Sprintf("testnet-%s", testnetName)
}

MkDocs Documentation

When adding features, update relevant documentation:

  • Getting Started: For installation or first-use changes
  • Commands: For new commands or flag changes
  • Workflows: For new usage patterns
  • Best Practices: For recommendations
  • Development: For contributor-facing changes
# Build docs locally
pip install mkdocs-material
mkdocs serve

# View at http://localhost:8000

Building Documentation

Setup Python Environment

It's recommended to use a Python virtual environment to avoid conflicts with system packages:

# Create virtual environment
python -m venv .venv

# Activate virtual environment
source .venv/bin/activate  # On Linux/macOS
# or
.venv\Scripts\activate     # On Windows

# Install dependencies
pip install mkdocs-material

Build and Preview

# Build documentation
make doc

# Serve locally for preview (with live reload)
make doc-serve
# Opens at http://127.0.0.1:8000

# When done, deactivate virtual environment
deactivate

Note: The .venv/ directory is already in .gitignore and won't be committed.


Deployment

Infrastructure Setup

The documentation is hosted as a pod in the Kubernetes cluster. No additional infrastructure setup is required beyond the cluster itself.

Deploying Documentation

Deploy content to the cluster:

# Activate venv if not already
source .venv/bin/activate

# Build, push, and deploy in one command
make doc-deploy

This will: 1. Build the MkDocs site 2. Create a Docker image with nginx, docs, and install script 3. Push to the container registry 4. Deploy to the cluster using Helm

Manual Steps

If you prefer to run steps individually:

# Build documentation
make doc

# Build Docker image
make doc-build-image

# Push to registry
make doc-push

# Deploy with Helm
helm upgrade --install txpark-docs ./helm/txpark-docs \
  --namespace txpark-system --create-namespace

Accessing Content

After deployment, content is accessible via HTTPS:

  • Documentation: https://doc.txpark.nomadic-labs.com/
  • Install script: https://doc.txpark.nomadic-labs.com/install.sh

Building Releases

Version Tagging

# Tag a release
git tag -a v1.2.0 -m "Release v1.2.0"
git push origin v1.2.0

Build Binaries

# Build for all platforms
make build-all

# Build for specific platform
GOOS=linux GOARCH=amd64 go build -o bin/txpark-linux-amd64 cmd/txpark/main.go
GOOS=darwin GOARCH=arm64 go build -o bin/txpark-darwin-arm64 cmd/txpark/main.go

Release Process

  1. Update version in cmd/txpark/main.go
  2. Update CHANGELOG.md
  3. Create and push version tag
  4. GitLab CI builds and uploads binaries
  5. Create release notes on GitLab

Contributing Guidelines

Before Submitting

  • [ ] Code follows project style guidelines
  • [ ] Tests pass: go test ./...
  • [ ] Linters pass: golangci-lint run
  • [ ] Documentation updated
  • [ ] Commit messages are descriptive
  • [ ] Branch is up to date with main

Merge Request Template

## Description

Brief description of the changes.

## Type of Change

- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Testing

Describe testing performed:
- [ ] Unit tests added/updated
- [ ] Manual testing completed
- [ ] Integration tests passed

## Checklist

- [ ] Code follows project style
- [ ] Documentation updated
- [ ] Tests added/updated
- [ ] No breaking changes (or documented)

## Related Issues

Closes #123

Code Review Process

  1. Create merge request
  2. Automated checks run (tests, linters)
  3. Team reviews code
  4. Address feedback
  5. Maintainer approves and merges

Debugging

Enable Debug Logging

# Run with debug output
go run cmd/txpark/main.go --debug status my-testnet

# Or with built binary
./bin/txpark --debug logs my-testnet evm-sequencer

Using Delve Debugger

# Install delve
go install github.com/go-delve/delve/cmd/dlv@latest

# Debug with breakpoints
dlv debug cmd/txpark/main.go -- status my-testnet

# In delve:
# (dlv) break internal/cmd/status.go:42
# (dlv) continue
# (dlv) print testnetName

Common Issues

Import cycle:

package gitlab.com/tezos-infra/evm/txpark/internal/cmd
    imports gitlab.com/tezos-infra/evm/txpark/internal/k8s
    imports gitlab.com/tezos-infra/evm/txpark/internal/cmd: import cycle

Solution: Refactor to break the cycle, usually by moving shared code to a separate package.

Undefined function:

undefined: logger.Strings

Solution: Check internal/logger/logger.go for available helper functions. Use logger.Any for complex types.


Architecture Decisions

Why Cobra?

  • Industry standard for Go CLIs
  • Rich feature set (subcommands, flags, aliases)
  • Excellent documentation and community support
  • Easy to test and maintain

Why Custom Error Types?

// Provides better UX with actionable suggestions
errors.NewNotFoundError("testnet", name).
    WithSuggestion("run 'txpark list' to see available testnets")

Instead of generic errors, users get: - Clear error classification - Contextual information - Actionable next steps

Why Helm?

  • Declarative infrastructure
  • Templating and values system
  • Rollback capabilities
  • Wide adoption in Kubernetes ecosystem

Performance Considerations

Efficient Kubernetes Queries

// ✅ Use label selectors
pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
    LabelSelector: "component=evm-sequencer",
})

// ❌ Don't fetch all and filter in Go
allPods, _ := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
for _, pod := range allPods.Items {
    if pod.Labels["component"] == "evm-sequencer" { ... }
}

Parallel Operations

// When listing multiple testnets
var wg sync.WaitGroup
results := make(chan *testnet.Info, len(namespaces))

for _, ns := range namespaces {
    wg.Add(1)
    go func(namespace string) {
        defer wg.Done()
        info, err := testnet.GetInfo(ctx, client, namespace)
        if err == nil {
            results <- info
        }
    }(ns)
}

wg.Wait()
close(results)

Security Best Practices

Never Log Secrets

// ❌ Don't log sensitive data
log.Info("Creating secret", logger.String("secretKey", secretValue))

// ✅ Log only metadata
log.Info("Creating secret", logger.String("name", secretName))

Validate Input

// Always validate user input
func validateTestnetName(name string) error {
    pattern := `^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
    matched, _ := regexp.MatchString(pattern, name)
    if !matched {
        return errors.NewValidationError("invalid testnet name")
    }
    return nil
}

Getting Help


Next Steps