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

     1  package consensus
     2  
     3  import (
     4  	"math/rand"
     5  	"testing"
     6  
     7  	"github.com/dgraph-io/badger/v2"
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/module/metrics"
    14  	"github.com/onflow/flow-go/module/trace"
    15  	mockprot "github.com/onflow/flow-go/state/protocol/mock"
    16  	storage "github.com/onflow/flow-go/storage/badger"
    17  	"github.com/onflow/flow-go/storage/badger/operation"
    18  	mockstor "github.com/onflow/flow-go/storage/mock"
    19  	"github.com/onflow/flow-go/utils/unittest"
    20  )
    21  
    22  func LogCleanup(list *[]flow.Identifier) func(flow.Identifier) error {
    23  	return func(blockID flow.Identifier) error {
    24  		*list = append(*list, blockID)
    25  		return nil
    26  	}
    27  }
    28  
    29  func TestNewFinalizer(t *testing.T) {
    30  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
    31  		headers := &mockstor.Headers{}
    32  		state := &mockprot.FollowerState{}
    33  		tracer := trace.NewNoopTracer()
    34  		fin := NewFinalizer(db, headers, state, tracer)
    35  		assert.Equal(t, fin.db, db)
    36  		assert.Equal(t, fin.headers, headers)
    37  		assert.Equal(t, fin.state, state)
    38  	})
    39  }
    40  
    41  // TestMakeFinalValidChain checks whether calling `MakeFinal` with the ID of a valid
    42  // descendant block of the latest finalized header results in the finalization of the
    43  // valid descendant and all of its parents up to the finalized header, but excluding
    44  // the children of the valid descendant.
    45  func TestMakeFinalValidChain(t *testing.T) {
    46  
    47  	// create one block that we consider the last finalized
    48  	final := unittest.BlockHeaderFixture()
    49  	final.Height = uint64(rand.Uint32())
    50  
    51  	// generate a couple of children that are pending
    52  	parent := final
    53  	var pending []*flow.Header
    54  	total := 8
    55  	for i := 0; i < total; i++ {
    56  		header := unittest.BlockHeaderFixture()
    57  		header.Height = parent.Height + 1
    58  		header.ParentID = parent.ID()
    59  		pending = append(pending, header)
    60  		parent = header
    61  	}
    62  
    63  	// create a mock protocol state to check finalize calls
    64  	state := mockprot.NewFollowerState(t)
    65  
    66  	// make sure we get a finalize call for the blocks that we want to
    67  	cutoff := total - 3
    68  	var lastID flow.Identifier
    69  	for i := 0; i < cutoff; i++ {
    70  		state.On("Finalize", mock.Anything, pending[i].ID()).Return(nil)
    71  		lastID = pending[i].ID()
    72  	}
    73  
    74  	// this will hold the IDs of blocks clean up
    75  	var list []flow.Identifier
    76  
    77  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
    78  
    79  		// insert the latest finalized height
    80  		err := db.Update(operation.InsertFinalizedHeight(final.Height))
    81  		require.NoError(t, err)
    82  
    83  		// map the finalized height to the finalized block ID
    84  		err = db.Update(operation.IndexBlockHeight(final.Height, final.ID()))
    85  		require.NoError(t, err)
    86  
    87  		// insert the finalized block header into the DB
    88  		err = db.Update(operation.InsertHeader(final.ID(), final))
    89  		require.NoError(t, err)
    90  
    91  		// insert all of the pending blocks into the DB
    92  		for _, header := range pending {
    93  			err = db.Update(operation.InsertHeader(header.ID(), header))
    94  			require.NoError(t, err)
    95  		}
    96  
    97  		// initialize the finalizer with the dependencies and make the call
    98  		metrics := metrics.NewNoopCollector()
    99  		fin := Finalizer{
   100  			db:      db,
   101  			headers: storage.NewHeaders(metrics, db),
   102  			state:   state,
   103  			tracer:  trace.NewNoopTracer(),
   104  			cleanup: LogCleanup(&list),
   105  		}
   106  		err = fin.MakeFinal(lastID)
   107  		require.NoError(t, err)
   108  	})
   109  
   110  	// make sure that finalize was called on protocol state for all desired blocks
   111  	state.AssertExpectations(t)
   112  
   113  	// make sure that cleanup was called for all of them too
   114  	assert.ElementsMatch(t, list, flow.GetIDs(pending[:cutoff]))
   115  }
   116  
   117  // TestMakeFinalInvalidHeight checks whether we receive an error when calling `MakeFinal`
   118  // with a header that is at the same height as the already highest finalized header.
   119  func TestMakeFinalInvalidHeight(t *testing.T) {
   120  
   121  	// create one block that we consider the last finalized
   122  	final := unittest.BlockHeaderFixture()
   123  	final.Height = uint64(rand.Uint32())
   124  
   125  	// generate an alternative block at same height
   126  	pending := unittest.BlockHeaderFixture()
   127  	pending.Height = final.Height
   128  
   129  	// create a mock protocol state to check finalize calls
   130  	state := mockprot.NewFollowerState(t)
   131  
   132  	// this will hold the IDs of blocks clean up
   133  	var list []flow.Identifier
   134  
   135  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
   136  
   137  		// insert the latest finalized height
   138  		err := db.Update(operation.InsertFinalizedHeight(final.Height))
   139  		require.NoError(t, err)
   140  
   141  		// map the finalized height to the finalized block ID
   142  		err = db.Update(operation.IndexBlockHeight(final.Height, final.ID()))
   143  		require.NoError(t, err)
   144  
   145  		// insert the finalized block header into the DB
   146  		err = db.Update(operation.InsertHeader(final.ID(), final))
   147  		require.NoError(t, err)
   148  
   149  		// insert all of the pending header into DB
   150  		err = db.Update(operation.InsertHeader(pending.ID(), pending))
   151  		require.NoError(t, err)
   152  
   153  		// initialize the finalizer with the dependencies and make the call
   154  		metrics := metrics.NewNoopCollector()
   155  		fin := Finalizer{
   156  			db:      db,
   157  			headers: storage.NewHeaders(metrics, db),
   158  			state:   state,
   159  			tracer:  trace.NewNoopTracer(),
   160  			cleanup: LogCleanup(&list),
   161  		}
   162  		err = fin.MakeFinal(pending.ID())
   163  		require.Error(t, err)
   164  	})
   165  
   166  	// make sure that nothing was finalized
   167  	state.AssertExpectations(t)
   168  
   169  	// make sure no cleanup was done
   170  	assert.Empty(t, list)
   171  }
   172  
   173  // TestMakeFinalDuplicate checks whether calling `MakeFinal` with the ID of the currently
   174  // highest finalized header is a no-op and does not result in an error.
   175  func TestMakeFinalDuplicate(t *testing.T) {
   176  
   177  	// create one block that we consider the last finalized
   178  	final := unittest.BlockHeaderFixture()
   179  	final.Height = uint64(rand.Uint32())
   180  
   181  	// create a mock protocol state to check finalize calls
   182  	state := mockprot.NewFollowerState(t)
   183  
   184  	// this will hold the IDs of blocks clean up
   185  	var list []flow.Identifier
   186  
   187  	unittest.RunWithBadgerDB(t, func(db *badger.DB) {
   188  
   189  		// insert the latest finalized height
   190  		err := db.Update(operation.InsertFinalizedHeight(final.Height))
   191  		require.NoError(t, err)
   192  
   193  		// map the finalized height to the finalized block ID
   194  		err = db.Update(operation.IndexBlockHeight(final.Height, final.ID()))
   195  		require.NoError(t, err)
   196  
   197  		// insert the finalized block header into the DB
   198  		err = db.Update(operation.InsertHeader(final.ID(), final))
   199  		require.NoError(t, err)
   200  
   201  		// initialize the finalizer with the dependencies and make the call
   202  		metrics := metrics.NewNoopCollector()
   203  		fin := Finalizer{
   204  			db:      db,
   205  			headers: storage.NewHeaders(metrics, db),
   206  			state:   state,
   207  			tracer:  trace.NewNoopTracer(),
   208  			cleanup: LogCleanup(&list),
   209  		}
   210  		err = fin.MakeFinal(final.ID())
   211  		require.NoError(t, err)
   212  	})
   213  
   214  	// make sure that nothing was finalized
   215  	state.AssertExpectations(t)
   216  
   217  	// make sure no cleanup was done
   218  	assert.Empty(t, list)
   219  }