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
This commit is contained in:
aitbc
2026-04-25 20:08:09 +02:00
parent 8d69dd6685
commit e4df4caaeb
3 changed files with 18 additions and 9 deletions

View File

@@ -278,18 +278,16 @@ class PoAProposer:
# Use original tx_data from mempool to preserve type and payload # Use original tx_data from mempool to preserve type and payload
tx_data_for_transition = tx.content.copy() tx_data_for_transition = tx.content.copy()
tx_data_for_transition["nonce"] = sender_account.nonce 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( success, error_msg = state_transition.apply_transaction(
session, self._config.chain_id, tx_data_for_transition, tx.tx_hash session, self._config.chain_id, tx_data_for_transition, tx.tx_hash
) )
if not success: 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 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 # Check if transaction already exists in database
existing_tx = session.exec( existing_tx = session.exec(
select(Transaction).where( select(Transaction).where(

View File

@@ -112,6 +112,7 @@ def _normalize_transaction_data(tx_data: Dict[str, Any], chain_id: str) -> Dict[
"from": sender.strip(), "from": sender.strip(),
"to": recipient.strip(), "to": recipient.strip(),
"amount": amount, "amount": amount,
"value": amount, # Add value field for state transition compatibility
"fee": fee, "fee": fee,
"nonce": nonce, "nonce": nonce,
"payload": payload, "payload": payload,

View File

@@ -10,6 +10,7 @@ from __future__ import annotations
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from sqlmodel import Session, select from sqlmodel import Session, select
from sqlalchemy import select, text
from datetime import datetime from datetime import datetime
from ..models import Account, Transaction, Receipt from ..models import Account, Transaction, Receipt
@@ -165,6 +166,7 @@ class StateTransition:
Returns: Returns:
Tuple of (success, error_message) Tuple of (success, error_message)
""" """
logger.info(f"apply_transaction called for tx {tx_hash}, tx_data keys: {list(tx_data.keys())}")
# Validate first # Validate first
is_valid, error_msg = self.validate_transaction(session, chain_id, tx_data, tx_hash) is_valid, error_msg = self.validate_transaction(session, chain_id, tx_data, tx_hash)
if not is_valid: if not is_valid:
@@ -198,7 +200,7 @@ class StateTransition:
else: else:
tx_type = "TRANSFER" tx_type = "TRANSFER"
# Apply balance changes # Apply balance changes using explicit SQL UPDATE
value = tx_data.get("value", 0) value = tx_data.get("value", 0)
fee = tx_data.get("fee", 0) fee = tx_data.get("fee", 0)
@@ -209,12 +211,20 @@ class StateTransition:
total_cost = value + fee total_cost = value + fee
recipient_account = session.get(Account, (chain_id, recipient_addr)) recipient_account = session.get(Account, (chain_id, recipient_addr))
sender_account.balance -= total_cost # Use explicit SQL UPDATE to ensure persistence
sender_account.nonce += 1 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 # For MESSAGE transactions, skip recipient balance change
if tx_type != "MESSAGE": 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 # For RECEIPT_CLAIM transactions, mint reward and update receipt status
if tx_type == "RECEIPT_CLAIM": if tx_type == "RECEIPT_CLAIM":