feat(blockchain): production genesis with encrypted keystore, remove admin minting
- Introduce production setup script: scripts/setup_production.py - Generates aitbc1genesis (treasury) and aitbc1treasury (spending) wallets - Encrypts keys via AES-GCM, stores password in keystore/.password (600) - Creates allocations.json and genesis.json with fixed total supply - Sets mint_per_unit=0 (no inflation) - Update make_genesis.py: - Accept allocations file instead of single faucet - Use 'allocations' key in genesis (renamed from 'accounts') - Enforce mint_per_unit=0 in default params - Remove admin mint endpoint: - Deleting MintFaucetRequest and /rpc/admin/mintFaucet from router.py - Removes faucet CLI command from cli/aitbc_cli/commands/blockchain.py - RPC supply endpoint now computes total supply from genesis file (fixed) - Validators endpoint derives list from trusted_proposers config - Config enhancements (config.py): - Add keystore_path and keystore_password_file - Change mint_per_unit default to 0 - main.py: Auto-load proposer private key from keystore into settings.proposer_key (hex) for future use - Launcher scripts: - scripts/mainnet_up.sh: Loads .env.production, derives proposer_id from keystore if needed, starts node + RPC - scripts/devnet_up.sh: Updated to use new allocations-based genesis and proper proposer address - Documentation: - Rewrite blockchain-node/README.md for production model (no faucet, keystore management, multi-chain) - Update MEMORY.md with production blockchain section - Database: Multi-chain support already present via chain_id foreign keys. This change makes the blockchain production‑ready: immutable supply, secure key storage, and removal of dev‑only admin functions. Co-authored-by: Andreas Michael Fleckl <andreas@example.com>
This commit is contained in:
@@ -61,11 +61,6 @@ class EstimateFeeRequest(BaseModel):
|
||||
payload: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class MintFaucetRequest(BaseModel):
|
||||
address: str
|
||||
amount: int = Field(gt=0)
|
||||
|
||||
|
||||
@router.get("/head", summary="Get current chain head")
|
||||
async def get_head(chain_id: str = "ait-devnet") -> Dict[str, Any]:
|
||||
metrics_registry.increment("rpc_get_head_total")
|
||||
@@ -530,24 +525,6 @@ async def estimate_fee(request: EstimateFeeRequest) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@router.post("/admin/mintFaucet", summary="Mint devnet funds to an address")
|
||||
async def mint_faucet(request: MintFaucetRequest, chain_id: str = "ait-devnet") -> Dict[str, Any]:
|
||||
metrics_registry.increment("rpc_mint_faucet_total")
|
||||
start = time.perf_counter()
|
||||
with session_scope() as session:
|
||||
account = session.get(Account, (chain_id, request.address))
|
||||
if account is None:
|
||||
account = Account(chain_id=chain_id, address=request.address, balance=request.amount)
|
||||
session.add(account)
|
||||
else:
|
||||
account.balance += request.amount
|
||||
session.commit()
|
||||
updated_balance = account.balance
|
||||
metrics_registry.increment("rpc_mint_faucet_success_total")
|
||||
metrics_registry.observe("rpc_mint_faucet_duration_seconds", time.perf_counter() - start)
|
||||
return {"address": request.address, "balance": updated_balance}
|
||||
|
||||
|
||||
class ImportBlockRequest(BaseModel):
|
||||
height: int
|
||||
hash: str
|
||||
@@ -663,15 +640,27 @@ async def get_token_supply(chain_id: str = "ait-devnet") -> Dict[str, Any]:
|
||||
start = time.perf_counter()
|
||||
|
||||
with session_scope() as session:
|
||||
# Simple implementation for now
|
||||
# Sum balances of all accounts in this chain
|
||||
result = session.exec(select(func.sum(Account.balance)).where(Account.chain_id == chain_id)).one_or_none()
|
||||
circulating = int(result) if result is not None else 0
|
||||
|
||||
# Total supply is read from genesis (fixed), or fallback to circulating if unavailable
|
||||
# Try to locate genesis file
|
||||
genesis_path = Path(f"./data/{chain_id}/genesis.json")
|
||||
total_supply = circulating # default fallback
|
||||
if genesis_path.exists():
|
||||
try:
|
||||
with open(genesis_path) as f:
|
||||
g = json.load(f)
|
||||
total_supply = sum(a["balance"] for a in g.get("allocations", []))
|
||||
except Exception:
|
||||
total_supply = circulating
|
||||
|
||||
response = {
|
||||
"chain_id": chain_id,
|
||||
"total_supply": 1000000000, # 1 billion from genesis
|
||||
"circulating_supply": 0, # No transactions yet
|
||||
"faucet_balance": 1000000000, # All tokens in faucet
|
||||
"faucet_address": "ait1faucet000000000000000000000000000000000",
|
||||
"total_supply": total_supply,
|
||||
"circulating_supply": circulating,
|
||||
"mint_per_unit": cfg.mint_per_unit,
|
||||
"total_accounts": 0
|
||||
}
|
||||
|
||||
metrics_registry.observe("rpc_supply_duration_seconds", time.perf_counter() - start)
|
||||
@@ -682,30 +671,35 @@ async def get_token_supply(chain_id: str = "ait-devnet") -> Dict[str, Any]:
|
||||
async def get_validators(chain_id: str = "ait-devnet") -> Dict[str, Any]:
|
||||
"""List blockchain validators (authorities)"""
|
||||
from ..config import settings as cfg
|
||||
|
||||
|
||||
metrics_registry.increment("rpc_validators_total")
|
||||
start = time.perf_counter()
|
||||
|
||||
# For PoA chain, validators are the authorities from genesis
|
||||
# In a full implementation, this would query the actual validator set
|
||||
|
||||
# Build validator set from trusted_proposers config (comma-separated)
|
||||
trusted = [p.strip() for p in cfg.trusted_proposers.split(",") if p.strip()]
|
||||
if not trusted:
|
||||
# Fallback to the node's own proposer_id as the sole validator
|
||||
trusted = [cfg.proposer_id]
|
||||
|
||||
validators = [
|
||||
{
|
||||
"address": "ait1devproposer000000000000000000000000000000",
|
||||
"address": addr,
|
||||
"weight": 1,
|
||||
"status": "active",
|
||||
"last_block_height": None, # Would be populated from actual validator tracking
|
||||
"last_block_height": None, # Could be populated from metrics
|
||||
"total_blocks_produced": None
|
||||
}
|
||||
for addr in trusted
|
||||
]
|
||||
|
||||
|
||||
response = {
|
||||
"chain_id": chain_id,
|
||||
"validators": validators,
|
||||
"total_validators": len(validators),
|
||||
"consensus_type": "PoA", # Proof of Authority
|
||||
"consensus_type": "PoA",
|
||||
"proposer_id": cfg.proposer_id
|
||||
}
|
||||
|
||||
|
||||
metrics_registry.observe("rpc_validators_duration_seconds", time.perf_counter() - start)
|
||||
return response
|
||||
|
||||
|
||||
Reference in New Issue
Block a user