github.com/koko1123/flow-go-1@v0.29.6/engine/consensus/compliance/engine_test.go (about) 1 package compliance 2 3 import ( 4 "math/rand" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/mock" 10 "github.com/stretchr/testify/require" 11 "github.com/stretchr/testify/suite" 12 13 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 14 "github.com/koko1123/flow-go-1/engine" 15 "github.com/koko1123/flow-go-1/model/flow" 16 "github.com/koko1123/flow-go-1/model/messages" 17 modulemock "github.com/koko1123/flow-go-1/module/mock" 18 "github.com/koko1123/flow-go-1/network/channels" 19 "github.com/koko1123/flow-go-1/utils/unittest" 20 ) 21 22 func TestComplianceEngine(t *testing.T) { 23 suite.Run(t, new(ComplianceSuite)) 24 } 25 26 type ComplianceSuite struct { 27 ComplianceCoreSuite 28 29 engine *Engine 30 } 31 32 func (cs *ComplianceSuite) SetupTest() { 33 cs.ComplianceCoreSuite.SetupTest() 34 e, err := NewEngine(unittest.Logger(), cs.net, cs.me, cs.prov, cs.core) 35 require.NoError(cs.T(), err) 36 cs.engine = e 37 38 ready := func() <-chan struct{} { 39 channel := make(chan struct{}) 40 close(channel) 41 return channel 42 }() 43 44 cs.hotstuff.On("Start", mock.Anything) 45 cs.hotstuff.On("Ready", mock.Anything).Return(ready) 46 <-cs.engine.Ready() 47 } 48 49 // TestSendVote tests that single vote can be send and properly processed 50 func (cs *ComplianceSuite) TestSendVote() { 51 // create parameters to send a vote 52 blockID := unittest.IdentifierFixture() 53 view := rand.Uint64() 54 sig := unittest.SignatureFixture() 55 recipientID := unittest.IdentifierFixture() 56 57 // submit the vote 58 err := cs.engine.SendVote(blockID, view, sig, recipientID) 59 require.NoError(cs.T(), err, "should pass send vote") 60 61 done := func() <-chan struct{} { 62 channel := make(chan struct{}) 63 close(channel) 64 return channel 65 }() 66 67 cs.hotstuff.On("Done", mock.Anything).Return(done) 68 69 // The vote is transmitted asynchronously. We allow 10ms for the vote to be received: 70 <-time.After(10 * time.Millisecond) 71 <-cs.engine.Done() 72 73 // check it was called with right params 74 vote := messages.BlockVote{ 75 BlockID: blockID, 76 View: view, 77 SigData: sig, 78 } 79 cs.con.AssertCalled(cs.T(), "Unicast", &vote, recipientID) 80 } 81 82 // TestBroadcastProposalWithDelay tests broadcasting proposals with different 83 // inputs 84 func (cs *ComplianceSuite) TestBroadcastProposalWithDelay() { 85 86 // add execution node to participants to make sure we exclude them from broadcast 87 cs.participants = append(cs.participants, unittest.IdentityFixture(unittest.WithRole(flow.RoleExecution))) 88 89 // generate a parent with height and chain ID set 90 parent := unittest.BlockHeaderFixture() 91 parent.ChainID = "test" 92 parent.Height = 10 93 cs.headerDB[parent.ID()] = parent 94 95 // create a block with the parent and store the payload with correct ID 96 block := unittest.BlockWithParentFixture(parent) 97 block.Header.ProposerID = cs.myID 98 cs.payloadDB[block.ID()] = block.Payload 99 100 // keep a duplicate of the correct header to check against leader 101 header := block.Header 102 103 // unset chain and height to make sure they are correctly reconstructed 104 block.Header.ChainID = "" 105 block.Header.Height = 0 106 107 cs.hotstuff.On("SubmitProposal", block.Header, parent.View).Return(doneChan()).Once() 108 109 // submit to broadcast proposal 110 err := cs.engine.BroadcastProposalWithDelay(block.Header, 0) 111 require.NoError(cs.T(), err, "header broadcast should pass") 112 113 // make sure chain ID and height were reconstructed and 114 // we broadcast to correct nodes 115 header.ChainID = "test" 116 header.Height = 11 117 msg := &messages.BlockProposal{ 118 Block: messages.UntrustedBlockFromInternal(block), 119 } 120 121 done := func() <-chan struct{} { 122 channel := make(chan struct{}) 123 close(channel) 124 return channel 125 }() 126 127 cs.hotstuff.On("Done", mock.Anything).Return(done) 128 129 <-time.After(10 * time.Millisecond) 130 <-cs.engine.Done() 131 cs.con.AssertCalled(cs.T(), "Publish", msg, cs.participants[1].NodeID, cs.participants[2].NodeID) 132 133 // should fail with wrong proposer 134 header.ProposerID = unittest.IdentifierFixture() 135 err = cs.engine.BroadcastProposalWithDelay(header, 0) 136 require.Error(cs.T(), err, "should fail with wrong proposer") 137 header.ProposerID = cs.myID 138 139 // should fail with changed (missing) parent 140 header.ParentID[0]++ 141 err = cs.engine.BroadcastProposalWithDelay(header, 0) 142 require.Error(cs.T(), err, "should fail with missing parent") 143 header.ParentID[0]-- 144 145 // should fail with wrong block ID (payload unavailable) 146 header.View++ 147 err = cs.engine.BroadcastProposalWithDelay(header, 0) 148 require.Error(cs.T(), err, "should fail with missing payload") 149 header.View-- 150 } 151 152 // TestSubmittingMultipleVotes tests that we can send multiple votes and they 153 // are queued and processed in expected way 154 func (cs *ComplianceSuite) TestSubmittingMultipleEntries() { 155 cs.hotstuff.On("Done", mock.Anything).Return(doneChan()) 156 157 // create a vote 158 originID := unittest.IdentifierFixture() 159 voteCount := 15 160 161 var wg sync.WaitGroup 162 wg.Add(1) 163 go func() { 164 for i := 0; i < voteCount; i++ { 165 vote := messages.BlockVote{ 166 BlockID: unittest.IdentifierFixture(), 167 View: rand.Uint64(), 168 SigData: unittest.SignatureFixture(), 169 } 170 cs.voteAggregator.On("AddVote", &model.Vote{ 171 View: vote.View, 172 BlockID: vote.BlockID, 173 SignerID: originID, 174 SigData: vote.SigData, 175 }).Return().Once() 176 // execute the vote submission 177 _ = cs.engine.Process(channels.ConsensusCommittee, originID, &vote) 178 } 179 wg.Done() 180 }() 181 wg.Add(1) 182 go func() { 183 // create a proposal that directly descends from the latest finalized header 184 originID := cs.participants[1].NodeID 185 block := unittest.BlockWithParentFixture(cs.head) 186 proposal := unittest.ProposalFromBlock(block) 187 188 // store the data for retrieval 189 cs.headerDB[block.Header.ParentID] = cs.head 190 cs.hotstuff.On("SubmitProposal", block.Header, cs.head.View).Return(doneChan()) 191 _ = cs.engine.Process(channels.ConsensusCommittee, originID, proposal) 192 wg.Done() 193 }() 194 195 wg.Wait() 196 197 time.Sleep(time.Second) 198 199 // check that submit vote was called with correct parameters 200 cs.hotstuff.AssertExpectations(cs.T()) 201 cs.voteAggregator.AssertExpectations(cs.T()) 202 } 203 204 // TestProcessUnsupportedMessageType tests that Process and ProcessLocal correctly handle a case where invalid message type 205 // was submitted from network layer. 206 func (cs *ComplianceSuite) TestProcessUnsupportedMessageType() { 207 invalidEvent := uint64(42) 208 err := cs.engine.Process("ch", unittest.IdentifierFixture(), invalidEvent) 209 // shouldn't result in error since byzantine inputs are expected 210 require.NoError(cs.T(), err) 211 // in case of local processing error cannot be consumed since all inputs are trusted 212 err = cs.engine.ProcessLocal(invalidEvent) 213 require.Error(cs.T(), err) 214 require.True(cs.T(), engine.IsIncompatibleInputTypeError(err)) 215 } 216 217 // TestOnFinalizedBlock tests if finalized block gets processed when send through `Engine`. 218 // Tests the whole processing pipeline. 219 func (cs *ComplianceSuite) TestOnFinalizedBlock() { 220 finalizedBlock := unittest.BlockHeaderFixture() 221 cs.head = finalizedBlock 222 223 *cs.pending = modulemock.PendingBlockBuffer{} 224 cs.pending.On("PruneByView", finalizedBlock.View).Return(nil).Once() 225 cs.pending.On("Size").Return(uint(0)).Once() 226 cs.engine.OnFinalizedBlock(model.BlockFromFlow(finalizedBlock, finalizedBlock.View-1)) 227 228 require.Eventually(cs.T(), 229 func() bool { 230 return cs.pending.AssertCalled(cs.T(), "PruneByView", finalizedBlock.View) 231 }, time.Second, time.Millisecond*20) 232 }