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:
HAOYUatHZ 2023-02-20 16:28:43 +08:00 committed by GitHub
parent 8a78c736a5
commit ec9254b0b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 209 additions and 10 deletions

View file

@ -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(),

View file

@ -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"`

View file

@ -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
View 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))
}

View file

@ -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))
)