Skip to content

Commit e83887b

Browse files
committed
[antithesis] Add test setup for xsvm
1 parent ef81f95 commit e83887b

File tree

21 files changed

+1171
-85
lines changed

21 files changed

+1171
-85
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ jobs:
265265
- name: Check image build
266266
shell: bash
267267
run: bash -x scripts/tests.build_image.sh
268-
test_build_antithesis_avalanchego_image:
269-
name: Antithesis avalanchego build
268+
test_build_antithesis_images:
269+
name: Build Antithesis images
270270
runs-on: ubuntu-latest
271271
steps:
272272
- uses: actions/checkout@v4
@@ -275,6 +275,11 @@ jobs:
275275
run: bash -x scripts/tests.build_antithesis_images.sh
276276
env:
277277
TEST_SETUP: avalanchego
278+
- name: Check image build for xsvm test setup
279+
shell: bash
280+
run: bash -x scripts/tests.build_antithesis_images.sh
281+
env:
282+
TEST_SETUP: xsvm
278283
govulncheck:
279284
runs-on: ubuntu-latest
280285
name: govulncheck

.github/workflows/publish_antithesis_images.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ jobs:
3131
IMAGE_PREFIX: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
3232
TAG: latest
3333
TEST_SETUP: avalanchego
34+
35+
- name: Build images for xsvm test setup
36+
run: bash -x ./scripts/build_antithesis_images.sh
37+
env:
38+
IMAGE_PREFIX: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
39+
TAG: latest
40+
TEST_SETUP: xsvm

scripts/build_antithesis_images.sh

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ set -euo pipefail
55
# Builds docker images for antithesis testing.
66

77
# e.g.,
8-
# ./scripts/build_antithesis_images.sh # Build local images
9-
# IMAGE_PREFIX=<registry>/<repo> TAG=latest ./scripts/build_antithesis_images.sh # Specify a prefix to enable image push and use a specific tag
8+
# TEST_SETUP=avalanchego ./scripts/build_antithesis_images.sh # Build local images for avalanchego
9+
# TEST_SETUP=xsvm ./scripts/build_antithesis_images.sh # Build local images for xsvm
10+
# TEST_SETUP=xsvm IMAGE_PREFIX=<registry>/<repo> TAG=latest ./scripts/build_antithesis_images.sh # Specify a prefix to enable image push and use a specific tag
1011

1112
# Directory above this script
1213
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
@@ -28,11 +29,14 @@ GO_VERSION="$(go list -m -f '{{.GoVersion}}')"
2829
function build_images {
2930
local test_setup=$1
3031
local uninstrumented_node_dockerfile=$2
32+
local node_only=${3:-}
3133

3234
# Define image names
3335
local base_image_name="antithesis-${test_setup}"
36+
local avalanchego_node_image_name="antithesis-avalanchego-node:${TAG}"
3437
if [[ -n "${IMAGE_PREFIX}" ]]; then
3538
base_image_name="${IMAGE_PREFIX}/${base_image_name}"
39+
avalanchego_node_image_name="${IMAGE_PREFIX}/${avalanchego_node_image_name}"
3640
fi
3741
local node_image_name="${base_image_name}-node:${TAG}"
3842
local workload_image_name="${base_image_name}-workload:${TAG}"
@@ -49,18 +53,34 @@ function build_images {
4953
fi
5054

5155
# Define default build command
52-
local docker_cmd="docker buildx build --build-arg GO_VERSION=${GO_VERSION}"
56+
local docker_cmd="docker buildx build --build-arg GO_VERSION=${GO_VERSION} --build-arg NODE_IMAGE=${node_image_name}"
57+
58+
if [[ "${test_setup}" == "xsvm" ]]; then
59+
# The xsvm node image is build on the avalanchego node image
60+
docker_cmd="${docker_cmd} --build-arg AVALANCHEGO_NODE_IMAGE=${avalanchego_node_image_name}"
61+
fi
5362

5463
# Build node image first to allow the config and workload image builds to use it.
5564
${docker_cmd} -t "${node_image_name}" -f "${node_dockerfile}" "${AVALANCHE_PATH}"
56-
${docker_cmd} --build-arg NODE_IMAGE="${node_image_name}" -t "${workload_image_name}" -f "${base_dockerfile}.workload" "${AVALANCHE_PATH}"
57-
${docker_cmd} --build-arg IMAGE_TAG="${TAG}" -t "${config_image_name}" -f "${base_dockerfile}.config" "${AVALANCHE_PATH}"
65+
66+
if [[ -z "${node_only}" || "${node_only}" == "0" ]]; then
67+
# Skip building the config and workload images. Supports building the avalanchego
68+
# node image as the base image for the xsvm node image.
69+
${docker_cmd} --build-arg IMAGE_TAG="${TAG}" -t "${config_image_name}" -f "${base_dockerfile}.config" "${AVALANCHE_PATH}"
70+
${docker_cmd} -t "${workload_image_name}" -f "${base_dockerfile}.workload" "${AVALANCHE_PATH}"
71+
fi
5872
}
5973

6074
TEST_SETUP="${TEST_SETUP:-}"
6175
if [[ "${TEST_SETUP}" == "avalanchego" ]]; then
6276
build_images avalanchego "${AVALANCHE_PATH}/Dockerfile"
77+
elif [[ "${TEST_SETUP}" == "xsvm" ]]; then
78+
# Only build the node image to use as the base for the xsvm image
79+
NODE_ONLY=1
80+
build_images avalanchego "${AVALANCHE_PATH}/Dockerfile" "${NODE_ONLY}"
81+
82+
build_images xsvm "${AVALANCHE_PATH}/vms/example/xsvm/Dockerfile"
6383
else
64-
echo "TEST_SETUP must be set. Valid values are 'avalanchego'"
84+
echo "TEST_SETUP must be set. Valid values are 'avalanchego' or 'xsvm'"
6585
exit 255
6686
fi
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
# Directory above this script
6+
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
7+
# Load the constants
8+
source "$AVALANCHE_PATH"/scripts/constants.sh
9+
10+
echo "Building Workload..."
11+
go build -o "$AVALANCHE_PATH/build/antithesis-xsvm-workload" "$AVALANCHE_PATH/tests/antithesis/xsvm/"*.go

tests/antithesis/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ enables discovery and reproduction of anomalous behavior.
88

99
## Package details
1010

11-
| Filename | Purpose |
12-
|:-------------|:----------------------------------------------------------------------------------|
13-
| compose.go | Enables generation of Docker Compose project files for antithesis testing. |
14-
| avalanchego/ | Contains resources supporting antithesis testing of avalanchego's primary chains. |
15-
11+
| Filename | Purpose |
12+
|:-------------|:--------------------------------------------------------------------------------|
13+
| compose.go | Generates Docker Compose project files and volume paths for antithesis testing. |
14+
| initdb.go | Initializes db state for subnet testing |
15+
| avalanchego/ | Defines an antithesis test setup for avalanchego's primary chains. |
16+
| xsvm/ | Defines an antithesis test setup for the xsvm VM. |
1617

1718
## Instrumentation
1819

tests/antithesis/avalanchego/Dockerfile.node

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ RUN mkdir -p /symbols
5656
COPY --from=builder /avalanchego_instrumented/symbols /symbols
5757
COPY --from=builder /opt/antithesis/lib/libvoidstar.so /usr/lib/libvoidstar.so
5858

59+
# Use the same path as the uninstrumented node image for consistency
60+
WORKDIR /avalanchego/build
61+
5962
# Copy the executable into the container
6063
COPY --from=builder /avalanchego_instrumented/customer/build/avalanchego ./avalanchego
6164

tests/antithesis/compose.go

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,34 @@ func newComposeProject(network *tmpnet.Network, nodeImageName string, workloadIm
104104
config.StakingSignerKeyContentKey: signerKey,
105105
}
106106

107-
nodeName := "avalanche"
107+
serviceName := getServiceName(i)
108+
109+
volumes := []types.ServiceVolumeConfig{
110+
{
111+
Type: types.VolumeTypeBind,
112+
Source: fmt.Sprintf("./volumes/%s/logs", serviceName),
113+
Target: "/root/.avalanchego/logs",
114+
},
115+
}
116+
117+
trackSubnets, err := node.Flags.GetStringVal(config.TrackSubnetsKey)
118+
if err != nil {
119+
return nil, err
120+
}
121+
if len(trackSubnets) > 0 {
122+
env[config.TrackSubnetsKey] = trackSubnets
123+
// DB volume will need to initialized with the subnet
124+
volumes = append(volumes, types.ServiceVolumeConfig{
125+
Type: types.VolumeTypeBind,
126+
Source: fmt.Sprintf("./volumes/%s/db", serviceName),
127+
Target: "/root/.avalanchego/db",
128+
})
129+
}
130+
108131
if i == 0 {
109-
nodeName += "-bootstrap-node"
110132
bootstrapIP = address + ":9651"
111133
bootstrapIDs = node.NodeID.String()
112134
} else {
113-
nodeName = fmt.Sprintf("%s-node-%d", nodeName, i+1)
114135
env[config.BootstrapIPsKey] = bootstrapIP
115136
env[config.BootstrapIDsKey] = bootstrapIDs
116137
}
@@ -120,18 +141,12 @@ func newComposeProject(network *tmpnet.Network, nodeImageName string, workloadIm
120141
env = keyMapToEnvVarMap(env)
121142

122143
services[i+1] = types.ServiceConfig{
123-
Name: nodeName,
124-
ContainerName: nodeName,
125-
Hostname: nodeName,
144+
Name: serviceName,
145+
ContainerName: serviceName,
146+
Hostname: serviceName,
126147
Image: nodeImageName,
127-
Volumes: []types.ServiceVolumeConfig{
128-
{
129-
Type: types.VolumeTypeBind,
130-
Source: fmt.Sprintf("./volumes/%s/logs", nodeName),
131-
Target: "/root/.avalanchego/logs",
132-
},
133-
},
134-
Environment: env.ToMappingWithEquals(),
148+
Volumes: volumes,
149+
Environment: env.ToMappingWithEquals(),
135150
Networks: map[string]*types.ServiceNetworkConfig{
136151
networkName: {
137152
Ipv4Address: address,
@@ -146,6 +161,9 @@ func newComposeProject(network *tmpnet.Network, nodeImageName string, workloadIm
146161
workloadEnv := types.Mapping{
147162
"AVAWL_URIS": strings.Join(uris, " "),
148163
}
164+
if len(network.Subnets) > 0 && len(network.Subnets[0].Chains) > 0 {
165+
workloadEnv["AVAWL_CHAIN_ID"] = network.Subnets[0].Chains[0].ChainID.String()
166+
}
149167

150168
workloadName := "workload"
151169
services[0] = types.ServiceConfig{
@@ -191,3 +209,14 @@ func keyMapToEnvVarMap(keyMap types.Mapping) types.Mapping {
191209
}
192210
return envVarMap
193211
}
212+
213+
// Retrieve the service name for a node at the given index. Common to
214+
// GenerateComposeConfig and InitDBVolumes to ensure consistency
215+
// between db volumes configuration and volume paths.
216+
func getServiceName(index int) string {
217+
baseName := "avalanche"
218+
if index == 0 {
219+
return baseName + "-bootstrap-node"
220+
}
221+
return fmt.Sprintf("%s-node-%d", baseName, index+1)
222+
}

tests/antithesis/initdb.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package antithesis
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"time"
13+
14+
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
15+
"github.com/ava-labs/avalanchego/utils/perms"
16+
)
17+
18+
// Initialize the db volumes for a docker-compose configuration. The returned network will be updated with
19+
// subnet and chain IDs and the target path will be configured with volumes for each node containing the
20+
// initialized db state.
21+
func InitDBVolumes(network *tmpnet.Network, avalancheGoPath string, pluginDir string, targetPath string) error {
22+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2)
23+
defer cancel()
24+
if err := tmpnet.StartNewNetwork(
25+
ctx,
26+
os.Stdout,
27+
network,
28+
"",
29+
avalancheGoPath,
30+
pluginDir,
31+
0, // nodeCount is 0 because nodes should already be configured for subnets
32+
); err != nil {
33+
return fmt.Errorf("failed to start network: %w", err)
34+
}
35+
// Since the goal is to initialize the DB, we can stop the network after it has been started successfully
36+
if err := network.Stop(ctx); err != nil {
37+
return fmt.Errorf("failed to stop network: %w", err)
38+
}
39+
40+
absPath, err := filepath.Abs(targetPath)
41+
if err != nil {
42+
return fmt.Errorf("failed to convert target path to absolute path: %w", err)
43+
}
44+
45+
// Create volumes in the target path and copy the db state from the nodes
46+
for i, node := range network.Nodes {
47+
sourcePath := filepath.Join(node.GetDataDir(), "db")
48+
destPath := filepath.Join(absPath, "volumes", getServiceName(i))
49+
if err := os.MkdirAll(destPath, perms.ReadWriteExecute); err != nil {
50+
return fmt.Errorf("failed to create volume path %q: %w", destPath, err)
51+
}
52+
cmd := exec.Command("cp", "-r", sourcePath, destPath)
53+
if err := cmd.Run(); err != nil {
54+
return fmt.Errorf("failed to copy db state from %q to %q: %w", sourcePath, destPath, err)
55+
}
56+
}
57+
58+
return nil
59+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# The version is supplied as a build argument rather than hard-coded
2+
# to minimize the cost of version changes.
3+
ARG GO_VERSION
4+
5+
# NODE_IMAGE needs to identify an existing xsvm node image and should include the tag
6+
ARG NODE_IMAGE
7+
8+
FROM $NODE_IMAGE AS xsvm_node
9+
10+
# ============= Compilation Stage ================
11+
FROM golang:$GO_VERSION-bullseye AS builder
12+
13+
WORKDIR /build
14+
# Copy and download avalanche dependencies using go mod
15+
COPY go.mod .
16+
COPY go.sum .
17+
RUN go mod download
18+
19+
# Copy the code into the container
20+
COPY . .
21+
22+
# IMAGE_TAG should be set to the tag for the images in the generated docker compose file.
23+
ARG IMAGE_TAG=latest
24+
25+
# Copy the avalanchego binary and plugin from the node image
26+
RUN mkdir -p ./build/plugins
27+
COPY --from=xsvm_node /avalanchego/build/avalanchego ./build
28+
COPY --from=xsvm_node /root/.avalanchego/plugins/* ./build/plugins/
29+
30+
# Generate docker compose configuration
31+
RUN AVALANCHEGO_PATH=./build/avalanchego AVALANCHEGO_PLUGIN_DIR=./build/plugins\
32+
TARGET_PATH=./build IMAGE_TAG="$IMAGE_TAG" go run ./tests/antithesis/xsvm/gencomposeconfig
33+
34+
# ============= Cleanup Stage ================
35+
FROM scratch AS execution
36+
37+
# Copy the docker compose file and volumes into the container
38+
COPY --from=builder /build/build/docker-compose.yml /docker-compose.yml
39+
COPY --from=builder /build/build/volumes /volumes
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# The version is supplied as a build argument rather than hard-coded
2+
# to minimize the cost of version changes.
3+
ARG GO_VERSION
4+
5+
# AVALANCHEGO_NODE_IMAGE needs to identify an existing avalanchego node image and should include the tag
6+
ARG AVALANCHEGO_NODE_IMAGE
7+
8+
# Antithesis: Getting the Antithesis golang instrumentation library
9+
FROM docker.io/antithesishq/go-instrumentor AS instrumentor
10+
11+
# ============= Compilation Stage ================
12+
FROM golang:$GO_VERSION-bullseye AS builder
13+
14+
WORKDIR /build
15+
# Copy and download avalanche dependencies using go mod
16+
COPY go.mod .
17+
COPY go.sum .
18+
RUN go mod download
19+
20+
# Copy the code into the container
21+
COPY . .
22+
23+
# Keep the commit hash to easily verify the exact version that is running
24+
RUN git rev-parse HEAD > ./commit_hash.txt
25+
26+
# Copy the instrumentor and supporting files to their correct locations
27+
COPY --from=instrumentor /opt/antithesis /opt/antithesis
28+
COPY --from=instrumentor /opt/antithesis/lib /lib
29+
30+
# Create the destination output directory for the instrumented code
31+
RUN mkdir -p /avalanchego_instrumented
32+
33+
# Park the .git file in a safe location
34+
RUN mkdir -p /opt/tmp/
35+
RUN cp -r .git /opt/tmp/
36+
37+
# Instrument avalanchego
38+
RUN /opt/antithesis/bin/goinstrumentor \
39+
-stderrthreshold=INFO \
40+
-antithesis /opt/antithesis/instrumentation \
41+
. \
42+
/avalanchego_instrumented
43+
44+
WORKDIR /avalanchego_instrumented/customer
45+
RUN go mod download
46+
RUN ln -s /opt/tmp/.git .git
47+
48+
# Build xsvm VM
49+
RUN ./scripts/build_xsvm.sh
50+
51+
# ============= Cleanup Stage ================
52+
FROM $AVALANCHEGO_NODE_IMAGE AS execution
53+
54+
# The commit hash and antithesis dependencies should be part of the base image.
55+
56+
# Copy the executable into the container
57+
RUN mkdir -p /root/.avalanchego/plugins
58+
COPY --from=builder /avalanchego_instrumented/customer/build/xsvm \
59+
/root/.avalanchego/plugins/v3m4wPxaHpvGr8qfMeyK6PRW3idZrPHmYcMTt7oXdK47yurVH
60+
61+
# The node image's entrypoint will be reused.

0 commit comments

Comments
 (0)