github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/bootstrap/storage_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package bootstrap
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/MetalBlockchain/metalgo/database"
    14  	"github.com/MetalBlockchain/metalgo/database/memdb"
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/snow/choices"
    17  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    18  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman/snowmantest"
    19  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    20  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/block"
    21  	"github.com/MetalBlockchain/metalgo/snow/engine/snowman/bootstrap/interval"
    22  	"github.com/MetalBlockchain/metalgo/utils/logging"
    23  	"github.com/MetalBlockchain/metalgo/utils/set"
    24  )
    25  
    26  var _ block.Parser = testParser(nil)
    27  
    28  func TestGetMissingBlockIDs(t *testing.T) {
    29  	blocks := snowmantest.BuildChain(7)
    30  	parser := makeParser(blocks)
    31  
    32  	tests := []struct {
    33  		name               string
    34  		blocks             []snowman.Block
    35  		lastAcceptedHeight uint64
    36  		expected           set.Set[ids.ID]
    37  	}{
    38  		{
    39  			name:               "initially empty",
    40  			blocks:             nil,
    41  			lastAcceptedHeight: 0,
    42  			expected:           nil,
    43  		},
    44  		{
    45  			name:               "wants one block",
    46  			blocks:             []snowman.Block{blocks[4]},
    47  			lastAcceptedHeight: 0,
    48  			expected:           set.Of(blocks[3].ID()),
    49  		},
    50  		{
    51  			name:               "wants multiple blocks",
    52  			blocks:             []snowman.Block{blocks[2], blocks[4]},
    53  			lastAcceptedHeight: 0,
    54  			expected:           set.Of(blocks[1].ID(), blocks[3].ID()),
    55  		},
    56  		{
    57  			name:               "doesn't want last accepted block",
    58  			blocks:             []snowman.Block{blocks[1]},
    59  			lastAcceptedHeight: 0,
    60  			expected:           nil,
    61  		},
    62  		{
    63  			name:               "doesn't want known block",
    64  			blocks:             []snowman.Block{blocks[2], blocks[3]},
    65  			lastAcceptedHeight: 0,
    66  			expected:           set.Of(blocks[1].ID()),
    67  		},
    68  		{
    69  			name:               "doesn't want already accepted block",
    70  			blocks:             []snowman.Block{blocks[1]},
    71  			lastAcceptedHeight: 4,
    72  			expected:           nil,
    73  		},
    74  		{
    75  			name:               "doesn't underflow",
    76  			blocks:             []snowman.Block{blocks[0]},
    77  			lastAcceptedHeight: 0,
    78  			expected:           nil,
    79  		},
    80  	}
    81  	for _, test := range tests {
    82  		t.Run(test.name, func(t *testing.T) {
    83  			require := require.New(t)
    84  
    85  			db := memdb.New()
    86  			tree, err := interval.NewTree(db)
    87  			require.NoError(err)
    88  			for _, blk := range test.blocks {
    89  				_, err := interval.Add(db, tree, 0, blk.Height(), blk.Bytes())
    90  				require.NoError(err)
    91  			}
    92  
    93  			missingBlockIDs, err := getMissingBlockIDs(
    94  				context.Background(),
    95  				db,
    96  				parser,
    97  				tree,
    98  				test.lastAcceptedHeight,
    99  			)
   100  			require.NoError(err)
   101  			require.Equal(test.expected, missingBlockIDs)
   102  		})
   103  	}
   104  }
   105  
   106  func TestProcess(t *testing.T) {
   107  	blocks := snowmantest.BuildChain(7)
   108  
   109  	tests := []struct {
   110  		name                        string
   111  		initialBlocks               []snowman.Block
   112  		lastAcceptedHeight          uint64
   113  		missingBlockIDs             set.Set[ids.ID]
   114  		blk                         snowman.Block
   115  		ancestors                   map[ids.ID]snowman.Block
   116  		expectedParentID            ids.ID
   117  		expectedShouldFetchParentID bool
   118  		expectedMissingBlockIDs     set.Set[ids.ID]
   119  		expectedTrackedHeights      []uint64
   120  	}{
   121  		{
   122  			name:                        "add single block",
   123  			initialBlocks:               nil,
   124  			lastAcceptedHeight:          0,
   125  			missingBlockIDs:             set.Of(blocks[5].ID()),
   126  			blk:                         blocks[5],
   127  			ancestors:                   nil,
   128  			expectedParentID:            blocks[4].ID(),
   129  			expectedShouldFetchParentID: true,
   130  			expectedMissingBlockIDs:     set.Set[ids.ID]{},
   131  			expectedTrackedHeights:      []uint64{5},
   132  		},
   133  		{
   134  			name:               "add multiple blocks",
   135  			initialBlocks:      nil,
   136  			lastAcceptedHeight: 0,
   137  			missingBlockIDs:    set.Of(blocks[5].ID()),
   138  			blk:                blocks[5],
   139  			ancestors: map[ids.ID]snowman.Block{
   140  				blocks[4].ID(): blocks[4],
   141  			},
   142  			expectedParentID:            blocks[3].ID(),
   143  			expectedShouldFetchParentID: true,
   144  			expectedMissingBlockIDs:     set.Set[ids.ID]{},
   145  			expectedTrackedHeights:      []uint64{4, 5},
   146  		},
   147  		{
   148  			name:               "ignore non-consecutive blocks",
   149  			initialBlocks:      nil,
   150  			lastAcceptedHeight: 0,
   151  			missingBlockIDs:    set.Of(blocks[3].ID(), blocks[5].ID()),
   152  			blk:                blocks[5],
   153  			ancestors: map[ids.ID]snowman.Block{
   154  				blocks[3].ID(): blocks[3],
   155  			},
   156  			expectedParentID:            blocks[4].ID(),
   157  			expectedShouldFetchParentID: true,
   158  			expectedMissingBlockIDs:     set.Of(blocks[3].ID()),
   159  			expectedTrackedHeights:      []uint64{5},
   160  		},
   161  		{
   162  			name:                        "do not request the last accepted block",
   163  			initialBlocks:               nil,
   164  			lastAcceptedHeight:          2,
   165  			missingBlockIDs:             set.Of(blocks[3].ID()),
   166  			blk:                         blocks[3],
   167  			ancestors:                   nil,
   168  			expectedParentID:            ids.Empty,
   169  			expectedShouldFetchParentID: false,
   170  			expectedMissingBlockIDs:     set.Set[ids.ID]{},
   171  			expectedTrackedHeights:      []uint64{3},
   172  		},
   173  		{
   174  			name:                        "do not request already known block",
   175  			initialBlocks:               []snowman.Block{blocks[2]},
   176  			lastAcceptedHeight:          0,
   177  			missingBlockIDs:             set.Of(blocks[1].ID(), blocks[3].ID()),
   178  			blk:                         blocks[3],
   179  			ancestors:                   nil,
   180  			expectedParentID:            ids.Empty,
   181  			expectedShouldFetchParentID: false,
   182  			expectedMissingBlockIDs:     set.Of(blocks[1].ID()),
   183  			expectedTrackedHeights:      []uint64{2, 3},
   184  		},
   185  	}
   186  	for _, test := range tests {
   187  		t.Run(test.name, func(t *testing.T) {
   188  			require := require.New(t)
   189  
   190  			db := memdb.New()
   191  			tree, err := interval.NewTree(db)
   192  			require.NoError(err)
   193  			for _, blk := range test.initialBlocks {
   194  				_, err := interval.Add(db, tree, 0, blk.Height(), blk.Bytes())
   195  				require.NoError(err)
   196  			}
   197  
   198  			parentID, shouldFetchParentID, err := process(
   199  				db,
   200  				tree,
   201  				test.missingBlockIDs,
   202  				test.lastAcceptedHeight,
   203  				test.blk,
   204  				test.ancestors,
   205  			)
   206  			require.NoError(err)
   207  			require.Equal(test.expectedShouldFetchParentID, shouldFetchParentID)
   208  			require.Equal(test.expectedParentID, parentID)
   209  			require.Equal(test.expectedMissingBlockIDs, test.missingBlockIDs)
   210  
   211  			require.Equal(uint64(len(test.expectedTrackedHeights)), tree.Len())
   212  			for _, height := range test.expectedTrackedHeights {
   213  				require.True(tree.Contains(height))
   214  			}
   215  		})
   216  	}
   217  }
   218  
   219  func TestExecute(t *testing.T) {
   220  	const numBlocks = 7
   221  
   222  	unhalted := &common.Halter{}
   223  	halted := &common.Halter{}
   224  	halted.Halt(context.Background())
   225  
   226  	tests := []struct {
   227  		name                      string
   228  		haltable                  common.Haltable
   229  		lastAcceptedHeight        uint64
   230  		expectedProcessingHeights []uint64
   231  		expectedAcceptedHeights   []uint64
   232  	}{
   233  		{
   234  			name:                      "execute everything",
   235  			haltable:                  unhalted,
   236  			lastAcceptedHeight:        0,
   237  			expectedProcessingHeights: nil,
   238  			expectedAcceptedHeights:   []uint64{0, 1, 2, 3, 4, 5, 6},
   239  		},
   240  		{
   241  			name:                      "do not execute blocks accepted by height",
   242  			haltable:                  unhalted,
   243  			lastAcceptedHeight:        3,
   244  			expectedProcessingHeights: []uint64{1, 2, 3},
   245  			expectedAcceptedHeights:   []uint64{0, 4, 5, 6},
   246  		},
   247  		{
   248  			name:                      "do not execute blocks when halted",
   249  			haltable:                  halted,
   250  			lastAcceptedHeight:        0,
   251  			expectedProcessingHeights: []uint64{1, 2, 3, 4, 5, 6},
   252  			expectedAcceptedHeights:   []uint64{0},
   253  		},
   254  	}
   255  	for _, test := range tests {
   256  		t.Run(test.name, func(t *testing.T) {
   257  			require := require.New(t)
   258  
   259  			db := memdb.New()
   260  			tree, err := interval.NewTree(db)
   261  			require.NoError(err)
   262  
   263  			blocks := snowmantest.BuildChain(numBlocks)
   264  			parser := makeParser(blocks)
   265  			for _, blk := range blocks {
   266  				_, err := interval.Add(db, tree, 0, blk.Height(), blk.Bytes())
   267  				require.NoError(err)
   268  			}
   269  
   270  			require.NoError(execute(
   271  				context.Background(),
   272  				test.haltable,
   273  				logging.NoLog{}.Info,
   274  				db,
   275  				parser,
   276  				tree,
   277  				test.lastAcceptedHeight,
   278  			))
   279  			for _, height := range test.expectedProcessingHeights {
   280  				require.Equal(choices.Processing, blocks[height].Status())
   281  			}
   282  			for _, height := range test.expectedAcceptedHeights {
   283  				require.Equal(choices.Accepted, blocks[height].Status())
   284  			}
   285  
   286  			if test.haltable.Halted() {
   287  				return
   288  			}
   289  
   290  			size, err := database.Count(db)
   291  			require.NoError(err)
   292  			require.Zero(size)
   293  		})
   294  	}
   295  }
   296  
   297  type testParser func(context.Context, []byte) (snowman.Block, error)
   298  
   299  func (f testParser) ParseBlock(ctx context.Context, bytes []byte) (snowman.Block, error) {
   300  	return f(ctx, bytes)
   301  }
   302  
   303  func makeParser(blocks []*snowmantest.Block) block.Parser {
   304  	return testParser(func(_ context.Context, b []byte) (snowman.Block, error) {
   305  		for _, block := range blocks {
   306  			if bytes.Equal(b, block.Bytes()) {
   307  				return block, nil
   308  			}
   309  		}
   310  		return nil, database.ErrNotFound
   311  	})
   312  }