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
- 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
431 lines
16 KiB
Python
Executable File
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()
|