Skip to content

Commit

Permalink
feat: add block transaction count limit (ethereum#216)
Browse files Browse the repository at this point in the history
* feat: add block transaction count limit

* add tests

* nit
  • Loading branch information
Thegaram authored Feb 15, 2023
1 parent e3e0078 commit 30f4f57
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 3 deletions.
3 changes: 3 additions & 0 deletions consensus/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ var (
// ErrInvalidNumber is returned if a block's number doesn't equal its parent's
// plus one.
ErrInvalidNumber = errors.New("invalid block number")

// ErrInvalidTxCount is returned if a block contains too many transactions.
ErrInvalidTxCount = errors.New("invalid transaction count")
)
3 changes: 3 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
return ErrKnownBlock
}
if !v.config.IsValidTxCount(len(block.Transactions())) {
return consensus.ErrInvalidTxCount
}
// Header validity is known at this point, check the uncles and transactions
header := block.Header()
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
Expand Down
61 changes: 61 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3177,3 +3177,64 @@ func TestFeeVault(t *testing.T) {
t.Fatalf("fee vault balance incorrect: expected %d, got %d", expected, actual)
}
}

// TestTransactionCountLimit tests that the chain reject blocks with too many transactions.
func TestTransactionCountLimit(t *testing.T) {
// Create config that allows at most 1 transaction per block
config := params.TestChainConfig
config.MaxTxPerBlock = new(int)
*config.MaxTxPerBlock = 1

var (
engine = ethash.NewFaker()
db = rawdb.NewMemoryDatabase()
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000000000)
gspec = &Genesis{Config: config, Alloc: GenesisAlloc{address: {Balance: funds}}}
genesis = gspec.MustCommit(db)
)

addTx := func(b *BlockGen) {
tx := types.NewTransaction(b.TxNonce(address), address, big.NewInt(0), 50000, b.header.BaseFee, nil)
signed, _ := types.SignTx(tx, types.HomesteadSigner{}, key)
b.AddTx(signed)
}

// Initialize blockchain
blockchain, err := NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("failed to create new chain manager: %v", err)
}
defer blockchain.Stop()

// Insert empty block
block1, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) {
// empty
})

if _, err := blockchain.InsertChain(block1); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}

// Insert block with 1 transaction
block2, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) {
addTx(b)
})

if _, err := blockchain.InsertChain(block2); err != nil {
t.Fatalf("failed to insert chain: %v", err)
}

// Insert block with 2 transactions
block3, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) {
addTx(b)
addTx(b)
})

_, err = blockchain.InsertChain(block3)

if !errors.Is(err, consensus.ErrInvalidTxCount) {
t.Fatalf("error mismatch: have: %v, want: %v", err, consensus.ErrInvalidTxCount)
}
}
5 changes: 5 additions & 0 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,11 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
}
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
}
// If we have collected enough transactions then we're done
if !w.chainConfig.IsValidTxCount(w.current.tcount + 1) {
log.Trace("Transaction count limit reached", "have", w.current.tcount, "want", w.chainConfig.MaxTxPerBlock)
break
}
// If we don't have enough gas for any further transactions then we're done
if w.current.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
Expand Down
14 changes: 11 additions & 3 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,16 +258,16 @@ var (
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, false, nil, true, true}
AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, false, nil, true, true, nil}

// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, false, nil, true, true}
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, false, nil, true, true, nil}

TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, false, &common.Address{123}, true, true}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, false, &common.Address{123}, true, true, nil}
TestRules = TestChainConfig.Rules(new(big.Int))
)

Expand Down Expand Up @@ -367,6 +367,9 @@ type ChainConfig struct {

// Scroll genesis extension: enable EIP-1559 in tx pool, EnableEIP2718 should be true too.
EnableEIP1559 bool `json:"enableEIP1559,omitempty"`

// Scroll genesis extension: Maximum number of transactions per block [optional]
MaxTxPerBlock *int `json:"maxTxPerBlock,omitempty"`
}

// EthashConfig is the consensus engine configs for proof-of-work based sealing.
Expand Down Expand Up @@ -494,6 +497,11 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi
return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0
}

// IsValidTxCount returns whether the given block's transaction count is below the limit.
func (c *ChainConfig) IsValidTxCount(count int) bool {
return c.MaxTxPerBlock == nil || count <= *c.MaxTxPerBlock
}

// CheckCompatible checks whether scheduled fork transitions have been imported
// with a mismatching chain configuration.
func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError {
Expand Down

0 comments on commit 30f4f57

Please sign in to comment.