Skip to content

Conversation

@alpe
Copy link
Contributor

@alpe alpe commented Nov 4, 2025

🚧 WIP - early version

Integration tests are failing and some unit tests are missing

Overview

@github-actions
Copy link
Contributor

github-actions bot commented Nov 4, 2025

The latest Buf updates on your PR. Results from workflow CI and Release / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedNov 10, 2025, 1:27 PM

@codecov
Copy link

codecov bot commented Nov 7, 2025

Codecov Report

❌ Patch coverage is 34.65517% with 758 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.88%. Comparing base (3a9c9ba) to head (c7952fe).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
pkg/raft/node.go 0.00% 245 Missing ⚠️
pkg/raft/node_mock.go 50.94% 60 Missing and 18 partials ⚠️
block/internal/syncing/raft_retriever.go 0.00% 74 Missing ⚠️
pkg/rpc/server/http.go 1.66% 58 Missing and 1 partial ⚠️
node/full.go 28.20% 49 Missing and 7 partials ⚠️
node/failover.go 66.66% 38 Missing and 13 partials ⚠️
pkg/rpc/client/raft_cluster.go 0.00% 46 Missing ⚠️
block/internal/executing/executor.go 4.76% 35 Missing and 5 partials ⚠️
block/internal/syncing/syncer.go 29.41% 34 Missing and 2 partials ⚠️
pkg/raft/election.go 75.86% 15 Missing and 6 partials ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2814      +/-   ##
==========================================
- Coverage   64.79%   59.88%   -4.92%     
==========================================
  Files          81       89       +8     
  Lines        7224    10079    +2855     
==========================================
+ Hits         4681     6036    +1355     
- Misses       2003     3470    +1467     
- Partials      540      573      +33     
Flag Coverage Δ
combined 59.88% <34.65%> (-4.92%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@evstack evstack deleted a comment from claude bot Nov 7, 2025
@alpe
Copy link
Contributor Author

alpe commented Nov 7, 2025

@claude review this PR

@claude
Copy link
Contributor

claude bot commented Nov 7, 2025

Claude finished @alpe's task —— View job


Code Review: HA Failover Implementation

Comprehensive code review completed for the HA failover implementation based on Raft consensus.

Executive Summary

This PR implements high-availability failover using HashiCorp's Raft consensus library. The implementation adds Raft-based leader election, state replication, and automatic failover for sequencer nodes. While the architecture is sound, there are several critical issues that need attention, particularly around error handling, testing coverage, security, and performance.

Current Status: Integration tests failing, 630 lines missing test coverage (36% patch coverage)


1. Architecture & Code Quality

✅ Strengths

  1. Clean separation of concerns - Raft logic isolated in pkg/raft/, integration in node/failover.go
  2. Interface-based design - Good use of Runnable interface for leader/follower modes
  3. Factory pattern for mode switching - Clean approach to dynamic role changes
  4. Modular configuration - Raft config properly separated in pkg/config/config.go

⚠️ Issues & Recommendations

Critical: State Serialization

pkg/raft/node.go:238, pkg/raft/types.go:5

data, err := json.Marshal(state) // todo:use protobuf

Issue: Using JSON for Raft state replication introduces multiple problems:

  • No schema evolution support
  • Larger payload sizes (impacts network & disk)
  • Slower marshaling/unmarshaling
  • No forward/backward compatibility guarantees

Recommendation: Implement protobuf serialization ASAP. This is marked as TODO but is critical for production use. Add this to types/pb/evnode/v1/ and update RaftBlockState to use proto.


Critical: State Validation Logic

pkg/raft/types.go:16-24

func (s RaftBlockState) assertValid(next RaftBlockState) error {
    if s.Height > next.Height {
        return fmt.Errorf("invalid height: %d > %d", s.Height, next.Height)
    }
    if s.Timestamp > next.Timestamp {
        return fmt.Errorf("invalid timestamp: %d > %d", s.Timestamp, next.Timestamp)
    }
    return nil
}

Issues:

  1. Incomplete validation: Comment says "does not ensure that no gaps exist or chain continuity" but gaps could cause catastrophic state divergence
  2. Missing hash chain validation: Should verify next properly references s via hash
  3. No bounds checking: Heights should increment by exactly 1 (or document why gaps are acceptable)
  4. Timestamp validation too weak: Should enforce minimum progression, not just monotonicity

Recommendation:

func (s RaftBlockState) assertValid(next RaftBlockState) error {
    // Height must increment by exactly 1 (no gaps)
    if next.Height != s.Height + 1 {
        return fmt.Errorf("height gap detected: current=%d next=%d", s.Height, next.Height)
    }
    
    // Verify hash chain continuity
    if !bytes.Equal(next.PrevHash, s.Hash) {
        return fmt.Errorf("hash chain broken at height %d", next.Height)
    }
    
    // Enforce meaningful timestamp progression
    if next.Timestamp <= s.Timestamp {
        return fmt.Errorf("timestamp must increase: %d -> %d", s.Timestamp, next.Timestamp)
    }
    
    // Add max timestamp drift check to prevent far-future timestamps
    return nil
}

Major: Raft Node Initialization Error Handling

pkg/raft/node.go:56-113

Issues:

  1. Resource leaks on error: If NewRaft fails, the transport and stores are not closed
  2. Directory permissions too permissive: 0755 for raft data directory
  3. Error propagation loses context: Some errors don't wrap original error

Recommendation:

func NewNode(cfg *Config, clusterClient clusterClient, logger zerolog.Logger) (*Node, error) {
    if err := os.MkdirAll(cfg.RaftDir, 0700); err != nil { // More restrictive
        return nil, fmt.Errorf("create raft dir: %w", err)
    }
    
    // ... setup stores and transport ...
    
    // Use deferred cleanup on error
    var cleanup []func()
    defer func() {
        if err != nil {
            for _, fn := range cleanup {
                fn()
            }
        }
    }()
    
    cleanup = append(cleanup, func() { logStore.Close() })
    cleanup = append(cleanup, func() { stableStore.Close() })
    cleanup = append(cleanup, func() { transport.Close() })
    
    // ... rest of initialization
}

Major: Race Condition in FSM Apply

pkg/raft/node.go:316-322

if f.applyCh != nil {
    select {
    case f.applyCh <- RaftApplyMsg{Index: log.Index, State: &state}:
    default:
        f.logger.Warn().Msg("apply channel full, dropping message")
    }
}

Issues:

  1. Silent message drops: Dropping Raft apply messages can cause state divergence between Raft FSM and application state
  2. No backpressure mechanism: Should block or return error instead of dropping
  3. Channel capacity not documented: What's the expected capacity?

Recommendation:

// Either block (preferred for correctness):
if f.applyCh != nil {
    f.applyCh <- RaftApplyMsg{Index: log.Index, State: &state}
}

// OR return error to Raft (will cause node to fail):
if f.applyCh != nil {
    select {
    case f.applyCh <- msg:
    case <-time.After(applyTimeout):
        return fmt.Errorf("apply channel blocked for %s", applyTimeout)
    }
}

Major: Election Logic - Sync Check Race

pkg/raft/election.go:90-101

if becameLeader && !isCurrentlyLeader {
    if isStarted {
        time.Sleep(d.node.Config().SendTimeout) // Wait for in-flight messages
        if !runnable.IsSynced(d.node.GetState()) {
            if err := d.node.leadershipTransfer(); err != nil && !errors.Is(err, raft.ErrNotLeader) {
                return err
            }
            continue
        }
        // ...
    }
}

Issues:

  1. Hard-coded sleep is fragile: SendTimeout may not be sufficient for all in-flight messages
  2. TOCTOU race: State could change between IsSynced check and proceeding as leader
  3. Infinite transfer loop: If all nodes are unsynced, this could loop forever
  4. No transfer timeout: leadershipTransfer() might block indefinitely

Recommendation:

  • Add max retry count for leadership transfer
  • Use exponential backoff instead of fixed sleep
  • Add timeout to leadership transfer
  • Consider using Raft log index to verify sync instead of height comparison

2. Error Handling & Resilience

⚠️ Critical Issues

Shutdown Error Swallowing

node/failover.go:167-221

The Run method properly collects multiple errors but:

  1. HTTP server errors ignored on graceful shutdown - Only ErrServerClosed is expected, but other errors during shutdown should be logged
  2. P2P client close error ignored - Line 196: defer f.p2pClient.Close() // nolint: errcheck should collect the error
  3. Inconsistent error accumulation - Some places use errors.Join, others silently ignore

Recommendation: Consistently collect all errors and include them in the return value.


RPC Cluster Client Error Handling

pkg/rpc/client/raft_cluster.go:39-68

func (r RaftClusterClient) broadcast(ctx context.Context, path string, obj any) error {
    var peerErrs []error
    for _, peer := range r.peers {
        // ... attempt to contact peer ...
        if resp.StatusCode != http.StatusOK {
            peerErrs = append(peerErrs, fmt.Errorf("unexpected status: %s", resp.Status))
            continue
        }
    }
    if len(peerErrs) == len(r.peers) {
        return errors.Join(peerErrs...)
    }
    return nil // SUCCESS if ANY peer succeeds
}

Issues:

  1. Dangerous partial success semantics: Returns nil if ANY peer succeeds, even if most fail. This can cause split-brain scenarios.
  2. No quorum enforcement: Should require majority (or all) peers to succeed for Raft operations
  3. Timeout is global: 3-second timeout applies to all peers sequentially, not per-peer
  4. No retry logic: Transient network errors cause permanent failures

Recommendation:

// Broadcast to all peers in parallel with per-peer timeout
func (r RaftClusterClient) broadcast(ctx context.Context, path string, obj any) error {
    type result struct {
        peer string
        err  error
    }
    
    results := make(chan result, len(r.peers))
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    
    for _, peer := range r.peers {
        go func(p string) {
            results <- result{peer: p, err: r.sendToPeer(ctx, p, path, obj)}
        }(peer)
    }
    
    var successCount, failCount int
    var errs []error
    for i := 0; i < len(r.peers); i++ {
        res := <-results
        if res.err != nil {
            failCount++
            errs = append(errs, fmt.Errorf("%s: %w", res.peer, res.err))
        } else {
            successCount++
        }
    }
    
    // Require majority for Raft safety
    if successCount < len(r.peers)/2+1 {
        return fmt.Errorf("failed to reach quorum (%d/%d): %w", 
            successCount, len(r.peers), errors.Join(errs...))
    }
    return nil
}

3. Security Concerns

🔒 Critical Security Issues

Raft RPC Endpoint Authentication Missing

pkg/rpc/server/http.go:130-194

The /raft/join and /raft/remove endpoints have NO authentication:

mux.HandleFunc("/raft/join", func(w http.ResponseWriter, r *http.Request) {
    // ... anyone can call this and join the cluster
    if err := raftNode.AddPeer(rsp.NodeID, rsp.Address); err != nil {
        http.Error(w, "failed to join peer", http.StatusInternalServerError)
    }
})

Impact: Any attacker with network access can:

  1. Join malicious nodes to the Raft cluster
  2. Remove legitimate nodes from the cluster
  3. Cause denial of service
  4. Potentially achieve consensus takeover

Recommendation:

  • Add TLS mutual authentication for Raft endpoints
  • Require pre-shared secret or JWT token for cluster operations
  • Implement IP allowlisting for Raft management endpoints
  • Add rate limiting to prevent abuse

Raft Directory Permissions

pkg/raft/node.go:57

if err := os.MkdirAll(cfg.RaftDir, 0755); err != nil {

Issue: Directory is world-readable (0755), exposing Raft state including potentially sensitive data.

Recommendation: Use 0700 to restrict access to owner only.


Snapshot Store Writes to stderr

pkg/raft/node.go:84,94

snapshotStore, err := raft.NewFileSnapshotStore(cfg.RaftDir, int(cfg.SnapCount), os.Stderr)
transport, err := raft.NewTCPTransport(cfg.RaftAddr, addr, 3, 10*time.Second, os.Stderr)

Issue: Error output goes to stderr which may be logged. Could leak sensitive state information.

Recommendation: Use io.Discard or a dedicated logger with filtering.


4. Performance & Resource Management

⚠️ Performance Concerns

Blocking State Operations

block/internal/syncing/raft_retriever.go:82-96

The raftApplyLoop processes messages sequentially:

for {
    select {
    case <-ctx.Done():
        return
    case msg := <-applyCh:
        if err := r.consumeRaftBlock(ctx, msg.State); err != nil {
            r.logger.Error().Err(err).Uint64("height", msg.State.Height).Msg("failed to apply raft block")
        }
    }
}

Issues:

  1. Single-threaded processing: One slow block blocks all subsequent blocks
  2. No timeout on consumeRaftBlock: Could block indefinitely
  3. Errors are only logged: Failed blocks should halt the node or trigger catchup

Recommendation:

  • Add timeout to block processing
  • Consider parallel processing with ordering guarantees
  • Implement circuit breaker for persistent failures

Memory Allocation in Hot Path

pkg/raft/node.go:238-248

func (n *Node) Broadcast(_ context.Context, state *RaftBlockState) error {
    data, err := json.Marshal(state) // Allocation every block
    if err != nil {
        return fmt.Errorf("marshal block state: %w", err)
    }
    future := n.raft.Apply(data, n.config.SendTimeout)
    // ...
}

Issue: JSON marshaling allocates on every block broadcast. For high-throughput chains, this creates GC pressure.

Recommendation:

  • Use sync.Pool for marshal buffers
  • Pre-allocate buffer based on average block size
  • Switch to protobuf which is more efficient

No Backpressure on Block Production

block/internal/executing/executor.go

When Raft becomes a bottleneck, there's no backpressure mechanism to slow block production. This can lead to:

  • Memory exhaustion from buffered blocks
  • Raft log growth
  • Increased failover time

Recommendation: Monitor Raft lag and implement backpressure when lag exceeds threshold.


5. Testing & Reliability

❌ Major Testing Gaps

Coverage Analysis (from codecov report):

File Coverage Missing Lines Priority
pkg/raft/node.go 0.00% 202 🔴 CRITICAL
block/internal/syncing/raft_retriever.go 0.00% 60 🔴 CRITICAL
pkg/rpc/client/raft_cluster.go 0.00% 41 🔴 CRITICAL
pkg/rpc/server/http.go 2.17% 45 🔴 HIGH
node/full.go 30.00% 49 🟡 MEDIUM
node/failover.go 72.00% 35 🟢 ACCEPTABLE

Critical Missing Tests:

  1. No unit tests for pkg/raft/node.go - Core Raft wrapper has 0% coverage

    • Bootstrap scenarios
    • Join cluster scenarios
    • Peer add/remove
    • Snapshot/restore
    • Error paths
  2. No integration tests for Raft retriever - raft_retriever.go completely untested

    • Block application from Raft
    • Error handling
    • Stop/restart scenarios
  3. Missing RPC client tests - raft_cluster.go has no tests

    • Partial failure scenarios
    • Network timeout handling
    • Retry logic

Recommendations:

  1. Add unit tests for pkg/raft/node.go:
func TestNode_Bootstrap(t *testing.T)
func TestNode_JoinCluster(t *testing.T)
func TestNode_AddPeer_Idempotent(t *testing.T)
func TestNode_RemovePeer(t *testing.T)
func TestNode_LeaderElection(t *testing.T)
func TestNode_StateReplication(t *testing.T)
func TestNode_SnapshotRestore(t *testing.T)
  1. Add integration tests for failover scenarios:

    • Leader crash and recovery
    • Network partition (split brain)
    • Cascading failures
    • Rolling restarts
  2. Add chaos testing: Use tools like Jepsen or custom fault injection to test:

    • Random node crashes
    • Network delays/partitions
    • Clock skew
    • Disk failures

E2E Test Issues

test/e2e/failover_e2e_test.go:42-244

The E2E test TestLeaseFailoverE2E is good but has gaps:

  1. Only tests single leader failure - Should test multiple failures
  2. No partition recovery test - What happens with network splits?
  3. No concurrent transaction test - During failover, do transactions get lost?
  4. Assumes clean shutdown - Should test kill -9 scenarios
  5. Fixed timing assumptions - Uses hardcoded sleeps that may be brittle in CI

Recommendations:

  • Add test for follower failure (should be no-op)
  • Add test for majority failure (cluster should halt)
  • Add test for brain split and recovery
  • Use polling with timeout instead of fixed sleeps

6. Configuration & Documentation

⚠️ Configuration Issues

Missing Raft Configuration Validation

pkg/config/config.go:181

The RaftConfig struct has no validation:

type RaftConfig struct {
    Enable           bool            
    NodeID           string          
    RaftAddr         string          
    RaftDir          string          
    Bootstrap        bool            
    Peers            string          
    SnapCount        uint64          
    SendTimeout      time.Duration   
    HeartbeatTimeout time.Duration   
}

Issues:

  • No validation that NodeID is unique and non-empty
  • No validation that RaftAddr is valid TCP address
  • No validation that Peers format is correct (expects nodeID@addr)
  • No defaults for critical timeouts
  • Bootstrap can be true on multiple nodes (should prevent this)

Recommendation: Add Validate() method:

func (r *RaftConfig) Validate() error {
    if r.Enable {
        if r.NodeID == "" {
            return errors.New("raft.node_id is required when raft is enabled")
        }
        if r.RaftAddr == "" {
            return errors.New("raft.raft_addr is required")
        }
        if _, _, err := net.SplitHostPort(r.RaftAddr); err != nil {
            return fmt.Errorf("invalid raft.raft_addr: %w", err)
        }
        // Validate peers format
        if r.Peers != "" {
            for _, peer := range strings.Split(r.Peers, ",") {
                if _, err := splitPeerAddr(peer); err != nil {
                    return fmt.Errorf("invalid peer format '%s': %w", peer, err)
                }
            }
        }
        // Validate timeouts
        if r.SendTimeout == 0 {
            r.SendTimeout = 5 * time.Second
        }
        if r.HeartbeatTimeout == 0 {
            r.HeartbeatTimeout = 1 * time.Second
        }
    }
    return nil
}

Documentation Gaps:

  1. No ADR (Architecture Decision Record) - This is a major architectural change and should have an ADR explaining:

    • Why Raft was chosen over alternatives
    • Tradeoffs and limitations
    • Operational considerations
  2. No runbook - Operations team needs documentation for:

    • How to add/remove nodes from cluster
    • How to recover from failures
    • How to debug Raft issues
    • Monitoring and alerting recommendations
  3. No API documentation - The /raft/join and /raft/remove endpoints need OpenAPI/Swagger docs

  4. Configuration examples missing - Need examples for:

    • 3-node cluster setup
    • 5-node cluster setup
    • Single-node development mode

7. Concurrency & Race Conditions

⚠️ Potential Race Conditions

Leader Election State Management

pkg/raft/election.go:53-148

The Run() method has complex state machine with potential races:

var isStarted, isCurrentlyLeader bool
var workerCancel context.CancelFunc = func() {}

Issues:

  1. Multiple goroutines access shared state: isStarted, isCurrentlyLeader not protected by mutex
  2. Context cancellation races: workerCancel could be called concurrently
  3. No happens-before guarantee: Between checking isStarted and starting worker

Recommendation: Use proper synchronization or state machine library.


Failover State Races

node/failover.go:167-221

Multiple components started concurrently without clear ordering:

wg.Go(func() error {
    // RPC server
})
// Then separately:
f.p2pClient.Start(ctx)
f.headerSyncService.Start(ctx)
f.dataSyncService.Start(ctx)

Issue: P2P client must be ready before sync services start, but there's no coordination.

Recommendation: Add explicit startup ordering with health checks between stages.


8. Additional Observations

Code Smells

  1. Magic numbers:

    • pkg/raft/election.go:81 - 300 * time.Millisecond ticker should be configured
    • node/failover.go:148-150 - Hard-coded timeouts (10s, 120s)
  2. Copy-paste code:

    • newAggregatorMode and newSyncMode have significant duplication
    • Refactor common logic into shared helper
  3. Inconsistent naming:

    • raftNode vs RaftNode vs raft_node
    • leaderElection vs DynamicLeaderElection
  4. Commented-out code: None found (good!)


Summary of Recommendations by Priority

🔴 CRITICAL (Must Fix Before Production)

  1. Add authentication to Raft RPC endpoints (/raft/join, /raft/remove)
  2. Fix state validation in RaftBlockState.assertValid() - enforce height increments and hash chain
  3. Fix FSM apply channel drops - don't drop messages silently
  4. Add unit tests for pkg/raft/node.go (0% coverage)
  5. Fix RaftClusterClient partial success semantics - require quorum
  6. Implement protobuf serialization for Raft state

🟡 HIGH (Should Fix Soon)

  1. Fix resource leaks in NewNode error paths
  2. Add timeout and retry to leadership transfer
  3. Add integration tests for failover scenarios
  4. Add configuration validation for RaftConfig
  5. Fix directory permissions (0755 → 0700)
  6. Add backpressure mechanism for block production

🟢 MEDIUM (Technical Debt)

  1. Reduce code duplication between aggregator/sync mode setup
  2. Add ADR document explaining Raft design choices
  3. Add operational runbook
  4. Use sync.Pool for marshal buffers
  5. Add chaos/fault injection testing
  6. Improve E2E test coverage

⚪ LOW (Nice to Have)

  1. Add Prometheus metrics for Raft operations
  2. Add distributed tracing support
  3. Refactor magic numbers to constants
  4. Add OpenAPI docs for Raft endpoints

Positive Aspects ✨

  1. Well-structured code - Clean separation of concerns
  2. Good use of Go idioms - Contexts, error wrapping, interfaces
  3. Comprehensive E2E test - The failover E2E test is well-designed
  4. Proper error collection - Multi-error handling in shutdown paths
  5. Configuration flexibility - Good balance of configurability

Conclusion

This PR implements a solid foundation for HA failover using Raft, but requires significant work before production readiness. The main concerns are:

  1. Security - Unprotected Raft endpoints are a critical vulnerability
  2. Testing - 630 lines without test coverage including core Raft functionality
  3. Error Handling - Several cases where errors are dropped or mishandled
  4. State Validation - Insufficient validation could allow state divergence

Recommendation: Address the CRITICAL and HIGH priority issues before merging. Consider breaking this into smaller PRs to improve reviewability and reduce risk.


@alpe alpe mentioned this pull request Nov 10, 2025
@alpe alpe force-pushed the alex/adr023_ha-failover_raft5 branch from 9a28c30 to d771e5e Compare November 10, 2025 13:27
@github-actions
Copy link
Contributor

github-actions bot commented Nov 10, 2025

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedNov 12, 2025, 10:33 AM

@julienrbrt julienrbrt mentioned this pull request Nov 11, 2025
11 tasks
alpe and others added 7 commits November 11, 2025 12:36
This PR adds a prominent warning in the README to inform users not to
use tags or releases before v1.0.0, as they are not considered stable
and may contain breaking changes.

Fixes #2823

Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Marko <[email protected]>
## 🎯 Summary

This PR refactors the Docker build and release workflows to improve
maintainability, security, and separation of concerns. The changes
reorganize Dockerfiles into a consistent `apps/` directory structure and
split monolithic workflows into focused, reusable components.

---

## 📋 Changes

### Workflow Refactoring

- __Separated workflows__ for better modularity:

  - `ci.yml` - Main CI orchestrator with image tag determination
  - `docker.yml` - Reusable Docker build workflow
  - `docker-tests.yml` - Docker image testing workflow
  - `release.yml` - Tag-based release workflow for app publishing

- __Removed__ `ci_release.yml` (deprecated/consolidated into new
structure)

- __Enhanced__ `test.yml` - Simplified by removing Docker build logic
(134 lines removed)

### Docker Organization

- __Moved Dockerfiles__ to standardized locations:

  - `Dockerfile` → `apps/testapp/Dockerfile`
  - `da/cmd/local-da/Dockerfile` → `apps/local-da/Dockerfile`
  - Updated `apps/grpc/single/Dockerfile` and `docker-compose.yml`

- __Removed__ `Dockerfile.da` (consolidated)

### Documentation

- __Added__ `.github/RELEASE_QUICK_START.md` (231 lines)

  - Quick reference guide for Docker and Go module releases

## 🔄 Workflow Architecture

```javascript
┌─────────────┐
│   ci.yml    │  (Main orchestrator)
└──────┬──────┘
       │
       ├──► lint.yml
       ├──► docker.yml ──► Build images
       ├──► test.yml
       ├──► docker-tests.yml ──► Test images
       └──► proto.yml

┌─────────────────┐
│  release.yml    │  (Tag-based releases)
└─────────────────┘
       │
       └──► Build & push versioned images to GHCR
```

---

## 📝 Release Process

### Docker App Releases (Automated)

```bash
git tag evm/single/v0.2.0
git push origin evm/single/v0.2.0
# Workflow automatically builds and publishes to GHCR
```

### Tag Format

`{app-path}/v{major}.{minor}.{patch}`

Examples:

- `evm/single/v0.2.0` → `ghcr.io/evstack/ev-node-evm-single:v0.2.0`
- `testapp/v1.0.0` → `ghcr.io/evstack/ev-node-testapp:v1.0.0`

See [RELEASE_QUICK_START.md](.github/RELEASE_QUICK_START.md) for
complete guide.

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
<!--
Please read and fill out this form before submitting your PR.

Please make sure you have reviewed our contributors guide before
submitting your
first PR.

NOTE: PR titles should follow semantic commits:
https://www.conventionalcommits.org/en/v1.0.0/
-->

## Overview

<!-- 
Please provide an explanation of the PR, including the appropriate
context,
background, goal, and rationale. If there is an issue with this
information,
please provide a tl;dr and link the issue. 

Ex: Closes #<issue number>
-->
…2829)

<!--
Please read and fill out this form before submitting your PR.

Please make sure you have reviewed our contributors guide before
submitting your
first PR.

NOTE: PR titles should follow semantic commits:
https://www.conventionalcommits.org/en/v1.0.0/
-->

## Overview

<!-- 
Please provide an explanation of the PR, including the appropriate
context,
background, goal, and rationale. If there is an issue with this
information,
please provide a tl;dr and link the issue. 

Ex: Closes #<issue number>
-->

Inspired by #2781 and replace
all.



This change replaces occurrences of interface{} with the predeclared
identifier any, introduced in Go 1.18 as an alias for interface{}.

As noted in the [Go 1.18 Release
Notes](https://go.dev/doc/go1.18#language):
This improves readability and aligns the codebase with modern Go
conventions.

Signed-off-by: promalert <[email protected]>
Bumps [actions/github-script](https://github.com/actions/github-script)
from 7 to 8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/github-script/releases">actions/github-script's
releases</a>.</em></p>
<blockquote>
<h2>v8.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update Node.js version support to 24.x by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://github.com/actions/github-script/pull/637">actions/github-script#637</a></li>
<li>README for updating actions/github-script from v7 to v8 by <a
href="https://github.com/sneha-krip"><code>@​sneha-krip</code></a> in <a
href="https://github.com/actions/github-script/pull/653">actions/github-script#653</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://github.com/actions/github-script/pull/637">actions/github-script#637</a></li>
<li><a
href="https://github.com/sneha-krip"><code>@​sneha-krip</code></a> made
their first contribution in <a
href="https://github.com/actions/github-script/pull/653">actions/github-script#653</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v7.1.0...v8.0.0">https://github.com/actions/github-script/compare/v7.1.0...v8.0.0</a></p>
<h2>v7.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Upgrade husky to v9 by <a
href="https://github.com/benelan"><code>@​benelan</code></a> in <a
href="https://github.com/actions/github-script/pull/482">actions/github-script#482</a></li>
<li>Add workflow file for publishing releases to immutable action
package by <a
href="https://github.com/Jcambass"><code>@​Jcambass</code></a> in <a
href="https://github.com/actions/github-script/pull/485">actions/github-script#485</a></li>
<li>Upgrade IA Publish by <a
href="https://github.com/Jcambass"><code>@​Jcambass</code></a> in <a
href="https://github.com/actions/github-script/pull/486">actions/github-script#486</a></li>
<li>Fix workflow status badges by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://github.com/actions/github-script/pull/497">actions/github-script#497</a></li>
<li>Update usage of <code>actions/upload-artifact</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://github.com/actions/github-script/pull/512">actions/github-script#512</a></li>
<li>Clear up package name confusion by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://github.com/actions/github-script/pull/514">actions/github-script#514</a></li>
<li>Update dependencies with <code>npm audit fix</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://github.com/actions/github-script/pull/515">actions/github-script#515</a></li>
<li>Specify that the used script is JavaScript by <a
href="https://github.com/timotk"><code>@​timotk</code></a> in <a
href="https://github.com/actions/github-script/pull/478">actions/github-script#478</a></li>
<li>chore: Add Dependabot for NPM and Actions by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://github.com/actions/github-script/pull/472">actions/github-script#472</a></li>
<li>Define <code>permissions</code> in workflows and update actions by
<a href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in
<a
href="https://github.com/actions/github-script/pull/531">actions/github-script#531</a></li>
<li>chore: Add Dependabot for .github/actions/install-dependencies by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://github.com/actions/github-script/pull/532">actions/github-script#532</a></li>
<li>chore: Remove .vscode settings by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://github.com/actions/github-script/pull/533">actions/github-script#533</a></li>
<li>ci: Use github/setup-licensed by <a
href="https://github.com/nschonni"><code>@​nschonni</code></a> in <a
href="https://github.com/actions/github-script/pull/473">actions/github-script#473</a></li>
<li>make octokit instance available as octokit on top of github, to make
it easier to seamlessly copy examples from GitHub rest api or octokit
documentations by <a
href="https://github.com/iamstarkov"><code>@​iamstarkov</code></a> in <a
href="https://github.com/actions/github-script/pull/508">actions/github-script#508</a></li>
<li>Remove <code>octokit</code> README updates for v7 by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://github.com/actions/github-script/pull/557">actions/github-script#557</a></li>
<li>docs: add &quot;exec&quot; usage examples by <a
href="https://github.com/neilime"><code>@​neilime</code></a> in <a
href="https://github.com/actions/github-script/pull/546">actions/github-script#546</a></li>
<li>Bump ruby/setup-ruby from 1.213.0 to 1.222.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://github.com/actions/github-script/pull/563">actions/github-script#563</a></li>
<li>Bump ruby/setup-ruby from 1.222.0 to 1.229.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://github.com/actions/github-script/pull/575">actions/github-script#575</a></li>
<li>Clearly document passing inputs to the <code>script</code> by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://github.com/actions/github-script/pull/603">actions/github-script#603</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://github.com/actions/github-script/pull/610">actions/github-script#610</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/benelan"><code>@​benelan</code></a> made
their first contribution in <a
href="https://github.com/actions/github-script/pull/482">actions/github-script#482</a></li>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://github.com/actions/github-script/pull/485">actions/github-script#485</a></li>
<li><a href="https://github.com/timotk"><code>@​timotk</code></a> made
their first contribution in <a
href="https://github.com/actions/github-script/pull/478">actions/github-script#478</a></li>
<li><a
href="https://github.com/iamstarkov"><code>@​iamstarkov</code></a> made
their first contribution in <a
href="https://github.com/actions/github-script/pull/508">actions/github-script#508</a></li>
<li><a href="https://github.com/neilime"><code>@​neilime</code></a> made
their first contribution in <a
href="https://github.com/actions/github-script/pull/546">actions/github-script#546</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://github.com/actions/github-script/pull/610">actions/github-script#610</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/github-script/compare/v7...v7.1.0">https://github.com/actions/github-script/compare/v7...v7.1.0</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/github-script/commit/ed597411d8f924073f98dfc5c65a23a2325f34cd"><code>ed59741</code></a>
Merge pull request <a
href="https://github.com/actions/github-script/issues/653">#653</a>
from actions/sneha-krip/readme-for-v8</li>
<li><a
href="https://github.com/actions/github-script/commit/2dc352e4baefd91bec0d06f6ae2f1045d1687ca3"><code>2dc352e</code></a>
Bold minimum Actions Runner version in README</li>
<li><a
href="https://github.com/actions/github-script/commit/01e118c8d0d22115597e46514b5794e7bc3d56f1"><code>01e118c</code></a>
Update README for Node 24 runtime requirements</li>
<li><a
href="https://github.com/actions/github-script/commit/8b222ac82eda86dcad7795c9d49b839f7bf5b18b"><code>8b222ac</code></a>
Apply suggestion from <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a></li>
<li><a
href="https://github.com/actions/github-script/commit/adc0eeac992408a7b276994ca87edde1c8ce4d25"><code>adc0eea</code></a>
README for updating actions/github-script from v7 to v8</li>
<li><a
href="https://github.com/actions/github-script/commit/20fe497b3fe0c7be8aae5c9df711ac716dc9c425"><code>20fe497</code></a>
Merge pull request <a
href="https://github.com/actions/github-script/issues/637">#637</a>
from actions/node24</li>
<li><a
href="https://github.com/actions/github-script/commit/e7b7f222b11a03e8b695c4c7afba89a02ea20164"><code>e7b7f22</code></a>
update licenses</li>
<li><a
href="https://github.com/actions/github-script/commit/2c81ba05f308415d095291e6eeffe983d822345b"><code>2c81ba0</code></a>
Update Node.js version support to 24.x</li>
<li>See full diff in <a
href="https://github.com/actions/github-script/compare/v7...v8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/github-script&package-manager=github_actions&previous-version=7&new-version=8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…2826)

Bumps
[golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action)
from 8.0.0 to 9.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/golangci/golangci-lint-action/releases">golangci/golangci-lint-action's
releases</a>.</em></p>
<blockquote>
<h2>v9.0.0</h2>
<p>In the scope of this release, we change Nodejs runtime from node20 to
node24 (<a
href="https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/">https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/</a>).</p>
<h2>What's Changed</h2>
<h3>Changes</h3>
<ul>
<li>feat: add install-only option by <a
href="https://github.com/ldez"><code>@​ldez</code></a> in <a
href="https://github.com/golangci/golangci-lint-action/pull/1305">golangci/golangci-lint-action#1305</a></li>
<li>feat: support Module Plugin System by <a
href="https://github.com/ldez"><code>@​ldez</code></a> in <a
href="https://github.com/golangci/golangci-lint-action/pull/1306">golangci/golangci-lint-action#1306</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0">https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/0a35821d5c230e903fcfe077583637dea1b27b47"><code>0a35821</code></a>
docs: update readme</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/043b1b8d1c47e4591c1719682a050a7a0a82e19c"><code>043b1b8</code></a>
feat: support Module Plugin System (<a
href="https://github.com/golangci/golangci-lint-action/issues/1306">#1306</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/a66d26a4652b1a0b28a56b7c8b194c20f7c0b7f6"><code>a66d26a</code></a>
feat: add install-only option (<a
href="https://github.com/golangci/golangci-lint-action/issues/1305">#1305</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/7fe1b22e0c4632d6260fedfafd4b6025ac7418c3"><code>7fe1b22</code></a>
build(deps): bump the dependencies group with 2 updates (<a
href="https://github.com/golangci/golangci-lint-action/issues/1303">#1303</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/14973f18c82b6d66679563f71666ccee11907cb2"><code>14973f1</code></a>
build(deps-dev): bump the dev-dependencies group with 2 updates (<a
href="https://github.com/golangci/golangci-lint-action/issues/1299">#1299</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/8c2d575d9b37153325eebc4bb3a94cd09e1fae5d"><code>8c2d575</code></a>
build(deps): bump <code>@​types/node</code> from 24.8.1 to 24.9.1 in the
dependencies group...</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/b002b6ecfcabe6ac0e2c6cba1bcc779eb34ac51f"><code>b002b6e</code></a>
build(deps): bump actions/setup-node from 5 to 6 (<a
href="https://github.com/golangci/golangci-lint-action/issues/1296">#1296</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/c13f4ed1a9a677a28be0df3e11c34a78db85c77c"><code>c13f4ed</code></a>
build(deps): bump <code>@​types/node</code> from 24.7.2 to 24.8.1 in the
dependencies group...</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/b68d21b131098f33ec55c11c242113b4a10dc30a"><code>b68d21b</code></a>
docs: improve readme</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/06188a2a4a13a4786b4584e086b2040214cd4ca5"><code>06188a2</code></a>
build(deps): bump github/codeql-action from 3 to 4 (<a
href="https://github.com/golangci/golangci-lint-action/issues/1293">#1293</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golangci/golangci-lint-action&package-manager=github_actions&previous-version=8.0.0&new-version=9.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@github-actions
Copy link
Contributor

PR Preview Action v1.6.2

🚀 View preview at
https://evstack.github.io/docs-preview/pr-2814/

Built to branch main at 2025-11-11 11:39 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

alpe added 3 commits November 11, 2025 12:39
* main:
  build(deps): Bump golangci/golangci-lint-action from 8.0.0 to 9.0.0 (#2826)
  build(deps): Bump actions/github-script from 7 to 8 (#2827)
  refactor: replace interface{} with any for clarity and modernization (#2829)
  add note on full node for evm (#2825)
  chore: docker workflow refactor (#2820)
  docs: add version notice warning about pre-v1 releases (#2824)
* main:
  chore: bump tastora version (#2830)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants