github.com/koko1123/flow-go-1@v0.29.6/state/protocol/badger/state_test.go (about)

     1  package badger_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/dgraph-io/badger/v3"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	testmock "github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/koko1123/flow-go-1/model/flow"
    17  	"github.com/koko1123/flow-go-1/module/metrics"
    18  	"github.com/koko1123/flow-go-1/module/mock"
    19  	"github.com/koko1123/flow-go-1/state/protocol"
    20  	bprotocol "github.com/koko1123/flow-go-1/state/protocol/badger"
    21  	"github.com/koko1123/flow-go-1/state/protocol/inmem"
    22  	"github.com/koko1123/flow-go-1/state/protocol/util"
    23  	protoutil "github.com/koko1123/flow-go-1/state/protocol/util"
    24  	storagebadger "github.com/koko1123/flow-go-1/storage/badger"
    25  	storutil "github.com/koko1123/flow-go-1/storage/util"
    26  	"github.com/koko1123/flow-go-1/utils/unittest"
    27  )
    28  
    29  // TestBootstrapAndOpen verifies after bootstrapping with a root snapshot
    30  // we should be able to open it and got the same state.
    31  func TestBootstrapAndOpen(t *testing.T) {
    32  
    33  	// create a state root and bootstrap the protocol state with it
    34  	participants := unittest.CompleteIdentitySet()
    35  	rootSnapshot := unittest.RootSnapshotFixture(participants, func(block *flow.Block) {
    36  		block.Header.ParentID = unittest.IdentifierFixture()
    37  	})
    38  
    39  	protoutil.RunWithBootstrapState(t, rootSnapshot, func(db *badger.DB, _ *bprotocol.State) {
    40  
    41  		// expect the final view metric to be set to current epoch's final view
    42  		epoch := rootSnapshot.Epochs().Current()
    43  		finalView, err := epoch.FinalView()
    44  		require.NoError(t, err)
    45  		counter, err := epoch.Counter()
    46  		require.NoError(t, err)
    47  		phase, err := rootSnapshot.Phase()
    48  		require.NoError(t, err)
    49  
    50  		complianceMetrics := new(mock.ComplianceMetrics)
    51  		complianceMetrics.On("CommittedEpochFinalView", finalView).Once()
    52  		complianceMetrics.On("CurrentEpochCounter", counter).Once()
    53  		complianceMetrics.On("CurrentEpochPhase", phase).Once()
    54  		complianceMetrics.On("CurrentEpochFinalView", finalView).Once()
    55  		complianceMetrics.On("FinalizedHeight", testmock.Anything).Once()
    56  		complianceMetrics.On("SealedHeight", testmock.Anything).Once()
    57  
    58  		dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(epoch)
    59  		require.NoError(t, err)
    60  		complianceMetrics.On("CurrentDKGPhase1FinalView", dkgPhase1FinalView).Once()
    61  		complianceMetrics.On("CurrentDKGPhase2FinalView", dkgPhase2FinalView).Once()
    62  		complianceMetrics.On("CurrentDKGPhase3FinalView", dkgPhase3FinalView).Once()
    63  
    64  		noopMetrics := new(metrics.NoopCollector)
    65  		all := storagebadger.InitAll(noopMetrics, db)
    66  		// protocol state has been bootstrapped, now open a protocol state with the database
    67  		state, err := bprotocol.OpenState(complianceMetrics, db, all.Headers, all.Seals, all.Results, all.Blocks, all.Setups, all.EpochCommits, all.Statuses)
    68  		require.NoError(t, err)
    69  
    70  		complianceMetrics.AssertExpectations(t)
    71  
    72  		unittest.AssertSnapshotsEqual(t, rootSnapshot, state.Final())
    73  	})
    74  }
    75  
    76  // TestBootstrapAndOpen_EpochCommitted verifies after bootstrapping with a
    77  // root snapshot from EpochCommitted phase  we should be able to open it and
    78  // got the same state.
    79  func TestBootstrapAndOpen_EpochCommitted(t *testing.T) {
    80  
    81  	// create a state root and bootstrap the protocol state with it
    82  	participants := unittest.CompleteIdentitySet()
    83  	rootSnapshot := unittest.RootSnapshotFixture(participants, func(block *flow.Block) {
    84  		block.Header.ParentID = unittest.IdentifierFixture()
    85  	})
    86  	rootBlock, err := rootSnapshot.Head()
    87  	require.NoError(t, err)
    88  
    89  	// build an epoch on the root state and return a snapshot from the committed phase
    90  	committedPhaseSnapshot := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
    91  		unittest.NewEpochBuilder(t, state).BuildEpoch().CompleteEpoch()
    92  
    93  		// find the point where we transition to the epoch committed phase
    94  		for height := rootBlock.Height + 1; ; height++ {
    95  			phase, err := state.AtHeight(height).Phase()
    96  			require.NoError(t, err)
    97  			if phase == flow.EpochPhaseCommitted {
    98  				return state.AtHeight(height)
    99  			}
   100  		}
   101  	})
   102  
   103  	protoutil.RunWithBootstrapState(t, committedPhaseSnapshot, func(db *badger.DB, _ *bprotocol.State) {
   104  
   105  		complianceMetrics := new(mock.ComplianceMetrics)
   106  
   107  		// expect the final view metric to be set to next epoch's final view
   108  		finalView, err := committedPhaseSnapshot.Epochs().Next().FinalView()
   109  		require.NoError(t, err)
   110  		complianceMetrics.On("CommittedEpochFinalView", finalView).Once()
   111  
   112  		// expect counter to be set to current epochs counter
   113  		counter, err := committedPhaseSnapshot.Epochs().Current().Counter()
   114  		require.NoError(t, err)
   115  		complianceMetrics.On("CurrentEpochCounter", counter).Once()
   116  
   117  		// expect epoch phase to be set to current phase
   118  		phase, err := committedPhaseSnapshot.Phase()
   119  		require.NoError(t, err)
   120  		complianceMetrics.On("CurrentEpochPhase", phase).Once()
   121  
   122  		currentEpochFinalView, err := committedPhaseSnapshot.Epochs().Current().FinalView()
   123  		require.NoError(t, err)
   124  		complianceMetrics.On("CurrentEpochFinalView", currentEpochFinalView).Once()
   125  
   126  		dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView, err := protocol.DKGPhaseViews(committedPhaseSnapshot.Epochs().Current())
   127  		require.NoError(t, err)
   128  		complianceMetrics.On("CurrentDKGPhase1FinalView", dkgPhase1FinalView).Once()
   129  		complianceMetrics.On("CurrentDKGPhase2FinalView", dkgPhase2FinalView).Once()
   130  		complianceMetrics.On("CurrentDKGPhase3FinalView", dkgPhase3FinalView).Once()
   131  		complianceMetrics.On("FinalizedHeight", testmock.Anything).Once()
   132  		complianceMetrics.On("SealedHeight", testmock.Anything).Once()
   133  
   134  		noopMetrics := new(metrics.NoopCollector)
   135  		all := storagebadger.InitAll(noopMetrics, db)
   136  		state, err := bprotocol.OpenState(complianceMetrics, db, all.Headers, all.Seals, all.Results, all.Blocks, all.Setups, all.EpochCommits, all.Statuses)
   137  		require.NoError(t, err)
   138  
   139  		// assert update final view was called
   140  		complianceMetrics.AssertExpectations(t)
   141  
   142  		unittest.AssertSnapshotsEqual(t, committedPhaseSnapshot, state.Final())
   143  	})
   144  }
   145  
   146  // TestBootstrapNonRoot tests bootstrapping the protocol state from arbitrary states.
   147  //
   148  // NOTE: for all these cases, we build a final child block (CHILD). This is
   149  // needed otherwise the parent block would not have a valid QC, since the QC
   150  // is stored in the child.
   151  func TestBootstrapNonRoot(t *testing.T) {
   152  	t.Parallel()
   153  	// start with a regular post-spork root snapshot
   154  	participants := unittest.CompleteIdentitySet()
   155  	rootSnapshot := unittest.RootSnapshotFixture(participants)
   156  	rootBlock, err := rootSnapshot.Head()
   157  	require.NoError(t, err)
   158  
   159  	// should be able to bootstrap from snapshot after sealing a non-root block
   160  	// ROOT <- B1 <- B2(S1) <- CHILD
   161  	t.Run("with sealed block", func(t *testing.T) {
   162  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   163  			block1 := unittest.BlockWithParentFixture(rootBlock)
   164  			buildBlock(t, state, block1)
   165  
   166  			receipt1, seal1 := unittest.ReceiptAndSealForBlock(block1)
   167  			block2 := unittest.BlockWithParentFixture(block1.Header)
   168  			block2.SetPayload(unittest.PayloadFixture(unittest.WithSeals(seal1), unittest.WithReceipts(receipt1)))
   169  			buildBlock(t, state, block2)
   170  
   171  			child := unittest.BlockWithParentFixture(block2.Header)
   172  			buildBlock(t, state, child)
   173  
   174  			return state.AtBlockID(block2.ID())
   175  		})
   176  
   177  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   178  			require.NoError(t, err)
   179  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   180  		})
   181  	})
   182  
   183  	t.Run("with setup next epoch", func(t *testing.T) {
   184  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   185  			unittest.NewEpochBuilder(t, state).BuildEpoch()
   186  
   187  			// find the point where we transition to the epoch setup phase
   188  			for height := rootBlock.Height + 1; ; height++ {
   189  				phase, err := state.AtHeight(height).Phase()
   190  				require.NoError(t, err)
   191  				if phase == flow.EpochPhaseSetup {
   192  					return state.AtHeight(height)
   193  				}
   194  			}
   195  		})
   196  
   197  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   198  			require.NoError(t, err)
   199  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   200  		})
   201  	})
   202  
   203  	t.Run("with committed next epoch", func(t *testing.T) {
   204  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   205  			unittest.NewEpochBuilder(t, state).BuildEpoch().CompleteEpoch()
   206  
   207  			// find the point where we transition to the epoch committed phase
   208  			for height := rootBlock.Height + 1; ; height++ {
   209  				phase, err := state.AtHeight(height).Phase()
   210  				require.NoError(t, err)
   211  				if phase == flow.EpochPhaseCommitted {
   212  					return state.AtHeight(height)
   213  				}
   214  			}
   215  		})
   216  
   217  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   218  			require.NoError(t, err)
   219  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   220  		})
   221  	})
   222  
   223  	t.Run("with previous and next epoch", func(t *testing.T) {
   224  		after := snapshotAfter(t, rootSnapshot, func(state *bprotocol.FollowerState) protocol.Snapshot {
   225  			unittest.NewEpochBuilder(t, state).
   226  				BuildEpoch().CompleteEpoch(). // build epoch 2
   227  				BuildEpoch()                  // build epoch 3
   228  
   229  			// find a snapshot from epoch setup phase in epoch 2
   230  			epoch1Counter, err := rootSnapshot.Epochs().Current().Counter()
   231  			require.NoError(t, err)
   232  			for height := rootBlock.Height + 1; ; height++ {
   233  				snap := state.AtHeight(height)
   234  				counter, err := snap.Epochs().Current().Counter()
   235  				require.NoError(t, err)
   236  				phase, err := snap.Phase()
   237  				require.NoError(t, err)
   238  				if phase == flow.EpochPhaseSetup && counter == epoch1Counter+1 {
   239  					return snap
   240  				}
   241  			}
   242  		})
   243  
   244  		bootstrap(t, after, func(state *bprotocol.State, err error) {
   245  			require.NoError(t, err)
   246  			unittest.AssertSnapshotsEqual(t, after, state.Final())
   247  		})
   248  	})
   249  }
   250  
   251  func TestBootstrap_InvalidIdentities(t *testing.T) {
   252  	t.Run("duplicate node ID", func(t *testing.T) {
   253  		participants := unittest.CompleteIdentitySet()
   254  		dupeIDIdentity := unittest.IdentityFixture(unittest.WithNodeID(participants[0].NodeID))
   255  		participants = append(participants, dupeIDIdentity)
   256  
   257  		root := unittest.RootSnapshotFixture(participants)
   258  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   259  			assert.Error(t, err)
   260  		})
   261  	})
   262  
   263  	t.Run("zero weight", func(t *testing.T) {
   264  		zeroWeightIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification), unittest.WithWeight(0))
   265  		participants := unittest.CompleteIdentitySet(zeroWeightIdentity)
   266  		root := unittest.RootSnapshotFixture(participants)
   267  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   268  			assert.Error(t, err)
   269  		})
   270  	})
   271  
   272  	t.Run("missing role", func(t *testing.T) {
   273  		requiredRoles := []flow.Role{
   274  			flow.RoleConsensus,
   275  			flow.RoleExecution,
   276  			flow.RoleVerification,
   277  		}
   278  
   279  		for _, role := range requiredRoles {
   280  			t.Run(fmt.Sprintf("no %s nodes", role), func(t *testing.T) {
   281  				participants := unittest.IdentityListFixture(5, unittest.WithAllRolesExcept(role))
   282  				root := unittest.RootSnapshotFixture(participants)
   283  				bootstrap(t, root, func(state *bprotocol.State, err error) {
   284  					assert.Error(t, err)
   285  				})
   286  			})
   287  		}
   288  	})
   289  
   290  	t.Run("duplicate address", func(t *testing.T) {
   291  		participants := unittest.CompleteIdentitySet()
   292  		dupeAddressIdentity := unittest.IdentityFixture(unittest.WithAddress(participants[0].Address))
   293  		participants = append(participants, dupeAddressIdentity)
   294  
   295  		root := unittest.RootSnapshotFixture(participants)
   296  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   297  			assert.Error(t, err)
   298  		})
   299  	})
   300  
   301  	t.Run("non-canonical ordering", func(t *testing.T) {
   302  		participants := unittest.IdentityListFixture(20, unittest.WithAllRoles())
   303  
   304  		root := unittest.RootSnapshotFixture(participants)
   305  		// randomly shuffle the identities so they are not canonically ordered
   306  		encodable := root.Encodable()
   307  		encodable.Identities = participants.DeterministicShuffle(time.Now().UnixNano())
   308  		root = inmem.SnapshotFromEncodable(encodable)
   309  		bootstrap(t, root, func(state *bprotocol.State, err error) {
   310  			assert.Error(t, err)
   311  		})
   312  	})
   313  }
   314  
   315  func TestBootstrap_DisconnectedSealingSegment(t *testing.T) {
   316  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   317  	// convert to encodable to easily modify snapshot
   318  	encodable := rootSnapshot.Encodable()
   319  	// add an un-connected tail block to the sealing segment
   320  	tail := unittest.BlockFixture()
   321  	encodable.SealingSegment.Blocks = append([]*flow.Block{&tail}, encodable.SealingSegment.Blocks...)
   322  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   323  
   324  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   325  		assert.Error(t, err)
   326  	})
   327  }
   328  
   329  func TestBootstrap_SealingSegmentMissingSeal(t *testing.T) {
   330  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   331  	// convert to encodable to easily modify snapshot
   332  	encodable := rootSnapshot.Encodable()
   333  	// we are missing the required first seal
   334  	encodable.SealingSegment.FirstSeal = nil
   335  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   336  
   337  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   338  		assert.Error(t, err)
   339  	})
   340  }
   341  
   342  func TestBootstrap_SealingSegmentMissingResult(t *testing.T) {
   343  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   344  	// convert to encodable to easily modify snapshot
   345  	encodable := rootSnapshot.Encodable()
   346  	// we are missing the result referenced by the root seal
   347  	encodable.SealingSegment.ExecutionResults = nil
   348  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   349  
   350  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   351  		assert.Error(t, err)
   352  	})
   353  }
   354  
   355  func TestBootstrap_InvalidQuorumCertificate(t *testing.T) {
   356  	rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   357  	// convert to encodable to easily modify snapshot
   358  	encodable := rootSnapshot.Encodable()
   359  	encodable.QuorumCertificate.BlockID = unittest.IdentifierFixture()
   360  	rootSnapshot = inmem.SnapshotFromEncodable(encodable)
   361  
   362  	bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   363  		assert.Error(t, err)
   364  	})
   365  }
   366  
   367  func TestBootstrap_SealMismatch(t *testing.T) {
   368  	t.Run("seal doesn't match tail block", func(t *testing.T) {
   369  		rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   370  		// convert to encodable to easily modify snapshot
   371  		encodable := rootSnapshot.Encodable()
   372  		encodable.LatestSeal.BlockID = unittest.IdentifierFixture()
   373  
   374  		bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   375  			assert.Error(t, err)
   376  		})
   377  	})
   378  
   379  	t.Run("result doesn't match tail block", func(t *testing.T) {
   380  		rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   381  		// convert to encodable to easily modify snapshot
   382  		encodable := rootSnapshot.Encodable()
   383  		encodable.LatestResult.BlockID = unittest.IdentifierFixture()
   384  
   385  		bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   386  			assert.Error(t, err)
   387  		})
   388  	})
   389  
   390  	t.Run("seal doesn't match result", func(t *testing.T) {
   391  		rootSnapshot := unittest.RootSnapshotFixture(unittest.CompleteIdentitySet())
   392  		// convert to encodable to easily modify snapshot
   393  		encodable := rootSnapshot.Encodable()
   394  		encodable.LatestSeal.ResultID = unittest.IdentifierFixture()
   395  
   396  		bootstrap(t, rootSnapshot, func(state *bprotocol.State, err error) {
   397  			assert.Error(t, err)
   398  		})
   399  	})
   400  }
   401  
   402  // bootstraps protocol state with the given snapshot and invokes the callback
   403  // with the result of the constructor
   404  func bootstrap(t *testing.T, rootSnapshot protocol.Snapshot, f func(*bprotocol.State, error)) {
   405  	metrics := metrics.NewNoopCollector()
   406  	dir := unittest.TempDir(t)
   407  	defer os.RemoveAll(dir)
   408  	db := unittest.BadgerDB(t, dir)
   409  	defer db.Close()
   410  	headers, _, seals, _, _, blocks, setups, commits, statuses, results := storutil.StorageLayer(t, db)
   411  	state, err := bprotocol.Bootstrap(metrics, db, headers, seals, results, blocks, setups, commits, statuses, rootSnapshot)
   412  	f(state, err)
   413  }
   414  
   415  // snapshotAfter bootstraps the protocol state from the root snapshot, applies
   416  // the state-changing function f, clears the on-disk state, and returns a
   417  // memory-backed snapshot corresponding to that returned by f.
   418  //
   419  // This is used for generating valid snapshots to use when testing bootstrapping
   420  // from non-root states.
   421  func snapshotAfter(t *testing.T, rootSnapshot protocol.Snapshot, f func(*bprotocol.FollowerState) protocol.Snapshot) protocol.Snapshot {
   422  	var after protocol.Snapshot
   423  	protoutil.RunWithFollowerProtocolState(t, rootSnapshot, func(db *badger.DB, state *bprotocol.FollowerState) {
   424  		snap := f(state)
   425  		var err error
   426  		after, err = inmem.FromSnapshot(snap)
   427  		require.NoError(t, err)
   428  	})
   429  	return after
   430  }
   431  
   432  // buildBlock builds and marks valid the given block
   433  func buildBlock(t *testing.T, state protocol.MutableState, block *flow.Block) {
   434  	err := state.Extend(context.Background(), block)
   435  	require.NoError(t, err)
   436  	err = state.MarkValid(block.ID())
   437  	require.NoError(t, err)
   438  }
   439  
   440  // assertSealingSegmentBlocksQueryable bootstraps the state with the given
   441  // snapshot, then verifies that all sealing segment blocks are queryable.
   442  func assertSealingSegmentBlocksQueryableAfterBootstrap(t *testing.T, snapshot protocol.Snapshot) {
   443  	bootstrap(t, snapshot, func(state *bprotocol.State, err error) {
   444  		require.NoError(t, err)
   445  
   446  		segment, err := state.Final().SealingSegment()
   447  		assert.NoError(t, err)
   448  
   449  		// for each block in the sealing segment we should be able to query:
   450  		// * Head
   451  		// * SealedResult
   452  		// * Commit
   453  		for _, block := range segment.Blocks {
   454  			blockID := block.ID()
   455  			snap := state.AtBlockID(blockID)
   456  			header, err := snap.Head()
   457  			assert.NoError(t, err)
   458  			assert.Equal(t, blockID, header.ID())
   459  			_, seal, err := snap.SealedResult()
   460  			assert.NoError(t, err)
   461  			assert.Equal(t, segment.LatestSeals[blockID], seal.ID())
   462  			commit, err := snap.Commit()
   463  			assert.NoError(t, err)
   464  			assert.Equal(t, seal.FinalState, commit)
   465  		}
   466  		// for all blocks but the head, we should be unable to query SealingSegment:
   467  		for _, block := range segment.Blocks[:len(segment.Blocks)-1] {
   468  			snap := state.AtBlockID(block.ID())
   469  			_, err := snap.SealingSegment()
   470  			assert.ErrorIs(t, err, protocol.ErrSealingSegmentBelowRootBlock)
   471  		}
   472  	})
   473  }
   474  
   475  // BenchmarkFinal benchmarks retrieving the latest finalized block from storage.
   476  func BenchmarkFinal(b *testing.B) {
   477  	util.RunWithBootstrapState(b, unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()), func(db *badger.DB, state *bprotocol.State) {
   478  		b.ResetTimer()
   479  		for i := 0; i < b.N; i++ {
   480  			header, err := state.Final().Head()
   481  			assert.NoError(b, err)
   482  			assert.NotNil(b, header)
   483  		}
   484  	})
   485  }
   486  
   487  // BenchmarkFinal benchmarks retrieving the block by height from storage.
   488  func BenchmarkByHeight(b *testing.B) {
   489  	util.RunWithBootstrapState(b, unittest.RootSnapshotFixture(unittest.CompleteIdentitySet()), func(db *badger.DB, state *bprotocol.State) {
   490  		b.ResetTimer()
   491  		for i := 0; i < b.N; i++ {
   492  			header, err := state.AtHeight(0).Head()
   493  			assert.NoError(b, err)
   494  			assert.NotNil(b, header)
   495  		}
   496  	})
   497  }