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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.git
.github
.gitignore
.dockerignore
Dockerfile
docs
img
tests

# Makefile artifacts
*.o
*.so
*.x64
*.exe
cache-opencl.*
bin
__pycache__/
71 changes: 71 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: docker

on:
push:
branches: [master]
tags: ['v*']
pull_request:
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- name: Build image
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: profanity2:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Smoke test image
run: |
docker run --rm profanity2:ci --help | grep -q '^usage: '
docker run --rm profanity2:ci clinfo --version
# No GPU on the runner, so the entrypoint must refuse to start.
! docker run --rm profanity2:ci --benchmark -z "$(printf 'a%.0s' {1..128})"

- name: Log in to the container registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Collect image tags
if: github.event_name != 'pull_request'
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,format=short
type=raw,value=latest,enable={{is_default_branch}}

- name: Push image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
55 changes: 55 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# syntax=docker/dockerfile:1

# ---------------------------------------------------------------------------
# Build stage
# ---------------------------------------------------------------------------
FROM ubuntu:24.04 AS build

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
opencl-headers \
ocl-icd-opencl-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
COPY Makefile ./
COPY *.cpp *.hpp *.cl ./
RUN make -j"$(nproc)"

# ---------------------------------------------------------------------------
# Runtime stage
# ---------------------------------------------------------------------------
FROM ubuntu:24.04

LABEL org.opencontainers.image.title="profanity2" \
org.opencontainers.image.description="GPU vanity address generator for Ethereum (OpenCL)" \
org.opencontainers.image.source="https://github.com/1inch/profanity2" \
org.opencontainers.image.licenses="MIT"

# ocl-icd-libopencl1 is the ICD loader the binary links against, clinfo is kept
# for diagnosing "no devices found" on rented machines.
RUN apt-get update && apt-get install -y --no-install-recommends \
ocl-icd-libopencl1 \
clinfo \
&& rm -rf /var/lib/apt/lists/*

# The NVIDIA container runtime mounts libnvidia-opencl.so.1 into the container
# but does not register it with the ICD loader, so the vendor file has to be
# part of the image:
# https://github.com/NVIDIA/nvidia-container-toolkit/issues/682
RUN mkdir -p /etc/OpenCL/vendors \
&& echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd

# compute enables the OpenCL driver libraries, utility enables nvidia-smi.
ENV NVIDIA_VISIBLE_DEVICES=all \
NVIDIA_DRIVER_CAPABILITIES=compute,utility

# profanity2 loads keccak.cl/profanity.cl and stores its compiled kernel cache
# relative to the working directory, so the binary has to run from here.
WORKDIR /opt/profanity2
COPY --from=build /src/profanity2.x64 /src/keccak.cl /src/profanity.cl ./
COPY LICENSE ./
COPY docker/entrypoint.sh /usr/local/bin/profanity2-entrypoint
RUN chmod +x /usr/local/bin/profanity2-entrypoint && mkdir -p /workspace

ENTRYPOINT ["/usr/local/bin/profanity2-entrypoint"]
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ or against a vendor OpenCL SDK, plus troubleshooting for the most common errors
(`CL/cl.h: No such file or directory`, `CreateProcess(NULL, uname -s, ...) failed`,
empty device list, WSL2 limitations).

### Docker and rented GPUs

A prebuilt image with the OpenCL runtime is available, so nothing has to be installed on the machine that does the searching:

```bash
docker run --rm --gpus all ghcr.io/1inch/profanity2:latest --matching dead -z $PUBLIC_KEY
```

Since the tool only needs your public key, that machine does not have to be yours. See [docs/VASTAI.md](docs/VASTAI.md) for renting a GPU on vast.ai and running the image there with your own parameters.

# Usage
```
usage: ./profanity2 [OPTIONS]
Expand Down
146 changes: 146 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
#
# Entrypoint of the profanity2 container image.
#
# Arguments are passed straight to profanity2, which is what the "docker
# ENTRYPOINT" launch mode of vast.ai needs: whatever is typed into its
# Arguments field ends up here. Launch modes that replace the entrypoint
# (SSH/Jupyter) only offer environment variables, so the same options can be
# given through PROFANITY_ARGS and PUBLIC_KEY instead.
#
# PROFANITY_PUBLIC_KEY seed public key, added as `-z` unless already given
# PUBLIC_KEY same, but only when it holds 128 hexadecimal
# characters - GPU rental platforms hand out that name
# for their own SSH key
# PROFANITY_ARGS arguments to use when none are passed on the command
# line, split on whitespace (no shell quoting)
# PROFANITY_OUTPUT file the output is copied to, empty value disables it
# [default = /workspace/profanity2.log]
# PROFANITY_TIMEOUT stop after this long, e.g. 30m or 6h (exit code 124)
# PROFANITY_SKIP_GPU_CHECK skip the OpenCL device check before starting

set -euo pipefail

readonly BINARY=/opt/profanity2/profanity2.x64

log() {
printf 'profanity2-entrypoint: %s\n' "$*" >&2
}

count_opencl_platforms() {
local count=

if command -v clinfo >/dev/null 2>&1; then
count="$(clinfo -l 2>/dev/null | grep -c '^Platform #' || true)"

# Older clinfo releases format the compact listing differently, so fall
# back to the summary line of the full report before giving up.
case "$count" in
''|0|*[!0-9]*)
count="$(clinfo 2>/dev/null | awk '/^Number of platforms/ { print $NF; exit }' || true)"
;;
esac
fi

case "$count" in
''|*[!0-9]*) count=0 ;;
esac

printf '%s\n' "$count"
}

no_opencl_platform() {
log "error: no OpenCL platform found inside the container"
log ""
log " installed ICDs: $(echo /etc/OpenCL/vendors/*.icd)"
log " libnvidia-opencl.so.1: $(ldconfig -p | grep -c libnvidia-opencl.so.1) entries in the linker cache"
log ""
log " The container is running without a usable GPU driver. Start it with the"
log " NVIDIA runtime (docker run --gpus all ...) and keep \"compute\" in"
log " NVIDIA_DRIVER_CAPABILITIES. On vast.ai make sure the offer has an NVIDIA"
log " GPU. Run this image with the argument \"clinfo\" for the full diagnosis,"
log " or set PROFANITY_SKIP_GPU_CHECK=1 to start anyway."
}

args=("$@")

# Anything that does not look like a profanity2 option (they all start with a
# dash) is treated as a command to run instead, e.g. `clinfo` or `bash`.
if [ ${#args[@]} -gt 0 ] && [ "${args[0]#-}" = "${args[0]}" ]; then
exec "${args[@]}"
fi

if [ ${#args[@]} -eq 0 ] && [ -n "${PROFANITY_ARGS:-}" ]; then
read -r -a args <<<"$PROFANITY_ARGS"
fi

if [ ${#args[@]} -eq 0 ]; then
log "no arguments given, printing help"
log "pass the scoring mode as container arguments or in PROFANITY_ARGS"
args=(--help)
fi

wants_help=0
has_public_key=0
for arg in "${args[@]}"; do
case "$arg" in
-h|--help) wants_help=1 ;;
-z|--publicKey) has_public_key=1 ;;
esac
done

public_key="${PROFANITY_PUBLIC_KEY:-}"
if [ -z "$public_key" ] && [ -n "${PUBLIC_KEY:-}" ]; then
if printf '%s' "$PUBLIC_KEY" | grep -qE '^[0-9a-fA-F]{128}$'; then
public_key="$PUBLIC_KEY"
else
log "warning: ignoring PUBLIC_KEY, it does not hold 128 hexadecimal characters"
log " rental platforms set that name to their own SSH key; use"
log " PROFANITY_PUBLIC_KEY, or pass -z, to be unambiguous"
fi
fi

if [ "$has_public_key" -eq 0 ] && [ -n "$public_key" ]; then
args+=(-z "$public_key")
fi

if [ "$wants_help" -eq 0 ] && [ -z "${PROFANITY_SKIP_GPU_CHECK:-}" ]; then
platforms="$(count_opencl_platforms)"

if [ "$platforms" -eq 0 ]; then
no_opencl_platform
exit 1
fi

log "OpenCL platforms found: $platforms"
fi

cmd=("$BINARY" "${args[@]}")
if [ -n "${PROFANITY_TIMEOUT:-}" ]; then
log "run time limited to $PROFANITY_TIMEOUT"
cmd=(timeout "$PROFANITY_TIMEOUT" "${cmd[@]}")
fi

output="${PROFANITY_OUTPUT-/workspace/profanity2.log}"
if [ "$wants_help" -eq 1 ]; then
output=
fi

if [ -n "$output" ]; then
if mkdir -p "$(dirname "$output")" 2>/dev/null && touch "$output" 2>/dev/null; then
log "results are also appended to $output"
else
log "warning: $output is not writable, results only go to the container log"
output=
fi
fi

log "running: profanity2.x64 ${args[*]}"

if [ -n "$output" ]; then
# Only stdout is copied: the hashrate counter is printed to stderr with
# carriage returns and would fill the file with terminal escapes.
"${cmd[@]}" | tee -a "$output"
else
exec "${cmd[@]}"
fi
Loading
Loading