From e4df4caaeb37c65742cd6244dae03d251ad82c70 Mon Sep 17 00:00:00 2001 From: aitbc Date: Sat, 25 Apr 2026 20:08:09 +0200 Subject: [PATCH] fix: use explicit SQL UPDATE for balance changes and add value field mapping Changed transaction failure logging from error to warning level in PoA proposer. Removed immediate session.commit() after state transition as balance changes are now persisted via explicit SQL UPDATE statements. Added "value" field mapping from "amount" in transaction normalization and PoA proposer to ensure state transition compatibility. Replaced SQLAlchemy ORM balance updates with explicit SQL UPDATE statements using --- .../src/aitbc_chain/consensus/poa.py | 8 +++----- .../src/aitbc_chain/rpc/router.py | 1 + .../src/aitbc_chain/state/state_transition.py | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) 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":