Skip to content

Commit

Permalink
[blockchain] verify EIP1559 header (#4353)
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinxie committed Aug 13, 2024
1 parent edf397e commit 8dcbccf
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 21 deletions.
7 changes: 7 additions & 0 deletions action/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ package action

import "github.com/pkg/errors"

// constants for EIP-1559 dynamic fee
const (
DefaultBaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks.
DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
InitialBaseFee = 1000000000000 // Initial base fee for EIP-1559 blocks.
)

// vars
var (
ErrAddress = errors.New("invalid address")
Expand Down
2 changes: 2 additions & 0 deletions action/protocol/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ type (
MigrateNativeStake bool
AddClaimRewardAddress bool
EnforceLegacyEndorsement bool
EnableDynamicFeeTx bool
}

// FeatureWithHeightCtx provides feature check functions.
Expand Down Expand Up @@ -279,6 +280,7 @@ func WithFeatureCtx(ctx context.Context) context.Context {
MigrateNativeStake: g.IsUpernavik(height),
AddClaimRewardAddress: g.IsUpernavik(height),
EnforceLegacyEndorsement: !g.IsUpernavik(height),
EnableDynamicFeeTx: g.IsVanuatu(height),
},
)
}
Expand Down
8 changes: 8 additions & 0 deletions action/protocol/generic_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package protocol

import (
"context"
"math/big"

"github.com/pkg/errors"

Expand Down Expand Up @@ -76,6 +77,13 @@ func (v *GenericValidator) Validate(ctx context.Context, selp *action.SealedEnve
return action.ErrNonceTooLow
}
}
if ok && featureCtx.EnableDynamicFeeTx {
// check transaction's max fee can cover base fee
if selp.Envelope.GasFeeCap().Cmp(new(big.Int).SetUint64(action.InitialBaseFee)) < 0 {
return errors.Errorf("transaction cannot cover base fee, max fee = %s, base fee = %d",
selp.Envelope.GasFeeCap().String(), action.InitialBaseFee)
}
}
}

return selp.Action().SanityCheck()
Expand Down
4 changes: 2 additions & 2 deletions actpool/actpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,12 +283,12 @@ func (ap *actPool) checkSelpWithoutState(ctx context.Context, selp *action.Seale
}

// Reject action if the gas price is lower than the threshold
if selp.Encoding() != uint32(iotextypes.Encoding_ETHEREUM_UNPROTECTED) && selp.GasPrice().Cmp(ap.cfg.MinGasPrice()) < 0 {
if selp.Encoding() != uint32(iotextypes.Encoding_ETHEREUM_UNPROTECTED) && selp.GasFeeCap().Cmp(ap.cfg.MinGasPrice()) < 0 {
_actpoolMtc.WithLabelValues("gasPriceLower").Inc()
actHash, _ := selp.Hash()
log.L().Debug("action rejected due to low gas price",
zap.String("actionHash", hex.EncodeToString(actHash[:])),
zap.String("gasPrice", selp.GasPrice().String()))
zap.String("GasFeeCap", selp.GasFeeCap().String()))
return action.ErrUnderpriced
}

Expand Down
4 changes: 2 additions & 2 deletions actpool/actqueue.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (q *actQueue) NextAction() (bool, *big.Int) {
if len(q.ascQueue) == 0 {
return false, nil
}
return q.pendingNonce > q.accountNonce, q.items[q.ascQueue[0].nonce].GasPrice()
return q.pendingNonce > q.accountNonce, q.items[q.ascQueue[0].nonce].GasFeeCap()
}

// Put inserts a new action into the map, also updating the queue's nonce index
Expand All @@ -103,7 +103,7 @@ func (q *actQueue) Put(act *action.SealedEnvelope) error {

if actInPool, exist := q.items[nonce]; exist {
// act of higher gas price can cut in line
if nonce < q.pendingNonce && act.GasPrice().Cmp(actInPool.GasPrice()) != 1 {
if nonce < q.pendingNonce && act.GasFeeCap().Cmp(actInPool.GasFeeCap()) != 1 {
return action.ErrReplaceUnderpriced
}
// update action in q.items and q.index
Expand Down
15 changes: 14 additions & 1 deletion blockchain/block/builder.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Copyright (c) 2019 IoTeX Foundation
// Copyright (c) 2024 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.

package block

import (
"math/big"
"time"

"github.com/iotexproject/go-pkgs/bloom"
Expand Down Expand Up @@ -83,6 +84,18 @@ func (b *Builder) SetLogsBloom(f bloom.BloomFilter) *Builder {
return b
}

// SetGasUsed sets the used gas
func (b *Builder) SetGasUsed(g uint64) *Builder {
b.blk.Header.gasUsed = g
return b
}

// SetBaseFee sets the base fee
func (b *Builder) SetBaseFee(fee *big.Int) *Builder {
b.blk.Header.baseFee = new(big.Int).Set(fee)
return b
}

// SignAndBuild signs and then builds a block.
func (b *Builder) SignAndBuild(signerPrvKey crypto.PrivateKey) (Block, error) {
b.blk.Header.pubkey = signerPrvKey.PublicKey()
Expand Down
69 changes: 69 additions & 0 deletions blockchain/block/eip1559.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) 2024 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.

package block

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/pkg/errors"

"github.com/iotexproject/iotex-core/action"
"github.com/iotexproject/iotex-core/action/protocol"
"github.com/iotexproject/iotex-core/blockchain/genesis"
)

func VerifyEIP1559Header(g genesis.Blockchain, parent *protocol.TipInfo, header *Header) error {
if header.BaseFee() == nil {
return errors.New("header is missing baseFee")
}
// Verify the baseFee is correct based on the parent header.
expectedBaseFee := CalcBaseFee(g, parent)
if header.BaseFee().Cmp(expectedBaseFee) != 0 {
return errors.Errorf("invalid baseFee: have %s, want %s, parentBaseFee %s, parentGasUsed %d",
header.BaseFee(), expectedBaseFee, parent.BaseFee, parent.GasUsed)
}
return nil
}

// CalcBaseFee calculates the basefee of the header.
func CalcBaseFee(g genesis.Blockchain, parent *protocol.TipInfo) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
if parent.Height < g.VanuatuBlockHeight {
return new(big.Int).SetUint64(action.InitialBaseFee)
}

parentGasTarget := g.BlockGasLimitByHeight(parent.Height) / action.DefaultElasticityMultiplier
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed == parentGasTarget {
return new(big.Int).Set(parent.BaseFee)
}

var (
num = new(big.Int)
denom = new(big.Int)
)
if parent.GasUsed > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
// max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parent.GasUsed - parentGasTarget)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(action.DefaultBaseFeeChangeDenominator))
baseFeeDelta := math.BigMax(num, common.Big1)
return num.Add(parent.BaseFee, baseFeeDelta)
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
// max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator)
num.SetUint64(parentGasTarget - parent.GasUsed)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(action.DefaultBaseFeeChangeDenominator))
baseFee := num.Sub(parent.BaseFee, num)
return math.BigMax(baseFee, new(big.Int).SetUint64(action.InitialBaseFee))
}
}
40 changes: 40 additions & 0 deletions blockchain/block/eip1559_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2024 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.

package block

import (
"math/big"
"testing"

"github.com/iotexproject/iotex-core/action"
"github.com/iotexproject/iotex-core/action/protocol"
"github.com/iotexproject/iotex-core/blockchain/genesis"
"github.com/stretchr/testify/require"
)

func TestCalcBaseFee(t *testing.T) {
r := require.New(t)
tests := []struct {
parentBaseFee int64
parentGasUsed uint64
expectedBaseFee int64
}{
{action.InitialBaseFee, 25000000, action.InitialBaseFee}, // usage == target
{2 * action.InitialBaseFee, 24000000, 1990000000000}, // usage below target
{action.InitialBaseFee, 24000000, 1000000000000}, // usage below target but capped to default
{action.InitialBaseFee, 26000000, 1005000000000}, // usage above target
}
g := genesis.Default.Blockchain
for _, test := range tests {
parent := &protocol.TipInfo{
Height: g.VanuatuBlockHeight,
GasUsed: test.parentGasUsed,
BaseFee: big.NewInt(test.parentBaseFee),
}
expect := big.NewInt(test.expectedBaseFee)
r.Equal(expect, CalcBaseFee(g, parent))
}
}
9 changes: 7 additions & 2 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX Foundation
// Copyright (c) 2024 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
Expand Down Expand Up @@ -289,7 +289,12 @@ func (bc *blockchain) ValidateBlock(blk *block.Block) error {
tip.Hash,
)
}

// verify EIP1559 header (baseFee adjustment)
if blk.Header.BaseFee() != nil {
if err = block.VerifyEIP1559Header(bc.genesis.Blockchain, tip, &blk.Header); err != nil {
return errors.Wrap(err, "failed to verify EIP1559 header (baseFee adjustment)")
}
}
if !blk.Header.VerifySignature() {
return errors.Errorf("failed to verify block's signature with public key: %x", blk.PublicKey())
}
Expand Down
16 changes: 12 additions & 4 deletions blockchain/integrity/integrity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ func TestBlockchainHardForkFeatures(t *testing.T) {
cfg.Genesis.SumatraBlockHeight = 2
cfg.Genesis.TsunamiBlockHeight = 3
cfg.Genesis.UpernavikBlockHeight = 4
cfg.Genesis.VanuatuBlockHeight = 4
cfg.Genesis.InitBalanceMap[identityset.Address(27).String()] = unit.ConvertIotxToRau(10000000000).String()

ctx := context.Background()
Expand All @@ -1007,7 +1008,7 @@ func TestBlockchainHardForkFeatures(t *testing.T) {
pp := mock_poll.NewMockProtocol(ctrl)
pp.EXPECT().CreateGenesisStates(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
pp.EXPECT().Candidates(gomock.Any(), gomock.Any()).Return([]*state.Candidate{
&state.Candidate{
{
Address: producer.String(),
RewardAddress: producer.String(),
},
Expand Down Expand Up @@ -1213,6 +1214,8 @@ func TestBlockchainHardForkFeatures(t *testing.T) {
require.NoError(err)
require.EqualValues(3, blk2.Height())
require.Equal(3, len(blk2.Body.Actions))
require.Zero(blk2.Header.GasUsed())
require.Nil(blk2.Header.BaseFee())
require.NoError(bc.CommitBlock(blk2))

// 4 legacy fresh accounts are converted to zero-nonce account
Expand Down Expand Up @@ -1255,6 +1258,7 @@ func TestBlockchainHardForkFeatures(t *testing.T) {
require.True(tx.Protected())
require.EqualValues(cfg.Chain.EVMNetworkID, tx.ChainId().Uint64())
encoding, sig, pubkey, err := action.ExtractTypeSigPubkey(tx)
require.NoError(err)
require.Equal(iotextypes.Encoding_ETHEREUM_EIP155, encoding)
// use container to send tx
req := iotextypes.Action{
Expand Down Expand Up @@ -1300,6 +1304,8 @@ func TestBlockchainHardForkFeatures(t *testing.T) {
blk3, err := bc.MintNewBlock(blockTime)
require.NoError(err)
require.EqualValues(4, blk3.Height())
require.EqualValues(40020, blk3.Header.GasUsed())
require.EqualValues(action.InitialBaseFee, blk3.Header.BaseFee().Uint64())
require.Equal(4, len(blk3.Body.Actions))
require.NoError(bc.CommitBlock(blk3))

Expand Down Expand Up @@ -1396,8 +1402,8 @@ func TestBlockchainHardForkFeatures(t *testing.T) {
}

func TestConstantinople(t *testing.T) {
require := require.New(t)
testValidateBlockchain := func(cfg config.Config, t *testing.T) {
require := require.New(t)
ctx := context.Background()

registry := protocol.NewRegistry()
Expand Down Expand Up @@ -1608,6 +1614,7 @@ func TestConstantinople(t *testing.T) {
}
}

require := require.New(t)
cfg := config.Default
testTriePath, err := testutil.PathOfTempFile("trie")
require.NoError(err)
Expand Down Expand Up @@ -1643,8 +1650,8 @@ func TestConstantinople(t *testing.T) {
}

func TestLoadBlockchainfromDB(t *testing.T) {
require := require.New(t)
testValidateBlockchain := func(cfg config.Config, t *testing.T) {
require := require.New(t)
ctx := genesis.WithGenesisContext(context.Background(), cfg.Genesis)

registry := protocol.NewRegistry()
Expand Down Expand Up @@ -1699,7 +1706,7 @@ func TestLoadBlockchainfromDB(t *testing.T) {
require.NoError(addTestingTsfBlocks(cfg, bc, dao, ap))
//make sure pubsub is completed
err = testutil.WaitUntil(200*time.Millisecond, 3*time.Second, func() (bool, error) {
return 24 == ms.Counter(), nil
return ms.Counter() == 24, nil
})
require.NoError(err)
require.NoError(bc.Stop(ctx))
Expand Down Expand Up @@ -1869,6 +1876,7 @@ func TestLoadBlockchainfromDB(t *testing.T) {
}
}

require := require.New(t)
testTriePath, err := testutil.PathOfTempFile("trie")
require.NoError(err)
testDBPath, err := testutil.PathOfTempFile("db")
Expand Down
10 changes: 9 additions & 1 deletion state/factory/util.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX Foundation
// Copyright (c) 2024 IoTeX Foundation
// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
// This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
Expand Down Expand Up @@ -74,6 +74,14 @@ func calculateLogsBloom(ctx context.Context, receipts []*action.Receipt) bloom.B
return bloom
}

func calculateGasUsed(receipts []*action.Receipt) uint64 {
var gas uint64
for _, receipt := range receipts {
gas += receipt.GasConsumed
}
return gas
}

// generateWorkingSetCacheKey generates hash key for workingset cache by hashing blockheader core and producer pubkey
func generateWorkingSetCacheKey(blkHeader block.Header, producerAddr string) hash.Hash256 {
sum := append(blkHeader.SerializeCore(), []byte(producerAddr)...)
Expand Down
Loading

0 comments on commit 8dcbccf

Please sign in to comment.