diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 2d5b486943..a682fe3077 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -120,7 +120,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { GasLimit: gasLimit, GasUsed: 0, BaseFee: mid, - Extra: make([]byte, header.FeeWindowSize), + Extra: make([]byte, ap3.WindowSize), } baseFee, err := header.BaseFee( bc.config, parent, blockTime, @@ -159,7 +159,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { GasLimit: gasLimit, BaseFee: baseFee, ExcessBlobGas: &excessBlobGas, - Extra: make([]byte, header.FeeWindowSize), + Extra: make([]byte, ap3.WindowSize), } } diff --git a/plugin/evm/header/base_fee_test.go b/plugin/evm/header/base_fee_test.go index 609f115e23..d3dc3a5635 100644 --- a/plugin/evm/header/base_fee_test.go +++ b/plugin/evm/header/base_fee_test.go @@ -58,7 +58,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), }, - wantErr: errDynamicFeeWindowInsufficientLength, + wantErr: ap3.ErrWindowInsufficientLength, }, { name: "ap3_invalid_timestamp", @@ -66,7 +66,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), Time: 1, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), }, timestamp: 0, wantErr: errInvalidTimestamp, @@ -78,7 +78,7 @@ func TestBaseFee(t *testing.T) { Number: big.NewInt(1), GasUsed: ap3.TargetGas - ap3.IntrinsicBlockGas, Time: 1, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap3.MinBaseFee + 1), }, timestamp: 1, @@ -89,7 +89,7 @@ func TestBaseFee(t *testing.T) { upgrades: params.TestApricotPhase3Config.NetworkUpgrades, parent: &types.Header{ Number: big.NewInt(1), - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap3.MaxBaseFee), }, timestamp: 1, @@ -112,7 +112,7 @@ func TestBaseFee(t *testing.T) { upgrades: params.TestApricotPhase3Config.NetworkUpgrades, parent: &types.Header{ Number: big.NewInt(1), - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap3.MaxBaseFee), }, timestamp: 2 * ap3.WindowLen, @@ -137,7 +137,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: 2 * ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap3.MinBaseFee), }, timestamp: 1, @@ -161,7 +161,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: 1, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(1), }, timestamp: 2 * ap3.WindowLen, @@ -180,7 +180,7 @@ func TestBaseFee(t *testing.T) { upgrades: params.TestApricotPhase4Config.NetworkUpgrades, parent: &types.Header{ Number: big.NewInt(1), - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap4.MaxBaseFee), BlockGasCost: big.NewInt(ap4.MinBlockGasCost), }, @@ -205,7 +205,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap4.MinBaseFee), ExtDataGasUsed: big.NewInt(ap3.TargetGas), BlockGasCost: big.NewInt(ap4.MinBlockGasCost), @@ -238,7 +238,7 @@ func TestBaseFee(t *testing.T) { upgrades: params.TestApricotPhase5Config.NetworkUpgrades, parent: &types.Header{ Number: big.NewInt(1), - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap4.MaxBaseFee), }, timestamp: 1, @@ -262,7 +262,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap5.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap4.MinBaseFee), ExtDataGasUsed: big.NewInt(ap5.TargetGas), }, @@ -295,7 +295,7 @@ func TestBaseFee(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap5.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(etna.MinBaseFee), ExtDataGasUsed: big.NewInt(ap5.TargetGas), }, @@ -346,7 +346,7 @@ func TestEstimateNextBaseFee(t *testing.T) { upgrades: params.TestApricotPhase3Config.NetworkUpgrades, parent: &types.Header{ Number: big.NewInt(1), - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BaseFee: big.NewInt(ap3.MaxBaseFee), }, timestamp: 1, diff --git a/plugin/evm/header/dynamic_fee_windower.go b/plugin/evm/header/dynamic_fee_windower.go index 3503c60704..6308c54b73 100644 --- a/plugin/evm/header/dynamic_fee_windower.go +++ b/plugin/evm/header/dynamic_fee_windower.go @@ -4,12 +4,10 @@ package header import ( - "encoding/binary" "errors" "fmt" "math/big" - "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" @@ -20,10 +18,6 @@ import ( "github.com/ethereum/go-ethereum/common/math" ) -// FeeWindowSize is the number of bytes that are used to encode the dynamic fee -// window in the header's Extra field after the Apricot Phase 3 upgrade. -const FeeWindowSize = wrappers.LongLen * ap3.WindowLen - var ( maxUint256Plus1 = new(big.Int).Lsh(common.Big1, 256) maxUint256 = new(big.Int).Sub(maxUint256Plus1, common.Big1) @@ -41,8 +35,7 @@ var ( ap3BaseFeeChangeDenominator = big.NewInt(ap3.BaseFeeChangeDenominator) ap5BaseFeeChangeDenominator = big.NewInt(ap5.BaseFeeChangeDenominator) - errInvalidTimestamp = errors.New("invalid timestamp") - errDynamicFeeWindowInsufficientLength = errors.New("insufficient length for dynamic fee window") + errInvalidTimestamp = errors.New("invalid timestamp") ) // baseFeeFromWindow should only be called if `timestamp` >= `config.ApricotPhase3Timestamp` @@ -165,7 +158,7 @@ func feeWindow( return ap3.Window{}, nil } - dynamicFeeWindow, err := parseFeeWindow(parent.Extra) + dynamicFeeWindow, err := ap3.ParseWindow(parent.Extra) if err != nil { return ap3.Window{}, err } @@ -236,29 +229,3 @@ func selectBigWithinBounds(lowerBound, value, upperBound *big.Int) *big.Int { return value } } - -func parseFeeWindow(bytes []byte) (ap3.Window, error) { - if len(bytes) < FeeWindowSize { - return ap3.Window{}, fmt.Errorf("%w: expected at least %d bytes but got %d bytes", - errDynamicFeeWindowInsufficientLength, - FeeWindowSize, - len(bytes), - ) - } - - var window ap3.Window - for i := range window { - offset := i * wrappers.LongLen - window[i] = binary.BigEndian.Uint64(bytes[offset:]) - } - return window, nil -} - -func feeWindowBytes(w ap3.Window) []byte { - bytes := make([]byte, FeeWindowSize) - for i, v := range w { - offset := i * wrappers.LongLen - binary.BigEndian.PutUint64(bytes[offset:], v) - } - return bytes -} diff --git a/plugin/evm/header/dynamic_fee_windower_test.go b/plugin/evm/header/dynamic_fee_windower_test.go index 287537f0d2..6e554a1cc0 100644 --- a/plugin/evm/header/dynamic_fee_windower_test.go +++ b/plugin/evm/header/dynamic_fee_windower_test.go @@ -6,9 +6,6 @@ package header import ( "math/big" "testing" - - "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" - "github.com/stretchr/testify/require" ) func TestSelectBigWithinBounds(t *testing.T) { @@ -58,73 +55,3 @@ func TestSelectBigWithinBounds(t *testing.T) { }) } } - -func TestParseDynamicFeeWindow(t *testing.T) { - tests := []struct { - name string - bytes []byte - window ap3.Window - parseErr error - }{ - { - name: "insufficient_length", - bytes: make([]byte, FeeWindowSize-1), - parseErr: errDynamicFeeWindowInsufficientLength, - }, - { - name: "zero_window", - bytes: make([]byte, FeeWindowSize), - window: ap3.Window{}, - }, - { - name: "truncate_bytes", - bytes: []byte{ - FeeWindowSize: 1, - }, - window: ap3.Window{}, - }, - { - name: "endianess", - bytes: []byte{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - }, - window: ap3.Window{ - 0x0102030405060708, - 0x1112131415161718, - 0x2122232425262728, - 0x3132333435363738, - 0x4142434445464748, - 0x5152535455565758, - 0x6162636465666768, - 0x7172737475767778, - 0x8182838485868788, - 0x9192939495969798, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require := require.New(t) - - window, err := parseFeeWindow(test.bytes) - require.Equal(test.window, window) - require.ErrorIs(err, test.parseErr) - if test.parseErr != nil { - return - } - - expectedBytes := test.bytes[:FeeWindowSize] - bytes := feeWindowBytes(window) - require.Equal(expectedBytes, bytes) - }) - } -} diff --git a/plugin/evm/header/extra.go b/plugin/evm/header/extra.go index 79736f41d0..3d87d636b0 100644 --- a/plugin/evm/header/extra.go +++ b/plugin/evm/header/extra.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/plugin/evm/upgrade/ap3" ) var errInvalidExtraLength = errors.New("invalid header.Extra length") @@ -26,7 +27,7 @@ func ExtraPrefix( if err != nil { return nil, fmt.Errorf("failed to calculate fee window: %w", err) } - return feeWindowBytes(window), nil + return window.Bytes(), nil default: // Prior to AP3 there was no expected extra prefix. return nil, nil @@ -39,20 +40,20 @@ func VerifyExtra(rules params.AvalancheRules, extra []byte) error { extraLen := len(extra) switch { case rules.IsDurango: - if extraLen < FeeWindowSize { + if extraLen < ap3.WindowSize { return fmt.Errorf( "%w: expected >= %d but got %d", errInvalidExtraLength, - FeeWindowSize, + ap3.WindowSize, extraLen, ) } case rules.IsApricotPhase3: - if extraLen != FeeWindowSize { + if extraLen != ap3.WindowSize { return fmt.Errorf( "%w: expected %d but got %d", errInvalidExtraLength, - FeeWindowSize, + ap3.WindowSize, extraLen, ) } @@ -84,8 +85,8 @@ func PredicateBytesFromExtra(_ params.AvalancheRules, extra []byte) []byte { // to this size. // After Durango, the VM pre-verifies the extra data past the dynamic fee // rollup window is valid. - if len(extra) <= FeeWindowSize { + if len(extra) <= ap3.WindowSize { return nil } - return extra[FeeWindowSize:] + return extra[ap3.WindowSize:] } diff --git a/plugin/evm/header/extra_test.go b/plugin/evm/header/extra_test.go index 887aa9dbe2..345c2b776d 100644 --- a/plugin/evm/header/extra_test.go +++ b/plugin/evm/header/extra_test.go @@ -40,7 +40,7 @@ func TestExtraPrefix(t *testing.T) { Number: big.NewInt(1), }, timestamp: 1, - want: feeWindowBytes(ap3.Window{}), + want: (&ap3.Window{}).Bytes(), }, { name: "ap3_genesis_block", @@ -48,7 +48,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(0), }, - want: feeWindowBytes(ap3.Window{}), + want: (&ap3.Window{}).Bytes(), }, { name: "ap3_invalid_fee_window", @@ -56,7 +56,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), }, - wantErr: errDynamicFeeWindowInsufficientLength, + wantErr: ap3.ErrWindowInsufficientLength, }, { name: "ap3_invalid_timestamp", @@ -64,7 +64,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), Time: 1, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), }, timestamp: 0, wantErr: errInvalidTimestamp, @@ -75,9 +75,9 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{ + Extra: (&ap3.Window{ 1, 2, 3, 4, - }), + }).Bytes(), }, timestamp: 1, want: func() []byte { @@ -86,7 +86,7 @@ func TestExtraPrefix(t *testing.T) { } window.Add(ap3.TargetGas, ap3.IntrinsicBlockGas) window.Shift(1) - return feeWindowBytes(window) + return window.Bytes() }(), }, { @@ -95,7 +95,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(0), }, - want: feeWindowBytes(ap3.Window{}), + want: (&ap3.Window{}).Bytes(), }, { name: "ap4_no_block_gas_cost", @@ -103,14 +103,14 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), }, timestamp: 2, want: func() []byte { var window ap3.Window window.Add(ap3.TargetGas) window.Shift(2) - return feeWindowBytes(window) + return window.Bytes() }(), }, { @@ -119,7 +119,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BlockGasCost: big.NewInt(ap4.MinBlockGasCost), }, timestamp: 1, @@ -130,7 +130,7 @@ func TestExtraPrefix(t *testing.T) { (ap4.TargetBlockRate-1)*ap4.BlockGasCostStep, ) window.Shift(1) - return feeWindowBytes(window) + return window.Bytes() }(), }, { @@ -139,7 +139,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), ExtDataGasUsed: big.NewInt(5), }, timestamp: 1, @@ -150,7 +150,7 @@ func TestExtraPrefix(t *testing.T) { 5, ) window.Shift(1) - return feeWindowBytes(window) + return window.Bytes() }(), }, { @@ -159,9 +159,9 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap3.TargetGas, - Extra: feeWindowBytes(ap3.Window{ + Extra: (&ap3.Window{ 1, 2, 3, 4, - }), + }).Bytes(), ExtDataGasUsed: big.NewInt(5), BlockGasCost: big.NewInt(ap4.MinBlockGasCost), }, @@ -176,7 +176,7 @@ func TestExtraPrefix(t *testing.T) { (ap4.TargetBlockRate-1)*ap4.BlockGasCostStep, ) window.Shift(1) - return feeWindowBytes(window) + return window.Bytes() }(), }, { @@ -185,7 +185,7 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap5.TargetGas, - Extra: feeWindowBytes(ap3.Window{}), + Extra: (&ap3.Window{}).Bytes(), BlockGasCost: big.NewInt(ap4.MinBlockGasCost), }, timestamp: 1, @@ -193,7 +193,7 @@ func TestExtraPrefix(t *testing.T) { var window ap3.Window window.Add(ap5.TargetGas) window.Shift(1) - return feeWindowBytes(window) + return window.Bytes() }(), }, { @@ -202,9 +202,9 @@ func TestExtraPrefix(t *testing.T) { parent: &types.Header{ Number: big.NewInt(1), GasUsed: ap5.TargetGas, - Extra: feeWindowBytes(ap3.Window{ + Extra: (&ap3.Window{ 1, 2, 3, 4, - }), + }).Bytes(), ExtDataGasUsed: big.NewInt(5), BlockGasCost: big.NewInt(ap4.MinBlockGasCost), }, @@ -218,7 +218,7 @@ func TestExtraPrefix(t *testing.T) { 5, ) window.Shift(1) - return feeWindowBytes(window) + return window.Bytes() }(), }, } @@ -276,7 +276,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsApricotPhase3: true, }, - extra: make([]byte, FeeWindowSize), + extra: make([]byte, ap3.WindowSize), expected: nil, }, { @@ -284,7 +284,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsApricotPhase3: true, }, - extra: make([]byte, FeeWindowSize-1), + extra: make([]byte, ap3.WindowSize-1), expected: errInvalidExtraLength, }, { @@ -292,7 +292,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsApricotPhase3: true, }, - extra: make([]byte, FeeWindowSize+1), + extra: make([]byte, ap3.WindowSize+1), expected: errInvalidExtraLength, }, { @@ -300,7 +300,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsDurango: true, }, - extra: make([]byte, FeeWindowSize), + extra: make([]byte, ap3.WindowSize), expected: nil, }, { @@ -308,7 +308,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsDurango: true, }, - extra: make([]byte, FeeWindowSize+1), + extra: make([]byte, ap3.WindowSize+1), expected: nil, }, { @@ -316,7 +316,7 @@ func TestVerifyExtra(t *testing.T) { rules: params.AvalancheRules{ IsDurango: true, }, - extra: make([]byte, FeeWindowSize-1), + extra: make([]byte, ap3.WindowSize-1), expected: errInvalidExtraLength, }, } @@ -342,18 +342,18 @@ func TestPredicateBytesFromExtra(t *testing.T) { }, { name: "too_short", - extra: make([]byte, FeeWindowSize-1), + extra: make([]byte, ap3.WindowSize-1), expected: nil, }, { name: "empty_predicate", - extra: make([]byte, FeeWindowSize), + extra: make([]byte, ap3.WindowSize), expected: nil, }, { name: "non_empty_predicate", extra: []byte{ - FeeWindowSize: 5, + ap3.WindowSize: 5, }, expected: []byte{5}, }, diff --git a/plugin/evm/upgrade/acp176/acp176.go b/plugin/evm/upgrade/acp176/acp176.go index aa4d64077c..559a59c440 100644 --- a/plugin/evm/upgrade/acp176/acp176.go +++ b/plugin/evm/upgrade/acp176/acp176.go @@ -6,15 +6,17 @@ package acp176 import ( + "encoding/binary" + "errors" "fmt" "math" "math/big" "sort" + safemath "github.com/ava-labs/avalanchego/utils/math" + "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/avalanchego/vms/components/gas" "github.com/holiman/uint256" - - safemath "github.com/ava-labs/avalanchego/utils/math" ) const ( @@ -32,15 +34,40 @@ const ( MinMaxPerSecond = MinTargetPerSecond * TargetToMax MinMaxCapacity = MinMaxPerSecond * TimeToFillCapacity + StateSize = 3 * wrappers.LongLen + maxTargetExcess = 1_024_950_627 // TargetConversion * ln(MaxUint64 / MinTargetPerSecond) + 1 ) +var ErrStateInsufficientLength = errors.New("insufficient length for fee state") + // State represents the current state of the gas pricing and constraints. type State struct { Gas gas.State TargetExcess gas.Gas // q } +// ParseState returns the state from the provided bytes. It is the inverse of +// [State.Bytes]. This function allows for additional bytes to be padded at the +// end of the provided bytes. +func ParseState(bytes []byte) (State, error) { + if len(bytes) < StateSize { + return State{}, fmt.Errorf("%w: expected at least %d bytes but got %d bytes", + ErrStateInsufficientLength, + StateSize, + len(bytes), + ) + } + + return State{ + Gas: gas.State{ + Capacity: gas.Gas(binary.BigEndian.Uint64(bytes)), + Excess: gas.Gas(binary.BigEndian.Uint64(bytes[wrappers.LongLen:])), + }, + TargetExcess: gas.Gas(binary.BigEndian.Uint64(bytes[2*wrappers.LongLen:])), + }, nil +} + // Target returns the target gas consumed per second, `T`. // // Target = MinTargetPerSecond * e^(TargetExcess / TargetConversion) @@ -129,6 +156,15 @@ func (s *State) UpdateTargetExcess(desiredTargetExcess gas.Gas) { s.Gas.Capacity = min(s.Gas.Capacity, newMaxCapacity) } +// Bytes returns the binary representation of the state. +func (s *State) Bytes() []byte { + bytes := make([]byte, StateSize) + binary.BigEndian.PutUint64(bytes, uint64(s.Gas.Capacity)) + binary.BigEndian.PutUint64(bytes[wrappers.LongLen:], uint64(s.Gas.Excess)) + binary.BigEndian.PutUint64(bytes[2*wrappers.LongLen:], uint64(s.TargetExcess)) + return bytes +} + // DesiredTargetExcess calculates the optimal desiredTargetExcess given the // desired target. func DesiredTargetExcess(desiredTarget gas.Gas) gas.Gas { diff --git a/plugin/evm/upgrade/acp176/acp176_test.go b/plugin/evm/upgrade/acp176/acp176_test.go index a6d62ea30c..bbe8031025 100644 --- a/plugin/evm/upgrade/acp176/acp176_test.go +++ b/plugin/evm/upgrade/acp176/acp176_test.go @@ -595,6 +595,45 @@ var ( }, }, } + parseTests = []struct { + name string + bytes []byte + state State + expectedErr error + }{ + { + name: "insufficient_length", + bytes: make([]byte, StateSize-1), + expectedErr: ErrStateInsufficientLength, + }, + { + name: "zero_state", + bytes: make([]byte, StateSize), + state: State{}, + }, + { + name: "truncate_bytes", + bytes: []byte{ + StateSize: 1, + }, + state: State{}, + }, + { + name: "endianess", + bytes: []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + }, + state: State{ + Gas: gas.State{ + Capacity: 0x0102030405060708, + Excess: 0x1112131415161718, + }, + TargetExcess: 0x2122232425262728, + }, + }, + } ) func TestTarget(t *testing.T) { @@ -738,3 +777,51 @@ func BenchmarkDesiredTargetExcess(b *testing.B) { }) } } + +func TestParseState(t *testing.T) { + for _, test := range parseTests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + state, err := ParseState(test.bytes) + require.ErrorIs(err, test.expectedErr) + require.Equal(test.state, state) + }) + } +} + +func BenchmarkParseState(b *testing.B) { + for _, test := range parseTests { + b.Run(test.name, func(b *testing.B) { + for range b.N { + _, _ = ParseState(test.bytes) + } + }) + } +} + +func TestBytes(t *testing.T) { + for _, test := range parseTests { + if test.expectedErr != nil { + continue + } + t.Run(test.name, func(t *testing.T) { + expectedBytes := test.bytes[:StateSize] + bytes := test.state.Bytes() + require.Equal(t, expectedBytes, bytes) + }) + } +} + +func BenchmarkBytes(b *testing.B) { + for _, test := range parseTests { + if test.expectedErr != nil { + continue + } + b.Run(test.name, func(b *testing.B) { + for range b.N { + _ = test.state.Bytes() + } + }) + } +} diff --git a/plugin/evm/upgrade/ap3/window.go b/plugin/evm/upgrade/ap3/window.go index e2bc1a95e7..ce1f0a4d4d 100644 --- a/plugin/evm/upgrade/ap3/window.go +++ b/plugin/evm/upgrade/ap3/window.go @@ -5,8 +5,12 @@ package ap3 import ( + "encoding/binary" + "errors" + "fmt" "math" + "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/coreth/utils" safemath "github.com/ethereum/go-ethereum/common/math" ) @@ -48,13 +52,35 @@ const ( // // This value was modified in Apricot Phase 5. BaseFeeChangeDenominator = 12 + + // WindowSize is the number of bytes that are used to encode the window. + WindowSize = wrappers.LongLen * WindowLen ) +var ErrWindowInsufficientLength = errors.New("insufficient length for window") + // Window is a window of the last [WindowLen] seconds of gas usage. // // Index 0 is the oldest entry, and [WindowLen]-1 is the current entry. type Window [WindowLen]uint64 +func ParseWindow(bytes []byte) (Window, error) { + if len(bytes) < WindowSize { + return Window{}, fmt.Errorf("%w: expected at least %d bytes but got %d bytes", + ErrWindowInsufficientLength, + WindowSize, + len(bytes), + ) + } + + var window Window + for i := range window { + offset := i * wrappers.LongLen + window[i] = binary.BigEndian.Uint64(bytes[offset:]) + } + return window, nil +} + // Add adds the amounts to the most recent entry in the window. // // If the most recent entry overflows, it is set to [math.MaxUint64]. @@ -83,6 +109,15 @@ func (w *Window) Sum() uint64 { return add(0, w[:]...) } +func (w *Window) Bytes() []byte { + bytes := make([]byte, WindowSize) + for i, v := range w { + offset := i * wrappers.LongLen + binary.BigEndian.PutUint64(bytes[offset:], v) + } + return bytes +} + func add(sum uint64, values ...uint64) uint64 { var overflow bool for _, v := range values { diff --git a/plugin/evm/upgrade/ap3/window_test.go b/plugin/evm/upgrade/ap3/window_test.go index fd4b859006..96731c54cf 100644 --- a/plugin/evm/upgrade/ap3/window_test.go +++ b/plugin/evm/upgrade/ap3/window_test.go @@ -173,3 +173,73 @@ func TestWindow_Sum(t *testing.T) { }) } } + +func TestWindow_Bytes(t *testing.T) { + tests := []struct { + name string + bytes []byte + want Window + wantErr error + }{ + { + name: "insufficient_length", + bytes: make([]byte, WindowSize-1), + wantErr: ErrWindowInsufficientLength, + }, + { + name: "zero_window", + bytes: make([]byte, WindowSize), + want: Window{}, + }, + { + name: "truncate_bytes", + bytes: []byte{ + WindowSize: 1, + }, + want: Window{}, + }, + { + name: "endianess", + bytes: []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + }, + want: Window{ + 0x0102030405060708, + 0x1112131415161718, + 0x2122232425262728, + 0x3132333435363738, + 0x4142434445464748, + 0x5152535455565758, + 0x6162636465666768, + 0x7172737475767778, + 0x8182838485868788, + 0x9192939495969798, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + window, err := ParseWindow(test.bytes) + require.ErrorIs(err, test.wantErr) + require.Equal(test.want, window) + if test.wantErr != nil { + return + } + + expectedBytes := test.bytes[:WindowSize] + bytes := window.Bytes() + require.Equal(expectedBytes, bytes) + }) + } +}