Overview
The Executor is responsible for executing transactions and blocks, managing state transitions, and generating execution receipts. It handles transfers, contract deployments, and contract calls with proper balance, nonce, and gas validation.
Executor
Block execution engine that processes transactions and updates world state.
Constructor
Create a new executor.pub fn new(state: &'a StateManager<'a>) -> Self
State manager for account operations
Methods
execute_transaction
Execute a single transaction.This method:
- Verifies the nonce matches the account’s current nonce
- Checks the sender has sufficient balance for value + gas
- Increments the sender’s nonce
- Deducts max gas cost from sender
- Executes the transaction (transfer, deploy, or call)
- Refunds unused gas to sender
pub fn execute_transaction(&self, tx: &Transaction) -> Result<TransactionReceipt>
The transaction to execute
Result
Result<TransactionReceipt>
Returns a receipt with execution details, including success status, gas used, and any errors
execute_block
Execute all transactions in a block.Processes each transaction sequentially and aggregates the results into a block execution result.pub fn execute_block(&self, block: &Block) -> Result<BlockExecutionResult>
Result
Result<BlockExecutionResult>
Returns execution results for all transactions, total gas used, and new state root
TransactionReceipt
Receipt for a single transaction execution.
pub struct TransactionReceipt {
pub tx_hash: Hash,
pub success: bool,
pub gas_used: u64,
pub contract_address: Option<Address>,
pub error: Option<String>,
}
Hash of the executed transaction
Whether the transaction executed successfully
Amount of gas consumed by the transaction
Address of deployed contract (only for deployment transactions)
Error message if execution failed
BlockExecutionResult
Result of executing an entire block.
pub struct BlockExecutionResult {
pub block_hash: Hash,
pub receipts: Vec<TransactionReceipt>,
pub total_gas_used: u64,
pub state_root: Hash,
}
Hash of the executed block
Receipts for all transactions in the block
Total gas consumed by all transactions
New state root after execution
ExecutionError
Errors that can occur during execution.
pub enum ExecutionError {
Storage(StorageError),
InsufficientBalance { required: u64, available: u64 },
InvalidNonce { expected: u64, got: u64 },
OutOfGas,
Reverted,
VmError(String),
}
Sender doesn’t have enough balance for value + gas
Transaction nonce doesn’t match account nonce
Transaction ran out of gas during execution
Contract execution reverted
Transaction Types
The executor handles three types of transactions:
Transfer
Simple value transfer from one account to another.
- Gas cost: 21,000 units
- Requirements: Sender must have
value + (21,000 * gas_price) balance
- Effect: Transfers value from sender to recipient
Contract Deployment
Deploys a new smart contract.
- Gas cost: 32,000 base + 200 per bytecode byte
- Requirements: Sender must have deployment value + gas cost
- Effect: Creates new contract account with bytecode hash
- Returns: Contract address in receipt
Contract Call
Calls an existing smart contract.
- Gas cost: 21,000 base + 68 per calldata byte
- Requirements: Contract must exist and have code
- Effect: Executes contract code (VM execution in full implementation)
- Returns: Execution result
Usage Example
use minichain_chain::Executor;
use minichain_core::{Transaction, Keypair, Address, Account};
use minichain_storage::{Storage, StateManager};
// Setup storage and state
let storage = Storage::open_temporary().unwrap();
let state = StateManager::new(&storage);
// Create executor
let executor = Executor::new(&state);
// Setup accounts
let keypair = Keypair::generate();
let from = keypair.address();
let to = Address::from_bytes([2u8; 20]);
// Fund the sender
state.put_account(&from, &Account::new_user(100_000)).unwrap();
// Execute a transfer
let tx = Transaction::transfer(from, to, 1000, 0, 1).signed(&keypair);
let receipt = executor.execute_transaction(&tx).unwrap();
if receipt.success {
println!("Transfer successful!");
println!("Gas used: {}", receipt.gas_used);
} else {
println!("Transfer failed: {}", receipt.error.unwrap());
}
// Check balances after execution
let sender_balance = state.get_balance(&from).unwrap();
let recipient_balance = state.get_balance(&to).unwrap();
println!("Sender balance: {}", sender_balance);
println!("Recipient balance: {}", recipient_balance);
// Execute a contract deployment
let bytecode = vec![0x60, 0x80, 0x60, 0x40, 0x52];
let deploy_tx = Transaction::deploy(from, bytecode, 1, 100_000, 1).signed(&keypair);
let deploy_receipt = executor.execute_transaction(&deploy_tx).unwrap();
if deploy_receipt.success {
let contract_addr = deploy_receipt.contract_address.unwrap();
println!("Contract deployed at: {:?}", contract_addr);
// Verify contract exists
let contract = state.get_account(&contract_addr).unwrap();
assert!(contract.is_contract());
}
Block Execution Example
use minichain_chain::Executor;
use minichain_core::{Block, Transaction, Hash, Keypair, Address, Account};
use minichain_storage::{Storage, StateManager};
// Setup
let storage = Storage::open_temporary().unwrap();
let state = StateManager::new(&storage);
let executor = Executor::new(&state);
// Create accounts
let keypair1 = Keypair::generate();
let keypair2 = Keypair::generate();
let addr1 = keypair1.address();
let addr2 = keypair2.address();
state.put_account(&addr1, &Account::new_user(100_000)).unwrap();
state.put_account(&addr2, &Account::new_user(100_000)).unwrap();
// Create transactions
let tx1 = Transaction::transfer(addr1, addr2, 1000, 0, 1).signed(&keypair1);
let tx2 = Transaction::transfer(addr2, addr1, 500, 0, 1).signed(&keypair2);
// Create block
let block = Block::new(
1,
Hash::ZERO,
vec![tx1, tx2],
Hash::ZERO,
addr1
);
// Execute entire block
let result = executor.execute_block(&block).unwrap();
println!("Block executed: {:?}", result.block_hash);
println!("Total gas used: {}", result.total_gas_used);
println!("Transaction count: {}", result.receipts.len());
for (i, receipt) in result.receipts.iter().enumerate() {
println!("Transaction {}: success={}, gas={}",
i, receipt.success, receipt.gas_used
);
}
Gas Calculation
The executor uses simplified gas calculations:
| Operation | Gas Cost |
|---|
| Transfer | 21,000 |
| Contract deployment base | 32,000 |
| Deployment per byte | 200 |
| Contract call base | 21,000 |
| Call data per byte | 68 |
In a production implementation, gas costs would be more granular and include costs for storage operations, computation, memory usage, etc.
Implementation Notes
- Failed transactions still increment nonces and consume gas (as in Ethereum)
- The executor validates nonces and balances before execution
- Unused gas is refunded to the sender after execution
- Contract deployments calculate the contract address deterministically from sender address and nonce
- The current implementation has a simplified VM; a full implementation would integrate WASM or EVM
See Also