github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/integration/epoch_test.go (about)

     1  package integration_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/onflow/flow-go/cmd/bootstrap/run"
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/model/flow/filter"
    13  	"github.com/onflow/flow-go/state/protocol/inmem"
    14  	"github.com/onflow/flow-go/state/protocol/protocol_state/kvstore"
    15  	"github.com/onflow/flow-go/utils/unittest"
    16  )
    17  
    18  // should be able to reach consensus when identity table contains nodes which are joining in next epoch.
    19  func TestUnweightedNode(t *testing.T) {
    20  	// stop after building 2 blocks to ensure we can tolerate 0-weight (joining next
    21  	// epoch) identities, but don't cross an epoch boundary
    22  	stopper := NewStopper(2, 0)
    23  	participantsData := createConsensusIdentities(t, 3)
    24  	rootSnapshot := createRootSnapshot(t, participantsData)
    25  	consensusParticipants := NewConsensusParticipants(participantsData)
    26  
    27  	// add a consensus node to next epoch (it will have `flow.EpochParticipationStatusJoining` status in the current epoch)
    28  	nextEpochParticipantsData := createConsensusIdentities(t, 1)
    29  	// epoch 2 identities includes:
    30  	// * same collection node from epoch 1, so cluster QCs are consistent
    31  	// * 1 new consensus node, joining at epoch 2
    32  	// * random nodes with other roles
    33  	currentEpochCollectionNodes, err := rootSnapshot.Identities(filter.HasRole[flow.Identity](flow.RoleCollection))
    34  	require.NoError(t, err)
    35  	nextEpochIdentities := unittest.CompleteIdentitySet(
    36  		append(
    37  			currentEpochCollectionNodes,
    38  			nextEpochParticipantsData.Identities()...)...,
    39  	)
    40  	rootSnapshot = withNextEpoch(t, rootSnapshot, nextEpochIdentities, nextEpochParticipantsData, consensusParticipants, 10_000, func(block *flow.Block) *flow.QuorumCertificate {
    41  		return createRootQC(t, block, participantsData)
    42  	})
    43  	encodableSnap := rootSnapshot.Encodable()
    44  	encodableSnap.QuorumCertificate = createRootQC(t, rootSnapshot.Encodable().SealingSegment.Highest(), participantsData)
    45  	rootSnapshot = inmem.SnapshotFromEncodable(encodableSnap)
    46  
    47  	nodes, hub, runFor := createNodes(t, consensusParticipants, rootSnapshot, stopper)
    48  
    49  	hub.WithFilter(blockNothing)
    50  
    51  	runFor(60 * time.Second)
    52  
    53  	allViews := allFinalizedViews(t, nodes)
    54  	assertSafety(t, allViews)
    55  
    56  	cleanupNodes(nodes)
    57  }
    58  
    59  // test consensus across an epoch boundary, where both epochs have the same identity table.
    60  func TestStaticEpochTransition(t *testing.T) {
    61  	// must finalize 8 blocks, we specify the epoch transition after 4 views
    62  	stopper := NewStopper(8, 0)
    63  	participantsData := createConsensusIdentities(t, 3)
    64  	rootSnapshot := createRootSnapshot(t, participantsData)
    65  	consensusParticipants := NewConsensusParticipants(participantsData)
    66  
    67  	firstEpochCounter, err := rootSnapshot.Epochs().Current().Counter()
    68  	require.NoError(t, err)
    69  
    70  	// set up next epoch beginning in 4 views, with same identities as first epoch
    71  	nextEpochIdentities, err := rootSnapshot.Identities(filter.Any)
    72  	require.NoError(t, err)
    73  	rootSnapshot = withNextEpoch(t, rootSnapshot, nextEpochIdentities, participantsData, consensusParticipants, 4, func(block *flow.Block) *flow.QuorumCertificate {
    74  		return createRootQC(t, block, participantsData)
    75  	})
    76  
    77  	nodes, hub, runFor := createNodes(t, consensusParticipants, rootSnapshot, stopper)
    78  
    79  	hub.WithFilter(blockNothing)
    80  
    81  	runFor(30 * time.Second)
    82  
    83  	allViews := allFinalizedViews(t, nodes)
    84  	assertSafety(t, allViews)
    85  
    86  	// confirm that we have transitioned to the new epoch
    87  	pstate := nodes[0].state
    88  	afterCounter, err := pstate.Final().Epochs().Current().Counter()
    89  	require.NoError(t, err)
    90  	assert.Equal(t, firstEpochCounter+1, afterCounter)
    91  
    92  	cleanupNodes(nodes)
    93  }
    94  
    95  // test consensus across an epoch boundary, where the identity table changes
    96  // but the new epoch overlaps with the previous epoch.
    97  func TestEpochTransition_IdentitiesOverlap(t *testing.T) {
    98  	// must finalize 8 blocks, we specify the epoch transition after 4 views
    99  	stopper := NewStopper(8, 0)
   100  	privateNodeInfos := createPrivateNodeIdentities(4)
   101  	firstEpochConsensusParticipants := completeConsensusIdentities(t, privateNodeInfos[:3])
   102  	rootSnapshot := createRootSnapshot(t, firstEpochConsensusParticipants)
   103  	consensusParticipants := NewConsensusParticipants(firstEpochConsensusParticipants)
   104  
   105  	firstEpochCounter, err := rootSnapshot.Epochs().Current().Counter()
   106  	require.NoError(t, err)
   107  
   108  	// set up next epoch with 1 new consensus nodes and 2 consensus nodes from first epoch
   109  	// 1 consensus node is removed after the first epoch
   110  	firstEpochIdentities, err := rootSnapshot.Identities(filter.Any)
   111  	require.NoError(t, err)
   112  
   113  	removedIdentity := privateNodeInfos[0].Identity()
   114  	newIdentity := privateNodeInfos[3].Identity()
   115  	nextEpochIdentities := append(
   116  		firstEpochIdentities.Filter(filter.Not(filter.HasNodeID[flow.Identity](removedIdentity.NodeID))),
   117  		newIdentity,
   118  	)
   119  
   120  	// generate new identities for next epoch, it will generate new DKG keys for random beacon participants
   121  	nextEpochParticipantData := completeConsensusIdentities(t, privateNodeInfos[1:])
   122  	rootSnapshot = withNextEpoch(t, rootSnapshot, nextEpochIdentities, nextEpochParticipantData, consensusParticipants, 4, func(block *flow.Block) *flow.QuorumCertificate {
   123  		return createRootQC(t, block, firstEpochConsensusParticipants)
   124  	})
   125  
   126  	nodes, hub, runFor := createNodes(t, consensusParticipants, rootSnapshot, stopper)
   127  
   128  	hub.WithFilter(blockNothing)
   129  
   130  	runFor(30 * time.Second)
   131  
   132  	allViews := allFinalizedViews(t, nodes)
   133  	assertSafety(t, allViews)
   134  
   135  	// confirm that we have transitioned to the new epoch
   136  	pstate := nodes[0].state
   137  	afterCounter, err := pstate.Final().Epochs().Current().Counter()
   138  	require.NoError(t, err)
   139  	assert.Equal(t, firstEpochCounter+1, afterCounter)
   140  
   141  	cleanupNodes(nodes)
   142  }
   143  
   144  // test consensus across an epoch boundary, where the identity table in the new
   145  // epoch is disjoint from the identity table in the first epoch.
   146  func TestEpochTransition_IdentitiesDisjoint(t *testing.T) {
   147  	// must finalize 8 blocks, we specify the epoch transition after 4 views
   148  	stopper := NewStopper(8, 0)
   149  	firstEpochConsensusParticipants := createConsensusIdentities(t, 3)
   150  	rootSnapshot := createRootSnapshot(t, firstEpochConsensusParticipants)
   151  	consensusParticipants := NewConsensusParticipants(firstEpochConsensusParticipants)
   152  
   153  	firstEpochCounter, err := rootSnapshot.Epochs().Current().Counter()
   154  	require.NoError(t, err)
   155  
   156  	// prepare a next epoch with a completely different consensus committee
   157  	// (no overlapping consensus nodes)
   158  	firstEpochIdentities, err := rootSnapshot.Identities(filter.Any)
   159  	require.NoError(t, err)
   160  
   161  	nextEpochParticipantData := createConsensusIdentities(t, 3)
   162  	nextEpochIdentities := append(
   163  		firstEpochIdentities.Filter(filter.Not(filter.HasRole[flow.Identity](flow.RoleConsensus))), // remove all consensus nodes
   164  		nextEpochParticipantData.Identities()...,                                                   // add new consensus nodes
   165  	)
   166  
   167  	rootSnapshot = withNextEpoch(t, rootSnapshot, nextEpochIdentities, nextEpochParticipantData, consensusParticipants, 4, func(block *flow.Block) *flow.QuorumCertificate {
   168  		return createRootQC(t, block, firstEpochConsensusParticipants)
   169  	})
   170  
   171  	nodes, hub, runFor := createNodes(t, consensusParticipants, rootSnapshot, stopper)
   172  
   173  	hub.WithFilter(blockNothing)
   174  
   175  	runFor(60 * time.Second)
   176  
   177  	allViews := allFinalizedViews(t, nodes)
   178  	assertSafety(t, allViews)
   179  
   180  	// confirm that we have transitioned to the new epoch
   181  	pstate := nodes[0].state
   182  	afterCounter, err := pstate.Final().Epochs().Current().Counter()
   183  	require.NoError(t, err)
   184  	assert.Equal(t, firstEpochCounter+1, afterCounter)
   185  
   186  	cleanupNodes(nodes)
   187  }
   188  
   189  // withNextEpoch adds a valid next epoch with the given identities to the input
   190  // snapshot. Also sets the length of the first (current) epoch to curEpochViews.
   191  //
   192  // We make the first (current) epoch start in committed phase so we can transition
   193  // to the next epoch upon reaching the appropriate view without any further changes
   194  // to the protocol state.
   195  func withNextEpoch(
   196  	t *testing.T,
   197  	snapshot *inmem.Snapshot,
   198  	nextEpochIdentities flow.IdentityList,
   199  	nextEpochParticipantData *run.ParticipantData,
   200  	participantsCache *ConsensusParticipants,
   201  	curEpochViews uint64,
   202  	createQC func(block *flow.Block) *flow.QuorumCertificate,
   203  ) *inmem.Snapshot {
   204  	nextEpochIdentities = nextEpochIdentities.Sort(flow.Canonical[flow.Identity])
   205  
   206  	// convert to encodable representation for simple modification
   207  	encodableSnapshot := snapshot.Encodable()
   208  
   209  	rootProtocolState := encodableSnapshot.SealingSegment.LatestProtocolStateEntry()
   210  	epochProtocolState := rootProtocolState.EpochEntry
   211  	currEpochSetup := epochProtocolState.CurrentEpochSetup
   212  	currEpochCommit := epochProtocolState.CurrentEpochCommit
   213  
   214  	// Set current epoch length
   215  	currEpochSetup.FinalView = currEpochSetup.FirstView + curEpochViews - 1
   216  	epochProtocolState.CurrentEpoch.SetupID = currEpochSetup.ID()
   217  
   218  	// Construct events for next epoch
   219  	nextEpochSetup := &flow.EpochSetup{
   220  		Counter:      currEpochSetup.Counter + 1,
   221  		FirstView:    currEpochSetup.FinalView + 1,
   222  		FinalView:    currEpochSetup.FinalView + 1 + 10_000,
   223  		RandomSource: unittest.SeedFixture(flow.EpochSetupRandomSourceLength),
   224  		Participants: nextEpochIdentities.ToSkeleton(),
   225  		Assignments:  unittest.ClusterAssignment(1, nextEpochIdentities.ToSkeleton()),
   226  	}
   227  	nextEpochCommit := &flow.EpochCommit{
   228  		Counter:            nextEpochSetup.Counter,
   229  		ClusterQCs:         currEpochCommit.ClusterQCs,
   230  		DKGParticipantKeys: nextEpochParticipantData.PublicBeaconKeys(),
   231  		DKGGroupKey:        nextEpochParticipantData.GroupKey,
   232  	}
   233  	epochProtocolState.NextEpoch = &flow.EpochStateContainer{
   234  		SetupID:          nextEpochSetup.ID(),
   235  		CommitID:         nextEpochCommit.ID(),
   236  		ActiveIdentities: flow.DynamicIdentityEntryListFromIdentities(nextEpochIdentities),
   237  	}
   238  	// Re-construct epoch protocol state with modified events (constructs ActiveIdentity fields)
   239  	epochProtocolState, err := flow.NewRichProtocolStateEntry(
   240  		epochProtocolState.ProtocolStateEntry,
   241  		epochProtocolState.PreviousEpochSetup, epochProtocolState.PreviousEpochCommit,
   242  		currEpochSetup, currEpochCommit,
   243  		nextEpochSetup, nextEpochCommit)
   244  	require.NoError(t, err)
   245  
   246  	// Store the modified epoch protocol state entry and corresponding KV store entry
   247  	rootKVStore := kvstore.NewDefaultKVStore(epochProtocolState.ID())
   248  	protocolVersion, encodedKVStore, err := rootKVStore.VersionedEncode()
   249  	require.NoError(t, err)
   250  	encodableSnapshot.SealingSegment.ProtocolStateEntries = map[flow.Identifier]*flow.ProtocolStateEntryWrapper{
   251  		rootKVStore.ID(): {
   252  			KVStore: flow.PSKeyValueStoreData{
   253  				Version: protocolVersion,
   254  				Data:    encodedKVStore,
   255  			},
   256  			EpochEntry: epochProtocolState,
   257  		},
   258  	}
   259  
   260  	// Since we modified the root protocol state, we need to update the root block's ProtocolStateID field.
   261  	rootBlock := encodableSnapshot.SealingSegment.Blocks[0]
   262  	rootBlockPayload := rootBlock.Payload
   263  	rootBlockPayload.ProtocolStateID = rootKVStore.ID()
   264  	rootBlock.SetPayload(*rootBlockPayload)
   265  	// Since we changed the root block, we need to update the QC, root result, and root seal.
   266  	encodableSnapshot.LatestResult.BlockID = rootBlock.ID()
   267  	encodableSnapshot.LatestSeal.ResultID = encodableSnapshot.LatestResult.ID()
   268  	encodableSnapshot.LatestSeal.BlockID = rootBlock.ID()
   269  	encodableSnapshot.SealingSegment.LatestSeals = map[flow.Identifier]flow.Identifier{
   270  		rootBlock.ID(): encodableSnapshot.LatestSeal.ID(),
   271  	}
   272  	encodableSnapshot.QuorumCertificate = createQC(rootBlock)
   273  
   274  	participantsCache.Update(nextEpochSetup.Counter, nextEpochParticipantData)
   275  
   276  	return inmem.SnapshotFromEncodable(encodableSnapshot)
   277  }