Skip to content

Commit

Permalink
simplify calculations in state_transition.go; add guards and derive g…
Browse files Browse the repository at this point in the history
…asPrice in TxPool.validateTx
  • Loading branch information
i-norden committed Dec 5, 2019
1 parent 1d77388 commit 52d5c08
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 79 deletions.
30 changes: 12 additions & 18 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,14 +300,14 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) {
// Ensure message is initialized properly.
// EIP1559 guards
if b.config.IsEIP1559Finalized(block.Number()) && call.GasPremium == nil || call.FeeCap == nil || call.GasPrice != nil {
return nil, 0, false, fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.config.EIP1559FinalizedBlock.Uint64())
if b.config.IsEIP1559Finalized(block.Number()) && (call.GasPremium == nil || call.FeeCap == nil || call.GasPrice != nil) {
return nil, 0, false, core.ErrTxNotEIP1559
}
if !b.config.IsEIP1559(block.Number()) && call.GasPremium != nil || call.FeeCap != nil || call.GasPrice == nil {
return nil, 0, false, fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.config.EIP1559Block.Uint64())
if !b.config.IsEIP1559(block.Number()) && (call.GasPremium != nil || call.FeeCap != nil || call.GasPrice == nil) {
return nil, 0, false, core.ErrTxIsEIP1559
}
if call.GasPrice != nil && (call.GasPremium != nil || call.FeeCap != nil) {
return nil, 0, false, errors.New("if GasPrice is set, GasPremium and FeeCap must not be set")
return nil, 0, false, core.ErrTxSetsLegacyAndEIP1559Fields
}
if call.FeeCap != nil && call.GasPremium == nil {
return nil, 0, false, errors.New("if FeeCap is set, GasPremium must be set")
Expand Down Expand Up @@ -350,23 +350,17 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
defer b.mu.Unlock()

// EIP1559 guards
if b.config.IsEIP1559Finalized(b.blockchain.CurrentBlock().Number()) && tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil {
return fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.config.EIP1559FinalizedBlock.Uint64())
if b.config.IsEIP1559Finalized(b.blockchain.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) {
return core.ErrTxNotEIP1559
}
if !b.config.IsEIP1559(b.blockchain.CurrentBlock().Number()) && tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil {
return fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.config.EIP1559Block.Uint64())
if !b.config.IsEIP1559(b.blockchain.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) {
return core.ErrTxIsEIP1559
}
if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) {
return errors.New("if GasPrice is set, GasPremium and FeeCap must not be set")
return core.ErrTxSetsLegacyAndEIP1559Fields
}
if tx.FeeCap() != nil && tx.GasPremium() == nil {
return errors.New("if FeeCap is set, GasPremium must be set")
}
if tx.GasPremium() != nil && tx.FeeCap() == nil {
return errors.New("if GasPremium is set, FeeCap must be set")
}
if tx.GasPrice() == nil && tx.GasPremium() == nil {
return errors.New("either GasPrice or GasPremium and FeeCap need to be set")
if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) {
return core.ErrMissingGasFields
}

sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx)
Expand Down
8 changes: 5 additions & 3 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,14 @@ type BlockGen struct {
// SetCoinbase sets the coinbase of the generated block.
// It can be called at most once.
func (b *BlockGen) SetCoinbase(addr common.Address) {
if b.gasPool != nil || b.gasPool1559 != nil {
if b.gasPool != nil {
if len(b.txs) > 0 {
panic("coinbase must be set before adding transactions")
}
panic("coinbase can only be set once")
}
b.header.Coinbase = addr
b.gasPool = new(GasPool).AddGas(b.header.GasLimit)
b.gasPool1559 = new(GasPool).AddGas(params.MaxGasEIP1559)
}

// SetExtra sets the extra data field of the generated block.
Expand Down Expand Up @@ -101,9 +100,12 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
// added. If contract code relies on the BLOCKHASH instruction,
// the block in chain will be returned.
func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
if b.gasPool == nil || b.gasPool1559 == nil {
if b.gasPool == nil {
b.SetCoinbase(common.Address{})
}
if b.gasPool1559 == nil && b.config.IsEIP1559(b.header.Number) {
b.gasPool1559 = new(GasPool).AddGas(params.MaxGasEIP1559)
}
b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs))
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.gasPool1559, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
if err != nil {
Expand Down
77 changes: 42 additions & 35 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,18 @@ The state transitioning model does all the necessary work to work out a valid ne
6) Derive new state root
*/
type StateTransition struct {
gp *GasPool
gp1559 *GasPool
msg Message
gas uint64
gasPrice *big.Int
initialGas uint64
value *big.Int
data []byte
state vm.StateDB
evm *vm.EVM
isEIP1559 bool
gp *GasPool
gp1559 *GasPool
msg Message
gas uint64
gasPrice *big.Int
initialGas uint64
value *big.Int
data []byte
state vm.StateDB
evm *vm.EVM
isEIP1559 bool
eip1559GasPrice *big.Int
}

// Message represents a message sent to a contract.
Expand Down Expand Up @@ -121,10 +122,7 @@ func IntrinsicGas(data []byte, contractCreation, isEIP155 bool, isEIP2028 bool)
// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) *StateTransition {
isEIP1559 := evm.ChainConfig().IsEIP1559(evm.BlockNumber) && msg.GasPremium() != nil && msg.FeeCap() != nil && evm.BaseFee != nil && gp1559 != nil
// check that msg doesn't try to set both legacy and 1559 params
// check that legacy has legacy params set
// reject 1559 trxs if we are before the 1559 fork blocknumber
return &StateTransition{
st := &StateTransition{
gp: gp,
gp1559: gp1559,
evm: evm,
Expand All @@ -135,6 +133,14 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp, gp1559 *GasPool) *StateTra
state: evm.StateDB,
isEIP1559: isEIP1559,
}
if isEIP1559 {
// EP1559 gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap)
st.eip1559GasPrice = new(big.Int).Add(evm.BaseFee, msg.GasPremium())
if st.eip1559GasPrice.Cmp(msg.FeeCap()) > 0 {
st.eip1559GasPrice.Set(msg.FeeCap())
}
}
return st
}

// ApplyMessage computes the new state by applying the given message
Expand Down Expand Up @@ -173,13 +179,8 @@ func (st *StateTransition) buyGas() error {
}

func (st *StateTransition) buyGasEIP1559() error {
// gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap)
gasPrice := new(big.Int).Add(st.evm.BaseFee, st.msg.GasPremium())
if gasPrice.Cmp(st.msg.FeeCap()) > 0 {
gasPrice.Set(st.msg.FeeCap())
}
// tx.origin pays gasPrice * tx.gas
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), gasPrice)
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.eip1559GasPrice)
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas
}
Expand Down Expand Up @@ -218,10 +219,26 @@ func (st *StateTransition) preCheck() error {
return ErrNonceTooLow
}
}
// If we are past the EIP1559 finalization block and this transaction does not conform with EIP1559, throw an error
// If we have reached the EIP1559 finalization block and we do not conform with EIP1559, throw an error
if st.evm.ChainConfig().IsEIP1559Finalized(st.evm.BlockNumber) && !st.isEIP1559 {
return ErrTxNotEIP1559
}
// If we are before the EIP1559 initialization block, throw an error if we have EIP1559 fields or do not have a GasPrice
if !st.evm.ChainConfig().IsEIP1559(st.evm.BlockNumber) && (st.msg.GasPremium() != nil || st.msg.FeeCap() != nil || st.gp1559 != nil || st.evm.BaseFee != nil || st.msg.GasPrice() == nil) {
return ErrTxIsEIP1559
}
// If transaction has both legacy and EIP1559 fields, throw an error
if (st.msg.GasPremium() != nil || st.msg.FeeCap() != nil) && st.msg.GasPrice() != nil {
return ErrTxSetsLegacyAndEIP1559Fields
}
// We need a BaseFee if we are past EIP1559 initialization
if st.evm.ChainConfig().IsEIP1559(st.evm.BlockNumber) && st.evm.BaseFee == nil {
return ErrNoBaseFee
}
// We need either a GasPrice or a FeeCap and GasPremium to be set
if st.msg.GasPrice() == nil && (st.msg.GasPremium() == nil || st.msg.FeeCap() == nil) {
return ErrMissingGasFields
}
return st.buyGas()
}

Expand Down Expand Up @@ -272,12 +289,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
}
st.refundGas()
if st.isEIP1559 {
gasPrice := new(big.Int).Add(st.evm.BaseFee, st.msg.GasPremium())
if gasPrice.Cmp(st.msg.FeeCap()) > 0 {
gasPrice.Set(st.msg.FeeCap())
}
// block.coinbase gains (gasprice - BASEFEE) * gasused
coinBaseCredit := new(big.Int).Mul(new(big.Int).Sub(gasPrice, st.evm.BaseFee), new(big.Int).SetUint64(st.gasUsed()))
coinBaseCredit := new(big.Int).Mul(new(big.Int).Sub(st.eip1559GasPrice, st.evm.BaseFee), new(big.Int).SetUint64(st.gasUsed()))
// If gasprice < BASEFEE (due to the fee_cap), this means that the block.coinbase loses funds from this operation;
// in this case, check that the post-balance is non-negative and throw an exception if it is negative.
if coinBaseCredit.Sign() < 0 {
Expand All @@ -287,7 +300,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
return nil, 0, vmerr != nil, errInsufficientCoinbaseBalance
}
}
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), gasPrice))
st.state.AddBalance(st.evm.Coinbase, coinBaseCredit)

return ret, st.gasUsed(), vmerr != nil, err
}
Expand Down Expand Up @@ -331,13 +344,7 @@ func (st *StateTransition) refundGasEIP1559() {

// tx.origin gets refunded gasprice * (tx.gas - gasused)
txGasSubUsed := new(big.Int).Sub(new(big.Int).SetUint64(st.msg.Gas()), new(big.Int).SetUint64(st.gasUsed()))

// gasPrice = min(BASEFEE + tx.fee_premium, tx.fee_cap)
gasPrice := new(big.Int).Add(st.evm.BaseFee, st.msg.GasPremium())
if gasPrice.Cmp(st.msg.FeeCap()) > 0 {
gasPrice.Set(st.msg.FeeCap())
}
remaining := new(big.Int).Mul(gasPrice, txGasSubUsed)
remaining := new(big.Int).Mul(st.eip1559GasPrice, txGasSubUsed)
st.state.AddBalance(st.msg.From(), remaining)

// Also return remaining gas to the block gas counter so it is
Expand Down
48 changes: 45 additions & 3 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,24 @@ var (
// making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data")

// ErrTxNotEIP1559 is returned if we have passed the EIP1559 finalized block height
// ErrTxNotEIP1559 is returned if we have reached the EIP1559 finalized block height
// and the input transaction does not conform to with EIP1559
ErrTxNotEIP1559 = errors.New("transaction does not conform with EIP1559")
ErrTxNotEIP1559 = fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", params.EIP1559ForkFinalizedBlockNumber)

// ErrTxIsEIP1559 is returned if we have not reached the EIP1559 initialization block height
// and the input transaction is not of the legacy type
ErrTxIsEIP1559 = fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", params.EIP1559ForkBlockNumber)

// ErrTxSetsLegacyAndEIP1559Fields is returned if a transaction attempts to set
// both legacy (GasPrice) and EIP1559 (GasPremium and FeeCap) fields
ErrTxSetsLegacyAndEIP1559Fields = errors.New("transaction sets both legacy and EIP1559 fields")

// ErrNoBaseFee is returned if we are past the EIP1559 initialization block but
// the current header does not provide a BaseFee
ErrNoBaseFee = errors.New("current header does not provide the BaseFee needed to process EIP1559 transactions")

// ErrMissingGasFields is returned if neither GasPrice nor GasPremium and FeeCap are set
ErrMissingGasFields = errors.New("either GasPrice or GasPremium and FeeCap need to be set")
)

var (
Expand Down Expand Up @@ -514,6 +529,33 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// EIP1559 guards
if pool.chainconfig.IsEIP1559(pool.chain.CurrentBlock().Number()) && pool.chain.CurrentBlock().BaseFee() == nil {
return ErrNoBaseFee
}
if pool.chainconfig.IsEIP1559Finalized(pool.chain.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) {
return ErrTxNotEIP1559
}
if !pool.chainconfig.IsEIP1559(pool.chain.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) {
return ErrTxIsEIP1559
}
if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) {
return ErrTxSetsLegacyAndEIP1559Fields
}
if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) {
return ErrMissingGasFields
}
// Set the gasPrice to the tx.GasPrice() if it is non nil (legacy transaction)
var gasPrice *big.Int
if tx.GasPrice() != nil {
gasPrice = tx.GasPrice()
} else { // Derive the gasPrice from the tx.GasPremium() and tx.FeeCap() (EIP1559 transaction)
gasPrice = new(big.Int).Add(pool.chain.CurrentBlock().BaseFee(), tx.GasPremium())
if gasPrice.Cmp(tx.FeeCap()) > 0 {
gasPrice.Set(tx.FeeCap())
}
}

// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
return ErrOversizedData
Expand All @@ -534,7 +576,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
}
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
if !local && pool.gasPrice.Cmp(gasPrice) > 0 {
return ErrUnderpriced
}
// Ensure the transaction adheres to nonce ordering
Expand Down
37 changes: 17 additions & 20 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,14 +763,14 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

// EIP1559 guards
if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil {
return nil, 0, false, fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.ChainConfig().EIP1559FinalizedBlock.Uint64())
if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && (args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil) {
return nil, 0, false, core.ErrTxNotEIP1559
}
if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil {
return nil, 0, false, fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.ChainConfig().EIP1559Block.Uint64())
if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && (args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil) {
return nil, 0, false, core.ErrTxIsEIP1559
}
if args.GasPrice != nil && (args.GasPremium != nil || args.FeeCap != nil) {
return nil, 0, false, errors.New("if GasPrice is set, GasPremium and FeeCap must not be set")
return nil, 0, false, core.ErrTxSetsLegacyAndEIP1559Fields
}
if args.FeeCap != nil && args.GasPremium == nil {
return nil, 0, false, errors.New("if FeeCap is set, GasPremium must be set")
Expand Down Expand Up @@ -1395,14 +1395,14 @@ type SendTxArgs struct {
// setDefaults is a helper function that fills in default values for unspecified tx fields.
func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
// EIP1559 guards
if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil {
return fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", b.ChainConfig().EIP1559FinalizedBlock.Uint64())
if b.ChainConfig().IsEIP1559Finalized(b.CurrentBlock().Number()) && (args.GasPremium == nil || args.FeeCap == nil || args.GasPrice != nil) {
return core.ErrTxNotEIP1559
}
if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil {
return fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", b.ChainConfig().EIP1559Block.Uint64())
if !b.ChainConfig().IsEIP1559(b.CurrentBlock().Number()) && (args.GasPremium != nil || args.FeeCap != nil || args.GasPrice == nil) {
return core.ErrTxIsEIP1559
}
if args.GasPrice != nil && (args.GasPremium != nil || args.FeeCap != nil) {
return errors.New("if GasPrice is set, GasPremium and FeeCap must not be set")
return core.ErrTxSetsLegacyAndEIP1559Fields
}
if args.FeeCap != nil && args.GasPremium == nil {
return errors.New("if FeeCap is set, GasPremium must be set")
Expand Down Expand Up @@ -1559,20 +1559,17 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
return common.Hash{}, err
}
// EIP1559 guards
if s.b.ChainConfig().IsEIP1559Finalized(s.b.CurrentBlock().Number()) && tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil {
return common.Hash{}, fmt.Errorf("after block %d EIP1559 is finalized and transactions must contain a GasPremium and FeeCap and not contain a GasPrice", s.b.ChainConfig().EIP1559FinalizedBlock.Uint64())
if s.b.ChainConfig().IsEIP1559Finalized(s.b.CurrentBlock().Number()) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) {
return common.Hash{}, core.ErrTxNotEIP1559
}
if !s.b.ChainConfig().IsEIP1559(s.b.CurrentBlock().Number()) && tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil {
return common.Hash{}, fmt.Errorf("before block %d EIP1559 is not activated and transactions must contain a GasPrice and not contain a GasPremium or FeeCap", s.b.ChainConfig().EIP1559Block.Uint64())
if !s.b.ChainConfig().IsEIP1559(s.b.CurrentBlock().Number()) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) {
return common.Hash{}, core.ErrTxIsEIP1559
}
if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) {
return common.Hash{}, errors.New("if GasPrice is set, GasPremium and FeeCap must not be set")
return common.Hash{}, core.ErrTxSetsLegacyAndEIP1559Fields
}
if tx.FeeCap() != nil && tx.GasPremium() == nil {
return common.Hash{}, errors.New("if FeeCap is set, GasPremium must be set")
}
if tx.GasPremium() != nil && tx.FeeCap() == nil {
return common.Hash{}, errors.New("if GasPremium is set, FeeCap must be set")
if tx.GasPrice() == nil && (tx.GasPremium() == nil || tx.FeeCap() == nil) {
return common.Hash{}, core.ErrMissingGasFields
}
return SubmitTransaction(ctx, s.b, tx)
}
Expand Down

0 comments on commit 52d5c08

Please sign in to comment.