Repository: pactus-project/pactus
Pull request #730 (2023-10-05 03:53:24)

fix: cache certificate by height
## Description

This PR implements a more accurate approach to caching the certificate by its height.
It also removes the `height` parameter from the `CommitBlock` function, as the certificate already contains the `height` of the committing block.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pactus-project/pactus/pull/730?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pactus-project) Report
> Merging [#730](https://app.codecov.io/gh/pactus-project/pactus/pull/730?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pactus-project) (1aa8c9e) into [main](https://app.codecov.io/gh/pactus-project/pactus/commit/a25efe24c5f40f197c74593363255cd503712ae3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pactus-project) (a25efe2) will **decrease** coverage by `0.10%`.
> Report is 1 commits behind head on main.
> The diff coverage is `84.61%`.


```diff
@@            Coverage Diff             @@
##             main     #730      +/-   ##
==========================================
- Coverage   83.05%   82.96%   -0.10%     
==========================================
  Files         172      172              
  Lines        8020     8024       +4     
==========================================
- Hits         6661     6657       -4     
- Misses       1043     1049       +6     
- Partials      316      318       +2     
```



Patch:
diff --git a/consensus/commit.go b/consensus/commit.go
index 88372858..4bb924bf 100644
--- a/consensus/commit.go
+++ b/consensus/commit.go
@@ -19,7 +19,7 @@ func (s *commitState) decide() {
 	precommits := s.log.PrecommitVoteSet(s.round)
 	votes := precommits.BlockVotes(certBlock.Hash())
 	cert := s.makeCertificate(votes)
-	err := s.state.CommitBlock(s.height, certBlock, cert)
+	err := s.state.CommitBlock(certBlock, cert)
 	if err != nil {
 		s.logger.Error("committing block failed", "block", certBlock, "error", err)
 	} else {
@@ -27,7 +27,7 @@ func (s *commitState) decide() {
 	}
 
 	// Now we can announce the committed block and certificate
-	s.announceNewBlock(s.height, certBlock, cert)
+	s.announceNewBlock(certBlock, cert)
 
 	s.enterNewState(s.newHeightState)
 }
diff --git a/consensus/consensus.go b/consensus/consensus.go
index d9493969..f70ff126 100644
--- a/consensus/consensus.go
+++ b/consensus/consensus.go
@@ -651,10 +651,10 @@ func (cs *consensus) broadcastVote(v *vote.Vote) {
 		message.NewVoteMessage(v))
 }
 
-func (cs *consensus) announceNewBlock(h uint32, b *block.Block, c *certificate.Certificate) {
+func (cs *consensus) announceNewBlock(blk *block.Block, cert *certificate.Certificate) {
 	go cs.mediator.OnBlockAnnounce(cs)
 	cs.broadcaster(cs.valKey.Address(),
-		message.NewBlockAnnounceMessage(h, b, c))
+		message.NewBlockAnnounceMessage(blk, cert))
 }
 
 func (cs *consensus) makeCertificate(votes map[crypto.Address]*vote.Vote) *certificate.Certificate {
diff --git a/state/facade.go b/state/facade.go
index 28e16627..b8bd7d5f 100644
--- a/state/facade.go
+++ b/state/facade.go
@@ -26,8 +26,8 @@ type Facade interface {
 	LastCertificate() *certificate.Certificate
 	UpdateLastCertificate(v *vote.Vote) error
 	ProposeBlock(valKey *bls.ValidatorKey, rewardAddr crypto.Address, round int16) (*block.Block, error)
-	ValidateBlock(block *block.Block) error
-	CommitBlock(height uint32, block *block.Block, cert *certificate.Certificate) error
+	ValidateBlock(blk *block.Block) error
+	CommitBlock(blk *block.Block, cert *certificate.Certificate) error
 	CommitteeValidators() []*validator.Validator
 	IsInCommittee(addr crypto.Address) bool
 	Proposer(round int16) *validator.Validator
diff --git a/state/lastinfo/last_info.go b/state/lastinfo/last_info.go
index fdcfe453..f907f471 100644
--- a/state/lastinfo/last_info.go
+++ b/state/lastinfo/last_info.go
@@ -19,7 +19,6 @@ import (
 type LastInfo struct {
 	lk sync.RWMutex // TODO: this lock looks unnecessary
 
-	lastBlockHeight   uint32
 	lastSortitionSeed sortition.VerifiableSeed
 	lastBlockHash     hash.Hash
 	lastCert          *certificate.Certificate
@@ -42,7 +41,11 @@ func (li *LastInfo) BlockHeight() uint32 {
 	li.lk.RLock()
 	defer li.lk.RUnlock()
 
-	return li.lastBlockHeight
+	if li.lastCert == nil {
+		return 0
+	}
+
+	return li.lastCert.Height()
 }
 
 func (li *LastInfo) BlockHash() hash.Hash {
@@ -80,13 +83,6 @@ func (li *LastInfo) UpdateSortitionSeed(lastSortitionSeed sortition.VerifiableSe
 	li.lastSortitionSeed = lastSortitionSeed
 }
 
-func (li *LastInfo) UpdateBlockHeight(lastBlockHeight uint32) {
-	li.lk.Lock()
-	defer li.lk.Unlock()
-
-	li.lastBlockHeight = lastBlockHeight
-}
-
 func (li *LastInfo) UpdateBlockHash(lastBlockHash hash.Hash) {
 	li.lk.Lock()
 	defer li.lk.Unlock()
@@ -116,11 +112,12 @@ func (li *LastInfo) UpdateValidators(vals []*validator.Validator) {
 }
 
 func (li *LastInfo) RestoreLastInfo(store store.Store, committeeSize int) (committee.Committee, error) {
-	height, cert := store.LastCertificate()
-	logger.Debug("try to restore last state info", "height", height)
-	sb, err := store.Block(height)
+	lastCert := store.LastCertificate()
+	lastHeight := lastCert.Height()
+	logger.Debug("try to restore last state info", "height", lastHeight)
+	sb, err := store.Block(lastHeight)
 	if err != nil {
-		return nil, fmt.Errorf("unable to retrieve block %v: %w", height, err)
+		return nil, fmt.Errorf("unable to retrieve block %v: %w", lastHeight, err)
 	}
 
 	lastBlock, err := sb.ToBlock()
@@ -128,8 +125,7 @@ func (li *LastInfo) RestoreLastInfo(store store.Store, committeeSize int) (commi
 		return nil, err
 	}
 
-	li.lastBlockHeight = height
-	li.lastCert = cert
+	li.lastCert = lastCert
 	li.lastSortitionSeed = lastBlock.Header().SortitionSeed()
 	li.lastBlockHash = lastBlock.Hash()
 	li.lastBlockTime = lastBlock.Header().Time()
diff --git a/state/mock.go b/state/mock.go
index 16126c01..6ec99a23 100644
--- a/state/mock.go
+++ b/state/mock.go
@@ -55,10 +55,10 @@ func MockingState(ts *testsuite.TestSuite) *MockState {
 
 func (m *MockState) CommitTestBlocks(num int) {
 	for i := 0; i < num; i++ {
-		blk := m.ts.GenerateTestBlock()
-		cert := m.ts.GenerateTestCertificate()
+		height := m.LastBlockHeight() + 1
+		blk, cert := m.ts.GenerateTestBlock(height)
 
-		m.TestStore.SaveBlock(m.LastBlockHeight()+1, blk, cert)
+		m.TestStore.SaveBlock(blk, cert)
 	}
 }
 
@@ -99,14 +99,14 @@ func (m *MockState) UpdateLastCertificate(_ *vote.Vote) error {
 	return nil
 }
 
-func (m *MockState) CommitBlock(h uint32, b *block.Block, cert *certificate.Certificate) error {
+func (m *MockState) CommitBlock(b *block.Block, cert *certificate.Certificate) error {
 	m.lk.Lock()
 	defer m.lk.Unlock()
 
-	if h != m.TestStore.LastHeight+1 {
+	if cert.Height() != m.TestStore.LastHeight+1 {
 		return fmt.Errorf("invalid height")
 	}
-	m.TestStore.SaveBlock(h, b, cert)
+	m.TestStore.SaveBlock(b, cert)
 	return nil
 }
 
@@ -115,8 +115,8 @@ func (m *MockState) Close() error {
 }
 
 func (m *MockState) ProposeBlock(valKey *bls.ValidatorKey, _ crypto.Address, _ int16) (*block.Block, error) {
-	b := m.ts.GenerateTestBlockWithProposer(valKey.Address())
-	return b, nil
+	blk, _ := m.ts.GenerateTestBlockWithProposer(m.TestStore.LastHeight, valKey.Address())
+	return blk, nil
 }
 
 func (m *MockState) ValidateBlock(_ *block.Block) error {
diff --git a/state/state.go b/state/state.go
index c6eb071e..da8cd463 100644
--- a/state/state.go
+++ b/state/state.go
@@ -361,33 +361,34 @@ func (st *state) ProposeBlock(valKey *bls.ValidatorKey, rewardAddr crypto.Addres
 	return block, nil
 }
 
-func (st *state) ValidateBlock(block *block.Block) error {
+func (st *state) ValidateBlock(blk *block.Block) error {
 	st.lk.Lock()
 	defer st.lk.Unlock()
 
-	if err := st.validateBlock(block); err != nil {
+	if err := st.validateBlock(blk); err != nil {
 		return err
 	}
 
-	t := block.Header().Time()
+	t := blk.Header().Time()
 	if err := st.validateBlockTime(t); err != nil {
 		return err
 	}
 
 	sb := st.concreteSandbox()
-	return st.executeBlock(block, sb)
+	return st.executeBlock(blk, sb)
 }
 
-func (st *state) CommitBlock(height uint32, block *block.Block, cert *certificate.Certificate) error {
+func (st *state) CommitBlock(blk *block.Block, cert *certificate.Certificate) error {
 	st.lk.Lock()
 	defer st.lk.Unlock()
 
+	height := cert.Height()
 	if height != st.lastInfo.BlockHeight()+1 {
 		st.logger.Debug("block is committed before", "height", height)
 		return nil
 	}
 
-	err := st.validateCertificate(cert, block.Hash())
+	err := st.validateCertificate(cert, blk.Hash())
 	if err != nil {
 		return err
 	}
@@ -399,26 +400,26 @@ func (st *state) CommitBlock(height uint32, block *block.Block, cert *certificat
 	// tries to commit them.
 	// We should never have a fork in our blockchain.
 	// But if it happens, here we can catch it.
-	if block.Header().PrevBlockHash() != st.lastInfo.BlockHash() {
+	if blk.Header().PrevBlockHash() != st.lastInfo.BlockHash() {
 		st.logger.Panic("a possible fork is detected",
 			"our hash", st.lastInfo.BlockHash(),
-			"block hash", block.Header().PrevBlockHash())
+			"block hash", blk.Header().PrevBlockHash())
 		return errors.Error(errors.ErrInvalidBlock)
 	}
 
-	err = st.validateBlock(block)
+	err = st.validateBlock(blk)
 	if err != nil {
 		return err
 	}
 
 	// Verify proposer
 	proposer := st.committee.Proposer(cert.Round())
-	if proposer.Address() != block.Header().ProposerAddress() {
+	if proposer.Address() != blk.Header().ProposerAddress() {
 		return errors.Errorf(errors.ErrInvalidBlock,
-			"invalid proposer, expected %s, got %s", proposer.Address(), block.Header().ProposerAddress())
+			"invalid proposer, expected %s, got %s", proposer.Address(), blk.Header().ProposerAddress())
 	}
 	// Validate sortition seed
-	seed := block.Header().SortitionSeed()
+	seed := blk.Header().SortitionSeed()
 	if !seed.Verify(proposer.PublicKey(), st.lastInfo.SortitionSeed()) {
 		return errors.Errorf(errors.ErrInvalidBlock, "invalid sortition seed")
 	}
@@ -426,26 +427,25 @@ func (st *state) CommitBlock(height uint32, block *block.Block, cert *certificat
 	// -----------------------------------
 	// Execute block
 	sb := st.concreteSandbox()
-	if err := st.executeBlock(block, sb); err != nil {
+	if err := st.executeBlock(blk, sb); err != nil {
 		return err
 	}
 
 	// -----------------------------------
 	// Commit block
-	st.lastInfo.UpdateBlockHeight(height)
-	st.lastInfo.UpdateBlockHash(block.Hash())
-	st.lastInfo.UpdateBlockTime(block.Header().Time())
-	st.lastInfo.UpdateSortitionSeed(block.Header().SortitionSeed())
+	st.lastInfo.UpdateBlockHash(blk.Hash())
+	st.lastInfo.UpdateBlockTime(blk.Header().Time())
+	st.lastInfo.UpdateSortitionSeed(blk.Header().SortitionSeed())
 	st.lastInfo.UpdateCertificate(cert)
 	st.lastInfo.UpdateValidators(st.committee.Validators())
 
 	// Commit and update the committee
 	st.commitSandbox(sb, cert.Round())
 
-	st.store.SaveBlock(height, block, cert)
+	st.store.SaveBlock(blk, cert)
 
 	// Remove transactions from pool
-	for _, trx := range block.Transactions() {
+	for _, trx := range blk.Transactions() {
 		st.txPool.RemoveTx(trx.ID())
 	}
 
@@ -453,7 +453,7 @@ func (st *state) CommitBlock(height uint32, block *block.Block, cert *certificat
 		st.logger.Panic("unable to update state", "error", err)
 	}
 
-	st.logger.Info("new block committed", "block", block, "round", cert.Round())
+	st.logger.Info("new block committed", "block", blk, "round", cert.Round())
 
 	st.evaluateSortition()
 
@@ -463,7 +463,7 @@ func (st *state) CommitBlock(height uint32, block *block.Block, cert *certificat
 
 	// -----------------------------------
 	// Publishing the events to the zmq
-	st.publishEvents(height, block)
+	st.publishEvents(height, blk)
 
 	return nil
 }
@@ -701,15 +701,15 @@ func (st *state) Params() param.Params {
 }
 
 // publishEvents publishes block related events.
-func (st *state) publishEvents(height uint32, block *block.Block) {
+func (st *state) publishEvents(height uint32, blk *block.Block) {
 	if st.eventCh == nil {
 		return
 	}
-	blockEvent := event.CreateBlockEvent(block.Hash(), height)
+	blockEvent := event.CreateBlockEvent(blk.Hash(), height)
 	st.eventCh <- blockEvent
 
-	for i := 1; i < block.Transactions().Len(); i++ {
-		tx := block.Transactions().Get(i)
+	for i := 1; i < blk.Transactions().Len(); i++ {
+		tx := blk.Transactions().Get(i)
 
 		accChangeEvent := event.CreateAccountChangeEvent(tx.Payload().Signer(), height)
 		st.eventCh <- accChangeEvent
diff --git a/state/validation.go b/state/validation.go
index 958f6953..b0ebca09 100644
--- a/state/validation.go
+++ b/state/validation.go
@@ -7,18 +7,18 @@ import (
 	"github.com/pactus-project/pactus/util/errors"
 )
 
-func (st *state) validateBlock(block *block.Block) error {
-	if block.Header().Version() != st.params.BlockVersion {
+func (st *state) validateBlock(blk *block.Block) error {
+	if blk.Header().Version() != st.params.BlockVersion {
 		return errors.Errorf(errors.ErrInvalidBlock,
 			"invalid version")
 	}
 
-	if block.Header().StateRoot() != st.stateRoot() {
+	if blk.Header().StateRoot() != st.stateRoot() {
 		return errors.Errorf(errors.ErrInvalidBlock,
-			"state root is not same as we expected, expected %v, got %v", st.stateRoot(), block.Header().StateRoot())
+			"state root is not same as we expected, expected %v, got %v", st.stateRoot(), blk.Header().StateRoot())
 	}
 
-	return st.validatePrevCertificate(block.PrevCertificate(), block.Header().PrevBlockHash())
+	return st.validatePrevCertificate(blk.PrevCertificate(), blk.Header().PrevBlockHash())
 }
 
 // validatePrevCertificate validates certificate for the previous block.
diff --git a/store/block.go b/store/block.go
index c879f01c..b20913f6 100644
--- a/store/block.go
+++ b/store/block.go
@@ -31,7 +31,7 @@ func newBlockStore(db *leveldb.DB) *blockStore {
 	}
 }
 
-func (bs *blockStore) saveBlock(batch *leveldb.Batch, height uint32, block *block.Block) []blockRegion {
+func (bs *blockStore) saveBlock(batch *leveldb.Batch, height uint32, blk *block.Block) []blockRegion {
 	if height > 1 {
 		if !bs.hasBlock(height - 1) {
 			logger.Panic("previous block not found", "height", height)
@@ -41,28 +41,28 @@ func (bs *blockStore) saveBlock(batch *leveldb.Batch, height uint32, block *bloc
 		logger.Panic("duplicated block", "height", height)
 	}
 
-	blockHash := block.Hash()
-	regs := make([]blockRegion, block.Transactions().Len())
-	w := bytes.NewBuffer(make([]byte, 0, block.SerializeSize()+hash.HashSize))
+	blockHash := blk.Hash()
+	regs := make([]blockRegion, blk.Transactions().Len())
+	w := bytes.NewBuffer(make([]byte, 0, blk.SerializeSize()+hash.HashSize))
 	err := encoding.WriteElement(w, &blockHash)
 	if err != nil {
 		panic(err) // Should we panic?
 	}
-	err = block.Header().Encode(w)
+	err = blk.Header().Encode(w)
 	if err != nil {
 		panic(err) // Should we panic?
 	}
-	if block.PrevCertificate() != nil {
-		err = block.PrevCertificate().Encode(w)
+	if blk.PrevCertificate() != nil {
+		err = blk.PrevCertificate().Encode(w)
 		if err != nil {
 			panic(err) // Should we panic?
 		}
 	}
-	err = encoding.WriteVarInt(w, uint64(block.Transactions().Len()))
+	err = encoding.WriteVarInt(w, uint64(blk.Transactions().Len()))
 	if err != nil {
 		panic(err) // Should we panic?
 	}
-	for i, trx := range block.Transactions() {
+	for i, trx := range blk.Transactions() {
 		offset := w.Len()
 		regs[i].height = height
 		regs[i].offset = uint32(offset)
diff --git a/store/interface.go b/store/interface.go
index 425c5118..88869586 100644
--- a/store/interface.go
+++ b/store/interface.go
@@ -94,7 +94,7 @@ type Reader interface {
 	IterateValidators(consumer func(*validator.Validator) (stop bool))
 	IterateAccounts(consumer func(crypto.Address, *account.Account) (stop bool))
 	TotalValidators() int32
-	LastCertificate() (uint32, *certificate.Certificate)
+	LastCertificate() *certificate.Certificate
 }
 
 type Store interface {
@@ -102,7 +102,7 @@ type Store interface {
 
 	UpdateAccount(addr crypto.Address, acc *account.Account)
 	UpdateValidator(val *validator.Validator)
-	SaveBlock(height uint32, block *block.Block, cert *certificate.Certificate)
+	SaveBlock(blk *block.Block, cert *certificate.Certificate)
 	WriteBatch() error
 	Close() error
 }
diff --git a/store/mock.go b/store/mock.go
index 735a692e..9dbff261 100644
--- a/store/mock.go
+++ b/store/mock.go
@@ -203,17 +203,17 @@ func (m *MockStore) IterateValidators(consumer func(*validator.Validator) (stop
 	}
 }
 
-func (m *MockStore) SaveBlock(height uint32, b *block.Block, cert *certificate.Certificate) {
-	m.Blocks[height] = b
-	m.LastHeight = height
+func (m *MockStore) SaveBlock(b *block.Block, cert *certificate.Certificate) {
+	m.Blocks[cert.Height()] = b
+	m.LastHeight = cert.Height()
 	m.LastCert = cert
 }
 
-func (m *MockStore) LastCertificate() (uint32, *certificate.Certificate) {
+func (m *MockStore) LastCertificate() *certificate.Certificate {
 	if m.LastHeight == 0 {
-		return 0, nil
+		return nil
 	}
-	return m.LastHeight, m.LastCert
+	return m.LastCert
 }
 
 func (m *MockStore) WriteBatch() error {
@@ -233,9 +233,8 @@ func (m *MockStore) AddTestAccount() (*account.Account, crypto.Address) {
 }
 
 func (m *MockStore) AddTestBlock(height uint32) *block.Block {
-	blk := m.ts.GenerateTestBlock()
-	cert := m.ts.GenerateTestCertificate()
-	m.SaveBlock(height, blk, cert)
+	blk, cert := m.ts.GenerateTestBlock(height)
+	m.SaveBlock(blk, cert)
 	return blk
 }
 
diff --git a/store/store.go b/store/store.go
index f38d3b1f..812f22d8 100644
--- a/store/store.go
+++ b/store/store.go
@@ -89,18 +89,19 @@ func (s *store) Close() error {
 	return s.db.Close()
 }
 
-func (s *store) SaveBlock(height uint32, block *block.Block, cert *certificate.Certificate) {
+func (s *store) SaveBlock(blk *block.Block, cert *certificate.Certificate) {
 	s.lk.Lock()
 	defer s.lk.Unlock()
 
-	reg := s.blockStore.saveBlock(s.batch, height, block)
-	for i, trx := range block.Transactions() {
+	height := cert.Height()
+	reg := s.blockStore.saveBlock(s.batch, height, blk)
+	for i, trx := range blk.Transactions() {
 		s.txStore.saveTx(s.batch, trx.ID(), &reg[i])
 	}
 
-	// Save last certificate
-	w := bytes.NewBuffer(make([]byte, 0, 8+cert.SerializeSize()))
-	err := encoding.WriteElements(w, lastStoreVersion, height)
+	// Save last certificate: [version: 4 bytes]+[certificate: variant]
+	w := bytes.NewBuffer(make([]byte, 0, 4+cert.SerializeSize()))
+	err := encoding.WriteElements(w, lastStoreVersion)
 	if err != nil {
 		panic(err)
 	}
@@ -289,28 +290,27 @@ func (s *store) UpdateValidator(acc *validator.Validator) {
 	s.validatorStore.updateValidator(s.batch, acc)
 }
 
-func (s *store) LastCertificate() (uint32, *certificate.Certificate) {
+func (s *store) LastCertificate() *certificate.Certificate {
 	s.lk.Lock()
 	defer s.lk.Unlock()
 
 	data, _ := tryGet(s.db, lastInfoKey)
 	if data == nil {
 		// Genesis block
-		return 0, nil
+		return nil
 	}
 	r := bytes.NewReader(data)
 	version := int32(0)
-	height := uint32(0)
 	cert := new(certificate.Certificate)
-	err := encoding.ReadElements(r, &version, &height)
+	err := encoding.ReadElements(r, &version)
 	if err != nil {
-		return 0, nil
+		return nil
 	}
 	err = cert.Decode(r)
 	if err != nil {
-		return 0, nil
+		return nil
 	}
-	return height, cert
+	return cert
 }
 
 func (s *store) WriteBatch() error {
diff --git a/sync/bundle/message/block_announce.go b/sync/bundle/message/block_announce.go
index a44a4b36..054df712 100644
--- a/sync/bundle/message/block_announce.go
+++ b/sync/bundle/message/block_announce.go
@@ -8,16 +8,14 @@ import (
 )
 
 type BlockAnnounceMessage struct {
-	Height      uint32                   `cbor:"1,keyasint"`
-	Block       *block.Block             `cbor:"2,keyasint"`
-	Certificate *certificate.Certificate `cbor:"3,keyasint"`
+	Block       *block.Block             `cbor:"1,keyasint"`
+	Certificate *certificate.Certificate `cbor:"2,keyasint"`
 }
 
-func NewBlockAnnounceMessage(h uint32, b *block.Block, c *certificate.Certificate) *BlockAnnounceMessage {
+func NewBlockAnnounceMessage(blk *block.Block, cert *certificate.Certificate) *BlockAnnounceMessage {
 	return &BlockAnnounceMessage{
-		Height:      h,
-		Block:       b,
-		Certificate: c,
+		Block:       blk,
+		Certificate: cert,
 	}
 }
 
@@ -28,10 +26,16 @@ func (m *BlockAnnounceMessage) BasicCheck() error {
 	return m.Certificate.BasicCheck()
 }
 
+func (m *BlockAnnounceMessage) Height() uint32 {
+	return m.Certificate.Height()
+}
+
 func (m *BlockAnnounceMessage) Type() Type {
 	return TypeBlockAnnounce
 }
 
 func (m *BlockAnnounceMessage) String() string {
-	return fmt.Sprintf("{⌘ %d %v}", m.Height, m.Block.Hash().ShortString())
+	return fmt.Sprintf("{⌘ %d %v}",
+		m.Certificate.Height(),
+		m.Block.Hash().ShortString())
 }
diff --git a/sync/bundle/message/blocks_response.go b/sync/bundle/message/blocks_response.go
index 5188ed88..ab3d1e37 100644
--- a/sync/bundle/message/blocks_response.go
+++ b/sync/bundle/message/blocks_response.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 
 	"github.com/pactus-project/pactus/types/certificate"
-	"github.com/pactus-project/pactus/util/errors"
 )
 
 type BlocksResponseMessage struct {
@@ -30,9 +29,6 @@ func NewBlocksResponseMessage(code ResponseCode, reason string, sid int, from ui
 }
 
 func (m *BlocksResponseMessage) BasicCheck() error {
-	if m.From == 0 && len(m.CommittedBlocksData) != 0 {
-		return errors.Errorf(errors.ErrInvalidHeight, "unexpected block for height zero")
-	}
 	if m.LastCertificate != nil {
 		if err := m.LastCertificate.BasicCheck(); err != nil {
 			return err
@@ -58,13 +54,6 @@ func (m *BlocksResponseMessage) To() uint32 {
 	return m.From + m.Count() - 1
 }
 
-func (m *BlocksResponseMessage) LastCertificateHeight() uint32 {
-	if m.LastCertificate != nil {
-		return m.From
-	}
-	return 0
-}
-
 func (m *BlocksResponseMessage) String() string {
 	return fmt.Sprintf("{⚓ %d %s %v-%v}", m.SessionID, m.ResponseCode, m.From, m.To())
 }
diff --git a/sync/cache/cache.go b/sync/cache/cache.go
index 5a2bc8b9..09d2820b 100644
--- a/sync/cache/cache.go
+++ b/sync/cache/cache.go
@@ -42,9 +42,14 @@ func (c *Cache) GetBlock(height uint32) *block.Block {
 	return nil
 }
 
-func (c *Cache) AddBlock(height uint32, block *block.Block) {
-	c.blocks.Add(height, block)
-	c.AddCertificate(height-1, block.PrevCertificate())
+func (c *Cache) AddBlock(blk *block.Block) {
+	prvCert := blk.PrevCertificate()
+	if prvCert == nil {
+		c.blocks.Add(1, blk)
+	} else {
+		c.blocks.Add(prvCert.Height()+1, blk)
+		c.certs.Add(prvCert.Height(), prvCert)
+	}
 }
 
 func (c *Cache) GetCertificate(height uint32) *certificate.Certificate {
@@ -56,9 +61,9 @@ func (c *Cache) GetCertificate(height uint32) *certificate.Certificate {
 	return nil
 }
 
-func (c *Cache) AddCertificate(height uint32, cert *certificate.Certificate) {
+func (c *Cache) AddCertificate(cert *certificate.Certificate) {
 	if cert != nil {
-		c.certs.Add(height, cert)
+		c.certs.Add(cert.Height(), cert)
 	}
 }
 
diff --git a/sync/handler_block_announce.go b/sync/handler_block_announce.go
index 16de02b6..fe066f85 100644
--- a/sync/handler_block_announce.go
+++ b/sync/handler_block_announce.go
@@ -20,8 +20,8 @@ func (handler *blockAnnounceHandler) ParseMessage(m message.Message, initiator p
 	msg := m.(*message.BlockAnnounceMessage)
 	handler.logger.Trace("parsing BlockAnnounce message", "message", msg)
 
-	handler.cache.AddCertificate(msg.Height, msg.Certificate)
-	handler.cache.AddBlock(msg.Height, msg.Block)
+	handler.cache.AddCertificate(msg.Certificate)
+	handler.cache.AddBlock(msg.Block)
 
 	err := handler.tryCommitBlocks()
 	if err != nil {
@@ -29,7 +29,7 @@ func (handler *blockAnnounceHandler) ParseMessage(m message.Message, initiator p
 	}
 	handler.moveConsensusToNewHeight()
 
-	handler.peerSet.UpdateHeight(initiator, msg.Height, msg.Block.Hash())
+	handler.peerSet.UpdateHeight(initiator, msg.Height(), msg.Block.Hash())
 	handler.updateBlockchain()
 
 	return nil
diff --git a/sync/handler_blocks_response.go b/sync/handler_blocks_response.go
index a810497b..cc772915 100644
--- a/sync/handler_blocks_response.go
+++ b/sync/handler_blocks_response.go
@@ -24,16 +24,21 @@ func (handler *blocksResponseHandler) ParseMessage(m message.Message, initiator
 	if msg.IsRequestRejected() {
 		handler.logger.Warn("blocks request is rejected", "pid", initiator.ShortString(), "reason", msg.Reason)
 	} else {
+		// TODO:
+		// It is good to check the latest height before adding blocks to the cache.
+		// If they have already been committed, this message can be ignored.
+		// Need to test!
 		height := msg.From
 		for _, data := range msg.CommittedBlocksData {
 			blk, err := block.FromBytes(data)
 			if err != nil {
 				return err
 			}
-			handler.cache.AddBlock(height, blk)
+			handler.cache.AddBlock(blk)
+
 			height++
 		}
-		handler.cache.AddCertificate(msg.From, msg.LastCertificate)
+		handler.cache.AddCertificate(msg.LastCertificate)
 		err := handler.tryCommitBlocks()
 		if err != nil {
 			return err
diff --git a/sync/sync.go b/sync/sync.go
index f6a1b525..d391d607 100644
--- a/sync/sync.go
+++ b/sync/sync.go
@@ -428,7 +428,7 @@ func (sync *synchronizer) tryCommitBlocks() error {
 		}
 
 		sync.logger.Trace("committing block", "height", height, "block", blk)
-		if err := sync.state.CommitBlock(height, blk, cert); err != nil {
+		if err := sync.state.CommitBlock(blk, cert); err != nil {
 			return err
 		}
 		height = height + 1
diff --git a/types/proposal/proposal.go b/types/proposal/proposal.go
index 7eaea69c..5b10938c 100644
--- a/types/proposal/proposal.go
+++ b/types/proposal/proposal.go
@@ -22,12 +22,12 @@ type proposalData struct {
 	Signature *bls.Signature `cbor:"4,keyasint"`
 }
 
-func NewProposal(height uint32, round int16, block *block.Block) *Proposal {
+func NewProposal(height uint32, round int16, blk *block.Block) *Proposal {
 	return &Proposal{
 		data: proposalData{
 			Height: height,
 			Round:  round,
-			Block:  block,
+			Block:  blk,
 		},
 	}
 }


Test patch:
diff --git a/consensus/consensus_test.go b/consensus/consensus_test.go
index fb6c7acc..1ba03713 100644
--- a/consensus/consensus_test.go
+++ b/consensus/consensus_test.go
@@ -323,18 +323,18 @@ func (td *testData) commitBlockForAllStates(t *testing.T) (*block.Block, *certif
 
 	sig := bls.SignatureAggregate(sig1, sig2, sig4)
 	cert := certificate.NewCertificate(height+1, 0, []int32{0, 1, 2, 3}, []int32{2}, sig)
-	block := p.Block()
+	blk := p.Block()
 
-	err = td.consX.state.CommitBlock(height+1, block, cert)
+	err = td.consX.state.CommitBlock(blk, cert)
 	assert.NoError(t, err)
-	err = td.consY.state.CommitBlock(height+1, block, cert)
+	err = td.consY.state.CommitBlock(blk, cert)
 	assert.NoError(t, err)
-	err = td.consB.state.CommitBlock(height+1, block, cert)
+	err = td.consB.state.CommitBlock(blk, cert)
 	assert.NoError(t, err)
-	err = td.consP.state.CommitBlock(height+1, block, cert)
+	err = td.consP.state.CommitBlock(blk, cert)
 	assert.NoError(t, err)
 
-	return block, cert
+	return blk, cert
 }
 
 func (td *testData) makeProposal(t *testing.T, height uint32, round int16) *proposal.Proposal {
@@ -619,17 +619,15 @@ func TestNonActiveValidator(t *testing.T) {
 
 	t.Run("non-active instances should move to new height", func(t *testing.T) {
 		b1, cert1 := td.commitBlockForAllStates(t)
-		b2, cert2 := td.commitBlockForAllStates(t)
 
 		nonActiveCons.MoveToNewHeight()
 		td.checkHeightRound(t, nonActiveCons, 1, 0)
 
-		assert.NoError(t, nonActiveCons.state.CommitBlock(1, b1, cert1))
-		assert.NoError(t, nonActiveCons.state.CommitBlock(2, b2, cert2))
+		assert.NoError(t, nonActiveCons.state.CommitBlock(b1, cert1))
 
 		nonActiveCons.MoveToNewHeight()
 		td.newHeightTimeout(nonActiveCons)
-		td.checkHeightRound(t, nonActiveCons, 3, 0)
+		td.checkHeightRound(t, nonActiveCons, 2, 0)
 	})
 }
 
diff --git a/consensus/cp_test.go b/consensus/cp_test.go
index 37d7109f..ce08adfe 100644
--- a/consensus/cp_test.go
+++ b/consensus/cp_test.go
@@ -201,7 +201,7 @@ func TestInvalidJustInitZero(t *testing.T) {
 	h := uint32(1)
 	r := int16(0)
 	just := &vote.JustInitZero{
-		QCert: td.GenerateTestCertificate(),
+		QCert: td.GenerateTestCertificate(h),
 	}
 
 	t.Run("invalid value: one", func(t *testing.T) {
@@ -230,7 +230,7 @@ func TestInvalidJustInitZero(t *testing.T) {
 		err := td.consX.checkJust(v)
 		assert.ErrorIs(t, err, invalidJustificationError{
 			JustType: just.Type(),
-			Reason:   fmt.Sprintf("certificate height is invalid (expected 1 got %v)", just.QCert.Height()),
+			Reason:   fmt.Sprintf("certificate has an unexpected committers: %v", just.QCert.Committers()),
 		})
 	})
 }
@@ -242,7 +242,7 @@ func TestInvalidJustPreVoteHard(t *testing.T) {
 	h := uint32(1)
 	r := int16(0)
 	just := &vote.JustPreVoteHard{
-		QCert: td.GenerateTestCertificate(),
+		QCert: td.GenerateTestCertificate(h),
 	}
 
 	t.Run("invalid value: abstain", func(t *testing.T) {
@@ -271,7 +271,7 @@ func TestInvalidJustPreVoteHard(t *testing.T) {
 		err := td.consX.checkJust(v)
 		assert.ErrorIs(t, err, invalidJustificationError{
 			JustType: just.Type(),
-			Reason:   fmt.Sprintf("certificate height is invalid (expected 1 got %v)", just.QCert.Height()),
+			Reason:   fmt.Sprintf("certificate has an unexpected committers: %v", just.QCert.Committers()),
 		})
 	})
 }
@@ -283,7 +283,7 @@ func TestInvalidJustPreVoteSoft(t *testing.T) {
 	h := uint32(1)
 	r := int16(0)
 	just := &vote.JustPreVoteSoft{
-		QCert: td.GenerateTestCertificate(),
+		QCert: td.GenerateTestCertificate(h),
 	}
 
 	t.Run("invalid value: abstain", func(t *testing.T) {
@@ -312,7 +312,7 @@ func TestInvalidJustPreVoteSoft(t *testing.T) {
 		err := td.consX.checkJust(v)
 		assert.ErrorIs(t, err, invalidJustificationError{
 			JustType: just.Type(),
-			Reason:   fmt.Sprintf("certificate height is invalid (expected 1 got %v)", just.QCert.Height()),
+			Reason:   fmt.Sprintf("certificate has an unexpected committers: %v", just.QCert.Committers()),
 		})
 	})
 }
@@ -324,7 +324,7 @@ func TestInvalidJustMainVoteNoConflict(t *testing.T) {
 	h := uint32(1)
 	r := int16(0)
 	just := &vote.JustMainVoteNoConflict{
-		QCert: td.GenerateTestCertificate(),
+		QCert: td.GenerateTestCertificate(h),
 	}
 
 	t.Run("invalid value: abstain", func(t *testing.T) {
@@ -343,7 +343,7 @@ func TestInvalidJustMainVoteNoConflict(t *testing.T) {
 		err := td.consX.checkJust(v)
 		assert.ErrorIs(t, err, invalidJustificationError{
 			JustType: just.Type(),
-			Reason:   fmt.Sprintf("certificate height is invalid (expected 1 got %v)", just.QCert.Height()),
+			Reason:   fmt.Sprintf("certificate has an unexpected committers: %v", just.QCert.Committers()),
 		})
 	})
 }
@@ -358,7 +358,7 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 	t.Run("invalid value: zero", func(t *testing.T) {
 		just := &vote.JustMainVoteConflict{
 			Just0: &vote.JustInitZero{
-				QCert: td.GenerateTestCertificate(),
+				QCert: td.GenerateTestCertificate(h),
 			},
 			Just1: &vote.JustInitOne{},
 		}
@@ -374,7 +374,7 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 	t.Run("invalid value: one", func(t *testing.T) {
 		just := &vote.JustMainVoteConflict{
 			Just0: &vote.JustInitZero{
-				QCert: td.GenerateTestCertificate(),
+				QCert: td.GenerateTestCertificate(h),
 			},
 			Just1: &vote.JustInitOne{},
 		}
@@ -390,7 +390,7 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 	t.Run("invalid value: unexpected justification (just0)", func(t *testing.T) {
 		just := &vote.JustMainVoteConflict{
 			Just0: &vote.JustPreVoteSoft{
-				QCert: td.GenerateTestCertificate(),
+				QCert: td.GenerateTestCertificate(h),
 			},
 			Just1: &vote.JustInitOne{},
 		}
@@ -406,10 +406,10 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 	t.Run("invalid value: unexpected justification", func(t *testing.T) {
 		just := &vote.JustMainVoteConflict{
 			Just0: &vote.JustInitZero{
-				QCert: td.GenerateTestCertificate(),
+				QCert: td.GenerateTestCertificate(h),
 			},
 			Just1: &vote.JustPreVoteSoft{
-				QCert: td.GenerateTestCertificate(),
+				QCert: td.GenerateTestCertificate(h),
 			},
 		}
 		v := vote.NewCPMainVote(td.RandHash(), h, r, 1, vote.CPValueAbstain, just, td.valKeys[tIndexB].Address())
@@ -423,7 +423,7 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 
 	t.Run("invalid certificate", func(t *testing.T) {
 		just0 := &vote.JustInitZero{
-			QCert: td.GenerateTestCertificate(),
+			QCert: td.GenerateTestCertificate(h),
 		}
 		just := &vote.JustMainVoteConflict{
 			Just0: just0,
@@ -434,18 +434,18 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 		err := td.consX.checkJust(v)
 		assert.ErrorIs(t, err, invalidJustificationError{
 			JustType: just0.Type(),
-			Reason:   fmt.Sprintf("certificate height is invalid (expected 1 got %v)", just0.QCert.Height()),
+			Reason:   fmt.Sprintf("certificate has an unexpected committers: %v", just0.QCert.Committers()),
 		})
 	})
 
 	t.Run("invalid certificate", func(t *testing.T) {
 		just0 := &vote.JustPreVoteSoft{
-			QCert: td.GenerateTestCertificate(),
+			QCert: td.GenerateTestCertificate(h),
 		}
 		just := &vote.JustMainVoteConflict{
 			Just0: just0,
 			Just1: &vote.JustPreVoteSoft{
-				QCert: td.GenerateTestCertificate(),
+				QCert: td.GenerateTestCertificate(h),
 			},
 		}
 		v := vote.NewCPMainVote(td.RandHash(), h, r, 1, vote.CPValueAbstain, just, td.valKeys[tIndexB].Address())
@@ -453,7 +453,7 @@ func TestInvalidJustMainVoteConflict(t *testing.T) {
 		err := td.consX.checkJust(v)
 		assert.ErrorIs(t, err, invalidJustificationError{
 			JustType: just0.Type(),
-			Reason:   fmt.Sprintf("certificate height is invalid (expected 1 got %v)", just0.QCert.Height()),
+			Reason:   fmt.Sprintf("certificate has an unexpected committers: %v", just0.QCert.Committers()),
 		})
 	})
 }
diff --git a/consensus/manager_test.go b/consensus/manager_test.go
index 8544d3b3..b7de96b6 100644
--- a/consensus/manager_test.go
+++ b/consensus/manager_test.go
@@ -64,7 +64,9 @@ func TestManager(t *testing.T) {
 	valKeys[4] = ts.RandValKey()
 	broadcastCh := make(chan message.Message, 500)
 
-	state.TestStore.SaveBlock(ts.RandHeight(), ts.GenerateTestBlock(), ts.GenerateTestCertificate())
+	height := ts.RandHeight()
+	blk, cert := ts.GenerateTestBlock(height)
+	state.TestStore.SaveBlock(blk, cert)
 
 	Mgr := NewManager(testConfig(), state, valKeys, rewardAddrs, broadcastCh)
 	mgr := Mgr.(*manager)
@@ -77,7 +79,7 @@ func TestManager(t *testing.T) {
 
 	assert.False(t, mgr.HasActiveInstance())
 	mgr.MoveToNewHeight()
-	curHeight := state.TestStore.LastHeight + 1
+	curHeight, _ := mgr.HeightRound()
 
 	newHeightTimeout(consA)
 
@@ -165,13 +167,13 @@ func TestManager(t *testing.T) {
 
 		assert.Len(t, mgr.upcomingVotes, 3)
 
-		err := state.CommitBlock(curHeight,
-			ts.GenerateTestBlockWithTime(util.Now().Add(1*time.Hour)), ts.GenerateTestCertificate())
+		blk, cert := ts.GenerateTestBlockWithTime(curHeight, util.Now().Add(1*time.Hour))
+		err := state.CommitBlock(blk, cert)
 		assert.NoError(t, err)
 		curHeight++
 
-		err = state.CommitBlock(curHeight,
-			ts.GenerateTestBlockWithTime(util.Now().Add(1*time.Hour)), ts.GenerateTestCertificate())
+		blk, cert = ts.GenerateTestBlockWithTime(curHeight, util.Now().Add(2*time.Hour))
+		err = state.CommitBlock(blk, cert)
 		assert.NoError(t, err)
 		curHeight++
 
@@ -200,13 +202,13 @@ func TestManager(t *testing.T) {
 
 		assert.Len(t, mgr.upcomingProposals, 3)
 
-		err := state.CommitBlock(curHeight,
-			ts.GenerateTestBlockWithTime(util.Now().Add(1*time.Hour)), ts.GenerateTestCertificate())
+		blk, cert := ts.GenerateTestBlockWithTime(curHeight, util.Now().Add(1*time.Hour))
+		err := state.CommitBlock(blk, cert)
 		assert.NoError(t, err)
 		curHeight++
 
-		err = state.CommitBlock(curHeight,
-			ts.GenerateTestBlockWithTime(util.Now().Add(1*time.Hour)), ts.GenerateTestCertificate())
+		blk, cert = ts.GenerateTestBlockWithTime(curHeight, util.Now().Add(2*time.Hour))
+		err = state.CommitBlock(blk, cert)
 		assert.NoError(t, err)
 
 		mgr.MoveToNewHeight()
diff --git a/consensus/propose_test.go b/consensus/propose_test.go
index f93b795b..19b7bf7d 100644
--- a/consensus/propose_test.go
+++ b/consensus/propose_test.go
@@ -23,14 +23,14 @@ func TestSetProposalInvalidProposer(t *testing.T) {
 	assert.Nil(t, td.consY.RoundProposal(0))
 
 	addr := td.valKeys[tIndexB].Address()
-	b := td.GenerateTestBlockWithProposer(addr)
-	p := proposal.NewProposal(1, 0, b)
+	blk, _ := td.GenerateTestBlockWithProposer(1, addr)
+	invalidProp := proposal.NewProposal(1, 0, blk)
 
-	td.consY.SetProposal(p)
+	td.consY.SetProposal(invalidProp)
 	assert.Nil(t, td.consY.RoundProposal(0))
 
-	td.HelperSignProposal(td.valKeys[tIndexB], p) // Invalid signature
-	td.consY.SetProposal(p)
+	td.HelperSignProposal(td.valKeys[tIndexB], invalidProp)
+	td.consY.SetProposal(invalidProp)
 	assert.Nil(t, td.consY.RoundProposal(0))
 }
 
@@ -38,15 +38,15 @@ func TestSetProposalInvalidBlock(t *testing.T) {
 	td := setup(t)
 
 	addr := td.valKeys[tIndexB].Address()
-	invBlock := td.GenerateTestBlockWithProposer(addr)
-	p := proposal.NewProposal(1, 2, invBlock)
-	td.HelperSignProposal(td.valKeys[tIndexB], p)
+	blk, _ := td.GenerateTestBlockWithProposer(1, addr)
+	invProp := proposal.NewProposal(1, 2, blk)
+	td.HelperSignProposal(td.valKeys[tIndexB], invProp)
 
 	td.enterNewHeight(td.consP)
 	td.enterNextRound(td.consP)
 	td.enterNextRound(td.consP)
 
-	td.consP.SetProposal(p)
+	td.consP.SetProposal(invProp)
 	assert.Nil(t, td.consP.RoundProposal(2))
 }
 
@@ -54,12 +54,12 @@ func TestSetProposalInvalidHeight(t *testing.T) {
 	td := setup(t)
 
 	addr := td.valKeys[tIndexB].Address()
-	invBlock := td.GenerateTestBlockWithProposer(addr)
-	p := proposal.NewProposal(2, 0, invBlock)
-	td.HelperSignProposal(td.valKeys[tIndexB], p)
+	blk, _ := td.GenerateTestBlockWithProposer(2, addr)
+	invProp := proposal.NewProposal(2, 0, blk)
+	td.HelperSignProposal(td.valKeys[tIndexB], invProp)
 
 	td.enterNewHeight(td.consY)
-	td.consY.SetProposal(p)
+	td.consY.SetProposal(invProp)
 	assert.Nil(t, td.consY.RoundProposal(2))
 }
 
diff --git a/sandbox/sandbox_test.go b/sandbox/sandbox_test.go
index 8bfe7474..95371c5b 100644
--- a/sandbox/sandbox_test.go
+++ b/sandbox/sandbox_test.go
@@ -48,10 +48,9 @@ func setup(t *testing.T) *testData {
 	}
 
 	lastHeight := uint32(21)
-	for i := uint32(1); i < lastHeight; i++ {
-		b := ts.GenerateTestBlock()
-		c := ts.GenerateTestCertificate()
-		mockStore.SaveBlock(i, b, c)
+	for height := uint32(1); height < lastHeight; height++ {
+		blk, cert := ts.GenerateTestBlock(height)
+		mockStore.SaveBlock(blk, cert)
 	}
 	sandbox := NewSandbox(mockStore.LastHeight,
 		mockStore, params, committee, totalPower).(*sandbox)
@@ -375,7 +374,8 @@ func TestPowerDelta(t *testing.T) {
 func TestVerifyProof(t *testing.T) {
 	td := setup(t)
 
-	lastHeight, _ := td.store.LastCertificate()
+	lastCert := td.store.LastCertificate()
+	lastHeight := lastCert.Height()
 	vals := td.sandbox.committee.Validators()
 
 	// Try to evaluate a valid sortition
diff --git a/state/execution_test.go b/state/execution_test.go
index d6ecc7f6..50bfa2a0 100644
--- a/state/execution_test.go
+++ b/state/execution_test.go
@@ -18,8 +18,8 @@ func TestProposeBlock(t *testing.T) {
 		td.moveToNextHeightForAllStates(t)
 	}
 	b1, c1 := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3)
-	assert.NoError(t, td.state1.CommitBlock(curHeight+1, b1, c1))
-	assert.NoError(t, td.state2.CommitBlock(curHeight+1, b1, c1))
+	assert.NoError(t, td.state1.CommitBlock(b1, c1))
+	assert.NoError(t, td.state2.CommitBlock(b1, c1))
 	assert.Equal(t, td.state1.LastBlockHeight(), curHeight+1)
 
 	invSubsidyTx := tx.NewSubsidyTx(1, td.valKey2.Address(),
@@ -46,7 +46,7 @@ func TestProposeBlock(t *testing.T) {
 	assert.Equal(t, b2.Header().PrevBlockHash(), b1.Hash())
 	assert.Equal(t, b2.Transactions()[1:], block.Txs{trx1, trx2})
 	assert.True(t, b2.Transactions()[0].IsSubsidyTx())
-	assert.NoError(t, td.state1.CommitBlock(curHeight+2, b2, c2))
+	assert.NoError(t, td.state1.CommitBlock(b2, c2))
 
 	assert.Equal(t, td.state1.TotalPower(), int64(1000000004))
 	assert.Equal(t, td.state1.committee.TotalPower(), int64(4))
@@ -56,7 +56,7 @@ func TestExecuteBlock(t *testing.T) {
 	td := setup(t)
 
 	b1, c1 := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3)
-	assert.NoError(t, td.state1.CommitBlock(1, b1, c1))
+	assert.NoError(t, td.state1.CommitBlock(b1, c1))
 
 	proposerAddr := td.RandAccAddress()
 	rewardAddr := td.RandAccAddress()
diff --git a/state/lastinfo/last_info_test.go b/state/lastinfo/last_info_test.go
index 6ad3b8a9..0746f63f 100644
--- a/state/lastinfo/last_info_test.go
+++ b/state/lastinfo/last_info_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/pactus-project/pactus/crypto"
 	"github.com/pactus-project/pactus/crypto/bls"
+	"github.com/pactus-project/pactus/crypto/hash"
 	"github.com/pactus-project/pactus/store"
 	"github.com/pactus-project/pactus/types/block"
 	"github.com/pactus-project/pactus/types/certificate"
@@ -13,6 +14,7 @@ import (
 	"github.com/pactus-project/pactus/util"
 	"github.com/pactus-project/pactus/util/testsuite"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 // The best way to test this module is by writing test code in the `state.CommitBlock` function
@@ -31,6 +33,9 @@ func setup(t *testing.T) *testData {
 	mockStore := store.MockingStore(ts)
 	lastInfo := NewLastInfo()
 
+	require.Zero(t, lastInfo.BlockHeight())
+	require.Equal(t, hash.UndefHash, lastInfo.BlockHash())
+
 	pub0, _ := ts.RandBLSKeyPair()
 	pub1, _ := ts.RandBLSKeyPair()
 	pub2, _ := ts.RandBLSKeyPair()
@@ -67,8 +72,8 @@ func setup(t *testing.T) *testData {
 	trx := tx.NewSortitionTx(1, pub4.ValidatorAddress(), ts.RandProof())
 	ts.HelperSignTransaction(prv4, trx)
 	prevHash := ts.RandHash()
-	prevCert := ts.GenerateTestCertificate()
 	lastHeight := ts.RandHeight()
+	prevCert := ts.GenerateTestCertificate(lastHeight - 1)
 	lastSeed := ts.RandSeed()
 	lastBlock := block.MakeBlock(1, util.Now(), block.Txs{trx},
 		prevHash,
@@ -77,11 +82,10 @@ func setup(t *testing.T) *testData {
 
 	sig := valKey.Sign([]byte("fatdog"))
 	lastCert := certificate.NewCertificate(lastHeight, 0, committers, []int32{}, sig)
-	mockStore.SaveBlock(lastHeight, lastBlock, lastCert)
+	mockStore.SaveBlock(lastBlock, lastCert)
 	assert.Equal(t, mockStore.LastHeight, lastHeight)
 
 	lastInfo.UpdateSortitionSeed(lastSeed)
-	lastInfo.UpdateBlockHeight(lastHeight)
 	lastInfo.UpdateBlockHash(lastBlock.Hash())
 	lastInfo.UpdateCertificate(lastCert)
 	lastInfo.UpdateBlockTime(lastBlock.Header().Time())
diff --git a/state/state_test.go b/state/state_test.go
index 944e7f97..59543e0f 100644
--- a/state/state_test.go
+++ b/state/state_test.go
@@ -172,13 +172,15 @@ func (td *testData) makeCertificateAndSign(t *testing.T, blockHash hash.Hash, ro
 	return certificate.NewCertificate(height+1, round, committers, absentees, bls.SignatureAggregate(sigs...))
 }
 
-func (td *testData) commitBlockForAllStates(t *testing.T, b *block.Block, c *certificate.Certificate) {
+func (td *testData) commitBlockForAllStates(t *testing.T,
+	blk *block.Block, cert *certificate.Certificate,
+) {
 	t.Helper()
 
-	assert.NoError(t, td.state1.CommitBlock(td.state1.lastInfo.BlockHeight()+1, b, c))
-	assert.NoError(t, td.state2.CommitBlock(td.state2.lastInfo.BlockHeight()+1, b, c))
-	assert.NoError(t, td.state3.CommitBlock(td.state3.lastInfo.BlockHeight()+1, b, c))
-	assert.NoError(t, td.state4.CommitBlock(td.state4.lastInfo.BlockHeight()+1, b, c))
+	assert.NoError(t, td.state1.CommitBlock(blk, cert))
+	assert.NoError(t, td.state2.CommitBlock(blk, cert))
+	assert.NoError(t, td.state3.CommitBlock(blk, cert))
+	assert.NoError(t, td.state4.CommitBlock(blk, cert))
 }
 
 func (td *testData) moveToNextHeightForAllStates(t *testing.T) {
@@ -236,11 +238,11 @@ func TestBlockTime(t *testing.T) {
 	})
 
 	t.Run("Commit one block: LastBlockTime is the time of the first block", func(t *testing.T) {
-		b1, c1 := td.makeBlockAndCertificate(t, 1, td.valKey1, td.valKey2, td.valKey3)
-		assert.NoError(t, td.state1.CommitBlock(1, b1, c1))
+		blk, cert := td.makeBlockAndCertificate(t, 1, td.valKey1, td.valKey2, td.valKey3)
+		assert.NoError(t, td.state1.CommitBlock(blk, cert))
 
 		assert.NotEqual(t, td.state1.LastBlockTime(), td.state1.Genesis().GenesisTime())
-		assert.Equal(t, td.state1.LastBlockTime(), b1.Header().Time())
+		assert.Equal(t, td.state1.LastBlockTime(), blk.Header().Time())
 	})
 }
 
@@ -248,11 +250,12 @@ func TestCommitBlocks(t *testing.T) {
 	td := setup(t)
 
 	b1, c1 := td.makeBlockAndCertificate(t, 1, td.valKey1, td.valKey2, td.valKey3)
-	invBlock := td.GenerateTestBlock()
-	assert.Error(t, td.state1.CommitBlock(1, invBlock, c1))
+	invBlock, invCert := td.GenerateTestBlock(1)
+	assert.Error(t, td.state1.CommitBlock(invBlock, c1))
+	assert.Error(t, td.state1.CommitBlock(b1, invCert))
 	// No error here but block is ignored, because the height is invalid
-	assert.NoError(t, td.state1.CommitBlock(2, b1, c1))
-	assert.NoError(t, td.state1.CommitBlock(1, b1, c1))
+	assert.NoError(t, td.state1.CommitBlock(b1, c1))
+	assert.NoError(t, td.state1.CommitBlock(b1, c1))
 
 	assert.Equal(t, td.state1.LastBlockHash(), b1.Hash())
 	assert.Equal(t, td.state1.LastBlockTime(), b1.Header().Time())
@@ -344,7 +347,8 @@ func TestUpdateLastCertificate(t *testing.T) {
 
 	invValKey := td.RandValKey()
 	notActiveValKey := td.RandValKey()
-	val := validator.NewValidator(notActiveValKey.PublicKey(), td.RandInt32(100))
+	valNum := int32(4) // [0..3] are in the committee now
+	val := validator.NewValidator(notActiveValKey.PublicKey(), valNum)
 	td.state1.store.UpdateValidator(val)
 
 	v1 := vote.NewPrepareVote(blk.Hash(), cert.Height(), cert.Round(), td.valKey3.Address())
@@ -417,8 +421,8 @@ func TestBlockProposal(t *testing.T) {
 func TestInvalidBlock(t *testing.T) {
 	td := setup(t)
 
-	b := td.GenerateTestBlock()
-	assert.Error(t, td.state1.ValidateBlock(b))
+	invBlk, _ := td.GenerateTestBlock(td.RandHeight())
+	assert.Error(t, td.state1.ValidateBlock(invBlk))
 }
 
 func TestForkDetection(t *testing.T) {
@@ -426,25 +430,25 @@ func TestForkDetection(t *testing.T) {
 
 	td.moveToNextHeightForAllStates(t)
 
-	b5m, c5m := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3)
-	b5f, c5f := td.makeBlockAndCertificate(t, 1, td.valKey1, td.valKey2, td.valKey3)
-	assert.NoError(t, td.state1.CommitBlock(2, b5m, c5m))
-	assert.NoError(t, td.state2.CommitBlock(2, b5m, c5m))
-	assert.NoError(t, td.state3.CommitBlock(2, b5m, c5m))
-	assert.NoError(t, td.state4.CommitBlock(2, b5f, c5f))
+	b2m, c2m := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3)
+	b2f, c2f := td.makeBlockAndCertificate(t, 1, td.valKey1, td.valKey2, td.valKey3)
+	assert.NoError(t, td.state1.CommitBlock(b2m, c2m))
+	assert.NoError(t, td.state2.CommitBlock(b2m, c2m))
+	assert.NoError(t, td.state3.CommitBlock(b2m, c2m))
+	assert.NoError(t, td.state4.CommitBlock(b2f, c2f))
 
-	b6, c6 := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3)
+	b3, c3 := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3)
 
-	assert.NoError(t, td.state1.CommitBlock(3, b6, c6))
-	assert.NoError(t, td.state2.CommitBlock(3, b6, c6))
-	assert.NoError(t, td.state3.CommitBlock(3, b6, c6))
+	assert.NoError(t, td.state1.CommitBlock(b3, c3))
+	assert.NoError(t, td.state2.CommitBlock(b3, c3))
+	assert.NoError(t, td.state3.CommitBlock(b3, c3))
 	t.Run("Fork is detected, Should panic ", func(t *testing.T) {
 		defer func() {
 			if r := recover(); r == nil {
 				t.Errorf("The code did not panic")
 			}
 		}()
-		assert.Error(t, td.state4.CommitBlock(3, b6, c6))
+		assert.Error(t, td.state4.CommitBlock(b3, c3))
 	})
 }
 
@@ -472,7 +476,7 @@ func TestSortition(t *testing.T) {
 
 		b, c := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3, td.valKey4)
 		td.commitBlockForAllStates(t, b, c)
-		require.NoError(t, stNew.CommitBlock(height, b, c))
+		require.NoError(t, stNew.CommitBlock(b, c))
 	}
 
 	assert.False(t, stNew.evaluateSortition()) //  bonding period
@@ -480,7 +484,7 @@ func TestSortition(t *testing.T) {
 	// Certificate next block
 	b, c := td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3, td.valKey4)
 	td.commitBlockForAllStates(t, b, c)
-	require.NoError(t, stNew.CommitBlock(height, b, c))
+	require.NoError(t, stNew.CommitBlock(b, c))
 	height++
 
 	assert.True(t, stNew.evaluateSortition())                             //  ok
@@ -490,7 +494,7 @@ func TestSortition(t *testing.T) {
 	// Certificate next block, new validator should be in the committee now
 	b, c = td.makeBlockAndCertificate(t, 0, td.valKey1, td.valKey2, td.valKey3, td.valKey4)
 	td.commitBlockForAllStates(t, b, c)
-	require.NoError(t, stNew.CommitBlock(height, b, c))
+	require.NoError(t, stNew.CommitBlock(b, c))
 
 	assert.True(t, stNew.evaluateSortition()) // in the committee
 	assert.True(t, td.state1.committee.Contains(td.valKey1.Address()))
@@ -520,11 +524,11 @@ func TestSortition(t *testing.T) {
 
 	c14 := certificate.NewCertificate(height, 3, []int32{4, 0, 1, 2, 3}, []int32{0}, bls.SignatureAggregate(sigs...))
 
-	assert.NoError(t, st1.CommitBlock(height, b14, c14))
-	assert.NoError(t, td.state1.CommitBlock(height, b14, c14))
-	assert.NoError(t, td.state2.CommitBlock(height, b14, c14))
-	assert.NoError(t, td.state3.CommitBlock(height, b14, c14))
-	assert.NoError(t, td.state4.CommitBlock(height, b14, c14))
+	assert.NoError(t, st1.CommitBlock(b14, c14))
+	assert.NoError(t, td.state1.CommitBlock(b14, c14))
+	assert.NoError(t, td.state2.CommitBlock(b14, c14))
+	assert.NoError(t, td.state3.CommitBlock(b14, c14))
+	assert.NoError(t, td.state4.CommitBlock(b14, c14))
 
 	assert.Equal(t, td.state1.CommitteePower(), int64(1000000004))
 	assert.Equal(t, td.state1.TotalValidators(), int32(5))
@@ -686,8 +690,8 @@ func TestLoadState(t *testing.T) {
 	assert.Equal(t, td.state1.TotalPower(), st1Load.(*state).TotalPower())
 	assert.Equal(t, td.state1.store.TotalAccounts(), int32(6))
 
-	require.NoError(t, st1Load.CommitBlock(6, b6, c6))
-	require.NoError(t, td.state2.CommitBlock(6, b6, c6))
+	require.NoError(t, st1Load.CommitBlock(b6, c6))
+	require.NoError(t, td.state2.CommitBlock(b6, c6))
 }
 
 func TestLoadStateAfterChangingGenesis(t *testing.T) {
@@ -794,7 +798,7 @@ func TestCommittingInvalidBlock(t *testing.T) {
 
 	// td.state1 receives a block with version 2 and rejects it.
 	// It is possible that the same block would be considered valid by td.state2.
-	assert.Error(t, td.state1.CommitBlock(2, b, c))
+	assert.Error(t, td.state1.CommitBlock(b, c))
 }
 
 func TestCalcFee(t *testing.T) {
diff --git a/state/validation_test.go b/state/validation_test.go
index feea7dc8..0b8e4369 100644
--- a/state/validation_test.go
+++ b/state/validation_test.go
@@ -42,7 +42,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := valKey5.Sign(signBytes)
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2}, aggSig)
 
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.Error(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 
 	t.Run("Invalid round, should return error", func(t *testing.T) {
@@ -54,19 +54,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := aggregate([]crypto.Signature{sig1, sig2, sig4})
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2}, aggSig)
 
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
-	})
-
-	t.Run("Invalid height, should return error", func(t *testing.T) {
-		committers := td.state2.committee.Committers()
-		signBytes := certificate.BlockCertificateSignBytes(nextBlockHash, height+1, round)
-		sig1 := td.valKey1.Sign(signBytes)
-		sig2 := td.valKey2.Sign(signBytes)
-		sig4 := td.valKey4.Sign(signBytes)
-		aggSig := aggregate([]crypto.Signature{sig1, sig2, sig4})
-		cert := certificate.NewCertificate(height+1, 0, committers, []int32{2}, aggSig)
-
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.Error(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 
 	t.Run("Invalid block hash, should return error", func(t *testing.T) {
@@ -79,7 +67,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := aggregate([]crypto.Signature{sig1, sig2, sig4})
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2}, aggSig)
 
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.Error(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 
 	t.Run("Invalid committer, should return error", func(t *testing.T) {
@@ -92,7 +80,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := aggregate([]crypto.Signature{sig1, sig2, sig4})
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2}, aggSig)
 
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.Error(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 
 	t.Run("Invalid committers, should return error", func(t *testing.T) {
@@ -105,7 +93,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := aggregate([]crypto.Signature{sig1, sig2, sig4})
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2}, aggSig)
 
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.Error(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 
 	t.Run("Doesn't have 2/3 majority", func(t *testing.T) {
@@ -116,7 +104,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := aggregate([]crypto.Signature{sig1, sig2})
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2, 3}, aggSig)
 
-		assert.Error(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.Error(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 
 	t.Run("Ok, should return no error", func(t *testing.T) {
@@ -128,7 +116,7 @@ func TestCertificateValidation(t *testing.T) {
 		aggSig := aggregate([]crypto.Signature{sig1, sig2, sig4})
 		cert := certificate.NewCertificate(height, 0, committers, []int32{2}, aggSig)
 
-		assert.NoError(t, td.state1.CommitBlock(height, nextBlock, cert))
+		assert.NoError(t, td.state1.CommitBlock(nextBlock, cert))
 	})
 }
 
@@ -197,7 +185,7 @@ func TestBlockValidation(t *testing.T) {
 		c := td.makeCertificateAndSign(t, b.Hash(), 0, td.valKey1, td.valKey2, td.valKey3, td.valKey4)
 
 		assert.NoError(t, td.state1.validateBlock(b))
-		assert.Error(t, td.state1.CommitBlock(2, b, c), "Invalid ProposerAddress")
+		assert.Error(t, td.state1.CommitBlock(b, c), "Invalid ProposerAddress")
 	})
 
 	t.Run("Invalid SortitionSeed", func(t *testing.T) {
@@ -207,7 +195,7 @@ func TestBlockValidation(t *testing.T) {
 		c := td.makeCertificateAndSign(t, b.Hash(), 0, td.valKey1, td.valKey2, td.valKey3, td.valKey4)
 
 		assert.NoError(t, td.state1.validateBlock(b))
-		assert.Error(t, td.state1.CommitBlock(2, b, c), "Invalid SortitionSeed")
+		assert.Error(t, td.state1.CommitBlock(b, c), "Invalid SortitionSeed")
 	})
 
 	t.Run("Ok", func(t *testing.T) {
@@ -217,6 +205,6 @@ func TestBlockValidation(t *testing.T) {
 		c := td.makeCertificateAndSign(t, b.Hash(), 0, td.valKey1, td.valKey2, td.valKey3, td.valKey4)
 
 		assert.NoError(t, td.state1.validateBlock(b))
-		assert.NoError(t, td.state1.CommitBlock(2, b, c), "Looks Good")
+		assert.NoError(t, td.state1.CommitBlock(b, c), "Looks Good")
 	})
 }
diff --git a/store/block_test.go b/store/block_test.go
index f90298a4..f8469d77 100644
--- a/store/block_test.go
+++ b/store/block_test.go
@@ -10,9 +10,10 @@ import (
 func TestBlockStore(t *testing.T) {
 	td := setup(t)
 
-	lastHeight, _ := td.store.LastCertificate()
-	b1 := td.GenerateTestBlock()
-	c1 := td.GenerateTestCertificate()
+	lastCert := td.store.LastCertificate()
+	lastHeight := lastCert.Height()
+	nextBlk, nextCrert := td.GenerateTestBlock(lastHeight + 1)
+	nextNextBlk, nextNextCrert := td.GenerateTestBlock(lastHeight + 2)
 
 	t.Run("Missed block, Should panic ", func(t *testing.T) {
 		defer func() {
@@ -20,29 +21,30 @@ func TestBlockStore(t *testing.T) {
 				t.Errorf("The code did not panic")
 			}
 		}()
-		td.store.SaveBlock(lastHeight+2, b1, c1)
+		td.store.SaveBlock(nextNextBlk, nextNextCrert)
 	})
 
 	t.Run("Add block, don't batch write", func(t *testing.T) {
-		td.store.SaveBlock(lastHeight+1, b1, c1)
+		td.store.SaveBlock(nextBlk, nextCrert)
 		b2, err := td.store.Block(lastHeight + 1)
 		assert.Error(t, err)
 		assert.Nil(t, b2)
 	})
 
 	t.Run("Add block, batch write", func(t *testing.T) {
-		td.store.SaveBlock(lastHeight+1, b1, c1)
+		td.store.SaveBlock(nextBlk, nextCrert)
 		assert.NoError(t, td.store.WriteBatch())
-		sb, err := td.store.Block(lastHeight + 1)
+
+		committedBlock, err := td.store.Block(lastHeight + 1)
 		assert.NoError(t, err)
-		d, _ := b1.Bytes()
-		assert.Equal(t, sb.Height, lastHeight+1)
-		assert.True(t, bytes.Equal(sb.Data, d))
+		assert.Equal(t, committedBlock.Height, lastHeight+1)
+
+		d, _ := nextBlk.Bytes()
+		assert.True(t, bytes.Equal(committedBlock.Data, d))
 
-		h, cert := td.store.LastCertificate()
+		cert := td.store.LastCertificate()
 		assert.NoError(t, err)
-		assert.Equal(t, h, lastHeight+1)
-		assert.Equal(t, cert.Hash(), c1.Hash())
+		assert.Equal(t, cert.Hash(), nextCrert.Hash())
 	})
 
 	t.Run("Duplicated block, Should panic ", func(t *testing.T) {
@@ -51,6 +53,6 @@ func TestBlockStore(t *testing.T) {
 				t.Errorf("The code did not panic")
 			}
 		}()
-		td.store.SaveBlock(lastHeight, b1, c1)
+		td.store.SaveBlock(nextBlk, nextCrert)
 	})
 }
diff --git a/store/store_test.go b/store/store_test.go
index a5736983..99f7d0b9 100644
--- a/store/store_test.go
+++ b/store/store_test.go
@@ -35,22 +35,14 @@ func setup(t *testing.T) *testData {
 		store:     s.(*store),
 	}
 
-	td.saveTestBlocks(t, 10)
+	// Save 10 blocks
+	for height := uint32(0); height < 10; height++ {
+		blk, cert := td.GenerateTestBlock(height + 1)
 
-	return td
-}
-
-func (td *testData) saveTestBlocks(t *testing.T, num int) {
-	t.Helper()
-
-	lastHeight, _ := td.store.LastCertificate()
-	for i := 0; i < num; i++ {
-		b := td.GenerateTestBlock()
-		c := td.GenerateTestCertificate()
-
-		td.store.SaveBlock(lastHeight+uint32(i+1), b, c)
+		td.store.SaveBlock(blk, cert)
 		assert.NoError(t, td.store.WriteBatch())
 	}
+	return td
 }
 
 func TestBlockHash(t *testing.T) {
@@ -92,16 +84,20 @@ func TestWriteAndClosePeacefully(t *testing.T) {
 func TestRetrieveBlockAndTransactions(t *testing.T) {
 	td := setup(t)
 
-	lastHeight, _ := td.store.LastCertificate()
+	lastCert := td.store.LastCertificate()
+	lastHeight := lastCert.Height()
 	committedBlock, err := td.store.Block(lastHeight)
 	assert.NoError(t, err)
 	assert.Equal(t, lastHeight, committedBlock.Height)
 	block, _ := committedBlock.ToBlock()
+	assert.Equal(t, block.PrevCertificate().Height(), lastHeight-1)
+
 	for _, trx := range block.Transactions() {
 		committedTx, err := td.store.Transaction(trx.ID())
 		assert.NoError(t, err)
-		assert.Equal(t, committedTx.TxID, trx.ID())
-		assert.Equal(t, committedTx.BlockTime, block.Header().UnixTime())
+		assert.Equal(t, block.Header().UnixTime(), committedTx.BlockTime)
+		assert.Equal(t, trx.ID(), committedTx.TxID)
+		assert.Equal(t, lastHeight, committedTx.Height)
 		trx2, _ := committedTx.ToTx()
 		assert.Equal(t, trx2.ID(), trx.ID())
 	}
@@ -140,13 +136,13 @@ func TestStrippedPublicKey(t *testing.T) {
 	assert.NotNil(t, trx0PubKey)
 	knownPubKey := trx0PubKey.(*bls.PublicKey)
 
-	lastHeight, _ := td.store.LastCertificate()
-	lockTime := lastHeight
+	lastCert := td.store.LastCertificate()
+	lastHeight := lastCert.Height()
 	randPubkey, _ := td.RandBLSKeyPair()
 
-	trx0 := tx.NewTransferTx(lockTime, knownPubKey.AccountAddress(), td.RandAccAddress(), 1, 1, "")
-	trx1 := tx.NewTransferTx(lockTime, randPubkey.AccountAddress(), td.RandAccAddress(), 1, 1, "")
-	trx2 := tx.NewTransferTx(lockTime, randPubkey.AccountAddress(), td.RandAccAddress(), 1, 1, "")
+	trx0 := tx.NewTransferTx(lastHeight, knownPubKey.AccountAddress(), td.RandAccAddress(), 1, 1, "")
+	trx1 := tx.NewTransferTx(lastHeight, randPubkey.AccountAddress(), td.RandAccAddress(), 1, 1, "")
+	trx2 := tx.NewTransferTx(lastHeight, randPubkey.AccountAddress(), td.RandAccAddress(), 1, 1, "")
 
 	trx0.SetSignature(td.RandBLSSignature())
 	trx1.SetSignature(td.RandBLSSignature())
@@ -169,9 +165,8 @@ func TestStrippedPublicKey(t *testing.T) {
 		trxs := block.Txs{test.trx}
 
 		// Make a block
-		prevCert := td.GenerateTestCertificate()
 		blk := block.MakeBlock(1, util.Now(), trxs, td.RandHash(), td.RandHash(),
-			prevCert, td.RandSeed(), td.RandValAddress())
+			lastCert, td.RandSeed(), td.RandValAddress())
 
 		trxData, _ := test.trx.Bytes()
 		blkData, _ := blk.Bytes()
diff --git a/sync/bundle/bundle_test.go b/sync/bundle/bundle_test.go
index bb5bc694..fee592ce 100644
--- a/sync/bundle/bundle_test.go
+++ b/sync/bundle/bundle_test.go
@@ -37,8 +37,8 @@ func TestMessageCompress(t *testing.T) {
 
 	blocksData := [][]byte{}
 	for i := 0; i < 10; i++ {
-		b := ts.GenerateTestBlock()
-		d, _ := b.Bytes()
+		blk, _ := ts.GenerateTestBlock(ts.RandHeight())
+		d, _ := blk.Bytes()
 		blocksData = append(blocksData, d)
 	}
 	msg := message.NewBlocksResponseMessage(message.ResponseCodeOK, message.ResponseCodeOK.String(),
diff --git a/sync/bundle/message/block_announce_test.go b/sync/bundle/message/block_announce_test.go
index ec31f86d..f34426d7 100644
--- a/sync/bundle/message/block_announce_test.go
+++ b/sync/bundle/message/block_announce_test.go
@@ -1,6 +1,7 @@
 package message
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/pactus-project/pactus/types/certificate"
@@ -17,9 +18,8 @@ func TestBlockAnnounceMessage(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
 	t.Run("Invalid certificate", func(t *testing.T) {
-		b := ts.GenerateTestBlock()
-		c := certificate.NewCertificate(0, 0, nil, nil, nil)
-		m := NewBlockAnnounceMessage(100, b, c)
+		blk, cert := ts.GenerateTestBlock(0)
+		m := NewBlockAnnounceMessage(blk, cert)
 		err := m.BasicCheck()
 
 		assert.ErrorIs(t, err, certificate.BasicCheckError{
@@ -28,11 +28,12 @@ func TestBlockAnnounceMessage(t *testing.T) {
 	})
 
 	t.Run("OK", func(t *testing.T) {
-		b := ts.GenerateTestBlock()
-		c := ts.GenerateTestCertificate()
-		m := NewBlockAnnounceMessage(100, b, c)
+		height := ts.RandHeight()
+		blk, cert := ts.GenerateTestBlock(height)
+		m := NewBlockAnnounceMessage(blk, cert)
 
 		assert.NoError(t, m.BasicCheck())
-		assert.Contains(t, m.String(), "100")
+		assert.Equal(t, height, m.Height())
+		assert.Contains(t, m.String(), fmt.Sprintf("%d", height))
 	})
 }
diff --git a/sync/bundle/message/blocks_response_test.go b/sync/bundle/message/blocks_response_test.go
index ffea4bb7..a28c1718 100644
--- a/sync/bundle/message/blocks_response_test.go
+++ b/sync/bundle/message/blocks_response_test.go
@@ -4,7 +4,6 @@ import (
 	"testing"
 
 	"github.com/pactus-project/pactus/types/certificate"
-	"github.com/pactus-project/pactus/util/errors"
 	"github.com/pactus-project/pactus/util/testsuite"
 	"github.com/stretchr/testify/assert"
 )
@@ -19,10 +18,10 @@ func TestBlocksResponseMessage(t *testing.T) {
 
 	sid := 123
 	t.Run("Invalid certificate", func(t *testing.T) {
-		b := ts.GenerateTestBlock()
-		c := certificate.NewCertificate(0, 0, nil, nil, nil)
-		d, _ := b.Bytes()
-		m := NewBlocksResponseMessage(ResponseCodeMoreBlocks, ResponseCodeMoreBlocks.String(), sid, 100, [][]byte{d}, c)
+		blk, cert := ts.GenerateTestBlock(0)
+		d, _ := blk.Bytes()
+		m := NewBlocksResponseMessage(ResponseCodeMoreBlocks, ResponseCodeMoreBlocks.String(),
+			sid, ts.RandHeight(), [][]byte{d}, cert)
 		err := m.BasicCheck()
 
 		assert.ErrorIs(t, err, certificate.BasicCheckError{
@@ -30,24 +29,16 @@ func TestBlocksResponseMessage(t *testing.T) {
 		})
 	})
 
-	t.Run("Unexpected block for height zero", func(t *testing.T) {
-		b := ts.GenerateTestBlock()
-		d, _ := b.Bytes()
-		m := NewBlocksResponseMessage(ResponseCodeMoreBlocks, ResponseCodeMoreBlocks.String(), sid, 0, [][]byte{d}, nil)
-
-		assert.Equal(t, errors.Code(m.BasicCheck()), errors.ErrInvalidHeight)
-	})
-
 	t.Run("OK", func(t *testing.T) {
-		b1 := ts.GenerateTestBlock()
-		b2 := ts.GenerateTestBlock()
-		d1, _ := b1.Bytes()
-		d2, _ := b2.Bytes()
-		m := NewBlocksResponseMessage(ResponseCodeMoreBlocks, ResponseCodeMoreBlocks.String(), sid, 100,
-			[][]byte{d1, d2}, nil)
+		height := ts.RandHeight()
+		blk1, _ := ts.GenerateTestBlock(height)
+		blk2, cert2 := ts.GenerateTestBlock(height + 1)
+		d1, _ := blk1.Bytes()
+		d2, _ := blk2.Bytes()
+		m := NewBlocksResponseMessage(ResponseCodeMoreBlocks, ResponseCodeMoreBlocks.String(),
+			sid, 100, [][]byte{d1, d2}, cert2)
 
 		assert.NoError(t, m.BasicCheck())
-		assert.Zero(t, m.LastCertificateHeight())
 		assert.Contains(t, m.String(), "100")
 		assert.Equal(t, m.Reason, ResponseCodeMoreBlocks.String())
 	})
@@ -69,10 +60,11 @@ func TestLatestBlocksResponseCode(t *testing.T) {
 	})
 
 	t.Run("OK - MoreBlocks", func(t *testing.T) {
-		b1 := ts.GenerateTestBlock()
-		b2 := ts.GenerateTestBlock()
-		d1, _ := b1.Bytes()
-		d2, _ := b2.Bytes()
+		height := ts.RandHeight()
+		blk1, _ := ts.GenerateTestBlock(height)
+		blk2, _ := ts.GenerateTestBlock(height + 1)
+		d1, _ := blk1.Bytes()
+		d2, _ := blk2.Bytes()
 		reason := ts.RandString(16)
 		m := NewBlocksResponseMessage(ResponseCodeMoreBlocks, reason, 1, 100, [][]byte{d1, d2}, nil)
 
@@ -80,13 +72,13 @@ func TestLatestBlocksResponseCode(t *testing.T) {
 		assert.Equal(t, m.From, uint32(100))
 		assert.Equal(t, m.To(), uint32(101))
 		assert.Equal(t, m.Count(), uint32(2))
-		assert.Zero(t, m.LastCertificateHeight())
 		assert.False(t, m.IsRequestRejected())
 		assert.Equal(t, m.Reason, reason)
 	})
 
 	t.Run("OK - Synced", func(t *testing.T) {
-		cert := ts.GenerateTestCertificate()
+		height := ts.RandHeight()
+		_, cert := ts.GenerateTestBlock(height)
 
 		reason := ts.RandString(16)
 		m := NewBlocksResponseMessage(ResponseCodeSynced, reason, 1, 100, nil, cert)
@@ -95,7 +87,6 @@ func TestLatestBlocksResponseCode(t *testing.T) {
 		assert.Equal(t, m.From, uint32(100))
 		assert.Zero(t, m.To())
 		assert.Zero(t, m.Count())
-		assert.Equal(t, m.LastCertificateHeight(), uint32(100))
 		assert.False(t, m.IsRequestRejected())
 		assert.Equal(t, m.Reason, reason)
 	})
diff --git a/sync/cache/cache_test.go b/sync/cache/cache_test.go
index 06f1c1cb..fc6ce7dc 100644
--- a/sync/cache/cache_test.go
+++ b/sync/cache/cache_test.go
@@ -7,31 +7,40 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestCacheBlocks(t *testing.T) {
+func TestAddBlockOne(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
+	cache, _ := NewCache(10)
+
+	blk1, _ := ts.GenerateTestBlock(1)
+	cache.AddBlock(blk1)
+
+	assert.True(t, cache.HasBlockInCache(1))
+	assert.Equal(t, blk1, cache.GetBlock(1))
+	assert.Nil(t, cache.GetCertificate(0))
+}
 
+func TestAddBlocks(t *testing.T) {
+	ts := testsuite.NewTestSuite(t)
 	cache, _ := NewCache(10)
 
-	b1 := ts.GenerateTestBlock()
-	b2 := ts.GenerateTestBlock()
 	testHeight := ts.RandHeight()
-
-	cache.AddBlock(testHeight, b1)
-	cache.AddBlock(testHeight+1, b2)
+	blk1, _ := ts.GenerateTestBlock(testHeight)
+	cache.AddBlock(blk1)
 
 	assert.True(t, cache.HasBlockInCache(testHeight))
-	assert.True(t, cache.HasBlockInCache(testHeight+1))
-	assert.False(t, cache.HasBlockInCache(testHeight+3))
+	assert.Equal(t, blk1, cache.GetBlock(testHeight))
+	assert.Equal(t, blk1.PrevCertificate(), cache.GetCertificate(testHeight-1))
+}
 
-	assert.NotNil(t, cache.GetBlock(testHeight))
-	assert.NotNil(t, cache.GetBlock(testHeight+1))
-	assert.Nil(t, cache.GetBlock(testHeight+2))
+func TestAddCertificate(t *testing.T) {
+	ts := testsuite.NewTestSuite(t)
+	cache, _ := NewCache(10)
 
-	assert.Equal(t, cache.GetBlock(testHeight).Hash(), b1.Hash())
-	assert.Equal(t, cache.GetBlock(testHeight+1).Hash(), b2.Hash())
-	assert.Nil(t, cache.GetCertificate(0))
-	assert.Equal(t, cache.GetCertificate(testHeight).Hash(), b2.PrevCertificate().Hash())
-	assert.Nil(t, cache.GetCertificate(4))
+	testHeight := ts.RandHeight()
+	_, cert1 := ts.GenerateTestBlock(testHeight)
+	cache.AddCertificate(cert1)
+
+	assert.Equal(t, cert1, cache.GetCertificate(testHeight))
 }
 
 func TestClearCache(t *testing.T) {
@@ -39,9 +48,8 @@ func TestClearCache(t *testing.T) {
 
 	cache, _ := NewCache(10)
 
-	b := ts.GenerateTestBlock()
-
-	cache.AddBlock(2, b)
+	blk, _ := ts.GenerateTestBlock(ts.RandHeight())
+	cache.AddBlock(blk)
 
 	assert.Equal(t, cache.Len(), 1)
 	cache.Clear()
@@ -56,13 +64,29 @@ func TestCacheIsFull(t *testing.T) {
 
 	i := int32(0)
 	for ; i < 10; i++ {
-		b := ts.GenerateTestBlock()
-		cache.AddBlock(uint32(i+1), b)
+		blk, _ := ts.GenerateTestBlock(uint32(i + 1))
+		cache.AddBlock(blk)
 	}
 
-	newBlock := ts.GenerateTestBlock()
-	cache.AddBlock(uint32(i+1), newBlock)
+	newBlock, _ := ts.GenerateTestBlock(uint32(i + 1))
+	cache.AddBlock(newBlock)
 
 	assert.NotNil(t, cache.GetBlock(uint32(i+1)))
 	assert.Nil(t, cache.GetBlock(1))
 }
+
+func TestAddAgain(t *testing.T) {
+	ts := testsuite.NewTestSuite(t)
+
+	cache, _ := NewCache(10)
+
+	height := ts.RandHeight()
+	firstBlk, _ := ts.GenerateTestBlock(height)
+	secondBlk, _ := ts.GenerateTestBlock(height)
+
+	cache.AddBlock(firstBlk)
+	assert.Equal(t, firstBlk, cache.GetBlock(height))
+
+	cache.AddBlock(secondBlk)
+	assert.Equal(t, secondBlk, cache.GetBlock(height))
+}
diff --git a/sync/handler_block_announce_test.go b/sync/handler_block_announce_test.go
index 41318d16..c57bd82b 100644
--- a/sync/handler_block_announce_test.go
+++ b/sync/handler_block_announce_test.go
@@ -6,7 +6,6 @@ import (
 	"github.com/pactus-project/pactus/sync/bundle/message"
 	"github.com/pactus-project/pactus/sync/services"
 	"github.com/pactus-project/pactus/types/certificate"
-	"github.com/pactus-project/pactus/util"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -14,17 +13,12 @@ func TestParsingBlockAnnounceMessages(t *testing.T) {
 	td := setup(t, nil)
 
 	pid := td.RandPeerID()
-	lastBlockHeight := td.state.LastBlockHeight()
-	blockInterval := td.state.Genesis().Params().BlockInterval()
-	blockTime := util.RoundNow(int(blockInterval.Seconds()))
-	b1 := td.GenerateTestBlockWithTime(blockTime)
-	c1 := td.GenerateTestCertificate()
-	msg1 := message.NewBlockAnnounceMessage(lastBlockHeight+1, b1, c1)
-
-	blockTime = blockTime.Add(blockInterval)
-	b2 := td.GenerateTestBlockWithTime(blockTime)
-	c2 := td.GenerateTestCertificate()
-	msg2 := message.NewBlockAnnounceMessage(lastBlockHeight+2, b2, c2)
+	lastHeight := td.state.LastBlockHeight()
+	blk1, cert1 := td.GenerateTestBlock(lastHeight + 1)
+	msg1 := message.NewBlockAnnounceMessage(blk1, cert1)
+
+	blk2, cert2 := td.GenerateTestBlock(lastHeight + 2)
+	msg2 := message.NewBlockAnnounceMessage(blk2, cert2)
 
 	pub, _ := td.RandBLSKeyPair()
 	td.addPeer(t, pub, pid, services.New(services.Network))
@@ -33,17 +27,17 @@ func TestParsingBlockAnnounceMessages(t *testing.T) {
 		assert.NoError(t, td.receivingNewMessage(td.sync, msg2, pid))
 
 		msg1 := td.shouldPublishMessageWithThisType(t, td.network, message.TypeBlocksRequest)
-		assert.Equal(t, msg1.Message.(*message.BlocksRequestMessage).From, lastBlockHeight+1)
+		assert.Equal(t, msg1.Message.(*message.BlocksRequestMessage).From, lastHeight+1)
 
 		peer := td.sync.peerSet.GetPeer(pid)
-		assert.Equal(t, peer.Height, lastBlockHeight+2)
-		assert.Equal(t, td.sync.state.LastBlockHeight(), lastBlockHeight)
+		assert.Equal(t, peer.Height, lastHeight+2)
+		assert.Equal(t, td.sync.state.LastBlockHeight(), lastHeight)
 	})
 
 	t.Run("Receiving missed block, should commit both blocks", func(t *testing.T) {
 		assert.NoError(t, td.receivingNewMessage(td.sync, msg1, pid))
 
-		assert.Equal(t, td.sync.state.LastBlockHeight(), lastBlockHeight+2)
+		assert.Equal(t, td.sync.state.LastBlockHeight(), lastHeight+2)
 	})
 }
 
@@ -51,10 +45,10 @@ func TestInvalidBlockAnnounce(t *testing.T) {
 	td := setup(t, nil)
 
 	pid := td.RandPeerID()
-	lastBlockHeight := td.state.LastBlockHeight()
-	blk := td.GenerateTestBlock()
-	invCert := certificate.NewCertificate(td.RandHeight(), 0, nil, nil, nil)
-	msg := message.NewBlockAnnounceMessage(lastBlockHeight+1, blk, invCert)
+	lastHeight := td.state.LastBlockHeight()
+	blk, _ := td.GenerateTestBlock(lastHeight + 1)
+	invCert := certificate.NewCertificate(lastHeight+1, 0, nil, nil, nil)
+	msg := message.NewBlockAnnounceMessage(blk, invCert)
 
 	err := td.receivingNewMessage(td.sync, msg, pid)
 	assert.Error(t, err)
@@ -66,7 +60,7 @@ func TestBroadcastingBlockAnnounceMessages(t *testing.T) {
 	td.state.CommitTestBlocks(21)
 	blk, _ := td.state.CommittedBlock(td.state.LastBlockHeight()).ToBlock()
 	msg := message.NewBlockAnnounceMessage(
-		td.state.LastBlockHeight(), blk, td.state.LastCertificate())
+		blk, td.state.LastCertificate())
 
 	t.Run("Not in the committee, should not broadcast block announce message", func(t *testing.T) {
 		td.sync.broadcast(msg)
@@ -80,6 +74,6 @@ func TestBroadcastingBlockAnnounceMessages(t *testing.T) {
 		td.sync.broadcast(msg)
 
 		msg1 := td.shouldPublishMessageWithThisType(t, td.network, message.TypeBlockAnnounce)
-		assert.Equal(t, msg1.Message.(*message.BlockAnnounceMessage).Height, msg.Height)
+		assert.Equal(t, msg1.Message.(*message.BlockAnnounceMessage).Certificate.Height(), msg.Certificate.Height())
 	})
 }
diff --git a/sync/handler_blocks_request_test.go b/sync/handler_blocks_request_test.go
index 63b077f7..91383dab 100644
--- a/sync/handler_blocks_request_test.go
+++ b/sync/handler_blocks_request_test.go
@@ -61,14 +61,12 @@ func TestLatestBlocksRequestMessages(t *testing.T) {
 				assert.Equal(t, bdl1.Message.(*message.BlocksResponseMessage).From, curHeight-config.BlockPerMessage)
 				assert.Equal(t, bdl1.Message.(*message.BlocksResponseMessage).To(), curHeight-1)
 				assert.Equal(t, bdl1.Message.(*message.BlocksResponseMessage).Count(), config.BlockPerMessage)
-				assert.Zero(t, bdl1.Message.(*message.BlocksResponseMessage).LastCertificateHeight())
 
 				bdl2 := td.shouldPublishMessageWithThisType(t, td.network, message.TypeBlocksResponse)
 				assert.Equal(t, bdl2.Message.(*message.BlocksResponseMessage).ResponseCode, message.ResponseCodeNoMoreBlocks)
 				assert.Zero(t, bdl2.Message.(*message.BlocksResponseMessage).From)
 				assert.Zero(t, bdl2.Message.(*message.BlocksResponseMessage).To())
 				assert.Zero(t, bdl2.Message.(*message.BlocksResponseMessage).Count())
-				assert.Zero(t, bdl1.Message.(*message.BlocksResponseMessage).LastCertificateHeight())
 			})
 
 			t.Run("Peer synced", func(t *testing.T) {
@@ -80,14 +78,12 @@ func TestLatestBlocksRequestMessages(t *testing.T) {
 				assert.Equal(t, bdl1.Message.(*message.BlocksResponseMessage).From, curHeight-config.BlockPerMessage+1)
 				assert.Equal(t, bdl1.Message.(*message.BlocksResponseMessage).To(), curHeight)
 				assert.Equal(t, bdl1.Message.(*message.BlocksResponseMessage).Count(), config.BlockPerMessage)
-				assert.Zero(t, bdl1.Message.(*message.BlocksResponseMessage).LastCertificateHeight())
 
 				bdl2 := td.shouldPublishMessageWithThisType(t, td.network, message.TypeBlocksResponse)
 				assert.Equal(t, bdl2.Message.(*message.BlocksResponseMessage).ResponseCode, message.ResponseCodeSynced)
 				assert.Equal(t, bdl2.Message.(*message.BlocksResponseMessage).From, curHeight)
 				assert.Zero(t, bdl2.Message.(*message.BlocksResponseMessage).To())
 				assert.Zero(t, bdl2.Message.(*message.BlocksResponseMessage).Count())
-				assert.Equal(t, bdl2.Message.(*message.BlocksResponseMessage).LastCertificateHeight(), curHeight)
 			})
 		})
 
diff --git a/sync/handler_blocks_response_test.go b/sync/handler_blocks_response_test.go
index 2cfb0bcb..e82fbd48 100644
--- a/sync/handler_blocks_response_test.go
+++ b/sync/handler_blocks_response_test.go
@@ -26,8 +26,11 @@ import (
 func TestInvalidBlockData(t *testing.T) {
 	td := setup(t, nil)
 
+	lastHeight := td.state.LastBlockHeight()
+	prevCert := td.GenerateTestCertificate(lastHeight)
+	cert := td.GenerateTestCertificate(lastHeight + 1)
 	blk := block.MakeBlock(1, time.Now(), nil, td.RandHash(), td.RandHash(),
-		td.GenerateTestCertificate(), td.RandSeed(), td.RandValAddress())
+		prevCert, td.RandSeed(), td.RandValAddress())
 	data, _ := blk.Bytes()
 	tests := []struct {
 		data []byte
@@ -48,9 +51,8 @@ func TestInvalidBlockData(t *testing.T) {
 	for _, test := range tests {
 		pid := td.RandPeerID()
 		sid := td.sync.peerSet.OpenSession(pid).SessionID()
-		cert := td.GenerateTestCertificate()
 		msg := message.NewBlocksResponseMessage(message.ResponseCodeMoreBlocks, message.ResponseCodeMoreBlocks.String(), sid,
-			td.state.LastBlockHeight()+1, [][]byte{test.data}, cert)
+			lastHeight+1, [][]byte{test.data}, cert)
 
 		err := td.receivingNewMessage(td.sync, msg, pid)
 		assert.ErrorIs(t, err, test.err)
@@ -60,10 +62,9 @@ func TestInvalidBlockData(t *testing.T) {
 func TestOneBlockShorter(t *testing.T) {
 	td := setup(t, nil)
 
-	lastBlockHeight := td.state.LastBlockHeight()
-	b1 := td.GenerateTestBlock()
-	c1 := td.GenerateTestCertificate()
-	d1, _ := b1.Bytes()
+	lastHeight := td.state.LastBlockHeight()
+	blk1, cert1 := td.GenerateTestBlock(lastHeight + 1)
+	d1, _ := blk1.Bytes()
 	pid := td.RandPeerID()
 
 	pub, _ := td.RandBLSKeyPair()
@@ -71,28 +72,45 @@ func TestOneBlockShorter(t *testing.T) {
 
 	sid := td.sync.peerSet.OpenSession(pid).SessionID()
 	msg := message.NewBlocksResponseMessage(message.ResponseCodeSynced, t.Name(), sid,
-		lastBlockHeight+1, [][]byte{d1}, c1)
+		lastHeight+1, [][]byte{d1}, cert1)
 	assert.NoError(t, td.receivingNewMessage(td.sync, msg, pid))
 
 	assert.Nil(t, td.sync.peerSet.FindSession(sid))
-	assert.Equal(t, td.state.LastBlockHeight(), lastBlockHeight+1)
+	assert.Equal(t, td.state.LastBlockHeight(), lastHeight+1)
 }
 
 func TestStrippedPublicKey(t *testing.T) {
 	td := setup(t, nil)
 
-	td.state.CommitTestBlocks(2)
-
-	// Add a peer
-	pid := td.RandPeerID()
-	pub, _ := td.RandBLSKeyPair()
-	td.addPeer(t, pub, pid, services.New(services.None))
-
-	blk1 := td.GenerateTestBlock()
-	trx := *td.state.TestStore.Blocks[1].Transactions()[0]
-	trxs := []*tx.Tx{&trx}
-	blk2 := block.MakeBlock(1, time.Now(), trxs, td.RandHash(), td.RandHash(),
-		td.GenerateTestCertificate(), td.RandSeed(), td.RandValAddress())
+	lastHeight := td.state.LastBlockHeight()
+
+	// Add a new block and keep the signer key
+	indexedPub, indexedPrv := td.RandBLSKeyPair()
+	trx0 := tx.NewTransferTx(lastHeight, indexedPub.AccountAddress(), td.RandAccAddress(), 1, 1, "")
+	td.HelperSignTransaction(indexedPrv, trx0)
+	trxs0 := []*tx.Tx{trx0}
+	blk0 := block.MakeBlock(1, time.Now(), trxs0, td.RandHash(), td.RandHash(),
+		td.state.LastCertificate(), td.RandSeed(), td.RandValAddress())
+	cert0 := td.GenerateTestCertificate(lastHeight + 1)
+	err := td.state.CommitBlock(blk0, cert0)
+	require.NoError(t, err)
+	lastHeight++
+	// -----
+
+	rndPub, rndPrv := td.RandBLSKeyPair()
+	trx1 := tx.NewTransferTx(lastHeight, rndPub.AccountAddress(), td.RandAccAddress(), 1, 1, "")
+	td.HelperSignTransaction(rndPrv, trx1)
+	trx1.StripPublicKey()
+	trxs1 := []*tx.Tx{trx1}
+	blk1 := block.MakeBlock(1, time.Now(), trxs1, td.RandHash(), td.RandHash(),
+		cert0, td.RandSeed(), td.RandValAddress())
+
+	trx2 := tx.NewTransferTx(lastHeight, indexedPub.AccountAddress(), td.RandAccAddress(), 1, 1, "")
+	td.HelperSignTransaction(indexedPrv, trx2)
+	trx2.StripPublicKey()
+	trxs2 := []*tx.Tx{trx2}
+	blk2 := block.MakeBlock(1, time.Now(), trxs2, td.RandHash(), td.RandHash(),
+		cert0, td.RandSeed(), td.RandValAddress())
 
 	tests := []struct {
 		blk *block.Block
@@ -108,15 +126,17 @@ func TestStrippedPublicKey(t *testing.T) {
 		},
 	}
 
+	// Add a peer
+	pid := td.RandPeerID()
+	peerPubKey, _ := td.RandBLSKeyPair()
+	td.addPeer(t, peerPubKey, pid, services.New(services.None))
+
 	for _, test := range tests {
-		assert.NoError(t, test.blk.BasicCheck())
-		trx0 := test.blk.Transactions()[0]
-		trx0.StripPublicKey()
-		cert := td.GenerateTestCertificate()
 		blkData, _ := test.blk.Bytes()
 		sid := td.sync.peerSet.OpenSession(pid).SessionID()
+		cert := td.GenerateTestCertificate(lastHeight + 1)
 		msg := message.NewBlocksResponseMessage(message.ResponseCodeMoreBlocks, message.ResponseCodeRejected.String(), sid,
-			td.state.LastBlockHeight()+1, [][]byte{blkData}, cert)
+			lastHeight+1, [][]byte{blkData}, cert)
 		err := td.receivingNewMessage(td.sync, msg, pid)
 
 		assert.ErrorIs(t, err, test.err)
@@ -151,9 +171,8 @@ func TestSyncing(t *testing.T) {
 	blockInterval := stateBob.Genesis().Params().BlockInterval()
 	blockTime := util.RoundNow(int(blockInterval.Seconds()))
 	for i := uint32(0); i < 100; i++ {
-		blk := ts.GenerateTestBlockWithTime(blockTime)
-		cert := ts.GenerateTestCertificate()
-		assert.NoError(t, stateBob.CommitBlock(i+1, blk, cert))
+		blk, cert := ts.GenerateTestBlockWithTime(i+1, blockTime)
+		assert.NoError(t, stateBob.CommitBlock(blk, cert))
 
 		blockTime = blockTime.Add(blockInterval)
 	}
diff --git a/sync/sync_test.go b/sync/sync_test.go
index 16c6f956..6b73d800 100644
--- a/sync/sync_test.go
+++ b/sync/sync_test.go
@@ -250,14 +250,12 @@ func TestTestNetFlags(t *testing.T) {
 func TestDownload(t *testing.T) {
 	td := setup(t, nil)
 
-	ourBlockHeight := td.state.LastBlockHeight()
-	// To make sure the peer is not synced,
-	// we add a block in past (more than 2 hours)
+	// LastBlockTime is the genesis time (Testnet)
+	// It should request blocks to get synced.
 
-	blk := td.GenerateTestBlock()
-	cert := td.GenerateTestCertificate()
+	blk, cert := td.GenerateTestBlock(td.RandHeight())
 	pid := td.RandPeerID()
-	msg := message.NewBlockAnnounceMessage(ourBlockHeight+LatestBlockInterval+1, blk, cert)
+	msg := message.NewBlockAnnounceMessage(blk, cert)
 
 	t.Run("try to download blocks, but the peer is not known", func(t *testing.T) {
 		assert.NoError(t, td.receivingNewMessage(td.sync, msg, pid))
diff --git a/types/block/block_test.go b/types/block/block_test.go
index 30105abf..f8dd82c1 100644
--- a/types/block/block_test.go
+++ b/types/block/block_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/fxamacker/cbor/v2"
 	"github.com/pactus-project/pactus/crypto/hash"
 	"github.com/pactus-project/pactus/types/block"
-	"github.com/pactus-project/pactus/types/certificate"
 	"github.com/pactus-project/pactus/util"
 	"github.com/pactus-project/pactus/util/simplemerkle"
 	"github.com/pactus-project/pactus/util/testsuite"
@@ -18,8 +17,23 @@ func TestBasicCheck(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
 	t.Run("No transactions", func(t *testing.T) {
-		b0 := ts.GenerateTestBlock()
-		b := block.NewBlock(b0.Header(), b0.PrevCertificate(), block.Txs{})
+		d := ts.DecodingHex(
+			"01" + // Version
+				"00000000" + // UnixTime
+				"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + // PrevBlockHash
+				"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + // StateRoot
+				"333333333333333333333333333333333333333333333333" + // SortitionSeed
+				"333333333333333333333333333333333333333333333333" +
+				"01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + // ProposerAddress
+				"04030201" + // PrevCert: Height
+				"0100" + // PrevCert: Round
+				"0401020304" + // PrevCert: Committers
+				"0102" + // PrevCert: Absentees
+				"b53d79e156e9417e010fa21f2b2a96bee6be46fcd233295d" +
+				"2f697cdb9e782b6112ac01c80d0d9d64c2320664c77fa2a6" + // PrevCert: Signature
+				"00") // Txs: Len
+
+		b, _ := block.FromBytes(d)
 
 		err := b.BasicCheck()
 		assert.ErrorIs(t, err, block.BasicCheckError{
@@ -28,7 +42,7 @@ func TestBasicCheck(t *testing.T) {
 	})
 
 	t.Run("Without the previous certificate", func(t *testing.T) {
-		b0 := ts.GenerateTestBlock()
+		b0, _ := ts.GenerateTestBlock(ts.RandHeight())
 		b := block.NewBlock(b0.Header(), nil, b0.Transactions())
 
 		err := b.BasicCheck()
@@ -38,10 +52,32 @@ func TestBasicCheck(t *testing.T) {
 	})
 
 	t.Run("Invalid certificate", func(t *testing.T) {
-		b0 := ts.GenerateTestBlock()
-		cert0 := b0.PrevCertificate()
-		invCert := certificate.NewCertificate(0, 0, cert0.Committers(), cert0.Absentees(), cert0.Signature())
-		b := block.NewBlock(b0.Header(), invCert, b0.Transactions())
+		d := ts.DecodingHex(
+			"01" + // Version
+				"00000000" + // UnixTime
+				"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + // PrevBlockHash
+				"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + // StateRoot
+				"333333333333333333333333333333333333333333333333" + // SortitionSeed
+				"333333333333333333333333333333333333333333333333" +
+				"01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + // ProposerAddress
+				"00000000" + // PrevCert: Height
+				"0100" + // PrevCert: Round
+				"0401020304" + // PrevCert: Committers
+				"0102" + // PrevCert: Absentees
+				"b53d79e156e9417e010fa21f2b2a96bee6be46fcd233295d" +
+				"2f697cdb9e782b6112ac01c80d0d9d64c2320664c77fa2a6" + // PrevCert: Signature
+				"01" + // Txs: Len
+				"00" + // Tx[0]: Flags
+				"01" + // Tx[0]: Version
+				"01000000" + // Tx[0]: LockTime
+				"01" + // Tx[0]: Fee
+				"00" + // Tx[0]: Memo
+				"01" + // Tx[0]: PayloadType
+				"00" + // Tx[0]: Sender (treasury)
+				"022222222222222222222222222222222222222222" + // Tx[0]: Receiver
+				"01") // Tx[0]: Amount
+
+		b, _ := block.FromBytes(d)
 
 		err := b.BasicCheck()
 		assert.ErrorIs(t, err, block.BasicCheckError{
@@ -50,17 +86,36 @@ func TestBasicCheck(t *testing.T) {
 	})
 
 	t.Run("Invalid transaction", func(t *testing.T) {
-		b0 := ts.GenerateTestBlock()
-		trxs0 := b0.Transactions()
-		invalidValKey := ts.RandValKey()
-		invSig := invalidValKey.Sign(trxs0[0].SignBytes())
-		trxs0[0].SetSignature(invSig)
+		d := ts.DecodingHex(
+			"01" + // Version
+				"00000000" + // UnixTime
+				"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + // PrevBlockHash
+				"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" + // StateRoot
+				"333333333333333333333333333333333333333333333333" + // SortitionSeed
+				"333333333333333333333333333333333333333333333333" +
+				"01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + // ProposerAddress
+				"04030201" + // PrevCert: Height
+				"0100" + // PrevCert: Round
+				"0401020304" + // PrevCert: Committers
+				"0102" + // PrevCert: Absentees
+				"b53d79e156e9417e010fa21f2b2a96bee6be46fcd233295d" +
+				"2f697cdb9e782b6112ac01c80d0d9d64c2320664c77fa2a6" + // PrevCert: Signature
+				"01" + // Txs: Len
+				"00" + // Tx[0]: Flags
+				"00" + // Tx[0]: Version
+				"00000000" + // Tx[0]: LockTime
+				"00" + // Tx[0]: Fee
+				"00" + // Tx[0]: Memo
+				"01" + // Tx[0]: PayloadType
+				"00" + // Tx[0]: Sender (treasury)
+				"022222222222222222222222222222222222222222" + // Tx[0]: Receiver
+				"01") // Tx[0]: Amount
 
-		b := block.NewBlock(b0.Header(), b0.PrevCertificate(), trxs0)
+		b, _ := block.FromBytes(d)
 
 		err := b.BasicCheck()
 		assert.ErrorIs(t, err, block.BasicCheckError{
-			Reason: "invalid transaction: invalid signature",
+			Reason: "invalid transaction: invalid version: 0",
 		})
 	})
 
@@ -87,7 +142,7 @@ func TestBasicCheck(t *testing.T) {
 				"00" + // Tx[0]: Memo
 				"01" + // Tx[0]: PayloadType
 				"00" + // Tx[0]: Sender (treasury)
-				"012222222222222222222222222222222222222222" + // Tx[0]: Receiver
+				"022222222222222222222222222222222222222222" + // Tx[0]: Receiver
 				"01") // Tx[0]: Amount
 
 		b, _ := block.FromBytes(d)
@@ -121,7 +176,7 @@ func TestBasicCheck(t *testing.T) {
 				"00" + // Tx[0]: Memo
 				"01" + // Tx[0]: PayloadType
 				"00" + // Tx[0]: Sender (treasury)
-				"012222222222222222222222222222222222222222" + // Tx[0]: Receiver
+				"022222222222222222222222222222222222222222" + // Tx[0]: Receiver
 				"01") // Tx[0]: Amount
 
 		_, err := block.FromBytes(d)
@@ -151,7 +206,7 @@ func TestBasicCheck(t *testing.T) {
 				"00" + // Tx[0]: Memo
 				"01" + // Tx[0]: PayloadType
 				"00" + // Tx[0]: Sender (treasury)
-				"012222222222222222222222222222222222222222" + // Tx[0]: Receiver
+				"022222222222222222222222222222222222222222" + // Tx[0]: Receiver
 				"01") // Tx[0]: Amount
 
 		b, _ := block.FromBytes(d)
@@ -197,25 +252,25 @@ func TestBasicCheck(t *testing.T) {
 func TestCBORMarshaling(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	b1 := ts.GenerateTestBlock()
-	bz1, err := cbor.Marshal(b1)
+	blk1, _ := ts.GenerateTestBlock(ts.RandHeight())
+	bz1, err := cbor.Marshal(blk1)
 	assert.NoError(t, err)
-	var b2 block.Block
-	err = cbor.Unmarshal(bz1, &b2)
+	var blk2 block.Block
+	err = cbor.Unmarshal(bz1, &blk2)
 	assert.NoError(t, err)
-	assert.NoError(t, b2.BasicCheck())
-	assert.Equal(t, b1.Hash(), b2.Hash())
+	assert.NoError(t, blk2.BasicCheck())
+	assert.Equal(t, blk1.Hash(), blk2.Hash())
 
-	assert.Equal(t, b1.Hash(), b2.Hash())
+	assert.Equal(t, blk1.Hash(), blk2.Hash())
 
-	err = cbor.Unmarshal([]byte{1}, &b2)
+	err = cbor.Unmarshal([]byte{1}, &blk2)
 	assert.Error(t, err)
 }
 
 func TestEncodingBlock(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	blk := ts.GenerateTestBlock()
+	blk, _ := ts.GenerateTestBlock(ts.RandHeight())
 	length := blk.SerializeSize()
 
 	for i := 0; i < length; i++ {
@@ -241,7 +296,7 @@ func TestEncodingBlock(t *testing.T) {
 func TestTxFromBytes(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	blk := ts.GenerateTestBlock()
+	blk, _ := ts.GenerateTestBlock(ts.RandHeight())
 	bs, _ := blk.Bytes()
 	_, err := block.FromBytes(bs)
 	assert.NoError(t, err)
@@ -310,13 +365,13 @@ func TestBlockHash(t *testing.T) {
 func TestMakeBlock(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	b0 := ts.GenerateTestBlock()
-	b1 := block.MakeBlock(1, b0.Header().Time(), b0.Transactions(),
-		b0.Header().PrevBlockHash(),
-		b0.Header().StateRoot(),
-		b0.PrevCertificate(),
-		b0.Header().SortitionSeed(),
-		b0.Header().ProposerAddress())
+	blk0, _ := ts.GenerateTestBlock(ts.RandHeight())
+	blk1 := block.MakeBlock(1, blk0.Header().Time(), blk0.Transactions(),
+		blk0.Header().PrevBlockHash(),
+		blk0.Header().StateRoot(),
+		blk0.PrevCertificate(),
+		blk0.Header().SortitionSeed(),
+		blk0.Header().ProposerAddress())
 
-	assert.Equal(t, b0.Hash(), b1.Hash())
+	assert.Equal(t, blk0.Hash(), blk1.Hash())
 }
diff --git a/types/certificate/certificate_test.go b/types/certificate/certificate_test.go
index 9eb2e676..ac997a86 100644
--- a/types/certificate/certificate_test.go
+++ b/types/certificate/certificate_test.go
@@ -40,18 +40,18 @@ func TestCertificate(t *testing.T) {
 func TestCertificateCBORMarshaling(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	c1 := ts.GenerateTestCertificate()
-	bz1, err := cbor.Marshal(c1)
+	cert1 := ts.GenerateTestCertificate(ts.RandHeight())
+	bz1, err := cbor.Marshal(cert1)
 	assert.NoError(t, err)
-	var c2 certificate.Certificate
-	err = cbor.Unmarshal(bz1, &c2)
+	var cert2 certificate.Certificate
+	err = cbor.Unmarshal(bz1, &cert2)
 	assert.NoError(t, err)
-	assert.NoError(t, c2.BasicCheck())
-	assert.Equal(t, c1.Hash(), c1.Hash())
+	assert.NoError(t, cert2.BasicCheck())
+	assert.Equal(t, cert1.Hash(), cert1.Hash())
 
-	assert.Equal(t, c1.Hash(), c2.Hash())
+	assert.Equal(t, cert1.Hash(), cert2.Hash())
 
-	err = cbor.Unmarshal([]byte{1}, &c2)
+	err = cbor.Unmarshal([]byte{1}, &cert2)
 	assert.Error(t, err)
 }
 
@@ -60,16 +60,16 @@ func TestCertificateSignBytes(t *testing.T) {
 
 	hash := ts.RandHash()
 	height := ts.RandHeight()
-	c1 := ts.GenerateTestCertificate()
-	bz := certificate.BlockCertificateSignBytes(hash, height, c1.Round())
-	assert.NotEqual(t, bz, certificate.BlockCertificateSignBytes(hash, height, c1.Round()+1))
-	assert.NotEqual(t, bz, certificate.BlockCertificateSignBytes(ts.RandHash(), height, c1.Round()))
+	cert := ts.GenerateTestCertificate(height)
+	bz := certificate.BlockCertificateSignBytes(hash, height, cert.Round())
+	assert.NotEqual(t, bz, certificate.BlockCertificateSignBytes(hash, height, cert.Round()+1))
+	assert.NotEqual(t, bz, certificate.BlockCertificateSignBytes(ts.RandHash(), height, cert.Round()))
 }
 
 func TestInvalidCertificate(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	cert0 := ts.GenerateTestCertificate()
+	cert0 := ts.GenerateTestCertificate(ts.RandHeight())
 
 	t.Run("Invalid height", func(t *testing.T) {
 		cert := certificate.NewCertificate(0, 0, cert0.Committers(), cert0.Absentees(), cert0.Signature())
@@ -143,22 +143,22 @@ func TestInvalidCertificate(t *testing.T) {
 func TestCertificateHash(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	temp := ts.GenerateTestCertificate()
+	cert0 := ts.GenerateTestCertificate(ts.RandHeight())
 
-	cert1 := certificate.NewCertificate(temp.Height(), temp.Round(),
-		[]int32{10, 18, 2, 6}, []int32{}, temp.Signature())
+	cert1 := certificate.NewCertificate(cert0.Height(), cert0.Round(),
+		[]int32{10, 18, 2, 6}, []int32{}, cert0.Signature())
 	assert.Equal(t, cert1.Committers(), []int32{10, 18, 2, 6})
 	assert.Equal(t, cert1.Absentees(), []int32{})
 	assert.NoError(t, cert1.BasicCheck())
 
-	cert2 := certificate.NewCertificate(temp.Height(), temp.Round(),
-		[]int32{10, 18, 2, 6}, []int32{2, 6}, temp.Signature())
+	cert2 := certificate.NewCertificate(cert0.Height(), cert0.Round(),
+		[]int32{10, 18, 2, 6}, []int32{2, 6}, cert0.Signature())
 	assert.Equal(t, cert2.Committers(), []int32{10, 18, 2, 6})
 	assert.Equal(t, cert2.Absentees(), []int32{2, 6})
 	assert.NoError(t, cert2.BasicCheck())
 
-	cert3 := certificate.NewCertificate(temp.Height(), temp.Round(),
-		[]int32{10, 18, 2, 6}, []int32{18}, temp.Signature())
+	cert3 := certificate.NewCertificate(cert0.Height(), cert0.Round(),
+		[]int32{10, 18, 2, 6}, []int32{18}, cert0.Signature())
 	assert.Equal(t, cert3.Committers(), []int32{10, 18, 2, 6})
 	assert.Equal(t, cert3.Absentees(), []int32{18})
 	assert.NoError(t, cert3.BasicCheck())
@@ -167,7 +167,7 @@ func TestCertificateHash(t *testing.T) {
 func TestEncodingCertificate(t *testing.T) {
 	ts := testsuite.NewTestSuite(t)
 
-	cert1 := ts.GenerateTestCertificate()
+	cert1 := ts.GenerateTestCertificate(ts.RandHeight())
 	length := cert1.SerializeSize()
 
 	for i := 0; i < length; i++ {
diff --git a/util/testsuite/testsuite.go b/util/testsuite/testsuite.go
index 1c161256..9fa6bcd7 100644
--- a/util/testsuite/testsuite.go
+++ b/util/testsuite/testsuite.go
@@ -254,21 +254,24 @@ func (ts *TestSuite) GenerateTestValidator(number int32) (*validator.Validator,
 }
 
 // GenerateTestBlockWithProposer generates a block with the give proposer address for testing purposes.
-func (ts *TestSuite) GenerateTestBlockWithProposer(proposer crypto.Address) *block.Block {
-	return ts.generateTestBlock(proposer, util.Now())
+func (ts *TestSuite) GenerateTestBlockWithProposer(height uint32, proposer crypto.Address,
+) (*block.Block, *certificate.Certificate) {
+	return ts.generateTestBlock(height, proposer, util.Now())
 }
 
 // GenerateTestBlockWithTime generates a block with the given time for testing purposes.
-func (ts *TestSuite) GenerateTestBlockWithTime(time time.Time) *block.Block {
-	return ts.generateTestBlock(ts.RandValAddress(), time)
+func (ts *TestSuite) GenerateTestBlockWithTime(height uint32, time time.Time,
+) (*block.Block, *certificate.Certificate) {
+	return ts.generateTestBlock(height, ts.RandValAddress(), time)
 }
 
 // GenerateTestBlock generates a block for testing purposes.
-func (ts *TestSuite) GenerateTestBlock() *block.Block {
-	return ts.generateTestBlock(ts.RandValAddress(), util.Now())
+func (ts *TestSuite) GenerateTestBlock(height uint32) (*block.Block, *certificate.Certificate) {
+	return ts.generateTestBlock(height, ts.RandValAddress(), util.Now())
 }
 
-func (ts *TestSuite) generateTestBlock(proposer crypto.Address, time time.Time) *block.Block {
+func (ts *TestSuite) generateTestBlock(height uint32, proposer crypto.Address, time time.Time,
+) (*block.Block, *certificate.Certificate) {
 	txs := block.NewTxs()
 	tx1, _ := ts.GenerateTestTransferTx()
 	tx2, _ := ts.GenerateTestSortitionTx()
@@ -282,18 +285,25 @@ func (ts *TestSuite) generateTestBlock(proposer crypto.Address, time time.Time)
 	txs.Append(tx4)
 	txs.Append(tx5)
 
-	cert := ts.GenerateTestCertificate()
+	prevCert := ts.GenerateTestCertificate(height - 1)
+	prevBlockHash := ts.RandHash()
+	if height == 1 {
+		prevCert = nil
+		prevBlockHash = hash.UndefHash
+	}
+	blockCert := ts.GenerateTestCertificate(height)
 	header := block.NewHeader(1, time,
 		ts.RandHash(),
-		ts.RandHash(),
+		prevBlockHash,
 		ts.RandSeed(),
 		proposer)
 
-	return block.NewBlock(header, cert, txs)
+	blk := block.NewBlock(header, prevCert, txs)
+	return blk, blockCert
 }
 
 // GenerateTestCertificate generates a certificate for testing purposes.
-func (ts *TestSuite) GenerateTestCertificate() *certificate.Certificate {
+func (ts *TestSuite) GenerateTestCertificate(height uint32) *certificate.Certificate {
 	sig := ts.RandBLSSignature()
 
 	c1 := ts.RandInt32NonZero(10)
@@ -301,7 +311,7 @@ func (ts *TestSuite) GenerateTestCertificate() *certificate.Certificate {
 	c3 := ts.RandInt32NonZero(10) + 20
 	c4 := ts.RandInt32NonZero(10) + 30
 	return certificate.NewCertificate(
-		ts.RandHeight(),
+		height,
 		ts.RandRound(),
 		[]int32{c1, c2, c3, c4},
 		[]int32{c2},
@@ -311,11 +321,11 @@ func (ts *TestSuite) GenerateTestCertificate() *certificate.Certificate {
 // GenerateTestProposal generates a proposal for testing purposes.
 func (ts *TestSuite) GenerateTestProposal(height uint32, round int16) (*proposal.Proposal, *bls.ValidatorKey) {
 	valKey := ts.RandValKey()
-	b := ts.GenerateTestBlockWithProposer(valKey.Address())
-	p := proposal.NewProposal(height, round, b)
-	ts.HelperSignProposal(valKey, p)
+	blk, _ := ts.GenerateTestBlockWithProposer(height, valKey.Address())
+	proposal := proposal.NewProposal(height, round, blk)
+	ts.HelperSignProposal(valKey, proposal)
 
-	return p, valKey
+	return proposal, valKey
 }
 
 // GenerateTestTransferTx generates a transfer transaction for testing purposes.