fix: restructure password resolution and improve genesis chain_id detection
Some checks failed
CLI Tests / test-cli (push) Has been cancelled
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
Some checks failed
CLI Tests / test-cli (push) Has been cancelled
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Security Scanning / security-scan (push) Has been cancelled
- Restructured password resolution in transactions send/batch to check env var before TTY - Changed from os.environ.get() to 'in os.environ' to detect empty string env vars - Added unencrypted wallet detection to skip password for --no-encrypt wallets - Updated genesis info to auto-detect chain_id from blockchain RPC health endpoint - Added --rpc-url option to genesis info for custom RPC URL - Improved error messages to show RPC URL used for detection - Fixes termios.error when AITBC_WALLET_PASSWORD is set to empty string
This commit is contained in:
@@ -162,16 +162,27 @@ def verify(ctx, chain_id: str):
|
|||||||
|
|
||||||
|
|
||||||
@genesis.command()
|
@genesis.command()
|
||||||
@click.option("--chain-id", default=None, help="Chain ID to show info for (auto-detected from config if not provided)")
|
@click.option("--chain-id", default=None, help="Chain ID to show info for (auto-detected from blockchain node if not provided)")
|
||||||
@click.option("--data-dir", default=None, help="Data directory path (default: /var/lib/aitbc/data)")
|
@click.option("--data-dir", default=None, help="Data directory path (default: /var/lib/aitbc/data)")
|
||||||
|
@click.option("--rpc-url", default=None, help="Blockchain RPC URL for chain ID auto-detection (default: http://localhost:8006)")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def info(ctx, chain_id: str, data_dir: Optional[str]):
|
def info(ctx, chain_id: str, data_dir: Optional[str], rpc_url: Optional[str]):
|
||||||
"""Show genesis block information"""
|
"""Show genesis block information"""
|
||||||
# Auto-detect chain_id from config if not provided
|
# Auto-detect chain_id from blockchain node if not provided
|
||||||
if not chain_id:
|
if not chain_id:
|
||||||
from ..config import get_config
|
from ..config import get_config
|
||||||
config = get_config()
|
config = get_config()
|
||||||
chain_id = getattr(config, 'chain_id', 'ait-mainnet')
|
|
||||||
|
# Try to get chain_id from RPC health endpoint
|
||||||
|
if not rpc_url:
|
||||||
|
rpc_url = getattr(config, 'blockchain_rpc_url', 'http://localhost:8006')
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ..utils.chain_id import get_chain_id
|
||||||
|
chain_id = get_chain_id(rpc_url, override=None, timeout=5)
|
||||||
|
except Exception:
|
||||||
|
# Fallback to config or default
|
||||||
|
chain_id = getattr(config, 'chain_id', 'ait-mainnet')
|
||||||
|
|
||||||
# Use provided data dir or default
|
# Use provided data dir or default
|
||||||
if not data_dir:
|
if not data_dir:
|
||||||
@@ -184,6 +195,7 @@ def info(ctx, chain_id: str, data_dir: Optional[str]):
|
|||||||
if not genesis_path.exists():
|
if not genesis_path.exists():
|
||||||
error(f"Genesis config not found: {genesis_path}")
|
error(f"Genesis config not found: {genesis_path}")
|
||||||
error(f"Chain ID: {chain_id}, Data directory: {data_dir}")
|
error(f"Chain ID: {chain_id}, Data directory: {data_dir}")
|
||||||
|
error(f"RPC URL used for detection: {rpc_url}")
|
||||||
error("Run 'aitbc genesis init' to create genesis block")
|
error("Run 'aitbc genesis init' to create genesis block")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -134,25 +134,56 @@ def _send_transaction_impl(from_wallet: str, to_address: str, amount: float, fee
|
|||||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||||
def send(from_wallet: str, to_address: str, amount: float, fee: float, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
def send(from_wallet: str, to_address: str, amount: float, fee: float, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
||||||
"""Send transaction from one wallet to another"""
|
"""Send transaction from one wallet to another"""
|
||||||
if password_file:
|
# Password resolution priority:
|
||||||
|
# 1. --password flag
|
||||||
|
# 2. --password-file flag
|
||||||
|
# 3. AITBC_WALLET_PASSWORD environment variable
|
||||||
|
# 4. Check if wallet is unencrypted (skip password)
|
||||||
|
# 5. Interactive getpass prompt (only if TTY)
|
||||||
|
|
||||||
|
if password is not None:
|
||||||
|
# Password provided via flag (even if empty string)
|
||||||
|
pass
|
||||||
|
elif password_file:
|
||||||
with open(password_file) as f:
|
with open(password_file) as f:
|
||||||
password = f.read().strip()
|
password = f.read().strip()
|
||||||
elif not password:
|
elif "AITBC_WALLET_PASSWORD" in os.environ:
|
||||||
# Check if we're in a TTY environment
|
# Environment variable is set (even if empty)
|
||||||
if not sys.stdin.isatty():
|
password = os.environ["AITBC_WALLET_PASSWORD"]
|
||||||
# Non-interactive: try environment variable
|
else:
|
||||||
password = os.environ.get("AITBC_WALLET_PASSWORD")
|
# Check if wallet is unencrypted
|
||||||
if not password:
|
keystore_dir = DEFAULT_KEYSTORE_DIR
|
||||||
|
sender_keystore = keystore_dir / f"{from_wallet}.json"
|
||||||
|
if sender_keystore.exists():
|
||||||
|
with open(sender_keystore) as f:
|
||||||
|
sender_data = json.load(f)
|
||||||
|
# If wallet has no encrypted_private_key, it's unencrypted
|
||||||
|
if not sender_data.get("encrypted_private_key"):
|
||||||
|
password = "" # Empty password for unencrypted wallets
|
||||||
|
else:
|
||||||
|
# Wallet is encrypted, need password
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
||||||
|
raise click.Abort()
|
||||||
|
else:
|
||||||
|
import getpass
|
||||||
|
try:
|
||||||
|
password = getpass.getpass("Enter wallet password: ")
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Password prompt failed: {e}")
|
||||||
|
raise click.Abort()
|
||||||
|
else:
|
||||||
|
# Wallet file doesn't exist, will fail later in _send_transaction_impl
|
||||||
|
if not sys.stdin.isatty():
|
||||||
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
||||||
raise click.Abort()
|
raise click.Abort()
|
||||||
else:
|
else:
|
||||||
# Interactive: prompt for password
|
import getpass
|
||||||
import getpass
|
try:
|
||||||
try:
|
password = getpass.getpass("Enter wallet password: ")
|
||||||
password = getpass.getpass("Enter wallet password: ")
|
except Exception as e:
|
||||||
except Exception as e:
|
error(f"Password prompt failed: {e}")
|
||||||
error(f"Password prompt failed: {e}")
|
raise click.Abort()
|
||||||
raise click.Abort()
|
|
||||||
|
|
||||||
if not rpc_url:
|
if not rpc_url:
|
||||||
rpc_url = DEFAULT_RPC_URL
|
rpc_url = DEFAULT_RPC_URL
|
||||||
@@ -169,25 +200,72 @@ def send(from_wallet: str, to_address: str, amount: float, fee: float, password:
|
|||||||
@click.option('--rpc-url', help='Blockchain RPC URL')
|
@click.option('--rpc-url', help='Blockchain RPC URL')
|
||||||
def batch(transactions_file: str, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
def batch(transactions_file: str, password: Optional[str], password_file: Optional[str], rpc_url: Optional[str]):
|
||||||
"""Send batch transactions"""
|
"""Send batch transactions"""
|
||||||
if password_file:
|
# Password resolution priority:
|
||||||
|
# 1. --password flag
|
||||||
|
# 2. --password-file flag
|
||||||
|
# 3. AITBC_WALLET_PASSWORD environment variable
|
||||||
|
# 4. Check if wallet is unencrypted (skip password)
|
||||||
|
# 5. Interactive getpass prompt (only if TTY)
|
||||||
|
|
||||||
|
if password is not None:
|
||||||
|
# Password provided via flag (even if empty string)
|
||||||
|
pass
|
||||||
|
elif password_file:
|
||||||
with open(password_file) as f:
|
with open(password_file) as f:
|
||||||
password = f.read().strip()
|
password = f.read().strip()
|
||||||
elif not password:
|
elif "AITBC_WALLET_PASSWORD" in os.environ:
|
||||||
# Check if we're in a TTY environment
|
# Environment variable is set (even if empty)
|
||||||
if not sys.stdin.isatty():
|
password = os.environ["AITBC_WALLET_PASSWORD"]
|
||||||
# Non-interactive: try environment variable
|
else:
|
||||||
password = os.environ.get("AITBC_WALLET_PASSWORD")
|
# Check if first wallet is unencrypted
|
||||||
if not password:
|
with open(transactions_file) as f:
|
||||||
|
transactions_data = json.load(f)
|
||||||
|
if transactions_data:
|
||||||
|
first_wallet = transactions_data[0].get('from_wallet')
|
||||||
|
keystore_dir = DEFAULT_KEYSTORE_DIR
|
||||||
|
sender_keystore = keystore_dir / f"{first_wallet}.json"
|
||||||
|
if sender_keystore.exists():
|
||||||
|
with open(sender_keystore) as f:
|
||||||
|
sender_data = json.load(f)
|
||||||
|
# If wallet has no encrypted_private_key, it's unencrypted
|
||||||
|
if not sender_data.get("encrypted_private_key"):
|
||||||
|
password = "" # Empty password for unencrypted wallets
|
||||||
|
else:
|
||||||
|
# Wallet is encrypted, need password
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
||||||
|
raise click.Abort()
|
||||||
|
else:
|
||||||
|
import getpass
|
||||||
|
try:
|
||||||
|
password = getpass.getpass("Enter wallet password: ")
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Password prompt failed: {e}")
|
||||||
|
raise click.Abort()
|
||||||
|
else:
|
||||||
|
# Wallet file doesn't exist
|
||||||
|
if not sys.stdin.isatty():
|
||||||
|
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
||||||
|
raise click.Abort()
|
||||||
|
else:
|
||||||
|
import getpass
|
||||||
|
try:
|
||||||
|
password = getpass.getpass("Enter wallet password: ")
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Password prompt failed: {e}")
|
||||||
|
raise click.Abort()
|
||||||
|
else:
|
||||||
|
# Empty transactions file
|
||||||
|
if not sys.stdin.isatty():
|
||||||
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
error("No TTY available for password prompt. Use --password or --password-file, or set AITBC_WALLET_PASSWORD environment variable.")
|
||||||
raise click.Abort()
|
raise click.Abort()
|
||||||
else:
|
else:
|
||||||
# Interactive: prompt for password
|
import getpass
|
||||||
import getpass
|
try:
|
||||||
try:
|
password = getpass.getpass("Enter wallet password: ")
|
||||||
password = getpass.getpass("Enter wallet password: ")
|
except Exception as e:
|
||||||
except Exception as e:
|
error(f"Password prompt failed: {e}")
|
||||||
error(f"Password prompt failed: {e}")
|
raise click.Abort()
|
||||||
raise click.Abort()
|
|
||||||
|
|
||||||
if not rpc_url:
|
if not rpc_url:
|
||||||
rpc_url = DEFAULT_RPC_URL
|
rpc_url = DEFAULT_RPC_URL
|
||||||
|
|||||||
Reference in New Issue
Block a user