Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
- name: Start devnet
run: |
cd ./my-awesome-avs/
DOCKERS_HOST=localhost devkit avs devnet start --skip-avs-run
devkit avs devnet start
sleep 10

- name: Check devnet RPC is live
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ clean: ## Remove binary
@rm -f $(APP_NAME) ~/bin/$(APP_NAME)

build/darwin-arm64:
GOOS=darwin GOARCH=arm64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/darwin-arm64/devkit cmd/$(APP_NAME)/main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/darwin-arm64/devkit cmd/$(APP_NAME)/main.go

build/darwin-amd64:
GOOS=darwin GOARCH=amd64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/darwin-amd64/devkit cmd/$(APP_NAME)/main.go
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/darwin-amd64/devkit cmd/$(APP_NAME)/main.go

build/linux-arm64:
GOOS=linux GOARCH=arm64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/linux-arm64/devkit cmd/$(APP_NAME)/main.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/linux-arm64/devkit cmd/$(APP_NAME)/main.go

build/linux-amd64:
GOOS=linux GOARCH=amd64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/linux-amd64/devkit cmd/$(APP_NAME)/main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(ALL_FLAGS) $(GO) build $(GO_FLAGS) -o release/linux-amd64/devkit cmd/$(APP_NAME)/main.go


.PHONY: release
Expand Down
2 changes: 2 additions & 0 deletions docker/anvil/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ services:
command: "--host 0.0.0.0 --fork-url ${FORK_RPC_URL} --fork-block-number ${FORK_BLOCK_NUMBER} ${ANVIL_ARGS}"
ports:
- "${DEVNET_PORT}:8545"
extra_hosts:
- "host.docker.internal:host-gateway"
8 changes: 5 additions & 3 deletions pkg/commands/devnet_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func StartDevnetAction(cCtx *cli.Context) error {
return fmt.Errorf("fork-url not set; set fork-url in ./config/context/devnet.yaml or .env and consult README for guidance")
}

// Ensure fork URL uses appropriate Docker host for container environments
dockerForkUrl := devnet.EnsureDockerHost(forkUrl)

// Get the block_time from env/config
blockTime, err := devnet.GetDevnetBlockTimeOrDefault(config, devnet.L1)
if err != nil {
Expand All @@ -120,14 +123,14 @@ func StartDevnetAction(cCtx *cli.Context) error {
"FOUNDRY_IMAGE="+chainImage,
"ANVIL_ARGS="+chainArgs,
fmt.Sprintf("DEVNET_PORT=%d", port),
"FORK_RPC_URL="+forkUrl,
"FORK_RPC_URL="+dockerForkUrl,
fmt.Sprintf("FORK_BLOCK_NUMBER=%d", l1ChainConfig.Fork.Block),
"AVS_CONTAINER_NAME="+containerName,
)
if err := cmd.Run(); err != nil {
return fmt.Errorf("❌ Failed to start devnet: %w", err)
}
rpcUrl := fmt.Sprintf("http://localhost:%d", port)
rpcUrl := devnet.GetRPCURL(port)
logger.Info("Waiting for devnet to be ready...")

// Set path for context yamls
Expand Down Expand Up @@ -176,7 +179,6 @@ func StartDevnetAction(cCtx *cli.Context) error {

// Sleep for 4 second to ensure the devnet is fully started
time.Sleep(4 * time.Second)

// Fund the wallets defined in config
err = devnet.FundWalletsDevnet(config, rpcUrl)
if err != nil {
Expand Down
1 change: 0 additions & 1 deletion pkg/common/devnet/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package devnet
const FOUNDRY_IMAGE = "ghcr.io/foundry-rs/foundry:stable"
const CHAIN_ARGS = "--chain-id 31337"
const FUND_VALUE = "10000000000000000000"
const RPC_URL = "http://localhost:8545"
const CONTEXT = "devnet"
const L1 = "l1"

Expand Down
133 changes: 133 additions & 0 deletions pkg/common/devnet/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import (
"fmt"
"log"
"net"
"net/url"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"time"

"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -47,3 +52,131 @@ func GetDockerPsDevnetArgs() []string {
"--format", "{{.Names}}: {{.Ports}}",
}
}

// GetDockerHost returns the appropriate Docker host based on environment and platform.
// Uses DOCKERS_HOST environment variable if set, otherwise detects OS:
// - Linux: defaults to localhost (Docker containers can access host via localhost)
// - macOS/Windows: defaults to host.docker.internal (required for Docker Desktop)
func GetDockerHost() string {
if dockersHost := os.Getenv("DOCKERS_HOST"); dockersHost != "" {
return dockersHost
}

// Detect OS and set appropriate default
if runtime.GOOS == "linux" {
return "localhost"
} else {
return "host.docker.internal"
}
}

// EnsureDockerHost replaces localhost/127.0.0.1 in URLs with the appropriate Docker host.
// Only replaces when localhost/127.0.0.1 are the actual hostname, not substrings.
// This ensures URLs work correctly when passed to Docker containers across platforms.
func EnsureDockerHost(inputUrl string) string {
dockerHost := GetDockerHost()

// Handle edge cases first: bare localhost/127.0.0.1 strings
trimmed := strings.TrimSpace(inputUrl)
if trimmed == "localhost" || trimmed == "127.0.0.1" {
return dockerHost
}

// Parse the URL to work with components safely
parsedUrl, err := url.Parse(inputUrl)
if err != nil {
// If URL parsing fails, fall back to regex-based replacement
return ensureDockerHostRegex(inputUrl, dockerHost)
}

// Extract hostname (without port)
hostname := parsedUrl.Hostname()

// Handle the case where URL parsing succeeded but hostname is empty
// This happens with strings like "localhost:8545" (parsed as scheme:opaque)
if hostname == "" {
// Check if the scheme is localhost or 127.0.0.1 (meaning it was parsed as scheme:opaque)
if parsedUrl.Scheme == "localhost" || parsedUrl.Scheme == "127.0.0.1" {
// Reconstruct as host:port format
if parsedUrl.Opaque != "" {
return fmt.Sprintf("%s:%s", dockerHost, parsedUrl.Opaque)
} else {
return dockerHost
}
}
// If hostname is empty but it's not the scheme:opaque case, fall back to regex
return ensureDockerHostRegex(inputUrl, dockerHost)
}

// Only replace if hostname is exactly localhost or 127.0.0.1
if hostname == "localhost" || hostname == "127.0.0.1" {
// Replace just the hostname part
if parsedUrl.Port() != "" {
parsedUrl.Host = fmt.Sprintf("%s:%s", dockerHost, parsedUrl.Port())
} else {
parsedUrl.Host = dockerHost
}
return parsedUrl.String()
}

// Return original URL if hostname doesn't match
return inputUrl
}

// ensureDockerHostRegex provides regex-based fallback for malformed URLs
func ensureDockerHostRegex(inputUrl string, dockerHost string) string {
// Pattern to match URLs with schemes (http, https, ws, wss) followed by localhost
// This ensures we only rewrite actual localhost URLs, not subdomains like "api.localhost.company.com"
schemeLocalhostPattern := regexp.MustCompile(`(https?|wss?)://localhost(:[0-9]+)?(/\S*)?`)
schemeIPPattern := regexp.MustCompile(`(https?|wss?)://127\.0\.0\.1(:[0-9]+)?(/\S*)?`)

// Pattern to match malformed scheme-like strings with localhost/127.0.0.1
// This handles cases like "ht tp://localhost" or "ht\x00tp://localhost"
malformedSchemeLocalhostPattern := regexp.MustCompile(`\S*tp://localhost(:[0-9]+)?(/\S*)?`)
malformedSchemeIPPattern := regexp.MustCompile(`\S*tp://127\.0\.0\.1(:[0-9]+)?(/\S*)?`)

// Pattern to match standalone localhost (no scheme) at start of string or after whitespace/equals
// This avoids matching localhost as part of a larger domain name
standaloneLocalhostPattern := regexp.MustCompile(`(?:^|[\s=])localhost(:[0-9]+)?(?:[\s/=?#]|$)`)
standaloneIPPattern := regexp.MustCompile(`(?:^|[\s=])127\.0\.0\.1(:[0-9]+)?(?:[\s/=?#]|$)`)

result := inputUrl

// Replace scheme-based localhost URLs
result = schemeLocalhostPattern.ReplaceAllStringFunc(result, func(match string) string {
return strings.Replace(match, "localhost", dockerHost, 1)
})

// Replace scheme-based 127.0.0.1 URLs
result = schemeIPPattern.ReplaceAllStringFunc(result, func(match string) string {
return strings.Replace(match, "127.0.0.1", dockerHost, 1)
})

// Replace malformed scheme localhost patterns
result = malformedSchemeLocalhostPattern.ReplaceAllStringFunc(result, func(match string) string {
return strings.Replace(match, "localhost", dockerHost, 1)
})

// Replace malformed scheme 127.0.0.1 patterns
result = malformedSchemeIPPattern.ReplaceAllStringFunc(result, func(match string) string {
return strings.Replace(match, "127.0.0.1", dockerHost, 1)
})

// Replace standalone localhost patterns
result = standaloneLocalhostPattern.ReplaceAllStringFunc(result, func(match string) string {
return strings.Replace(match, "localhost", dockerHost, 1)
})

// Replace standalone 127.0.0.1 patterns
result = standaloneIPPattern.ReplaceAllStringFunc(result, func(match string) string {
return strings.Replace(match, "127.0.0.1", dockerHost, 1)
})

return result
}

// GetRPCURL returns the RPC URL for accessing the devnet container from the host.
// This should always use localhost since it's for host→container communication
func GetRPCURL(port int) string {
return fmt.Sprintf("http://localhost:%d", port)
}
Loading
Loading