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 }