Skip to content
Merged
167 changes: 153 additions & 14 deletions extras/nginx_docker/Makefile
Original file line number Diff line number Diff line change
@@ -1,40 +1,179 @@
.PHONY: all
all: docker
all: help

tag = 769498303037.dkr.ecr.us-east-1.amazonaws.com/webtank:latest
no_rate_limit_tag = 769498303037.dkr.ecr.us-east-1.amazonaws.com/webtank:no-rate-limit-latest
DEFAULT_LATEST_TAG = latest
DEFAULT_NO_RATE_LIMIT_TAG = no-rate-limit-latest

# Build and Push Commands
# =======================

# GCP / Nano Testnet
NANO_TESTNET_REGISTRY = us-central1-docker.pkg.dev/nano-testnet/fullnodes/webtank

NANO_TESTNET_BRAVO_TAG_LATEST = bravo-latest
NANO_TESTNET_BRAVO_TAG_NO_RATE_LIMIT = bravo-no-rate-limit-latest

.PHONY: nano-testnet
nano-testnet: nano-testnet-default nano-testnet-no-rate-limit nano-testnet-bravo-default nano-testnet-bravo-no-rate-limit
@echo "All Nano Testnet images built and pushed successfully!"

.PHONY: nano-testnet-default
nano-testnet-default: clean nginx.conf set_real_ip_from_cloudfront
@echo "Building and pushing latest image for Nano Testnet..."
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(NANO_TESTNET_REGISTRY):$(DEFAULT_LATEST_TAG) .

.PHONY: nano-testnet-no-rate-limit
nano-testnet-no-rate-limit: clean nginx_no_rate_limit.conf set_real_ip_from_cloudfront
@echo "Building and pushing no-rate-limit image for Nano Testnet..."
mv nginx_no_rate_limit.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(NANO_TESTNET_REGISTRY):$(DEFAULT_NO_RATE_LIMIT_TAG) .

.PHONY: nano-testnet-bravo-default
nano-testnet-bravo-default: clean nginx_bravo.conf set_real_ip_from_cloudfront
@echo "Building and pushing bravo image for Nano Testnet..."
mv nginx_bravo.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(NANO_TESTNET_REGISTRY):$(NANO_TESTNET_BRAVO_TAG_LATEST) .

.PHONY: nano-testnet-bravo-no-rate-limit
nano-testnet-bravo-no-rate-limit: clean nginx_bravo.conf set_real_ip_from_cloudfront
@echo "Building and pushing no-rate-limit bravo image for Nano Testnet..."
mv nginx_bravo.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(NANO_TESTNET_REGISTRY):$(NANO_TESTNET_BRAVO_TAG_NO_RATE_LIMIT) .

# GCP / Standalone Fullnodes
STANDALONE_FULLNODES_REGISTRY = us-central1-docker.pkg.dev/standalone-fullnodes/fullnodes/webtank

.PHONY: standalone-fullnodes
standalone-fullnodes: standalone-fullnodes-default standalone-fullnodes-no-rate-limit
@echo "All Standalone Fullnodes images built and pushed successfully!"

.PHONY: standalone-fullnodes-default
standalone-fullnodes-default: clean nginx.conf set_real_ip_from_cloudfront
@echo "Building and pushing latest image for Standalone Fullnodes..."
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(STANDALONE_FULLNODES_REGISTRY):$(DEFAULT_LATEST_TAG) .

.PHONY: standalone-fullnodes-no-rate-limit
standalone-fullnodes-no-rate-limit: clean nginx_no_rate_limit.conf set_real_ip_from_cloudfront
@echo "Building and pushing no-rate-limit image for Standalone Fullnodes..."
mv nginx_no_rate_limit.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(STANDALONE_FULLNODES_REGISTRY):$(DEFAULT_NO_RATE_LIMIT_TAG) .

# GCP / Ekvilibro
EKVILIBRO_REGISTRY = us-central1-docker.pkg.dev/ekvilibro/fullnodes/webtank

.PHONY: ekvilibro
ekvilibro: ekvilibro-default ekvilibro-no-rate-limit
@echo "All Ekvilibro images built and pushed successfully!"

.PHONY: ekvilibro-default
ekvilibro-default: clean nginx.conf set_real_ip_from_cloudfront
@echo "Building and pushing latest image for Ekvilibro..."
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(EKVILIBRO_REGISTRY):$(DEFAULT_LATEST_TAG) .

.PHONY: ekvilibro-no-rate-limit
ekvilibro-no-rate-limit: clean nginx_no_rate_limit.conf set_real_ip_from_cloudfront
@echo "Building and pushing no-rate-limit image for Ekvilibro..."
mv nginx_no_rate_limit.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(EKVILIBRO_REGISTRY):$(DEFAULT_NO_RATE_LIMIT_TAG) .

# AWS / Main Account
AWS_MAIN_REGISTRY = 769498303037.dkr.ecr.us-east-1.amazonaws.com/webtank

.PHONY: aws-main
aws-main: aws-main-default aws-main-no-rate-limit
@echo "All AWS Main images built and pushed successfully!"

.PHONY: aws-main-default
aws-main-default: clean nginx.conf set_real_ip_from_cloudfront
@echo "Building and pushing latest image for AWS Main..."
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(AWS_MAIN_REGISTRY):$(DEFAULT_LATEST_TAG) .

.PHONY: aws-main-no-rate-limit
aws-main-no-rate-limit: clean nginx_no_rate_limit.conf set_real_ip_from_cloudfront
@echo "Building and pushing no-rate-limit image for AWS Main..."
mv nginx_no_rate_limit.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(AWS_MAIN_REGISTRY):$(DEFAULT_NO_RATE_LIMIT_TAG) .

# Build All (convenience command)
.PHONY: build-all
build-all: nano-testnet standalone-fullnodes ekvilibro aws-main
@echo "All images built and pushed successfully!"

# Legacy commands for backward compatibility
.PHONY: docker
docker: docker-default docker-no-rate-limit
docker: aws-main

# Default Nginx Image
.PHONY: docker-default
docker-default: nginx.conf set_real_ip_from_cloudfront
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(tag) .
docker-default: aws-main-default

.PHONY: docker-no-rate-limit
docker-no-rate-limit: aws-main-no-rate-limit

# Configuration Generation
# ========================

nginx.conf: export PYTHONPATH := ../..
nginx.conf:
@python -c "import os; import hathor; print('Using hathor-core from:', os.path.dirname(hathor.__file__))"
python -m hathor generate_nginx_config - > $@

# Nginx Image used for private use cases, with rate limits disabled
.PHONY: docker-no-rate-limit
docker-no-rate-limit: nginx_no_rate_limit.conf set_real_ip_from_cloudfront
mv nginx_no_rate_limit.conf nginx.conf
docker buildx build --pull --push --platform linux/arm64/v8,linux/amd64 --tag $(no_rate_limit_tag) .

nginx_no_rate_limit.conf: export PYTHONPATH := ../..
nginx_no_rate_limit.conf:
@python -c "import os; import hathor; print('Using hathor-core from:', os.path.dirname(hathor.__file__))"
python -m hathor generate_nginx_config --disable-rate-limits - > $@

nginx_bravo.conf: export PYTHONPATH := ../..
nginx_bravo.conf:
@python -c "import os; import hathor; print('Using hathor-core from:', os.path.dirname(hathor.__file__))"
python -m hathor generate_nginx_config --override nano-testnet-bravo - > $@

set_real_ip_from_cloudfront:
curl https://ip-ranges.amazonaws.com/ip-ranges.json -s \
| jq '.prefixes|map(select(.service=="CLOUDFRONT"))[]|.ip_prefix' -r \
| sort -h \
| xargs -n 1 printf "set_real_ip_from %s;\n" \
> $@

# Utility Commands
# ===============

.PHONY: clean
clean:
rm -f nginx.conf set_real_ip_from_cloudfront
rm -f nginx.conf nginx_no_rate_limit.conf nginx_bravo.conf set_real_ip_from_cloudfront

.PHONY: help
help:
@echo "Available commands:"
@echo ""
@echo "Project/Account Commands:"
@echo " nano-testnet - Build and push all images for GCP Project Nano Testnet"
@echo " nano-testnet-default - Build and push default image for GCP Project Nano Testnet"
@echo " nano-testnet-no-rate-limit - Build and push no-rate-limit image for GCP Project Nano Testnet"
@echo " nano-testnet-bravo-default - Build and push bravo image for GCP Project Nano Testnet"
@echo " nano-testnet-bravo-no-rate-limit - Build and push no-rate-limit bravo image for GCP Project Nano Testnet"
@echo " standalone-fullnodes - Build and push all images for GCP Project Standalone Fullnodes"
@echo " standalone-fullnodes-default - Build and push default image for GCP Project Standalone Fullnodes"
@echo " standalone-fullnodes-no-rate-limit - Build and push no-rate-limit image for GCP Project Standalone Fullnodes"
@echo " ekvilibro - Build and push all images for GCP Project Ekvilibro"
@echo " ekvilibro-default - Build and push default image for GCP Project Ekvilibro"
@echo " ekvilibro-no-rate-limit - Build and push no-rate-limit image for GCP Project Ekvilibro"
@echo " aws-main - Build and push all images for AWS Main Account"
@echo " aws-main-default - Build and push default image for AWS Main Account"
@echo " aws-main-no-rate-limit - Build and push no-rate-limit image for AWS Main Account"
@echo ""
@echo "Utility Commands:"
@echo " build-all - Build and push all active project images"
@echo " clean - Remove generated files"
@echo " help - Show this help message"
@echo ""
@echo "Legacy Commands (for backward compatibility):"
@echo " docker - Alias for aws-main"
@echo " docker-default - Alias for aws-main-default"
@echo " docker-no-rate-limit - Alias for aws-main-no-rate-limit"
@echo ""
@echo "Supported Projects/Accounts:"
@echo " - Nano Testnet: $(NANO_TESTNET_REGISTRY)"
@echo " - Standalone Fullnodes: $(STANDALONE_FULLNODES_REGISTRY)"
@echo " - Ekvilibro: $(EKVILIBRO_REGISTRY)"
@echo " - AWS Main Account: $(AWS_MAIN_REGISTRY)"
@echo ""
24 changes: 17 additions & 7 deletions hathor/cli/nginx_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,20 @@ def _scale_rate_limit(raw_rate: str, rate_k: float) -> str:
return f'{int(scaled_rate_amount)}{rate_units}'


def _get_visibility(source: dict[str, Any], fallback: Visibility) -> tuple[Visibility, bool]:
def _get_visibility(source: dict[str, Any], fallback: Visibility, override: str) -> tuple[Visibility, bool, bool]:
if 'x-visibility-override' in source and override in source['x-visibility-override']:
visibility = source['x-visibility-override'][override]
return Visibility(visibility), False, True
if 'x-visibility' in source:
return Visibility(source['x-visibility']), False
return Visibility(source['x-visibility']), False, False
else:
return fallback, True
return fallback, True, False


def generate_nginx_config(openapi: dict[str, Any], *, out_file: TextIO, rate_k: float = 1.0,
fallback_visibility: Visibility = Visibility.PRIVATE,
disable_rate_limits: bool = False) -> None:
disable_rate_limits: bool = False,
override: str = "") -> None:
""" Entry point of the functionality provided by the cli
"""
from datetime import datetime
Expand All @@ -122,9 +126,11 @@ def generate_nginx_config(openapi: dict[str, Any], *, out_file: TextIO, rate_k:
locations: dict[str, dict[str, Any]] = {}
limit_rate_zones: list[RateLimitZone] = []
for path, params in openapi['paths'].items():
visibility, did_fallback = _get_visibility(params, fallback_visibility)
visibility, did_fallback, did_override = _get_visibility(params, fallback_visibility, override)
if did_fallback:
warn(f'Visibility not set for path `{path}`, falling back to {fallback_visibility}')
if did_override:
warn(f'Visibility overridden for path `{path}` to {visibility}')
if visibility is Visibility.PRIVATE:
continue

Expand All @@ -138,7 +144,7 @@ def generate_nginx_config(openapi: dict[str, Any], *, out_file: TextIO, rate_k:
if method not in params:
continue
method_params = params[method]
method_visibility, _ = _get_visibility(method_params, Visibility.PUBLIC)
method_visibility, _, _ = _get_visibility(method_params, Visibility.PUBLIC, override)
if method_visibility is Visibility.PRIVATE:
continue
allowed_methods.add(method.upper())
Expand All @@ -150,6 +156,7 @@ def generate_nginx_config(openapi: dict[str, Any], *, out_file: TextIO, rate_k:

rate_limits = params.get('x-rate-limit')
if not rate_limits:
warn(f'Path `{path}` is public but has no rate limits, ignoring')
continue

path_key = path.lower().replace('/', '__').replace('.', '__').replace('{', '').replace('}', '')
Expand Down Expand Up @@ -352,11 +359,14 @@ def main():
help='Set the visibility for paths without `x-visibility`, defaults to private')
parser.add_argument('--disable-rate-limits', type=bool, default=False,
help='Disable including rate-limits in the config, defaults to False')
parser.add_argument('--override', type=str, default='',
help='Override visibility for paths with `x-visibility-override` for the given value')
parser.add_argument('out', type=argparse.FileType('w', encoding='UTF-8'), default=sys.stdout, nargs='?',
help='Output file where nginx config will be written')
args = parser.parse_args()

openapi = get_openapi(args.input_openapi_json)
generate_nginx_config(openapi, out_file=args.out, rate_k=args.rate_multiplier,
fallback_visibility=args.fallback_visibility,
disable_rate_limits=args.disable_rate_limits)
disable_rate_limits=args.disable_rate_limits,
override=args.override)
10 changes: 7 additions & 3 deletions hathor/cli/run_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ def create_parser(cls) -> ArgumentParser:

netargs = parser.add_mutually_exclusive_group()
netargs.add_argument('--nano-testnet', action='store_true', help='Connect to Hathor nano-testnet')
netargs.add_argument('--testnet', action='store_true', help='Connect to Hathor testnet')
netargs.add_argument('--testnet', action='store_true', help='Connect to Hathor the default testnet'
' (currently testnet-hotel)')
netargs.add_argument('--testnet-hotel', action='store_true', help='Connect to Hathor testnet-hotel')
netargs.add_argument('--testnet-golf', action='store_true', help='Connect to Hathor testnet-golf')
netargs.add_argument('--localnet', action='store_true', help='Create a localnet with default configuration.')

parser.add_argument('--test-mode-tx-weight', action='store_true',
Expand Down Expand Up @@ -498,8 +500,8 @@ def __init__(self, *, argv=None):
from hathor.conf import (
LOCALNET_SETTINGS_FILEPATH,
NANO_TESTNET_SETTINGS_FILEPATH,
TESTNET_GOLF_SETTINGS_FILEPATH,
TESTNET_HOTEL_SETTINGS_FILEPATH,
TESTNET_SETTINGS_FILEPATH,
)
from hathor.conf.get_settings import get_global_settings
self.log = logger.new()
Expand All @@ -516,9 +518,11 @@ def __init__(self, *, argv=None):
if self._args.config_yaml:
os.environ['HATHOR_CONFIG_YAML'] = self._args.config_yaml
elif self._args.testnet:
os.environ['HATHOR_CONFIG_YAML'] = TESTNET_SETTINGS_FILEPATH
os.environ['HATHOR_CONFIG_YAML'] = TESTNET_HOTEL_SETTINGS_FILEPATH
elif self._args.testnet_hotel:
os.environ['HATHOR_CONFIG_YAML'] = TESTNET_HOTEL_SETTINGS_FILEPATH
elif self._args.testnet_golf:
os.environ['HATHOR_CONFIG_YAML'] = TESTNET_GOLF_SETTINGS_FILEPATH
elif self._args.nano_testnet:
os.environ['HATHOR_CONFIG_YAML'] = NANO_TESTNET_SETTINGS_FILEPATH
elif self._args.localnet:
Expand Down
1 change: 1 addition & 0 deletions hathor/cli/run_node_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class RunNodeArgs(BaseModel, extra=Extra.allow):
unsafe_mode: Optional[str]
testnet: bool
testnet_hotel: bool
testnet_golf: bool
test_mode_tx_weight: bool
dns: Optional[str]
peer: Optional[str]
Expand Down
6 changes: 3 additions & 3 deletions hathor/conf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
parent_dir = Path(__file__).parent

MAINNET_SETTINGS_FILEPATH = str(parent_dir / 'mainnet.yml')
TESTNET_SETTINGS_FILEPATH = str(parent_dir / 'testnet.yml')
TESTNET_HOTEL_SETTINGS_FILEPATH = str(parent_dir / 'testnet_hotel.yml')
TESTNET_GOLF_SETTINGS_FILEPATH = str(parent_dir / 'testnet_golf.yml')
TESTNET_HOTEL_SETTINGS_FILEPATH = str(parent_dir / 'testnet.yml')
NANO_TESTNET_SETTINGS_FILEPATH = str(parent_dir / 'nano_testnet.yml')
LOCALNET_SETTINGS_FILEPATH = str(parent_dir / 'localnet.yml')
UNITTESTS_SETTINGS_FILEPATH = str(parent_dir / 'unittests.yml')

__all__ = [
'MAINNET_SETTINGS_FILEPATH',
'TESTNET_SETTINGS_FILEPATH',
'TESTNET_GOLF_SETTINGS_FILEPATH',
'TESTNET_HOTEL_SETTINGS_FILEPATH',
'NANO_TESTNET_SETTINGS_FILEPATH',
'LOCALNET_SETTINGS_FILEPATH',
Expand Down
9 changes: 9 additions & 0 deletions hathor/conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ def GENESIS_TX2_TIMESTAMP(self) -> int:
# List of soft voided transaction.
SOFT_VOIDED_TX_IDS: list[bytes] = []

# List of transactions to skip verification.
SKIP_VERIFICATION: list[bytes] = []

# Identifier used in metadata's voided_by to mark a tx as soft-voided.
SOFT_VOIDED_ID: bytes = b'tx-non-grata'

Expand Down Expand Up @@ -592,6 +595,12 @@ def _validate_tokens(genesis_tokens: int, values: dict[str, Any]) -> int:
allow_reuse=True,
each_item=True
)(parse_hex_str),
_parse_skipped_verification_tx_id=pydantic.validator(
'SKIP_VERIFICATION',
pre=True,
allow_reuse=True,
each_item=True
)(parse_hex_str),
_parse_checkpoints=pydantic.validator(
'CHECKPOINTS',
pre=True
Expand Down
Loading
Loading