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
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 block number
Snow: nil,
redhdx marked this conversation as resolved.
Show resolved Hide resolved
}

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 block number
Snow: 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),
redhdx marked this conversation as resolved.
Show resolved Hide resolved
// TODO update block number
Snow: 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 + 1) {
redhdx marked this conversation as resolved.
Show resolved Hide resolved
l1BaseFee, err = calculateL1GasPrice(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 calculateL1GasPrice(ctx context.Context, ba *FetchingAttributesBuilder, epoch eth.BlockID) (*big.Int, error) {
owen-reorg marked this conversation as resolved.
Show resolved Hide resolved
// 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.
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, epoch.Hash)
redhdx marked this conversation as resolved.
Show resolved Hide resolved
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(epoch.Hash, newBlockInfo)
redhdx marked this conversation as resolved.
Show resolved Hide resolved
blockHash = newBlockInfo.ParentHash
}
}
latestBlockHash = epoch.Hash
latestL1GasPrice = bsc.FinalGasPrice(allMedianGasPrice)
return latestL1GasPrice, nil
}
33 changes: 32 additions & 1 deletion op-node/rollup/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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 +92,8 @@ 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"`
// Snow switch time (nil = no fork, 0 = already on Snow)
Snow *uint64 `json:"snow,omitempty"`
redhdx marked this conversation as resolved.
Show resolved Hide resolved

// 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 +297,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.Snow, time)
bnoieh marked this conversation as resolved.
Show resolved Hide resolved
}

// 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,6 +312,16 @@ 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.
Expand All @@ -307,10 +332,14 @@ func (c *Config) Description(l2Chains map[string]string) string {
if l2Chains != nil {
networkL2 = l2Chains[c.L2ChainID.String()]
}
// replace using opBNB networks
networkL2 = NetworkNames[c.L2ChainID.String()]
redhdx marked this conversation as resolved.
Show resolved Hide resolved
if networkL2 == "" {
networkL2 = "unknown L2"
}
networkL1 := params.NetworkNames[c.L1ChainID.String()]
// replace using bsc networks
networkL1 = NetworkNames[c.L1ChainID.String()]
redhdx marked this conversation as resolved.
Show resolved Hide resolved
if networkL1 == "" {
networkL1 = "unknown L1"
}
Expand All @@ -328,6 +357,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.Snow))
// Report the protocol version
banner += fmt.Sprintf("Node supports up to OP-Stack Protocol Version: %s\n", OPStackSupport)
return banner
Expand Down Expand Up @@ -355,7 +386,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", fmtForkTimeOrUnset(c.Snow),
)
}

Expand Down
48 changes: 48 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,25 @@ var DefaultBaseFee = big.NewInt(3000000000)
var DefaultOPBNBTestnetBaseFee = big.NewInt(5000000000)
var OPBNBTestnet = big.NewInt(5611)

const (
percentile = 50
CountBlockSize = 21
// BlockInfoCacheCap 21 validators in bsc, finalize block requires 15, 2 * (21+15) = 72 to buffer
BlockInfoCacheCap = 72
)

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 +99,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