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  }