pchain_runtime/execution/
phase.rs

1/*
2    Copyright © 2023, ParallelChain Lab
3    Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
4*/
5
6//! Defines structures and functions which are useful in state transition across common phases.
7//!
8//! Common Phases include:
9//! - [Pre-Charge](https://github.com/parallelchain-io/parallelchain-protocol/blob/master/Runtime.md#pre-charge): simple checks to ensure
10//! transaction can be included in a block.
11//! - [Charge](https://github.com/parallelchain-io/parallelchain-protocol/blob/master/Runtime.md#charge): refunds the amount of gas charged
12//! in the pre-charge step that wasn't used in the transaction's execution. It then transfers fee to the proposer and the treasury.
13//!
14//! The actual command execution happens in Commands Phase. It is implemented in modules [account](crate::execution::account) and
15//! [protocol](crate::execution::protocol).
16
17use pchain_world_state::storage::WorldStateStorage;
18
19use crate::{
20    formulas::{TOTAL_BASE_FEE, TREASURY_CUT_OF_BASE_FEE},
21    gas,
22    transition::StateChangesResult,
23    TransitionError,
24};
25
26use super::state::ExecutionState;
27
28/// Pre-Charge is a Phase in State Transition. It transits state and returns gas consumption if success.
29pub(crate) fn pre_charge<S>(state: &mut ExecutionState<S>) -> Result<u64, TransitionError>
30where
31    S: WorldStateStorage + Send + Sync + Clone + 'static,
32{
33    let init_gas = gas::tx_inclusion_cost(state.tx_size, state.commands_len);
34    if state.tx.gas_limit < init_gas {
35        return Err(TransitionError::PreExecutionGasExhausted);
36    }
37
38    let signer = state.tx.signer;
39    let origin_nonce = state.ws.nonce(signer);
40    if state.tx.nonce != origin_nonce {
41        return Err(TransitionError::WrongNonce);
42    }
43
44    let origin_balance = state.ws.balance(state.tx.signer);
45    let gas_limit = state.tx.gas_limit;
46    let base_fee = state.bd.this_base_fee;
47    let priority_fee = state.tx.priority_fee_per_gas;
48
49    // pre_charge = gas_limit * (base_fee + priority_fee)
50    let pre_charge = base_fee
51        .checked_add(priority_fee)
52        .and_then(|fee| gas_limit.checked_mul(fee) )
53        .ok_or(TransitionError::NotEnoughBalanceForGasLimit)?; // Overflow check
54
55    // pre_charged_balance = origin_balance - pre_charge
56    let pre_charged_balance = origin_balance
57        .checked_sub(pre_charge)
58        .ok_or(TransitionError::NotEnoughBalanceForGasLimit)?; // pre_charge > origin_balance
59
60    // Apply change directly to World State
61    state.ws.with_commit().set_balance(signer, pre_charged_balance);
62
63    state.set_gas_consumed(init_gas);
64    Ok(init_gas)
65}
66
67/// finalize gas consumption of this Command Phase. Return Error GasExhaust if gas has already been exhausted
68pub(crate) fn finalize_gas_consumption<S>(
69    mut state: ExecutionState<S>,
70) -> Result<ExecutionState<S>, StateChangesResult<S>>
71where
72    S: pchain_world_state::storage::WorldStateStorage + Send + Sync + Clone + 'static,
73{
74    let gas_used = state.total_gas_to_be_consumed();
75    if state.tx.gas_limit < gas_used {
76        return Err(abort(state, TransitionError::ExecutionProperGasExhausted));
77    }
78    state.set_gas_consumed(gas_used);
79    Ok(state)
80}
81
82/// Abort is operation that causes all World State sets in the Commands Phase to be reverted.
83pub(crate) fn abort<S>(
84    mut state: ExecutionState<S>,
85    transition_err: TransitionError,
86) -> StateChangesResult<S>
87where
88    S: pchain_world_state::storage::WorldStateStorage + Send + Sync + Clone + 'static,
89{
90    state.revert_changes();
91    let gas_used = std::cmp::min(state.tx.gas_limit, state.total_gas_to_be_consumed());
92    state.set_gas_consumed(gas_used);
93    charge(state, Some(transition_err))
94}
95
96/// Charge is a Phase in State Transition. It finalizes balance of accounts to world state.
97pub(crate) fn charge<S>(
98    mut state: ExecutionState<S>,
99    transition_result: Option<TransitionError>,
100) -> StateChangesResult<S>
101where
102    S: WorldStateStorage + Send + Sync + Clone + 'static,
103{
104    let signer = state.tx.signer;
105    let base_fee = state.bd.this_base_fee;
106    let priority_fee = state.tx.priority_fee_per_gas;
107    let gas_used = std::cmp::min(state.gas_consumed(), state.tx.gas_limit); // Safety for avoiding overflow
108    let gas_unused = state.tx.gas_limit.saturating_sub(gas_used);
109
110    // Finalize signer's balance
111    let signer_balance = state.purge_balance(signer);
112    let new_signer_balance = signer_balance + gas_unused * (base_fee + priority_fee);
113
114    // Transfer priority fee to Proposer
115    let proposer_address = state.bd.proposer_address;
116    let mut proposer_balance = state.purge_balance(proposer_address);
117    if signer == proposer_address {
118        proposer_balance = new_signer_balance;
119    }
120    let new_proposer_balance = proposer_balance.saturating_add(gas_used * priority_fee);
121
122    // Burn the gas to Treasury account
123    let treasury_address = state.bd.treasury_address;
124    let mut treasury_balance = state.purge_balance(treasury_address);
125    if signer == treasury_address {
126        treasury_balance = new_signer_balance;
127    }
128    if proposer_address == treasury_address {
129        treasury_balance = new_proposer_balance;
130    }
131    let new_treasury_balance = treasury_balance
132        .saturating_add((gas_used * base_fee * TREASURY_CUT_OF_BASE_FEE) / TOTAL_BASE_FEE);
133
134    // Commit updated balances
135    state
136        .ws
137        .with_commit()
138        .set_balance(signer, new_signer_balance);
139    state
140        .ws
141        .with_commit()
142        .set_balance(proposer_address, new_proposer_balance);
143    state
144        .ws
145        .with_commit()
146        .set_balance(treasury_address, new_treasury_balance);
147
148    // Commit Signer's Nonce
149    let nonce = state.ws.nonce(signer).saturating_add(1);
150    state.ws.with_commit().set_nonce(signer, nonce);
151
152    state.set_gas_consumed(gas_used);
153    StateChangesResult::new(state, transition_result)
154}