diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66dba3f52e..58856ed188 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: token: ${{ secrets.AVALANCHE_PAT }} - uses: actions/setup-go@v5 with: - go-version: "~1.22.8" + go-version-file: "go.mod" - name: change avalanchego dep if: ${{ github.event_name == 'workflow_dispatch' }} run: | @@ -72,7 +72,7 @@ jobs: token: ${{ secrets.AVALANCHE_PAT }} - uses: actions/setup-go@v5 with: - go-version: "~1.22.8" + go-version-file: "go.mod" - name: change avalanchego dep if: ${{ github.event_name == 'workflow_dispatch' }} run: | @@ -121,7 +121,7 @@ jobs: token: ${{ secrets.AVALANCHE_PAT }} - uses: actions/setup-go@v5 with: - go-version: "~1.22.8" + go-version-file: "go.mod" - name: Run e2e tests run: E2E_SERIAL=1 ./scripts/tests.e2e.sh shell: bash diff --git a/.github/workflows/sync-subnet-evm-branch.yml b/.github/workflows/sync-subnet-evm-branch.yml index e053aec587..e8c37e7744 100644 --- a/.github/workflows/sync-subnet-evm-branch.yml +++ b/.github/workflows/sync-subnet-evm-branch.yml @@ -13,7 +13,6 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 with: - go-version: "~1.22.8" + go-version-file: "go.mod" diff --git a/Dockerfile b/Dockerfile index 1b370e61ed..69e570d2d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # ============= Compilation Stage ================ -FROM golang:1.22.8-bullseye AS builder +FROM golang:1.23.6-bullseye AS builder ARG AVALANCHE_VERSION @@ -17,7 +17,7 @@ WORKDIR $GOPATH/src/github.com/ava-labs/avalanchego RUN go mod download # Replace the coreth dependency RUN go mod edit -replace github.com/ava-labs/coreth=../coreth -RUN go mod download && go mod tidy -compat=1.22 +RUN go mod download && go mod tidy -compat=1.23 # Build the AvalancheGo binary with local version of coreth. RUN ./scripts/build_avalanche.sh diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index c1035f6b8a..d4f6ff6216 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2179,7 +2179,7 @@ func golangBindings(t *testing.T, overload bool) { if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } - tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.22") + tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.23") tidier.Dir = pkg if out, err := tidier.CombinedOutput(); err != nil { t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index fea139d7d0..984280f60e 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -26,6 +26,8 @@ import ( var ( allowedFutureBlockTime = 10 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks + ErrInsufficientBlockGas = errors.New("insufficient gas to cover the block cost") + errInvalidBlockTime = errors.New("timestamp less than parent's") errUnclesUnsupported = errors.New("uncles unsupported") errExtDataGasUsedNil = errors.New("extDataGasUsed is nil") @@ -319,15 +321,16 @@ func (eng *DummyEngine) verifyBlockFee( // set by the base fee of this block. blockGas := new(big.Int).Div(totalBlockFee, baseFee) - // Require that the amount of gas purchased by the effective tips within the block, [blockGas], - // covers at least [requiredBlockGasCost]. + // Require that the amount of gas purchased by the effective tips within the + // block covers at least `requiredBlockGasCost`. // - // NOTE: To determine the [requiredBlockFee], multiply [requiredBlockGasCost] - // by [baseFee]. + // NOTE: To determine the required block fee, multiply + // `requiredBlockGasCost` by `baseFee`. if blockGas.Cmp(requiredBlockGasCost) < 0 { - return fmt.Errorf( - "insufficient gas (%d) to cover the block cost (%d) at base fee (%d) (total block fee: %d)", - blockGas, requiredBlockGasCost, baseFee, totalBlockFee, + return fmt.Errorf("%w: expected %d but got %d", + ErrInsufficientBlockGas, + requiredBlockGasCost, + blockGas, ) } return nil diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 3960a10f5f..11b4368fc7 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -6,7 +6,6 @@ package core import ( "fmt" "math/big" - "strings" "testing" "time" @@ -15,6 +14,7 @@ import ( "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/upgrade/ap4" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/crypto" "github.com/ava-labs/libevm/ethdb" @@ -1284,6 +1284,7 @@ func TestReprocessAcceptBlockIdenticalStateRoot(t *testing.T, create func(db eth func TestGenerateChainInvalidBlockFee(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( + require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -1299,14 +1300,12 @@ func TestGenerateChainInvalidBlockFee(t *testing.T, create func(db ethdb.Databas } blockchain, err := create(chainDB, gspec, common.Hash{}) - if err != nil { - t.Fatal(err) - } - defer blockchain.Stop() + require.NoError(err) + t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) - _, _, _, err = GenerateChainWithGenesis(gspec, blockchain.engine, 3, 0, func(i int, gen *BlockGen) { + _, _, _, err = GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), @@ -1318,21 +1317,15 @@ func TestGenerateChainInvalidBlockFee(t *testing.T, create func(db ethdb.Databas }) signedTx, err := types.SignTx(tx, signer, key1) - if err != nil { - t.Fatal(err) - } + require.NoError(err) gen.AddTx(signedTx) }) - if err == nil { - t.Fatal("should not have been able to build a block because of insufficient block fee") - } - if !strings.Contains(err.Error(), "insufficient gas (0) to cover the block cost (400000)") { - t.Fatalf("should have gotten insufficient block fee error but got %v instead", err) - } + require.ErrorIs(err, dummy.ErrInsufficientBlockGas) } func TestInsertChainInvalidBlockFee(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( + require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -1348,15 +1341,13 @@ func TestInsertChainInvalidBlockFee(t *testing.T, create func(db ethdb.Database, } blockchain, err := create(chainDB, gspec, common.Hash{}) - if err != nil { - t.Fatal(err) - } - defer blockchain.Stop() + require.NoError(err) + t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) eng := dummy.NewFakerWithMode(TestCallbacks, dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}) - _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, 0, func(i int, gen *BlockGen) { + _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), @@ -1368,25 +1359,17 @@ func TestInsertChainInvalidBlockFee(t *testing.T, create func(db ethdb.Database, }) signedTx, err := types.SignTx(tx, signer, key1) - if err != nil { - t.Fatal(err) - } + require.NoError(err) gen.AddTx(signedTx) }) - if err != nil { - t.Fatal(err) - } + require.NoError(err) _, err = blockchain.InsertChain(chain) - if err == nil { - t.Fatal("should not have been able to build a block because of insufficient block fee") - } - if !strings.Contains(err.Error(), "insufficient gas (0) to cover the block cost (400000)") { - t.Fatalf("should have gotten insufficient block fee error but got %v instead", err) - } + require.ErrorIs(err, dummy.ErrInsufficientBlockGas) } func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { var ( + require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -1404,16 +1387,14 @@ func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, g } blockchain, err := create(chainDB, gspec, common.Hash{}) - if err != nil { - t.Fatal(err) - } - defer blockchain.Stop() + require.NoError(err) + t.Cleanup(blockchain.Stop) // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) tip := big.NewInt(50000 * params.GWei) transfer := big.NewInt(10000) - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, 0, func(i int, gen *BlockGen) { + _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 3, ap4.TargetBlockRate-1, func(i int, gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, @@ -1427,22 +1408,15 @@ func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, g }) signedTx, err := types.SignTx(tx, signer, key1) - if err != nil { - t.Fatal(err) - } + require.NoError(err) gen.AddTx(signedTx) }) - if err != nil { - t.Fatal(err) - } + require.NoError(err) // Insert three blocks into the chain and accept only the first block. - if _, err := blockchain.InsertChain(chain); err != nil { - t.Fatal(err) - } - if err := blockchain.Accept(chain[0]); err != nil { - t.Fatal(err) - } + _, err = blockchain.InsertChain(chain) + require.NoError(err) + require.NoError(blockchain.Accept(chain[0])) blockchain.DrainAcceptorQueue() // check the state of the last accepted block @@ -1455,7 +1429,7 @@ func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, g transfer := uint256.MustFromBig(transfer) genesisBalance := uint256.MustFromBig(genesisBalance) expectedBalance1 := new(uint256.Int).Sub(genesisBalance, transfer) - baseFee := big.NewInt(225 * params.GWei) + baseFee := chain[0].BaseFee() feeSpend := new(big.Int).Mul(new(big.Int).Add(baseFee, tip), new(big.Int).SetUint64(params.TxGas)) expectedBalance1.Sub(expectedBalance1, uint256.MustFromBig(feeSpend)) if balance1.Cmp(expectedBalance1) != 0 { diff --git a/eth/gasprice/fee_info_provider.go b/eth/gasprice/fee_info_provider.go index 61a874edb9..718b2d969e 100644 --- a/eth/gasprice/fee_info_provider.go +++ b/eth/gasprice/fee_info_provider.go @@ -92,10 +92,17 @@ func (f *feeInfoProvider) addHeader(ctx context.Context, header *types.Header) ( timestamp: header.Time, baseFee: header.BaseFee, } + + totalGasUsed := new(big.Int).SetUint64(header.GasUsed) + if used := types.GetHeaderExtra(header).ExtDataGasUsed; used != nil { + totalGasUsed.Add(totalGasUsed, used) + } + minGasUsed := new(big.Int).SetUint64(f.minGasUsed) + // Don't bias the estimate with blocks containing a limited number of transactions paying to // expedite block production. var err error - if f.minGasUsed <= header.GasUsed { + if minGasUsed.Cmp(totalGasUsed) <= 0 { // Compute minimum required tip to be included in previous block // // NOTE: Using this approach, we will never recommend that the caller diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index f413979864..a029a3d967 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -251,7 +251,7 @@ func TestSuggestTipCapEmptyExtDataGasUsage(t *testing.T) { numBlocks: 3, extDataGasUsage: nil, genBlock: testGenBlock(t, 55, 370), - expectedTip: big.NewInt(5_713_963_963), + expectedTip: big.NewInt(5_713_963_964), }, defaultOracleConfig()) } @@ -261,7 +261,7 @@ func TestSuggestTipCapSimple(t *testing.T) { numBlocks: 3, extDataGasUsage: common.Big0, genBlock: testGenBlock(t, 55, 370), - expectedTip: big.NewInt(5_713_963_963), + expectedTip: big.NewInt(5_713_963_964), }, defaultOracleConfig()) } @@ -317,7 +317,7 @@ func TestSuggestTipCapSmallTips(t *testing.T) { } }, // NOTE: small tips do not bias estimate - expectedTip: big.NewInt(5_713_963_963), + expectedTip: big.NewInt(5_713_963_964), }, defaultOracleConfig()) } @@ -327,7 +327,7 @@ func TestSuggestTipCapExtDataUsage(t *testing.T) { numBlocks: 3, extDataGasUsage: big.NewInt(10_000), genBlock: testGenBlock(t, 55, 370), - expectedTip: big.NewInt(5_706_726_649), + expectedTip: big.NewInt(5_706_726_650), }, defaultOracleConfig()) } @@ -383,7 +383,7 @@ func TestSuggestTipCapMaxBlocksLookback(t *testing.T) { numBlocks: 20, extDataGasUsage: common.Big0, genBlock: testGenBlock(t, 550, 370), - expectedTip: big.NewInt(51_565_264_256), + expectedTip: big.NewInt(51_565_264_257), }, defaultOracleConfig()) } @@ -393,6 +393,19 @@ func TestSuggestTipCapMaxBlocksSecondsLookback(t *testing.T) { numBlocks: 20, extDataGasUsage: big.NewInt(1), genBlock: testGenBlock(t, 550, 370), - expectedTip: big.NewInt(92_212_529_423), + expectedTip: big.NewInt(92_212_529_424), + }, timeCrunchOracleConfig()) +} + +func TestSuggestTipCapIncludesExtraDataGas(t *testing.T) { + applyGasPriceTest(t, suggestTipCapTest{ + chainConfig: params.TestChainConfig, + numBlocks: 20, + extDataGasUsage: DefaultMinGasUsed, + // The tip on the transaction is very large to pay the block gas cost. + genBlock: testGenBlock(t, 100_000, 1), + // The actual tip doesn't matter, we just want to ensure that the tip is + // non-zero when almost all the gas is coming from the extDataGasUsage. + expectedTip: big.NewInt(83_603_561_239), }, timeCrunchOracleConfig()) } diff --git a/plugin/evm/atomic/tx.go b/plugin/evm/atomic/tx.go index 72337a4829..30791e4000 100644 --- a/plugin/evm/atomic/tx.go +++ b/plugin/evm/atomic/tx.go @@ -298,15 +298,16 @@ func CalculateDynamicFee(cost uint64, baseFee *big.Int) (uint64, error) { if baseFee == nil { return 0, errNilBaseFee } - bigCost := new(big.Int).SetUint64(cost) - fee := new(big.Int).Mul(bigCost, baseFee) - feeToRoundUp := new(big.Int).Add(fee, x2cRateMinus1.ToBig()) - feeInNAVAX := new(big.Int).Div(feeToRoundUp, X2CRate.ToBig()) - if !feeInNAVAX.IsUint64() { + // fee = (cost * baseFee + [X2CRate] - 1) / [X2CRate] + fee := new(big.Int).SetUint64(cost) + fee.Mul(fee, baseFee) + fee.Add(fee, x2cRateMinus1.ToBig()) + fee.Div(fee, X2CRate.ToBig()) + if !fee.IsUint64() { // the fee is more than can fit in a uint64 return 0, errFeeOverflow } - return feeInNAVAX.Uint64(), nil + return fee.Uint64(), nil } func calcBytesCost(len int) uint64 { diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index e0f6681cec..0d1da54009 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -182,18 +182,10 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { // If we are in ApricotPhase4, ensure that ExtDataGasUsed is populated correctly. if rulesExtra.IsApricotPhase4 { - // Make sure ExtDataGasUsed is not nil and correct - if headerExtra.ExtDataGasUsed == nil { - return errNilExtDataGasUsedApricotPhase4 - } if rulesExtra.IsApricotPhase5 { if !utils.BigLessOrEqualUint64(headerExtra.ExtDataGasUsed, ap5.AtomicGasLimit) { return fmt.Errorf("too large extDataGasUsed: %d", headerExtra.ExtDataGasUsed) } - } else { - if !headerExtra.ExtDataGasUsed.IsUint64() { - return fmt.Errorf("too large extDataGasUsed: %d", headerExtra.ExtDataGasUsed) - } } var totalGasUsed uint64 for _, atomicTx := range b.atomicTxs { @@ -204,14 +196,14 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if err != nil { return err } - totalGasUsed, err = safemath.Add64(totalGasUsed, gasUsed) + totalGasUsed, err = safemath.Add(totalGasUsed, gasUsed) if err != nil { return err } } switch { - case headerExtra.ExtDataGasUsed.Cmp(new(big.Int).SetUint64(totalGasUsed)) != 0: + case !utils.BigEqualUint64(headerExtra.ExtDataGasUsed, totalGasUsed): return fmt.Errorf("invalid extDataGasUsed: have %d, want %d", headerExtra.ExtDataGasUsed, totalGasUsed) // Make sure BlockGasCost is not nil diff --git a/plugin/evm/header/block_gas_cost.go b/plugin/evm/header/block_gas_cost.go index 18e96879b6..06f9c1b45b 100644 --- a/plugin/evm/header/block_gas_cost.go +++ b/plugin/evm/header/block_gas_cost.go @@ -11,12 +11,14 @@ import ( "github.com/ava-labs/coreth/params/extras" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap4" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap5" + "github.com/ava-labs/libevm/common" ) var ( errBaseFeeNil = errors.New("base fee is nil") errBlockGasCostNil = errors.New("block gas cost is nil") errExtDataGasUsedNil = errors.New("extDataGasUsed is nil") + errNoGasUsed = errors.New("no gas used") ) // BlockGasCost calculates the required block gas cost based on the parent @@ -95,12 +97,21 @@ func EstimateRequiredTip( } // totalGasUsed = GasUsed + ExtDataGasUsed + headerExtra := types.GetHeaderExtra(header) totalGasUsed := new(big.Int).SetUint64(header.GasUsed) - totalGasUsed.Add(totalGasUsed, extra.ExtDataGasUsed) + totalGasUsed.Add(totalGasUsed, headerExtra.ExtDataGasUsed) + if totalGasUsed.Sign() == 0 { + return nil, errNoGasUsed + } - // totalRequiredTips = blockGasCost * baseFee + // totalRequiredTips = blockGasCost * baseFee + totalGasUsed - 1 + // + // We add totalGasUsed - 1 to ensure that the total required tips + // calculation rounds up. totalRequiredTips := new(big.Int) - totalRequiredTips.Mul(extra.BlockGasCost, header.BaseFee) + totalRequiredTips.Mul(headerExtra.BlockGasCost, header.BaseFee) + totalRequiredTips.Add(totalRequiredTips, totalGasUsed) + totalRequiredTips.Sub(totalRequiredTips, common.Big1) // estimatedTip = totalRequiredTips / totalGasUsed estimatedTip := totalRequiredTips.Div(totalRequiredTips, totalGasUsed) diff --git a/plugin/evm/header/block_gas_cost_test.go b/plugin/evm/header/block_gas_cost_test.go index 762baec09c..3dbbcb6f7a 100644 --- a/plugin/evm/header/block_gas_cost_test.go +++ b/plugin/evm/header/block_gas_cost_test.go @@ -211,6 +211,19 @@ func TestEstimateRequiredTip(t *testing.T) { }, wantErr: errExtDataGasUsedNil, }, + { + name: "no_gas_used", + ap4Timestamp: utils.NewUint64(0), + header: &types.Header{ + GasUsed: 0, + BaseFee: big.NewInt(1), + }, + headerExtra: &types.HeaderExtra{ + ExtDataGasUsed: big.NewInt(0), + BlockGasCost: big.NewInt(1), + }, + wantErr: errNoGasUsed, + }, { name: "success", ap4Timestamp: utils.NewUint64(0), @@ -227,6 +240,22 @@ func TestEstimateRequiredTip(t *testing.T) { // estimatedTip = totalRequiredTips / totalGasUsed want: big.NewInt((101112 * 456) / (123 + 789)), }, + { + name: "success_rounds_up", + ap4Timestamp: utils.NewUint64(0), + header: &types.Header{ + GasUsed: 124, + BaseFee: big.NewInt(456), + }, + headerExtra: &types.HeaderExtra{ + ExtDataGasUsed: big.NewInt(789), + BlockGasCost: big.NewInt(101112), + }, + // totalGasUsed = GasUsed + ExtDataGasUsed + // totalRequiredTips = BlockGasCost * BaseFee + // estimatedTip = totalRequiredTips / totalGasUsed + want: big.NewInt((101112*456)/(124+789) + 1), // +1 to round up + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { diff --git a/plugin/evm/upgrade/acp176/acp176.go b/plugin/evm/upgrade/acp176/acp176.go index 88b582749f..aa4d64077c 100644 --- a/plugin/evm/upgrade/acp176/acp176.go +++ b/plugin/evm/upgrade/acp176/acp176.go @@ -25,11 +25,14 @@ const ( TimeToFillCapacity = 10 // in seconds TargetToMax = 2 // multiplier to convert from target per second to max per second - TargetToPriceUpdateConversion = 43 // 43s ~= 30s * ln(2) which makes the price double at most every ~30 seconds + TargetToPriceUpdateConversion = 87 // 87s ~= 60s * ln(2) which makes the price double at most every ~60 seconds MaxTargetChangeRate = 1024 // Controls the rate that the target can change per block. - targetToMaxCapacity = TargetToMax * TimeToFillCapacity - maxTargetExcess = 1_024_950_627 // TargetConversion * ln(MaxUint64 / MinTargetPerSecond) + 1 + TargetToMaxCapacity = TargetToMax * TimeToFillCapacity + MinMaxPerSecond = MinTargetPerSecond * TargetToMax + MinMaxCapacity = MinMaxPerSecond * TimeToFillCapacity + + maxTargetExcess = 1_024_950_627 // TargetConversion * ln(MaxUint64 / MinTargetPerSecond) + 1 ) // State represents the current state of the gas pricing and constraints. @@ -52,23 +55,15 @@ func (s *State) Target() gas.Gas { // MaxCapacity returns the maximum possible accrued gas capacity, `C`. func (s *State) MaxCapacity() gas.Gas { targetPerSecond := s.Target() - maxCapacity, err := safemath.Mul(targetToMaxCapacity, targetPerSecond) - if err != nil { - maxCapacity = math.MaxUint64 - } - return maxCapacity + return mulWithUpperBound(targetPerSecond, TargetToMaxCapacity) } // GasPrice returns the current required fee per gas. // // GasPrice = MinGasPrice * e^(Excess / (Target() * TargetToPriceUpdateConversion)) func (s *State) GasPrice() gas.Price { - target := s.Target() - priceUpdateConversion, err := safemath.Mul(TargetToPriceUpdateConversion, target) // K - if err != nil { - priceUpdateConversion = math.MaxUint64 - } - + targetPerSecond := s.Target() + priceUpdateConversion := mulWithUpperBound(targetPerSecond, TargetToPriceUpdateConversion) // K return gas.CalculatePrice(MinGasPrice, s.Gas.Excess, priceUpdateConversion) } @@ -76,14 +71,8 @@ func (s *State) GasPrice() gas.Price { // the elapsed seconds. func (s *State) AdvanceTime(seconds uint64) { targetPerSecond := s.Target() - maxPerSecond, err := safemath.Mul(TargetToMax, targetPerSecond) // R - if err != nil { - maxPerSecond = math.MaxUint64 - } - maxCapacity, err := safemath.Mul(TimeToFillCapacity, maxPerSecond) // C - if err != nil { - maxCapacity = math.MaxUint64 - } + maxPerSecond := mulWithUpperBound(targetPerSecond, TargetToMax) // R + maxCapacity := mulWithUpperBound(maxPerSecond, TimeToFillCapacity) // C s.Gas = s.Gas.AdvanceTime( maxCapacity, maxPerSecond, @@ -134,15 +123,18 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { newTargetPerSecond, previousTargetPerSecond, ) + + // Ensure the gas capacity does not exceed the maximum capacity. + newMaxCapacity := mulWithUpperBound(newTargetPerSecond, TargetToMaxCapacity) // C + s.Gas.Capacity = min(s.Gas.Capacity, newMaxCapacity) } // DesiredTargetExcess calculates the optimal desiredTargetExcess given the // desired target. -// -// This could be solved directly by calculating D * ln(desiredTarget / P) using -// floating point math. However, it introduces inaccuracies. So, we use a binary -// search to find the closest integer solution. func DesiredTargetExcess(desiredTarget gas.Gas) gas.Gas { + // This could be solved directly by calculating D * ln(desiredTarget / P) + // using floating point math. However, it introduces inaccuracies. So, we + // use a binary search to find the closest integer solution. return gas.Gas(sort.Search(maxTargetExcess, func(targetExcessGuess int) bool { state := State{ TargetExcess: gas.Gas(targetExcessGuess), @@ -180,3 +172,13 @@ func scaleExcess( bigExcess.Div(&bigExcess, &bigTarget) return gas.Gas(bigExcess.Uint64()) } + +// mulWithUpperBound multiplies two numbers and returns the result. If the +// result overflows, it returns [math.MaxUint64]. +func mulWithUpperBound(a, b gas.Gas) gas.Gas { + product, err := safemath.Mul(a, b) + if err != nil { + return math.MaxUint64 + } + return product +} diff --git a/plugin/evm/upgrade/acp176/acp176_test.go b/plugin/evm/upgrade/acp176/acp176_test.go index c0d513c4f7..6b164dc92a 100644 --- a/plugin/evm/upgrade/acp176/acp176_test.go +++ b/plugin/evm/upgrade/acp176/acp176_test.go @@ -33,130 +33,130 @@ var ( TargetExcess: 0, }, target: MinTargetPerSecond, - maxCapacity: targetToMaxCapacity * MinTargetPerSecond, + maxCapacity: MinMaxCapacity, gasPrice: MinGasPrice, }, { name: "almost_excess_change", state: State{ Gas: gas.State{ - Excess: 29_805_331, // MinTargetPerSecond * ln(2) * TargetToPriceUpdateConversion + Excess: 60_303_808, // MinTargetPerSecond * ln(2) * TargetToPriceUpdateConversion }, TargetExcess: 33, // Largest excess that doesn't increase the target }, skipTestDesiredTargetExcess: true, target: MinTargetPerSecond, - maxCapacity: targetToMaxCapacity * MinTargetPerSecond, + maxCapacity: MinMaxCapacity, gasPrice: 2 * MinGasPrice, }, { name: "small_excess_change", state: State{ Gas: gas.State{ - Excess: 29_805_362, // (MinTargetPerSecond + 1) * ln(2) * TargetToPriceUpdateConversion + Excess: 60_303_868, // (MinTargetPerSecond + 1) * ln(2) * TargetToPriceUpdateConversion }, TargetExcess: 34, // Smallest excess that increases the target }, target: MinTargetPerSecond + 1, - maxCapacity: targetToMaxCapacity * (MinTargetPerSecond + 1), + maxCapacity: TargetToMaxCapacity * (MinTargetPerSecond + 1), gasPrice: 2 * MinGasPrice, }, { name: "max_initial_excess_change", state: State{ Gas: gas.State{ - Excess: 47_286_485, // (MinTargetPerSecond + 977) * ln(3) * TargetToPriceUpdateConversion + Excess: 95_672_652, // (MinTargetPerSecond + 977) * ln(3) * TargetToPriceUpdateConversion }, TargetExcess: MaxTargetExcessDiff, }, skipTestDesiredTargetExcess: true, target: MinTargetPerSecond + 977, - maxCapacity: targetToMaxCapacity * (MinTargetPerSecond + 977), + maxCapacity: TargetToMaxCapacity * (MinTargetPerSecond + 977), gasPrice: 3 * MinGasPrice, }, { name: "current_target", state: State{ Gas: gas.State{ - Excess: 1_336_650_647, // 1_500_000 * ln(nAVAX) * TargetToPriceUpdateConversion + Excess: 2_704_386_192, // 1_500_000 * ln(nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 13_605_152, // 2^25 * ln(1.5) }, target: 1_500_000, - maxCapacity: targetToMaxCapacity * 1_500_000, - gasPrice: (nAVAX + 7) * MinGasPrice, // +7 due to approximation + maxCapacity: TargetToMaxCapacity * 1_500_000, + gasPrice: nAVAX*MinGasPrice + 2, // +2 due to approximation }, { name: "3m_target", state: State{ Gas: gas.State{ - Excess: 3_267_368_247, // 3_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 6_610_721_802, // 3_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 36_863_312, // 2^25 * ln(3) }, target: 3_000_000, - maxCapacity: targetToMaxCapacity * 3_000_000, - gasPrice: (100*nAVAX + 31) * MinGasPrice, // +31 due to approximation + maxCapacity: TargetToMaxCapacity * 3_000_000, + gasPrice: 100*nAVAX*MinGasPrice + 4, // +4 due to approximation }, { name: "6m_target", state: State{ Gas: gas.State{ - Excess: 6_534_736_494, // 6_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 13_221_443_604, // 6_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 60_121_472, // 2^25 * ln(6) }, target: 6_000_000, - maxCapacity: targetToMaxCapacity * 6_000_000, - gasPrice: (100*nAVAX + 31) * MinGasPrice, // +31 due to approximation + maxCapacity: TargetToMaxCapacity * 6_000_000, + gasPrice: 100*nAVAX*MinGasPrice + 4, // +4 due to approximation }, { name: "10m_target", state: State{ Gas: gas.State{ - Excess: 10_891_227_490, // 10_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 22_035_739_340, // 10_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 77_261_935, // 2^25 * ln(10) }, target: 10_000_000, - maxCapacity: targetToMaxCapacity * 10_000_000, - gasPrice: (100*nAVAX + 31) * MinGasPrice, // +31 due to approximation + maxCapacity: TargetToMaxCapacity * 10_000_000, + gasPrice: 100*nAVAX*MinGasPrice + 5, // +5 due to approximation }, { name: "100m_target", state: State{ Gas: gas.State{ - Excess: 108_912_274_899, // 100_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 220_357_393_400, // 100_000_000 * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 154_523_870, // 2^25 * ln(100) }, target: 100_000_000, - maxCapacity: targetToMaxCapacity * 100_000_000, - gasPrice: (100*nAVAX + 8) * MinGasPrice, // +8 due to approximation + maxCapacity: TargetToMaxCapacity * 100_000_000, + gasPrice: 100*nAVAX*MinGasPrice + 5, // +5 due to approximation }, { name: "low_1b_target", state: State{ Gas: gas.State{ - Excess: 1_089_122_722_848, // (1_000_000_000 - 24) * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 2_203_573_881_110, // (1_000_000_000 - 24) * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 231_785_804, // 2^25 * ln(1000) }, target: 1_000_000_000 - 24, - maxCapacity: targetToMaxCapacity * (1_000_000_000 - 24), - gasPrice: (100*nAVAX + 1) * MinGasPrice, // +1 due to approximation + maxCapacity: TargetToMaxCapacity * (1_000_000_000 - 24), + gasPrice: 100 * nAVAX * MinGasPrice, }, { name: "high_1b_target", state: State{ Gas: gas.State{ - Excess: 1_089_122_755_521, // (1_000_000_000 + 6) * ln(100*nAVAX) * TargetToPriceUpdateConversion + Excess: 2_203_573_947_217, // (1_000_000_000 + 6) * ln(100*nAVAX) * TargetToPriceUpdateConversion }, TargetExcess: 231_785_805, // 2^25 * ln(1000) + 1 }, target: 1_000_000_000 + 6, - maxCapacity: targetToMaxCapacity * (1_000_000_000 + 6), - gasPrice: (100 * nAVAX) * MinGasPrice, + maxCapacity: TargetToMaxCapacity * (1_000_000_000 + 6), + gasPrice: 100 * nAVAX * MinGasPrice, }, { name: "largest_max_capacity", @@ -200,7 +200,7 @@ var ( Gas: gas.State{ Excess: math.MaxUint64, }, - TargetExcess: 1_024_950_627, // 2^25 * ln(MaxUint64 / MinTargetPerSecond) + 1 + TargetExcess: maxTargetExcess, }, target: math.MaxUint64, maxCapacity: math.MaxUint64, @@ -558,6 +558,42 @@ var ( TargetExcess: 2 * MaxTargetExcessDiff, }, }, + { + name: "reduce_capacity", + initial: State{ + Gas: gas.State{ + Capacity: 20_039_100, // MinTargetPerSecond * e^(2*MaxTargetExcessDiff / TargetConversion) + Excess: 2_000_000_000, + }, + TargetExcess: 2 * MaxTargetExcessDiff, + }, + desiredTargetExcess: 0, + expected: State{ + Gas: gas.State{ + Capacity: 20_019_540, // MinTargetPerSecond * e^(MaxTargetExcessDiff / TargetConversion) + Excess: 1_998_047_816, // 2M * NewTarget / OldTarget + }, + TargetExcess: MaxTargetExcessDiff, + }, + }, + { + name: "overflow_max_capacity", + initial: State{ + Gas: gas.State{ + Capacity: math.MaxUint64, + Excess: 2_000_000_000, + }, + TargetExcess: maxTargetExcess, + }, + desiredTargetExcess: 0, + expected: State{ + Gas: gas.State{ + Capacity: math.MaxUint64, + Excess: 1_998_047_867, // 2M * NewTarget / OldTarget + }, + TargetExcess: maxTargetExcess - MaxTargetExcessDiff, + }, + }, } ) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index b6111d1e4c..40905d0a9a 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -165,16 +165,15 @@ var ( ) var ( - errEmptyBlock = errors.New("empty block") - errUnsupportedFXs = errors.New("unsupported feature extensions") - errInvalidBlock = errors.New("invalid block") - errInvalidNonce = errors.New("invalid nonce") - errUnclesUnsupported = errors.New("uncles unsupported") - errRejectedParent = errors.New("rejected parent") - errNilBaseFeeApricotPhase3 = errors.New("nil base fee is invalid after apricotPhase3") - errNilExtDataGasUsedApricotPhase4 = errors.New("nil extDataGasUsed is invalid after apricotPhase4") - errNilBlockGasCostApricotPhase4 = errors.New("nil blockGasCost is invalid after apricotPhase4") - errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") + errEmptyBlock = errors.New("empty block") + errUnsupportedFXs = errors.New("unsupported feature extensions") + errInvalidBlock = errors.New("invalid block") + errInvalidNonce = errors.New("invalid nonce") + errUnclesUnsupported = errors.New("uncles unsupported") + errRejectedParent = errors.New("rejected parent") + errNilBaseFeeApricotPhase3 = errors.New("nil base fee is invalid after apricotPhase3") + errNilBlockGasCostApricotPhase4 = errors.New("nil blockGasCost is invalid after apricotPhase4") + errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") ) var originalStderr *os.File diff --git a/scripts/lint.sh b/scripts/lint.sh deleted file mode 100755 index 643291d934..0000000000 --- a/scripts/lint.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -# Upstream is compatible with v1.54.x at time of this writing, and -# checking for this specific version is an attempt to avoid skew -# between local and CI execution. The latest version (v1.55.1) seems -# to cause spurious failures -KNOWN_GOOD_VERSION="v1.56" -VERSION="$(golangci-lint --version | sed -e 's+golangci-lint has version \(v1.*\)\..* built.*+\1+')" -if [[ "${VERSION}" != "${KNOWN_GOOD_VERSION}" ]]; then - echo "expected golangci-lint ${KNOWN_GOOD_VERSION}, but ${VERSION} was used" - echo "${KNOWN_GOOD_VERSION} is used in CI and should be used locally to ensure compatible results" - echo "installation command: go install github.com/golangci/golangci-lint/cmd/golangci-lint@${KNOWN_GOOD_VERSION}" - exit 255 -fi - -golangci-lint run --path-prefix=. --timeout 3m