Skip to content

Commit

Permalink
Blockscout multinet (#1398)
Browse files Browse the repository at this point in the history
* blockscout urls and docs, run logs download in parallel and ignore local stuff

* remove unused vars

* blockscout docs

* changelog
  • Loading branch information
skudasov authored Nov 27, 2024
1 parent 2839080 commit 24b9149
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 63 deletions.
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- [Mocking Services](framework/components/mocking.md)
- [Copying Files](framework/copying_files.md)
- [External Environment](framework/components/external.md)
- [Troubleshooting](framework/components/troubleshooting.md)
- [Secrets]()
- [Observability Stack](framework/observability/observability_stack.md)
- [Metrics](framework/observability/metrics.md)
Expand Down
12 changes: 12 additions & 0 deletions book/src/framework/components/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Troubleshooting

## Can't run `anvil`, issue with `Rosetta`
```
2024/11/27 15:20:27 ⏳ Waiting for container id 79f8a68c07cc image: f4hrenh9it/foundry:latest. Waiting for: &{Port:8546 timeout:0x14000901278 PollInterval:100ms skipInternalCheck:false}
2024/11/27 15:20:27 container logs (all exposed ports, [8546/tcp], were not mapped in 5s: port 8546/tcp is not mapped yet
wait until ready: check target: retries: 1, port: "", last err: container exited with code 133):
rosetta error: Rosetta is only intended to run on Apple Silicon with a macOS host using Virtualization.framework with Rosetta mode enabled
```
#### Solution

Update your docker to `Docker version 27.3.1, build ce12230`
6 changes: 6 additions & 0 deletions book/src/framework/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
- `Docker` [OrbStack](https://orbstack.dev/) or [Docker Desktop](https://www.docker.com/products/docker-desktop/), we recommend OrbStack (faster, smaller memory footprint)
- [Golang](https://go.dev/doc/install)

Tested with
```
Docker version 27.3.1
OrbStack Version: 1.8.2 (1080200)
```

## Test setup

To start writing tests create a directory for your project with `go.mod` and add a package
Expand Down
8 changes: 8 additions & 0 deletions book/src/framework/observability/blockscout.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ To remove it, we also clean up all Blockscout databases to prevent stale data wh
ctf bs down
```

## Selecting Blockchain Node

By default, we connect to the first `anvil` node, but you can select the node explicitly
```
ctf bs -r http://host.docker.internal:8545 d
ctf bs -r http://host.docker.internal:8555 d
```

<div class="warning">

Blockscout isn’t ideal for local, ephemeral environments, as it won’t re-index blocks and transactions on test reruns. The easiest approach is to set up Blockscout first, initialize the test environment, switch to the [cache](../components/caching.md) config, and run tests without restarting RPC nodes.
Expand Down
2 changes: 2 additions & 0 deletions framework/.changeset/v0.2.12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Omit all local tooling container logs
- Allow to connect Blockscout to any node
7 changes: 5 additions & 2 deletions framework/cmd/blockscout.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package main
import (
"fmt"
"github.com/smartcontractkit/chainlink-testing-framework/framework"
"os"
"path/filepath"
)

func blockscoutUp() error {
func blockscoutUp(url string) error {
framework.L.Info().Msg("Creating local Blockscout stack")
if err := extractAllFiles("observability"); err != nil {
return err
}
os.Setenv("BLOCKSCOUT_RPC_URL", url)
err := runCommand("bash", "-c", fmt.Sprintf(`
cd %s && \
docker compose up -d
Expand All @@ -23,8 +25,9 @@ func blockscoutUp() error {
return nil
}

func blockscoutDown() error {
func blockscoutDown(url string) error {
framework.L.Info().Msg("Removing local Blockscout stack")
os.Setenv("BLOCKSCOUT_RPC_URL", url)
err := runCommand("bash", "-c", fmt.Sprintf(`
cd %s && \
docker compose down -v
Expand Down
22 changes: 15 additions & 7 deletions framework/cmd/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import (
)

type nodeSetForm struct {
Network string
CLVersion string
Nodes int
Observability bool
Blockscout bool
Network string
CLVersion string
Nodes int
Observability bool
Blockscout bool
BlockscoutRPCURL string
}

func createComponentsFromForm(form *nodeSetForm) error {
Expand Down Expand Up @@ -83,7 +84,7 @@ func createComponentsFromForm(form *nodeSetForm) error {
}
switch form.Blockscout {
case true:
if err := blockscoutUp(); err != nil {
if err := blockscoutUp(form.BlockscoutRPCURL); err != nil {
return err
}
}
Expand All @@ -107,7 +108,7 @@ func cleanup(form *nodeSetForm) error {
}
switch form.Blockscout {
case true:
if err := blockscoutDown(); err != nil {
if err := blockscoutDown(form.BlockscoutRPCURL); err != nil {
return err
}
}
Expand Down Expand Up @@ -156,6 +157,13 @@ Docker Desktop (https://www.docker.com/products/docker-desktop/)
huh.NewConfirm().
Title("Do you need to spin up a Blockscout stack?").
Value(&f.Blockscout),
huh.NewSelect[string]().
Title("To which blockchain node you want Blockscout to connect?").
Options(
huh.NewOption("Network 1", "http://host.docker.internal:8545"),
huh.NewOption("Network 2", "http://host.docker.internal:8550"),
).
Value(&f.BlockscoutRPCURL),
),
)

Expand Down
21 changes: 17 additions & 4 deletions framework/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,31 +114,44 @@ func main() {
Name: "blockscout",
Aliases: []string{"bs"},
Usage: "Controls local Blockscout stack",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "rpc",
Aliases: []string{"r"},
Usage: "RPC URL for blockchain node to index",
Value: "http://host.docker.internal:8545",
},
},
Subcommands: []*cli.Command{
{
Name: "up",
Usage: "ctf bs up",
Aliases: []string{"u"},
Description: "Spins up Blockscout stack",
Action: func(c *cli.Context) error { return blockscoutUp() },
Action: func(c *cli.Context) error {
return blockscoutUp(c.String("rpc"))
},
},
{
Name: "down",
Usage: "ctf bs down",
Aliases: []string{"d"},
Description: "Removes Blockscout stack, wipes all Blockscout databases data",
Action: func(c *cli.Context) error { return blockscoutDown() },
Action: func(c *cli.Context) error {
return blockscoutDown(c.String("rpc"))
},
},
{
Name: "reboot",
Usage: "ctf bs reboot or ctf bs r",
Aliases: []string{"r"},
Description: "Reboots Blockscout stack",
Action: func(c *cli.Context) error {
if err := blockscoutDown(); err != nil {
rpc := c.String("rpc")
if err := blockscoutDown(rpc); err != nil {
return err
}
return blockscoutUp()
return blockscoutUp(rpc)
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions framework/cmd/observability/blockscout/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ services:
- db:database
environment:
ETHEREUM_JSONRPC_VARIANT: 'geth'
ETHEREUM_JSONRPC_HTTP_URL: ${BLOCKSCOUT_RPC_URL}
ETHEREUM_JSONRPC_TRACE_URL: ${BLOCKSCOUT_RPC_URL}

visualizer:
extends:
Expand Down
3 changes: 1 addition & 2 deletions framework/components/simple_node_set/node_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,12 @@ func printURLs(out *Output) {
if out == nil {
return
}
httpURLs, p2pURLs, pgURLs := make([]string, 0), make([]string, 0), make([]string, 0)
httpURLs, _, pgURLs := make([]string, 0), make([]string, 0), make([]string, 0)
for _, n := range out.CLNodes {
httpURLs = append(httpURLs, n.Node.HostURL)
pgURLs = append(pgURLs, n.PostgreSQL.Url)
}
framework.L.Info().Any("UI", httpURLs).Send()
framework.L.Debug().Any("P2P", p2pURLs).Send()
framework.L.Debug().Any("DB", pgURLs).Send()
}

Expand Down
99 changes: 63 additions & 36 deletions framework/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/docker/go-connections/nat"
"github.com/google/uuid"
tc "github.com/testcontainers/testcontainers-go"
"golang.org/x/sync/errgroup"
"io"
"os"
"os/exec"
Expand Down Expand Up @@ -230,6 +231,23 @@ func (dc *DockerClient) copyToContainer(containerID, sourceFile, targetPath stri
return nil
}

func in(s string, substrings []string) bool {
for _, substr := range substrings {
if strings.Contains(s, substr) {
return true
}
}
return false
}

func isLocalToolDockerContainer(containerName string) bool {
if in(containerName, []string{"/sig-provider", "/stats", "/stats-db", "/db", "/backend", "/promtail", "/compose", "/blockscout", "/frontend", "/user-ops-indexer", "/visualizer", "/redis-db", "/proxy"}) {
L.Debug().Str("Container", containerName).Msg("Ignoring local tool container output")
return true
}
return false
}

// WriteAllContainersLogs writes all Docker container logs to the default logs directory
func WriteAllContainersLogs() error {
L.Info().Msg("Writing Docker containers logs")
Expand All @@ -247,49 +265,58 @@ func WriteAllContainersLogs() error {
return fmt.Errorf("failed to list Docker containers: %w", err)
}

eg := &errgroup.Group{}

for _, containerInfo := range containers {
containerName := containerInfo.Names[0]
logOptions := container.LogsOptions{ShowStdout: true, ShowStderr: true}
logs, err := provider.Client().ContainerLogs(context.Background(), containerInfo.ID, logOptions)
if err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to fetch logs for container")
continue
}
logFilePath := filepath.Join(DefaultCTFLogsDir, fmt.Sprintf("%s.log", containerName))
logFile, err := os.Create(logFilePath)
if err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to create container log file")
continue
}
// Parse and write logs
header := make([]byte, 8) // Docker stream header is 8 bytes
for {
_, err := io.ReadFull(logs, header)
if err == io.EOF {
break
eg.Go(func() error {
containerName := containerInfo.Names[0]
if isLocalToolDockerContainer(containerName) {
return nil
}
L.Debug().Str("Container", containerName).Msg("Collecting logs")
logOptions := container.LogsOptions{ShowStdout: true, ShowStderr: true}
logs, err := provider.Client().ContainerLogs(context.Background(), containerInfo.ID, logOptions)
if err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to read log stream header")
break
L.Error().Err(err).Str("Container", containerName).Msg("failed to fetch logs for container")
return err
}

// Extract log message size
msgSize := binary.BigEndian.Uint32(header[4:8])

// Read the log message
msg := make([]byte, msgSize)
_, err = io.ReadFull(logs, msg)
logFilePath := filepath.Join(DefaultCTFLogsDir, fmt.Sprintf("%s.log", containerName))
logFile, err := os.Create(logFilePath)
if err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to read log message")
break
L.Error().Err(err).Str("Container", containerName).Msg("failed to create container log file")
return err
}
// Parse and write logs
header := make([]byte, 8) // Docker stream header is 8 bytes
for {
_, err := io.ReadFull(logs, header)
if err == io.EOF {
break
}
if err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to read log stream header")
break
}

// Write the log message to the file
if _, err := logFile.Write(msg); err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to write log message to file")
break
// Extract log message size
msgSize := binary.BigEndian.Uint32(header[4:8])

// Read the log message
msg := make([]byte, msgSize)
_, err = io.ReadFull(logs, msg)
if err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to read log message")
break
}

// Write the log message to the file
if _, err := logFile.Write(msg); err != nil {
L.Error().Err(err).Str("Container", containerName).Msg("failed to write log message to file")
break
}
}
}
return nil
})
}
return nil
return eg.Wait()
}
8 changes: 4 additions & 4 deletions framework/examples/myproject/fork.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

[blockchain_dst]
chain_id = "2337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate", "-b", "1"]
docker_cmd_params = ["-b", "1"]
image = "f4hrenh9it/foundry:latest"
port = "8545"
type = "anvil"
docker_cmd_params = ["-b", "1"]

[blockchain_src]
chain_id = "3337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate", "-b", "1"]
docker_cmd_params = ["-b", "1"]
image = "f4hrenh9it/foundry:latest"
port = "8555"
type = "anvil"
docker_cmd_params = ["-b", "1"]
14 changes: 8 additions & 6 deletions framework/examples/myproject/fork_plus_offchain.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
[contracts_src]
[contracts_dst]

[blockchain_dst]
chain_id = "2337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate", "-b", "1"]
docker_cmd_params = ["-b", "1"]
image = "f4hrenh9it/foundry:latest"
port = "8545"
type = "anvil"
docker_cmd_params = ["-b", "1"]

[blockchain_src]
chain_id = "3337"
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate"]
# docker_cmd_params = ["--fork-url", "wss://avalanche-fuji-c-chain-rpc.publicnode.com", "--auto-impersonate", "-b", "1"]
docker_cmd_params = ["-b", "1"]
image = "f4hrenh9it/foundry:latest"
port = "8555"
type = "anvil"
docker_cmd_params = ["-b", "1"]

[contracts_dst]

[contracts_src]

[nodeset]
nodes = 5
Expand Down
2 changes: 2 additions & 0 deletions framework/examples/myproject/fork_plus_offchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ func TestOffChainAndFork(t *testing.T) {
WithTracing(seth.TracingLevel_All, []string{seth.TraceOutput_Console}).
WithPrivateKeys([]string{blockchain.DefaultAnvilPrivateKey}).
Build()
require.NoError(t, err)
scDst, err := seth.NewClientBuilder().
WithRpcUrl(bcDst.Nodes[0].HostWSUrl).
WithGasPriceEstimations(true, 0, seth.Priority_Fast).
WithTracing(seth.TracingLevel_All, []string{seth.TraceOutput_Console}).
WithPrivateKeys([]string{blockchain.DefaultAnvilPrivateKey}).
Build()
require.NoError(t, err)

// deploy 2 example product contracts
// you should replace it with chainlink-deployments
Expand Down
Loading

0 comments on commit 24b9149

Please sign in to comment.