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

     1  package forkchoice
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
    13  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks"
    14  	"github.com/koko1123/flow-go-1/consensus/hotstuff/forks/finalizer"
    15  	"github.com/koko1123/flow-go-1/consensus/hotstuff/mocks"
    16  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    17  	"github.com/koko1123/flow-go-1/model/flow"
    18  	mockfinalizer "github.com/koko1123/flow-go-1/module/mock"
    19  	"github.com/onflow/flow-go/crypto/hash"
    20  )
    21  
    22  // TestForks_Initialization tests that Forks correctly reports trusted Root
    23  func TestForks_Initialization(t *testing.T) {
    24  	forks, _, _, root := initForks(t, 1)
    25  
    26  	assert.Equal(t, forks.FinalizedView(), uint64(1))
    27  	assert.Equal(t, forks.FinalizedBlock(), root.Block)
    28  
    29  	assert.Equal(t, forks.GetBlocksForView(0), []*model.Block{})
    30  	assert.Equal(t, forks.GetBlocksForView(1), []*model.Block{root.Block})
    31  	assert.Equal(t, forks.GetBlocksForView(2), []*model.Block{})
    32  
    33  	b, found := forks.GetBlock(root.Block.BlockID)
    34  	assert.True(t, found, "Missing trusted Root ")
    35  	assert.Equal(t, root.Block, b)
    36  }
    37  
    38  // TestForks_AddBlock verifies that Block can be added
    39  func TestForks_AddBlock(t *testing.T) {
    40  	forks, _, notifier, root := initForks(t, 1)
    41  
    42  	block02 := makeBlock(2, root.QC, flow.ZeroID)
    43  	notifier.On("OnBlockIncorporated", block02).Return().Once()
    44  	err := forks.AddBlock(block02)
    45  	if err != nil {
    46  		assert.Fail(t, err.Error())
    47  	}
    48  	notifier.AssertExpectations(t)
    49  
    50  	assert.Equal(t, forks.GetBlocksForView(2), []*model.Block{block02})
    51  	b, found := forks.GetBlock(block02.BlockID)
    52  	assert.True(t, found)
    53  	assert.Equal(t, block02, b)
    54  }
    55  
    56  // TestForks_3ChainFinalization tests happy-path direct 3-chain finalization
    57  func TestForks_3ChainFinalization(t *testing.T) {
    58  	forks, finCallback, notifier, root := initForks(t, 1) // includes genesis block (v1)
    59  
    60  	block2 := makeBlock(2, root.QC, flow.ZeroID)
    61  	notifier.On("OnBlockIncorporated", block2).Return().Once()
    62  	addBlock2Forks(t, block2, forks)
    63  	notifier.AssertExpectations(t)
    64  
    65  	block3 := makeBlock(3, qc(block2.View, block2.BlockID), flow.ZeroID)
    66  	notifier.On("OnBlockIncorporated", block3).Return().Once()
    67  	notifier.On("OnQcIncorporated", block3.QC).Return().Once()
    68  	addBlock2Forks(t, block3, forks)
    69  	notifier.AssertExpectations(t)
    70  
    71  	// creates direct 3-chain on genesis block (1), which is already finalized
    72  	block4 := makeBlock(4, qc(block3.View, block3.BlockID), flow.ZeroID)
    73  	notifier.On("OnBlockIncorporated", block4).Return().Once()
    74  	notifier.On("OnQcIncorporated", block4.QC).Return().Once()
    75  	addBlock2Forks(t, block4, forks)
    76  	notifier.AssertExpectations(t)
    77  
    78  	// creates direct 3-chain on block (2) => finalize (2)
    79  	block5 := makeBlock(5, qc(block4.View, block4.BlockID), flow.ZeroID)
    80  	notifier.On("OnBlockIncorporated", block5).Return().Once()
    81  	notifier.On("OnQcIncorporated", block5.QC).Return().Once()
    82  	notifier.On("OnFinalizedBlock", block2).Return().Once()
    83  	finCallback.On("MakeFinal", block2.BlockID).Return(nil).Once()
    84  	finCallback.On("MakeValid", mock.Anything).Return(nil)
    85  	addBlock2Forks(t, block5, forks)
    86  	notifier.AssertExpectations(t)
    87  	finCallback.AssertExpectations(t)
    88  
    89  	// creates direct 3-chain on block (3) => finalize (3)
    90  	block6 := makeBlock(6, qc(block5.View, block5.BlockID), flow.ZeroID)
    91  	notifier.On("OnBlockIncorporated", block6).Return().Once()
    92  	notifier.On("OnQcIncorporated", block6.QC).Return().Once()
    93  	notifier.On("OnFinalizedBlock", block3).Return().Once()
    94  	finCallback.On("MakeFinal", block3.BlockID).Return(nil).Once()
    95  	finCallback.On("MakeValid", mock.Anything).Return(nil)
    96  	addBlock2Forks(t, block6, forks)
    97  	notifier.AssertExpectations(t)
    98  	finCallback.AssertExpectations(t)
    99  }
   100  
   101  func addBlock2Forks(t *testing.T, block *model.Block, forks hotstuff.Forks) {
   102  	err := forks.AddBlock(block)
   103  	if err != nil {
   104  		assert.Fail(t, err.Error())
   105  	}
   106  	verifyStored(t, block, forks)
   107  }
   108  
   109  // verifyStored verifies that block is stored in forks
   110  func verifyStored(t *testing.T, block *model.Block, forks hotstuff.Forks) {
   111  	b, found := forks.GetBlock(block.BlockID)
   112  	assert.True(t, found)
   113  	assert.Equal(t, block, b)
   114  
   115  	found = false
   116  	siblings := forks.GetBlocksForView(block.View)
   117  	assert.True(t, len(siblings) > 0)
   118  	for _, b := range forks.GetBlocksForView(block.View) {
   119  		if b != block {
   120  			continue
   121  		}
   122  		if found { // we already found block in slice, i.e. this is a duplicate
   123  			assert.Fail(t, fmt.Sprintf("Duplicate block: %v", block.BlockID))
   124  		}
   125  		found = true
   126  	}
   127  	assert.True(t, found, fmt.Sprintf("Did not find block: %v", block.BlockID))
   128  }
   129  
   130  func initForks(t *testing.T, view uint64) (*forks.Forks, *mockfinalizer.Finalizer, *mocks.Consumer, *forks.BlockQC) {
   131  	notifier := &mocks.Consumer{}
   132  	finalizationCallback := &mockfinalizer.Finalizer{}
   133  	finalizationCallback.On("MakeValid", mock.Anything).Return(nil)
   134  
   135  	// construct Finalizer
   136  	root := makeRootBlock(t, view)
   137  	notifier.On("OnBlockIncorporated", root.Block).Return().Once()
   138  	fnlzr, err := finalizer.New(root, finalizationCallback, notifier)
   139  	require.NoError(t, err)
   140  
   141  	// construct ForkChoice
   142  	notifier.On("OnQcIncorporated", root.QC).Return().Once()
   143  	fc, err := NewNewestForkChoice(fnlzr, notifier)
   144  	require.NoError(t, err)
   145  
   146  	notifier.AssertExpectations(t)
   147  	return forks.New(fnlzr, fc), finalizationCallback, notifier, root
   148  }
   149  
   150  func makeRootBlock(t *testing.T, view uint64) *forks.BlockQC {
   151  	// construct Finalizer with Genesis Block
   152  	genesisBlock := makeBlock(view, nil, flow.ZeroID)
   153  	genesisQC := qc(view, genesisBlock.BlockID)
   154  	root := forks.BlockQC{Block: genesisBlock, QC: genesisQC}
   155  	return &root
   156  }
   157  
   158  func qc(view uint64, id flow.Identifier) *flow.QuorumCertificate {
   159  	return &flow.QuorumCertificate{View: view, BlockID: id}
   160  }
   161  
   162  func makeBlock(blockView uint64, blockQC *flow.QuorumCertificate, payloadHash flow.Identifier) *model.Block {
   163  	if blockQC == nil {
   164  		blockQC = qc(0, flow.Identifier{})
   165  	}
   166  	id := computeID(blockView, blockQC, payloadHash)
   167  	return &model.Block{
   168  		BlockID:     id,
   169  		View:        blockView,
   170  		QC:          blockQC,
   171  		PayloadHash: payloadHash,
   172  	}
   173  }
   174  
   175  // computeID is an INCOMPLETE STUB needed so we can test Forks.
   176  // When computing the Block's ID, this implementation only considers
   177  // the fields used by Forks.
   178  // TODO need full implementation
   179  func computeID(view uint64, qc *flow.QuorumCertificate, payloadHash flow.Identifier) flow.Identifier {
   180  	id := make([]byte, 0)
   181  
   182  	viewBytes := make([]byte, 8)
   183  	binary.BigEndian.PutUint64(viewBytes, view)
   184  	id = append(id, viewBytes...)
   185  
   186  	qcView := make([]byte, 8)
   187  	binary.BigEndian.PutUint64(qcView, qc.View)
   188  	id = append(id, qcView...)
   189  	id = append(id, qc.BlockID[:]...)
   190  
   191  	id = append(id, payloadHash[:]...)
   192  
   193  	var h [hash.HashLenSHA3_256]byte
   194  	hash.ComputeSHA3_256(&h, id)
   195  
   196  	return flow.HashToID(h[:])
   197  }