Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize l1 gas price calculation after snow hardfork #169

Merged
merged 13 commits into from
Mar 26, 2024
1 change: 1 addition & 0 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,7 @@ func NewL2StorageConfig(config *DeployConfig, block *types.Block) (state.Storage
//}

var baseFee *big.Int
// Simplify the basefee when constructing the genesis block and ignore the Snow fork logic just in genesis.
if config.Fermat != nil && config.Fermat.Cmp(big.NewInt(0)) <= 0 {
baseFee = bsc.BaseFeeByNetworks(big.NewInt(int64(config.L2ChainID)))
} else {
Expand Down
34 changes: 20 additions & 14 deletions op-node/chaincfg/chains.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ func GetRollupConfig(name string) (*rollup.Config, error) {
var NetworksByName = map[string]rollup.Config{
"opBNBMainnet": OPBNBMainnet,
"opBNBTestnet": OPBNBTestnet,
"opBNBDevnet": OPBNBDevnet,
"opBNBQANet": OPBNBQANet,
}

var NetworksByChainId = map[string]rollup.Config{
"204": OPBNBMainnet,
"5611": OPBNBTestnet,
"1320": OPBNBDevnet,
"1322": OPBNBQANet,
}

func GetRollupConfigByNetwork(name string) (rollup.Config, error) {
Expand Down Expand Up @@ -141,6 +141,8 @@ var OPBNBMainnet = rollup.Config{
L1SystemConfigAddress: common.HexToAddress("0x7ac836148c14c74086d57f7828f2d065672db3b8"),
RegolithTime: u64Ptr(0),
Fermat: big.NewInt(9397477), // Nov-28-2023 06 AM +UTC
// TODO update timestamp
SnowTime: nil,
}

var OPBNBTestnet = rollup.Config{
Expand Down Expand Up @@ -172,21 +174,23 @@ var OPBNBTestnet = rollup.Config{
L1SystemConfigAddress: common.HexToAddress("0x406ac857817708eaf4ca3a82317ef4ae3d1ea23b"),
RegolithTime: u64Ptr(0),
Fermat: big.NewInt(12113000), // Nov-03-2023 06 AM +UTC
// TODO update timestamp
SnowTime: nil,
}

var OPBNBDevnet = rollup.Config{
var OPBNBQANet = rollup.Config{
Genesis: rollup.Genesis{
L1: eth.BlockID{
Hash: common.HexToHash("0x29aee50ab3edefa64219e5c9b9c07f7d1953a98f2f4003d2c6fd93abeee4b706"),
Number: 2890195,
Hash: common.HexToHash("0x3db93722c9951fe1da25dd652c6e2367674a97161df2acea322e915cab0d58ba"),
Number: 742038,
},
L2: eth.BlockID{
Hash: common.HexToHash("0x49d448b8dc98cc95e3968615ff3dbd904d9eec8252c5f52271f029896e6147ee"),
Hash: common.HexToHash("0x1cba296441b55cf9b5b306b6aef43e68e9aeff2450d68c391dec448604cf3baf"),
Number: 0,
},
L2Time: 1694166483,
L2Time: 1704856150,
SystemConfig: eth.SystemConfig{
BatcherAddr: common.HexToAddress("0x425a3598cb5e2d37213936e187914ea2059957ba"),
BatcherAddr: common.HexToAddress("0xe309831c77d5fb5f189dd97c598e26e5c014f2d6"),
Overhead: eth.Bytes32(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000834")),
Scalar: eth.Bytes32(common.HexToHash("0x00000000000000000000000000000000000000000000000000000000000f4240")),
GasLimit: 100000000,
Expand All @@ -196,13 +200,15 @@ var OPBNBDevnet = rollup.Config{
MaxSequencerDrift: 600,
SeqWindowSize: 14400,
ChannelTimeout: 1200,
L1ChainID: big.NewInt(797),
L2ChainID: big.NewInt(1320),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000000204"),
DepositContractAddress: common.HexToAddress("0xd93160096c5b65bb036b3269eb02328ddadb9856"),
L1SystemConfigAddress: common.HexToAddress("0xf053067cec8d8990de2ba9e17ec2f16c63c7bec4"),
L1ChainID: big.NewInt(714),
L2ChainID: big.NewInt(1322),
BatchInboxAddress: common.HexToAddress("0xff00000000000000000000000000000000001322"),
DepositContractAddress: common.HexToAddress("0xb7cdbce0b1f153b4cb2acc36aeb4d9d2cdda1132"),
L1SystemConfigAddress: common.HexToAddress("0x6a2607255801095b23256a341b24d31275fe2438"),
RegolithTime: u64Ptr(0),
Fermat: big.NewInt(3615117),
// Fermat: big.NewInt(3615117),
// TODO update timestamp
SnowTime: nil,
}

func u64Ptr(v uint64) *uint64 {
Expand Down
69 changes: 57 additions & 12 deletions op-node/rollup/derive/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth"
)

var (
latestBlockHash common.Hash
latestL1GasPrice *big.Int
)

// L1ReceiptsFetcher fetches L1 header info and receipts for the payload attributes derivation (the info tx and deposits)
type L1ReceiptsFetcher interface {
InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error)
Expand Down Expand Up @@ -60,18 +65,6 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
return nil, NewTemporaryError(fmt.Errorf("failed to retrieve L2 parent block: %w", err))
}

// Calculate bsc block base fee
var l1BaseFee *big.Int
if ba.cfg.IsFermat(big.NewInt(int64(l2Parent.Number + 1))) {
l1BaseFee = bsc.BaseFeeByNetworks(ba.cfg.L2ChainID)
} else {
_, transactions, err := ba.l1.InfoAndTxsByHash(ctx, epoch.Hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and txs: %w", err))
}
l1BaseFee = bsc.BaseFeeByTransactions(transactions)
}

// If the L1 origin changed this block, then we are in the first block of the epoch. In this
// case we need to fetch all transaction receipts from the L1 origin block so we can scan for
// user deposits.
Expand Down Expand Up @@ -112,6 +105,22 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
seqNumber = l2Parent.SequenceNumber + 1
}

// Calculate bsc block base fee
var l1BaseFee *big.Int
if ba.cfg.IsSnow(l2Parent.Time + ba.cfg.BlockTime) {
l1BaseFee, err = SnowL1GasPrice(ctx, ba, epoch)
if err != nil {
return nil, err
}
} else if ba.cfg.IsFermat(big.NewInt(int64(l2Parent.Number + 1))) {
l1BaseFee = bsc.BaseFeeByNetworks(ba.cfg.L2ChainID)
} else {
_, transactions, err := ba.l1.InfoAndTxsByHash(ctx, epoch.Hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and txs: %w", err))
}
l1BaseFee = bsc.BaseFeeByTransactions(transactions)
}
l1Info = bsc.NewBlockInfoBSCWrapper(l1Info, l1BaseFee)

// Sanity check the L1 origin was correctly selected to maintain the time invariant between L1 and L2
Expand Down Expand Up @@ -149,3 +158,39 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
func (ba *FetchingAttributesBuilder) CachePayloadByHash(payload *eth.ExecutionPayload) bool {
return ba.l2.CachePayloadByHash(payload)
}

func SnowL1GasPrice(ctx context.Context, ba *FetchingAttributesBuilder, epoch eth.BlockID) (*big.Int, error) {
// Consider this situation. If start a new l2 chain, starting from the block height of l1 less than CountBlockSize,
// in fact, this situation is unlikely to happen except some test cases.
if epoch.Number < bsc.CountBlockSize-1 {
return bsc.DefaultBaseFee, nil
}
if latestBlockHash == epoch.Hash {
return latestL1GasPrice, nil
}
var allMedianGasPrice []*big.Int
blockHash := epoch.Hash
for len(allMedianGasPrice) < bsc.CountBlockSize {
if blockInfo, ok := bsc.BlockInfoCache.Get(blockHash); ok {
allMedianGasPrice = append(allMedianGasPrice, blockInfo.MedianGasPrice)
blockHash = blockInfo.ParentHash
} else {
block, transactions, err := ba.l1.InfoAndTxsByHash(ctx, blockHash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and txs: %w", err))
owen-reorg marked this conversation as resolved.
Show resolved Hide resolved
}
medianGasPrice := bsc.MedianGasPrice(transactions)
allMedianGasPrice = append(allMedianGasPrice, medianGasPrice)
newBlockInfo := bsc.BlockInfo{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
MedianGasPrice: medianGasPrice,
}
bsc.BlockInfoCache.Add(block.Hash(), newBlockInfo)
blockHash = block.ParentHash()
}
}
latestBlockHash = epoch.Hash
latestL1GasPrice = bsc.FinalGasPrice(allMedianGasPrice)
return latestL1GasPrice, nil
}
46 changes: 38 additions & 8 deletions op-node/rollup/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import (
"math/big"
"time"

"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"

"github.com/ethereum-optimism/optimism/op-service/eth"
)

var (
Expand All @@ -36,6 +34,14 @@ var (
ErrL2ChainIDNotPositive = errors.New("L2 chain ID must be non-zero and positive")
)

// NetworkNames are user friendly names to use in the chain spec banner.
var NetworkNames = map[string]string{
"56": "bscMainnet",
"204": "opBNBMainnet",
"97": "bscTestnet",
"5611": "opBNBTestnet",
}
bnoieh marked this conversation as resolved.
Show resolved Hide resolved

type Genesis struct {
// The L1 block that the rollup starts *after* (no derived transactions)
L1 eth.BlockID `json:"l1"`
Expand Down Expand Up @@ -84,6 +90,9 @@ type Config struct {
// OPBNB hard fork L2 block number
// Fermat switch block (nil = no fork, 0 = already on Fermat)
Fermat *big.Int `json:"fermat,omitempty"`
// SnowTime sets the activation time of the next network upgrade.
// Active if SnowTime != nil && L2 block timestamp >= *SnowTime, inactive otherwise.
SnowTime *uint64 `json:"snow_time,omitempty"`

// Note: below addresses are part of the block-derivation process,
// and required to be the same network-wide to stay in consensus.
Expand Down Expand Up @@ -287,6 +296,11 @@ func (c *Config) IsFermat(num *big.Int) bool {
return isBlockForked(c.Fermat, num)
}

// IsSnow returns whether the time is either equal to the Snow fork time or greater.
func (c *Config) IsSnow(time uint64) bool {
return isTimestampForked(c.SnowTime, time)
}

// isBlockForked returns whether a fork scheduled at block s is active at the
// given head block. Whilst this method is the same as isTimestampForked, they
// are explicitly separate for clearer reading.
Expand All @@ -297,20 +311,32 @@ func isBlockForked(s, head *big.Int) bool {
return s.Cmp(head) <= 0
}

// isTimestampForked returns whether a fork scheduled at timestamp s is active
// at the given head timestamp. Whilst this method is the same as isBlockForked,
// they are explicitly separate for clearer reading.
func isTimestampForked(s *uint64, head uint64) bool {
if s == nil {
return false
}
return *s <= head
}

// Description outputs a banner describing the important parts of rollup configuration in a human-readable form.
// Optionally provide a mapping of L2 chain IDs to network names to label the L2 chain with if not unknown.
// The config should be config.Check()-ed before creating a description.
func (c *Config) Description(l2Chains map[string]string) string {
// Find and report the network the user is running
var banner string
networkL2 := ""
// replace using opBNB networks
networkL2 := NetworkNames[c.L2ChainID.String()]
if l2Chains != nil {
networkL2 = l2Chains[c.L2ChainID.String()]
}
if networkL2 == "" {
networkL2 = "unknown L2"
}
networkL1 := params.NetworkNames[c.L1ChainID.String()]
// replace using bsc networks
networkL1 := NetworkNames[c.L1ChainID.String()]
if networkL1 == "" {
networkL1 = "unknown L1"
}
Expand All @@ -328,6 +354,8 @@ func (c *Config) Description(l2Chains map[string]string) string {
banner += fmt.Sprintf(" - SpanBatch: %s\n", fmtForkTimeOrUnset(c.SpanBatchTime))
banner += "OPBNB hard forks (block based):\n"
banner += fmt.Sprintf(" - Fermat: #%-8v\n", c.Fermat)
banner += "OPBNB hard forks (timestamp based):\n"
banner += fmt.Sprintf(" - Snow: %s\n", fmtForkTimeOrUnset(c.SnowTime))
// Report the protocol version
banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport)
return banner
Expand All @@ -338,14 +366,16 @@ func (c *Config) Description(l2Chains map[string]string) string {
// The config should be config.Check()-ed before creating a description.
func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) {
// Find and report the network the user is running
networkL2 := ""
// replace using opBNB networks
networkL2 := NetworkNames[c.L2ChainID.String()]
if l2Chains != nil {
networkL2 = l2Chains[c.L2ChainID.String()]
}
if networkL2 == "" {
networkL2 = "unknown L2"
}
networkL1 := params.NetworkNames[c.L1ChainID.String()]
// replace using bsc networks
networkL1 := NetworkNames[c.L1ChainID.String()]
if networkL1 == "" {
networkL1 = "unknown L1"
}
Expand All @@ -355,7 +385,7 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) {
"l1_block_number", c.Genesis.L1.Number, "regolith_time", fmtForkTimeOrUnset(c.RegolithTime),
"canyon_time", fmtForkTimeOrUnset(c.CanyonTime),
"span_batch_time", fmtForkTimeOrUnset(c.SpanBatchTime),
"Fermat", c.Fermat,
"fermat", c.Fermat, "snow_time", fmtForkTimeOrUnset(c.SnowTime),
)
}

Expand Down
4 changes: 2 additions & 2 deletions op-node/rollup/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ func TestRandomConfigDescription(t *testing.T) {
})
t.Run("named L1", func(t *testing.T) {
config := randConfig()
config.L1ChainID = big.NewInt(5)
config.L1ChainID = big.NewInt(97)
out := config.Description(map[string]string{config.L2ChainID.String(): "foobar chain"})
require.Contains(t, out, "goerli")
require.Contains(t, out, "bscTestnet")
})
t.Run("unnamed", func(t *testing.T) {
config := randConfig()
Expand Down
47 changes: 47 additions & 0 deletions op-service/bsc/compat.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package bsc

import (
lru "github.com/hashicorp/golang-lru/v2"
"math/big"
"sort"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -14,6 +16,24 @@ var DefaultBaseFee = big.NewInt(3000000000)
var DefaultOPBNBTestnetBaseFee = big.NewInt(5000000000)
var OPBNBTestnet = big.NewInt(5611)

const (
percentile = 50
CountBlockSize = 21
BlockInfoCacheCap = 1000
)

type BlockInfo struct {
BlockHash common.Hash
ParentHash common.Hash
MedianGasPrice *big.Int
}

var BlockInfoCache *lru.Cache[common.Hash, BlockInfo]
owen-reorg marked this conversation as resolved.
Show resolved Hide resolved

func init() {
BlockInfoCache, _ = lru.New[common.Hash, BlockInfo](BlockInfoCacheCap)
}

type BlockInfoBSCWrapper struct {
eth.BlockInfo
baseFee *big.Int
Expand Down Expand Up @@ -78,3 +98,30 @@ func ToLegacyCallMsg(callMsg ethereum.CallMsg) ethereum.CallMsg {
Data: callMsg.Data,
}
}

func MedianGasPrice(transactions types.Transactions) *big.Int {
var nonZeroTxsGasPrice []*big.Int
for _, tx := range transactions {
if tx.GasPrice().Cmp(common.Big0) > 0 {
nonZeroTxsGasPrice = append(nonZeroTxsGasPrice, tx.GasPrice())
}
}
sort.Sort(bigIntArray(nonZeroTxsGasPrice))
redhdx marked this conversation as resolved.
Show resolved Hide resolved
medianGasPrice := DefaultBaseFee
if len(nonZeroTxsGasPrice) != 0 {
medianGasPrice = nonZeroTxsGasPrice[(len(nonZeroTxsGasPrice)-1)*percentile/100]
}
return medianGasPrice
}

func FinalGasPrice(allMedianGasPrice []*big.Int) *big.Int {
sort.Sort(bigIntArray(allMedianGasPrice))
finalGasPrice := allMedianGasPrice[(len(allMedianGasPrice)-1)*percentile/100]
return finalGasPrice
}

type bigIntArray []*big.Int

func (s bigIntArray) Len() int { return len(s) }
func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
Loading