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 }