Use Atomic Operations to Prevent State Inconsistencies
Severity: High
Description
Functions that modify multiple resources without transactional integrity may leave the system in an inconsistent state if an error occurs mid-operation. In a blockchain environment, where state consistency is critical to maintain trust and operational stability, this can lead to data corruption, incorrect balances, or unexpected behavior.
What should be avoided
Modifying multiple resources without rollback mechanisms can lead to partial updates if an error occurs:
#![allow(unused)] fn main() { fn transfer_funds(sender: &T::AccountId, recipient: &T::AccountId, amount: u32) { reduce_balance(sender, amount); // Ignoring the result. No rollback if this fails. let _ = increase_balance(recipient, amount); } }
In this example:
- Lack of Error Handling: If
increase_balance
fails, there is no mechanism to revert the changes made byreduce_balance
. This results in an inconsistent state where the sender’s balance is reduced, but the recipient’s balance remains unchanged, violating the atomicity of the transaction.
Best practice
Use atomic operations or implement rollback logic to ensure all changes are applied consistently:
#![allow(unused)] fn main() { // Add the Result<(), Error> return type to allow a // rollback if an error occurs in any of the functions. fn transfer_funds(sender: &T::AccountId, recipient: &T::AccountId, amount: u32) -> Result<(), Error> { reduce_balance(sender, amount)?; increase_balance(recipient, amount)?; Ok(()) } }
In this example:
- Transactional Integrity: If any part of the operation fails, the function returns an error, and no changes are committed.
- Consistency: Both
reduce_balance
andincrease_balance
succeed or fail together, maintaining the state integrity of the system.
Ensuring atomicity in such operations prevents inconsistencies and safeguards the reliability of the blockchain state.