@ -25,65 +25,9 @@ import (
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
var emptyCodeHash = crypto . Keccak256Hash ( nil )
// StateTransition represents a state transition.
//
// == 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 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 nil
// 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
msg Message
gas uint64
gasPrice * big . Int
gasFeeCap * big . Int
gasTipCap * big . Int
initialGas uint64
value * big . Int
data [ ] byte
state vm . StateDB
evm * vm . EVM
}
// Message represents a message sent to a contract.
type Message interface {
From ( ) common . Address
To ( ) * common . Address
GasPrice ( ) * big . Int
GasFeeCap ( ) * big . Int
GasTipCap ( ) * big . Int
Gas ( ) uint64
Value ( ) * big . Int
Nonce ( ) uint64
IsFake ( ) bool
Data ( ) [ ] byte
AccessList ( ) types . AccessList
}
// ExecutionResult includes all output after executing given evm
// message no matter the execution itself is successful or not.
type ExecutionResult struct {
@ -178,19 +122,47 @@ func toWordSize(size uint64) uint64 {
return ( size + 31 ) / 32
}
// 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 ( ) ,
gasFeeCap : msg . GasFeeCap ( ) ,
gasTipCap : msg . GasTipCap ( ) ,
value : msg . Value ( ) ,
data : msg . Data ( ) ,
state : evm . StateDB ,
// A Message contains the data derived from a single transaction that is relevant to state
// processing.
type Message struct {
To * common . Address
From common . Address
Nonce uint64
Value * big . Int
GasLimit uint64
GasPrice * big . Int
GasFeeCap * big . Int
GasTipCap * big . Int
Data [ ] byte
AccessList types . AccessList
// When SkipAccountCheckss is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA.
// This field will be set to true for operations like RPC eth_call.
SkipAccountChecks bool
}
// TransactionToMessage converts a transaction into a Message.
func TransactionToMessage ( tx * types . Transaction , s types . Signer , baseFee * big . Int ) ( * Message , error ) {
msg := & Message {
Nonce : tx . Nonce ( ) ,
GasLimit : tx . Gas ( ) ,
GasPrice : new ( big . Int ) . Set ( tx . GasPrice ( ) ) ,
GasFeeCap : new ( big . Int ) . Set ( tx . GasFeeCap ( ) ) ,
GasTipCap : new ( big . Int ) . Set ( tx . GasTipCap ( ) ) ,
To : tx . To ( ) ,
Value : tx . Value ( ) ,
Data : tx . Data ( ) ,
AccessList : tx . AccessList ( ) ,
SkipAccountChecks : false ,
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg . GasPrice = cmath . BigMin ( msg . GasPrice . Add ( msg . GasTipCap , baseFee ) , msg . GasFeeCap )
}
var err error
msg . From , err = types . Sender ( s , tx )
return msg , err
}
// ApplyMessage computes the new state by applying the given message
@ -200,82 +172,126 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
// 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 ) ( * ExecutionResult , error ) {
func ApplyMessage ( evm * vm . EVM , msg * Message , gp * GasPool ) ( * ExecutionResult , error ) {
return NewStateTransition ( evm , msg , gp ) . TransitionDb ( )
}
// StateTransition represents a state transition.
//
// == 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 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 nil
// 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
msg * Message
gasRemaining uint64
initialGas uint64
state vm . StateDB
evm * vm . EVM
}
// 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 ,
state : evm . StateDB ,
}
}
// to returns the recipient of the message.
func ( st * StateTransition ) to ( ) common . Address {
if st . msg == nil || st . msg . To ( ) == nil /* contract creation */ {
if st . msg == nil || st . msg . To == nil /* contract creation */ {
return common . Address { }
}
return * st . msg . To ( )
return * st . msg . To
}
func ( st * StateTransition ) buyGas ( ) error {
mgval := new ( big . Int ) . SetUint64 ( st . msg . Gas ( ) )
mgval = mgval . Mul ( mgval , st . gasPrice )
mgval := new ( big . Int ) . SetUint64 ( st . msg . GasLimit )
mgval = mgval . Mul ( mgval , st . ms g. G asPrice)
balanceCheck := mgval
if st . gasFeeCap != nil {
balanceCheck = new ( big . Int ) . SetUint64 ( st . msg . Gas ( ) )
balanceCheck = balanceCheck . Mul ( balanceCheck , st . gasFeeCap )
balanceCheck . Add ( balanceCheck , st . value )
if st . ms g. G asFeeCap != nil {
balanceCheck = new ( big . Int ) . SetUint64 ( st . msg . GasLimit )
balanceCheck = balanceCheck . Mul ( balanceCheck , st . ms g. G asFeeCap)
balanceCheck . Add ( balanceCheck , st . msg . V alue)
}
if have , want := st . state . GetBalance ( st . msg . From ( ) ) , balanceCheck ; have . Cmp ( want ) < 0 {
return fmt . Errorf ( "%w: address %v have %v want %v" , ErrInsufficientFunds , st . msg . From ( ) . Hex ( ) , have , want )
if have , want := st . state . GetBalance ( st . msg . From ) , balanceCheck ; have . Cmp ( want ) < 0 {
return fmt . Errorf ( "%w: address %v have %v want %v" , ErrInsufficientFunds , st . msg . From . Hex ( ) , have , want )
}
if err := st . gp . SubGas ( st . msg . Gas ( ) ) ; err != nil {
if err := st . gp . SubGas ( st . msg . GasLimit ) ; err != nil {
return err
}
st . gas += st . msg . Gas ( )
st . gasRemaining += st . msg . GasLimit
st . initialGas = st . msg . Gas ( )
st . state . SubBalance ( st . msg . From ( ) , mgval )
st . initialGas = st . msg . GasLimit
st . state . SubBalance ( st . msg . From , mgval )
return nil
}
func ( st * StateTransition ) preCheck ( ) error {
// Only check transactions that are not fake
if ! st . msg . IsFake ( ) {
msg := st . msg
if ! msg . SkipAccountChecks {
// Make sure this transaction's nonce is correct.
stNonce := st . state . GetNonce ( st . msg . From ( ) )
if msgNonce := st . msg . Nonce ( ) ; stNonce < msgNonce {
stNonce := st . state . GetNonce ( msg . From )
if msgNonce := msg . Nonce ; stNonce < msgNonce {
return fmt . Errorf ( "%w: address %v, tx: %d state: %d" , ErrNonceTooHigh ,
st . msg . From ( ) . Hex ( ) , msgNonce , stNonce )
msg . From . Hex ( ) , msgNonce , stNonce )
} else if stNonce > msgNonce {
return fmt . Errorf ( "%w: address %v, tx: %d state: %d" , ErrNonceTooLow ,
st . msg . From ( ) . Hex ( ) , msgNonce , stNonce )
msg . From . Hex ( ) , msgNonce , stNonce )
} else if stNonce + 1 < stNonce {
return fmt . Errorf ( "%w: address %v, nonce: %d" , ErrNonceMax ,
st . msg . From ( ) . Hex ( ) , stNonce )
msg . From . Hex ( ) , stNonce )
}
// Make sure the sender is an EOA
if codeHash := st . state . GetCodeHash ( st . msg . From ( ) ) ; codeHash != emptyCodeHash && codeHash != ( common . Hash { } ) {
codeHash := st . state . GetCodeHash ( msg . From )
if codeHash != ( common . Hash { } ) && codeHash != types . EmptyCodeHash {
return fmt . Errorf ( "%w: address %v, codehash: %s" , ErrSenderNoEOA ,
st . msg . From ( ) . Hex ( ) , codeHash )
msg . From . Hex ( ) , codeHash )
}
}
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
if st . evm . ChainConfig ( ) . IsLondon ( st . evm . Context . BlockNumber ) {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
if ! st . evm . Config . NoBaseFee || st . g asFeeCap. BitLen ( ) > 0 || st . g asTipCap. BitLen ( ) > 0 {
if l := st . g asFeeCap. BitLen ( ) ; l > 256 {
if ! st . evm . Config . NoBaseFee || msg . G asFeeCap. BitLen ( ) > 0 || msg . G asTipCap. BitLen ( ) > 0 {
if l := msg . G asFeeCap. BitLen ( ) ; l > 256 {
return fmt . Errorf ( "%w: address %v, maxFeePerGas bit length: %d" , ErrFeeCapVeryHigh ,
st . msg . From ( ) . Hex ( ) , l )
msg . From . Hex ( ) , l )
}
if l := st . g asTipCap. BitLen ( ) ; l > 256 {
if l := msg . G asTipCap. BitLen ( ) ; l > 256 {
return fmt . Errorf ( "%w: address %v, maxPriorityFeePerGas bit length: %d" , ErrTipVeryHigh ,
st . msg . From ( ) . Hex ( ) , l )
msg . From . Hex ( ) , l )
}
if st . g asFeeCap. Cmp ( st . g asTipCap) < 0 {
if msg . G asFeeCap. Cmp ( msg . G asTipCap) < 0 {
return fmt . Errorf ( "%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s" , ErrTipAboveFeeCap ,
st . msg . From ( ) . Hex ( ) , st . gasTipCap , st . g asFeeCap)
msg . From . Hex ( ) , msg . GasTipCap , msg . G asFeeCap)
}
// This will panic if baseFee is nil, but basefee presence is verified
// as part of header validation.
if st . g asFeeCap. Cmp ( st . evm . Context . BaseFee ) < 0 {
if msg . G asFeeCap. Cmp ( st . evm . Context . BaseFee ) < 0 {
return fmt . Errorf ( "%w: address %v, maxFeePerGas: %s baseFee: %s" , ErrFeeCapTooLow ,
st . msg . From ( ) . Hex ( ) , st . g asFeeCap, st . evm . Context . BaseFee )
msg . From . Hex ( ) , msg . G asFeeCap, st . evm . Context . BaseFee )
}
}
}
@ -311,52 +327,52 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st . evm . Config . Debug {
st . evm . Config . Tracer . CaptureTxStart ( st . initialGas )
defer func ( ) {
st . evm . Config . Tracer . CaptureTxEnd ( st . gas )
st . evm . Config . Tracer . CaptureTxEnd ( st . gasRemaining )
} ( )
}
var (
msg = st . msg
sender = vm . AccountRef ( msg . From ( ) )
sender = vm . AccountRef ( msg . From )
rules = st . evm . ChainConfig ( ) . Rules ( st . evm . Context . BlockNumber , st . evm . Context . Random != nil , st . evm . Context . Time )
contractCreation = msg . To ( ) == nil
contractCreation = msg . To == nil
)
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas , err := IntrinsicGas ( st . data , st . msg . AccessList ( ) , contractCreation , rules . IsHomestead , rules . IsIstanbul , rules . IsShanghai )
gas , err := IntrinsicGas ( msg . Data , msg . AccessList , contractCreation , rules . IsHomestead , rules . IsIstanbul , rules . IsShanghai )
if err != nil {
return nil , err
}
if st . gas < gas {
return nil , fmt . Errorf ( "%w: have %d, want %d" , ErrIntrinsicGas , st . gas , gas )
if st . gasRemaining < gas {
return nil , fmt . Errorf ( "%w: have %d, want %d" , ErrIntrinsicGas , st . gasRemaining , gas )
}
st . gas -= gas
st . gasRemaining -= gas
// Check clause 6
if msg . Value ( ) . Sign ( ) > 0 && ! st . evm . Context . CanTransfer ( st . state , msg . From ( ) , msg . Value ( ) ) {
return nil , fmt . Errorf ( "%w: address %v" , ErrInsufficientFundsForTransfer , msg . From ( ) . Hex ( ) )
if msg . Value . Sign ( ) > 0 && ! st . evm . Context . CanTransfer ( st . state , msg . From , msg . Value ) {
return nil , fmt . Errorf ( "%w: address %v" , ErrInsufficientFundsForTransfer , msg . From . Hex ( ) )
}
// Check whether the init code size has been exceeded.
if rules . IsShanghai && contractCreation && len ( st . d ata) > params . MaxInitCodeSize {
return nil , fmt . Errorf ( "%w: code size %v limit %v" , ErrMaxInitCodeSizeExceeded , len ( st . d ata) , params . MaxInitCodeSize )
if rules . IsShanghai && contractCreation && len ( msg . D ata) > params . MaxInitCodeSize {
return nil , fmt . Errorf ( "%w: code size %v limit %v" , ErrMaxInitCodeSizeExceeded , len ( msg . D ata) , params . MaxInitCodeSize )
}
// Execute the preparatory steps for state transition which includes:
// - prepare accessList(post-berlin)
// - reset transient storage(eip 1153)
st . state . Prepare ( rules , msg . From ( ) , st . evm . Context . Coinbase , msg . To ( ) , vm . ActivePrecompiles ( rules ) , msg . AccessList ( ) )
st . state . Prepare ( rules , msg . From , st . evm . Context . Coinbase , msg . To , vm . ActivePrecompiles ( rules ) , msg . AccessList )
var (
ret [ ] byte
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
if contractCreation {
ret , _ , st . gas , vmerr = st . evm . Create ( sender , st . d ata, st . gas , st . v alue)
ret , _ , st . gasRemaining , vmerr = st . evm . Create ( sender , msg . D ata, st . gasRemaining , msg . V alue )
} else {
// Increment the nonce for the next transaction
st . state . SetNonce ( msg . From ( ) , st . state . GetNonce ( sender . Address ( ) ) + 1 )
ret , st . gas , vmerr = st . evm . Call ( sender , st . to ( ) , st . d ata, st . gas , st . v alue)
st . state . SetNonce ( msg . From , st . state . GetNonce ( sender . Address ( ) ) + 1 )
ret , st . gasRemaining , vmerr = st . evm . Call ( sender , st . to ( ) , msg . D ata, st . gasRemaining , msg . V alue )
}
if ! rules . IsLondon {
@ -366,12 +382,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// After EIP-3529: refunds are capped to gasUsed / 5
st . refundGas ( params . RefundQuotientEIP3529 )
}
effectiveTip := st . g asPrice
effectiveTip := msg . G asPrice
if rules . IsLondon {
effectiveTip = cmath . BigMin ( st . g asTipCap, new ( big . Int ) . Sub ( st . g asFeeCap, st . evm . Context . BaseFee ) )
effectiveTip = cmath . BigMin ( msg . G asTipCap, new ( big . Int ) . Sub ( msg . G asFeeCap, st . evm . Context . BaseFee ) )
}
if st . evm . Config . NoBaseFee && st . g asFeeCap. Sign ( ) == 0 && st . g asTipCap. Sign ( ) == 0 {
if st . evm . Config . NoBaseFee && msg . G asFeeCap. Sign ( ) == 0 && msg . G asTipCap. Sign ( ) == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields
// are 0. This avoids a negative effectiveTip being applied to
// the coinbase when simulating calls.
@ -394,18 +410,18 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {
if refund > st . state . GetRefund ( ) {
refund = st . state . GetRefund ( )
}
st . gas += refund
st . gasRemaining += refund
// Return ETH for remaining gas, exchanged at the original rate.
remaining := new ( big . Int ) . Mul ( new ( big . Int ) . SetUint64 ( st . gas ) , st . gasPrice )
st . state . AddBalance ( st . msg . From ( ) , remaining )
remaining := new ( big . Int ) . Mul ( new ( big . Int ) . SetUint64 ( st . gasRemaining ) , st . ms g. G asPrice)
st . state . AddBalance ( st . msg . From , remaining )
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st . gp . AddGas ( st . gas )
st . gp . AddGas ( st . gasRemaining )
}
// gasUsed returns the amount of gas used up by the state transition.
func ( st * StateTransition ) gasUsed ( ) uint64 {
return st . initialGas - st . gas
return st . initialGas - st . gasRemaining
}