Files
aitbc/cli/aitbc_cli/commands/genesis.py
aitbc1 e0b0f901ef
Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (pull_request) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (pull_request) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.11) (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.12) (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.13) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (pull_request) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (pull_request) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (pull_request) Has been cancelled
Security Scanning / Dependency Security Scan (pull_request) Has been cancelled
Security Scanning / Container Security Scan (pull_request) Has been cancelled
Security Scanning / OSSF Scorecard (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-cli (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-services (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (pull_request) Has been cancelled
AITBC CI/CD Pipeline / security-scan (pull_request) Has been cancelled
AITBC CI/CD Pipeline / build (pull_request) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (pull_request) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (pull_request) Has been cancelled
AITBC CI/CD Pipeline / performance-test (pull_request) Has been cancelled
AITBC CI/CD Pipeline / docs (pull_request) Has been cancelled
AITBC CI/CD Pipeline / release (pull_request) Has been cancelled
AITBC CI/CD Pipeline / notify (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-summary (pull_request) Has been cancelled
Security Scanning / Security Summary Report (pull_request) Has been cancelled
refactor: simplify .env.example and add bulk sync capabilities
- Simplify .env.example to show actual config values instead of instructions
- Add blockchain core, coordinator API, and marketplace web example sections
- Add bulk import functionality to ChainSync for faster catchup
- Add fetch_blocks_range and bulk_import_from methods with batching
- Add init-production command to genesis CLI for production chain setup
- Add HTTP client management with configurable batch size and poll interval
2026-03-20 12:17:30 +01:00

431 lines
16 KiB
Python
Executable File

"""Genesis block generation commands for AITBC CLI"""
import click
import json
import yaml
from pathlib import Path
from datetime import datetime
from ..core.genesis_generator import GenesisGenerator, GenesisValidationError
from ..core.config import MultiChainConfig, load_multichain_config
from ..models.chain import GenesisConfig
from ..utils import output, error, success
@click.group()
def genesis():
"""Genesis block generation and management commands"""
pass
@genesis.command()
@click.argument('config_file', type=click.Path(exists=True))
@click.option('--output', '-o', 'output_file', help='Output file path')
@click.option('--template', help='Use predefined template')
@click.option('--format', type=click.Choice(['json', 'yaml']), default='json', help='Output format')
@click.pass_context
def create(ctx, config_file, output_file, template, format):
"""Create genesis block from configuration"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
if template:
# Create from template
genesis_block = generator.create_from_template(template, config_file)
else:
# Create from configuration file
with open(config_file, 'r') as f:
config_data = yaml.safe_load(f)
genesis_config = GenesisConfig(**config_data['genesis'])
genesis_block = generator.create_genesis(genesis_config)
# Determine output file
if output_file is None:
chain_id = genesis_block.chain_id
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = f"genesis_{chain_id}_{timestamp}.{format}"
# Save genesis block
output_path = Path(output_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
if format == 'yaml':
with open(output_path, 'w') as f:
yaml.dump(genesis_block.dict(), f, default_flow_style=False, indent=2)
else:
with open(output_path, 'w') as f:
json.dump(genesis_block.dict(), f, indent=2)
success("Genesis block created successfully!")
result = {
"Chain ID": genesis_block.chain_id,
"Chain Type": genesis_block.chain_type.value,
"Purpose": genesis_block.purpose,
"Name": genesis_block.name,
"Genesis Hash": genesis_block.hash,
"Output File": output_file,
"Format": format
}
output(result, ctx.obj.get('output_format', 'table'))
if genesis_block.privacy.visibility == "private":
success("Private chain genesis created! Use access codes to invite participants.")
except GenesisValidationError as e:
error(f"Genesis validation error: {str(e)}")
raise click.Abort()
except Exception as e:
error(f"Error creating genesis block: {str(e)}")
raise click.Abort()
@genesis.command()
@click.argument('genesis_file', type=click.Path(exists=True))
@click.pass_context
def validate(ctx, genesis_file):
"""Validate genesis block integrity"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
# Load genesis block
genesis_path = Path(genesis_file)
if genesis_path.suffix.lower() in ['.yaml', '.yml']:
with open(genesis_path, 'r') as f:
genesis_data = yaml.safe_load(f)
else:
with open(genesis_path, 'r') as f:
genesis_data = json.load(f)
from ..models.chain import GenesisBlock
genesis_block = GenesisBlock(**genesis_data)
# Validate genesis block
validation_result = generator.validate_genesis(genesis_block)
if validation_result.is_valid:
success("Genesis block is valid!")
# Show validation details
checks_data = [
{
"Check": check,
"Status": "✓ Pass" if passed else "✗ Fail"
}
for check, passed in validation_result.checks.items()
]
output(checks_data, ctx.obj.get('output_format', 'table'), title="Validation Results")
else:
error("Genesis block validation failed!")
# Show errors
errors_data = [
{
"Error": error_msg
}
for error_msg in validation_result.errors
]
output(errors_data, ctx.obj.get('output_format', 'table'), title="Validation Errors")
# Show failed checks
failed_checks = [
{
"Check": check,
"Status": "✗ Fail"
}
for check, passed in validation_result.checks.items()
if not passed
]
if failed_checks:
output(failed_checks, ctx.obj.get('output_format', 'table'), title="Failed Checks")
raise click.Abort()
except Exception as e:
error(f"Error validating genesis block: {str(e)}")
raise click.Abort()
@genesis.command()
@click.argument('genesis_file', type=click.Path(exists=True))
@click.pass_context
def info(ctx, genesis_file):
"""Show genesis block information"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
genesis_info = generator.get_genesis_info(genesis_file)
# Basic information
basic_info = {
"Chain ID": genesis_info["chain_id"],
"Chain Type": genesis_info["chain_type"],
"Purpose": genesis_info["purpose"],
"Name": genesis_info["name"],
"Description": genesis_info.get("description", "No description"),
"Created": genesis_info["created"],
"Genesis Hash": genesis_info["genesis_hash"],
"State Root": genesis_info["state_root"]
}
output(basic_info, ctx.obj.get('output_format', 'table'), title="Genesis Block Information")
# Configuration details
config_info = {
"Consensus Algorithm": genesis_info["consensus_algorithm"],
"Block Time": f"{genesis_info['block_time']}s",
"Gas Limit": f"{genesis_info['gas_limit']:,}",
"Gas Price": f"{genesis_info['gas_price'] / 1e9:.1f} gwei",
"Accounts Count": genesis_info["accounts_count"],
"Contracts Count": genesis_info["contracts_count"]
}
output(config_info, ctx.obj.get('output_format', 'table'), title="Configuration Details")
# Privacy settings
privacy_info = {
"Visibility": genesis_info["privacy_visibility"],
"Access Control": genesis_info["access_control"]
}
output(privacy_info, ctx.obj.get('output_format', 'table'), title="Privacy Settings")
# File information
file_info = {
"File Size": f"{genesis_info['file_size']:,} bytes",
"File Format": genesis_info["file_format"]
}
output(file_info, ctx.obj.get('output_format', 'table'), title="File Information")
except Exception as e:
error(f"Error getting genesis info: {str(e)}")
raise click.Abort()
@genesis.command()
@click.argument('genesis_file', type=click.Path(exists=True))
@click.pass_context
def hash(ctx, genesis_file):
"""Calculate genesis hash"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
genesis_hash = generator.calculate_genesis_hash(genesis_file)
result = {
"Genesis File": genesis_file,
"Genesis Hash": genesis_hash
}
output(result, ctx.obj.get('output_format', 'table'))
except Exception as e:
error(f"Error calculating genesis hash: {str(e)}")
raise click.Abort()
@genesis.command()
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='Output format')
@click.pass_context
def templates(ctx, format):
"""List available genesis templates"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
templates = generator.list_templates()
if not templates:
output("No templates found", ctx.obj.get('output_format', 'table'))
return
if format == 'json':
output(templates, ctx.obj.get('output_format', 'table'))
else:
templates_data = [
{
"Template": template_name,
"Description": template_info["description"],
"Chain Type": template_info["chain_type"],
"Purpose": template_info["purpose"]
}
for template_name, template_info in templates.items()
]
output(templates_data, ctx.obj.get('output_format', 'table'), title="Available Templates")
except Exception as e:
error(f"Error listing templates: {str(e)}")
raise click.Abort()
@genesis.command()
@click.argument('template_name')
@click.option('--output', '-o', help='Output file path')
@click.pass_context
def template_info(ctx, template_name, output):
"""Show detailed information about a template"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
templates = generator.list_templates()
if template_name not in templates:
error(f"Template {template_name} not found")
raise click.Abort()
template_info = templates[template_name]
info_data = {
"Template Name": template_name,
"Description": template_info["description"],
"Chain Type": template_info["chain_type"],
"Purpose": template_info["purpose"],
"File Path": template_info["file_path"]
}
output(info_data, ctx.obj.get('output_format', 'table'), title=f"Template Information: {template_name}")
# Show template content if requested
if output:
template_path = Path(template_info["file_path"])
if template_path.exists():
with open(template_path, 'r') as f:
template_content = f.read()
output_path = Path(output)
output_path.write_text(template_content)
success(f"Template content saved to {output}")
except Exception as e:
error(f"Error getting template info: {str(e)}")
raise click.Abort()
@genesis.command(name="init-production")
@click.option('--chain-id', default='ait-mainnet', show_default=True, help='Chain ID to initialize')
@click.option('--genesis-file', default='data/genesis_prod.yaml', show_default=True, help='Path to genesis YAML (copy to /opt/aitbc/genesis_prod.yaml if needed)')
@click.option('--force', is_flag=True, help='Overwrite existing DB (removes file if present)')
@click.pass_context
def init_production(ctx, chain_id, genesis_file, force):
"""Initialize production chain DB using genesis allocations."""
db_path = Path("/opt/aitbc/data") / chain_id / "chain.db"
if db_path.exists() and force:
db_path.unlink()
python_bin = Path(__file__).resolve().parents[3] / 'apps' / 'blockchain-node' / '.venv' / 'bin' / 'python3'
cmd = [
str(python_bin),
str(Path(__file__).resolve().parents[3] / 'scripts' / 'init_production_genesis.py'),
'--chain-id', chain_id,
]
try:
subprocess.run(cmd, check=True)
success(f"Initialized production genesis for {chain_id} at {db_path}")
except subprocess.CalledProcessError as e:
error(f"Genesis init failed: {e}")
raise click.Abort()
@genesis.command()
@click.argument('chain_id')
@click.option('--format', type=click.Choice(['json', 'yaml']), default='json', help='Export format')
@click.option('--output', '-o', help='Output file path')
@click.pass_context
def export(ctx, chain_id, format, output):
"""Export genesis block for a chain"""
try:
config = load_multichain_config()
generator = GenesisGenerator(config)
genesis_data = generator.export_genesis(chain_id, format)
if output:
output_path = Path(output)
output_path.parent.mkdir(parents=True, exist_ok=True)
if format == 'yaml':
# Parse JSON and convert to YAML
parsed_data = json.loads(genesis_data)
with open(output_path, 'w') as f:
yaml.dump(parsed_data, f, default_flow_style=False, indent=2)
else:
output_path.write_text(genesis_data)
success(f"Genesis block exported to {output}")
else:
# Print to stdout
if format == 'yaml':
parsed_data = json.loads(genesis_data)
output(yaml.dump(parsed_data, default_flow_style=False, indent=2),
ctx.obj.get('output_format', 'table'))
else:
output(genesis_data, ctx.obj.get('output_format', 'table'))
except Exception as e:
error(f"Error exporting genesis block: {str(e)}")
raise click.Abort()
@genesis.command()
@click.argument('template_name')
@click.argument('output_file')
@click.option('--format', type=click.Choice(['json', 'yaml']), default='yaml', help='Output format')
@click.pass_context
def create_template(ctx, template_name, output_file, format):
"""Create a new genesis template"""
try:
# Basic template structure
template_data = {
"description": f"Genesis template for {template_name}",
"genesis": {
"chain_type": "topic",
"purpose": template_name,
"name": f"{template_name.title()} Chain",
"description": f"A {template_name} chain for AITBC",
"consensus": {
"algorithm": "pos",
"block_time": 5,
"max_validators": 100,
"authorities": []
},
"privacy": {
"visibility": "public",
"access_control": "open",
"require_invitation": False
},
"parameters": {
"max_block_size": 1048576,
"max_gas_per_block": 10000000,
"min_gas_price": 1000000000,
"block_reward": "2000000000000000000"
},
"accounts": [],
"contracts": []
}
}
output_path = Path(output_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
if format == 'yaml':
with open(output_path, 'w') as f:
yaml.dump(template_data, f, default_flow_style=False, indent=2)
else:
with open(output_path, 'w') as f:
json.dump(template_data, f, indent=2)
success(f"Template created: {output_file}")
result = {
"Template Name": template_name,
"Output File": output_file,
"Format": format,
"Chain Type": template_data["genesis"]["chain_type"],
"Purpose": template_data["genesis"]["purpose"]
}
output(result, ctx.obj.get('output_format', 'table'))
except Exception as e:
error(f"Error creating template: {str(e)}")
raise click.Abort()