the state transition model
// The State Transitioning Model
// A state transition is a change made when a transaction is applied to the current world state
// The state transitioning model does all all the necessary work to work out a valid new state root.
// 1) Nonce handling
// 2) Pre pay gas
// 3) Create a new state object if the recipient is 0*32. If the recipient is empty, create a new state object
// 4) Value transfer
// == If contract creation ==
// 4a) Attempt to run transaction data
// 4b) If valid, use result as code for the new state object
// == end ==
// 5) Run Script section
// 6) Derive new state root
type StateTransition struct {
gp *GasPool // used to track the usage of Gas inside the block
msg Message // Message Call
gas uint64
gasPrice *big.Int
initialGas *big.Int
value *big.Int // this is the transfer value
data []byte // the input data
state vm.StateDB // StateDB
evm *vm.EVM // virtual machine
}
// Message represents a message sent to a contract.
type Message interface {
From() common.Address
//FromFrontier() (common.Address, error)
To() *common.Address //
GasPrice() *big.Int // Message of GasPrice
Gas() *big.Int //message of GasLimit
Value() *big.Int
Nonce() uint64
CheckNonce() bool
Data() []byte
}
Structure
// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{
gp: gp,
evm: evm,
msg: msg,
gasPrice: msg.GasPrice(),
initialGas: new(big.Int),
value: msg.Value(),
data: msg.Data(),
state: evm.StateDB,
}
}
执行 Message
// ApplyMessage computes the new state by applying the given message
// against the old state within the environment.
// ApplyMessage returns the bytes returned by any EVM execution (if it took place),
// the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, *big.Int, bool, error) {
st := NewStateTransition(evm, msg, gp)
ret, _, gasUsed, failed, err := st.TransitionDb()
return ret, gasUsed, failed, err
}
TransitionDb
// TransitionDb will transition the state by applying the current message and returning the result
// including the required gas for the operation as well as the used gas. It returns an error if it
// failed. An error indicates a consensus issue.
// TransitionDb
func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, failed bool, err error) {
if err = st.preCheck(); err != nil {
return
}
msg := st.msg
sender := st.from() // err checked in preCheck
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
contractCreation := msg.To() == nil // if to is nil the it is considered a contract creation
// Pay intrinsic gas
// TODO convert to uint64
// calculate the first Gas
intrinsicGas := IntrinsicGas(st.data, contractCreation, homestead)
if intrinsicGas.BitLen() > 64 {
return nil, nil, nil, false, vm.ErrOutOfGas
}
if err = st.useGas(intrinsicGas.Uint64()); err != nil {
return nil, nil, nil, false, err
}
var (
evm = st.evm
// vm errors do not effect consensus and are therefor
// not assigned to err, except for insufficient balance
// error.
vmerr error
)
if contractCreation { // call the method of evm
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else {
// Increment the nonce for the next transaction
// Each transaction applied will trigger the increment
st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)
}
if vmerr != nil {
log.Debug("VM returned with error", "err", vmerr)
// The only possible consensus-error would be if there wasn't
// sufficient balance to make the transfer happen. The first
// balance transfer may never fail.
if vmerr == vm.ErrInsufficientBalance {
return nil, nil, nil, false, vmerr
}
}
requiredGas = new(big.Int).Set(st.gasUsed()) // calculate the amount of Gas used
st.refundGas() // help miners got the money
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(st.gasUsed(), st.gasPrice))
// The difference between requiredGas and gasUsed is gas refund
return ret, requiredGas, st.gasUsed(), vmerr != nil, err
}
Regarding the calculation of g0, the detailed introduction in the Yellow Book and the Yellow Book have certain parts and components in the if contractCreation && homestead {igas.SetUint64(params.TxGasContractCreation)
This is because Gtxcreate+Gtransaction = TxGasContractCreation
func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int {
igas := new(big.Int)
if contractCreation && homestead {
igas.SetUint64(params.TxGasContractCreation)
} else {
igas.SetUint64(params.TxGas)
}
if len(data) > 0 {
var nz int64
for _, byt := range data {
if byt != 0 {
nz++
}
}
m := big.NewInt(nz)
m.Mul(m, new(big.Int).SetUint64(params.TxDataNonZeroGas))
igas.Add(igas, m)
m.SetInt64(int64(len(data)) - nz)
m.Mul(m, new(big.Int).SetUint64(params.TxDataZeroGas))
igas.Add(igas, m)
}
return igas
}
Pre-execution check
func (st *StateTransition) preCheck() error {
msg := st.msg
sender := st.from()
// Make sure this transaction's nonce is correct
if msg.CheckNonce() {
nonce := st.state.GetNonce(sender.Address())
// The current local nonce needs to be the same as msg's Nonce. Otherwise the state is out of sync.
if nonce < msg.Nonce() {
return ErrNonceTooHigh
} else if nonce > msg.Nonce() {
return ErrNonceTooLow
}
}
return st.buyGas()
}
buyGas, to achieve Gas's withholding fee, first deduct your GasLimit * GasPrice money. Then return a part based on the calculated status.
func (st *StateTransition) buyGas() error {
mgas := st.msg.Gas()
if mgas.BitLen() > 64 {
return vm.ErrOutOfGas
}
mgval := new(big.Int).Mul(mgas, st.gasPrice)
var (
state = st.state
sender = st.from()
)
if state.GetBalance(sender.Address()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
if err := st.gp.SubGas(mgas); err != nil { // Subtracted from the gaspool of the block, because the block is used by GasLimit to limit the entire block of Gas.
return err
}
st.gas += mgas.Uint64()
st.initialGas.Set(mgas)
state.SubBalance(sender.Address(), mgval)
// Substract from the account address: GasLimit * GasPrice
return nil
}
Gax refunds, gas refund are meant to reward you with running instructions that can alleviate the burden of the blockchain, such as emptying the account's storage. Or running the suicide command to clear the account. This will incentive users to empty their accounts.
func (st *StateTransition) refundGas() {
// Return eth for remaining gas to the sender account,
// exchanged at the original rate.
sender := st.from() // err already checked
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
// First, return the remaining Gas from the user.
st.state.AddBalance(sender.Address(), remaining)
// Apply refund counter, capped to half of the used gas.
uhalf := remaining.Div(st.gasUsed(), common.Big2)
refund := math.BigMin(uhalf, st.state.GetRefund())
st.gas += refund.Uint64()
// Add the amount of the tax refund to the user's account.
st.state.AddBalance(sender.Address(), refund.Mul(refund, st.gasPrice))
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(new(big.Int).SetUint64(st.gas))
}
StateTransition is used to process one transaction. Then StateProcessor is used to handle block-level transactions.
Structure and construction
// StateProcessor is a basic Processor, which takes care of transitioning
// state from one point to another.
//
// StateProcessor implements Processor.
type StateProcessor struct {
config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for block rewards
}
// NewStateProcessor initialises a new StateProcessor.
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
return &StateProcessor{
config: config,
bc: bc,
engine: engine,
}
}
Process,will be called by the blockchain
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, *big.Int, error) {
var (
receipts types.Receipts
totalUsedGas = big.NewInt(0)
header = block.Header()
allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit())
)
// Mutate the the block and state according to any hard-fork specs
// Hard fork processing of DAO events
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
misc.ApplyDAOHardFork(statedb)
}
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
statedb.Prepare(tx.Hash(), block.Hash(), i)
receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, totalUsedGas, cfg)
if err != nil {
return nil, nil, nil, err
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
// when block is processed, it is finalized, done by the help of consensus
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), receipts)
// return receipt log total gas usage and nil
return receipts, allLogs, totalUsedGas, nil
}
ApplyTransaction
// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, *big.Int, error) {
// Convert the transaction to a message
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, nil, err
}
// Create a new context to be used in the EVM environment
// this method can be used in other blockchain such as Hyperledger fabric
// to facilitate the usage of ethereum virtual machine
context := NewEVMContext(msg, header, bc, author)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg)
// Apply the transaction to the current state (included in the env)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, nil, err
}
// Update the state with pending changes
var root []byte
if config.IsByzantium(header.Number) {
statedb.Finalise(true)
} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
usedGas.Add(usedGas, gas)
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
// based on the eip phase, we're passing wether the root touch-delete accounts.
receipt := types.NewReceipt(root, failed, usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = new(big.Int).Set(gas)
// if the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
// Get all the logs and create a Bloom filter for the logs.
return receipt, gas, err
}