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 := ×tamp.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 }