github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/forks/forkchoice/newest_test.go (about)

     1  package forkchoice
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/mock"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	hs "github.com/koko1123/flow-go-1/consensus/hotstuff"
    11  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks"
    12  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer"
    13  	"github.com/koko1123/flow-go-1/consensus/hotstuff/mocks"
    14  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    15  	"github.com/koko1123/flow-go-1/model/flow"
    16  	mockfinalizer "github.com/koko1123/flow-go-1/module/mock"
    17  )
    18  
    19  type ViewPair struct {
    20  	qcView    uint64
    21  	blockView uint64
    22  }
    23  
    24  type Blocks struct {
    25  	blockMap  map[uint64]*model.Block
    26  	blockList []*model.Block
    27  }
    28  
    29  type QCs struct {
    30  	qcMap  map[uint64]*flow.QuorumCertificate
    31  	qcList []*flow.QuorumCertificate
    32  }
    33  
    34  // NOTATION:
    35  // [a, b] is a block at view "b" with a QC with view "a",
    36  // e.g., [1, 2] means a block at view "2" with an included  QC for view "1"
    37  
    38  // HAPPY PATH
    39  // As the leader of 6: we receive [1, 2], [2, 3], [3, 4], [4, 5] and receive enough votes to build QC for block 5.
    40  // the fork choice should return block and qc for 5
    41  func TestHappyPath(t *testing.T) {
    42  	curView := uint64(6)
    43  
    44  	f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1)
    45  
    46  	p1 := ViewPair{1, 2}
    47  	p2 := ViewPair{2, 3}
    48  	p3 := ViewPair{3, 4}
    49  	p4 := ViewPair{4, 5}
    50  
    51  	blocks := generateBlocks(root.QC, p1, p2, p3, p4)
    52  	for _, block := range blocks.blockList {
    53  		err := f.AddBlock(block)
    54  		require.NoError(t, err)
    55  	}
    56  
    57  	preferedQC := makeQC(5, blocks.blockMap[5].BlockID)
    58  	err := f.AddQC(preferedQC)
    59  	require.NoError(t, err)
    60  
    61  	// the fork choice should return block and qc for view 5
    62  	choiceQC, choiceBlock, err := f.MakeForkChoice(curView)
    63  	require.NoError(t, err)
    64  	require.Equal(t, preferedQC, choiceQC)
    65  	require.Equal(t, blocks.blockMap[choiceQC.View], choiceBlock)
    66  	notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC)
    67  }
    68  
    69  // NOT ENOUGH VOTES TO BUILD QC FOR LATEST FORK: fork with newest QC
    70  // As the leader of 6: we receive [1,2], [2,3], [3,4], [4,5] but we don't receive enough votes to build a qc for block 5.
    71  // the fork choice should return block and qc for 4
    72  func TestNoQcForLatestMainFork(t *testing.T) {
    73  	curView := uint64(6)
    74  
    75  	f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1)
    76  
    77  	p1 := ViewPair{1, 2}
    78  	p2 := ViewPair{2, 3}
    79  	p3 := ViewPair{3, 4}
    80  	p4 := ViewPair{4, 5}
    81  
    82  	blocks := generateBlocks(root.QC, p1, p2, p3, p4)
    83  	for _, block := range blocks.blockList {
    84  		err := f.AddBlock(block)
    85  		require.NoError(t, err)
    86  	}
    87  
    88  	// the fork choice should return block and qc for view 5
    89  	preferedQC := makeQC(4, blocks.blockMap[4].BlockID)
    90  	choiceQC, choiceBlock, err := f.MakeForkChoice(curView)
    91  	require.NoError(t, err)
    92  	require.Equal(t, preferedQC, choiceQC)
    93  	require.Equal(t, blocks.blockMap[preferedQC.View], choiceBlock)
    94  	notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC)
    95  }
    96  
    97  // NEWEST OVER LONGEST: HAPPY PATH: extend newest
    98  // As the leader of 7: we receive [1,2], [2,3], [3,4], [4,5], [2,6], and receive enough votes to build QC for block 6.
    99  // the fork choice should return block and qc for 6
   100  func TestNewestOverLongestHappyPath(t *testing.T) {
   101  	curView := uint64(7)
   102  
   103  	f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1)
   104  
   105  	p1 := ViewPair{1, 2}
   106  	p2 := ViewPair{2, 3}
   107  	p3 := ViewPair{3, 4}
   108  	p4 := ViewPair{4, 5}
   109  	p5 := ViewPair{2, 6}
   110  
   111  	blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5)
   112  	for _, block := range blocks.blockList {
   113  		err := f.AddBlock(block)
   114  		require.NoError(t, err)
   115  	}
   116  
   117  	preferedQC := makeQC(6, blocks.blockMap[6].BlockID)
   118  	err := f.AddQC(preferedQC)
   119  	require.NoError(t, err)
   120  
   121  	choiceQC, choiceBlock, err := f.MakeForkChoice(curView)
   122  	require.NoError(t, err)
   123  	require.Equal(t, preferedQC, choiceQC)
   124  	require.Equal(t, blocks.blockMap[preferedQC.View], choiceBlock)
   125  	notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC)
   126  }
   127  
   128  // NEWEST OVER LONGEST: NOT ENOUGH VOTES TO BUILD ON TOP OF NEWEST FORK: fork from newest
   129  // As the leader of 7: we receive [1,2], [2,3], [3,4], [4,5], [2,6], but we don't receive enough votes to build a qc for block 6.
   130  // the fork choice should return block and qc for 4
   131  func TestNewestOverLongestForkFromNewest(t *testing.T) {
   132  	curView := uint64(7)
   133  
   134  	f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1)
   135  
   136  	p1 := ViewPair{1, 2}
   137  	p2 := ViewPair{2, 3}
   138  	p3 := ViewPair{3, 4}
   139  	p4 := ViewPair{4, 5}
   140  	p5 := ViewPair{2, 6}
   141  
   142  	blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5)
   143  	for _, block := range blocks.blockList {
   144  		err := f.AddBlock(block)
   145  		require.NoError(t, err)
   146  	}
   147  
   148  	// the fork choice should return block and qc for view 4
   149  	preferedQC := makeQC(4, blocks.blockMap[4].BlockID)
   150  	notifier.On("OnForkChoiceGenerated", curView, preferedQC).Return().Once()
   151  	choiceQC, choiceBlock, err := f.MakeForkChoice(curView)
   152  	require.NoError(t, err)
   153  	require.Equal(t, preferedQC, choiceQC)
   154  	require.Equal(t, blocks.blockMap[preferedQC.View], choiceBlock)
   155  	notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC)
   156  }
   157  
   158  // FORK BELOW LOCKED: NOT ENOUGH VOTES TO BUILD ON TOP OF NEWEST FORK: fork from newest
   159  // As the leader of 8: we receive [1,2], [2,3], [3,4], [4,5], [2,6], [6,7], but we don't receive enough votes to build a qc for block 7.
   160  // the fork choice should return block and qc for 6
   161  func TestForkBelowLockedForkFromNewest(t *testing.T) {
   162  	curView := uint64(8)
   163  
   164  	f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1)
   165  
   166  	p1 := ViewPair{1, 2}
   167  	p2 := ViewPair{2, 3}
   168  	p3 := ViewPair{3, 4}
   169  	p4 := ViewPair{4, 5}
   170  	p5 := ViewPair{2, 6}
   171  	p6 := ViewPair{6, 7}
   172  
   173  	blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5, p6)
   174  	for _, block := range blocks.blockList {
   175  		err := f.AddBlock(block)
   176  		require.NoError(t, err)
   177  	}
   178  
   179  	// the fork choice should return block and qc for view 6
   180  	preferedQC := makeQC(6, blocks.blockMap[6].BlockID)
   181  	choiceQC, choiceBlock, err := f.MakeForkChoice(curView)
   182  	require.NoError(t, err)
   183  	require.Equal(t, preferedQC, choiceQC)
   184  	require.Equal(t, blocks.blockMap[choiceQC.View], choiceBlock)
   185  	notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC)
   186  }
   187  
   188  // FORK BELOW LOCKED: HAPPY PATH: extend newest
   189  // As the leader of 8: we receive [1,2], [2,3], [3,4], [4,5], [2,6], [6,7], and receive enough votes to build QC for block 7.
   190  // the fork choice should return block and qc for 7
   191  func TestForkBelowLockedHappyPath(t *testing.T) {
   192  	curView := uint64(8)
   193  
   194  	f, notifier, root := initNewestForkChoice(t, 1) // includes genesis block (v1)
   195  
   196  	p1 := ViewPair{1, 2}
   197  	p2 := ViewPair{2, 3}
   198  	p3 := ViewPair{3, 4}
   199  	p4 := ViewPair{4, 5}
   200  	p5 := ViewPair{2, 6}
   201  	p6 := ViewPair{6, 7}
   202  
   203  	blocks := generateBlocks(root.QC, p1, p2, p3, p4, p5, p6)
   204  	for _, block := range blocks.blockList {
   205  		err := f.AddBlock(block)
   206  		require.NoError(t, err)
   207  	}
   208  
   209  	preferedQC := makeQC(7, blocks.blockMap[7].BlockID)
   210  	err := f.AddQC(preferedQC)
   211  	require.NoError(t, err)
   212  
   213  	// the fork choice should return block and qc for view 7
   214  	choiceQC, choiceBlock, err := f.MakeForkChoice(curView)
   215  	require.NoError(t, err)
   216  	require.Equal(t, preferedQC, choiceQC)
   217  	require.Equal(t, blocks.blockMap[choiceQC.View], choiceBlock)
   218  	notifier.AssertCalled(t, "OnForkChoiceGenerated", curView, preferedQC)
   219  }
   220  
   221  // Verifies notification callbacks
   222  func TestOnQcIncorporated(t *testing.T) {
   223  	notifier := &mocks.Consumer{}
   224  	notifier.On("OnBlockIncorporated", mock.Anything).Return(nil)
   225  
   226  	finalizationCallback := &mockfinalizer.Finalizer{}
   227  	finalizationCallback.On("MakeFinal", mock.Anything).Return(nil)
   228  	finalizationCallback.On("MakeValid", mock.Anything).Return(nil)
   229  
   230  	// construct Finalizer
   231  	root := makeRootBlock(t, 1)
   232  	fnlzr, _ := finalizer.New(root, finalizationCallback, notifier)
   233  
   234  	// construct ForkChoice, it will trigger OnQcIncorporated
   235  	notifier.On("OnQcIncorporated", root.QC).Return(nil)
   236  	fc, _ := NewNewestForkChoice(fnlzr, notifier)
   237  	assert.NotNil(t, fc)
   238  
   239  	f := forks.New(fnlzr, fc)
   240  
   241  	p1 := ViewPair{1, 2}
   242  	p2 := ViewPair{2, 3}
   243  
   244  	blocks := generateBlocks(root.QC, p1, p2)
   245  
   246  	for _, block := range blocks.blockList {
   247  		// each call to AddBlock will trigger 'OnQcIncorporated'
   248  		notifier.On("OnQcIncorporated", block.QC).Return(nil)
   249  		err := f.AddBlock(block)
   250  		require.NoError(t, err)
   251  	}
   252  
   253  	preferedQC := makeQC(3, blocks.blockMap[3].BlockID)
   254  	// call to AddQC will trigger 'OnQcIncorporated'
   255  	notifier.On("OnQcIncorporated", preferedQC).Return(nil)
   256  	err := f.AddQC(preferedQC)
   257  	require.NoError(t, err)
   258  	notifier.AssertExpectations(t)
   259  }
   260  
   261  func generateBlocks(rootQC *flow.QuorumCertificate, viewPairs ...ViewPair) *Blocks {
   262  	blocks := &Blocks{
   263  		blockMap: make(map[uint64]*model.Block),
   264  	}
   265  	qcs := &QCs{
   266  		qcMap: make(map[uint64]*flow.QuorumCertificate),
   267  	}
   268  	var lastBlockView uint64
   269  	for _, viewPair := range viewPairs {
   270  		var qc *flow.QuorumCertificate
   271  		if viewPair.qcView == 1 {
   272  			qc = rootQC
   273  		} else {
   274  			existedQc, exists := qcs.qcMap[viewPair.qcView]
   275  			if !exists {
   276  				qc = makeQC(viewPair.qcView, blocks.blockMap[lastBlockView].BlockID)
   277  			} else {
   278  				qc = existedQc
   279  			}
   280  		}
   281  		qcs.AddQC(qc)
   282  
   283  		block := makeBlock(viewPair.blockView, qcs.qcMap[viewPair.qcView], flow.ZeroID)
   284  		lastBlockView = block.View
   285  		blocks.AddBlock(block)
   286  	}
   287  
   288  	return blocks
   289  }
   290  
   291  func initNewestForkChoice(t *testing.T, view uint64) (hs.Forks, *mocks.Consumer, *forks.BlockQC) {
   292  	notifier := &mocks.Consumer{}
   293  	notifier.On("OnBlockIncorporated", mock.Anything).Return(nil)
   294  	notifier.On("OnFinalizedBlock", mock.Anything).Return(nil)
   295  	notifier.On("OnForkChoiceGenerated", mock.Anything, mock.Anything).Return().Once()
   296  
   297  	finalizationCallback := &mockfinalizer.Finalizer{}
   298  	finalizationCallback.On("MakeFinal", mock.Anything).Return(nil)
   299  	finalizationCallback.On("MakeValid", mock.Anything).Return(nil)
   300  
   301  	// construct Finalizer
   302  	root := makeRootBlock(t, view)
   303  	fnlzr, _ := finalizer.New(root, finalizationCallback, notifier)
   304  
   305  	// construct ForkChoice
   306  	notifier.On("OnQcIncorporated", mock.Anything).Return(nil)
   307  	fc, _ := NewNewestForkChoice(fnlzr, notifier)
   308  
   309  	f := forks.New(fnlzr, fc)
   310  
   311  	return f, notifier, root
   312  }
   313  
   314  func makeQC(view uint64, blockID flow.Identifier) *flow.QuorumCertificate {
   315  	return &flow.QuorumCertificate{
   316  		View:    view,
   317  		BlockID: blockID,
   318  	}
   319  }
   320  
   321  func (b *Blocks) AddBlock(block *model.Block) {
   322  	b.blockMap[block.View] = block
   323  	b.blockList = append(b.blockList, block)
   324  }
   325  
   326  func (q *QCs) AddQC(qc *flow.QuorumCertificate) {
   327  	q.qcMap[qc.View] = qc
   328  	q.qcList = append(q.qcList, qc)
   329  }