diff --git a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py index 6d9c4366..600e75b4 100755 --- a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py +++ b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py @@ -278,18 +278,16 @@ class PoAProposer: # Use original tx_data from mempool to preserve type and payload tx_data_for_transition = tx.content.copy() tx_data_for_transition["nonce"] = sender_account.nonce + # Map "amount" to "value" for state transition compatibility + tx_data_for_transition["value"] = tx_data_for_transition.get("amount", 0) success, error_msg = state_transition.apply_transaction( session, self._config.chain_id, tx_data_for_transition, tx.tx_hash ) if not success: - self._logger.error(f"[PROPOSE] Failed to apply transaction {tx.tx_hash}: {error_msg}") + self._logger.warning(f"[PROPOSE] Failed to apply transaction {tx.tx_hash}: {error_msg}") continue - # Commit the balance changes immediately after successful state transition - session.commit() - self._logger.info(f"[PROPOSE] Committed balance changes for tx {tx.tx_hash}") - # Check if transaction already exists in database existing_tx = session.exec( select(Transaction).where( diff --git a/apps/blockchain-node/src/aitbc_chain/rpc/router.py b/apps/blockchain-node/src/aitbc_chain/rpc/router.py index 2d710400..5be7f98a 100755 --- a/apps/blockchain-node/src/aitbc_chain/rpc/router.py +++ b/apps/blockchain-node/src/aitbc_chain/rpc/router.py @@ -112,6 +112,7 @@ def _normalize_transaction_data(tx_data: Dict[str, Any], chain_id: str) -> Dict[ "from": sender.strip(), "to": recipient.strip(), "amount": amount, + "value": amount, # Add value field for state transition compatibility "fee": fee, "nonce": nonce, "payload": payload, 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 64088881..bd577d8b 100644 --- a/apps/blockchain-node/src/aitbc_chain/state/state_transition.py +++ b/apps/blockchain-node/src/aitbc_chain/state/state_transition.py @@ -10,6 +10,7 @@ from __future__ import annotations from typing import Dict, List, Optional, Tuple from sqlmodel import Session, select +from sqlalchemy import select, text from datetime import datetime from ..models import Account, Transaction, Receipt @@ -165,6 +166,7 @@ class StateTransition: Returns: Tuple of (success, error_message) """ + logger.info(f"apply_transaction called for tx {tx_hash}, tx_data keys: {list(tx_data.keys())}") # Validate first is_valid, error_msg = self.validate_transaction(session, chain_id, tx_data, tx_hash) if not is_valid: @@ -198,7 +200,7 @@ class StateTransition: else: tx_type = "TRANSFER" - # Apply balance changes + # Apply balance changes using explicit SQL UPDATE value = tx_data.get("value", 0) fee = tx_data.get("fee", 0) @@ -209,12 +211,20 @@ class StateTransition: total_cost = value + fee recipient_account = session.get(Account, (chain_id, recipient_addr)) - sender_account.balance -= total_cost - sender_account.nonce += 1 + # Use explicit SQL UPDATE to ensure persistence + logger.info(f"Updating sender balance: {sender_addr} -= {total_cost}") + session.execute( + text("UPDATE account SET balance = balance - :total_cost, nonce = nonce + 1 WHERE chain_id = :chain_id AND address = :sender_addr"), + {"total_cost": total_cost, "chain_id": chain_id, "sender_addr": sender_addr} + ) # For MESSAGE transactions, skip recipient balance change if tx_type != "MESSAGE": - recipient_account.balance += value + logger.info(f"Updating recipient balance: {recipient_addr} += {value}") + session.execute( + text("UPDATE account SET balance = balance + :value WHERE chain_id = :chain_id AND address = :recipient_addr"), + {"value": value, "chain_id": chain_id, "recipient_addr": recipient_addr} + ) # For RECEIPT_CLAIM transactions, mint reward and update receipt status if tx_type == "RECEIPT_CLAIM":