github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/scheme/rolldpos/rolldposctx_test.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package rolldpos
     7  
     8  import (
     9  	"context"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/facebookgo/clock"
    14  	"github.com/golang/protobuf/ptypes/timestamp"
    15  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    16  	"github.com/pkg/errors"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/iotexproject/iotex-core/action/protocol"
    20  	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
    21  	"github.com/iotexproject/iotex-core/blockchain/block"
    22  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    23  	"github.com/iotexproject/iotex-core/consensus/consensusfsm"
    24  	"github.com/iotexproject/iotex-core/db"
    25  	"github.com/iotexproject/iotex-core/endorsement"
    26  	"github.com/iotexproject/iotex-core/state"
    27  	"github.com/iotexproject/iotex-core/test/identityset"
    28  )
    29  
    30  var dummyCandidatesByHeightFunc = func(uint64) ([]string, error) { return nil, nil }
    31  
    32  func TestRollDPoSCtx(t *testing.T) {
    33  	require := require.New(t)
    34  	cfg := DefaultConfig
    35  	g := genesis.Default
    36  	dbConfig := db.DefaultConfig
    37  	dbConfig.DbPath = DefaultConfig.ConsensusDBPath
    38  	b, _, _, _, _ := makeChain(t)
    39  
    40  	t.Run("case 1:panic because of chain is nil", func(t *testing.T) {
    41  		_, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, nil, block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0)
    42  		require.Error(err)
    43  	})
    44  
    45  	t.Run("case 2:panic because of rp is nil", func(t *testing.T) {
    46  		_, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), nil, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0)
    47  		require.Error(err)
    48  	})
    49  
    50  	rp := rolldpos.NewProtocol(
    51  		genesis.Default.NumCandidateDelegates,
    52  		genesis.Default.NumDelegates,
    53  		genesis.Default.NumSubEpochs,
    54  	)
    55  	t.Run("case 3:panic because of clock is nil", func(t *testing.T) {
    56  		_, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, nil, 0)
    57  		require.Error(err)
    58  	})
    59  
    60  	c := clock.New()
    61  	cfg.FSM.AcceptBlockTTL = time.Second * 10
    62  	cfg.FSM.AcceptProposalEndorsementTTL = time.Second
    63  	cfg.FSM.AcceptLockEndorsementTTL = time.Second
    64  	cfg.FSM.CommitTTL = time.Second
    65  	t.Run("case 4:panic because of fsm time bigger than block interval", func(t *testing.T) {
    66  		_, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, 0)
    67  		require.Error(err)
    68  	})
    69  
    70  	g.Blockchain.BlockInterval = time.Second * 20
    71  	t.Run("case 5:panic because of nil CandidatesByHeight function", func(t *testing.T) {
    72  		_, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, nil, nil, "", nil, c, 0)
    73  		require.Error(err)
    74  	})
    75  
    76  	t.Run("case 6:normal", func(t *testing.T) {
    77  		bh := genesis.Default.BeringBlockHeight
    78  		rctx, err := NewRollDPoSCtx(consensusfsm.NewConsensusConfig(cfg.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, cfg.Delay), dbConfig, true, time.Second, true, NewChainManager(b), block.NewDeserializer(0), rp, nil, dummyCandidatesByHeightFunc, dummyCandidatesByHeightFunc, "", nil, c, bh)
    79  		require.NoError(err)
    80  		require.Equal(bh, rctx.RoundCalculator().beringHeight)
    81  		require.NotNil(rctx)
    82  	})
    83  }
    84  
    85  func TestCheckVoteEndorser(t *testing.T) {
    86  	require := require.New(t)
    87  	b, sf, _, rp, pp := makeChain(t)
    88  	c := clock.New()
    89  	g := genesis.Default
    90  	g.Blockchain.BlockInterval = time.Second * 20
    91  	delegatesByEpochFunc := func(epochnum uint64) ([]string, error) {
    92  		re := protocol.NewRegistry()
    93  		if err := rp.Register(re); err != nil {
    94  			return nil, err
    95  		}
    96  		tipHeight := b.TipHeight()
    97  		ctx := genesis.WithGenesisContext(
    98  			protocol.WithBlockchainCtx(
    99  				protocol.WithRegistry(context.Background(), re),
   100  				protocol.BlockchainCtx{
   101  					Tip: protocol.TipInfo{
   102  						Height: tipHeight,
   103  					},
   104  				},
   105  			), g)
   106  		tipEpochNum := rp.GetEpochNum(tipHeight)
   107  		var candidatesList state.CandidateList
   108  		var addrs []string
   109  		var err error
   110  		switch epochnum {
   111  		case tipEpochNum:
   112  			candidatesList, err = pp.Delegates(ctx, sf)
   113  		case tipEpochNum + 1:
   114  			candidatesList, err = pp.NextDelegates(ctx, sf)
   115  		default:
   116  			err = errors.Errorf("invalid epoch number %d compared to tip epoch number %d", epochnum, tipEpochNum)
   117  		}
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  		for _, cand := range candidatesList {
   122  			addrs = append(addrs, cand.Address)
   123  		}
   124  		return addrs, nil
   125  	}
   126  	rctx, err := NewRollDPoSCtx(
   127  		consensusfsm.NewConsensusConfig(DefaultConfig.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, DefaultConfig.Delay),
   128  		db.DefaultConfig,
   129  		true,
   130  		time.Second,
   131  		true,
   132  		NewChainManager(b),
   133  		block.NewDeserializer(0),
   134  		rp,
   135  		nil,
   136  		delegatesByEpochFunc,
   137  		delegatesByEpochFunc,
   138  		"",
   139  		nil,
   140  		c,
   141  		genesis.Default.BeringBlockHeight,
   142  	)
   143  	require.NoError(err)
   144  	require.NotNil(rctx)
   145  
   146  	// case 1:endorser nil caused panic
   147  	require.Panics(func() { rctx.CheckVoteEndorser(0, nil, nil) }, "")
   148  
   149  	// case 2:endorser address error
   150  	en := endorsement.NewEndorsement(time.Now(), identityset.PrivateKey(3).PublicKey(), nil)
   151  	require.Error(rctx.CheckVoteEndorser(51, nil, en))
   152  
   153  	// case 3:normal
   154  	en = endorsement.NewEndorsement(time.Now(), identityset.PrivateKey(10).PublicKey(), nil)
   155  	require.NoError(rctx.CheckVoteEndorser(51, nil, en))
   156  }
   157  
   158  func TestCheckBlockProposer(t *testing.T) {
   159  	require := require.New(t)
   160  	g := genesis.Default
   161  	b, sf, _, rp, pp := makeChain(t)
   162  	c := clock.New()
   163  	g.Blockchain.BlockInterval = time.Second * 20
   164  	delegatesByEpochFunc := func(epochnum uint64) ([]string, error) {
   165  		re := protocol.NewRegistry()
   166  		if err := rp.Register(re); err != nil {
   167  			return nil, err
   168  		}
   169  		tipHeight := b.TipHeight()
   170  		ctx := genesis.WithGenesisContext(
   171  			protocol.WithBlockchainCtx(
   172  				protocol.WithRegistry(context.Background(), re),
   173  				protocol.BlockchainCtx{
   174  					Tip: protocol.TipInfo{
   175  						Height: tipHeight,
   176  					},
   177  				},
   178  			), g)
   179  		tipEpochNum := rp.GetEpochNum(tipHeight)
   180  		var candidatesList state.CandidateList
   181  		var addrs []string
   182  		var err error
   183  		switch epochnum {
   184  		case tipEpochNum:
   185  			candidatesList, err = pp.Delegates(ctx, sf)
   186  		case tipEpochNum + 1:
   187  			candidatesList, err = pp.NextDelegates(ctx, sf)
   188  		default:
   189  			err = errors.Errorf("invalid epoch number %d compared to tip epoch number %d", epochnum, tipEpochNum)
   190  		}
   191  		if err != nil {
   192  			return nil, err
   193  		}
   194  		for _, cand := range candidatesList {
   195  			addrs = append(addrs, cand.Address)
   196  		}
   197  		return addrs, nil
   198  	}
   199  	rctx, err := NewRollDPoSCtx(
   200  		consensusfsm.NewConsensusConfig(DefaultConfig.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, DefaultConfig.Delay),
   201  		db.DefaultConfig,
   202  		true,
   203  		time.Second,
   204  		true,
   205  		NewChainManager(b),
   206  		block.NewDeserializer(0),
   207  		rp,
   208  		nil,
   209  		delegatesByEpochFunc,
   210  		delegatesByEpochFunc,
   211  		"",
   212  		nil,
   213  		c,
   214  		genesis.Default.BeringBlockHeight,
   215  	)
   216  	require.NoError(err)
   217  	require.NotNil(rctx)
   218  	block := getBlockforctx(t, 0, false)
   219  	en := endorsement.NewEndorsement(time.Unix(1596329600, 0), identityset.PrivateKey(10).PublicKey(), nil)
   220  	bp := newBlockProposal(&block, []*endorsement.Endorsement{en})
   221  
   222  	// case 1:panic caused by blockproposal is nil
   223  	require.Panics(func() {
   224  		rctx.CheckBlockProposer(51, nil, nil)
   225  	}, "blockproposal is nil")
   226  
   227  	// case 2:height != proposal.block.Height()
   228  	require.Error(rctx.CheckBlockProposer(1, bp, nil))
   229  
   230  	// case 3:panic caused by endorsement is nil
   231  	require.Panics(func() {
   232  		rctx.CheckBlockProposer(51, bp, nil)
   233  	}, "endorsement is nil")
   234  
   235  	// case 4:en's address is not proposer of the corresponding round
   236  	require.Error(rctx.CheckBlockProposer(51, bp, en))
   237  
   238  	// case 5:endorsor is not proposer of the corresponding round
   239  	en = endorsement.NewEndorsement(time.Unix(1596329600, 0), identityset.PrivateKey(22).PublicKey(), nil)
   240  	require.Error(rctx.CheckBlockProposer(51, bp, en))
   241  
   242  	// case 6:invalid block signature
   243  	block = getBlockforctx(t, 1, false)
   244  	en = endorsement.NewEndorsement(time.Unix(1596329600, 0), identityset.PrivateKey(1).PublicKey(), nil)
   245  	bp = newBlockProposal(&block, []*endorsement.Endorsement{en})
   246  	require.Error(rctx.CheckBlockProposer(51, bp, en))
   247  
   248  	// case 7:invalid endorsement for the vote when call AddVoteEndorsement
   249  	block = getBlockforctx(t, 1, true)
   250  	en = endorsement.NewEndorsement(time.Unix(1596329600, 0), identityset.PrivateKey(1).PublicKey(), nil)
   251  	en2 := endorsement.NewEndorsement(time.Unix(1596329600, 0), identityset.PrivateKey(7).PublicKey(), nil)
   252  	bp = newBlockProposal(&block, []*endorsement.Endorsement{en2, en})
   253  	require.Error(rctx.CheckBlockProposer(51, bp, en2))
   254  
   255  	// case 8:Insufficient endorsements
   256  	block = getBlockforctx(t, 1, true)
   257  	hash := block.HashBlock()
   258  	vote := NewConsensusVote(hash[:], COMMIT)
   259  	en2, err = endorsement.Endorse(identityset.PrivateKey(7), vote, time.Unix(1562382592, 0))
   260  	require.NoError(err)
   261  	bp = newBlockProposal(&block, []*endorsement.Endorsement{en2})
   262  	require.Error(rctx.CheckBlockProposer(51, bp, en2))
   263  
   264  	// case 9:normal
   265  	block = getBlockforctx(t, 1, true)
   266  	bp = newBlockProposal(&block, []*endorsement.Endorsement{en})
   267  	require.NoError(rctx.CheckBlockProposer(51, bp, en))
   268  }
   269  
   270  func TestNotProducingMultipleBlocks(t *testing.T) {
   271  	require := require.New(t)
   272  	b, sf, _, rp, pp := makeChain(t)
   273  	c := clock.New()
   274  	g := genesis.Default
   275  	g.Blockchain.BlockInterval = time.Second * 20
   276  	delegatesByEpoch := func(epochnum uint64) ([]string, error) {
   277  		re := protocol.NewRegistry()
   278  		if err := rp.Register(re); err != nil {
   279  			return nil, err
   280  		}
   281  		tipHeight := b.TipHeight()
   282  		ctx := genesis.WithGenesisContext(
   283  			protocol.WithBlockchainCtx(
   284  				protocol.WithRegistry(context.Background(), re),
   285  				protocol.BlockchainCtx{
   286  					Tip: protocol.TipInfo{
   287  						Height: tipHeight,
   288  					},
   289  				},
   290  			), g)
   291  		tipEpochNum := rp.GetEpochNum(tipHeight)
   292  		var candidatesList state.CandidateList
   293  		var addrs []string
   294  		var err error
   295  		switch epochnum {
   296  		case tipEpochNum:
   297  			candidatesList, err = pp.Delegates(ctx, sf)
   298  		case tipEpochNum + 1:
   299  			candidatesList, err = pp.NextDelegates(ctx, sf)
   300  		default:
   301  			err = errors.Errorf("invalid epoch number %d compared to tip epoch number %d", epochnum, tipEpochNum)
   302  		}
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  		for _, cand := range candidatesList {
   307  			addrs = append(addrs, cand.Address)
   308  		}
   309  		return addrs, nil
   310  	}
   311  	rctx, err := NewRollDPoSCtx(
   312  		consensusfsm.NewConsensusConfig(DefaultConfig.FSM, consensusfsm.DefaultDardanellesUpgradeConfig, g, DefaultConfig.Delay),
   313  		db.DefaultConfig,
   314  		true,
   315  		time.Second,
   316  		true,
   317  		NewChainManager(b),
   318  		block.NewDeserializer(0),
   319  		rp,
   320  		nil,
   321  		delegatesByEpoch,
   322  		delegatesByEpoch,
   323  		"",
   324  		identityset.PrivateKey(10),
   325  		c,
   326  		genesis.Default.BeringBlockHeight,
   327  	)
   328  	require.NoError(err)
   329  	require.NotNil(rctx)
   330  	require.NoError(rctx.Start(context.Background()))
   331  	defer rctx.Stop(context.Background())
   332  
   333  	res, err := rctx.Proposal()
   334  	require.NoError(err)
   335  	ecm, ok := res.(*EndorsedConsensusMessage)
   336  	require.True(ok)
   337  	hash1, err := ecm.Document().Hash()
   338  	require.NoError(err)
   339  	height1 := ecm.Height()
   340  
   341  	res2, err := rctx.Proposal()
   342  	require.NoError(err)
   343  	ecm2, ok := res2.(*EndorsedConsensusMessage)
   344  	require.True(ok)
   345  	hash2, err := ecm2.Document().Hash()
   346  	require.NoError(err)
   347  	require.Equal(hash1, hash2)
   348  	height2 := ecm2.Height()
   349  	require.Equal(height1, height2)
   350  }
   351  
   352  func getBlockforctx(t *testing.T, i int, sign bool) block.Block {
   353  	require := require.New(t)
   354  	ts := &timestamp.Timestamp{Seconds: 1596329600, Nanos: 10}
   355  	hcore := &iotextypes.BlockHeaderCore{
   356  		Version:          1,
   357  		Height:           51,
   358  		Timestamp:        ts,
   359  		PrevBlockHash:    []byte(""),
   360  		TxRoot:           []byte(""),
   361  		DeltaStateDigest: []byte(""),
   362  		ReceiptRoot:      []byte(""),
   363  	}
   364  	header := block.Header{}
   365  	protoHeader := &iotextypes.BlockHeader{Core: hcore, ProducerPubkey: identityset.PrivateKey(i).PublicKey().Bytes()}
   366  	require.NoError(header.LoadFromBlockHeaderProto(protoHeader))
   367  
   368  	if sign {
   369  		hash := header.HashHeaderCore()
   370  		sig, err := identityset.PrivateKey(i).Sign(hash[:])
   371  		require.NoError(err)
   372  		protoHeader = &iotextypes.BlockHeader{Core: hcore, ProducerPubkey: identityset.PrivateKey(i).PublicKey().Bytes(), Signature: sig}
   373  		require.NoError(header.LoadFromBlockHeaderProto(protoHeader))
   374  	}
   375  
   376  	b := block.Block{Header: header}
   377  	return b
   378  }