Skip to content

Commit

Permalink
first check on mockFraud Handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Faulty Tolly committed Sep 12, 2024
1 parent bf0e7eb commit c7939c7
Show file tree
Hide file tree
Showing 23 changed files with 2,012 additions and 138 deletions.
7 changes: 7 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ packages:
github.com/dymensionxyz/dymint/p2p:
interfaces:
GetProposerI:
github.com/dymensionxyz/dymint/block:
interfaces:
ExecutorI:
github.com/dymensionxyz/dymint/fraud:
interfaces:
Handler:




Expand Down
18 changes: 17 additions & 1 deletion block/block.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
package block

import (
"context"
"errors"
"fmt"
"github.com/dymensionxyz/dymint/fraud"

errorsmod "cosmossdk.io/errors"

"github.com/dymensionxyz/dymint/types"
)

// applyBlockWithFraudHandling calls applyBlock and handles fraud errors.
// Contract: block and commit must be validated before calling this function!
func (m *Manager) applyBlockWithFraudHandling(block *types.Block, commit *types.Commit, blockMetaData types.BlockMetaData) error {
err := m.applyBlock(block, commit, blockMetaData)
if errors.Is(err, fraud.ErrFraud) {
m.FraudHandler.HandleFault(context.Background(), err)
} else if err != nil {
return fmt.Errorf("apply block: %w", err)
}

return nil
}

// applyBlock applies the block to the store and the abci app.
// Contract: block and commit must be validated before calling this function!
// steps: save block -> execute block with app -> update state -> commit block to app -> update state's height and commit result.
Expand Down Expand Up @@ -152,7 +168,7 @@ func (m *Manager) attemptApplyCachedBlocks() error {
return fmt.Errorf("block not valid at height %d, dropping it: err:%w", cachedBlock.Block.Header.Height, err)
}

err := m.applyBlock(cachedBlock.Block, cachedBlock.Commit, types.BlockMetaData{Source: cachedBlock.Source})
err := m.applyBlockWithFraudHandling(cachedBlock.Block, cachedBlock.Commit, types.BlockMetaData{Source: cachedBlock.Source})
if err != nil {
return fmt.Errorf("apply cached block: expected height: %d: %w", expectedHeight, err)
}
Expand Down
14 changes: 13 additions & 1 deletion block/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ import (
// default minimum block max size allowed. not specific reason to set it to 10K, but we need to avoid no transactions can be included in a block.
const minBlockMaxBytes = 10000

type ExecutorI interface {
InitChain(genesis *tmtypes.GenesisDoc, valset []*tmtypes.Validator) (*abci.ResponseInitChain, error)
CreateBlock(height uint64, lastCommit *types.Commit, lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64) *types.Block
Commit(state *types.State, block *types.Block, resp *tmstate.ABCIResponses) ([]byte, int64, error)
GetAppInfo() (*abci.ResponseInfo, error)
ExecuteBlock(state *types.State, block *types.Block) (*tmstate.ABCIResponses, error)
UpdateStateAfterInitChain(s *types.State, res *abci.ResponseInitChain)
UpdateMempoolAfterInitChain(s *types.State)
UpdateStateAfterCommit(s *types.State, resp *tmstate.ABCIResponses, appHash []byte, height uint64)
UpdateProposerFromBlock(s *types.State, block *types.Block) bool
}

// Executor creates and applies blocks and maintains state.
type Executor struct {
localAddress []byte
Expand All @@ -34,7 +46,7 @@ type Executor struct {

// NewExecutor creates new instance of BlockExecutor.
// localAddress will be used in sequencer mode only.
func NewExecutor(localAddress []byte, chainID string, mempool mempool.Mempool, proxyApp proxy.AppConns, eventBus *tmtypes.EventBus, logger types.Logger) (*Executor, error) {
func NewExecutor(localAddress []byte, chainID string, mempool mempool.Mempool, proxyApp proxy.AppConns, eventBus *tmtypes.EventBus, logger types.Logger) (ExecutorI, error) {
be := Executor{
localAddress: localAddress,
chainID: chainID,
Expand Down
11 changes: 10 additions & 1 deletion block/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/dymensionxyz/dymint/fraud"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -45,7 +46,7 @@ type Manager struct {
// Store and execution
Store store.Store
State *types.State
Executor *Executor
Executor ExecutorI

// Clients and servers
Pubsub *pubsub.Server
Expand Down Expand Up @@ -77,6 +78,9 @@ type Manager struct {

// TargetHeight holds the value of the current highest block seen from either p2p (probably higher) or the DA
TargetHeight atomic.Uint64

// Fraud handler
FraudHandler fraud.Handler
}

// NewManager creates new block Manager.
Expand Down Expand Up @@ -116,6 +120,7 @@ func NewManager(
blockCache: &Cache{
cache: make(map[uint64]types.CachedBlock),
},
FraudHandler: nil, // TODO: create a default handler
}

err = m.LoadStateOnInit(store, genesis, logger)
Expand All @@ -137,6 +142,10 @@ func NewManager(
return m, nil
}

func (m *Manager) SetFraudHandler(handler fraud.Handler) {
m.FraudHandler = handler
}

// Start starts the block manager.
func (m *Manager) Start(ctx context.Context) error {
// Check if InitChain flow is needed
Expand Down
73 changes: 73 additions & 0 deletions block/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package block_test
import (
"context"
"crypto/rand"
"errors"
"github.com/dymensionxyz/dymint/fraud"
fraudmocks "github.com/dymensionxyz/dymint/mocks/github.com/dymensionxyz/dymint/fraud"
"sync/atomic"

"testing"
Expand Down Expand Up @@ -31,6 +34,7 @@ import (

"github.com/dymensionxyz/dymint/config"
"github.com/dymensionxyz/dymint/da"
block2 "github.com/dymensionxyz/dymint/mocks/github.com/dymensionxyz/dymint/block"
slregistry "github.com/dymensionxyz/dymint/settlement/registry"
"github.com/dymensionxyz/dymint/store"
)
Expand Down Expand Up @@ -545,6 +549,75 @@ func TestDAFetch(t *testing.T) {
}
}

func TestManager_ProcessNextDABatch_FraudHandling(t *testing.T) {
require := require.New(t)
// Setup app
app := testutil.GetAppMock(testutil.Info, testutil.Commit, testutil.EndBlock)
app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{
RollappParamUpdates: &abci.RollappParams{
Da: "mock",
Version: version.Commit,
},
ConsensusParamUpdates: &abci.ConsensusParams{
Block: &abci.BlockParams{
MaxGas: 40000000,
MaxBytes: 500000,
},
},
})
// Create proxy app
clientCreator := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(clientCreator)
err := proxyApp.Start()
require.NoError(err)
// Create a new mock store which should succeed to save the first block
mockStore := testutil.NewMockStore()
// Init manager
manager, err := testutil.GetManager(testutil.GetManagerConfig(), nil, 1, 1, 0, proxyApp, mockStore)
require.NoError(err)
commitHash := [32]byte{1}
manager.DAClient = testutil.GetMockDALC(log.TestingLogger())
manager.Retriever = manager.DAClient.(da.BatchRetriever)
app.On("Commit", mock.Anything).Return(abci.ResponseCommit{Data: commitHash[:]})
nextBatchStartHeight := manager.NextHeightToSubmit()
batch, err := testutil.GenerateBatch(nextBatchStartHeight, nextBatchStartHeight+uint64(testutil.DefaultTestBatchSize-1), manager.LocalKey)
require.NoError(err)
daResultSubmitBatch := manager.DAClient.SubmitBatch(batch)
require.Equal(daResultSubmitBatch.Code, da.StatusSuccess)
err = manager.SLClient.SubmitBatch(batch, manager.DAClient.GetClientType(), &daResultSubmitBatch)
require.NoError(err)

//// Mock Executor to return ErrFraud
mockExecutor := &block2.MockExecutorI{}
manager.Executor = mockExecutor
mockExecutor.On("GetAppInfo").Return(&abci.ResponseInfo{
LastBlockHeight: int64(batch.EndHeight()),
}, nil)
mockExecutor.On("ExecuteBlock", mock.Anything, mock.Anything).Return(nil, fraud.ErrFraud)

// Check that handle fault is called
mockFraudHandler := &fraudmocks.MockHandler{}
manager.FraudHandler = mockFraudHandler

mockFraudHandler.On("HandleFault", mock.Anything, mock.MatchedBy(func(err error) bool {
return errors.Is(err, fraud.ErrFraud)
})).Return(nil)

app.On("Commit", mock.Anything).Return(abci.ResponseCommit{Data: commitHash[:]}).Once()
app.On("Info", mock.Anything).Return(abci.ResponseInfo{
LastBlockHeight: int64(batch.EndHeight()),
LastBlockAppHash: commitHash[:],
})

// Call ProcessNextDABatch
err = manager.ProcessNextDABatch(daResultSubmitBatch.SubmitMetaData)

// Verify
require.NoError(err)
mockExecutor.AssertExpectations(t)
mockFraudHandler.AssertExpectations(t)
}

func TestManager_updateTargetHeight(t *testing.T) {
tests := []struct {
name string
Expand Down
4 changes: 2 additions & 2 deletions block/retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (m *Manager) applyLocalBlock(height uint64) error {
return fmt.Errorf("validate block from local store: height: %d: %w", height, err)
}

err = m.applyBlock(block, commit, types.BlockMetaData{Source: types.LocalDb})
err = m.applyBlockWithFraudHandling(block, commit, types.BlockMetaData{Source: types.LocalDb})
if err != nil {
return fmt.Errorf("apply block from local store: height: %d: %w", height, err)
}
Expand Down Expand Up @@ -146,7 +146,7 @@ func (m *Manager) ProcessNextDABatch(daMetaData *da.DASubmitMetaData) error {
continue
}

err := m.applyBlock(block, batch.Commits[i], types.BlockMetaData{Source: types.DA, DAHeight: daMetaData.Height})
err := m.applyBlockWithFraudHandling(block, batch.Commits[i], types.BlockMetaData{Source: types.DA, DAHeight: daMetaData.Height})
if err != nil {
return fmt.Errorf("apply block: height: %d: %w", block.Header.Height, err)
}
Expand Down
52 changes: 52 additions & 0 deletions fraud/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package fraud

import (
"context"
"errors"
"fmt"
)

// Handler is an interface that defines a method to handle faults.
// Contract: should not be blocking.
type Handler interface {
// HandleFault handles a fault that occurred in the system.
// The fault is passed as an error type.
HandleFault(ctx context.Context, fault error)
}

type GenericHandler struct {
handlers map[error]Handler
}

func NewGenericHandler() *GenericHandler {
return &GenericHandler{
handlers: make(map[error]Handler),
}
}

func (gh *GenericHandler) RegisterHandler(errorType error, handler Handler) error {
if errorType == nil {
return errors.New("error type cannot be nil")
}
if handler == nil {
return errors.New("handler cannot be nil")
}
if !errors.Is(errorType, ErrFraud) {
return fmt.Errorf("error type must be or wrap ErrFraud")
}
if _, exists := gh.handlers[errorType]; exists {
return fmt.Errorf("handler for error type %v already registered", errorType)
}
gh.handlers[errorType] = handler
return nil
}

func (gh *GenericHandler) HandleFault(ctx context.Context, fault error) {
for errType, handler := range gh.handlers {
if errors.Is(fault, errType) {
handler.HandleFault(ctx, fault)
return
}
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
panic(fmt.Sprintf("No handler registered for error type: %T", fault))
}
Loading

0 comments on commit c7939c7

Please sign in to comment.