Skip to content

Commit

Permalink
[ibft] merge back patch 1.1.3 (#251)
Browse files Browse the repository at this point in the history
# Description

The PR merges the already running banish mechanism to base branch.
Besides, build block should not keep on going, when there is timeout for
broadcasting.

# Changes include

- [x] Bugfix (non-breaking change that solves an issue)

## Testing

- [x] I have tested this code with the official test suite
  • Loading branch information
DarianShawn authored Nov 12, 2022
1 parent 7e051a9 commit a977703
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 36 deletions.
4 changes: 2 additions & 2 deletions command/genesis/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
}
Expand Down
6 changes: 6 additions & 0 deletions consensus/ibft/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
122 changes: 102 additions & 20 deletions consensus/ibft/ibft.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import (
)

const (
DefaultEpochSize = 100000
DefaultEpochSize = 100000
DefaultBanishAbnormalContract = false // banish abnormal contract whose execution consumes too much time.
)

var (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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: &currentState{},
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: &currentState{},
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
Expand Down Expand Up @@ -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 (
Expand All @@ -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 {
Expand Down Expand Up @@ -705,6 +725,7 @@ type demoteTransaction struct {
func (i *Ibft) writeTransactions(
gasLimit uint64,
transition transitionInterface,
terminalTime time.Time,
) (
includedTransactions []*types.Transaction,
shouldDropTxs []*types.Transaction,
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -775,6 +821,7 @@ func (i *Ibft) writeTransactions(

// no errors, go on
priceTxs.Shift()
i.banishLongTimeConsumingTx(tx, begin)

includedTransactions = append(includedTransactions, tx)
}
Expand All @@ -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,
Expand Down
78 changes: 65 additions & 13 deletions consensus/ibft/ibft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"testing"
"time"

Expand All @@ -24,6 +25,9 @@ import (

var (
defaultBlockGasLimit uint64 = 8000000

addr1 = types.StringToAddress("1")
addr2 = types.StringToAddress("2")
)

type MockBlockchain struct {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
)
}
2 changes: 1 addition & 1 deletion state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit a977703

Please sign in to comment.