github.com/turingchain2020/turingchain@v1.1.21/blockchain/chunkshard.go (about)

     1  // Copyright Turing Corp. 2018 All Rights Reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package blockchain
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/turingchain2020/turingchain/common"
    14  	dbm "github.com/turingchain2020/turingchain/common/db"
    15  	"github.com/turingchain2020/turingchain/types"
    16  )
    17  
    18  var (
    19  	//ErrNoBlockToChunk ...
    20  	ErrNoBlockToChunk = errors.New("ErrNoBlockToChunk")
    21  	//ErrNoChunkInfoToDownLoad ...
    22  	ErrNoChunkInfoToDownLoad = errors.New("ErrNoChunkInfoToDownLoad")
    23  )
    24  
    25  const (
    26  	// OnceMaxChunkNum 每次检测最大生成chunk数
    27  	OnceMaxChunkNum int32 = 30
    28  	// DelRollbackChunkNum 删除小于当前chunk为DelRollbackChunkNum
    29  	DelRollbackChunkNum int32 = 10
    30  	// MaxReqChunkRecord 每次请求最大MaxReqChunkRecord个chunk的record
    31  	MaxReqChunkRecord int32 = 100
    32  )
    33  
    34  func (chain *BlockChain) chunkProcessRoutine() {
    35  	defer chain.tickerwg.Done()
    36  
    37  	// 1.60s检测一次是否可以删除本地的body数据
    38  	// 2.10s检测一次是否可以触发归档操作
    39  
    40  	checkDelTicker := time.NewTicker(time.Minute)
    41  	checkGenChunkTicker := time.NewTicker(10 * time.Second)
    42  	for {
    43  		select {
    44  		case <-chain.quit:
    45  			return
    46  		case <-checkDelTicker.C:
    47  			go chain.CheckDeleteBlockBody()
    48  		case <-checkGenChunkTicker.C:
    49  			//主动查询当前未归档,然后进行触发
    50  			go chain.CheckGenChunkNum()
    51  		}
    52  	}
    53  }
    54  
    55  // CheckGenChunkNum 检测是否需要生成chunkNum
    56  func (chain *BlockChain) CheckGenChunkNum() {
    57  	if !atomic.CompareAndSwapInt32(&chain.processingGenChunk, 0, 1) {
    58  		// 保证同一时刻只存在一个该协程
    59  		return
    60  	}
    61  	defer atomic.StoreInt32(&chain.processingGenChunk, 0)
    62  	safetyChunkNum, _, _ := chain.CalcSafetyChunkInfo(chain.GetBlockHeight())
    63  	for i := int32(0); i < OnceMaxChunkNum; i++ {
    64  		chunkNum := chain.getMaxSerialChunkNum() + 1
    65  		if chunkNum > safetyChunkNum {
    66  			break
    67  		}
    68  		if err := chain.chunkShardHandle(chunkNum); err != nil {
    69  			break
    70  		}
    71  		if err := chain.updateMaxSerialChunkNum(chunkNum); err != nil {
    72  			break
    73  		}
    74  	}
    75  }
    76  
    77  // CheckDeleteBlockBody 检测是否需要删除已经归档BlockBody
    78  func (chain *BlockChain) CheckDeleteBlockBody() {
    79  	if !atomic.CompareAndSwapInt32(&chain.processingDeleteChunk, 0, 1) {
    80  		// 保证同一时刻只存在一个该协程
    81  		return
    82  	}
    83  	atomic.StoreInt32(&chain.processingDeleteChunk, 1)
    84  	defer atomic.StoreInt32(&chain.processingDeleteChunk, 0)
    85  	const onceDelChunkNum = 100 // 每次walkOverDeleteChunk的最大删除chunk个数
    86  	var count int64
    87  	var kvs []*types.KeyValue
    88  	toDelete := chain.blockStore.GetMaxDeletedChunkNum() + 1
    89  	chainlog.Info("CheckDeleteBlockBody start", "start", toDelete)
    90  
    91  	for toDelete+int64(DelRollbackChunkNum) < atomic.LoadInt64(&chain.maxSerialChunkNum) && count < onceDelChunkNum {
    92  		chainlog.Info("CheckDeleteBlockBody toDelete", "toDelete", toDelete)
    93  		kv := chain.DeleteBlockBody(toDelete)
    94  		kvs = append(kvs, kv...)
    95  		toDelete++
    96  		count++
    97  	}
    98  	atomic.AddInt64(&chain.deleteChunkCount, count)
    99  	batch := chain.GetDB().NewBatch(true)
   100  	batch.Reset()
   101  	for _, kv := range kvs {
   102  		batch.Delete(kv.GetKey())
   103  	}
   104  	if count != 0 {
   105  		dbm.MustWrite(batch)
   106  		if err := chain.blockStore.SetMaxDeletedChunkNum(toDelete - 1); err != nil {
   107  			chainlog.Error("CheckDeleteBlockBody", "SetMaxDeletedChunkNum error", err)
   108  		}
   109  	}
   110  
   111  	//删除超过100个chunk则进行数据库压缩
   112  	if atomic.LoadInt64(&chain.deleteChunkCount) >= 100 {
   113  		now := time.Now()
   114  		start := []byte("CHAIN-body-body-")
   115  		limit := make([]byte, len(start))
   116  		copy(limit, start)
   117  		limit[len(limit)-1]++
   118  		if err := chain.blockStore.db.CompactRange(start, limit); err != nil {
   119  			chainlog.Error("walkOverDeleteChunk", "CompactRange error", err)
   120  			return
   121  		}
   122  		chainlog.Info("walkOverDeleteChunk", "CompactRange time cost", time.Since(now))
   123  		atomic.StoreInt64(&chain.deleteChunkCount, 0)
   124  	}
   125  
   126  }
   127  
   128  // DeleteBlockBody del chunk body
   129  func (chain *BlockChain) DeleteBlockBody(chunkNum int64) []*types.KeyValue {
   130  	value, err := chain.blockStore.GetKey(calcChunkNumToHash(chunkNum))
   131  	if err != nil {
   132  		return nil
   133  	}
   134  	chunk := &types.ChunkInfo{}
   135  	err = types.Decode(value, chunk)
   136  	if err != nil {
   137  		return nil
   138  	}
   139  	var kvs []*types.KeyValue
   140  	for i := chunk.Start; i <= chunk.End; i++ {
   141  		kv, err := chain.deleteBlockBody(i)
   142  		if err != nil {
   143  			continue
   144  		}
   145  		kvs = append(kvs, kv...)
   146  	}
   147  	return kvs
   148  }
   149  
   150  func (chain *BlockChain) deleteBlockBody(height int64) (kvs []*types.KeyValue, err error) {
   151  	hash, err := chain.blockStore.GetBlockHashByHeight(height)
   152  	if err != nil {
   153  		chainlog.Error("deleteBlockBody GetBlockHashByHeight", "height", height, "error", err)
   154  		return nil, err
   155  	}
   156  	kvs, err = delBlockBodyTable(chain.blockStore.db, height, hash)
   157  	if err != nil {
   158  		chainlog.Error("deleteBlockBody delBlockBodyTable", "height", height, "error", err)
   159  		return nil, err
   160  	}
   161  	return kvs, err
   162  }
   163  
   164  func (chain *BlockChain) chunkShardHandle(chunkNum int64) error {
   165  	// 1、计算当前chunk信息;
   166  	// 2、生成归档记录;
   167  	// 3、生成辅助删除信息;
   168  	// 4、保存归档记录信息;
   169  	// 5、更新chunk最大连续序列号
   170  	start := chunkNum * chain.cfg.ChunkblockNum
   171  	end := start + chain.cfg.ChunkblockNum - 1
   172  	chunkHash, bodys, err := chain.genChunkBlocks(start, end)
   173  	if err != nil {
   174  		chainlog.Error("chunkShardHandle", "chunkNum", chunkNum, "start", start, "end", end, "err", err)
   175  		return err
   176  	}
   177  	chunk := &types.ChunkInfo{
   178  		ChunkNum:  chunkNum,
   179  		ChunkHash: chunkHash,
   180  		Start:     start,
   181  		End:       end,
   182  	}
   183  	kvs := genChunkRecord(chunk, bodys)
   184  	chain.saveChunkRecord(kvs)
   185  	if err := chain.notifyStoreChunkToP2P(chunk); err != nil {
   186  		return err
   187  	}
   188  	chainlog.Info("chunkShardHandle", "chunkNum", chunk.ChunkNum, "start", start, "end", end, "chunkHash", common.ToHex(chunkHash))
   189  	return nil
   190  }
   191  func (chain *BlockChain) getMaxSerialChunkNum() int64 {
   192  	return atomic.LoadInt64(&chain.maxSerialChunkNum)
   193  }
   194  
   195  func (chain *BlockChain) updateMaxSerialChunkNum(chunkNum int64) error {
   196  	err := chain.blockStore.SetMaxSerialChunkNum(chunkNum)
   197  	if err != nil {
   198  		return err
   199  	}
   200  	atomic.StoreInt64(&chain.maxSerialChunkNum, chunkNum)
   201  	return nil
   202  }
   203  
   204  func (chain *BlockChain) notifyStoreChunkToP2P(data *types.ChunkInfo) error {
   205  	if chain.client == nil {
   206  		chainlog.Error("notifyStoreChunkToP2P: chain client not bind message queue.")
   207  		return fmt.Errorf("no message queue")
   208  	}
   209  
   210  	req := &types.ChunkInfoMsg{
   211  		ChunkHash: data.ChunkHash,
   212  		Start:     data.Start,
   213  		End:       data.End,
   214  	}
   215  
   216  	chainlog.Debug("notifyStoreChunkToP2P", "chunknum", data.ChunkNum, "block start height",
   217  		data.Start, "block end height", data.End, "chunk hash", common.ToHex(data.ChunkHash))
   218  
   219  	msg := chain.client.NewMessage("p2p", types.EventNotifyStoreChunk, req)
   220  	err := chain.client.Send(msg, true)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	resp, err := chain.client.Wait(msg)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	if data, ok := resp.Data.(*types.Reply); ok && data.IsOk {
   230  		return nil
   231  	}
   232  	return fmt.Errorf("p2p process error")
   233  }
   234  
   235  func (chain *BlockChain) genChunkBlocks(start, end int64) ([]byte, *types.BlockBodys, error) {
   236  	var hashs types.ReplyHashes
   237  	var bodys types.BlockBodys
   238  	for i := start; i <= end; i++ {
   239  		detail, err := chain.blockStore.LoadBlock(i, nil)
   240  		if err != nil {
   241  			return nil, nil, err
   242  		}
   243  		body := chain.blockStore.BlockdetailToBlockBody(detail)
   244  		bodys.Items = append(bodys.Items, body)
   245  		hashs.Hashes = append(hashs.Hashes, body.Hash)
   246  	}
   247  	return hashs.Hash(), &bodys, nil
   248  }
   249  
   250  func (chain *BlockChain) saveChunkRecord(kvs []*types.KeyValue) {
   251  	chain.blockStore.mustSaveKvset(&types.LocalDBSet{KV: kvs})
   252  }
   253  
   254  // GetChunkBlockBody 从localdb本地获取chunkbody
   255  func (chain *BlockChain) GetChunkBlockBody(req *types.ChunkInfoMsg) (*types.BlockBodys, error) {
   256  	if req == nil || req.Start > req.End {
   257  		return nil, types.ErrInvalidParam
   258  	}
   259  	_, bodys, err := chain.genChunkBlocks(req.Start, req.End)
   260  	return bodys, err
   261  }
   262  
   263  // AddChunkRecord ...
   264  func (chain *BlockChain) AddChunkRecord(req *types.ChunkRecords) {
   265  	dbset := &types.LocalDBSet{}
   266  	for _, info := range req.Infos {
   267  		dbset.KV = append(dbset.KV, &types.KeyValue{Key: calcRecvChunkNumToHash(info.ChunkNum), Value: types.Encode(info)})
   268  	}
   269  	if len(dbset.KV) > 0 {
   270  		chain.blockStore.mustSaveKvset(dbset)
   271  	}
   272  }
   273  
   274  // GetChunkRecord ...
   275  func (chain *BlockChain) GetChunkRecord(req *types.ReqChunkRecords) (*types.ChunkRecords, error) {
   276  	if req.Start > req.End {
   277  		return nil, types.ErrInvalidParam
   278  	}
   279  	rep := &types.ChunkRecords{}
   280  	for i := req.Start; i <= req.End; i++ {
   281  		key := append([]byte{}, calcChunkNumToHash(i)...)
   282  		value, err := chain.blockStore.GetKey(key)
   283  		if err != nil {
   284  			return nil, types.ErrNotFound
   285  		}
   286  		chunk := &types.ChunkInfo{}
   287  		err = types.Decode(value, chunk)
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  		rep.Infos = append(rep.Infos, chunk)
   292  	}
   293  	if len(rep.Infos) == 0 {
   294  		return nil, types.ErrNotFound
   295  	}
   296  	return rep, nil
   297  }
   298  
   299  // GetCurRecvChunkNum ...
   300  func (chain *BlockChain) GetCurRecvChunkNum() int64 {
   301  	return chain.blockStore.getCurChunkNum(RecvChunkNumToHash)
   302  }
   303  
   304  // GetCurChunkNum ...
   305  func (chain *BlockChain) GetCurChunkNum() int64 {
   306  	return chain.blockStore.getCurChunkNum(ChunkNumToHash)
   307  }
   308  
   309  // CalcSafetyChunkInfo 计算安全的chunkNum用于生成chunk时候或者删除时候
   310  func (chain *BlockChain) CalcSafetyChunkInfo(height int64) (chunkNum, start, end int64) {
   311  	height = chain.calcSafetyChunkHeight(height)
   312  	if height < 0 {
   313  		return -1, 0, 0
   314  	}
   315  	return calcChunkInfo(chain.cfg, height)
   316  }
   317  
   318  func (chain *BlockChain) calcSafetyChunkHeight(height int64) int64 {
   319  	return height - MaxRollBlockNum - chain.cfg.ChunkblockNum
   320  }
   321  
   322  // CalcChunkInfo 主要用于计算验证
   323  func (chain *BlockChain) CalcChunkInfo(height int64) (chunkNum, start, end int64) {
   324  	return calcChunkInfo(chain.cfg, height)
   325  }
   326  
   327  func calcChunkInfo(cfg *types.BlockChain, height int64) (chunkNum, start, end int64) {
   328  	chunkNum = height / cfg.ChunkblockNum
   329  	start = chunkNum * cfg.ChunkblockNum
   330  	end = start + cfg.ChunkblockNum - 1
   331  	return chunkNum, start, end
   332  }
   333  
   334  // genChunkRecord 生成归档索引 1:blockhash--->chunkhash 2:blockHeight--->chunkhash
   335  func genChunkRecord(chunk *types.ChunkInfo, bodys *types.BlockBodys) []*types.KeyValue {
   336  	var kvs []*types.KeyValue
   337  	kvs = append(kvs, &types.KeyValue{Key: calcChunkNumToHash(chunk.ChunkNum), Value: types.Encode(chunk)})
   338  	kvs = append(kvs, &types.KeyValue{Key: calcChunkHashToNum(chunk.ChunkHash), Value: types.Encode(chunk)})
   339  	return kvs
   340  }