diff --git a/action/action.go b/action/action.go index 61dba38100..e862de9d59 100644 --- a/action/action.go +++ b/action/action.go @@ -53,6 +53,8 @@ type ( amountForCost interface{ Amount() *big.Int } gasLimitForCost interface{ GasLimitForCost() } + + validateSidecar interface{ ValidateSidecar() error } ) // Sign signs the action using sender's private key diff --git a/action/blob_data.go b/action/blob_data.go index 54cec778f5..7730943eb2 100644 --- a/action/blob_data.go +++ b/action/blob_data.go @@ -152,6 +152,10 @@ func (tx *BlobTxData) SanityCheck() error { if price := tx.blobFeeCap; price != nil && price.Sign() < 0 { return errors.Wrap(ErrNegativeValue, "negative blob fee cap") } + return nil +} + +func (tx *BlobTxData) ValidateSidecar() error { var ( size = len(tx.blobHashes) sidecar = tx.sidecar diff --git a/action/blob_data_test.go b/action/blob_data_test.go index bc670078f3..f6a55ce79e 100644 --- a/action/blob_data_test.go +++ b/action/blob_data_test.go @@ -76,44 +76,48 @@ func TestBlobTxData(t *testing.T) { decodeBlob := MustNoErrorV(fromProtoBlobTxData(&recv)) r.Equal(blobData, decodeBlob) }) - t.Run("Sanity", func(t *testing.T) { + t.Run("SanityCheck/ValidateSidecar", func(t *testing.T) { r.NoError(blobData.SanityCheck()) + blobData.blobFeeCap = uint256.NewInt(1) + blobData.blobFeeCap.Lsh(blobData.blobFeeCap, 255) + r.ErrorIs(blobData.SanityCheck(), ErrNegativeValue) + r.NoError(blobData.ValidateSidecar()) // check blob hashes size h := blobData.blobHashes blobData.blobHashes = blobData.blobHashes[:0] - r.ErrorContains(blobData.SanityCheck(), "blobless blob transaction") + r.ErrorContains(blobData.ValidateSidecar(), "blobless blob transaction") blobData.blobHashes = h // check Blobs, Commitments, Proofs size sidecar := blobData.sidecar sidecar.Blobs = append(sidecar.Blobs, kzg4844.Blob{}) - r.ErrorContains(blobData.SanityCheck(), "number of blobs and hashes mismatch") + r.ErrorContains(blobData.ValidateSidecar(), "number of blobs and hashes mismatch") sidecar.Blobs = sidecar.Blobs[:1] sidecar.Commitments = append(sidecar.Commitments, kzg4844.Commitment{}) - r.ErrorContains(blobData.SanityCheck(), "number of blobs and commitments mismatch") + r.ErrorContains(blobData.ValidateSidecar(), "number of blobs and commitments mismatch") sidecar.Commitments = sidecar.Commitments[:1] sidecar.Proofs = append(sidecar.Proofs, kzg4844.Proof{}) - r.ErrorContains(blobData.SanityCheck(), "number of blobs and proofs mismatch") + r.ErrorContains(blobData.ValidateSidecar(), "number of blobs and proofs mismatch") sidecar.Proofs = sidecar.Proofs[:1] - r.NoError(blobData.SanityCheck()) + r.NoError(blobData.ValidateSidecar()) // verify commitments hash b := sidecar.Commitments[0][3] sidecar.Commitments[0][3] = b + 1 - r.ErrorContains(blobData.SanityCheck(), "blob 0: computed hash 01fca1582898b9c172b690c0ea344713bb28199208d3553b5ed56f33e0f34034 mismatches transaction one") + r.ErrorContains(blobData.ValidateSidecar(), "blob 0: computed hash 01fca1582898b9c172b690c0ea344713bb28199208d3553b5ed56f33e0f34034 mismatches transaction one") sidecar.Commitments[0][3] = b // verify blobs via KZG b = sidecar.Blobs[0][31] sidecar.Blobs[0][31] = b + 1 - r.ErrorContains(blobData.SanityCheck(), "invalid blob 0: can't verify opening proof") + r.ErrorContains(blobData.ValidateSidecar(), "invalid blob 0: can't verify opening proof") sidecar.Blobs[0][31] = b b = sidecar.Proofs[0][42] sidecar.Proofs[0][42] = b + 1 - r.ErrorContains(blobData.SanityCheck(), "invalid blob 0: invalid compressed coordinate: square root doesn't exist") + r.ErrorContains(blobData.ValidateSidecar(), "invalid blob 0: invalid compressed coordinate: square root doesn't exist") sidecar.Proofs[0][42] = b b = sidecar.Proofs[0][47] sidecar.Proofs[0][47] = b + 1 - r.ErrorContains(blobData.SanityCheck(), "invalid blob 0: invalid point: subgroup check failed") + r.ErrorContains(blobData.ValidateSidecar(), "invalid blob 0: invalid point: subgroup check failed") sidecar.Proofs[0][47] = b - r.NoError(blobData.SanityCheck()) + r.NoError(blobData.ValidateSidecar()) }) } diff --git a/action/envelope.go b/action/envelope.go index 9aa03acb94..15ff031ba4 100644 --- a/action/envelope.go +++ b/action/envelope.go @@ -33,6 +33,7 @@ type ( SetGas(uint64) SetChainID(uint32) SanityCheck() error + ValidateSidecar() error } // TxData is the interface required to execute a transaction by EVM @@ -442,3 +443,10 @@ func (elp *envelope) SanityCheck() error { } return elp.common.SanityCheck() } + +func (elp *envelope) ValidateSidecar() error { + if vsc, ok := elp.common.(validateSidecar); ok { + return vsc.ValidateSidecar() + } + return nil +} diff --git a/action/protocol/context.go b/action/protocol/context.go index 8c6c693f7f..0d14b3f119 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -70,6 +70,8 @@ type ( BaseFee *big.Int // ExcessBlobGas is the excess blob gas of the block ExcessBlobGas uint64 + // SkipSidecarValidation dictates to validate sidecar (for blob tx) or not + SkipSidecarValidation bool } // ActionCtx provides action auxiliary information. diff --git a/action/protocol/execution/protocol.go b/action/protocol/execution/protocol.go index 4e3b580bfd..b17ee28013 100644 --- a/action/protocol/execution/protocol.go +++ b/action/protocol/execution/protocol.go @@ -99,6 +99,9 @@ func (p *Protocol) Validate(ctx context.Context, elp action.Envelope, _ protocol if dataSize > sizeLimit { return action.ErrOversizedData } + if len(elp.BlobHashes()) > 0 && elp.To() == nil { + return errors.New("cannot create contract in blob tx") + } return nil } diff --git a/action/protocol/generic_validator.go b/action/protocol/generic_validator.go index 36668f1314..57b749a8f1 100644 --- a/action/protocol/generic_validator.go +++ b/action/protocol/generic_validator.go @@ -123,6 +123,12 @@ func (v *GenericValidator) Validate(ctx context.Context, selp *action.SealedEnve if selp.BlobGasFeeCap().Cmp(basefee) < 0 { return errors.Wrapf(action.ErrUnderpriced, "blob fee cap is too low: %s, base fee: %s", selp.BlobGasFeeCap().String(), basefee.String()) } + // validate sidecar + if blkCtx, ok := GetBlockCtx(ctx); (ok && !blkCtx.SkipSidecarValidation) || selp.BlobTxSidecar() != nil { + if err := selp.ValidateSidecar(); err != nil { + return errors.Wrap(err, "failed to validate blob sidecar") + } + } } } return nil diff --git a/action/tx_blob.go b/action/tx_blob.go index 1a55ef1d55..8a8b021f5a 100644 --- a/action/tx_blob.go +++ b/action/tx_blob.go @@ -15,6 +15,8 @@ import ( "github.com/pkg/errors" ) +var _ validateSidecar = (*BlobTx)(nil) + // BlobTx represents EIP-4844 blob transaction type BlobTx struct { chainID uint32 @@ -131,6 +133,8 @@ func (tx *BlobTx) SanityCheck() error { return tx.blob.SanityCheck() } +func (tx *BlobTx) ValidateSidecar() error { return tx.blob.ValidateSidecar() } + func (tx *BlobTx) toProto() *iotextypes.ActionCore { actCore := iotextypes.ActionCore{ Version: BlobTxType, diff --git a/api/web3server.go b/api/web3server.go index 113ab3c125..f6131e2fd1 100644 --- a/api/web3server.go +++ b/api/web3server.go @@ -154,7 +154,7 @@ func (svr *web3Handler) handleWeb3Req(ctx context.Context, web3Req *gjson.Result res, err = svr.ethAccounts() case "eth_gasPrice": res, err = svr.gasPrice() - case "eth_maxPriorityFee": + case "eth_maxPriorityFeePerGas": res, err = svr.maxPriorityFee() case "eth_getBlockByHash": res, err = svr.getBlockByHash(web3Req) diff --git a/api/web3server_integrity_test.go b/api/web3server_integrity_test.go index 2b0d51a023..9726a8e2ed 100644 --- a/api/web3server_integrity_test.go +++ b/api/web3server_integrity_test.go @@ -197,7 +197,7 @@ func gasPrice(t *testing.T, handler *hTTPHandler) { func maxPriorityFee(t *testing.T, handler *hTTPHandler) { require := require.New(t) - result := serveTestHTTP(require, handler, "eth_maxPriorityFee", "[]") + result := serveTestHTTP(require, handler, "eth_maxPriorityFeePerGas", "[]") actual, ok := result.(string) require.True(ok) require.Equal(uint64ToHex(1000000000000), actual) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index bedf31c81a..fcb1c8c322 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -94,7 +94,7 @@ type ( // CommitBlock validates and appends a block to the chain CommitBlock(blk *block.Block) error // ValidateBlock validates a new block before adding it to the blockchain - ValidateBlock(blk *block.Block) error + ValidateBlock(*block.Block, ...BlockValidationOption) error // AddSubscriber make you listen to every single produced block AddSubscriber(BlockCreationSubscriber) error @@ -160,6 +160,20 @@ func ClockOption(clk clock.Clock) Option { } } +type ( + BlockValidationCfg struct { + skipSidecarValidation bool + } + + BlockValidationOption func(*BlockValidationCfg) +) + +func SkipSidecarValidationOption() BlockValidationOption { + return func(opts *BlockValidationCfg) { + opts.skipSidecarValidation = true + } +} + // NewBlockchain creates a new blockchain and DB instance func NewBlockchain(cfg Config, g genesis.Genesis, dao blockdao.BlockDAO, bbf BlockBuilderFactory, opts ...Option) Blockchain { // create the Blockchain @@ -258,7 +272,7 @@ func (bc *blockchain) TipHeight() uint64 { } // ValidateBlock validates a new block before adding it to the blockchain -func (bc *blockchain) ValidateBlock(blk *block.Block) error { +func (bc *blockchain) ValidateBlock(blk *block.Block, opts ...BlockValidationOption) error { bc.mu.RLock() defer bc.mu.RUnlock() timer := bc.timerFactory.NewTimer("ValidateBlock") @@ -311,21 +325,25 @@ func (bc *blockchain) ValidateBlock(blk *block.Block) error { if err != nil { return err } + cfg := BlockValidationCfg{} + for _, opt := range opts { + opt(&cfg) + } ctx = protocol.WithBlockCtx(ctx, protocol.BlockCtx{ - BlockHeight: blk.Height(), - BlockTimeStamp: blk.Timestamp(), - GasLimit: bc.genesis.BlockGasLimitByHeight(blk.Height()), - Producer: producerAddr, - BaseFee: blk.BaseFee(), - ExcessBlobGas: blk.ExcessBlobGas(), + BlockHeight: blk.Height(), + BlockTimeStamp: blk.Timestamp(), + GasLimit: bc.genesis.BlockGasLimitByHeight(blk.Height()), + Producer: producerAddr, + BaseFee: blk.BaseFee(), + ExcessBlobGas: blk.ExcessBlobGas(), + SkipSidecarValidation: cfg.skipSidecarValidation, }, ) ctx = protocol.WithFeatureCtx(ctx) if bc.blockValidator == nil { return nil } - return bc.blockValidator.Validate(ctx, blk) } diff --git a/blockchain/genesis/genesis.go b/blockchain/genesis/genesis.go index 27b04cf287..778a958a8b 100644 --- a/blockchain/genesis/genesis.go +++ b/blockchain/genesis/genesis.go @@ -43,43 +43,44 @@ func init() { func defaultConfig() Genesis { return Genesis{ Blockchain: Blockchain{ - Timestamp: 1546329600, - BlockGasLimit: 20000000, - TsunamiBlockGasLimit: 50000000, - ActionGasLimit: 5000000, - BlockInterval: 10 * time.Second, - NumSubEpochs: 2, - DardanellesNumSubEpochs: 30, - NumDelegates: 24, - NumCandidateDelegates: 36, - TimeBasedRotation: false, - MaxBlobsPerBlock: 6, - PacificBlockHeight: 432001, - AleutianBlockHeight: 864001, - BeringBlockHeight: 1512001, - CookBlockHeight: 1641601, - DardanellesBlockHeight: 1816201, - DaytonaBlockHeight: 3238921, - EasterBlockHeight: 4478761, - FbkMigrationBlockHeight: 5157001, - FairbankBlockHeight: 5165641, - GreenlandBlockHeight: 6544441, - HawaiiBlockHeight: 11267641, - IcelandBlockHeight: 12289321, - JutlandBlockHeight: 13685401, - KamchatkaBlockHeight: 13816441, - LordHoweBlockHeight: 13979161, - MidwayBlockHeight: 16509241, - NewfoundlandBlockHeight: 17662681, - OkhotskBlockHeight: 21542761, - PalauBlockHeight: 22991401, - QuebecBlockHeight: 24838201, - RedseaBlockHeight: 26704441, - SumatraBlockHeight: 28516681, - TsunamiBlockHeight: 29275561, - UpernavikBlockHeight: 31174201, - VanuatuBlockHeight: 41174201, - ToBeEnabledBlockHeight: math.MaxUint64, + Timestamp: 1546329600, + BlockGasLimit: 20000000, + TsunamiBlockGasLimit: 50000000, + ActionGasLimit: 5000000, + BlockInterval: 10 * time.Second, + NumSubEpochs: 2, + DardanellesNumSubEpochs: 30, + NumDelegates: 24, + NumCandidateDelegates: 36, + TimeBasedRotation: false, + MaxBlobsPerBlock: 6, + MinBlocksForBlobRetention: 345600, + PacificBlockHeight: 432001, + AleutianBlockHeight: 864001, + BeringBlockHeight: 1512001, + CookBlockHeight: 1641601, + DardanellesBlockHeight: 1816201, + DaytonaBlockHeight: 3238921, + EasterBlockHeight: 4478761, + FbkMigrationBlockHeight: 5157001, + FairbankBlockHeight: 5165641, + GreenlandBlockHeight: 6544441, + HawaiiBlockHeight: 11267641, + IcelandBlockHeight: 12289321, + JutlandBlockHeight: 13685401, + KamchatkaBlockHeight: 13816441, + LordHoweBlockHeight: 13979161, + MidwayBlockHeight: 16509241, + NewfoundlandBlockHeight: 17662681, + OkhotskBlockHeight: 21542761, + PalauBlockHeight: 22991401, + QuebecBlockHeight: 24838201, + RedseaBlockHeight: 26704441, + SumatraBlockHeight: 28516681, + TsunamiBlockHeight: 29275561, + UpernavikBlockHeight: 31174201, + VanuatuBlockHeight: 41174201, + ToBeEnabledBlockHeight: math.MaxUint64, }, Account: Account{ InitBalanceMap: make(map[string]string), @@ -186,6 +187,8 @@ type ( TimeBasedRotation bool `yaml:"timeBasedRotation"` // MaxBlobsPerBlock is the maximum number of blobs per block MaxBlobsPerBlock uint64 `yaml:"maxBlobsPerBlock"` + // MinBlocksForBlobRetention is the minimum number of blocks for blob retention + MinBlocksForBlobRetention uint64 `yaml:"minBlocksForBlobRetention"` // PacificBlockHeight is the start height of using the logic of Pacific version // TODO: PacificBlockHeight is not added into protobuf definition for backward compatibility PacificBlockHeight uint64 `yaml:"pacificHeight"` diff --git a/chainservice/builder.go b/chainservice/builder.go index 4ea6ac8aff..03d4a1a20f 100644 --- a/chainservice/builder.go +++ b/chainservice/builder.go @@ -536,6 +536,20 @@ func (builder *Builder) buildBlockSyncer() error { chain := builder.cs.chain consens := builder.cs.consensus blockdao := builder.cs.blockdao + cfg := builder.cfg + // estimateTipHeight estimates the height of the block at the given time + // it ignores the influence of the block missing in the blockchain + // it must >= the real head height of the block + estimateTipHeight := func(blk *block.Block, duration time.Duration) uint64 { + if blk.Height() >= cfg.Genesis.DardanellesBlockHeight { + return blk.Height() + uint64(duration.Seconds()/float64(cfg.DardanellesUpgrade.BlockInterval)) + } + durationToDardanelles := time.Duration(cfg.Genesis.DardanellesBlockHeight-blk.Height()) * time.Duration(cfg.Genesis.BlockInterval) + if duration < durationToDardanelles { + return blk.Height() + uint64(duration.Seconds()/float64(cfg.Genesis.BlockInterval)) + } + return cfg.Genesis.DardanellesBlockHeight + uint64((duration-durationToDardanelles).Seconds()/float64(cfg.DardanellesUpgrade.BlockInterval)) + } blocksync, err := blocksync.NewBlockSyncer( builder.cfg.BlockSync, @@ -570,8 +584,13 @@ func (builder *Builder) buildBlockSyncer() error { retries = 4 } var err error + opts := []blockchain.BlockValidationOption{} + if now := time.Now(); now.After(blk.Timestamp()) && + blk.Height()+cfg.Genesis.MinBlocksForBlobRetention <= estimateTipHeight(blk, now.Sub(blk.Timestamp())) { + opts = append(opts, blockchain.SkipSidecarValidationOption()) + } for i := 0; i < retries; i++ { - if err = chain.ValidateBlock(blk); err == nil { + if err = chain.ValidateBlock(blk, opts...); err == nil { if err = chain.CommitBlock(blk); err == nil { break } @@ -831,13 +850,13 @@ func (builder *Builder) build(forSubChain, forTest bool) (*ChainService, error) if err := builder.buildConsensusComponent(); err != nil { return nil, err } - if err := builder.buildBlockSyncer(); err != nil { + if err := builder.buildNodeInfoManager(); err != nil { return nil, err } - if err := builder.buildActionSyncer(); err != nil { + if err := builder.buildBlockSyncer(); err != nil { return nil, err } - if err := builder.buildNodeInfoManager(); err != nil { + if err := builder.buildActionSyncer(); err != nil { return nil, err } cs := builder.cs diff --git a/test/mock/mock_blockchain/mock_blockchain.go b/test/mock/mock_blockchain/mock_blockchain.go index f8fe7ca3f7..fbe0387278 100644 --- a/test/mock/mock_blockchain/mock_blockchain.go +++ b/test/mock/mock_blockchain/mock_blockchain.go @@ -255,17 +255,22 @@ func (mr *MockBlockchainMockRecorder) TipHeight() *gomock.Call { } // ValidateBlock mocks base method. -func (m *MockBlockchain) ValidateBlock(blk *block.Block) error { +func (m *MockBlockchain) ValidateBlock(arg0 *block.Block, arg1 ...blockchain.BlockValidationOption) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateBlock", blk) + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ValidateBlock", varargs...) ret0, _ := ret[0].(error) return ret0 } // ValidateBlock indicates an expected call of ValidateBlock. -func (mr *MockBlockchainMockRecorder) ValidateBlock(blk interface{}) *gomock.Call { +func (mr *MockBlockchainMockRecorder) ValidateBlock(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateBlock", reflect.TypeOf((*MockBlockchain)(nil).ValidateBlock), blk) + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateBlock", reflect.TypeOf((*MockBlockchain)(nil).ValidateBlock), varargs...) } // MockBlockBuilderFactory is a mock of BlockBuilderFactory interface. diff --git a/test/mock/mock_envelope/mock_envelope.go b/test/mock/mock_envelope/mock_envelope.go index b37b735337..6184c9f274 100644 --- a/test/mock/mock_envelope/mock_envelope.go +++ b/test/mock/mock_envelope/mock_envelope.go @@ -400,6 +400,20 @@ func (mr *MockEnvelopeMockRecorder) ToEthTx(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToEthTx", reflect.TypeOf((*MockEnvelope)(nil).ToEthTx), arg0, arg1) } +// ValidateSidecar mocks base method. +func (m *MockEnvelope) ValidateSidecar() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateSidecar") + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidateSidecar indicates an expected call of ValidateSidecar. +func (mr *MockEnvelopeMockRecorder) ValidateSidecar() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateSidecar", reflect.TypeOf((*MockEnvelope)(nil).ValidateSidecar)) +} + // Value mocks base method. func (m *MockEnvelope) Value() *big.Int { m.ctrl.T.Helper() @@ -1029,6 +1043,20 @@ func (mr *MockTxCommonInternalMockRecorder) SanityCheck() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SanityCheck", reflect.TypeOf((*MockTxCommonInternal)(nil).SanityCheck)) } +// ValidateSidecar mocks base method. +func (m *MockTxCommonInternal) ValidateSidecar() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateSidecar") + ret0, _ := ret[0].(error) + return ret0 +} + +// ValidateSidecar indicates an expected call of ValidateSidecar. +func (mr *MockTxCommonInternalMockRecorder) ValidateSidecar() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateSidecar", reflect.TypeOf((*MockTxCommonInternal)(nil).ValidateSidecar)) +} + // Version mocks base method. func (m *MockTxCommonInternal) Version() uint32 { m.ctrl.T.Helper()