diff --git a/RELEASES.md b/RELEASES.md index c6c25d902d..11a802c507 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,7 @@ - Add pending releases here - Upgrade to Go version 1.24 +- Implement ACP-226: Set expected block gas cost to 0 in Granite network upgrade, removing block gas cost requirements for block building. - Implement ACP-226: Add timeMilliseconds (Unix uint64) timestamp to block header for Granite upgrade. ## [v0.7.9](https://github.com/ava-labs/subnet-evm/releases/tag/v0.7.9) diff --git a/core/blockchain_ext_test.go b/core/blockchain_ext_test.go index aed1828809..9700614bd4 100644 --- a/core/blockchain_ext_test.go +++ b/core/blockchain_ext_test.go @@ -1395,7 +1395,7 @@ func GenerateChainInvalidBlockFee(t *testing.T, create createFunc) { // Ensure that key1 has some funds in the genesis block. genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) gspec := &Genesis{ - Config: params.TestChainConfig, + Config: params.TestFortunaChainConfig, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } @@ -1404,10 +1404,10 @@ func GenerateChainInvalidBlockFee(t *testing.T, create createFunc) { t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. - signer := types.LatestSigner(params.TestChainConfig) + signer := types.LatestSigner(params.TestFortunaChainConfig) _, _, _, err = GenerateChainWithGenesis(gspec, blockchain.engine, 3, extras.TestChainConfig.FeeConfig.TargetBlockRate-1, func(_ int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, + ChainID: params.TestFortunaChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &addr2, Gas: ethparams.TxGas, @@ -1436,7 +1436,7 @@ func InsertChainInvalidBlockFee(t *testing.T, create createFunc) { // Ensure that key1 has some funds in the genesis block. genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) gspec := &Genesis{ - Config: params.TestChainConfig, + Config: params.TestFortunaChainConfig, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } @@ -1445,11 +1445,11 @@ func InsertChainInvalidBlockFee(t *testing.T, create createFunc) { t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. - signer := types.LatestSigner(params.TestChainConfig) + signer := types.LatestSigner(params.TestFortunaChainConfig) eng := dummy.NewFakerWithMode(dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}) _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, extras.TestChainConfig.FeeConfig.TargetBlockRate-1, func(_ int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, + ChainID: params.TestFortunaChainConfig.ChainID, Nonce: gen.TxNonce(addr1), To: &addr2, Gas: ethparams.TxGas, diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 3741aa6d3f..2d667b1075 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -237,12 +237,12 @@ func testGenBlock(t *testing.T, tip int64, numTx int) func(int, *core.BlockGen) b.SetCoinbase(common.Address{1}) txTip := big.NewInt(tip * params.GWei) - signer := types.LatestSigner(params.TestChainConfig) + signer := types.LatestSigner(params.TestFortunaChainConfig) baseFee := b.BaseFee() feeCap := new(big.Int).Add(baseFee, txTip) for j := 0; j < numTx; j++ { tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, + ChainID: params.TestFortunaChainConfig.ChainID, Nonce: b.TxNonce(addr), To: &common.Address{}, Gas: ethparams.TxGas, @@ -274,7 +274,7 @@ func TestSuggestTipCapNetworkUpgrades(t *testing.T) { func TestSuggestTipCapSimple(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, + chainConfig: params.TestFortunaChainConfig, numBlocks: 3, genBlock: testGenBlock(t, 55, 370), expectedTip: big.NewInt(1_287_001_288), @@ -283,7 +283,7 @@ func TestSuggestTipCapSimple(t *testing.T) { func TestSuggestTipCapSimpleFloor(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, + chainConfig: params.TestFortunaChainConfig, numBlocks: 1, genBlock: testGenBlock(t, 55, 370), expectedTip: big.NewInt(643_500_644), @@ -293,17 +293,17 @@ func TestSuggestTipCapSimpleFloor(t *testing.T) { func TestSuggestTipCapSmallTips(t *testing.T) { tip := big.NewInt(550 * params.GWei) applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, + chainConfig: params.TestFortunaChainConfig, numBlocks: 3, genBlock: func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{1}) - signer := types.LatestSigner(params.TestChainConfig) + signer := types.LatestSigner(params.TestFortunaChainConfig) baseFee := b.BaseFee() feeCap := new(big.Int).Add(baseFee, tip) for j := 0; j < 185; j++ { tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, + ChainID: params.TestFortunaChainConfig.ChainID, Nonce: b.TxNonce(addr), To: &common.Address{}, Gas: ethparams.TxGas, @@ -317,7 +317,7 @@ func TestSuggestTipCapSmallTips(t *testing.T) { } b.AddTx(tx) tx = types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, + ChainID: params.TestFortunaChainConfig.ChainID, Nonce: b.TxNonce(addr), To: &common.Address{}, Gas: ethparams.TxGas, @@ -336,7 +336,7 @@ func TestSuggestTipCapSmallTips(t *testing.T) { func TestSuggestTipCapMinGas(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, + chainConfig: params.TestFortunaChainConfig, numBlocks: 3, genBlock: testGenBlock(t, 500, 50), expectedTip: DefaultMinPrice, @@ -352,10 +352,10 @@ func TestSuggestGasPriceSubnetEVM(t *testing.T) { Percentile: 60, } - backend := newTestBackend(t, params.TestChainConfig, 3, func(i int, b *core.BlockGen) { + backend := newTestBackend(t, params.TestFortunaChainConfig, 3, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{1}) - signer := types.LatestSigner(params.TestChainConfig) + signer := types.LatestSigner(params.TestFortunaChainConfig) gasPrice := big.NewInt(legacy.BaseFee) for j := 0; j < 50; j++ { tx := types.NewTx(&types.LegacyTx{ @@ -379,18 +379,57 @@ func TestSuggestGasPriceSubnetEVM(t *testing.T) { require.NoError(t, err) } +// NOTE: [Oracle.SuggestTipCap] does NOT simply return the "required" (minimum) tip. +// The oracle computes a percentile of recent required tips (not observed on-chain tips) +// within a time/blocks lookback window and applies a small floor (e.g., 1 wei in tests): +// +// suggested = max(floor, recent-required-percentile) +// +// After Granite, BlockGasCost is 0 and per-block required tips are 0, so the oracle +// suggestion equals the floor (1 wei) in steady state, regardless of high on-chain tips. +// The cases below exercise behavior across forks using the same percentile logic and floor. func TestSuggestTipCapMaxBlocksLookback(t *testing.T) { + cases := []struct { + chainConfig *params.ChainConfig + expectedTip *big.Int + }{ + // TODO: remove Fortuna case when we activate Granite + { + chainConfig: params.TestFortunaChainConfig, + expectedTip: big.NewInt(1), + }, + { + chainConfig: params.TestChainConfig, + expectedTip: big.NewInt(1), + }, + } + for _, c := range cases { + applyGasPriceTest(t, suggestTipCapTest{ + chainConfig: c.chainConfig, + numBlocks: 200, + genBlock: testGenBlock(t, 550, 80), + expectedTip: c.expectedTip, + }, defaultOracleConfig()) + } +} + +// Post-Granite, even very high observed tx tips should not affect SuggestTipCap, which +// is computed from required tips. Since required tips are 0 in Granite, the returned +// suggestion should be the floor (1 wei). +func TestSuggestTipCapIgnoresObservedTipsPostGranite(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, + chainConfig: params.TestChainConfig, // Granite active in TestChainConfig numBlocks: 20, - genBlock: testGenBlock(t, 550, 370), - expectedTip: big.NewInt(5_807_226_111), + // Generate blocks with very high on-chain tips to ensure they wouldn't bias the result + // if the oracle looked at observed tips. Expectation remains 1 wei. + genBlock: testGenBlock(t, 100_000, 80), + expectedTip: big.NewInt(1), }, defaultOracleConfig()) } func TestSuggestTipCapMaxBlocksSecondsLookback(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, + chainConfig: params.TestFortunaChainConfig, numBlocks: 20, genBlock: testGenBlock(t, 550, 370), expectedTip: big.NewInt(10_384_877_852), @@ -407,14 +446,14 @@ func TestEstimateBaseFeeAfterFeeConfigUpdate(t *testing.T) { } // create a chain config with fee manager enabled at genesis with [addr] as the admin - chainConfig := params.Copy(params.TestChainConfig) + chainConfig := params.Copy(params.TestFortunaChainConfig) chainConfigExtra := params.GetExtra(&chainConfig) chainConfigExtra.GenesisPrecompiles = extras.Precompiles{ feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(0), []common.Address{addr}, nil, nil, nil), } // create a fee config with higher MinBaseFee and prepare it for inclusion in a tx - signer := types.LatestSigner(params.TestChainConfig) + signer := types.LatestSigner(params.TestFortunaChainConfig) highFeeConfig := chainConfigExtra.FeeConfig highFeeConfig.MinBaseFee = big.NewInt(28_000_000_000) data, err := feemanager.PackSetFeeConfig(highFeeConfig) diff --git a/plugin/evm/customheader/block_gas_cost.go b/plugin/evm/customheader/block_gas_cost.go index 2d9b74857a..66f3642e8c 100644 --- a/plugin/evm/customheader/block_gas_cost.go +++ b/plugin/evm/customheader/block_gas_cost.go @@ -28,6 +28,7 @@ var ( // BlockGasCost calculates the required block gas cost based on the parent // header and the timestamp of the new block. // Prior to Subnet-EVM, the returned block gas cost will be nil. +// In Granite, the returned block gas cost will be 0. func BlockGasCost( config *extras.ChainConfig, feeConfig commontype.FeeConfig, @@ -37,7 +38,12 @@ func BlockGasCost( if !config.IsSubnetEVM(timestamp) { return nil } + + if config.IsGranite(timestamp) { + return big.NewInt(0) + } step := feeConfig.BlockGasCostStep.Uint64() + // Treat an invalid parent/current time combination as 0 elapsed time. // // TODO: Does it even make sense to handle this? The timestamp should be diff --git a/plugin/evm/customheader/block_gas_cost_test.go b/plugin/evm/customheader/block_gas_cost_test.go index 55e47ab536..c94a273c04 100644 --- a/plugin/evm/customheader/block_gas_cost_test.go +++ b/plugin/evm/customheader/block_gas_cost_test.go @@ -76,8 +76,8 @@ func BlockGasCostTest(t *testing.T, feeConfig commontype.FeeConfig) { expected: nil, }, { - name: "normal", - upgrades: extras.TestChainConfig.NetworkUpgrades, + name: "normal_pre_granite", + upgrades: extras.TestFortunaChainConfig.NetworkUpgrades, parentTime: 10, parentCost: maxBlockGasCostBig, timestamp: 10 + targetBlockRate + 1, @@ -85,12 +85,20 @@ func BlockGasCostTest(t *testing.T, feeConfig commontype.FeeConfig) { }, { name: "negative_time_elapsed", - upgrades: extras.TestChainConfig.NetworkUpgrades, + upgrades: extras.TestFortunaChainConfig.NetworkUpgrades, parentTime: 10, parentCost: feeConfig.MinBlockGasCost, timestamp: 9, expected: new(big.Int).SetUint64(minBlockGasCost + blockGasCostStep*targetBlockRate), }, + { + name: "granite_returns_zero", + upgrades: extras.TestGraniteChainConfig.NetworkUpgrades, + parentTime: 10, + parentCost: big.NewInt(int64(maxBlockGasCost)), + timestamp: 10 + targetBlockRate + 1, + expected: big.NewInt(0), + }, } for _, test := range tests { diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index f127b0cc07..3244ad4673 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2179,100 +2179,6 @@ func testLastAcceptedBlockNumberAllow(t *testing.T, scheme string) { } } -// Regression test to ensure we can build blocks if we are starting with the -// Subnet EVM ruleset in genesis. -func TestBuildSubnetEVMBlock(t *testing.T) { - for _, scheme := range schemes { - t.Run(scheme, func(t *testing.T) { - testBuildSubnetEVMBlock(t, scheme) - }) - } -} - -func testBuildSubnetEVMBlock(t *testing.T, scheme string) { - tvm := newVM(t, testVMConfig{ - genesisJSON: genesisJSONSubnetEVM, - configJSON: getConfig(scheme, ""), - }) - - defer func() { - if err := tvm.vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - }() - - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - tvm.vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - - tx := types.NewTransaction(uint64(0), testEthAddrs[1], new(big.Int).Mul(firstTxAmount, big.NewInt(4)), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(tvm.vm.chainConfig.ChainID), testKeys[0].ToECDSA()) - if err != nil { - t.Fatal(err) - } - - txErrors := tvm.vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } - } - - blk := issueAndAccept(t, tvm.vm) - newHead := <-newTxPoolHeadChan - if newHead.Head.Hash() != common.Hash(blk.ID()) { - t.Fatalf("Expected new block to match") - } - - txs := make([]*types.Transaction, 10) - for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(tvm.vm.chainConfig.ChainID), testKeys[1].ToECDSA()) - if err != nil { - t.Fatal(err) - } - txs[i] = signedTx - } - errs := tvm.vm.txPool.AddRemotesSync(txs) - for i, err := range errs { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } - } - - blk = issueAndAccept(t, tvm.vm) - ethBlk := blk.(*chain.BlockWrapper).Block.(*wrappedBlock).ethBlock - if customtypes.BlockGasCost(ethBlk) == nil || customtypes.BlockGasCost(ethBlk).Cmp(big.NewInt(100)) < 0 { - t.Fatalf("expected blockGasCost to be at least 100 but got %d", customtypes.BlockGasCost(ethBlk)) - } - chainConfig := params.GetExtra(tvm.vm.chainConfig) - minRequiredTip, err := customheader.EstimateRequiredTip(chainConfig, ethBlk.Header()) - if err != nil { - t.Fatal(err) - } - if minRequiredTip == nil || minRequiredTip.Cmp(big.NewInt(0.05*utils.GWei)) < 0 { - t.Fatalf("expected minRequiredTip to be at least 0.05 gwei but got %d", minRequiredTip) - } - - lastAcceptedID, err := tvm.vm.LastAccepted(context.Background()) - if err != nil { - t.Fatal(err) - } - if lastAcceptedID != blk.ID() { - t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk.ID(), lastAcceptedID) - } - - // Confirm all txs are present - ethBlkTxs := tvm.vm.blockChain.GetBlockByNumber(2).Transactions() - for i, tx := range txs { - if len(ethBlkTxs) <= i { - t.Fatalf("missing transactions expected: %d but found: %d", len(txs), len(ethBlkTxs)) - } - if ethBlkTxs[i].Hash() != tx.Hash() { - t.Fatalf("expected tx at index %d to have hash: %x but has: %x", i, txs[i].Hash(), tx.Hash()) - } - } -} - func TestBuildAllowListActivationBlock(t *testing.T) { for _, scheme := range schemes { t.Run(scheme, func(t *testing.T) {