diff --git a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py index 007c58b9..3c46c244 100755 --- a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py +++ b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py @@ -24,29 +24,29 @@ def _sanitize_metric_suffix(value: str) -> str: return sanitized or "unknown" -# def _compute_state_root(session: Session, chain_id: str) -> str: -# """Compute state root from current account state.""" -# try: -# state_manager = StateManager() -# -# # Get all accounts for this chain -# accounts = session.exec( -# select(Account).where(Account.chain_id == chain_id) -# ).all() -# -# # Convert to dictionary -# account_dict = {acc.address: acc for acc in accounts} -# -# # Compute state root -# root = state_manager.compute_state_root(account_dict) -# -# # Return as hex string -# return '0x' + root.hex() -# except Exception as e: -# # If state root computation fails, return None for now -# # This can happen during genesis block creation when accounts don't exist yet -# logger.warning(f"Failed to compute state root: {e}") -# return None +def _compute_state_root(session: Session, chain_id: str) -> str: + """Compute state root from current account state.""" + try: + state_manager = StateManager() + + # Get all accounts for this chain + accounts = session.exec( + select(Account).where(Account.chain_id == chain_id) + ).all() + + # Convert to dictionary + account_dict = {acc.address: acc for acc in accounts} + + # Compute state root + root = state_manager.compute_state_root(account_dict) + + # Return as hex string + return '0x' + root.hex() + except Exception as e: + # If state root computation fails, return None for now + # This can happen during genesis block creation when accounts don't exist yet + logger.warning(f"Failed to compute state root: {e}") + return None @@ -287,6 +287,9 @@ class PoAProposer: # Compute block hash with transaction data block_hash = self._compute_block_hash(next_height, parent_hash, timestamp, processed_txs) + # Compute state root from account state + state_root = _compute_state_root(session, self._config.chain_id) + block = Block( chain_id=self._config.chain_id, height=next_height, @@ -295,7 +298,7 @@ class PoAProposer: proposer=self._config.proposer_id, timestamp=timestamp, tx_count=len(processed_txs), - state_root=None, # Temporarily disabled for debugging + state_root=state_root, ) session.add(block) session.commit() @@ -358,6 +361,9 @@ class PoAProposer: self._logger.info(f"Genesis block with hash {block_hash} already exists, skipping creation") return + # Compute state root for genesis block + state_root = _compute_state_root(session, self._config.chain_id) + genesis = Block( chain_id=self._config.chain_id, height=0, @@ -366,7 +372,7 @@ class PoAProposer: proposer="genesis", # Use "genesis" as the proposer for genesis block to avoid hash conflicts timestamp=timestamp, tx_count=0, - state_root=None, # Temporarily disabled for debugging + state_root=state_root, ) session.add(genesis) try: diff --git a/apps/blockchain-node/src/aitbc_chain/economics/gas.py b/apps/blockchain-node/src/aitbc_chain/economics/gas.py index b917daf6..acaccffc 100644 --- a/apps/blockchain-node/src/aitbc_chain/economics/gas.py +++ b/apps/blockchain-node/src/aitbc_chain/economics/gas.py @@ -17,6 +17,10 @@ class GasType(Enum): VALIDATOR_STAKE = "validator_stake" AGENT_OPERATION = "agent_operation" CONSENSUS = "consensus" + MESSAGE = "message" + RECEIPT_CLAIM = "receipt_claim" + GPU_MARKETPLACE = "gpu_marketplace" + EXCHANGE = "exchange" @dataclass class GasSchedule: @@ -91,6 +95,12 @@ class GasManager: base_gas=80000, gas_per_byte=0, complexity_multiplier=1.0 + ), + GasType.MESSAGE: GasSchedule( + gas_type=GasType.MESSAGE, + base_gas=21000, + gas_per_byte=0, + complexity_multiplier=1.0 ) } diff --git a/apps/blockchain-node/src/aitbc_chain/rpc/router.py b/apps/blockchain-node/src/aitbc_chain/rpc/router.py index 300519e5..15f1cf1f 100755 --- a/apps/blockchain-node/src/aitbc_chain/rpc/router.py +++ b/apps/blockchain-node/src/aitbc_chain/rpc/router.py @@ -173,7 +173,7 @@ def _serialize_receipt(receipt: Receipt) -> Dict[str, Any]: class TransactionRequest(BaseModel): - type: str = Field(description="Transaction type, e.g. TRANSFER, RECEIPT_CLAIM, GPU_MARKETPLACE, EXCHANGE") + type: str = Field(description="Transaction type, e.g. TRANSFER, RECEIPT_CLAIM, GPU_MARKETPLACE, EXCHANGE, MESSAGE") sender: str nonce: int fee: int = Field(ge=0) @@ -183,7 +183,7 @@ class TransactionRequest(BaseModel): @model_validator(mode="after") def normalize_type(self) -> "TransactionRequest": # type: ignore[override] normalized = self.type.upper() - valid_types = {"TRANSFER", "RECEIPT_CLAIM", "GPU_MARKETPLACE", "EXCHANGE"} + valid_types = {"TRANSFER", "RECEIPT_CLAIM", "GPU_MARKETPLACE", "EXCHANGE", "MESSAGE"} if normalized not in valid_types: raise ValueError(f"unsupported transaction type: {normalized}. Valid types: {valid_types}") self.type = normalized diff --git a/apps/blockchain-node/src/aitbc_chain/state/state_transition.py b/apps/blockchain-node/src/aitbc_chain/state/state_transition.py index 53f68ff4..038cbf14 100644 --- a/apps/blockchain-node/src/aitbc_chain/state/state_transition.py +++ b/apps/blockchain-node/src/aitbc_chain/state/state_transition.py @@ -68,20 +68,33 @@ class StateTransition: if tx_nonce != expected_nonce: return False, f"Invalid nonce for {sender_addr}: expected {expected_nonce}, got {tx_nonce}" + # Get transaction type + tx_type = tx_data.get("type", "TRANSFER").upper() + # Validate balance value = tx_data.get("value", 0) fee = tx_data.get("fee", 0) - total_cost = value + fee + + # For MESSAGE transactions, value must be 0 + if tx_type == "MESSAGE" and value != 0: + return False, f"MESSAGE transactions must have value=0, got {value}" + + # For MESSAGE transactions, only check fee + if tx_type == "MESSAGE": + total_cost = fee + else: + total_cost = value + fee if sender_account.balance < total_cost: return False, f"Insufficient balance for {sender_addr}: {sender_account.balance} < {total_cost}" - # Get recipient account + # Get recipient account (not required for MESSAGE) recipient_addr = tx_data.get("to") - recipient_account = session.get(Account, (chain_id, recipient_addr)) - - if not recipient_account: - return False, f"Recipient account not found: {recipient_addr}" + if tx_type != "MESSAGE": + recipient_account = session.get(Account, (chain_id, recipient_addr)) + + if not recipient_account: + return False, f"Recipient account not found: {recipient_addr}" return True, "Transaction validated successfully" @@ -114,17 +127,27 @@ class StateTransition: recipient_addr = tx_data.get("to") sender_account = session.get(Account, (chain_id, sender_addr)) - recipient_account = session.get(Account, (chain_id, recipient_addr)) + + # Get transaction type + tx_type = tx_data.get("type", "TRANSFER").upper() # Apply balance changes value = tx_data.get("value", 0) fee = tx_data.get("fee", 0) - total_cost = value + fee + + # For MESSAGE transactions, only deduct fee + if tx_type == "MESSAGE": + total_cost = fee + else: + total_cost = value + fee + recipient_account = session.get(Account, (chain_id, recipient_addr)) sender_account.balance -= total_cost sender_account.nonce += 1 - recipient_account.balance += value + # For MESSAGE transactions, skip recipient balance change + if tx_type != "MESSAGE": + recipient_account.balance += value # Mark transaction as processed self._processed_tx_hashes.add(tx_hash) @@ -132,7 +155,7 @@ class StateTransition: logger.info( f"Applied transaction {tx_hash}: " - f"{sender_addr} -> {recipient_addr}, value={value}, fee={fee}" + f"{sender_addr} -> {recipient_addr}, value={value}, fee={fee}, type={tx_type}" ) return True, "Transaction applied successfully" diff --git a/scripts/setup.sh b/scripts/setup.sh index e1582008..29288393 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -131,6 +131,75 @@ setup_runtime_directories() { success "Runtime directories setup completed" } +# Generate UUID +generate_uuid() { + if [ -f /proc/sys/kernel/random/uuid ]; then + cat /proc/sys/kernel/random/uuid + else + python3 -c "import uuid; print(uuid.uuid4())" + fi +} + +# Setup unique node identities +setup_node_identities() { + log "Setting up unique node identities..." + + # Generate unique proposer_id if not already set in /etc/aitbc/.env + if [ ! -f "/etc/aitbc/.env" ]; then + log "/etc/aitbc/.env does not exist, creating with unique IDs..." + PROPOSER_ID="ait1$(generate_uuid | tr -d '-')" + P2P_NODE_ID="node-$(generate_uuid | tr -d '-')" + cat > /etc/aitbc/.env << EOF +# AITBC Blockchain Configuration +# Auto-generated unique node identities +proposer_id=$PROPOSER_ID +p2p_node_id=$P2P_NODE_ID +EOF + log "Created /etc/aitbc/.env with unique IDs" + else + # Check if proposer_id exists, if not add it + if ! grep -q "^proposer_id=" /etc/aitbc/.env; then + PROPOSER_ID="ait1$(generate_uuid | tr -d '-')" + echo "proposer_id=$PROPOSER_ID" >> /etc/aitbc/.env + log "Added unique proposer_id to /etc/aitbc/.env" + else + log "proposer_id already exists in /etc/aitbc/.env" + fi + fi + + # Create /etc/aitbc/node.env with unique p2p_node_id if not exists + if [ ! -f "/etc/aitbc/node.env" ]; then + P2P_NODE_ID="node-$(generate_uuid | tr -d '-')" + cat > /etc/aitbc/node.env << EOF +# AITBC Node-Specific Environment Configuration +# This file contains variables unique to each node - DO NOT share across nodes + +# Node Identity +NODE_ID=aitbc + +# P2P Configuration +# P2P node identity (must be unique for each node) +p2p_node_id=$P2P_NODE_ID + +# P2P Peers (comma-separated list of peer nodes) +# Format: hostname:port (e.g., "aitbc1:7070,aitbc2:7070") +p2p_peers= +EOF + log "Created /etc/aitbc/node.env with unique p2p_node_id" + else + # Check if p2p_node_id exists, if not add it + if ! grep -q "^p2p_node_id=" /etc/aitbc/node.env; then + P2P_NODE_ID="node-$(generate_uuid | tr -d '-')" + echo "p2p_node_id=$P2P_NODE_ID" >> /etc/aitbc/node.env + log "Added unique p2p_node_id to /etc/aitbc/node.env" + else + log "p2p_node_id already exists in /etc/aitbc/node.env" + fi + fi + + success "Node identities setup completed" +} + # Setup Python virtual environments setup_venvs() { log "Setting up Python virtual environments..." @@ -344,6 +413,7 @@ main() { check_prerequisites clone_repo setup_runtime_directories + setup_node_identities setup_venvs install_services create_health_check