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 }