github.com/koko1123/flow-go-1@v0.29.6/engine/common/follower/engine_test.go (about)

     1  package follower_test
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/rs/zerolog"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/mock"
     9  	"github.com/stretchr/testify/require"
    10  	"github.com/stretchr/testify/suite"
    11  
    12  	"github.com/koko1123/flow-go-1/engine/common/follower"
    13  	"github.com/koko1123/flow-go-1/model/flow"
    14  	"github.com/koko1123/flow-go-1/module/compliance"
    15  	"github.com/koko1123/flow-go-1/module/metrics"
    16  	module "github.com/koko1123/flow-go-1/module/mock"
    17  	"github.com/koko1123/flow-go-1/module/trace"
    18  	"github.com/koko1123/flow-go-1/network/channels"
    19  	"github.com/koko1123/flow-go-1/network/mocknetwork"
    20  	protocol "github.com/koko1123/flow-go-1/state/protocol/mock"
    21  	realstorage "github.com/koko1123/flow-go-1/storage"
    22  	storage "github.com/koko1123/flow-go-1/storage/mock"
    23  	"github.com/koko1123/flow-go-1/utils/unittest"
    24  )
    25  
    26  type Suite struct {
    27  	suite.Suite
    28  
    29  	net      *mocknetwork.Network
    30  	con      *mocknetwork.Conduit
    31  	me       *module.Local
    32  	cleaner  *storage.Cleaner
    33  	headers  *storage.Headers
    34  	payloads *storage.Payloads
    35  	state    *protocol.MutableState
    36  	snapshot *protocol.Snapshot
    37  	cache    *module.PendingBlockBuffer
    38  	follower *module.HotStuffFollower
    39  
    40  	engine *follower.Engine
    41  	sync   *module.BlockRequester
    42  }
    43  
    44  func (suite *Suite) SetupTest() {
    45  
    46  	suite.net = new(mocknetwork.Network)
    47  	suite.con = new(mocknetwork.Conduit)
    48  	suite.me = new(module.Local)
    49  	suite.cleaner = new(storage.Cleaner)
    50  	suite.headers = new(storage.Headers)
    51  	suite.payloads = new(storage.Payloads)
    52  	suite.state = new(protocol.MutableState)
    53  	suite.snapshot = new(protocol.Snapshot)
    54  	suite.cache = new(module.PendingBlockBuffer)
    55  	suite.follower = new(module.HotStuffFollower)
    56  	suite.sync = new(module.BlockRequester)
    57  
    58  	suite.net.On("Register", mock.Anything, mock.Anything).Return(suite.con, nil)
    59  	suite.cleaner.On("RunGC").Return()
    60  	suite.headers.On("Store", mock.Anything).Return(nil)
    61  	suite.payloads.On("Store", mock.Anything, mock.Anything).Return(nil)
    62  	suite.state.On("Final").Return(suite.snapshot)
    63  	suite.cache.On("PruneByView", mock.Anything).Return()
    64  	suite.cache.On("Size", mock.Anything).Return(uint(0))
    65  
    66  	metrics := metrics.NewNoopCollector()
    67  	eng, err := follower.New(zerolog.Logger{},
    68  		suite.net,
    69  		suite.me,
    70  		metrics,
    71  		metrics,
    72  		suite.cleaner,
    73  		suite.headers,
    74  		suite.payloads,
    75  		suite.state,
    76  		suite.cache,
    77  		suite.follower,
    78  		suite.sync,
    79  		trace.NewNoopTracer())
    80  	require.Nil(suite.T(), err)
    81  
    82  	suite.engine = eng
    83  }
    84  
    85  func TestFollower(t *testing.T) {
    86  	suite.Run(t, new(Suite))
    87  }
    88  
    89  func (suite *Suite) TestHandlePendingBlock() {
    90  
    91  	originID := unittest.IdentifierFixture()
    92  	head := unittest.BlockFixture()
    93  	block := unittest.BlockFixture()
    94  
    95  	head.Header.Height = 10
    96  	block.Header.Height = 12
    97  
    98  	// not in cache
    99  	suite.cache.On("ByID", block.ID()).Return(flow.Slashable[flow.Block]{}, false).Once()
   100  	suite.headers.On("ByBlockID", block.ID()).Return(nil, realstorage.ErrNotFound).Once()
   101  
   102  	// don't return the parent when requested
   103  	suite.snapshot.On("Head").Return(head.Header, nil)
   104  	suite.cache.On("ByID", block.Header.ParentID).Return(flow.Slashable[flow.Block]{}, false).Once()
   105  	suite.headers.On("ByBlockID", block.Header.ParentID).Return(nil, realstorage.ErrNotFound).Once()
   106  
   107  	suite.cache.On("Add", mock.Anything, mock.Anything).Return(true).Once()
   108  	suite.sync.On("RequestBlock", block.Header.ParentID, block.Header.Height-1).Return().Once()
   109  
   110  	// submit the block
   111  	proposal := unittest.ProposalFromBlock(&block)
   112  	err := suite.engine.Process(channels.ReceiveBlocks, originID, proposal)
   113  	assert.Nil(suite.T(), err)
   114  
   115  	suite.follower.AssertNotCalled(suite.T(), "SubmitProposal", mock.Anything)
   116  	suite.cache.AssertExpectations(suite.T())
   117  	suite.con.AssertExpectations(suite.T())
   118  }
   119  
   120  func (suite *Suite) TestHandleProposal() {
   121  
   122  	originID := unittest.IdentifierFixture()
   123  	parent := unittest.BlockFixture()
   124  	block := unittest.BlockFixture()
   125  
   126  	parent.Header.Height = 10
   127  	block.Header.Height = 11
   128  	block.Header.ParentID = parent.ID()
   129  
   130  	// not in cache
   131  	suite.cache.On("ByID", block.ID()).Return(flow.Slashable[flow.Block]{}, false).Once()
   132  	suite.cache.On("ByID", block.Header.ParentID).Return(flow.Slashable[flow.Block]{}, false).Once()
   133  	suite.headers.On("ByBlockID", block.ID()).Return(nil, realstorage.ErrNotFound).Once()
   134  
   135  	// the parent is the last finalized state
   136  	suite.snapshot.On("Head").Return(parent.Header, nil)
   137  	// we should be able to extend the state with the block
   138  	suite.state.On("Extend", mock.Anything, &block).Return(nil).Once()
   139  	// we should be able to get the parent header by its ID
   140  	suite.headers.On("ByBlockID", block.Header.ParentID).Return(parent.Header, nil).Twice()
   141  	// we do not have any children cached
   142  	suite.cache.On("ByParentID", block.ID()).Return(nil, false)
   143  	// the proposal should be forwarded to the follower
   144  	suite.follower.On("SubmitProposal", block.Header, parent.Header.View).Once().Return(make(<-chan struct{}))
   145  
   146  	// submit the block
   147  	proposal := unittest.ProposalFromBlock(&block)
   148  	err := suite.engine.Process(channels.ReceiveBlocks, originID, proposal)
   149  	assert.Nil(suite.T(), err)
   150  
   151  	suite.follower.AssertExpectations(suite.T())
   152  }
   153  
   154  func (suite *Suite) TestHandleProposalSkipProposalThreshold() {
   155  
   156  	// mock latest finalized state
   157  	final := unittest.BlockHeaderFixture()
   158  	suite.snapshot.On("Head").Return(final, nil)
   159  
   160  	originID := unittest.IdentifierFixture()
   161  	block := unittest.BlockFixture()
   162  
   163  	block.Header.Height = final.Height + compliance.DefaultConfig().SkipNewProposalsThreshold + 1
   164  
   165  	// not in cache or storage
   166  	suite.cache.On("ByID", block.ID()).Return(flow.Slashable[flow.Block]{}, false).Once()
   167  	suite.headers.On("ByBlockID", block.ID()).Return(nil, realstorage.ErrNotFound).Once()
   168  
   169  	// submit the block
   170  	proposal := unittest.ProposalFromBlock(&block)
   171  	err := suite.engine.Process(channels.ReceiveBlocks, originID, proposal)
   172  	assert.NoError(suite.T(), err)
   173  
   174  	// block should be dropped - not added to state or cache
   175  	suite.state.AssertNotCalled(suite.T(), "Extend", mock.Anything)
   176  	suite.cache.AssertNotCalled(suite.T(), "Add", originID, mock.Anything)
   177  }
   178  
   179  func (suite *Suite) TestHandleProposalWithPendingChildren() {
   180  
   181  	originID := unittest.IdentifierFixture()
   182  	parent := unittest.BlockFixture()
   183  	block := unittest.BlockFixture()
   184  	child := unittest.BlockFixture()
   185  
   186  	parent.Header.Height = 9
   187  	block.Header.Height = 10
   188  	child.Header.Height = 11
   189  
   190  	block.Header.ParentID = parent.ID()
   191  	child.Header.ParentID = block.ID()
   192  
   193  	// the parent is the last finalized state
   194  	suite.snapshot.On("Head").Return(parent.Header, nil).Once()
   195  	suite.snapshot.On("Head").Return(block.Header, nil).Once()
   196  
   197  	// both parent and child not in cache
   198  	// suite.cache.On("ByID", child.ID()).Return(nil, false).Once()
   199  	suite.cache.On("ByID", block.ID()).Return(flow.Slashable[flow.Block]{}, false).Once()
   200  	suite.cache.On("ByID", block.Header.ParentID).Return(flow.Slashable[flow.Block]{}, false).Once()
   201  	// first time calling, assume it's not there
   202  	suite.headers.On("ByBlockID", block.ID()).Return(nil, realstorage.ErrNotFound).Once()
   203  	// should extend state with new block
   204  	suite.state.On("Extend", mock.Anything, &block).Return(nil).Once()
   205  	suite.state.On("Extend", mock.Anything, &child).Return(nil).Once()
   206  	// we have already received and stored the parent
   207  	suite.headers.On("ByBlockID", parent.ID()).Return(parent.Header, nil)
   208  	suite.headers.On("ByBlockID", block.ID()).Return(block.Header, nil).Once()
   209  	// should submit to follower
   210  	suite.follower.On("SubmitProposal", block.Header, parent.Header.View).Once().Return(make(<-chan struct{}))
   211  	suite.follower.On("SubmitProposal", child.Header, block.Header.View).Once().Return(make(<-chan struct{}))
   212  
   213  	// we have one pending child cached
   214  	pending := []flow.Slashable[flow.Block]{
   215  		{
   216  			OriginID: originID,
   217  			Message:  &child,
   218  		},
   219  	}
   220  	suite.cache.On("ByParentID", block.ID()).Return(pending, true)
   221  	suite.cache.On("ByParentID", child.ID()).Return(nil, false)
   222  	suite.cache.On("DropForParent", block.ID()).Once()
   223  
   224  	// submit the block proposal
   225  	proposal := unittest.ProposalFromBlock(&block)
   226  	err := suite.engine.Process(channels.ReceiveBlocks, originID, proposal)
   227  	assert.Nil(suite.T(), err)
   228  
   229  	suite.follower.AssertExpectations(suite.T())
   230  }