Skip to content

Commit 6b1e339

Browse files
authored
move recover related code from blockchain to recoverer (iotexproject#1665)
* move recover related code from blockchain to recoverer * fix recovery logic / change to DeleteBlockToTarget() / fix unit tests
1 parent 12712b3 commit 6b1e339

File tree

7 files changed

+62
-111
lines changed

7 files changed

+62
-111
lines changed

blockchain/blockchain.go

+1-64
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package blockchain
99
import (
1010
"context"
1111
"math/big"
12-
"os"
1312
"strconv"
1413
"sync"
1514
"sync/atomic"
@@ -44,7 +43,6 @@ import (
4443
"github.com/iotexproject/iotex-core/pkg/log"
4544
"github.com/iotexproject/iotex-core/pkg/prometheustimer"
4645
"github.com/iotexproject/iotex-core/pkg/util/byteutil"
47-
"github.com/iotexproject/iotex-core/pkg/util/fileutil"
4846
"github.com/iotexproject/iotex-core/state"
4947
"github.com/iotexproject/iotex-core/state/factory"
5048
)
@@ -82,7 +80,7 @@ type Blockchain interface {
8280
BlockFooterByHeight(height uint64) (*block.Footer, error)
8381
// BlockFooterByHash return block footer by hash
8482
BlockFooterByHash(h hash.Hash256) (*block.Footer, error)
85-
// GetFactory returns the state factory
83+
// Factory returns the state factory
8684
Factory() factory.Factory
8785
// BlockDAO returns the block dao
8886
BlockDAO() blockdao.BlockDAO
@@ -94,8 +92,6 @@ type Blockchain interface {
9492
TipHash() hash.Hash256
9593
// TipHeight returns tip block's height
9694
TipHeight() uint64
97-
// RecoverChainAndState recovers the chain to target height and refresh state db if necessary
98-
RecoverChainAndState(targetHeight uint64) error
9995
// Genesis returns the genesis
10096
Genesis() genesis.Genesis
10197

@@ -620,31 +616,6 @@ func (bc *blockchain) SimulateExecution(caller address.Address, ex *action.Execu
620616
return evm.ExecuteContract(ctx, ws, ex, bc.dao.GetBlockHash)
621617
}
622618

623-
// RecoverChainAndState recovers the chain to target height and refresh state db if necessary
624-
func (bc *blockchain) RecoverChainAndState(targetHeight uint64) error {
625-
var buildStateFromScratch bool
626-
stateHeight, err := bc.sf.Height()
627-
if err != nil {
628-
return err
629-
}
630-
if stateHeight == 0 {
631-
buildStateFromScratch = true
632-
}
633-
if targetHeight > 0 {
634-
if err := bc.recoverToHeight(targetHeight); err != nil {
635-
return errors.Wrapf(err, "failed to recover blockchain to target height %d", targetHeight)
636-
}
637-
if stateHeight > bc.tipHeight {
638-
buildStateFromScratch = true
639-
}
640-
}
641-
642-
if buildStateFromScratch {
643-
return bc.refreshStateDB()
644-
}
645-
return nil
646-
}
647-
648619
func (bc *blockchain) Genesis() genesis.Genesis {
649620
return bc.config.Genesis
650621
}
@@ -1062,40 +1033,6 @@ func (bc *blockchain) emitToSubscribers(blk *block.Block) {
10621033
}
10631034
}
10641035

1065-
// RecoverToHeight recovers the blockchain to target height
1066-
func (bc *blockchain) recoverToHeight(targetHeight uint64) error {
1067-
for bc.tipHeight > targetHeight {
1068-
if err := bc.dao.DeleteTipBlock(); err != nil {
1069-
return err
1070-
}
1071-
bc.tipHeight--
1072-
}
1073-
return nil
1074-
}
1075-
1076-
// RefreshStateDB deletes the existing state DB and creates a new one with state changes from genesis block
1077-
func (bc *blockchain) refreshStateDB() error {
1078-
// Delete existing state DB and reinitialize it
1079-
if fileutil.FileExists(bc.config.Chain.TrieDBPath) && os.Remove(bc.config.Chain.TrieDBPath) != nil {
1080-
return errors.New("failed to delete existing state DB")
1081-
}
1082-
if err := DefaultStateFactoryOption()(bc, bc.config); err != nil {
1083-
return errors.Wrap(err, "failed to reinitialize state DB")
1084-
}
1085-
1086-
ctx := protocol.WithRunActionsCtx(context.Background(), protocol.RunActionsCtx{
1087-
BlockTimeStamp: time.Unix(bc.config.Genesis.Timestamp, 0),
1088-
Registry: bc.registry,
1089-
})
1090-
if err := bc.sf.Start(ctx); err != nil {
1091-
return errors.Wrap(err, "failed to start state factory")
1092-
}
1093-
if err := bc.sf.Stop(context.Background()); err != nil {
1094-
return errors.Wrap(err, "failed to stop state factory")
1095-
}
1096-
return nil
1097-
}
1098-
10991036
func (bc *blockchain) createGrantRewardAction(rewardType int, height uint64) (action.SealedEnvelope, error) {
11001037
gb := action.GrantRewardBuilder{}
11011038
grant := gb.SetRewardType(rewardType).SetHeight(height).Build()

blockchain/blockdao/blockdao.go

+23-14
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ type (
9999
GetReceipts(uint64) ([]*action.Receipt, error)
100100
PutBlock(*block.Block) error
101101
Commit() error
102-
DeleteTipBlock() error
102+
DeleteBlockToTarget(uint64) error
103103
IndexFile(uint64, []byte) error
104104
GetFileIndex(uint64) ([]byte, error)
105105
KVStore() db.KVStore
@@ -302,26 +302,35 @@ func (dao *blockDAO) PutBlock(blk *block.Block) error {
302302
return dao.indexer.Commit()
303303
}
304304

305-
func (dao *blockDAO) DeleteTipBlock() error {
305+
func (dao *blockDAO) DeleteBlockToTarget(targetHeight uint64) error {
306306
dao.mutex.Lock()
307307
defer dao.mutex.Unlock()
308-
// Obtain tip block hash
309-
hash, err := dao.getTipHash()
308+
tipHeight, err := dao.getTipHeight()
310309
if err != nil {
311-
return errors.Wrap(err, "failed to get tip block hash")
312-
}
313-
blk, err := dao.getBlock(hash)
314-
if err != nil {
315-
return errors.Wrap(err, "failed to get tip block")
310+
return err
316311
}
317-
// delete block index if there's indexer
318-
if dao.indexer != nil {
319-
if err := dao.indexer.DeleteTipBlock(blk); err != nil {
312+
for tipHeight > targetHeight {
313+
// Obtain tip block hash
314+
h, err := dao.getTipHash()
315+
if err != nil {
316+
return errors.Wrap(err, "failed to get tip block hash")
317+
}
318+
blk, err := dao.getBlock(h)
319+
if err != nil {
320+
return errors.Wrap(err, "failed to get tip block")
321+
}
322+
// delete block index if there's indexer
323+
if dao.indexer != nil {
324+
if err := dao.indexer.DeleteTipBlock(blk); err != nil {
325+
return err
326+
}
327+
}
328+
if err := dao.deleteTipBlock(); err != nil {
320329
return err
321330
}
331+
tipHeight--
322332
}
323-
324-
return dao.deleteTipBlock()
333+
return nil
325334
}
326335

327336
func (dao *blockDAO) IndexFile(height uint64, index []byte) error {

blockchain/blockdao/blockdao_test.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ func TestBlockDAO(t *testing.T) {
251251
require.NoError(err)
252252
prevTipHash, err := dao.GetBlockHash(prevTipHeight)
253253
require.NoError(err)
254-
require.NoError(dao.DeleteTipBlock())
254+
require.NoError(dao.DeleteBlockToTarget(prevTipHeight - 1))
255255
tipHeight, err := indexer.GetBlockchainHeight()
256256
require.NoError(err)
257257
require.EqualValues(prevTipHeight-1, tipHeight)
@@ -293,8 +293,6 @@ func TestBlockDAO(t *testing.T) {
293293
}
294294
}
295295
}
296-
// cannot delete genesis block
297-
require.Error(dao.DeleteTipBlock())
298296
}
299297

300298
t.Run("In-memory KV Store for blocks", func(t *testing.T) {

e2etest/local_test.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -524,20 +524,17 @@ func TestStartExistingBlockchain(t *testing.T) {
524524
testutil.CleanupPath(t, testTriePath)
525525
require.NoError(svr.Stop(ctx))
526526
require.NoError(svr.ChainService(cfg.Chain.ID).Blockchain().Start(ctx))
527-
// Refresh state DB
528-
require.NoError(bc.RecoverChainAndState(0))
527+
height, _ := bc.Factory().Height()
528+
require.Equal(bc.TipHeight(), height)
529+
require.Equal(uint64(5), height)
529530
require.NoError(svr.ChainService(cfg.Chain.ID).Blockchain().Stop(ctx))
530531
svr, err = itx.NewServer(cfg)
531532
require.NoError(err)
532-
// Build states from height 1 to tip
533533
require.NoError(svr.Start(ctx))
534534
bc = svr.ChainService(chainID).Blockchain()
535-
height, _ := bc.Factory().Height()
536-
require.Equal(bc.TipHeight(), height)
537-
538535
// Recover to height 3 from empty state DB
539536
testutil.CleanupPath(t, testTriePath)
540-
require.NoError(bc.RecoverChainAndState(3))
537+
require.NoError(bc.BlockDAO().DeleteBlockToTarget(3))
541538
require.NoError(svr.Stop(ctx))
542539
svr, err = itx.NewServer(cfg)
543540
require.NoError(err)
@@ -549,7 +546,8 @@ func TestStartExistingBlockchain(t *testing.T) {
549546
require.Equal(uint64(3), height)
550547

551548
// Recover to height 2 from an existing state DB with Height 3
552-
require.NoError(bc.RecoverChainAndState(2))
549+
testutil.CleanupPath(t, testTriePath)
550+
require.NoError(bc.BlockDAO().DeleteBlockToTarget(2))
553551
require.NoError(svr.Stop(ctx))
554552
svr, err = itx.NewServer(cfg)
555553
require.NoError(err)

test/mock/mock_blockchain/mock_blockchain.go

-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/mock/mock_blockdao/mock_blockdao.go

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/staterecoverer/staterecoverer.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ import (
1515
glog "log"
1616
"os"
1717

18+
"github.com/pkg/errors"
1819
"go.uber.org/zap"
1920

21+
"github.com/iotexproject/iotex-core/blockchain"
2022
"github.com/iotexproject/iotex-core/blockchain/genesis"
2123
"github.com/iotexproject/iotex-core/config"
2224
"github.com/iotexproject/iotex-core/pkg/log"
25+
"github.com/iotexproject/iotex-core/pkg/util/fileutil"
2326
"github.com/iotexproject/iotex-core/server/itx"
2427
)
2528

@@ -68,8 +71,28 @@ func main() {
6871
log.L().Fatal("Failed to stop blockchain")
6972
}
7073
}()
71-
72-
if err := bc.RecoverChainAndState(uint64(recoveryHeight)); err != nil {
74+
if err := recoverChainAndState(bc, cfg, uint64(recoveryHeight)); err != nil {
7375
log.L().Fatal("Failed to recover chain and state.", zap.Error(err))
76+
} else {
77+
log.S().Infof("Success to recover chain and state to target height %d", recoveryHeight)
78+
}
79+
}
80+
81+
// recoverChainAndState recovers the chain to target height and refresh state db if necessary
82+
func recoverChainAndState(bc blockchain.Blockchain, cfg config.Config, targetHeight uint64) error {
83+
// recover the blockchain to target height(blockDAO)
84+
if err := bc.BlockDAO().DeleteBlockToTarget(targetHeight); err != nil {
85+
return errors.Wrapf(err, "failed to recover blockchain to target height %d", targetHeight)
86+
}
87+
stateHeight, err := bc.Factory().Height()
88+
if err != nil {
89+
return err
90+
}
91+
if targetHeight < stateHeight {
92+
// delete existing state DB (build from scratch)
93+
if fileutil.FileExists(cfg.Chain.TrieDBPath) && os.Remove(cfg.Chain.TrieDBPath) != nil {
94+
return errors.New("failed to delete existing state DB")
95+
}
7496
}
97+
return nil
7598
}

0 commit comments

Comments
 (0)