Skip to content

Commit e411346

Browse files
committed
[antithesis] Add test setup for xsvm
1 parent 099db46 commit e411346

24 files changed

+772
-129
lines changed

.github/workflows/ci.yml

Lines changed: 12 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_avalanchego_images:
269+
name: Build Antithesis avalanchego images
270270
runs-on: ubuntu-latest
271271
steps:
272272
- uses: actions/checkout@v4
@@ -275,6 +275,16 @@ jobs:
275275
run: bash -x scripts/tests.build_antithesis_images.sh
276276
env:
277277
TEST_SETUP: avalanchego
278+
test_build_antithesis_xsvm_images:
279+
name: Build Antithesis xsvm images
280+
runs-on: ubuntu-latest
281+
steps:
282+
- uses: actions/checkout@v4
283+
- name: Check image build for xsvm test setup
284+
shell: bash
285+
run: bash -x scripts/tests.build_antithesis_images.sh
286+
env:
287+
TEST_SETUP: xsvm
278288
govulncheck:
279289
runs-on: ubuntu-latest
280290
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

scripts/tests.build_antithesis_images.sh

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ set -euo pipefail
1010
# 4. Stopping the workload and its target network
1111
#
1212

13+
# e.g.,
14+
# TEST_SETUP=avalanchego ./scripts/tests.build_antithesis_images.sh # Test build of images for avalanchego test setup
15+
# DEBUG=1 TEST_SETUP=avalanchego ./scripts/tests.build_antithesis_images.sh # Do not detach the compose session to see output
16+
1317
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
1418

1519
# Discover the default tag that will be used for the image
@@ -27,6 +31,8 @@ docker create --name "${CONTAINER_NAME}" "${IMAGE_NAME}:${TAG}" /bin/true
2731

2832
# Create a temporary directory to write the compose configuration to
2933
TMPDIR="$(mktemp -d)"
34+
echo "using temporary directory ${TMPDIR} as the docker-compose path"
35+
3036
COMPOSE_FILE="${TMPDIR}/docker-compose.yml"
3137
COMPOSE_CMD="docker-compose -f ${COMPOSE_FILE}"
3238

@@ -36,8 +42,10 @@ function cleanup {
3642
docker rm "${CONTAINER_NAME}"
3743
echo "stopping and removing the docker compose project"
3844
${COMPOSE_CMD} down --volumes
39-
echo "removing temporary dir"
40-
rm -rf "${TMPDIR}"
45+
if [[ -z "${DEBUG:-}" ]]; then
46+
echo "removing temporary dir"
47+
rm -rf "${TMPDIR}"
48+
fi
4149
}
4250
trap cleanup EXIT
4351

@@ -47,9 +55,12 @@ docker cp "${CONTAINER_NAME}":/docker-compose.yml "${COMPOSE_FILE}"
4755
# Copy the volume paths out of the container
4856
docker cp "${CONTAINER_NAME}":/volumes "${TMPDIR}/"
4957

50-
# Run the docker compose project for one minute without error
58+
# Run the docker compose project for 2 minutes without error. 2
59+
# minutes is suggested because the way docker-compose brings all
60+
# containers up simultaneously and the lack of coordination results in
61+
# exponential back-off on the nodes trying to bootstrap.
5162
${COMPOSE_CMD} up -d
52-
sleep 60
63+
sleep 120
5364
if ${COMPOSE_CMD} ps -q | xargs docker inspect -f '{{ .State.Status }}' | grep -v 'running'; then
5465
echo "An error occurred."
5566
exit 255

tests/antithesis/README.md

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ 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+
| config.go | Defines common flags for the workload binary. |
15+
| init_db.go | Initializes db state for subnet testing. |
16+
| node_health.go | Helper to check node health. |
17+
| avalanchego/ | Defines an antithesis test setup for avalanchego's primary chains. |
18+
| xsvm/ | Defines an antithesis test setup for the xsvm VM. |
1619

1720
## Instrumentation
1821

@@ -45,3 +48,55 @@ a test setup:
4548
In addition, github workflows are suggested to ensure
4649
`scripts/tests.build_antithesis_images.sh` runs against PRs and
4750
`scripts/build_antithesis_images.sh` runs against pushes.
51+
52+
## Troubleshooting a test setup
53+
54+
### Running a workload directly
55+
56+
The workload of the 'avalanchego' test setup can be invoked against an
57+
arbitrary network:
58+
59+
```bash
60+
$ AVAWL_URIS="http://10.0.20.3:9650 http://10.0.20.4:9650" go run ./tests/antithesis/avalanchego
61+
```
62+
63+
The workload of a subnet test setup like 'xsvm' additionally requires
64+
a network with a configured chain for the xsvm VM and the ID for that
65+
chain needs to be provided to the workload:
66+
67+
```bash
68+
$ AVAWL_URIS=... CHAIN_IDS="2S9ypz...AzMj9" go run ./tests/antithesis/avalanchego
69+
```
70+
71+
### Running a workload with docker-compose
72+
73+
Running the test script for a given test setup with the DEBUG flag
74+
will avoid cleaning up the the temporary directory where the compose
75+
setup is written to. This will allow manual invocation of compose to
76+
see the log output of the workload.
77+
78+
```bash
79+
$ DEBUG=1 ./scripts/tests.build_antithesis_images.sh
80+
```
81+
82+
After the test script has finished building the images, the name of
83+
the temporary directory will appear in the output of the script:
84+
85+
```
86+
...
87+
using temporary directory /tmp/tmp.E6eHdDr4ln as the docker-compose path"
88+
...
89+
```
90+
91+
Running compose from the temporary directory will ensure the workload
92+
logging is output is stdout:
93+
94+
```bash
95+
$ cd [temporary directory]
96+
97+
# Start the compose project
98+
$ docker-compose up
99+
100+
# Stop the compose project
101+
$ docker-compose down --volumes
102+
```

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/avalanchego/main.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import (
1111
"os"
1212
"time"
1313

14-
"github.com/ava-labs/avalanchego/api/health"
1514
"github.com/ava-labs/avalanchego/database"
1615
"github.com/ava-labs/avalanchego/genesis"
1716
"github.com/ava-labs/avalanchego/ids"
1817
"github.com/ava-labs/avalanchego/snow/choices"
18+
"github.com/ava-labs/avalanchego/tests/antithesis"
1919
"github.com/ava-labs/avalanchego/utils/constants"
2020
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
2121
"github.com/ava-labs/avalanchego/utils/set"
@@ -38,13 +38,13 @@ import (
3838
const NumKeys = 5
3939

4040
func main() {
41-
c, err := NewConfig(os.Args)
41+
c, err := antithesis.NewConfig(os.Args)
4242
if err != nil {
4343
log.Fatalf("invalid config: %s", err)
4444
}
4545

4646
ctx := context.Background()
47-
awaitHealthyNodes(ctx, c.URIs)
47+
antithesis.AwaitHealthyNodes(ctx, c.URIs)
4848

4949
kc := secp256k1fx.NewKeychain(genesis.EWOQKey)
5050
walletSyncStartTime := time.Now()
@@ -133,39 +133,6 @@ func main() {
133133
genesisWorkload.run(ctx)
134134
}
135135

136-
func awaitHealthyNodes(ctx context.Context, uris []string) {
137-
for _, uri := range uris {
138-
awaitHealthyNode(ctx, uri)
139-
}
140-
log.Println("all nodes reported healthy")
141-
}
142-
143-
func awaitHealthyNode(ctx context.Context, uri string) {
144-
client := health.NewClient(uri)
145-
ticker := time.NewTicker(100 * time.Millisecond)
146-
defer ticker.Stop()
147-
148-
log.Printf("awaiting node health at %s", uri)
149-
for {
150-
res, err := client.Health(ctx, nil)
151-
switch {
152-
case err != nil:
153-
log.Printf("node couldn't be reached at %s", uri)
154-
case res.Healthy:
155-
log.Printf("node reported healthy at %s", uri)
156-
return
157-
default:
158-
log.Printf("node reported unhealthy at %s", uri)
159-
}
160-
161-
select {
162-
case <-ticker.C:
163-
case <-ctx.Done():
164-
log.Printf("node health check cancelled at %s", uri)
165-
}
166-
}
167-
}
168-
169136
type workload struct {
170137
id int
171138
wallet primary.Wallet

0 commit comments

Comments
 (0)