diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 8d95b3747c01..ebf5045b9e69 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -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 { diff --git a/op-node/chaincfg/chains.go b/op-node/chaincfg/chains.go index 8ebe866d7401..c8e4b1961475 100644 --- a/op-node/chaincfg/chains.go +++ b/op-node/chaincfg/chains.go @@ -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) { @@ -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{ @@ -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, @@ -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 { diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 5044daf2a933..5a1f73e7f834 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -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) @@ -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. @@ -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 @@ -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)) + } + 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 +} diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 485ef1df54b2..31c30fa9af26 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -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 ( @@ -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", +} + type Genesis struct { // The L1 block that the rollup starts *after* (no derived transactions) L1 eth.BlockID `json:"l1"` @@ -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. @@ -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. @@ -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" } @@ -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 @@ -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" } @@ -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), ) } diff --git a/op-node/rollup/types_test.go b/op-node/rollup/types_test.go index d4c4e8bc2357..a2b037b1f7a2 100644 --- a/op-node/rollup/types_test.go +++ b/op-node/rollup/types_test.go @@ -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() diff --git a/op-service/bsc/compat.go b/op-service/bsc/compat.go index 4c1aff6a9f08..06b7b243d988 100644 --- a/op-service/bsc/compat.go +++ b/op-service/bsc/compat.go @@ -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" @@ -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] + +func init() { + BlockInfoCache, _ = lru.New[common.Hash, BlockInfo](BlockInfoCacheCap) +} + type BlockInfoBSCWrapper struct { eth.BlockInfo baseFee *big.Int @@ -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)) + 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] }