diff --git a/Makefile b/Makefile index 67d9773..a871ad8 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,34 @@ -# List of binaries hacknet needs to function properly -COMMANDS := sudo tar zstd getent stress -$(foreach bin,$(COMMANDS),\ - $(if $(shell command -v $(bin) 2> /dev/null),$(info),$(error Missing required dependency: `$(bin)`))) +# OS Detection and Cross-Platform Support +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + OS := macos + export UID := $(shell id -u) + export GID := $(shell id -g) + # macOS: use sysctl for CPU count + STRESS_CORES ?= $(shell sysctl -n hw.ncpu) + # List of binaries hacknet needs to function properly + COMMANDS := sudo tar zstd stress + # macOS Docker Desktop maps host UID into its VM; running BSD tar via sudo + # restores archive ownership and leaves bind-mount sources unwritable. + # Extract as the current user so everything lands user-owned. + TAR_EXTRACT := tar -xf +else + OS := linux + # Linux: use getent + export UID := $(shell getent passwd $$(whoami) | cut -d":" -f 3) + export GID := $(shell getent passwd $$(whoami) | cut -d":" -f 4) + # Linux: use /proc/cpuinfo for CPU count + STRESS_CORES ?= $(shell cat /proc/cpuinfo | grep processor | wc -l) + # List of binaries hacknet needs to function properly + COMMANDS := sudo tar zstd getent stress + TAR_EXTRACT := sudo tar --same-owner -xf +endif + +# Verify required dependencies exist +$(foreach bin,$(COMMANDS),$(if $(shell command -v $(bin) 2> /dev/null),$(info),$(error Missing required dependency: `$(bin)`))) TARGET := $(firstword $(MAKECMDGOALS)) PARAMS := $(filter-out $(TARGET),$(MAKECMDGOALS)) + # Hardcode the chainstate dir if we're booting from genesis ifeq ($(TARGET),up-genesis) export CHAINSTATE_DIR := $(PWD)/docker/chainstate/genesis @@ -12,9 +37,6 @@ ifeq ($(TARGET),genesis) export CHAINSTATE_DIR := $(PWD)/docker/chainstate/genesis endif -# UID and GID are not currently used, but may be later to ensure consistent file permissions -export UID := $(shell getent passwd $$(whoami) | cut -d":" -f 3) -export GID := $(shell getent passwd $$(whoami) | cut -d":" -f 4) EPOCH := $(shell date +%s) PWD = $(shell pwd) # Set a unique project name (used for checking if the network is running) @@ -26,22 +48,15 @@ SERVICES := $(shell CHAINSTATE_DIR="" docker compose -f docker/docker-compose.ym # Pauses the bitcoin miner script. Default is set to nearly 1 trillion blocks PAUSE_HEIGHT ?= 999999999999 # Used for the stress testing target. modifies how much cpu to consume for how long -STRESS_CORES ?= $(shell cat /proc/cpuinfo | grep processor | wc -l) STRESS_TIMEOUT ?= 120 # Create the chainstate dir and extract an archive to it when the "up" target is used -$(CHAINSTATE_DIR): /usr/bin/tar /usr/bin/zstd - @if [ ! -d "$(CHAINSTATE_DIR)" ]; then \ - mkdir -p $(CHAINSTATE_DIR) - @if [ "$(TARGET)" = "up" ]; then - if [ -f "$(CHAINSTATE_ARCHIVE)" ]; then - sudo tar --same-owner -xf $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || exit 1 - else - @echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting" - rm -rf $(CHAINSTATE_DIR) - exit 1 - fi - fi +$(CHAINSTATE_DIR): + @if [ ! -d "$(CHAINSTATE_DIR)" ]; then mkdir -p $(CHAINSTATE_DIR) && \ + if [ "$(TARGET)" = "up" ]; then \ + [ -f "$(CHAINSTATE_ARCHIVE)" ] && $(TAR_EXTRACT) $(CHAINSTATE_ARCHIVE) -C $(CHAINSTATE_DIR) || \ + { echo "Chainstate archive ($(CHAINSTATE_ARCHIVE)) not found. Exiting"; rm -rf $(CHAINSTATE_DIR); exit 1; }; \ + fi; \ fi # Build the images with a cache if present @@ -70,33 +85,26 @@ check-not-running: # If the network is not running, we need to exit (ex: trying to restart a container) check-running: - @if test ! `docker compose ls --filter name=$(PROJECT) -q`; then \ - echo "Network not running. exiting"; \ - exit 1; \ - fi + @test `docker compose ls --filter name=$(PROJECT) -q` || { echo "Network not running. exiting"; exit 1; } -# For targets that need an arg, check that *something* is provided. it not, exit +# For targets that need an arg, check that *something* is provided. if not, exit check-params: | check-running - @if [ ! "$(PARAMS)" ]; then \ - echo "No service defined. Exiting"; \ - exit 1; \ - fi + @[ "$(PARAMS)" ] || { echo "No service defined. Exiting"; exit 1; } # Boot the network from a local chainstate archive up: check-not-running | build $(CHAINSTATE_DIR) @echo "Starting $(PROJECT) network from chainstate archive" + @echo " OS: $(OS)" @echo " Chainstate Dir: $(CHAINSTATE_DIR)" @echo " Chainstate Archive: $(CHAINSTATE_ARCHIVE)" echo "$(CHAINSTATE_DIR)" > .current-chainstate-dir docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) up -d # Boot the network from genesis -genesis: check-not-running | build $(CHAINSTATE_DIR) /usr/bin/sudo +genesis: check-not-running | build $(CHAINSTATE_DIR) @echo "Starting $(PROJECT) network from genesis" - @if [ -d "$(CHAINSTATE_DIR)" ]; then \ - echo " Removing existing genesis chainstate dir: $(CHAINSTATE_DIR)"; \ - sudo rm -rf $(CHAINSTATE_DIR); \ - fi + @echo " OS: $(OS)" + @[ -d "$(CHAINSTATE_DIR)" ] && { echo " Removing existing genesis chainstate dir: $(CHAINSTATE_DIR)"; sudo rm -rf $(CHAINSTATE_DIR); } @echo " Chainstate Dir: $(CHAINSTATE_DIR)" mkdir -p "$(CHAINSTATE_DIR)" echo "$(CHAINSTATE_DIR)" > .current-chainstate-dir @@ -117,9 +125,7 @@ down-prom: down: backup-logs current-chainstate-dir @echo "Shutting down $(PROJECT) network" docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) down - @if [ -f .current-chainstate-dir ]; then \ - rm -f .current-chainstate-dir - fi + @[ -f .current-chainstate-dir ] && rm -f .current-chainstate-dir # Secondary name to bring down the genesis network down-genesis: down @@ -128,9 +134,7 @@ down-genesis: down down-force: @echo "Force Shutting down $(PROJECT) network" docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) down - @if [ -f .current-chainstate-dir ]; then \ - rm -f .current-chainstate-dir - fi + @[ -f .current-chainstate-dir ] && rm -f .current-chainstate-dir # Stream specified service logs to STDOUT. Does not validate if PARAMS is supplied log: current-chainstate-dir @@ -142,31 +146,21 @@ log-all: current-chainstate-dir docker compose -f docker/docker-compose.yml --profile default -p $(PROJECT) logs -t -f # Backup all service logs to $ACTIVE_CHAINSTATE_DIR/logs/.log -backup-logs: current-chainstate-dir /usr/bin/sudo +backup-logs: current-chainstate-dir @if [ -f .current-chainstate-dir ]; then \ - $(eval ACTIVE_CHAINSTATE_DIR=$(shell cat .current-chainstate-dir)) - if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)" ]; then \ - echo "Chainstate Dir ($(ACTIVE_CHAINSTATE_DIR)) not found";\ - exit 1; \ - fi; \ - if [ ! -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ - mkdir -p $(ACTIVE_CHAINSTATE_DIR)/logs;\ - fi; \ + $(eval ACTIVE_CHAINSTATE_DIR=$(shell cat .current-chainstate-dir)) \ + [ -d "$(ACTIVE_CHAINSTATE_DIR)" ] || { echo "Chainstate Dir ($(ACTIVE_CHAINSTATE_DIR)) not found"; exit 1; }; \ + mkdir -p $(ACTIVE_CHAINSTATE_DIR)/logs; \ echo "Backing up logs to $(ACTIVE_CHAINSTATE_DIR)/logs"; \ - for service in $(SERVICES); do \ - docker logs -t $$service > $(ACTIVE_CHAINSTATE_DIR)/logs/$$service.log 2>&1; \ - done; \ + for service in $(SERVICES); do docker logs -t $$service > $(ACTIVE_CHAINSTATE_DIR)/logs/$$service.log 2>&1; done; \ fi # Replace the existing chainstate archive. Will be used with target `up` snapshot: current-chainstate-dir down @echo "Creating $(PROJECT) chainstate snapshot from $(ACTIVE_CHAINSTATE_DIR)" - @if [ -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ]; then \ - rm -rf $(ACTIVE_CHAINSTATE_DIR)/logs; \ - fi + @[ -d "$(ACTIVE_CHAINSTATE_DIR)/logs" ] && rm -rf $(ACTIVE_CHAINSTATE_DIR)/logs @echo "Creating snapshot: $(CHAINSTATE_ARCHIVE)" - @echo "cd $(ACTIVE_CHAINSTATE_DIR); sudo tar --zstd -cf $(CHAINSTATE_ARCHIVE) *; cd $(PWD)" - cd $(ACTIVE_CHAINSTATE_DIR); sudo tar --zstd -cf $(CHAINSTATE_ARCHIVE) *; cd $(PWD) + (cd $(ACTIVE_CHAINSTATE_DIR) && sudo tar --zstd -cf $(CHAINSTATE_ARCHIVE) *) # Pause all services in the network (netork is down, but recoverably with target 'unpause') pause: @@ -220,5 +214,5 @@ monitor: clean: down-force sudo rm -rf ./docker/chainstate/* -.PHONY: build build-no-cache current-chainstate-dir check-not-running check-running check-params up genesis up-genesis down down-genesis down-force log log-all backup-logs snapshot pause unpause stop start restart stress test monitor clean +.PHONY: build build-no-cache current-chainstate-dir check-not-running check-running check-params up genesis up-genesis down down-genesis down-force log log-all backup-logs snapshot pause unpause stop start restart stress test monitor clean up-prom down-prom .ONESHELL: all-in-one-shell diff --git a/README.md b/README.md index 034fec2..8b8dc93 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,216 @@ -# Hacknet +# Hacknet + - Configured for 3 stacks miners and signers - bind-mounts a local filesystem for data persistence - Uses a chainstate archive to boot the network quickly - Configurable signing weight across the 3 signers +- Designed to run on Linux (tested on Debian-based) or MacOS ## Quickstart ### Start network using a chainstate archive -*Note*: default chainstate archive at `./docker/chainstate.tar.zstd` will be used unless overridden by `CHAINSTATE_ARCHIVE` env var. + +_Note_: default chainstate archive at `./docker/chainstate.tar.zstd` will be used unless overridden by `CHAINSTATE_ARCHIVE` env var. Creates a dynamic chainstate folder at `./docker/chainstate/$(date +%s)` from a chainstate archive + ```sh make up ``` + To override the archive used to restore the network: + ```sh CHAINSTATE_ARCHIVE=./docker/chainsate_new.tar.zstd make up ``` + To override the chainstate dir and resume a stopped network: -*Note*: will not work for the `genesis` chainstate dir and absolute path is required +_Note_: will not work for the `genesis` chainstate dir and absolute path is required + ```sh CHAINSTATE_DIR=$(pwd)/docker/chainsate/ make up ``` ### Start network from genesis + Creates a static chainstate folder at `./docker/chainstate/genesis` + ```sh make genesis ``` ### Stop the network + ```sh make down ``` ## Full list of options + ### Logs + `docker logs -f ` will work, along with some defined Makefile targets #### Store logs from all services under the current chainstate folder + ```sh make backup-logs ``` #### Stream logs from all services + ```sh make log-all ``` + #### Stream single service logs + ```sh make log stacks-signer-1 -- -f ``` #### Log from a single service -*note* this will not follow the logs + +_note_ this will not follow the logs + ```sh make log stacks-signer-1 ``` ### Container management + #### Pause/Unpause service + To pause all services on the network + ```sh make pause ``` + To resume the network + ```sh make unpause ``` #### Restart a service + Used to simulate a node dropping off of the network + ```sh make restart ``` + ex: + ```sh make restart stacks-miner-3 61 ``` #### Stop/Start service (kill) + Stop a single service + ```sh make stop ``` + Restart the stopped service + ```sh make start ``` #### Force stop the hacknet network + If the network is in a "stuck" state where the Makefile targets are not stopping the services (i.e. the `.current-chainstate-dir` file was removed while network was running), `down-force` may be used to force stop the network. ```sh make down-force ``` -Additionally, `clean` target will call `down-force` *and also* delete any chainstates on disk in `./docker/chainstate/*` +Additionally, `clean` target will call `down-force` _and also_ delete any chainstates on disk in `./docker/chainstate/*` + ```sh make clean ``` ### Additional Features + #### Stress the CPU + To simulate CPU load. Can be modified with: + - `STRESS_CORES` to target how many worker threads (default will use all cores) - `STRESS_TIMEOUT` set a timeout (default of 120s) + ```sh make stress ``` + ```sh STRESS_CORES=10 STRESS_TIMEOUT=60 make stress ``` #### Monitor chain heights + Run a script outputting the current chain heights of each miner + ```sh make monitor ``` #### Create a chainstate snapshot + - Setting the env var `PAUSE_HEIGHT` is optional to pause the chain at a specific height, else a default of Bitcoin block `999999999999` is used. - Setting the env var `MINE_INTERVAL_EPOCH3` is recommended to reach the `PAUSE_HEIGHT` more quickly to create the snapshot - Optionally, the `CHAINSTATE_ARCHIVE` env var may be set to store the archive in a non-default location/name -**This operation will work with either the `up` or `genesis` targets** + **This operation will work with either the `up` or `genesis` targets** + ```sh make genesis ``` + or with env vars set: + ```sh MINE_INTERVAL_EPOCH3=10 PAUSE_HEIGHT=240 make genesis ``` + Followed by waiting until the Bitcoin miner reaches the specified height (ex: `docker logs -f bitcoin-miner`) Once the Bitcoin miner has reached the specified height: + ```sh make snapshot ``` + This will first bring down the network, then replace the existing `./docker/chainstate.tar.zstd` archive file used with the `up` Makefile target. -To create the chainstate archive in a non-default location/name *File path must be absolute*: +To create the chainstate archive in a non-default location/name _File path must be absolute_: + ```sh CHAINSTATE_ARCHIVE=$(pwd)/docker/chainstate_new.tar.zstd make snapshot ``` **Note**: `CHAINSTATE_ARCHIVE` must be defined to use with `make up` to use a non-default snapshot. ex: + ```sh CHAINSTATE_ARCHIVE=./docker/chainstate_new.tar.zstd make up ``` #### Prometheus sidecar + ##### Run prometheus and cadvisor + Runs a prometheus container to record data collected by `cadvisor` for tracking host/container metrics + ```sh make up-prom ``` + ##### Stop prometheus and cadvisor + ```sh make down-prom ``` @@ -176,7 +231,8 @@ make down-prom - **tx-broadcaster**: submits token transfer txs to ensure stacks block production during a sortition ## Bitcoin Miner -*Dedicated address for Bitcoin block production after initial setup (~200 blocks). This prevents conflicts with Stacks mining operations.* + +_Dedicated address for Bitcoin block production after initial setup (~200 blocks). This prevents conflicts with Stacks mining operations._ ```text ‣ Mnemonic: foot script pledge suit bread thing stage long auction craft label injury helmet drum ice govern glass tag lamp shield bike raccoon cloud hat @@ -307,13 +363,13 @@ make down-prom ‣ WIF: cMz2ZSsaVgWPFUkE44zHpJepB4NdwB9L938h53hQfFoot81AZFb3 ``` - ## Testing Accounts -*Unused but funded accounts that may be used to deploy contracts or other txs* + +_Unused but funded accounts that may be used to deploy contracts or other txs_ ### Deployer Account -*Unused but funded account that may be used to deploy contracts or other txs* +_Unused but funded account that may be used to deploy contracts or other txs_ ```text ‣ Mnemonic: keep can record bracket note hip face pudding castle detail few sunset review burger enhance foil lamp estate reopen butter then wasp pen kick diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5add6c4..55378cb 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -78,7 +78,7 @@ x-common-vars: - &STACKS_20_HEIGHT ${STACKS_20_HEIGHT:-0} - &STACKS_2_05_HEIGHT ${STACKS_2_05_HEIGHT:-203} - &STACKS_21_HEIGHT ${STACKS_21_HEIGHT:-204} - - &STACKS_POX2_HEIGHT ${STACKS_POX2_HEIGHT:-205} # 104 is is stacks_block=1, 106 is stacks_block=3 + - &STACKS_POX2_HEIGHT ${STACKS_POX2_HEIGHT:-205} # 104 is stacks_block=1, 106 is stacks_block=3 - &STACKS_22_HEIGHT ${STACKS_22_HEIGHT:-206} - &STACKS_23_HEIGHT ${STACKS_23_HEIGHT:-207} - &STACKS_24_HEIGHT ${STACKS_24_HEIGHT:-208} @@ -94,7 +94,7 @@ x-common-vars: - &REWARD_RECIPIENT_1 ${REWARD_RECIPIENT_1:-ST1XVSVQN0KP5SDYFNT8E5TXWVW0XZVQEDBMCJ3XM} # priv: a6143d20cd73d0dce2179e2af7771372a95b9d6795924492bd4d15d17709531e01 - &REWARD_RECIPIENT_2 ${REWARD_RECIPIENT_2:-ST2FW15NGB4H76FMVXKHYYSM865YVS6V3SA1GNABC} # priv: fe3087801196d8027008146b13e6d365920c2e4b7bc9969729ec2f0f22ef74fc01 - &REWARD_RECIPIENT_3 ${REWARD_RECIPIENT_3:-ST2MES40ZEXTX9M4YXW9QSWHRVC9HYT419S198VPM} # priv: ed7eb063c61b8e892987228f1fcfb74eab5009568861613dc4b074b708a7893701 - - &STACKS_CORE_BASE_BRANCH ${STACKS_CORE_BASE_BRANCH:-3.4.0.0.1} + - &STACKS_CORE_BASE_BRANCH ${STACKS_CORE_BASE_BRANCH:-3.4.0.0.2} # branch, tag, or commit SHA - &PAUSE_HEIGHT ${PAUSE_HEIGHT:-999999999999} - &PAUSE_TIMER 86400000 diff --git a/docker/stacks/Dockerfile b/docker/stacks/Dockerfile index 5574bc7..791e96f 100644 --- a/docker/stacks/Dockerfile +++ b/docker/stacks/Dockerfile @@ -1,14 +1,24 @@ FROM rust:bookworm AS builder # This will be overridden by the value from docker-compose +# Supports: branch name, tag, or commit SHA +# Examples: +# STACKS_CORE_BASE_BRANCH=develop (branch) +# STACKS_CORE_BASE_BRANCH=3.3.0.0.1 (tag) +# STACKS_CORE_BASE_BRANCH=abc123def456 (commit) ARG STACKS_CORE_BASE_BRANCH=master -RUN echo "Building Stacks Core from branch: $STACKS_CORE_BASE_BRANCH" +RUN echo "Building Stacks Core from: $STACKS_CORE_BASE_BRANCH" + +# Clone efficiently: shallow for branches/tags, targeted fetch for commits +RUN git init /code/stacks-core && \ + cd /code/stacks-core && \ + git remote add origin https://github.com/stacks-network/stacks-core.git && \ + git fetch --depth=1 origin $STACKS_CORE_BASE_BRANCH && \ + git checkout FETCH_HEAD -# Clone the specified branch from GitHub -RUN git clone --branch $STACKS_CORE_BASE_BRANCH --single-branch --depth=1 https://github.com/stacks-network/stacks-core.git /code/stacks-core WORKDIR /code/stacks-core -RUN apt-get update && apt-get install -y git libclang-dev llvm +RUN apt-get update && apt-get install -y libclang-dev llvm # Run an build that we'll cache the result of and then build the code RUN cargo build --features monitoring_prom,slog_json --bin stacks-node --bin stacks-signer