mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
feat: compute rollup gas price for StateTransition (#209)
* init * more * more * add L1BlockContainerAddress * finish * add more comments * rename `UsingSVM` to `UsingScroll` * update logic * refactor(rollup): add UsingScroll into ChainConfig * update * update comments * fee=l2fee+l1fee (#221) add l1fee into calc * improve * format note content. * feat(l1Fee): Add l1Fee in trace. (#223) * Add l1fee in trace. * Delete comment. --------- Co-authored-by: maskpp <maskpp266@gmail.com>
This commit is contained in:
parent
8a78c736a5
commit
ec9254b0b1
5 changed files with 209 additions and 10 deletions
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/core/vm"
|
||||
"github.com/scroll-tech/go-ethereum/crypto/codehash"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/fees"
|
||||
)
|
||||
|
||||
var emptyKeccakCodeHash = codehash.EmptyKeccakCodeHash
|
||||
|
|
@ -42,8 +43,10 @@ The state transitioning model does all the necessary work to work out a valid ne
|
|||
3) Create a new state object if the recipient is \0*32
|
||||
4) Value transfer
|
||||
== If contract creation ==
|
||||
4a) Attempt to run transaction data
|
||||
4b) If valid, use result as code for the new state object
|
||||
|
||||
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
|
||||
|
|
@ -60,6 +63,9 @@ type StateTransition struct {
|
|||
data []byte
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
|
||||
// l1 rollup fee
|
||||
l1Fee *big.Int
|
||||
}
|
||||
|
||||
// Message represents a message sent to a contract.
|
||||
|
|
@ -157,6 +163,11 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
|
|||
|
||||
// NewStateTransition initialises and returns a new state transition object.
|
||||
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
|
||||
l1Fee := new(big.Int)
|
||||
if evm.ChainConfig().UsingScroll {
|
||||
l1Fee, _ = fees.CalculateL1MsgFee(msg, evm.StateDB)
|
||||
}
|
||||
|
||||
return &StateTransition{
|
||||
gp: gp,
|
||||
evm: evm,
|
||||
|
|
@ -167,6 +178,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
|
|||
value: msg.Value(),
|
||||
data: msg.Data(),
|
||||
state: evm.StateDB,
|
||||
l1Fee: l1Fee,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +204,12 @@ func (st *StateTransition) to() common.Address {
|
|||
func (st *StateTransition) buyGas() error {
|
||||
mgval := new(big.Int).SetUint64(st.msg.Gas())
|
||||
mgval = mgval.Mul(mgval, st.gasPrice)
|
||||
|
||||
if st.evm.ChainConfig().UsingScroll {
|
||||
// always add l1fee, because all tx are L2-to-L1 ATM
|
||||
mgval = mgval.Add(mgval, st.l1Fee)
|
||||
}
|
||||
|
||||
balanceCheck := mgval
|
||||
if st.gasFeeCap != nil {
|
||||
balanceCheck = new(big.Int).SetUint64(st.msg.Gas())
|
||||
|
|
@ -262,13 +280,13 @@ func (st *StateTransition) preCheck() error {
|
|||
// TransitionDb will transition the state by applying the current message and
|
||||
// returning the evm execution result with following fields.
|
||||
//
|
||||
// - used gas:
|
||||
// total gas used (including gas being refunded)
|
||||
// - returndata:
|
||||
// the returned data from evm
|
||||
// - concrete execution error:
|
||||
// various **EVM** error which aborts the execution,
|
||||
// e.g. ErrOutOfGas, ErrExecutionReverted
|
||||
// - used gas:
|
||||
// total gas used (including gas being refunded)
|
||||
// - returndata:
|
||||
// the returned data from evm
|
||||
// - concrete execution error:
|
||||
// various **EVM** error which aborts the execution,
|
||||
// e.g. ErrOutOfGas, ErrExecutionReverted
|
||||
//
|
||||
// However if any consensus issue encountered, return the error directly with
|
||||
// nil evm execution result.
|
||||
|
|
@ -336,7 +354,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
if london {
|
||||
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
|
||||
}
|
||||
st.state.AddBalance(st.evm.FeeRecipient(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip))
|
||||
|
||||
if st.evm.ChainConfig().UsingScroll {
|
||||
// The L2 Fee is the same as the fee that is charged in the normal geth
|
||||
// codepath. Add the L1 fee to the L2 fee for the total fee that is sent
|
||||
// to the sequencer.
|
||||
l2Fee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)
|
||||
fee := new(big.Int).Add(st.l1Fee, l2Fee)
|
||||
st.state.AddBalance(st.evm.FeeRecipient(), fee)
|
||||
} else {
|
||||
st.state.AddBalance(st.evm.FeeRecipient(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip))
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
UsedGas: st.gasUsed(),
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ type StorageTrace struct {
|
|||
// while replaying a transaction in debug mode as well as transaction
|
||||
// execution status, the amount of gas used and the return value
|
||||
type ExecutionResult struct {
|
||||
L1Fee uint64 `json:"l1Fee,omitempty"`
|
||||
Gas uint64 `json:"gas"`
|
||||
Failed bool `json:"failed"`
|
||||
ReturnValue string `json:"returnValue"`
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
|
|
@ -14,6 +15,7 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/core/vm"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/fees"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/withdrawtrie"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
|
|
@ -328,11 +330,17 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc
|
|||
}
|
||||
}
|
||||
|
||||
l1Fee := big.NewInt(0)
|
||||
if vmenv.ChainConfig().UsingScroll {
|
||||
l1Fee, _ = fees.CalculateL1MsgFee(msg, vmenv.StateDB)
|
||||
}
|
||||
|
||||
env.executionResults[index] = &types.ExecutionResult{
|
||||
From: sender,
|
||||
To: receiver,
|
||||
AccountCreated: createdAcc,
|
||||
AccountsAfter: after,
|
||||
L1Fee: l1Fee.Uint64(),
|
||||
Gas: result.UsedGas,
|
||||
Failed: result.Failed(),
|
||||
ReturnValue: fmt.Sprintf("%x", returnVal),
|
||||
|
|
|
|||
153
rollup/fees/rollup_fee.go
Normal file
153
rollup/fees/rollup_fee.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package fees
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
|
||||
)
|
||||
|
||||
var (
|
||||
// errTransactionSigned represents the error case of passing in a signed
|
||||
// transaction to the L1 fee calculation routine. The signature is accounted
|
||||
// for externally
|
||||
errTransactionSigned = errors.New("transaction is signed")
|
||||
)
|
||||
|
||||
// Message represents the interface of a message.
|
||||
// It should be a subset of the methods found on
|
||||
// types.Message
|
||||
type Message interface {
|
||||
From() common.Address
|
||||
To() *common.Address
|
||||
GasPrice() *big.Int
|
||||
Gas() uint64
|
||||
Value() *big.Int
|
||||
Nonce() uint64
|
||||
Data() []byte
|
||||
}
|
||||
|
||||
// StateDB represents the StateDB interface
|
||||
// required to compute the L1 fee
|
||||
type StateDB interface {
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
}
|
||||
|
||||
// CalculateL1MsgFee computes the L1 portion of the fee given
|
||||
// a Message and a StateDB
|
||||
// Reference: https://github.com/ethereum-optimism/optimism/blob/develop/l2geth/rollup/fees/rollup_fee.go
|
||||
func CalculateL1MsgFee(msg Message, state StateDB) (*big.Int, error) {
|
||||
tx := asTransaction(msg)
|
||||
raw, err := rlpEncode(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state)
|
||||
l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar)
|
||||
return l1Fee, nil
|
||||
}
|
||||
|
||||
// asTransaction turns a Message into a types.Transaction
|
||||
func asTransaction(msg Message) *types.Transaction {
|
||||
if msg.To() == nil {
|
||||
return types.NewContractCreation(
|
||||
msg.Nonce(),
|
||||
msg.Value(),
|
||||
msg.Gas(),
|
||||
msg.GasPrice(),
|
||||
msg.Data(),
|
||||
)
|
||||
}
|
||||
return types.NewTransaction(
|
||||
msg.Nonce(),
|
||||
*msg.To(),
|
||||
msg.Value(),
|
||||
msg.Gas(),
|
||||
msg.GasPrice(),
|
||||
msg.Data(),
|
||||
)
|
||||
}
|
||||
|
||||
// rlpEncode RLP encodes the transaction into bytes
|
||||
// When a signature is not included, set pad to true to
|
||||
// fill in a dummy signature full on non 0 bytes
|
||||
func rlpEncode(tx *types.Transaction) ([]byte, error) {
|
||||
raw := new(bytes.Buffer)
|
||||
if err := tx.EncodeRLP(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, v, s := tx.RawSignatureValues()
|
||||
if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 {
|
||||
return nil, errTransactionSigned
|
||||
}
|
||||
|
||||
// Slice off the 0 bytes representing the signature
|
||||
b := raw.Bytes()
|
||||
return b[:len(b)-3], nil
|
||||
}
|
||||
|
||||
func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Float) {
|
||||
l1BaseFee := state.GetState(addr, rcfg.L1BaseFeeSlot)
|
||||
overhead := state.GetState(addr, rcfg.OverheadSlot)
|
||||
scalar := state.GetState(addr, rcfg.ScalarSlot)
|
||||
scaled := ScalePrecision(scalar.Big(), rcfg.Precision)
|
||||
return l1BaseFee.Big(), overhead.Big(), scaled
|
||||
}
|
||||
|
||||
// ScalePrecision will scale a value by precision
|
||||
func ScalePrecision(scalar, precision *big.Int) *big.Float {
|
||||
fscalar := new(big.Float).SetInt(scalar)
|
||||
fdivisor := new(big.Float).SetInt(precision)
|
||||
// fscalar / fdivisor
|
||||
return new(big.Float).Quo(fscalar, fdivisor)
|
||||
}
|
||||
|
||||
// CalculateL1Fee computes the L1 fee
|
||||
func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Float) *big.Int {
|
||||
l1GasUsed := CalculateL1GasUsed(data, overhead)
|
||||
l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice)
|
||||
return mulByFloat(l1Fee, scalar)
|
||||
}
|
||||
|
||||
// CalculateL1GasUsed computes the L1 gas used based on the calldata and
|
||||
// constant sized overhead. The overhead can be decreased as the cost of the
|
||||
// batch submission goes down via contract optimizations. This will not overflow
|
||||
// under standard network conditions.
|
||||
func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int {
|
||||
zeroes, ones := zeroesAndOnes(data)
|
||||
zeroesGas := zeroes * params.TxDataZeroGas
|
||||
onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028
|
||||
l1Gas := new(big.Int).SetUint64(zeroesGas + onesGas)
|
||||
return new(big.Int).Add(l1Gas, overhead)
|
||||
}
|
||||
|
||||
// zeroesAndOnes counts the number of 0 bytes and non 0 bytes in a byte slice
|
||||
func zeroesAndOnes(data []byte) (uint64, uint64) {
|
||||
var zeroes uint64
|
||||
var ones uint64
|
||||
for _, byt := range data {
|
||||
if byt == 0 {
|
||||
zeroes++
|
||||
} else {
|
||||
ones++
|
||||
}
|
||||
}
|
||||
return zeroes, ones
|
||||
}
|
||||
|
||||
// mulByFloat multiplies a big.Int by a float and returns the
|
||||
// big.Int rounded upwards
|
||||
func mulByFloat(num *big.Int, float *big.Float) *big.Int {
|
||||
n := new(big.Float).SetUint64(num.Uint64())
|
||||
product := n.Mul(n, float)
|
||||
pfloat, _ := product.Float64()
|
||||
rounded := math.Ceil(pfloat)
|
||||
return new(big.Int).SetUint64(uint64(rounded))
|
||||
}
|
||||
|
|
@ -15,4 +15,13 @@ var (
|
|||
// see contracts/src/L2/predeploys/L2MessageQueue.sol
|
||||
L2MessageQueueAddress = common.HexToAddress("0x5300000000000000000000000000000000000000")
|
||||
WithdrawTrieRootSlot = common.BigToHash(big.NewInt(0))
|
||||
|
||||
// L1GasPriceOracleAddress is the address of the L1GasPriceOracle
|
||||
// predeploy
|
||||
// see scroll-tech/scroll/contracts/src/L2/predeploys/L1GasPriceOracle.sol
|
||||
L1GasPriceOracleAddress = common.HexToAddress("0x5300000000000000000000000000000000000002")
|
||||
Precision = new(big.Int).SetUint64(1e9)
|
||||
L1BaseFeeSlot = common.BigToHash(big.NewInt(1))
|
||||
OverheadSlot = common.BigToHash(big.NewInt(2))
|
||||
ScalarSlot = common.BigToHash(big.NewInt(3))
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue