github.com/koko1123/flow-go-1@v0.29.6/consensus/integration/epoch_test.go (about) 1 package integration_test 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/require" 10 11 "github.com/koko1123/flow-go-1/cmd/bootstrap/run" 12 "github.com/koko1123/flow-go-1/model/encodable" 13 "github.com/koko1123/flow-go-1/model/flow" 14 "github.com/koko1123/flow-go-1/model/flow/filter" 15 "github.com/koko1123/flow-go-1/model/flow/mapfunc" 16 "github.com/koko1123/flow-go-1/model/flow/order" 17 "github.com/koko1123/flow-go-1/module/irrecoverable" 18 "github.com/koko1123/flow-go-1/state/protocol/inmem" 19 "github.com/koko1123/flow-go-1/utils/unittest" 20 ) 21 22 // should be able to reach consensus when identity table contains nodes with 0 weight. 23 func TestUnweightedNode(t *testing.T) { 24 // stop after building 2 blocks to ensure we can tolerate 0-weight (joining next 25 // epoch) identities, but don't cross an epoch boundary 26 // stop after building 2 blocks to ensure we can tolerate 0-weight (joining next 27 // epoch) identities, but don't cross an epoch boundary 28 stopper := NewStopper(2, 0) 29 participantsData := createConsensusIdentities(t, 3) 30 rootSnapshot := createRootSnapshot(t, participantsData) 31 consensusParticipants := NewConsensusParticipants(participantsData) 32 33 // add a consensus node to next epoch (it will have 0 weight in the current epoch) 34 nextEpochParticipantsData := createConsensusIdentities(t, 1) 35 nextEpochIdentities := unittest.CompleteIdentitySet(nextEpochParticipantsData.Identities()...) 36 rootSnapshot = withNextEpoch( 37 rootSnapshot, 38 nextEpochIdentities, 39 nextEpochParticipantsData, 40 consensusParticipants, 41 10_000, 42 ) 43 44 nodes, hub := createNodes(t, consensusParticipants, rootSnapshot, stopper) 45 46 hub.WithFilter(blockNothing) 47 48 ctx, cancel := context.WithCancel(context.Background()) 49 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 50 51 runNodes(signalerCtx, nodes) 52 53 unittest.AssertClosesBefore(t, stopper.stopped, 60*time.Second) 54 55 allViews := allFinalizedViews(t, nodes) 56 assertSafety(t, allViews) 57 58 stopNodes(t, cancel, nodes) 59 cleanupNodes(nodes) 60 } 61 62 // test consensus across an epoch boundary, where both epochs have the same identity table. 63 func TestStaticEpochTransition(t *testing.T) { 64 // must finalize 8 blocks, we specify the epoch transition after 4 views 65 stopper := NewStopper(8, 0) 66 participantsData := createConsensusIdentities(t, 3) 67 rootSnapshot := createRootSnapshot(t, participantsData) 68 consensusParticipants := NewConsensusParticipants(participantsData) 69 70 firstEpochCounter, err := rootSnapshot.Epochs().Current().Counter() 71 require.NoError(t, err) 72 73 // set up next epoch beginning in 4 views, with same identities as first epoch 74 nextEpochIdentities, err := rootSnapshot.Identities(filter.Any) 75 require.NoError(t, err) 76 rootSnapshot = withNextEpoch( 77 rootSnapshot, 78 nextEpochIdentities, 79 participantsData, 80 consensusParticipants, 81 4, 82 ) 83 84 nodes, hub := createNodes(t, consensusParticipants, rootSnapshot, stopper) 85 86 hub.WithFilter(blockNothing) 87 88 ctx, cancel := context.WithCancel(context.Background()) 89 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 90 91 runNodes(signalerCtx, nodes) 92 93 unittest.AssertClosesBefore(t, stopper.stopped, 30*time.Second) 94 95 allViews := allFinalizedViews(t, nodes) 96 assertSafety(t, allViews) 97 98 // confirm that we have transitioned to the new epoch 99 pstate := nodes[0].state 100 afterCounter, err := pstate.Final().Epochs().Current().Counter() 101 require.NoError(t, err) 102 assert.Equal(t, firstEpochCounter+1, afterCounter) 103 104 stopNodes(t, cancel, nodes) 105 cleanupNodes(nodes) 106 } 107 108 // test consensus across an epoch boundary, where the identity table changes 109 // but the new epoch overlaps with the previous epoch. 110 func TestEpochTransition_IdentitiesOverlap(t *testing.T) { 111 // must finalize 8 blocks, we specify the epoch transition after 4 views 112 stopper := NewStopper(8, 0) 113 privateNodeInfos := createPrivateNodeIdentities(4) 114 firstEpochConsensusParticipants := completeConsensusIdentities(t, privateNodeInfos[:3]) 115 rootSnapshot := createRootSnapshot(t, firstEpochConsensusParticipants) 116 consensusParticipants := NewConsensusParticipants(firstEpochConsensusParticipants) 117 118 firstEpochCounter, err := rootSnapshot.Epochs().Current().Counter() 119 require.NoError(t, err) 120 121 // set up next epoch with 1 new consensus nodes and 2 consensus nodes from first epoch 122 // 1 consensus node is removed after the first epoch 123 firstEpochIdentities, err := rootSnapshot.Identities(filter.Any) 124 require.NoError(t, err) 125 126 removedIdentity := privateNodeInfos[0].Identity() 127 newIdentity := privateNodeInfos[3].Identity() 128 nextEpochIdentities := append( 129 firstEpochIdentities.Filter(filter.Not(filter.HasNodeID(removedIdentity.NodeID))), 130 newIdentity, 131 ) 132 133 // generate new identities for next epoch, it will generate new DKG keys for random beacon participants 134 nextEpochParticipantData := completeConsensusIdentities(t, privateNodeInfos[1:]) 135 rootSnapshot = withNextEpoch( 136 rootSnapshot, 137 nextEpochIdentities, 138 nextEpochParticipantData, 139 consensusParticipants, 140 4, 141 ) 142 143 nodes, hub := createNodes(t, consensusParticipants, rootSnapshot, stopper) 144 145 hub.WithFilter(blockNothing) 146 147 ctx, cancel := context.WithCancel(context.Background()) 148 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 149 150 runNodes(signalerCtx, nodes) 151 152 unittest.AssertClosesBefore(t, stopper.stopped, 30*time.Second) 153 154 allViews := allFinalizedViews(t, nodes) 155 assertSafety(t, allViews) 156 157 // confirm that we have transitioned to the new epoch 158 pstate := nodes[0].state 159 afterCounter, err := pstate.Final().Epochs().Current().Counter() 160 require.NoError(t, err) 161 assert.Equal(t, firstEpochCounter+1, afterCounter) 162 163 stopNodes(t, cancel, nodes) 164 cleanupNodes(nodes) 165 } 166 167 // test consensus across an epoch boundary, where the identity table in the new 168 // epoch is disjoint from the identity table in the first epoch. 169 func TestEpochTransition_IdentitiesDisjoint(t *testing.T) { 170 // must finalize 8 blocks, we specify the epoch transition after 4 views 171 stopper := NewStopper(8, 0) 172 firstEpochConsensusParticipants := createConsensusIdentities(t, 3) 173 rootSnapshot := createRootSnapshot(t, firstEpochConsensusParticipants) 174 consensusParticipants := NewConsensusParticipants(firstEpochConsensusParticipants) 175 176 firstEpochCounter, err := rootSnapshot.Epochs().Current().Counter() 177 require.NoError(t, err) 178 179 // prepare a next epoch with a completely different consensus committee 180 // (no overlapping consensus nodes) 181 firstEpochIdentities, err := rootSnapshot.Identities(filter.Any) 182 require.NoError(t, err) 183 184 nextEpochParticipantData := createConsensusIdentities(t, 3) 185 nextEpochIdentities := append( 186 firstEpochIdentities.Filter(filter.Not(filter.HasRole(flow.RoleConsensus))), // remove all consensus nodes 187 nextEpochParticipantData.Identities()..., // add new consensus nodes 188 ) 189 190 rootSnapshot = withNextEpoch( 191 rootSnapshot, 192 nextEpochIdentities, 193 nextEpochParticipantData, 194 consensusParticipants, 195 4, 196 ) 197 198 nodes, hub := createNodes(t, consensusParticipants, rootSnapshot, stopper) 199 200 hub.WithFilter(blockNothing) 201 202 ctx, cancel := context.WithCancel(context.Background()) 203 signalerCtx, _ := irrecoverable.WithSignaler(ctx) 204 205 runNodes(signalerCtx, nodes) 206 207 unittest.AssertClosesBefore(t, stopper.stopped, 60*time.Second) 208 209 allViews := allFinalizedViews(t, nodes) 210 assertSafety(t, allViews) 211 212 // confirm that we have transitioned to the new epoch 213 pstate := nodes[0].state 214 afterCounter, err := pstate.Final().Epochs().Current().Counter() 215 require.NoError(t, err) 216 assert.Equal(t, firstEpochCounter+1, afterCounter) 217 218 stopNodes(t, cancel, nodes) 219 cleanupNodes(nodes) 220 } 221 222 // withNextEpoch adds a valid next epoch with the given identities to the input 223 // snapshot. Also sets the length of the first (current) epoch to curEpochViews. 224 // 225 // We make the first (current) epoch start in committed phase so we can transition 226 // to the next epoch upon reaching the appropriate view without any further changes 227 // to the protocol state. 228 func withNextEpoch( 229 snapshot *inmem.Snapshot, 230 nextEpochIdentities flow.IdentityList, 231 nextEpochParticipantData *run.ParticipantData, 232 participantsCache *ConsensusParticipants, 233 curEpochViews uint64, 234 ) *inmem.Snapshot { 235 236 // convert to encodable representation for simple modification 237 encodableSnapshot := snapshot.Encodable() 238 239 nextEpochIdentities = nextEpochIdentities.Sort(order.Canonical) 240 241 currEpoch := &encodableSnapshot.Epochs.Current // take pointer so assignments apply 242 currEpoch.FinalView = currEpoch.FirstView + curEpochViews - 1 // first epoch lasts curEpochViews 243 encodableSnapshot.Epochs.Next = &inmem.EncodableEpoch{ 244 Counter: currEpoch.Counter + 1, 245 FirstView: currEpoch.FinalView + 1, 246 FinalView: currEpoch.FinalView + 1 + 10000, 247 RandomSource: unittest.SeedFixture(flow.EpochSetupRandomSourceLength), 248 InitialIdentities: nextEpochIdentities, 249 // must include info corresponding to EpochCommit event, since we are 250 // starting in committed phase 251 Clustering: unittest.ClusterList(1, nextEpochIdentities), 252 Clusters: currEpoch.Clusters, 253 DKG: &inmem.EncodableDKG{ 254 GroupKey: encodable.RandomBeaconPubKey{ 255 PublicKey: nextEpochParticipantData.GroupKey, 256 }, 257 Participants: nextEpochParticipantData.Lookup, 258 }, 259 } 260 261 participantsCache.Update(encodableSnapshot.Epochs.Next.Counter, nextEpochParticipantData) 262 263 // we must start the current epoch in committed phase so we can transition to the next epoch 264 encodableSnapshot.Phase = flow.EpochPhaseCommitted 265 encodableSnapshot.LatestSeal.ResultID = encodableSnapshot.LatestResult.ID() 266 267 // set identities for root snapshot to include next epoch identities, 268 // since we are in committed phase 269 encodableSnapshot.Identities = append( 270 // all the current epoch identities 271 encodableSnapshot.Identities, 272 // and all the NEW identities in next epoch, with 0 weight 273 nextEpochIdentities. 274 Filter(filter.Not(filter.In(encodableSnapshot.Identities))). 275 Map(mapfunc.WithWeight(0))..., 276 ).Sort(order.Canonical) 277 278 return inmem.SnapshotFromEncodable(encodableSnapshot) 279 }