diff --git a/Dockerfile b/Dockerfile index 14471980..ecd2d5a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,73 @@ -FROM golang:1.23.6 AS builder +# --------------------------------------------------------------------------- +# Stage 1: Fetch configs +# --------------------------------------------------------------------------- +FROM --platform=$BUILDPLATFORM golang:1.23.6-bookworm AS config-fetcher - -WORKDIR /multiversx +WORKDIR /src COPY . . -RUN go mod tidy +WORKDIR /src/cmd/chainsimulator +RUN go build -o chainsimulator \ + && ./chainsimulator --fetch-configs-and-close -WORKDIR /multiversx/cmd/chainsimulator +# --------------------------------------------------------------------------- +# Stage 2: Build the binary + extract Wasmer libs +# --------------------------------------------------------------------------- +FROM golang:1.23.6-bookworm AS builder -RUN go build -o chainsimulator +WORKDIR /src +COPY . . -RUN mkdir -p /lib_amd64 /lib_arm64 +# Download all modules +RUN go mod download -RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-v | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer/libwasmer_linux_amd64.so /lib_amd64/ -RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-go | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer2/libvmexeccapi.so /lib_amd64/ +WORKDIR /src/cmd/chainsimulator -RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-v | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer/libwasmer_linux_arm64_shim.so /lib_arm64/ -RUN cp /go/pkg/mod/github.com/multiversx/$(cat /multiversx/go.sum | grep mx-chain-vm-go | sort -n | tail -n -1 | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer2/libvmexeccapi_arm.so /lib_arm64/ +# Build with optimizations: strip debug symbols, smaller binary +# CGO_ENABLED=1 is implicit and required by Wasmer bindings. +RUN go build \ + -ldflags="-s -w" \ + -trimpath \ + -o /out/chainsimulator +# --------------------------------------------------------------------------- +# Extract architecture-specific Wasmer shared libraries +# --------------------------------------------------------------------------- +RUN mkdir -p /out/lib -FROM ubuntu:22.04 -ARG TARGETARCH -RUN apt-get update && apt-get install -y git curl +RUN cp /go/pkg/mod/github.com/multiversx/$(cat /src/go.sum \ + | grep mx-chain-vm-v | sort -n | tail -n -1 \ + | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer/libwasmer_linux_$(dpkg --print-architecture | sed 's/arm64/arm64_shim/').so \ + /out/lib/ 2>/dev/null || true -COPY --from=builder /multiversx/cmd/chainsimulator /multiversx +RUN cp /go/pkg/mod/github.com/multiversx/$(cat /src/go.sum \ + | grep mx-chain-vm-go | sort -n | tail -n -1 \ + | awk -F '/' '{print$3}' | sed 's/ /@/g')/wasmer2/libvmexeccapi$(dpkg --print-architecture | sed 's/amd64//;s/arm64/_arm/').so \ + /out/lib/ 2>/dev/null || true -EXPOSE 8085 +# --------------------------------------------------------------------------- +# Stage 3: Minimal runtime image (distroless) +# --------------------------------------------------------------------------- +FROM gcr.io/distroless/cc-debian13:nonroot -WORKDIR /multiversx +LABEL org.opencontainers.image.title="mx-chain-simulator-go" \ + org.opencontainers.image.description="MultiversX Chain Simulator" \ + org.opencontainers.image.source="https://github.com/multiversx/mx-chain-simulator-go" \ + org.opencontainers.image.licenses="GPL-3.0" -# Copy architecture-specific files -COPY --from=builder "/lib_${TARGETARCH}/*" "/lib/" +# Copy binary +COPY --from=builder --chown=nonroot:nonroot /out/chainsimulator /app/chainsimulator -CMD ["/bin/bash"] +# Copy pre-fetched configs +COPY --from=config-fetcher --chown=nonroot:nonroot /src/cmd/chainsimulator/config /app/config -ENTRYPOINT ["./chainsimulator"] +# Copy Wasmer libs +COPY --from=builder /out/lib/ /lib/ +WORKDIR /app +EXPOSE 8085 + +# Run as non-root for security (UID 65532 is "nonroot" in distroless) +USER nonroot:nonroot +ENTRYPOINT ["./chainsimulator"] diff --git a/Makefile b/Makefile index 9e3e34e3..633bb73e 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,123 @@ -CHAIN_SIMULATOR_IMAGE_NAME=chainsimulator -CHAIN_SIMULATOR_IMAGE_TAG=latest -DOCKER_FILE=Dockerfile -IMAGE_NAME=simulator_image +IMAGE_NAME ?= chainsimulator +IMAGE_TAG ?= latest +REGISTRY ?= multiversx +DOCKER_FILE ?= Dockerfile +PLATFORMS ?= linux/amd64,linux/arm64 +CONTAINER_NAME ?= simulator_instance +FULL_IMAGE = $(REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) +## Build the Go binary locally +.PHONY: build +build: + cd cmd/chainsimulator && go build -ldflags="-s -w" -trimpath -o chainsimulator + +## Fetch configs locally +.PHONY: fetch-configs +fetch-configs: build + cd cmd/chainsimulator && ./chainsimulator --fetch-configs-and-close + +## Build Docker image (single arch, current platform) +.PHONY: docker-build docker-build: - docker build \ - -t ${CHAIN_SIMULATOR_IMAGE_NAME}:${CHAIN_SIMULATOR_IMAGE_TAG} \ - -f ${DOCKER_FILE} \ - . + DOCKER_BUILDKIT=1 docker build \ + -t $(FULL_IMAGE) \ + -f $(DOCKER_FILE) \ + . -run-faucet-test: - $(MAKE) docker-build - docker run -d --name "${IMAGE_NAME}" -p 8085:8085 ${CHAIN_SIMULATOR_IMAGE_NAME}:${CHAIN_SIMULATOR_IMAGE_TAG} +## Build multi-arch image and push to registry (requires login) +## This is the correct way to produce a multi-platform manifest. +.PHONY: docker-build-push +docker-build-push: + docker buildx build \ + --platform $(PLATFORMS) \ + -t $(FULL_IMAGE) \ + -f $(DOCKER_FILE) \ + --push \ + . + +## Register QEMU for cross-platform builds (run once per boot) +.PHONY: qemu-setup +qemu-setup: + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + +## Run the simulator container +.PHONY: docker-run +docker-run: + docker run -d \ + --name "$(CONTAINER_NAME)" \ + --read-only \ + --tmpfs /tmp \ + -p 8085:8085 \ + $(FULL_IMAGE) + +## Stop and remove the simulator container +.PHONY: docker-stop +docker-stop: + docker stop "$(CONTAINER_NAME)" 2>/dev/null || true + docker rm "$(CONTAINER_NAME)" 2>/dev/null || true + +## Run faucet example test +.PHONY: run-faucet-test +run-faucet-test: docker-build + docker run -d --name "$(CONTAINER_NAME)" -p 8085:8085 $(FULL_IMAGE) sleep 2s cd examples/faucet && /bin/bash faucet.sh - docker stop "${IMAGE_NAME}" - docker rm ${IMAGE_NAME} 2> /dev/null + $(MAKE) docker-stop +## Run all examples +.PHONY: run-examples run-examples: printf '%s\n' '{ File = "enableEpochs.toml", Path = "EnableEpochs.StakeLimitsEnableEpoch", Value = 1000000 },' > temp.txt sed -i '4r temp.txt' cmd/chainsimulator/config/nodeOverrideDefault.toml rm temp.txt - $(MAKE) docker-build - docker run -d --name "${IMAGE_NAME}" -p 8085:8085 ${CHAIN_SIMULATOR_IMAGE_NAME}:${CHAIN_SIMULATOR_IMAGE_TAG} + docker run -d --name "$(CONTAINER_NAME)" -p 8085:8085 $(FULL_IMAGE) cd scripts/run-examples && /bin/bash install-python-deps.sh && /bin/bash script.sh - docker stop "${IMAGE_NAME}" - docker rm ${IMAGE_NAME} + $(MAKE) docker-stop +## Install golint if not already present +.PHONY: lint-install lint-install: ifeq (,$(wildcard test -f bin/golangci-lint)) @echo "Installing golint" curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s endif +## Run golint on the codebase +.PHONY: run-lint run-lint: @echo "Running golint" bin/golangci-lint run --max-issues-per-linter 0 --max-same-issues 0 --timeout=2m +## Run lint installation and then the linter +.PHONY: lint lint: lint-install run-lint + +## Show image details +.PHONY: docker-info +docker-info: + @echo "Image: $(FULL_IMAGE)" + @echo "Dockerfile: $(DOCKER_FILE)" + @echo "Platforms: $(PLATFORMS)" + @docker images $(FULL_IMAGE) --format "Size: {{.Size}}" + +## Run security scan with trivy (if installed) +.PHONY: docker-scan +docker-scan: + trivy image --severity HIGH,CRITICAL $(FULL_IMAGE) + +## Show available targets +.PHONY: help +help: + @echo "Available targets:" + @echo " build Build Go binary locally" + @echo " fetch-configs Fetch configs from GitHub repos" + @echo " docker-build Build Docker image (current platform, docker build)" + @echo " docker-build-push Build & push multi-arch image to registry" + @echo " qemu-setup Register QEMU for cross-arch builds" + @echo " docker-run Run the simulator container (read-only)" + @echo " docker-stop Stop and remove container" + @echo " docker-info Show image details" + @echo " docker-scan Trivy security scan" + @echo " lint Run linter" + @echo " help Show this help" diff --git a/README.md b/README.md index 5d5eb73d..19e894bf 100644 --- a/README.md +++ b/README.md @@ -420,8 +420,10 @@ This endpoint resets (clears) an internal cache used by the `/validator/statisti Before proceeding, ensure you have the following prerequisites: -- Go programming environment set up. +- Go 1.23.x programming environment set up. - Git installed. +- Docker with BuildKit support (for container builds). +- Docker Buildx (for multi-arch builds). ## Install @@ -431,6 +433,11 @@ Using the `cmd/chainsimulator` package as root, execute the following commands: - install go dependencies: `go install` - build executable: `go build -o chainsimulator` +Alternatively, use the Makefile: +``` +make build +``` + Note: go version 1.23.* should be used to build the executable. @@ -513,14 +520,38 @@ INFO [2024-04-18 10:48:47.231] chain simulator's is accessible through the URL ``` -### Build docker image +### Docker + +The Docker image uses a multi-stage build with a [distroless](https://github.com/GoogleContainerTools/distroless) runtime image (`gcr.io/distroless/cc-debian13`). The container runs as a non-root user (UID 65532) for security. Configs are pre-fetched at build time so `git` and `curl` are not required at runtime. + +#### Build (single arch, current platform) +``` +make docker-build +``` + +#### Build & push multi-arch (amd64 + arm64) +``` +make docker-build-push +``` + +For cross-platform builds from an amd64 host, register QEMU first (once per boot): +``` +make qemu-setup +``` + +#### Run +``` +make docker-run +``` + +The container is started in read-only mode with a tmpfs on `/tmp`. To pass extra flags: ``` -DOCKER_BUILDKIT=1 docker build -t chainsimulator:latest . +docker run -p 8085:8085 multiversx/chainsimulator:latest --log-level *:DEBUG ``` -### Run with docker +#### Available Makefile targets ``` -docker run -p 8085:8085 chainsimulator:latest --log-level *:DEBUG +make help ``` ### Enable `HostDriver`