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  }