diff --git a/command/genesis/params.go b/command/genesis/params.go index 6daf64f8fb..de9c56169d 100644 --- a/command/genesis/params.go +++ b/command/genesis/params.go @@ -215,8 +215,8 @@ func (p *genesisParams) initConsensusEngineConfig() { func (p *genesisParams) initIBFTEngineMap(mechanism ibft.MechanismType) { p.consensusEngineConfig = map[string]interface{}{ string(server.IBFTConsensus): map[string]interface{}{ - "type": mechanism, - "epochSize": p.epochSize, + ibft.KeyType: mechanism, + ibft.KeyEpochSize: p.epochSize, }, } } diff --git a/consensus/ibft/hooks.go b/consensus/ibft/hooks.go index 92fec6442a..758e1c15f4 100644 --- a/consensus/ibft/hooks.go +++ b/consensus/ibft/hooks.go @@ -6,6 +6,12 @@ import ( "github.com/dogechain-lab/dogechain/helper/common" ) +const ( + KeyType = "type" + KeyEpochSize = "epochSize" + KeyBanishAbnormalContract = "banishAbnormalContract" +) + // Define the type of the IBFT consensus type MechanismType string diff --git a/consensus/ibft/ibft.go b/consensus/ibft/ibft.go index 017eed6f7a..4ae2677fcc 100644 --- a/consensus/ibft/ibft.go +++ b/consensus/ibft/ibft.go @@ -28,7 +28,8 @@ import ( ) const ( - DefaultEpochSize = 100000 + DefaultEpochSize = 100000 + DefaultBanishAbnormalContract = false // banish abnormal contract whose execution consumes too much time. ) var ( @@ -103,6 +104,10 @@ type Ibft struct { mechanisms []ConsensusMechanism // IBFT ConsensusMechanism used (PoA / PoS) blockTime time.Duration // Minimum block generation time in seconds + + // for banishing some exhausting contracts + banishAbnormalContract bool + exhaustingContracts map[types.Address]struct{} } // runHook runs a specified hook if it is present in the hook map @@ -136,14 +141,14 @@ func Factory( params *consensus.ConsensusParams, ) (consensus.Consensus, error) { var epochSize uint64 - if definedEpochSize, ok := params.Config.Config["epochSize"]; !ok { + if definedEpochSize, ok := params.Config.Config[KeyEpochSize]; !ok { // No epoch size defined, use the default one epochSize = DefaultEpochSize } else { // Epoch size is defined, use the passed in one readSize, ok := definedEpochSize.(float64) if !ok { - return nil, errors.New("invalid type assertion") + return nil, errors.New("epochSize invalid type assertion") } epochSize = uint64(readSize) @@ -154,22 +159,36 @@ func Factory( } } + var banishAbnormalContract bool + if definedBanish, ok := params.Config.Config[KeyBanishAbnormalContract]; !ok { + banishAbnormalContract = DefaultBanishAbnormalContract + } else { + banish, ok := definedBanish.(bool) + if !ok { + return nil, errors.New("banishAbnormalContract invalid type assertion") + } + + banishAbnormalContract = banish + } + p := &Ibft{ - logger: params.Logger.Named("ibft"), - config: params.Config, - Grpc: params.Grpc, - blockchain: params.Blockchain, - executor: params.Executor, - closeCh: make(chan struct{}), - isClosed: atomic.NewBool(false), - txpool: params.Txpool, - state: ¤tState{}, - network: params.Network, - epochSize: epochSize, - sealing: params.Seal, - metrics: params.Metrics, - secretsManager: params.SecretsManager, - blockTime: time.Duration(params.BlockTime) * time.Second, + logger: params.Logger.Named("ibft"), + config: params.Config, + Grpc: params.Grpc, + blockchain: params.Blockchain, + executor: params.Executor, + closeCh: make(chan struct{}), + isClosed: atomic.NewBool(false), + txpool: params.Txpool, + state: ¤tState{}, + network: params.Network, + epochSize: epochSize, + sealing: params.Seal, + metrics: params.Metrics, + secretsManager: params.SecretsManager, + blockTime: time.Duration(params.BlockTime) * time.Second, + banishAbnormalContract: banishAbnormalContract, + exhaustingContracts: make(map[types.Address]struct{}), } // Initialize the mechanism @@ -622,6 +641,7 @@ func (i *Ibft) buildBlock(snap *Snapshot, parent *types.Header) (*types.Block, e if err != nil { return nil, err } + // If the mechanism is PoS -> build a regular block if it's not an end-of-epoch block // If the mechanism is PoA -> always build a regular block, regardless of epoch var ( @@ -631,7 +651,7 @@ func (i *Ibft) buildBlock(snap *Snapshot, parent *types.Header) (*types.Block, e ) if i.shouldWriteTransactions(header.Number) { - txs, dropTxs, resetTxs = i.writeTransactions(gasLimit, transition) + txs, dropTxs, resetTxs = i.writeTransactions(gasLimit, transition, headerTime.Add(i.blockTime)) } if err := i.PreStateCommit(header, transition); err != nil { @@ -705,6 +725,7 @@ type demoteTransaction struct { func (i *Ibft) writeTransactions( gasLimit uint64, transition transitionInterface, + terminalTime time.Time, ) ( includedTransactions []*types.Transaction, shouldDropTxs []*types.Transaction, @@ -716,13 +737,31 @@ func (i *Ibft) writeTransactions( priceTxs := types.NewTransactionsByPriceAndNonce(pendingTxs) for { + // terminate transaction executing once timeout + if i.shouldTerminate(terminalTime) { + i.logger.Info("block building time exceeds") + + break + } + tx := priceTxs.Peek() if tx == nil { - i.logger.Debug("no more transactions") + i.logger.Info("no more transactions") break } + if i.shouldBanishTx(tx) { + i.logger.Info("banish some exausting contract and drop all sender transactions", + "address", tx.To, + "from", tx.From, + ) + + shouldDropTxs = append(shouldDropTxs, tx) + + continue + } + if tx.ExceedsBlockGasLimit(gasLimit) { // the account transactions should be dropped shouldDropTxs = append(shouldDropTxs, tx) @@ -736,7 +775,14 @@ func (i *Ibft) writeTransactions( continue } + begin := time.Now() // for duration calculation + if err := transition.Write(tx); err != nil { + i.banishLongTimeConsumingTx(tx, begin) + + i.logger.Debug("write transaction failed", "hash", tx.Hash, "from", tx.From, + "nonce", tx.Nonce, "err", err) + //nolint:errorlint if _, ok := err.(*state.AllGasUsedError); ok { // no more transaction could be packed @@ -775,6 +821,7 @@ func (i *Ibft) writeTransactions( // no errors, go on priceTxs.Shift() + i.banishLongTimeConsumingTx(tx, begin) includedTransactions = append(includedTransactions, tx) } @@ -788,6 +835,41 @@ func (i *Ibft) writeTransactions( return } +func (i *Ibft) shouldTerminate(terminalTime time.Time) bool { + return time.Now().After(terminalTime) +} + +func (i *Ibft) shouldBanishTx(tx *types.Transaction) bool { + if !i.banishAbnormalContract || tx.To == nil { + return false + } + + // if tx send to some banish contract, drop it + _, shouldBanish := i.exhaustingContracts[*tx.To] + + return shouldBanish +} + +func (i *Ibft) banishLongTimeConsumingTx(tx *types.Transaction, begin time.Time) { + duration := time.Since(begin).Milliseconds() + if duration < i.blockTime.Milliseconds() || + tx.To == nil { // long contract creation is tolerable + return + } + + // banish the contract + i.exhaustingContracts[*tx.To] = struct{}{} + + i.logger.Info("banish contract who consumes too many CPU time", + "duration", duration, + "from", tx.From, + "to", tx.To, + "gasPrice", tx.GasPrice, + "gas", tx.Gas, + "len", len(tx.Input), + ) +} + // runAcceptState runs the Accept state loop // // The Accept state always checks the snapshot, and the validator set. If the current node is not in the validators set, diff --git a/consensus/ibft/ibft_test.go b/consensus/ibft/ibft_test.go index a0d3bf84c3..b84aecb545 100644 --- a/consensus/ibft/ibft_test.go +++ b/consensus/ibft/ibft_test.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "errors" "fmt" + "math/big" "testing" "time" @@ -24,6 +25,9 @@ import ( var ( defaultBlockGasLimit uint64 = 8000000 + + addr1 = types.StringToAddress("1") + addr2 = types.StringToAddress("2") ) type MockBlockchain struct { @@ -833,7 +837,8 @@ func TestIBFT_WriteTransactions(t *testing.T) { m.txpool = mockTxPool mockTransition := setupMockTransition(test, mockTxPool) - included, shouldDropTxs, shouldDemoteTxs := m.writeTransactions(1000, mockTransition) + endTime := time.Now().Add(time.Second) + included, shouldDropTxs, shouldDemoteTxs := m.writeTransactions(1000, mockTransition, endTime) assert.Equal(t, test.params.expectedIncludedTxnsCount, len(included)) assert.Equal(t, test.params.expectedFailReceiptsWritten, len(mockTransition.failReceiptsWritten)) @@ -1189,18 +1194,19 @@ func newMockIbft(t *testing.T, accounts []string, validatorAccount string) *mock } ibft := &Ibft{ - logger: hclog.NewNullLogger(), - config: &consensus.Config{}, - blockchain: m, - validatorKey: addr.priv, - validatorKeyAddr: addr.Address(), - closeCh: make(chan struct{}), - isClosed: atomic.NewBool(false), - updateCh: make(chan struct{}), - operator: &operator{}, - state: newState(), - epochSize: DefaultEpochSize, - metrics: consensus.NilMetrics(), + logger: hclog.NewNullLogger(), + config: &consensus.Config{}, + blockchain: m, + validatorKey: addr.priv, + validatorKeyAddr: addr.Address(), + closeCh: make(chan struct{}), + isClosed: atomic.NewBool(false), + updateCh: make(chan struct{}), + operator: &operator{}, + state: newState(), + epochSize: DefaultEpochSize, + metrics: consensus.NilMetrics(), + exhaustingContracts: make(map[types.Address]struct{}), } initIbftMechanism(PoA, ibft) @@ -1660,3 +1666,49 @@ func TestGetIBFTForks(t *testing.T) { }) } } + +func Test_shouldBanishTx(t *testing.T) { + mockTx := &types.Transaction{ + Nonce: 0, + GasPrice: big.NewInt(1000), + Gas: defaultBlockGasLimit, + To: &addr2, + Value: big.NewInt(10), + Input: []byte{'m', 'o', 'k', 'e'}, + From: addr1, + } + + i := newMockIbft(t, []string{"A", "B", "C", "D"}, "A") + i.Ibft.banishAbnormalContract = true + i.Ibft.exhaustingContracts[addr2] = struct{}{} + + assert.True(t, i.shouldBanishTx(mockTx)) +} + +func Test_banishLongTimeConsumingTx(t *testing.T) { + mockTx := &types.Transaction{ + Nonce: 0, + GasPrice: big.NewInt(1000), + Gas: defaultBlockGasLimit, + To: &addr2, + Value: big.NewInt(10), + Input: []byte{'m', 'o', 'k', 'e'}, + From: addr1, + } + + i := newMockIbft(t, []string{"A", "B", "C", "D"}, "A") + i.Ibft.banishAbnormalContract = true + + // make sure begin time out what we set + begin := time.Now().Add(-1*i.blockTime - 1) + + i.banishLongTimeConsumingTx(mockTx, begin) + + assert.Equal( + t, + map[types.Address]struct{}{ + addr2: {}, + }, + i.exhaustingContracts, + ) +} diff --git a/state/executor.go b/state/executor.go index 7a02c2da99..d74200fad7 100644 --- a/state/executor.go +++ b/state/executor.go @@ -294,7 +294,7 @@ func (t *Transition) Write(txn *types.Transaction) error { result, e := t.Apply(msg) if e != nil { - t.logger.Error("failed to apply tx", "err", e) + t.logger.Debug("failed to apply tx", "err", e) return e }