github.com/onflow/flow-go@v0.33.17/consensus/integration/integration_test.go (about)

     1  package integration_test
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/module/irrecoverable"
    13  	"github.com/onflow/flow-go/module/util"
    14  	"github.com/onflow/flow-go/network/channels"
    15  	"github.com/onflow/flow-go/utils/unittest"
    16  )
    17  
    18  func runNodes(signalerCtx irrecoverable.SignalerContext, nodes []*Node) {
    19  	for _, n := range nodes {
    20  		go func(n *Node) {
    21  			n.committee.Start(signalerCtx)
    22  			n.hot.Start(signalerCtx)
    23  			n.voteAggregator.Start(signalerCtx)
    24  			n.timeoutAggregator.Start(signalerCtx)
    25  			n.compliance.Start(signalerCtx)
    26  			n.messageHub.Start(signalerCtx)
    27  			n.sync.Start(signalerCtx)
    28  			<-util.AllReady(n.committee, n.hot, n.voteAggregator, n.timeoutAggregator, n.compliance, n.sync, n.messageHub)
    29  		}(n)
    30  	}
    31  }
    32  
    33  func stopNodes(t *testing.T, cancel context.CancelFunc, nodes []*Node) {
    34  	stoppingNodes := make([]<-chan struct{}, 0)
    35  	cancel()
    36  	for _, n := range nodes {
    37  		stoppingNodes = append(stoppingNodes, util.AllDone(
    38  			n.committee,
    39  			n.hot,
    40  			n.voteAggregator,
    41  			n.timeoutAggregator,
    42  			n.compliance,
    43  			n.sync,
    44  			n.messageHub,
    45  		))
    46  	}
    47  	unittest.RequireCloseBefore(t, util.AllClosed(stoppingNodes...), time.Second, "requiring nodes to stop")
    48  }
    49  
    50  // happy path: with 3 nodes, they can reach consensus
    51  func Test3Nodes(t *testing.T) {
    52  	stopper := NewStopper(5, 0)
    53  	participantsData := createConsensusIdentities(t, 3)
    54  	rootSnapshot := createRootSnapshot(t, participantsData)
    55  	nodes, hub, runFor := createNodes(t, NewConsensusParticipants(participantsData), rootSnapshot, stopper)
    56  
    57  	hub.WithFilter(blockNothing)
    58  
    59  	runFor(30 * time.Second)
    60  
    61  	allViews := allFinalizedViews(t, nodes)
    62  	assertSafety(t, allViews)
    63  
    64  	cleanupNodes(nodes)
    65  }
    66  
    67  // with 5 nodes, and one node completely blocked, the other 4 nodes can still reach consensus
    68  func Test5Nodes(t *testing.T) {
    69  	// 4 nodes should be able to finalize at least 3 blocks.
    70  	stopper := NewStopper(2, 1)
    71  	participantsData := createConsensusIdentities(t, 5)
    72  	rootSnapshot := createRootSnapshot(t, participantsData)
    73  	nodes, hub, runFor := createNodes(t, NewConsensusParticipants(participantsData), rootSnapshot, stopper)
    74  
    75  	hub.WithFilter(blockNodes(nodes[0]))
    76  
    77  	runFor(30 * time.Second)
    78  
    79  	header, err := nodes[0].state.Final().Head()
    80  	require.NoError(t, err)
    81  
    82  	// the first node was blocked, never finalize any block
    83  	require.Equal(t, uint64(0), header.View)
    84  
    85  	allViews := allFinalizedViews(t, nodes[1:])
    86  	assertSafety(t, allViews)
    87  
    88  	cleanupNodes(nodes)
    89  }
    90  
    91  // TODO: verify if each receiver lost 50% messages, the network can't reach consensus
    92  
    93  func allFinalizedViews(t *testing.T, nodes []*Node) [][]uint64 {
    94  	allViews := make([][]uint64, 0)
    95  
    96  	// verify all nodes arrive at the same state
    97  	for _, node := range nodes {
    98  		views := chainViews(t, node)
    99  		allViews = append(allViews, views)
   100  	}
   101  
   102  	// sort all Views by chain length
   103  	sort.Slice(allViews, func(i, j int) bool {
   104  		return len(allViews[i]) < len(allViews[j])
   105  	})
   106  
   107  	return allViews
   108  }
   109  
   110  func assertSafety(t *testing.T, allViews [][]uint64) {
   111  	// find the longest chain of finalized views
   112  	longest := allViews[len(allViews)-1]
   113  
   114  	for _, views := range allViews {
   115  		// each view in a chain should match with the longest chain
   116  		for j, view := range views {
   117  			require.Equal(t, longest[j], view, "each view in a chain must match with the view in longest chain at the same height, but didn't")
   118  		}
   119  	}
   120  }
   121  
   122  func chainViews(t *testing.T, node *Node) []uint64 {
   123  	views := make([]uint64, 0)
   124  
   125  	head, err := node.state.Final().Head()
   126  	require.NoError(t, err)
   127  	for head != nil && head.View > 0 {
   128  		views = append(views, head.View)
   129  		head, err = node.headers.ByBlockID(head.ParentID)
   130  		require.NoError(t, err)
   131  	}
   132  
   133  	// reverse all views to runFor from lower view to higher view
   134  	low2high := make([]uint64, 0)
   135  	for i := len(views) - 1; i >= 0; i-- {
   136  		low2high = append(low2high, views[i])
   137  	}
   138  	return low2high
   139  }
   140  
   141  // BlockOrDelayFunc is a function for deciding whether a message (or other event) should be
   142  // blocked or delayed. The first return value specifies whether the event should be dropped
   143  // entirely (return value `true`) or should be delivered (return value `false`). The second
   144  // return value specifies the delay by which the message should be delivered.
   145  // Implementations must be CONCURRENCY SAFE.
   146  type BlockOrDelayFunc func(channel channels.Channel, event interface{}, sender, receiver *Node) (bool, time.Duration)
   147  
   148  // blockNothing specifies that _all_ messages should be delivered without delay.
   149  // I.e. this function returns always `false` (no blocking), `0` (no delay).
   150  func blockNothing(_ channels.Channel, _ interface{}, _, _ *Node) (bool, time.Duration) {
   151  	return false, 0
   152  }
   153  
   154  // blockNodes specifies that all messages sent or received by any member of the `denyList`
   155  // should be dropped, i.e. we return `true` (block message), `0` (no delay).
   156  // For nodes _not_ in the `denyList`,  we return `false` (no blocking), `0` (no delay).
   157  func blockNodes(denyList ...*Node) BlockOrDelayFunc {
   158  	denyMap := make(map[flow.Identifier]*Node, len(denyList))
   159  	for _, n := range denyList {
   160  		denyMap[n.id.ID()] = n
   161  	}
   162  	// no concurrency protection needed as blackList is only read but not modified
   163  	return func(channel channels.Channel, event interface{}, sender, receiver *Node) (bool, time.Duration) {
   164  		if _, ok := denyMap[sender.id.ID()]; ok {
   165  			return true, 0 // block the message
   166  		}
   167  		if _, ok := denyMap[receiver.id.ID()]; ok {
   168  			return true, 0 // block the message
   169  		}
   170  		return false, 0 // allow the message
   171  	}
   172  }