Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (push) Has been cancelled
AITBC CI/CD Pipeline / test-cli (push) Has been cancelled
AITBC CI/CD Pipeline / test-services (push) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (push) Has been cancelled
AITBC CI/CD Pipeline / security-scan (push) Has been cancelled
AITBC CI/CD Pipeline / build (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (push) Has been cancelled
AITBC CI/CD Pipeline / performance-test (push) Has been cancelled
AITBC CI/CD Pipeline / docs (push) Has been cancelled
AITBC CI/CD Pipeline / release (push) Has been cancelled
AITBC CI/CD Pipeline / notify (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.11) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.12) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.13) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-summary (push) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (push) Has been cancelled
Security Scanning / Dependency Security Scan (push) Has been cancelled
Security Scanning / Container Security Scan (push) Has been cancelled
Security Scanning / OSSF Scorecard (push) Has been cancelled
Security Scanning / Security Summary Report (push) Has been cancelled
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
476 lines
18 KiB
Python
Executable File
476 lines
18 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
|
|
from .keystore import create_keystore_via_script
|
|
import subprocess
|
|
import sys
|
|
|
|
@click.group()
|
|
def genesis():
|
|
"""Genesis block generation and management commands"""
|
|
pass
|
|
|
|
|
|
@genesis.command()
|
|
@click.option('--address', required=True, help='Wallet address (id) to create')
|
|
@click.option('--password-file', default='/opt/aitbc/data/keystore/.password', show_default=True, type=click.Path(exists=True, dir_okay=False), help='Path to password file')
|
|
@click.option('--output-dir', default='/opt/aitbc/data/keystore', show_default=True, help='Directory to write keystore file')
|
|
@click.option('--force', is_flag=True, help='Overwrite existing keystore file if present')
|
|
@click.pass_context
|
|
def create_keystore(ctx, address, password_file, output_dir, force):
|
|
"""Create an encrypted keystore for a genesis/treasury address."""
|
|
try:
|
|
create_keystore_via_script(address=address, password_file=password_file, output_dir=output_dir, force=force)
|
|
success(f"Created keystore for {address} at {output_dir}")
|
|
except Exception as e:
|
|
error(f"Error creating keystore: {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('--db', default='/opt/aitbc/data/ait-mainnet/chain.db', show_default=True, help='SQLite DB path')
|
|
@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, db, force):
|
|
"""Initialize production chain DB using genesis allocations."""
|
|
db_path = Path(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,
|
|
'--db', db,
|
|
]
|
|
try:
|
|
subprocess.run(cmd, check=True)
|
|
success(f"Initialized production genesis for {chain_id} at {db}")
|
|
except subprocess.CalledProcessError as e:
|
|
error(f"Genesis init failed: {e}")
|
|
raise click.Abort()
|
|
|
|
@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()
|