Commit 2b61b2b1 authored by linj's avatar linj Committed by vipwzw

update chain33

parent 6252a9f1
...@@ -25,17 +25,20 @@ import ( ...@@ -25,17 +25,20 @@ import (
//var //var
var ( var (
blockLastHeight = []byte("blockLastHeight") blockLastHeight = []byte("blockLastHeight")
bodyPerfix = []byte("Body:") bodyPrefix = []byte("Body:")
LastSequence = []byte("LastSequence") LastSequence = []byte("LastSequence")
headerPerfix = []byte("Header:") headerPrefix = []byte("Header:")
heightToHeaderPerfix = []byte("HH:") heightToHeaderPrefix = []byte("HH:")
hashPerfix = []byte("Hash:") hashPrefix = []byte("Hash:")
tdPerfix = []byte("TD:") tdPrefix = []byte("TD:")
heightToHashKeyPerfix = []byte("Height:") heightToHashKeyPrefix = []byte("Height:")
seqToHashKey = []byte("Seq:") seqToHashKey = []byte("Seq:")
HashToSeqPerfix = []byte("HashToSeq:") HashToSeqPrefix = []byte("HashToSeq:")
seqCBPrefix = []byte("SCB:") seqCBPrefix = []byte("SCB:")
seqCBLastNumPrefix = []byte("SCBL:") seqCBLastNumPrefix = []byte("SCBL:")
paraSeqToHashKey = []byte("ParaSeq:")
HashToParaSeqPrefix = []byte("HashToParaSeq:")
LastParaSequence = []byte("LastParaSequence")
storeLog = chainlog.New("submodule", "store") storeLog = chainlog.New("submodule", "store")
AddBlock int64 = 1 AddBlock int64 = 1
DelBlock int64 = 2 DelBlock int64 = 2
...@@ -44,15 +47,15 @@ var ( ...@@ -44,15 +47,15 @@ var (
//GetLocalDBKeyList 获取本地键值列表 //GetLocalDBKeyList 获取本地键值列表
func GetLocalDBKeyList() [][]byte { func GetLocalDBKeyList() [][]byte {
return [][]byte{ return [][]byte{
blockLastHeight, bodyPerfix, LastSequence, headerPerfix, heightToHeaderPerfix, blockLastHeight, bodyPrefix, LastSequence, headerPrefix, heightToHeaderPrefix,
hashPerfix, tdPerfix, heightToHashKeyPerfix, seqToHashKey, HashToSeqPerfix, hashPrefix, tdPrefix, heightToHashKeyPrefix, seqToHashKey, HashToSeqPrefix,
seqCBPrefix, seqCBLastNumPrefix, tempBlockKey, lastTempBlockKey, seqCBPrefix, seqCBLastNumPrefix, tempBlockKey, lastTempBlockKey, LastParaSequence,
} }
} }
//存储block hash对应的blockbody信息 //存储block hash对应的blockbody信息
func calcHashToBlockBodyKey(hash []byte) []byte { func calcHashToBlockBodyKey(hash []byte) []byte {
return append(bodyPerfix, hash...) return append(bodyPrefix, hash...)
} }
//并发访问的可能性(每次开辟新内存) //并发访问的可能性(每次开辟新内存)
...@@ -61,42 +64,65 @@ func calcSeqCBKey(name []byte) []byte { ...@@ -61,42 +64,65 @@ func calcSeqCBKey(name []byte) []byte {
} }
//并发访问的可能性(每次开辟新内存) //并发访问的可能性(每次开辟新内存)
func caclSeqCBLastNumKey(name []byte) []byte { func calcSeqCBLastNumKey(name []byte) []byte {
return append(append([]byte{}, seqCBLastNumPrefix...), name...) return append(append([]byte{}, seqCBLastNumPrefix...), name...)
} }
//存储block hash对应的header信息 //存储block hash对应的header信息
func calcHashToBlockHeaderKey(hash []byte) []byte { func calcHashToBlockHeaderKey(hash []byte) []byte {
return append(headerPerfix, hash...) return append(headerPrefix, hash...)
} }
func calcHeightToBlockHeaderKey(height int64) []byte { func calcHeightToBlockHeaderKey(height int64) []byte {
return append(heightToHeaderPerfix, []byte(fmt.Sprintf("%012d", height))...) return append(heightToHeaderPrefix, []byte(fmt.Sprintf("%012d", height))...)
} }
//存储block hash对应的block height //存储block hash对应的block height
func calcHashToHeightKey(hash []byte) []byte { func calcHashToHeightKey(hash []byte) []byte {
return append(hashPerfix, hash...) return append(hashPrefix, hash...)
} }
//存储block hash对应的block总难度TD //存储block hash对应的block总难度TD
func calcHashToTdKey(hash []byte) []byte { func calcHashToTdKey(hash []byte) []byte {
return append(tdPerfix, hash...) return append(tdPrefix, hash...)
} }
//存储block height 对应的block hash //存储block height 对应的block hash
func calcHeightToHashKey(height int64) []byte { func calcHeightToHashKey(height int64) []byte {
return append(heightToHashKeyPerfix, []byte(fmt.Sprintf("%v", height))...) return append(heightToHashKeyPrefix, []byte(fmt.Sprintf("%v", height))...)
} }
//存储block操作序列号对应的block hash,KEY=Seq:sequence //存储block操作序列号对应的block hash,KEY=Seq:sequence
func calcSequenceToHashKey(sequence int64) []byte { func calcSequenceToHashKey(sequence int64, isPara bool) []byte {
if isPara {
return append(paraSeqToHashKey, []byte(fmt.Sprintf("%v", sequence))...)
}
return append(seqToHashKey, []byte(fmt.Sprintf("%v", sequence))...) return append(seqToHashKey, []byte(fmt.Sprintf("%v", sequence))...)
} }
//存储block hash对应的seq序列号,KEY=Seq:sequence,只用于平行链addblock操作,方便delblock回退是查找对应seq的hash //存储block hash对应的seq序列号,KEY=Seq:sequence,只用于平行链addblock操作,方便delblock回退是查找对应seq的hash
func calcHashToSequenceKey(hash []byte) []byte { func calcHashToSequenceKey(hash []byte, isPara bool) []byte {
return append(HashToSeqPerfix, hash...) if isPara {
return append(HashToParaSeqPrefix, hash...)
}
return append(HashToSeqPrefix, hash...)
}
func calcLastSeqKey(isPara bool) []byte {
if isPara {
return LastParaSequence
}
return LastSequence
}
//存储block hash对应的seq序列号,KEY=Seq:sequence,只用于平行链addblock操作,方便delblock回退是查找对应seq的hash
func calcHashToMainSequenceKey(hash []byte) []byte {
return append(HashToSeqPrefix, hash...)
}
//存储block操作序列号对应的block hash,KEY=MainSeq:sequence
func calcMainSequenceToHashKey(sequence int64) []byte {
return append(seqToHashKey, []byte(fmt.Sprintf("%v", sequence))...)
} }
//BlockStore 区块存储 //BlockStore 区块存储
...@@ -106,7 +132,8 @@ type BlockStore struct { ...@@ -106,7 +132,8 @@ type BlockStore struct {
height int64 height int64
lastBlock *types.Block lastBlock *types.Block
lastheaderlock sync.Mutex lastheaderlock sync.Mutex
chain *BlockChain saveSequence bool
isParaChain bool
} }
//NewBlockStore new //NewBlockStore new
...@@ -122,7 +149,10 @@ func NewBlockStore(chain *BlockChain, db dbm.DB, client queue.Client) *BlockStor ...@@ -122,7 +149,10 @@ func NewBlockStore(chain *BlockChain, db dbm.DB, client queue.Client) *BlockStor
height: height, height: height,
db: db, db: db,
client: client, client: client,
chain: chain, }
if chain != nil {
blockStore.saveSequence = chain.isRecordBlockSequence
blockStore.isParaChain = chain.isParaChain
} }
if height == -1 { if height == -1 {
chainlog.Info("load block height error, may be init database", "height", height) chainlog.Info("load block height error, may be init database", "height", height)
...@@ -558,7 +588,7 @@ func (bs *BlockStore) SaveBlock(storeBatch dbm.Batch, blockdetail *types.BlockDe ...@@ -558,7 +588,7 @@ func (bs *BlockStore) SaveBlock(storeBatch dbm.Batch, blockdetail *types.BlockDe
//存储block height和block hash的对应关系,便于通过height查询block //存储block height和block hash的对应关系,便于通过height查询block
storeBatch.Set(calcHeightToHashKey(height), hash) storeBatch.Set(calcHeightToHashKey(height), hash)
if bs.chain.isRecordBlockSequence || bs.chain.isParaChain { if bs.saveSequence || bs.isParaChain {
//存储记录block序列执行的type add //存储记录block序列执行的type add
lastSequence, err = bs.saveBlockSequence(storeBatch, hash, height, AddBlock, sequence) lastSequence, err = bs.saveBlockSequence(storeBatch, hash, height, AddBlock, sequence)
if err != nil { if err != nil {
...@@ -587,7 +617,7 @@ func (bs *BlockStore) DelBlock(storeBatch dbm.Batch, blockdetail *types.BlockDet ...@@ -587,7 +617,7 @@ func (bs *BlockStore) DelBlock(storeBatch dbm.Batch, blockdetail *types.BlockDet
storeBatch.Delete(calcHeightToHashKey(height)) storeBatch.Delete(calcHeightToHashKey(height))
storeBatch.Delete(calcHeightToBlockHeaderKey(height)) storeBatch.Delete(calcHeightToBlockHeaderKey(height))
if bs.chain.isRecordBlockSequence || bs.chain.isParaChain { if bs.saveSequence || bs.isParaChain {
//存储记录block序列执行的type del //存储记录block序列执行的type del
lastSequence, err := bs.saveBlockSequence(storeBatch, hash, height, DelBlock, sequence) lastSequence, err := bs.saveBlockSequence(storeBatch, hash, height, DelBlock, sequence)
if err != nil { if err != nil {
...@@ -917,7 +947,8 @@ func (bs *BlockStore) dbMaybeStoreBlock(blockdetail *types.BlockDetail, sync boo ...@@ -917,7 +947,8 @@ func (bs *BlockStore) dbMaybeStoreBlock(blockdetail *types.BlockDetail, sync boo
//LoadBlockLastSequence 获取当前最新的block操作序列号 //LoadBlockLastSequence 获取当前最新的block操作序列号
func (bs *BlockStore) LoadBlockLastSequence() (int64, error) { func (bs *BlockStore) LoadBlockLastSequence() (int64, error) {
bytes, err := bs.db.Get(LastSequence) lastKey := calcLastSeqKey(bs.isParaChain)
bytes, err := bs.db.Get(lastKey)
if bytes == nil || err != nil { if bytes == nil || err != nil {
if err != dbm.ErrNotFoundInDb { if err != dbm.ErrNotFoundInDb {
storeLog.Error("LoadBlockLastSequence", "error", err) storeLog.Error("LoadBlockLastSequence", "error", err)
...@@ -927,13 +958,25 @@ func (bs *BlockStore) LoadBlockLastSequence() (int64, error) { ...@@ -927,13 +958,25 @@ func (bs *BlockStore) LoadBlockLastSequence() (int64, error) {
return decodeHeight(bytes) return decodeHeight(bytes)
} }
//LoadBlockLastMainSequence 获取当前最新的block操作序列号
func (bs *BlockStore) LoadBlockLastMainSequence() (int64, error) {
bytes, err := bs.db.Get(LastSequence)
if bytes == nil || err != nil {
if err != dbm.ErrNotFoundInDb {
storeLog.Error("LoadBlockLastMainSequence", "error", err)
}
return -1, types.ErrHeightNotExist
}
return decodeHeight(bytes)
}
func (bs *BlockStore) setSeqCBLastNum(name []byte, num int64) error { func (bs *BlockStore) setSeqCBLastNum(name []byte, num int64) error {
return bs.db.SetSync(caclSeqCBLastNumKey(name), types.Encode(&types.Int64{Data: num})) return bs.db.SetSync(calcSeqCBLastNumKey(name), types.Encode(&types.Int64{Data: num}))
} }
//Seq的合法值从0开始的,所以没有获取到或者获取失败都应该返回-1 //Seq的合法值从0开始的,所以没有获取到或者获取失败都应该返回-1
func (bs *BlockStore) getSeqCBLastNum(name []byte) int64 { func (bs *BlockStore) getSeqCBLastNum(name []byte) int64 {
bytes, err := bs.db.Get(caclSeqCBLastNumKey(name)) bytes, err := bs.db.Get(calcSeqCBLastNumKey(name))
if bytes == nil || err != nil { if bytes == nil || err != nil {
if err != dbm.ErrNotFoundInDb { if err != dbm.ErrNotFoundInDb {
storeLog.Error("getSeqCBLastNum", "error", err) storeLog.Error("getSeqCBLastNum", "error", err)
...@@ -950,14 +993,14 @@ func (bs *BlockStore) getSeqCBLastNum(name []byte) int64 { ...@@ -950,14 +993,14 @@ func (bs *BlockStore) getSeqCBLastNum(name []byte) int64 {
} }
//SaveBlockSequence 存储block 序列执行的类型用于blockchain的恢复 //SaveBlockSequence 存储block 序列执行的类型用于blockchain的恢复
//获取当前的序列号,将此序列号加1存储本block的hash ,当主链使能isRecordBlockSequence //获取当前的序列号,将此序列号加1存储本block的hash ,当使能isRecordBlockSequence
// 平行链使能isParaChain时,sequence序列号是传入的 //平行链使能isParaChain时,而外保存主链的 sequence
// 需要保存
// seq->hash, hash->seq, last_seq
// 平行链需要主链的对应信息
func (bs *BlockStore) saveBlockSequence(storeBatch dbm.Batch, hash []byte, height int64, Type int64, sequence int64) (int64, error) { func (bs *BlockStore) saveBlockSequence(storeBatch dbm.Batch, hash []byte, height int64, Type int64, sequence int64) (int64, error) {
var blockSequence types.BlockSequence
var newSequence int64 var newSequence int64
if bs.saveSequence {
if bs.chain.isRecordBlockSequence {
Sequence, err := bs.LoadBlockLastSequence() Sequence, err := bs.LoadBlockLastSequence()
if err != nil { if err != nil {
storeLog.Error("SaveBlockSequence", "LoadBlockLastSequence err", err) storeLog.Error("SaveBlockSequence", "LoadBlockLastSequence err", err)
...@@ -969,31 +1012,52 @@ func (bs *BlockStore) saveBlockSequence(storeBatch dbm.Batch, hash []byte, heigh ...@@ -969,31 +1012,52 @@ func (bs *BlockStore) saveBlockSequence(storeBatch dbm.Batch, hash []byte, heigh
newSequence = Sequence + 1 newSequence = Sequence + 1
//开启isRecordBlockSequence功能必须从0开始同步数据,不允许从非0高度开启此功能 //开启isRecordBlockSequence功能必须从0开始同步数据,不允许从非0高度开启此功能
if newSequence == 0 && height != 0 { if newSequence == 0 && height != 0 {
storeLog.Error("isRecordBlockSequence is true must Synchronizing data from zero block", "height", height) storeLog.Error("isRecordBlockSequence is true must Synchronizing data from zero block", "height", height, "seq", newSequence)
panic(errors.New("isRecordBlockSequence is true must Synchronizing data from zero block")) panic(errors.New("isRecordBlockSequence is true must Synchronizing data from zero block"))
} }
} else if bs.chain.isParaChain {
newSequence = sequence // seq->hash
} var blockSequence types.BlockSequence
blockSequence.Hash = hash blockSequence.Hash = hash
blockSequence.Type = Type blockSequence.Type = Type
BlockSequenceByte, err := proto.Marshal(&blockSequence) BlockSequenceByte, err := proto.Marshal(&blockSequence)
if err != nil { if err != nil {
storeLog.Error("SaveBlockSequence Marshal BlockSequence", "hash", common.ToHex(hash), "error", err) storeLog.Error("SaveBlockSequence Marshal BlockSequence", "hash", common.ToHex(hash), "error", err)
return newSequence, err return newSequence, err
} }
storeBatch.Set(calcSequenceToHashKey(newSequence, bs.isParaChain), BlockSequenceByte)
// seq->hash sequenceBytes := types.Encode(&types.Int64{Data: newSequence})
storeBatch.Set(calcSequenceToHashKey(newSequence), BlockSequenceByte) // hash->seq 只记录add block时的hash和seq对应关系
if Type == AddBlock {
storeBatch.Set(calcHashToSequenceKey(hash, bs.isParaChain), sequenceBytes)
}
// 记录last seq
storeBatch.Set(calcLastSeqKey(bs.isParaChain), sequenceBytes)
}
if !bs.isParaChain {
return newSequence, nil
}
//parachain hash->seq 只记录add block时的hash和seq对应关系 mainSeq := sequence
var blockSequence types.BlockSequence
blockSequence.Hash = hash
blockSequence.Type = Type
BlockSequenceByte, err := proto.Marshal(&blockSequence)
if err != nil {
storeLog.Error("SaveBlockSequence Marshal BlockSequence", "hash", common.ToHex(hash), "error", err)
return newSequence, err
}
storeBatch.Set(calcMainSequenceToHashKey(mainSeq), BlockSequenceByte)
// hash->seq 只记录add block时的hash和seq对应关系
sequenceBytes := types.Encode(&types.Int64{Data: mainSeq})
if Type == AddBlock { if Type == AddBlock {
Sequencebytes := types.Encode(&types.Int64{Data: newSequence}) storeBatch.Set(calcHashToMainSequenceKey(hash), sequenceBytes)
storeBatch.Set(calcHashToSequenceKey(hash), Sequencebytes)
} }
Sequencebytes := types.Encode(&types.Int64{Data: newSequence}) storeBatch.Set(LastSequence, sequenceBytes)
storeBatch.Set(LastSequence, Sequencebytes)
return newSequence, nil return newSequence, nil
} }
...@@ -1008,11 +1072,20 @@ func (bs *BlockStore) LoadBlockBySequence(Sequence int64) (*types.BlockDetail, i ...@@ -1008,11 +1072,20 @@ func (bs *BlockStore) LoadBlockBySequence(Sequence int64) (*types.BlockDetail, i
return bs.loadBlockByHash(BlockSequence.Hash) return bs.loadBlockByHash(BlockSequence.Hash)
} }
//LoadBlockByMainSequence 通过main seq高度获取BlockDetail信息
func (bs *BlockStore) LoadBlockByMainSequence(sequence int64) (*types.BlockDetail, int, error) {
//首先通过Sequence序列号获取对应的blockhash和操作类型从db中
BlockSequence, err := bs.GetBlockByMainSequence(sequence)
if err != nil {
return nil, 0, err
}
return bs.loadBlockByHash(BlockSequence.Hash)
}
//GetBlockSequence 从db数据库中获取指定Sequence对应的block序列操作信息 //GetBlockSequence 从db数据库中获取指定Sequence对应的block序列操作信息
func (bs *BlockStore) GetBlockSequence(Sequence int64) (*types.BlockSequence, error) { func (bs *BlockStore) GetBlockSequence(Sequence int64) (*types.BlockSequence, error) {
var blockSeq types.BlockSequence var blockSeq types.BlockSequence
blockSeqByte, err := bs.db.Get(calcSequenceToHashKey(Sequence)) blockSeqByte, err := bs.db.Get(calcSequenceToHashKey(Sequence, bs.isParaChain))
if blockSeqByte == nil || err != nil { if blockSeqByte == nil || err != nil {
if err != dbm.ErrNotFoundInDb { if err != dbm.ErrNotFoundInDb {
storeLog.Error("GetBlockSequence", "error", err) storeLog.Error("GetBlockSequence", "error", err)
...@@ -1028,10 +1101,29 @@ func (bs *BlockStore) GetBlockSequence(Sequence int64) (*types.BlockSequence, er ...@@ -1028,10 +1101,29 @@ func (bs *BlockStore) GetBlockSequence(Sequence int64) (*types.BlockSequence, er
return &blockSeq, nil return &blockSeq, nil
} }
//GetSequenceByHash 通过block还是获取对应的seq,只提供给parachain使用 //GetBlockByMainSequence 从db数据库中获取指定Sequence对应的block序列操作信息
func (bs *BlockStore) GetBlockByMainSequence(sequence int64) (*types.BlockSequence, error) {
var blockSeq types.BlockSequence
blockSeqByte, err := bs.db.Get(calcMainSequenceToHashKey(sequence))
if blockSeqByte == nil || err != nil {
if err != dbm.ErrNotFoundInDb {
storeLog.Error("GetBlockByMainSequence", "error", err)
}
return nil, types.ErrHeightNotExist
}
err = proto.Unmarshal(blockSeqByte, &blockSeq)
if err != nil {
storeLog.Error("GetBlockByMainSequence", "err", err)
return nil, err
}
return &blockSeq, nil
}
//GetSequenceByHash 通过block还是获取对应的seq
func (bs *BlockStore) GetSequenceByHash(hash []byte) (int64, error) { func (bs *BlockStore) GetSequenceByHash(hash []byte) (int64, error) {
var seq types.Int64 var seq types.Int64
seqbytes, err := bs.db.Get(calcHashToSequenceKey(hash)) seqbytes, err := bs.db.Get(calcHashToSequenceKey(hash, bs.isParaChain))
if seqbytes == nil || err != nil { if seqbytes == nil || err != nil {
if err != dbm.ErrNotFoundInDb { if err != dbm.ErrNotFoundInDb {
storeLog.Error("GetSequenceByHash", "error", err) storeLog.Error("GetSequenceByHash", "error", err)
...@@ -1047,6 +1139,25 @@ func (bs *BlockStore) GetSequenceByHash(hash []byte) (int64, error) { ...@@ -1047,6 +1139,25 @@ func (bs *BlockStore) GetSequenceByHash(hash []byte) (int64, error) {
return seq.Data, nil return seq.Data, nil
} }
//GetMainSequenceByHash 通过block还是获取对应的seq,只提供给parachain使用
func (bs *BlockStore) GetMainSequenceByHash(hash []byte) (int64, error) {
var seq types.Int64
seqbytes, err := bs.db.Get(calcHashToMainSequenceKey(hash))
if seqbytes == nil || err != nil {
if err != dbm.ErrNotFoundInDb {
storeLog.Error("GetMainSequenceByHash", "error", err)
}
return -1, types.ErrHashNotExist
}
err = types.Decode(seqbytes, &seq)
if err != nil {
storeLog.Error("GetMainSequenceByHash types.Decode", "error", err)
return -1, types.ErrUnmarshal
}
return seq.Data, nil
}
//GetDbVersion 获取blockchain的数据库版本号 //GetDbVersion 获取blockchain的数据库版本号
func (bs *BlockStore) GetDbVersion() int64 { func (bs *BlockStore) GetDbVersion() int64 {
ver := types.Int64{} ver := types.Int64{}
...@@ -1128,38 +1239,38 @@ func (bs *BlockStore) SetStoreUpgradeMeta(meta *types.UpgradeMeta) error { ...@@ -1128,38 +1239,38 @@ func (bs *BlockStore) SetStoreUpgradeMeta(meta *types.UpgradeMeta) error {
return bs.db.SetSync(version.StoreDBMeta, verByte) return bs.db.SetSync(version.StoreDBMeta, verByte)
} }
//isRecordBlockSequence配置的合法性检测 //SequenceMustValid 配置的合法性检测
func (bs *BlockStore) isRecordBlockSequenceValid(chain *BlockChain) { func (bs *BlockStore) SequenceMustValid(recordSequence bool) {
lastHeight := bs.Height() lastHeight := bs.Height()
lastSequence, err := bs.LoadBlockLastSequence() lastSequence, err := bs.LoadBlockLastSequence()
if err != nil { if err != nil {
if err != types.ErrHeightNotExist { if err != types.ErrHeightNotExist {
storeLog.Error("isRecordBlockSequenceValid", "LoadBlockLastSequence err", err) storeLog.Error("SequenceMustValid", "LoadBlockLastSequence err", err)
panic(err) panic(err)
} }
} }
//使能isRecordBlockSequence时的检测 //使能isRecordBlockSequence时的检测
if chain.isRecordBlockSequence { if recordSequence {
//中途开启isRecordBlockSequence报错 //中途开启isRecordBlockSequence报错
if lastSequence == -1 && lastHeight != -1 { if lastSequence == -1 && lastHeight != -1 {
storeLog.Error("isRecordBlockSequenceValid", "lastHeight", lastHeight, "lastSequence", lastSequence) storeLog.Error("SequenceMustValid", "lastHeight", lastHeight, "lastSequence", lastSequence)
panic("isRecordBlockSequence is true must Synchronizing data from zero block") panic("isRecordBlockSequence is true must Synchronizing data from zero block")
} }
//lastSequence 必须大于等于lastheight //lastSequence 必须大于等于lastheight
if lastHeight > lastSequence { if lastHeight > lastSequence {
storeLog.Error("isRecordBlockSequenceValid", "lastHeight", lastHeight, "lastSequence", lastSequence) storeLog.Error("SequenceMustValid", "lastHeight", lastHeight, "lastSequence", lastSequence)
panic("lastSequence must greater than or equal to lastHeight") panic("lastSequence must greater than or equal to lastHeight")
} }
//通过lastSequence获取对应的blockhash != lastHeader.hash 报错 //通过lastSequence获取对应的blockhash != lastHeader.hash 报错
if lastSequence != -1 { if lastSequence != -1 {
blockSequence, err := bs.GetBlockSequence(lastSequence) blockSequence, err := bs.GetBlockSequence(lastSequence)
if err != nil { if err != nil {
storeLog.Error("isRecordBlockSequenceValid", "lastSequence", lastSequence, "GetBlockSequence err", err) storeLog.Error("SequenceMustValid", "lastSequence", lastSequence, "GetBlockSequence err", err)
panic(err) panic(err)
} }
lastHeader := bs.LastHeader() lastHeader := bs.LastHeader()
if !bytes.Equal(lastHeader.Hash, blockSequence.Hash) { if !bytes.Equal(lastHeader.Hash, blockSequence.Hash) {
storeLog.Error("isRecordBlockSequenceValid:", "lastHeight", lastHeight, "lastSequence", lastSequence, "lastHeader.Hash", common.ToHex(lastHeader.Hash), "blockSequence.Hash", common.ToHex(blockSequence.Hash)) storeLog.Error("SequenceMustValid:", "lastHeight", lastHeight, "lastSequence", lastSequence, "lastHeader.Hash", common.ToHex(lastHeader.Hash), "blockSequence.Hash", common.ToHex(blockSequence.Hash))
panic("The hash values of lastSequence and lastHeight are different.") panic("The hash values of lastSequence and lastHeight are different.")
} }
} }
...@@ -1167,7 +1278,7 @@ func (bs *BlockStore) isRecordBlockSequenceValid(chain *BlockChain) { ...@@ -1167,7 +1278,7 @@ func (bs *BlockStore) isRecordBlockSequenceValid(chain *BlockChain) {
} }
//去使能isRecordBlockSequence时的检测 //去使能isRecordBlockSequence时的检测
if lastSequence != -1 { if lastSequence != -1 {
storeLog.Error("isRecordBlockSequenceValid", "lastSequence", lastSequence) storeLog.Error("SequenceMustValid", "lastSequence", lastSequence)
panic("can not disable isRecordBlockSequence") panic("can not disable isRecordBlockSequence")
} }
} }
package blockchain_test package blockchain
import ( import (
"testing" "testing"
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/33cn/chain33/blockchain"
dbm "github.com/33cn/chain33/common/db" dbm "github.com/33cn/chain33/common/db"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -20,7 +19,7 @@ func TestGetStoreUpgradeMeta(t *testing.T) { ...@@ -20,7 +19,7 @@ func TestGetStoreUpgradeMeta(t *testing.T) {
blockStoreDB := dbm.NewDB("blockchain", "leveldb", dir, 100) blockStoreDB := dbm.NewDB("blockchain", "leveldb", dir, 100)
blockStore := blockchain.NewBlockStore(nil, blockStoreDB, nil) blockStore := NewBlockStore(nil, blockStoreDB, nil)
require.NotNil(t, blockStore) require.NotNil(t, blockStore)
meta, err := blockStore.GetStoreUpgradeMeta() meta, err := blockStore.GetStoreUpgradeMeta()
...@@ -34,3 +33,105 @@ func TestGetStoreUpgradeMeta(t *testing.T) { ...@@ -34,3 +33,105 @@ func TestGetStoreUpgradeMeta(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, meta.Version, "1.0.0") require.Equal(t, meta.Version, "1.0.0")
} }
func TestSeqSaveAndGet(t *testing.T) {
dir, err := ioutil.TempDir("", "example")
assert.Nil(t, err)
defer os.RemoveAll(dir) // clean up
os.RemoveAll(dir) //删除已存在目录
blockStoreDB := dbm.NewDB("blockchain", "leveldb", dir, 100)
blockStore := NewBlockStore(nil, blockStoreDB, nil)
assert.NotNil(t, blockStore)
blockStore.saveSequence = true
blockStore.isParaChain = false
newBatch := blockStore.NewBatch(true)
seq, err := blockStore.saveBlockSequence(newBatch, []byte("s0"), 0, 1, 0)
assert.Nil(t, err)
assert.Equal(t, int64(0), seq)
err = newBatch.Write()
assert.Nil(t, err)
newBatch = blockStore.NewBatch(true)
seq, err = blockStore.saveBlockSequence(newBatch, []byte("s1"), 1, 1, 0)
assert.Nil(t, err)
assert.Equal(t, int64(1), seq)
err = newBatch.Write()
assert.Nil(t, err)
s, err := blockStore.LoadBlockLastSequence()
assert.Nil(t, err)
assert.Equal(t, int64(1), s)
s2, err := blockStore.GetBlockSequence(s)
assert.Nil(t, err)
assert.Equal(t, []byte("s1"), s2.Hash)
s3, err := blockStore.GetSequenceByHash([]byte("s1"))
assert.Nil(t, err)
assert.Equal(t, int64(1), s3)
}
func TestParaSeqSaveAndGet(t *testing.T) {
dir, err := ioutil.TempDir("", "example")
assert.Nil(t, err)
defer os.RemoveAll(dir) // clean up
os.RemoveAll(dir) //删除已存在目录
blockStoreDB := dbm.NewDB("blockchain", "leveldb", dir, 100)
blockStore := NewBlockStore(nil, blockStoreDB, nil)
assert.NotNil(t, blockStore)
blockStore.saveSequence = true
blockStore.isParaChain = true
newBatch := blockStore.NewBatch(true)
seq, err := blockStore.saveBlockSequence(newBatch, []byte("s0"), 0, 1, 1)
assert.Nil(t, err)
assert.Equal(t, int64(0), seq)
err = newBatch.Write()
assert.Nil(t, err)
newBatch = blockStore.NewBatch(true)
seq, err = blockStore.saveBlockSequence(newBatch, []byte("s1"), 1, 1, 10)
assert.Nil(t, err)
assert.Equal(t, int64(1), seq)
err = newBatch.Write()
assert.Nil(t, err)
s, err := blockStore.LoadBlockLastSequence()
assert.Nil(t, err)
assert.Equal(t, int64(1), s)
s2, err := blockStore.GetBlockSequence(s)
assert.Nil(t, err)
assert.Equal(t, []byte("s1"), s2.Hash)
s3, err := blockStore.GetSequenceByHash([]byte("s1"))
assert.Nil(t, err)
assert.Equal(t, int64(1), s3)
s4, err := blockStore.GetMainSequenceByHash([]byte("s1"))
assert.Nil(t, err)
assert.Equal(t, int64(10), s4)
s5, err := blockStore.LoadBlockLastMainSequence()
assert.Nil(t, err)
assert.Equal(t, int64(10), s5)
s6, err := blockStore.GetBlockByMainSequence(1)
assert.Nil(t, err)
assert.Equal(t, []byte("s0"), s6.Hash)
chain := &BlockChain{
blockStore: blockStore,
}
s7, err := chain.ProcGetMainSeqByHash([]byte("s0"))
assert.Nil(t, err)
assert.Equal(t, int64(1), s7)
_, err = chain.ProcGetMainSeqByHash([]byte("s0-not-exist"))
assert.NotNil(t, err)
}
...@@ -245,9 +245,8 @@ func (chain *BlockChain) GetOrphanPool() *OrphanPool { ...@@ -245,9 +245,8 @@ func (chain *BlockChain) GetOrphanPool() *OrphanPool {
//InitBlockChain 区块链初始化 //InitBlockChain 区块链初始化
func (chain *BlockChain) InitBlockChain() { func (chain *BlockChain) InitBlockChain() {
//isRecordBlockSequence配置的合法性检测 //isRecordBlockSequence配置的合法性检测
if !chain.cfg.IsParaChain { chain.blockStore.SequenceMustValid(chain.isRecordBlockSequence)
chain.blockStore.isRecordBlockSequenceValid(chain)
}
//先缓存最新的128个block信息到cache中 //先缓存最新的128个block信息到cache中
curheight := chain.GetBlockHeight() curheight := chain.GetBlockHeight()
if types.IsEnable("TxHeight") { if types.IsEnable("TxHeight") {
......
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
"github.com/33cn/chain33/common/log" "github.com/33cn/chain33/common/log"
"github.com/33cn/chain33/common/log/log15" "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/common/merkle" "github.com/33cn/chain33/common/merkle"
"github.com/33cn/chain33/queue"
_ "github.com/33cn/chain33/system" _ "github.com/33cn/chain33/system"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
"github.com/33cn/chain33/util" "github.com/33cn/chain33/util"
...@@ -129,6 +130,8 @@ func TestBlockChain(t *testing.T) { ...@@ -129,6 +130,8 @@ func TestBlockChain(t *testing.T) {
testReadBlockToExec(t, blockchain) testReadBlockToExec(t, blockchain)
testReExecBlock(t, blockchain) testReExecBlock(t, blockchain)
testUpgradeStore(t, blockchain) testUpgradeStore(t, blockchain)
testProcMainSeqMsg(t, blockchain)
} }
func testProcAddBlockMsg(t *testing.T, mock33 *testnode.Chain33Mock, blockchain *blockchain.BlockChain) { func testProcAddBlockMsg(t *testing.T, mock33 *testnode.Chain33Mock, blockchain *blockchain.BlockChain) {
...@@ -1124,3 +1127,17 @@ func testUpgradeStore(t *testing.T, chain *blockchain.BlockChain) { ...@@ -1124,3 +1127,17 @@ func testUpgradeStore(t *testing.T, chain *blockchain.BlockChain) {
chain.UpgradeStore() chain.UpgradeStore()
chainlog.Info("UpgradeStore end ---------------------") chainlog.Info("UpgradeStore end ---------------------")
} }
func testProcMainSeqMsg(t *testing.T, blockchain *blockchain.BlockChain) {
chainlog.Info("testProcMainSeqMsg begin -------------------")
msg := queue.NewMessage(1, "blockchain", types.EventGetLastBlockMainSequence, nil)
blockchain.GetLastBlockMainSequence(msg)
assert.Equal(t, int64(types.EventGetLastBlockMainSequence), msg.Ty)
msg = queue.NewMessage(1, "blockchain", types.EventGetMainSeqByHash, &types.ReqHash{Hash: []byte("hash")})
blockchain.GetMainSeqByHash(msg)
assert.Equal(t, int64(types.EventGetMainSeqByHash), msg.Ty)
chainlog.Info("testProcMainSeqMsg end --------------------")
}
...@@ -70,7 +70,6 @@ func (chain *BlockChain) ProcRecvMsg() { ...@@ -70,7 +70,6 @@ func (chain *BlockChain) ProcRecvMsg() {
case types.EventGetBlockByHashes: case types.EventGetBlockByHashes:
go chain.processMsg(msg, reqnum, chain.getBlockByHashes) go chain.processMsg(msg, reqnum, chain.getBlockByHashes)
case types.EventGetBlockBySeq: case types.EventGetBlockBySeq:
go chain.processMsg(msg, reqnum, chain.getBlockBySeq) go chain.processMsg(msg, reqnum, chain.getBlockBySeq)
...@@ -90,6 +89,12 @@ func (chain *BlockChain) ProcRecvMsg() { ...@@ -90,6 +89,12 @@ func (chain *BlockChain) ProcRecvMsg() {
case types.EventGetSeqCBLastNum: case types.EventGetSeqCBLastNum:
go chain.processMsg(msg, reqnum, chain.getSeqCBLastNum) go chain.processMsg(msg, reqnum, chain.getSeqCBLastNum)
case types.EventGetLastBlockMainSequence:
go chain.processMsg(msg, reqnum, chain.GetLastBlockMainSequence)
case types.EventGetMainSeqByHash:
go chain.processMsg(msg, reqnum, chain.GetMainSeqByHash)
default: default:
go chain.processMsg(msg, reqnum, chain.unknowMsg) go chain.processMsg(msg, reqnum, chain.unknowMsg)
} }
...@@ -519,3 +524,28 @@ func (chain *BlockChain) localAddrTxCount(msg *queue.Message) { ...@@ -519,3 +524,28 @@ func (chain *BlockChain) localAddrTxCount(msg *queue.Message) {
counts = count.Data counts = count.Data
msg.Reply(chain.client.NewMessage("rpc", types.EventLocalReplyValue, &types.Int64{Data: counts})) msg.Reply(chain.client.NewMessage("rpc", types.EventLocalReplyValue, &types.Int64{Data: counts}))
} }
//GetLastBlockMainSequence 获取最新的block执行序列号
func (chain *BlockChain) GetLastBlockMainSequence(msg *queue.Message) {
var lastSequence types.Int64
var err error
lastSequence.Data, err = chain.blockStore.LoadBlockLastMainSequence()
if err != nil {
chainlog.Debug("GetLastBlockMainSequence", "err", err)
msg.Reply(chain.client.NewMessage("rpc", types.EventReplyLastBlockMainSequence, err))
return
}
msg.Reply(chain.client.NewMessage("rpc", types.EventReplyLastBlockMainSequence, &lastSequence))
}
//GetMainSeqByHash parachian 通过blockhash获取对应的seq,只记录了addblock时的seq
func (chain *BlockChain) GetMainSeqByHash(msg *queue.Message) {
blockhash := (msg.Data).(*types.ReqHash)
seq, err := chain.ProcGetMainSeqByHash(blockhash.Hash)
if err != nil {
chainlog.Error("GetMainSeqByHash", "err", err.Error())
msg.Reply(chain.client.NewMessage("rpc", types.EventReplyMainSeqByHash, err))
return
}
msg.Reply(chain.client.NewMessage("rpc", types.EventReplyMainSeqByHash, &types.Int64{Data: seq}))
}
...@@ -402,7 +402,7 @@ func (b *BlockChain) connectBlock(node *blockNode, blockdetail *types.BlockDetai ...@@ -402,7 +402,7 @@ func (b *BlockChain) connectBlock(node *blockNode, blockdetail *types.BlockDetai
} }
} }
//目前非平行链并开启isRecordBlockSequence功能 //目前非平行链并开启isRecordBlockSequence功能
if b.isRecordBlockSequence && !b.isParaChain { if b.isRecordBlockSequence {
b.pushseq.updateSeq(lastSequence) b.pushseq.updateSeq(lastSequence)
} }
return blockdetail, nil return blockdetail, nil
...@@ -471,7 +471,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, blockdetail *types.BlockDe ...@@ -471,7 +471,7 @@ func (b *BlockChain) disconnectBlock(node *blockNode, blockdetail *types.BlockDe
chainlog.Debug("disconnectBlock success", "newtipnode.hash", common.ToHex(newtipnode.hash), "delblock.parent.hash", common.ToHex(blockdetail.Block.GetParentHash())) chainlog.Debug("disconnectBlock success", "newtipnode.hash", common.ToHex(newtipnode.hash), "delblock.parent.hash", common.ToHex(blockdetail.Block.GetParentHash()))
//目前非平行链并开启isRecordBlockSequence功能 //目前非平行链并开启isRecordBlockSequence功能
if b.isRecordBlockSequence && !b.isParaChain { if b.isRecordBlockSequence {
b.pushseq.updateSeq(lastSequence) b.pushseq.updateSeq(lastSequence)
} }
return nil return nil
......
...@@ -90,6 +90,18 @@ func (chain *BlockChain) ProcGetSeqByHash(hash []byte) (int64, error) { ...@@ -90,6 +90,18 @@ func (chain *BlockChain) ProcGetSeqByHash(hash []byte) (int64, error) {
return seq, err return seq, err
} }
//ProcGetMainSeqByHash 处理共识过来的通过blockhash获取seq的消息,只提供add block时的seq,用于平行链block回退
func (chain *BlockChain) ProcGetMainSeqByHash(hash []byte) (int64, error) {
if len(hash) == 0 {
chainlog.Error("ProcGetMainSeqByHash input hash is null")
return -1, types.ErrInvalidParam
}
seq, err := chain.blockStore.GetMainSequenceByHash(hash)
chainlog.Debug("ProcGetMainSeqByHash", "blockhash", common.ToHex(hash), "seq", seq, "err", err)
return seq, err
}
//ProcAddBlockSeqCB 添加seq callback //ProcAddBlockSeqCB 添加seq callback
func (chain *BlockChain) ProcAddBlockSeqCB(cb *types.BlockSeqCB) error { func (chain *BlockChain) ProcAddBlockSeqCB(cb *types.BlockSeqCB) error {
if cb == nil { if cb == nil {
......
...@@ -162,6 +162,18 @@ func (m *mockBlockChain) SetQueueClient(q queue.Queue) { ...@@ -162,6 +162,18 @@ func (m *mockBlockChain) SetQueueClient(q queue.Queue) {
} else { } else {
msg.ReplyErr("transaction id must 9999", types.ErrInvalidParam) msg.ReplyErr("transaction id must 9999", types.ErrInvalidParam)
} }
case types.EventGetMainSeqByHash:
if req, ok := msg.GetData().(*types.ReqHash); ok && string(req.Hash) == "exist-hash" {
msg.Reply(client.NewMessage(blockchainKey, types.EventReplyMainSeqByHash, &types.Int64{Data: 9999}))
} else {
msg.ReplyErr("transaction hash is not exist-hash", types.ErrInvalidParam)
}
case types.EventGetLastBlockMainSequence:
if _, ok := msg.GetData().(*types.ReqNil); ok {
msg.Reply(client.NewMessage(blockchainKey, types.EventReplyLastBlockMainSequence, &types.Int64{Data: 9999}))
} else {
msg.ReplyErr("request must be nil", types.ErrInvalidParam)
}
default: default:
msg.ReplyErr("Do not support", types.ErrNotSupport) msg.ReplyErr("Do not support", types.ErrNotSupport)
} }
......
...@@ -361,6 +361,29 @@ func (_m *QueueProtocolAPI) GetHeaders(param *types.ReqBlocks) (*types.Headers, ...@@ -361,6 +361,29 @@ func (_m *QueueProtocolAPI) GetHeaders(param *types.ReqBlocks) (*types.Headers,
return r0, r1 return r0, r1
} }
// GetLastBlockMainSequence provides a mock function with given fields:
func (_m *QueueProtocolAPI) GetLastBlockMainSequence() (*types.Int64, error) {
ret := _m.Called()
var r0 *types.Int64
if rf, ok := ret.Get(0).(func() *types.Int64); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.Int64)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetLastBlockSequence provides a mock function with given fields: // GetLastBlockSequence provides a mock function with given fields:
func (_m *QueueProtocolAPI) GetLastBlockSequence() (*types.Int64, error) { func (_m *QueueProtocolAPI) GetLastBlockSequence() (*types.Int64, error) {
ret := _m.Called() ret := _m.Called()
...@@ -430,6 +453,29 @@ func (_m *QueueProtocolAPI) GetLastMempool() (*types.ReplyTxList, error) { ...@@ -430,6 +453,29 @@ func (_m *QueueProtocolAPI) GetLastMempool() (*types.ReplyTxList, error) {
return r0, r1 return r0, r1
} }
// GetMainSequenceByHash provides a mock function with given fields: param
func (_m *QueueProtocolAPI) GetMainSequenceByHash(param *types.ReqHash) (*types.Int64, error) {
ret := _m.Called(param)
var r0 *types.Int64
if rf, ok := ret.Get(0).(func(*types.ReqHash) *types.Int64); ok {
r0 = rf(param)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*types.Int64)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*types.ReqHash) error); ok {
r1 = rf(param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMempool provides a mock function with given fields: // GetMempool provides a mock function with given fields:
func (_m *QueueProtocolAPI) GetMempool() (*types.ReplyTxList, error) { func (_m *QueueProtocolAPI) GetMempool() (*types.ReplyTxList, error) {
ret := _m.Called() ret := _m.Called()
......
...@@ -1139,3 +1139,36 @@ func (q *QueueProtocol) GetSeqCallBackLastNum(param *types.ReqString) (*types.In ...@@ -1139,3 +1139,36 @@ func (q *QueueProtocol) GetSeqCallBackLastNum(param *types.ReqString) (*types.In
} }
return nil, types.ErrTypeAsset return nil, types.ErrTypeAsset
} }
// GetLastBlockMainSequence 获取最新的block执行序列号
func (q *QueueProtocol) GetLastBlockMainSequence() (*types.Int64, error) {
msg, err := q.query(blockchainKey, types.EventGetLastBlockMainSequence, &types.ReqNil{})
if err != nil {
log.Error("GetLastBlockMainSequence", "Error", err.Error())
return nil, err
}
if reply, ok := msg.GetData().(*types.Int64); ok {
return reply, nil
}
return nil, types.ErrTypeAsset
}
// GetMainSequenceByHash 通过hash获取对应的执行序列号
func (q *QueueProtocol) GetMainSequenceByHash(param *types.ReqHash) (*types.Int64, error) {
if param == nil {
err := types.ErrInvalidParam
log.Error("GetMainSequenceByHash", "Error", err)
return nil, err
}
msg, err := q.query(blockchainKey, types.EventGetMainSeqByHash, param)
if err != nil {
log.Error("GetMainSequenceByHash", "Error", err.Error())
return nil, err
}
if reply, ok := msg.GetData().(*types.Int64); ok {
return reply, nil
}
return nil, types.ErrTypeAsset
}
...@@ -1196,3 +1196,26 @@ func TestGetBlockBySeq(t *testing.T) { ...@@ -1196,3 +1196,26 @@ func TestGetBlockBySeq(t *testing.T) {
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestGetMainSeq(t *testing.T) {
net := queue.New("test-seq-api")
defer net.Close()
chain := &mockBlockChain{}
chain.SetQueueClient(net)
defer chain.Close()
api, err := client.New(net.Client(), nil)
assert.Nil(t, err)
seq, err := api.GetMainSequenceByHash(&types.ReqHash{Hash: []byte("exist-hash")})
assert.Nil(t, err)
assert.Equal(t, int64(9999), seq.Data)
seq, err = api.GetMainSequenceByHash(&types.ReqHash{Hash: []byte("")})
assert.NotNil(t, err)
seq1, err := api.GetLastBlockMainSequence()
assert.Nil(t, err)
assert.Equal(t, int64(9999), seq1.Data)
}
...@@ -128,6 +128,12 @@ type QueueProtocolAPI interface { ...@@ -128,6 +128,12 @@ type QueueProtocolAPI interface {
//types.EventGetSequenceByHash: //types.EventGetSequenceByHash:
GetSequenceByHash(param *types.ReqHash) (*types.Int64, error) GetSequenceByHash(param *types.ReqHash) (*types.Int64, error)
// 在平行链上获得主链Sequence相关的接口
//types.EventGetLastBlockSequence:
GetLastBlockMainSequence() (*types.Int64, error)
//types.EventGetSequenceByHash:
GetMainSequenceByHash(param *types.ReqHash) (*types.Int64, error)
// --------------- blockchain interfaces end // --------------- blockchain interfaces end
// +++++++++++++++ store interfaces begin // +++++++++++++++ store interfaces begin
......
...@@ -59,7 +59,7 @@ func NewMempool(cfg *types.Mempool) *Mempool { ...@@ -59,7 +59,7 @@ func NewMempool(cfg *types.Mempool) *Mempool {
pool.cfg = cfg pool.cfg = cfg
pool.poolHeader = make(chan struct{}, 2) pool.poolHeader = make(chan struct{}, 2)
pool.removeBlockTicket = time.NewTicker(time.Minute) pool.removeBlockTicket = time.NewTicker(time.Minute)
pool.cache = newCache(cfg.MaxTxNumPerAccount, cfg.MaxTxLast) pool.cache = newCache(cfg.MaxTxNumPerAccount, cfg.MaxTxLast, cfg.PoolCacheSize)
return pool return pool
} }
...@@ -421,3 +421,26 @@ func (mem *Mempool) setSync(status bool) { ...@@ -421,3 +421,26 @@ func (mem *Mempool) setSync(status bool) {
mem.sync = status mem.sync = status
mem.proxyMtx.Unlock() mem.proxyMtx.Unlock()
} }
// getTxListByHash 从qcache或者SHashTxCache中获取hash对应的tx交易列表
func (mem *Mempool) getTxListByHash(hashList *types.ReqTxHashList) *types.ReplyTxList {
mem.proxyMtx.Lock()
defer mem.proxyMtx.Unlock()
var replyTxList types.ReplyTxList
//通过短hash来获取tx交易
if hashList.GetIsShortHash() {
for _, sHash := range hashList.GetHashes() {
tx := mem.cache.GetSHashTxCache(sHash)
replyTxList.Txs = append(replyTxList.Txs, tx)
}
return &replyTxList
}
//通过hash来获取tx交易
for _, hash := range hashList.GetHashes() {
tx := mem.cache.getTxByHash(hash)
replyTxList.Txs = append(replyTxList.Txs, tx)
}
return &replyTxList
}
...@@ -34,13 +34,15 @@ type txCache struct { ...@@ -34,13 +34,15 @@ type txCache struct {
qcache QueueCache qcache QueueCache
totalFee int64 totalFee int64
totalByte int64 totalByte int64
*SHashTxCache
} }
//NewTxCache init accountIndex and last cache //NewTxCache init accountIndex and last cache
func newCache(maxTxPerAccount int64, sizeLast int64) *txCache { func newCache(maxTxPerAccount int64, sizeLast int64, poolCacheSize int64) *txCache {
return &txCache{ return &txCache{
AccountTxIndex: NewAccountTxIndex(int(maxTxPerAccount)), AccountTxIndex: NewAccountTxIndex(int(maxTxPerAccount)),
LastTxCache: NewLastTxCache(int(sizeLast)), LastTxCache: NewLastTxCache(int(sizeLast)),
SHashTxCache: NewSHashTxCache(int(poolCacheSize)),
} }
} }
...@@ -64,6 +66,7 @@ func (cache *txCache) Remove(hash string) { ...@@ -64,6 +66,7 @@ func (cache *txCache) Remove(hash string) {
cache.LastTxCache.Remove(tx) cache.LastTxCache.Remove(tx)
cache.totalFee -= tx.Fee cache.totalFee -= tx.Fee
cache.totalByte -= int64(proto.Size(tx)) cache.totalByte -= int64(proto.Size(tx))
cache.SHashTxCache.Remove(tx)
} }
//Exist 是否存在 //Exist 是否存在
...@@ -132,6 +135,7 @@ func (cache *txCache) Push(tx *types.Transaction) error { ...@@ -132,6 +135,7 @@ func (cache *txCache) Push(tx *types.Transaction) error {
cache.LastTxCache.Push(tx) cache.LastTxCache.Push(tx)
cache.totalFee += tx.Fee cache.totalFee += tx.Fee
cache.totalByte += int64(proto.Size(tx)) cache.totalByte += int64(proto.Size(tx))
cache.SHashTxCache.Push(tx)
return nil return nil
} }
...@@ -156,3 +160,12 @@ func isExpired(item *Item, height, blockTime int64) bool { ...@@ -156,3 +160,12 @@ func isExpired(item *Item, height, blockTime int64) bool {
} }
return false return false
} }
//getTxByHash 通过交易hash获取tx交易信息
func (cache *txCache) getTxByHash(hash string) *types.Transaction {
item, err := cache.qcache.GetItem(hash)
if err != nil {
return nil
}
return item.Value
}
...@@ -90,6 +90,9 @@ func (mem *Mempool) eventProcess() { ...@@ -90,6 +90,9 @@ func (mem *Mempool) eventProcess() {
case types.EventGetProperFee: case types.EventGetProperFee:
// 获取对应排队策略中合适的手续费 // 获取对应排队策略中合适的手续费
mem.eventGetProperFee(msg) mem.eventGetProperFee(msg)
// 消息类型EventTxListByHash:通过hash获取对应的tx列表
case types.EventTxListByHash:
mem.eventTxListByHash(msg)
default: default:
} }
mlog.Debug("mempool", "cost", types.Since(beg), "msg", types.GetEventName(int(msg.Ty))) mlog.Debug("mempool", "cost", types.Since(beg), "msg", types.GetEventName(int(msg.Ty)))
...@@ -205,3 +208,10 @@ func (mem *Mempool) checkSign(data *queue.Message) *queue.Message { ...@@ -205,3 +208,10 @@ func (mem *Mempool) checkSign(data *queue.Message) *queue.Message {
data.Data = types.ErrSign data.Data = types.ErrSign
return data return data
} }
// eventTxListByHash 通过hash获取tx列表
func (mem *Mempool) eventTxListByHash(msg *queue.Message) {
shashList := msg.GetData().(*types.ReqTxHashList)
replytxList := mem.getTxListByHash(shashList)
msg.Reply(mem.client.NewMessage("", types.EventReplyTxList, replytxList))
}
...@@ -1144,3 +1144,94 @@ func execProcess(q queue.Queue) { ...@@ -1144,3 +1144,94 @@ func execProcess(q queue.Queue) {
} }
}() }()
} }
func TestTx(t *testing.T) {
subConfig := SubConfig{10240, 10000}
cache := newCache(10240, 10, 10240)
cache.SetQueueCache(NewSimpleQueue(subConfig))
tx := &types.Transaction{Execer: []byte("user.write"), Payload: types.Encode(transfer), Fee: 100000000, Expire: 0, To: toAddr}
var replyTxList types.ReplyTxList
var sHastList types.ReqTxHashList
var hastList types.ReqTxHashList
for i := 1; i <= 10240; i++ {
tx.Expire = int64(i)
cache.Push(tx)
sHastList.Hashes = append(sHastList.Hashes, types.CalcTxShortHash(tx.Hash()))
hastList.Hashes = append(hastList.Hashes, string(tx.Hash()))
}
for i := 1; i <= 1600; i++ {
Tx := cache.GetSHashTxCache(sHastList.Hashes[i])
if Tx == nil {
panic("TestTx:GetSHashTxCache is nil")
}
replyTxList.Txs = append(replyTxList.Txs, Tx)
}
for i := 1; i <= 1600; i++ {
Tx := cache.getTxByHash(hastList.Hashes[i])
if Tx == nil {
panic("TestTx:getTxByHash is nil")
}
replyTxList.Txs = append(replyTxList.Txs, Tx)
}
}
func TestEventTxListByHash(t *testing.T) {
q, mem := initEnv(0)
defer q.Close()
defer mem.Close()
// add tx
hashes, err := add4TxHash(mem.client)
if err != nil {
t.Error("add tx error", err.Error())
return
}
//通过交易hash获取交易信息
reqTxHashList := types.ReqTxHashList{
Hashes: hashes,
IsShortHash: false,
}
msg1 := mem.client.NewMessage("mempool", types.EventTxListByHash, &reqTxHashList)
mem.client.Send(msg1, true)
data1, err := mem.client.Wait(msg1)
if err != nil {
t.Error(err)
return
}
txs1 := data1.GetData().(*types.ReplyTxList).GetTxs()
if len(txs1) != 4 {
t.Error("TestEventTxListByHash:get txlist number error")
}
for i, tx := range txs1 {
if hashes[i] != string(tx.Hash()) {
t.Error("TestEventTxListByHash:hash mismatch")
}
}
//通过短hash获取tx交易
var shashes []string
for _, hash := range hashes {
shashes = append(shashes, types.CalcTxShortHash([]byte(hash)))
}
reqTxHashList.Hashes = shashes
reqTxHashList.IsShortHash = true
msg2 := mem.client.NewMessage("mempool", types.EventTxListByHash, &reqTxHashList)
mem.client.Send(msg2, true)
data2, err := mem.client.Wait(msg2)
if err != nil {
t.Error(err)
return
}
txs2 := data2.GetData().(*types.ReplyTxList).GetTxs()
for i, tx := range txs2 {
if hashes[i] != string(tx.Hash()) {
t.Error("TestEventTxListByHash:shash mismatch")
}
}
}
package mempool
import (
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/listmap"
log "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/types"
)
var shashlog = log.New("module", "mempool.shash")
//SHashTxCache 通过shorthash缓存交易
type SHashTxCache struct {
max int
l *listmap.ListMap
}
//NewSHashTxCache 创建通过短hash交易的cache
func NewSHashTxCache(size int) *SHashTxCache {
return &SHashTxCache{
max: size,
l: listmap.New(),
}
}
//GetSHashTxCache 返回shorthash对应的tx交易信息
func (cache *SHashTxCache) GetSHashTxCache(sHash string) *types.Transaction {
tx, err := cache.l.GetItem(sHash)
if err != nil {
return nil
}
return tx.(*types.Transaction)
}
//Remove remove tx of SHashTxCache
func (cache *SHashTxCache) Remove(tx *types.Transaction) {
txhash := tx.Hash()
cache.l.Remove(types.CalcTxShortHash(txhash))
//shashlog.Debug("SHashTxCache:Remove", "shash", types.CalcTxShortHash(txhash), "txhash", common.ToHex(txhash))
}
//Push tx into SHashTxCache
func (cache *SHashTxCache) Push(tx *types.Transaction) {
shash := types.CalcTxShortHash(tx.Hash())
if cache.Exist(shash) {
shashlog.Error("SHashTxCache:Push:Exist", "oldhash", common.ToHex(cache.GetSHashTxCache(shash).Hash()), "newhash", common.ToHex(tx.Hash()))
return
}
if cache.l.Size() >= cache.max {
shashlog.Error("SHashTxCache:Push:ErrMemFull", "cache.l.Size()", cache.l.Size(), "cache.max", cache.max)
return
}
cache.l.Push(shash, tx)
//shashlog.Debug("SHashTxCache:Push", "shash", shash, "txhash", common.ToHex(tx.Hash()))
}
//Exist 是否存在
func (cache *SHashTxCache) Exist(shash string) bool {
return cache.l.Exist(shash)
}
...@@ -152,10 +152,16 @@ const ( ...@@ -152,10 +152,16 @@ const (
EventReplyProperFee = 141 EventReplyProperFee = 141
EventReExecBlock = 142 EventReExecBlock = 142
EventTxListByHash = 143
//exec //exec
EventBlockChainQuery = 212 EventBlockChainQuery = 212
EventConsensusQuery = 213 EventConsensusQuery = 213
// BlockChain 接收的事件
EventGetLastBlockMainSequence = 300
EventReplyLastBlockMainSequence = 301
EventGetMainSeqByHash = 302
EventReplyMainSeqByHash = 303
) )
var eventName = map[int]string{ var eventName = map[int]string{
...@@ -301,4 +307,10 @@ var eventName = map[int]string{ ...@@ -301,4 +307,10 @@ var eventName = map[int]string{
//mempool //mempool
EventGetProperFee: "EventGetProperFee", EventGetProperFee: "EventGetProperFee",
EventReplyProperFee: "EventReplyProperFee", EventReplyProperFee: "EventReplyProperFee",
EventTxListByHash: "EventTxListByHash",
// block chain
EventGetLastBlockMainSequence: "EventGetLastBlockMainSequence",
EventReplyLastBlockMainSequence: "EventReplyLastBlockMainSequence",
EventGetMainSeqByHash: "EventGetMainSeqByHash",
EventReplyMainSeqByHash: "EventReplyMainSeqByHash",
} }
...@@ -242,3 +242,9 @@ message UpgradeMeta { ...@@ -242,3 +242,9 @@ message UpgradeMeta {
string version = 2; string version = 2;
int64 height = 3; int64 height = 3;
} }
//通过交易hash获取交易列表,需要区分是短hash还是全hash值
message ReqTxHashList {
repeated string hashes = 1;
bool isShortHash = 2;
}
...@@ -2016,6 +2016,54 @@ func (m *UpgradeMeta) GetHeight() int64 { ...@@ -2016,6 +2016,54 @@ func (m *UpgradeMeta) GetHeight() int64 {
return 0 return 0
} }
//通过交易hash获取交易列表,需要区分是短hash还是全hash值
type ReqTxHashList struct {
Hashes []string `protobuf:"bytes,1,rep,name=hashes,proto3" json:"hashes,omitempty"`
IsShortHash bool `protobuf:"varint,2,opt,name=isShortHash,proto3" json:"isShortHash,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ReqTxHashList) Reset() { *m = ReqTxHashList{} }
func (m *ReqTxHashList) String() string { return proto.CompactTextString(m) }
func (*ReqTxHashList) ProtoMessage() {}
func (*ReqTxHashList) Descriptor() ([]byte, []int) {
return fileDescriptor_2cc4e03d2c28c490, []int{35}
}
func (m *ReqTxHashList) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReqTxHashList.Unmarshal(m, b)
}
func (m *ReqTxHashList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ReqTxHashList.Marshal(b, m, deterministic)
}
func (m *ReqTxHashList) XXX_Merge(src proto.Message) {
xxx_messageInfo_ReqTxHashList.Merge(m, src)
}
func (m *ReqTxHashList) XXX_Size() int {
return xxx_messageInfo_ReqTxHashList.Size(m)
}
func (m *ReqTxHashList) XXX_DiscardUnknown() {
xxx_messageInfo_ReqTxHashList.DiscardUnknown(m)
}
var xxx_messageInfo_ReqTxHashList proto.InternalMessageInfo
func (m *ReqTxHashList) GetHashes() []string {
if m != nil {
return m.Hashes
}
return nil
}
func (m *ReqTxHashList) GetIsShortHash() bool {
if m != nil {
return m.IsShortHash
}
return false
}
func init() { func init() {
proto.RegisterType((*AssetsGenesis)(nil), "types.AssetsGenesis") proto.RegisterType((*AssetsGenesis)(nil), "types.AssetsGenesis")
proto.RegisterType((*AssetsTransferToExec)(nil), "types.AssetsTransferToExec") proto.RegisterType((*AssetsTransferToExec)(nil), "types.AssetsTransferToExec")
...@@ -2052,93 +2100,96 @@ func init() { ...@@ -2052,93 +2100,96 @@ func init() {
proto.RegisterType((*ReqDecodeRawTransaction)(nil), "types.ReqDecodeRawTransaction") proto.RegisterType((*ReqDecodeRawTransaction)(nil), "types.ReqDecodeRawTransaction")
proto.RegisterType((*UserWrite)(nil), "types.UserWrite") proto.RegisterType((*UserWrite)(nil), "types.UserWrite")
proto.RegisterType((*UpgradeMeta)(nil), "types.UpgradeMeta") proto.RegisterType((*UpgradeMeta)(nil), "types.UpgradeMeta")
proto.RegisterType((*ReqTxHashList)(nil), "types.ReqTxHashList")
} }
func init() { proto.RegisterFile("transaction.proto", fileDescriptor_2cc4e03d2c28c490) } func init() { proto.RegisterFile("transaction.proto", fileDescriptor_2cc4e03d2c28c490) }
var fileDescriptor_2cc4e03d2c28c490 = []byte{ var fileDescriptor_2cc4e03d2c28c490 = []byte{
// 1326 bytes of a gzipped FileDescriptorProto // 1351 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0xd1, 0x6e, 0x1b, 0xb7, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x57, 0xdd, 0x8e, 0x13, 0xc7,
0x12, 0x85, 0xb4, 0x5a, 0x5b, 0x1a, 0x29, 0xb9, 0xf1, 0xc2, 0x48, 0x84, 0xe0, 0xde, 0xc4, 0x97, 0x12, 0x96, 0x3d, 0x1e, 0xaf, 0x5d, 0xf6, 0x72, 0xd8, 0xd1, 0x0a, 0x2c, 0x74, 0x0e, 0xf8, 0x8c,
0x48, 0x81, 0x20, 0x08, 0x64, 0xc0, 0xce, 0x5b, 0x0b, 0xb4, 0x49, 0xdc, 0x26, 0x81, 0x93, 0x34, 0x88, 0x84, 0x10, 0xf2, 0x4a, 0xbb, 0xdc, 0x25, 0x52, 0x02, 0x6c, 0x02, 0xab, 0x05, 0x42, 0x1a,
0x65, 0x94, 0xa4, 0x68, 0xfb, 0x42, 0xaf, 0xc6, 0xd2, 0x36, 0xd2, 0x52, 0xe6, 0x52, 0xce, 0xea, 0x03, 0x51, 0x92, 0x9b, 0xde, 0x71, 0xad, 0x3d, 0xc1, 0x9e, 0xf6, 0xf6, 0xb4, 0x97, 0xf1, 0x0b,
0x07, 0xfa, 0xd2, 0xbe, 0xf5, 0x93, 0xfa, 0x03, 0xfd, 0x8c, 0x7e, 0x46, 0xc1, 0x21, 0xb9, 0x4b, 0xe4, 0x26, 0xb9, 0xcb, 0x23, 0xe5, 0x05, 0xf2, 0x18, 0x79, 0x8c, 0xa8, 0xab, 0xbb, 0x67, 0xda,
0x59, 0x72, 0x90, 0x87, 0x02, 0x7d, 0xe3, 0x21, 0x47, 0x33, 0x67, 0xce, 0x0c, 0x67, 0x29, 0xd8, 0x6b, 0x1b, 0x71, 0x11, 0x29, 0x77, 0x5d, 0xd5, 0xe5, 0xaa, 0xaf, 0xbe, 0xfa, 0x99, 0x36, 0xec,
0xd1, 0x4a, 0xe4, 0x85, 0x48, 0x75, 0x26, 0xf3, 0xc1, 0x5c, 0x49, 0x2d, 0x93, 0x58, 0x2f, 0xe7, 0x29, 0xc9, 0xb3, 0x9c, 0x27, 0x2a, 0x15, 0xd9, 0x60, 0x2e, 0x85, 0x12, 0x51, 0xa8, 0x96, 0x73,
0x58, 0xdc, 0xec, 0xa5, 0x72, 0x36, 0xf3, 0x9b, 0xec, 0x05, 0x5c, 0x79, 0x58, 0x14, 0xa8, 0x8b, 0xcc, 0x6f, 0x75, 0x13, 0x31, 0x9b, 0x39, 0x65, 0xfc, 0x02, 0x76, 0x1f, 0xe5, 0x39, 0xaa, 0xfc,
0x27, 0x98, 0x63, 0x91, 0x15, 0xc9, 0x75, 0xd8, 0x12, 0x33, 0xb9, 0xc8, 0x75, 0xbf, 0xb9, 0xd7, 0x29, 0x66, 0x98, 0xa7, 0x79, 0x74, 0x03, 0x9a, 0x7c, 0x26, 0x16, 0x99, 0xea, 0xd5, 0xfb, 0xb5,
0xb8, 0x1b, 0x71, 0x87, 0x92, 0x3b, 0x70, 0x45, 0xa1, 0x5e, 0xa8, 0xfc, 0xe1, 0x68, 0xa4, 0xb0, 0x7b, 0x01, 0xb3, 0x52, 0x74, 0x17, 0x76, 0x25, 0xaa, 0x85, 0xcc, 0x1e, 0x8d, 0x46, 0x12, 0xf3,
0x28, 0xfa, 0xd1, 0x5e, 0xe3, 0x6e, 0x87, 0xaf, 0x6e, 0xb2, 0xdf, 0x1a, 0xb0, 0x6b, 0xfd, 0x0d, 0xbc, 0x17, 0xf4, 0x6b, 0xf7, 0xda, 0x6c, 0x55, 0x19, 0xff, 0x56, 0x83, 0x7d, 0xe3, 0x6f, 0xa8,
0x4d, 0xfc, 0x53, 0x54, 0x43, 0xf9, 0x75, 0x89, 0x69, 0xf2, 0x5f, 0xe8, 0xa4, 0x32, 0xcb, 0xb5, 0xe3, 0x9f, 0xa3, 0x1c, 0x8a, 0xaf, 0x0b, 0x4c, 0xa2, 0xff, 0x42, 0x3b, 0x11, 0x69, 0xa6, 0xc4,
0x7c, 0x8f, 0x79, 0xbf, 0x41, 0x3f, 0xad, 0x37, 0x2e, 0x0d, 0x9a, 0x40, 0x2b, 0x97, 0x1a, 0x29, 0x7b, 0xcc, 0x7a, 0x35, 0xfa, 0x69, 0xa5, 0xd8, 0x1a, 0x34, 0x82, 0x46, 0x26, 0x14, 0x52, 0xac,
0x56, 0x8f, 0xd3, 0x3a, 0xb9, 0x09, 0x6d, 0x2c, 0x31, 0x7d, 0x29, 0x66, 0xd8, 0x6f, 0x91, 0xa3, 0x2e, 0xa3, 0x73, 0x74, 0x0b, 0x5a, 0x58, 0x60, 0xf2, 0x92, 0xcf, 0xb0, 0xd7, 0x20, 0x47, 0xa5,
0x0a, 0x27, 0x57, 0xa1, 0xa9, 0x65, 0x3f, 0xa6, 0xdd, 0xa6, 0x96, 0xec, 0x97, 0x06, 0x5c, 0xb5, 0x1c, 0x5d, 0x83, 0xba, 0x12, 0xbd, 0x90, 0xb4, 0x75, 0x25, 0xe2, 0x5f, 0x6a, 0x70, 0xcd, 0xc0,
0x74, 0xde, 0x65, 0x7a, 0x32, 0x52, 0xe2, 0xc3, 0xbf, 0x44, 0xe4, 0x67, 0xcf, 0xc3, 0xcb, 0xf2, 0x79, 0x97, 0xaa, 0xc9, 0x48, 0xf2, 0x0f, 0xff, 0x12, 0x90, 0x9f, 0x1d, 0x0e, 0x47, 0xcb, 0x3f,
0x0f, 0xf2, 0xb0, 0xb1, 0x5a, 0x55, 0xac, 0x63, 0x88, 0x29, 0x96, 0x31, 0x36, 0x84, 0x9c, 0x77, 0x88, 0xc3, 0xc4, 0x6a, 0x94, 0xb1, 0x4e, 0x21, 0xa4, 0x58, 0xda, 0x58, 0x03, 0xb2, 0xde, 0xe9,
0x5a, 0x1b, 0xc7, 0xc5, 0x72, 0x76, 0x22, 0xa7, 0xe4, 0xb8, 0xc3, 0x1d, 0x0a, 0x02, 0x46, 0x61, 0xac, 0x1d, 0xe7, 0xcb, 0xd9, 0x99, 0x98, 0x92, 0xe3, 0x36, 0xb3, 0x92, 0x17, 0x30, 0xf0, 0x03,
0x40, 0xf6, 0x57, 0x03, 0xda, 0x8f, 0x15, 0x0a, 0x8d, 0xc3, 0xd2, 0x45, 0x6a, 0xf8, 0x48, 0x97, 0xc6, 0x7f, 0xd5, 0xa0, 0xf5, 0x44, 0x22, 0x57, 0x38, 0x2c, 0x6c, 0xa4, 0x9a, 0x8b, 0xb4, 0x15,
0xb2, 0xbc, 0x06, 0xd1, 0x29, 0xa2, 0xf3, 0x64, 0x96, 0x15, 0xef, 0x56, 0xc0, 0xfb, 0x16, 0x40, 0xe5, 0x75, 0x08, 0xce, 0x11, 0xad, 0x27, 0x7d, 0x2c, 0x71, 0x37, 0x3c, 0xdc, 0xb7, 0x01, 0xd2,
0x56, 0xd5, 0x85, 0xb4, 0x6a, 0xf3, 0x60, 0x27, 0xe9, 0xc3, 0x76, 0x56, 0x0c, 0x49, 0x9f, 0x2d, 0xb2, 0x2e, 0xc4, 0x55, 0x8b, 0x79, 0x9a, 0xa8, 0x07, 0x3b, 0x69, 0x3e, 0x24, 0x7e, 0x9a, 0x74,
0x3a, 0xf4, 0x30, 0xd9, 0x83, 0x2e, 0xc9, 0xf4, 0xda, 0x66, 0xb2, 0x4d, 0x84, 0xc2, 0xad, 0x95, 0xe9, 0xc4, 0xa8, 0x0f, 0x1d, 0xa2, 0xe9, 0xb5, 0xc9, 0x64, 0x87, 0x00, 0xf9, 0xaa, 0x95, 0xda,
0xda, 0xb4, 0x2f, 0xd4, 0xe6, 0x3a, 0x6c, 0x99, 0x35, 0xaa, 0x7e, 0xc7, 0x4a, 0x60, 0x11, 0xcb, 0xb4, 0xae, 0xd4, 0xe6, 0x06, 0x34, 0xf5, 0x19, 0x65, 0xaf, 0x6d, 0x28, 0x30, 0x52, 0x9c, 0x41,
0xa1, 0xc7, 0xf1, 0x9d, 0xca, 0x34, 0x72, 0xf1, 0xc1, 0x65, 0x5b, 0x56, 0xd9, 0xfa, 0xec, 0xa3, 0x97, 0xe1, 0x3b, 0x99, 0x2a, 0x64, 0xfc, 0x83, 0xcd, 0xb6, 0x28, 0xb3, 0x75, 0xd9, 0x07, 0x7e,
0x30, 0x7b, 0x2c, 0xe7, 0x99, 0xf2, 0xd5, 0x77, 0xc8, 0x67, 0x1f, 0xd7, 0xd9, 0xef, 0x42, 0x9c, 0xf6, 0x58, 0xcc, 0x53, 0xe9, 0xaa, 0x6f, 0x25, 0x97, 0x7d, 0x58, 0x65, 0xbf, 0x0f, 0x61, 0x9a,
0xe5, 0x23, 0x2c, 0x29, 0x8f, 0x98, 0x5b, 0xc0, 0xee, 0xc1, 0x75, 0xa7, 0x6c, 0x7d, 0x55, 0x9f, 0x8d, 0xb0, 0xa0, 0x3c, 0x42, 0x66, 0x84, 0xf8, 0x3e, 0xdc, 0xb0, 0xcc, 0x56, 0xa3, 0xfa, 0x54,
0x28, 0xb9, 0x98, 0x1b, 0x0f, 0xba, 0x2c, 0xfa, 0x8d, 0xbd, 0xe8, 0x6e, 0x87, 0x9b, 0x25, 0xbb, 0x8a, 0xc5, 0x5c, 0x7b, 0x50, 0x45, 0xde, 0xab, 0xf5, 0x83, 0x7b, 0x6d, 0xa6, 0x8f, 0xf1, 0x6d,
0x05, 0xed, 0x37, 0x79, 0x91, 0x8d, 0xf3, 0x61, 0x69, 0xb4, 0x1c, 0x09, 0x2d, 0x88, 0x59, 0x8f, 0x68, 0xbd, 0xc9, 0xf2, 0x74, 0x9c, 0x0d, 0x0b, 0xcd, 0xe5, 0x88, 0x2b, 0x4e, 0xc8, 0xba, 0x8c,
0xd3, 0x9a, 0x49, 0xe8, 0xbe, 0x94, 0x8f, 0xc4, 0x54, 0xe4, 0xa9, 0x29, 0xd4, 0x2e, 0xc4, 0xba, 0xce, 0xb1, 0x80, 0xce, 0x4b, 0xf1, 0x98, 0x4f, 0x79, 0x96, 0xe8, 0x42, 0xed, 0x43, 0xa8, 0x8a,
0x7c, 0x8a, 0x9e, 0xbd, 0x05, 0x46, 0xd0, 0xb9, 0x58, 0x9a, 0xab, 0xea, 0x8a, 0xef, 0x21, 0x9d, 0x67, 0xe8, 0xd0, 0x1b, 0x41, 0x13, 0x3a, 0xe7, 0x4b, 0x3d, 0xaa, 0xb6, 0xf8, 0x4e, 0xa4, 0x1b,
0xa8, 0xec, 0xfc, 0x3d, 0x2e, 0x5d, 0x7e, 0x1e, 0x5e, 0x96, 0x24, 0xfb, 0xb5, 0x09, 0xdd, 0x80, 0x99, 0x5e, 0xbe, 0xc7, 0xa5, 0xcd, 0xcf, 0x89, 0xdb, 0x92, 0x8c, 0x7f, 0xad, 0x43, 0xc7, 0xc3,
0x77, 0x20, 0xaa, 0xa5, 0xe5, 0x90, 0x8b, 0x39, 0x95, 0x62, 0x44, 0x31, 0x7b, 0xdc, 0xc3, 0x64, 0xed, 0x91, 0x6a, 0x60, 0x59, 0xc9, 0xc6, 0x9c, 0x0a, 0x3e, 0xa2, 0x98, 0x5d, 0xe6, 0xc4, 0x68,
0x00, 0x1d, 0x93, 0x90, 0xd0, 0x0b, 0x65, 0x5b, 0xa5, 0x7b, 0x70, 0x6d, 0x40, 0x23, 0x6a, 0xf0, 0x00, 0x6d, 0x9d, 0x10, 0x57, 0x0b, 0x69, 0x5a, 0xa5, 0x73, 0x78, 0x7d, 0x40, 0x2b, 0x6a, 0xf0,
0xda, 0xef, 0xf3, 0xda, 0xc4, 0xcb, 0xda, 0xaa, 0x65, 0xad, 0xb9, 0x59, 0xad, 0x7d, 0x01, 0x76, 0xda, 0xe9, 0x59, 0x65, 0xe2, 0x68, 0x6d, 0x54, 0xb4, 0x56, 0xd8, 0x0c, 0xd7, 0xae, 0x00, 0xfb,
0x21, 0xce, 0x65, 0x9e, 0x22, 0xc9, 0x1d, 0x71, 0x0b, 0x5c, 0xf9, 0xb6, 0xab, 0xf2, 0xdd, 0x02, 0x10, 0x66, 0x22, 0x4b, 0x90, 0xe8, 0x0e, 0x98, 0x11, 0x6c, 0xf9, 0x76, 0xca, 0xf2, 0xdd, 0x06,
0x18, 0x1b, 0xb5, 0x1f, 0x53, 0x03, 0xb7, 0xa9, 0x32, 0xc1, 0x8e, 0xf1, 0x3e, 0x41, 0x31, 0x72, 0x18, 0x6b, 0xb6, 0x9f, 0x50, 0x03, 0xb7, 0xa8, 0x32, 0x9e, 0x46, 0x7b, 0x9f, 0x20, 0x1f, 0xd9,
0x6d, 0xd2, 0xe3, 0x0e, 0x51, 0x2b, 0x63, 0xa9, 0xfb, 0xe0, 0x5a, 0x19, 0x4b, 0xcd, 0x1e, 0x40, 0x36, 0xe9, 0x32, 0x2b, 0x51, 0x2b, 0x63, 0xa1, 0x7a, 0x60, 0x5b, 0x19, 0x0b, 0x15, 0x3f, 0x84,
0x2f, 0x10, 0xa3, 0x48, 0xee, 0xd4, 0x05, 0xec, 0x1e, 0x24, 0x2e, 0xab, 0xc0, 0xc2, 0x16, 0xf5, 0xae, 0x47, 0x46, 0x1e, 0xdd, 0xad, 0x0a, 0xd8, 0x39, 0x8c, 0x6c, 0x56, 0x9e, 0x85, 0x29, 0xea,
0x4b, 0xb8, 0xc2, 0xb3, 0x7c, 0x5c, 0x65, 0x9b, 0x0c, 0x20, 0xce, 0x34, 0xce, 0xfc, 0x0f, 0xfb, 0x97, 0xb0, 0xcb, 0xd2, 0x6c, 0x5c, 0x66, 0x1b, 0x0d, 0x20, 0x4c, 0x15, 0xce, 0xdc, 0x0f, 0x7b,
0xee, 0x87, 0x2b, 0x46, 0xcf, 0x34, 0xce, 0xb8, 0x35, 0x63, 0xcf, 0x60, 0x67, 0xed, 0xcc, 0xf0, 0xf6, 0x87, 0x2b, 0x46, 0x27, 0x0a, 0x67, 0xcc, 0x98, 0xc5, 0x27, 0xb0, 0xb7, 0x76, 0xa7, 0x71,
0x9e, 0x2f, 0x4e, 0x4c, 0x29, 0x8d, 0x97, 0x1e, 0x77, 0xc8, 0x0c, 0x9c, 0x5a, 0xef, 0x26, 0x1d, 0xcf, 0x17, 0x67, 0xba, 0x94, 0xda, 0x4b, 0x97, 0x59, 0x49, 0x2f, 0x9c, 0x8a, 0xef, 0x3a, 0x5d,
0xd5, 0x1b, 0xec, 0x3b, 0xe8, 0xd4, 0x3c, 0x8c, 0x54, 0x4b, 0x2a, 0x64, 0xcc, 0x9b, 0x7a, 0x19, 0x55, 0x8a, 0xf8, 0x3b, 0x68, 0x57, 0x38, 0x34, 0x55, 0x4b, 0x2a, 0x64, 0xc8, 0xea, 0x6a, 0xe9,
0xb8, 0xb4, 0x35, 0xdc, 0xe8, 0xd2, 0x8e, 0xa4, 0xc0, 0xe5, 0x4f, 0xd0, 0x33, 0xcd, 0xf5, 0xed, 0xb9, 0x34, 0x35, 0xdc, 0xe8, 0xd2, 0xac, 0x24, 0xcf, 0xe5, 0x4f, 0xd0, 0xd5, 0xcd, 0xf5, 0xed,
0x39, 0xaa, 0xf3, 0x0c, 0xe9, 0x3e, 0x2b, 0x4c, 0xb3, 0x73, 0xd7, 0x23, 0x11, 0xf7, 0xd0, 0x9c, 0x25, 0xca, 0xcb, 0x14, 0x69, 0x9e, 0x25, 0x26, 0xe9, 0xa5, 0xed, 0x91, 0x80, 0x39, 0x51, 0xdf,
0x9c, 0xd8, 0xde, 0x75, 0x83, 0xc4, 0x43, 0x73, 0xa2, 0xcb, 0xc7, 0xc1, 0x5c, 0xf2, 0x90, 0xfd, 0x9c, 0x99, 0xde, 0xb5, 0x8b, 0xc4, 0x89, 0xfa, 0x46, 0x15, 0x4f, 0xbc, 0xbd, 0xe4, 0xc4, 0xf8,
0xde, 0x80, 0x6d, 0x8e, 0x67, 0xd4, 0xbe, 0x09, 0xb4, 0x84, 0xe9, 0x6a, 0x37, 0xe8, 0x84, 0xdb, 0xf7, 0x1a, 0xec, 0x30, 0xbc, 0xa0, 0xf6, 0x8d, 0xa0, 0xc1, 0x75, 0x57, 0xdb, 0x45, 0xc7, 0xad,
0x3b, 0x9d, 0x8a, 0x31, 0x39, 0x8c, 0x39, 0xad, 0x4d, 0x63, 0xa4, 0x95, 0xaf, 0x98, 0x5b, 0x60, 0xee, 0x7c, 0xca, 0xc7, 0xe4, 0x30, 0x64, 0x74, 0xd6, 0x8d, 0x91, 0x94, 0xbe, 0x42, 0x66, 0x04,
0xb2, 0x18, 0x65, 0x0a, 0xa9, 0x30, 0xd4, 0x5e, 0x31, 0xaf, 0x37, 0x6c, 0x1b, 0x64, 0xe3, 0x89, 0x9d, 0xc5, 0x28, 0x95, 0x48, 0x85, 0xa1, 0xf6, 0x0a, 0x59, 0xa5, 0x30, 0x6d, 0x90, 0x8e, 0x27,
0xf6, 0x4d, 0x66, 0xd1, 0xea, 0x9d, 0x8e, 0xfc, 0x9d, 0xfe, 0x1e, 0x80, 0xe3, 0xd9, 0x2b, 0x95, 0xca, 0x35, 0x99, 0x91, 0x56, 0x67, 0x3a, 0x70, 0x33, 0xfd, 0x3d, 0x00, 0xc3, 0x8b, 0x57, 0x32,
0x9d, 0x8b, 0x74, 0x59, 0xc7, 0x6b, 0x5c, 0x1a, 0xaf, 0x79, 0x79, 0xbc, 0x28, 0x8c, 0xc7, 0x6e, 0xbd, 0xe4, 0xc9, 0xb2, 0x8a, 0x57, 0xdb, 0x1a, 0xaf, 0xbe, 0x3d, 0x5e, 0xe0, 0xc7, 0x8b, 0x6f,
0x40, 0xfc, 0x14, 0xcb, 0xf5, 0xb1, 0xc4, 0x16, 0xd0, 0xe5, 0x38, 0x9f, 0x2e, 0x87, 0xe5, 0xb3, 0x42, 0xf8, 0x0c, 0x8b, 0xf5, 0xb5, 0x14, 0x2f, 0xa0, 0xc3, 0x70, 0x3e, 0x5d, 0x0e, 0x8b, 0x93,
0xfc, 0x54, 0x9a, 0xbc, 0x27, 0xa2, 0x98, 0xf8, 0xe9, 0x60, 0xd6, 0x81, 0xcf, 0xe6, 0xe6, 0x1c, 0xec, 0x5c, 0xe8, 0xbc, 0x27, 0x3c, 0x9f, 0xb8, 0xed, 0xa0, 0xcf, 0x9e, 0xcf, 0xfa, 0xe6, 0x1c,
0xa2, 0x20, 0x87, 0xe4, 0x0e, 0x6c, 0x09, 0xfa, 0x56, 0xf5, 0x5b, 0xd4, 0x86, 0x3d, 0xd7, 0x86, 0x02, 0x2f, 0x87, 0xe8, 0x2e, 0x34, 0x39, 0x7d, 0xab, 0x7a, 0x0d, 0x6a, 0xc3, 0xae, 0x6d, 0x43,
0xf4, 0x51, 0xe1, 0xee, 0x8c, 0xfd, 0x1f, 0x3a, 0x1c, 0xcf, 0x86, 0xe5, 0xf3, 0xac, 0xd0, 0xab, 0xfa, 0xa8, 0x30, 0x7b, 0x17, 0xff, 0x1f, 0xda, 0x0c, 0x2f, 0x86, 0xc5, 0xf3, 0x34, 0x57, 0xab,
0x89, 0x46, 0x2e, 0x51, 0x76, 0x58, 0x31, 0x23, 0xa3, 0x4f, 0xbb, 0x14, 0x03, 0xb8, 0x4a, 0x3f, 0x89, 0x06, 0x36, 0xd1, 0xf8, 0xa8, 0x44, 0x46, 0x46, 0x9f, 0x36, 0x14, 0x03, 0xb8, 0x46, 0x3f,
0x7a, 0xa5, 0xe4, 0x1c, 0xd5, 0x37, 0x88, 0x46, 0xaf, 0xb9, 0x07, 0x2e, 0x40, 0xbd, 0xc1, 0x38, 0x7a, 0x25, 0xc5, 0x1c, 0xe5, 0x37, 0x88, 0x9a, 0xaf, 0xb9, 0x13, 0x6c, 0x80, 0x4a, 0x11, 0x33,
0xc0, 0xb0, 0x7c, 0x2a, 0x8a, 0x09, 0xc5, 0x30, 0x99, 0x8a, 0x62, 0x82, 0x85, 0x6f, 0x7e, 0x8b, 0x80, 0x61, 0xf1, 0x8c, 0xe7, 0x13, 0x8a, 0xa1, 0x33, 0xe5, 0xf9, 0x04, 0x73, 0xd7, 0xfc, 0x46,
0x6a, 0x82, 0xcd, 0x80, 0x60, 0x30, 0x40, 0xa2, 0xbd, 0xa8, 0x1e, 0x20, 0xec, 0x0b, 0xf3, 0x25, 0xaa, 0x00, 0xd6, 0x3d, 0x80, 0xde, 0x02, 0x09, 0xfa, 0x41, 0xb5, 0x40, 0xe2, 0x2f, 0xf4, 0x97,
0xa8, 0x24, 0x2d, 0x92, 0xfb, 0xa6, 0x0b, 0x69, 0x79, 0x81, 0x7d, 0x60, 0xc5, 0xbd, 0x09, 0x1b, 0xa0, 0xa4, 0x34, 0x8f, 0x1e, 0xe8, 0x2e, 0xa4, 0xe3, 0x15, 0xf4, 0x9e, 0x15, 0x73, 0x26, 0xf1,
0x98, 0x1e, 0x48, 0x31, 0x9b, 0xeb, 0xe7, 0x72, 0xbc, 0x76, 0x97, 0xae, 0x41, 0x34, 0x95, 0x63, 0x40, 0xf7, 0x40, 0x82, 0xe9, 0x5c, 0x3d, 0x17, 0xe3, 0xb5, 0x59, 0xba, 0x0e, 0xc1, 0x54, 0x8c,
0x77, 0x91, 0xcc, 0x92, 0x09, 0xd3, 0xc8, 0x64, 0xbf, 0x66, 0x7c, 0x1b, 0x9a, 0xc7, 0x6f, 0xe9, 0xed, 0x20, 0xe9, 0x63, 0xcc, 0x75, 0x23, 0x93, 0xfd, 0x9a, 0xf1, 0x1d, 0xa8, 0x9f, 0xbe, 0xa5,
0xb2, 0x76, 0x0f, 0xfe, 0xe3, 0x62, 0x1e, 0xe3, 0xf2, 0xad, 0x98, 0x2e, 0x90, 0x37, 0x8f, 0xdf, 0x61, 0xed, 0x1c, 0xfe, 0xc7, 0xc6, 0x3c, 0xc5, 0xe5, 0x5b, 0x3e, 0x5d, 0x20, 0xab, 0x9f, 0xbe,
0x26, 0x9f, 0x41, 0x6b, 0x2a, 0xc7, 0x05, 0xf1, 0xef, 0x1e, 0xec, 0x54, 0xb4, 0x7c, 0x78, 0x4e, 0x8d, 0x3e, 0x83, 0xc6, 0x54, 0x8c, 0x73, 0xc2, 0xdf, 0x39, 0xdc, 0x2b, 0x61, 0xb9, 0xf0, 0x8c,
0xc7, 0xec, 0xc8, 0x54, 0x82, 0xf6, 0x8e, 0x84, 0x16, 0x6b, 0x61, 0x3e, 0xd1, 0xcb, 0x9f, 0x0d, 0xae, 0xe3, 0x63, 0x5d, 0x09, 0xd2, 0x1d, 0x73, 0xc5, 0xd7, 0xc2, 0x7c, 0xa2, 0x97, 0x3f, 0x6b,
0x68, 0x0f, 0x4b, 0x8e, 0xc5, 0x62, 0xaa, 0x83, 0x9e, 0x6a, 0x6c, 0xee, 0xa9, 0x66, 0xf0, 0xad, 0xd0, 0x1a, 0x16, 0x0c, 0xf3, 0xc5, 0x54, 0x79, 0x3d, 0x55, 0xdb, 0xdc, 0x53, 0x75, 0xef, 0x5b,
0x4b, 0x18, 0x35, 0xad, 0x9d, 0xf2, 0x9b, 0x4a, 0x6f, 0xbe, 0xaf, 0x0f, 0xa0, 0xab, 0x6c, 0xc8, 0x17, 0xc5, 0xd4, 0xb4, 0x66, 0xcb, 0x6f, 0x2a, 0xbd, 0xfe, 0xbe, 0x3e, 0x84, 0x8e, 0x34, 0x21,
0x91, 0x70, 0x4f, 0x85, 0x50, 0xe9, 0x8a, 0x3e, 0x0f, 0xcd, 0x4c, 0x77, 0x9c, 0x4c, 0x65, 0xfa, 0x47, 0xdc, 0x3e, 0x15, 0x7c, 0xa6, 0x4b, 0xf8, 0xcc, 0x37, 0xd3, 0xdd, 0x71, 0x36, 0x15, 0xc9,
0x5e, 0x67, 0x33, 0xff, 0x1d, 0xa8, 0x37, 0xcc, 0x90, 0xb7, 0x11, 0xe8, 0x25, 0xb0, 0x45, 0x97, 0x7b, 0x95, 0xce, 0xdc, 0x77, 0xa0, 0x52, 0xe8, 0x25, 0x6f, 0x22, 0xd0, 0x4b, 0xa0, 0x49, 0x43,
0x26, 0xd8, 0x61, 0x7f, 0x34, 0x61, 0x27, 0xe0, 0x71, 0x84, 0x5a, 0x64, 0x53, 0xc7, 0xb6, 0xf1, 0xe3, 0x69, 0xe2, 0x3f, 0xea, 0xb0, 0xe7, 0xe1, 0x38, 0x46, 0xc5, 0xd3, 0xa9, 0x45, 0x5b, 0xfb,
0x51, 0xb6, 0xf7, 0x69, 0x9a, 0x19, 0x1a, 0x94, 0xe9, 0x66, 0xa6, 0xde, 0x84, 0x26, 0xa8, 0x92, 0x28, 0xda, 0x07, 0xb4, 0xcd, 0x34, 0x0c, 0xca, 0x74, 0x33, 0x52, 0x67, 0x42, 0x1b, 0x54, 0x0a,
0xf2, 0xd4, 0x6a, 0x6c, 0x26, 0x28, 0xa1, 0x40, 0xc5, 0xd6, 0x66, 0x15, 0xe3, 0xf0, 0x66, 0xae, 0x71, 0x6e, 0x38, 0xd6, 0x1b, 0x94, 0x24, 0x8f, 0xc5, 0xc6, 0x66, 0x16, 0x43, 0x7f, 0x32, 0x57,
0xe4, 0xba, 0x75, 0x31, 0xd7, 0xfa, 0x35, 0xb6, 0xbd, 0xf2, 0x1a, 0xbb, 0x09, 0xed, 0x53, 0x25, 0x72, 0x6d, 0x5e, 0xcd, 0xb5, 0x7a, 0x8d, 0xed, 0xac, 0xbc, 0xc6, 0x6e, 0x41, 0xeb, 0x5c, 0x8a,
0x67, 0x34, 0x21, 0xdd, 0x5b, 0xc8, 0xe3, 0x0b, 0xfa, 0x74, 0x2e, 0xea, 0x13, 0xcc, 0x02, 0xf8, 0x19, 0x6d, 0x48, 0xfb, 0x16, 0x72, 0xf2, 0x15, 0x7e, 0xda, 0x57, 0xf9, 0xf1, 0x76, 0x01, 0x7c,
0xc8, 0x2c, 0xf8, 0x0a, 0x92, 0x35, 0x11, 0x8b, 0xe4, 0x5e, 0x78, 0xdf, 0xfb, 0xeb, 0x32, 0x5a, 0x64, 0x17, 0x7c, 0x05, 0xd1, 0x1a, 0x89, 0x79, 0x74, 0xdf, 0x9f, 0xf7, 0xde, 0x3a, 0x8d, 0xc6,
0x3b, 0x7b, 0xeb, 0xf7, 0xa0, 0xed, 0x86, 0x39, 0xdd, 0x55, 0xc3, 0xcd, 0xbf, 0x7f, 0x2c, 0x60, 0xce, 0x4c, 0x7d, 0x1f, 0x5a, 0x76, 0x99, 0xd3, 0xac, 0x6a, 0x6c, 0xee, 0xfd, 0x63, 0x84, 0xf8,
0xfb, 0x70, 0x83, 0xe3, 0xd9, 0x11, 0xa6, 0x72, 0x44, 0xef, 0xb3, 0xe0, 0xed, 0xb1, 0xf1, 0xb5, 0x00, 0x6e, 0x32, 0xbc, 0x38, 0xc6, 0x44, 0x8c, 0xe8, 0x7d, 0xe6, 0xbd, 0x3d, 0x36, 0xbe, 0x76,
0xc3, 0x3e, 0x87, 0xce, 0x9b, 0x02, 0x15, 0x3d, 0xe8, 0xc8, 0x44, 0xce, 0xb3, 0xb4, 0x32, 0x31, 0xe2, 0xcf, 0xa1, 0xfd, 0x26, 0x47, 0x49, 0x0f, 0x3a, 0x32, 0x11, 0xf3, 0x34, 0x29, 0x4d, 0xb4,
0xc0, 0x7c, 0x5d, 0x52, 0x99, 0x6b, 0x74, 0x73, 0xa1, 0xc3, 0x3d, 0x64, 0x3f, 0x42, 0xf7, 0xcd, 0xa0, 0xbf, 0x2e, 0x89, 0xc8, 0x14, 0xda, 0xbd, 0xd0, 0x66, 0x4e, 0x8c, 0x7f, 0x84, 0xce, 0x9b,
0x7c, 0xac, 0xc4, 0x08, 0x5f, 0xa0, 0x16, 0x46, 0xc2, 0x42, 0x0b, 0xa5, 0xb3, 0x7c, 0x4c, 0x1e, 0xf9, 0x58, 0xf2, 0x11, 0xbe, 0x40, 0xc5, 0x35, 0x85, 0xb9, 0xe2, 0x52, 0xa5, 0xd9, 0x98, 0x3c,
0xda, 0xbc, 0xc2, 0xc6, 0xc9, 0x39, 0xaa, 0xc2, 0x0f, 0xf3, 0x0e, 0xf7, 0xf0, 0xb2, 0x51, 0xfe, 0xb4, 0x58, 0x29, 0x6b, 0x27, 0x97, 0x28, 0x73, 0xb7, 0xcc, 0xdb, 0xcc, 0x89, 0x5b, 0x57, 0xf9,
0xe8, 0xf6, 0x0f, 0xff, 0x1b, 0x67, 0x7a, 0xb2, 0x38, 0x19, 0xa4, 0x72, 0xb6, 0x7f, 0x78, 0x98, 0x09, 0xec, 0xd2, 0xea, 0xdc, 0xb2, 0xb5, 0xda, 0xe5, 0xd6, 0xea, 0x43, 0x27, 0xcd, 0x5f, 0x4f,
0xe6, 0xfb, 0xe9, 0x44, 0x64, 0xf9, 0xe1, 0xe1, 0x3e, 0x89, 0x74, 0xb2, 0x45, 0xff, 0xcd, 0x0e, 0x84, 0x54, 0xda, 0x94, 0xdc, 0xb7, 0x98, 0xaf, 0x7a, 0x7c, 0xe7, 0x87, 0xff, 0x8d, 0x53, 0x35,
0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x05, 0xe5, 0x01, 0x1a, 0xc5, 0x0d, 0x00, 0x00, 0x59, 0x9c, 0x0d, 0x12, 0x31, 0x3b, 0x38, 0x3a, 0x4a, 0xb2, 0x83, 0x64, 0xc2, 0xd3, 0xec, 0xe8,
0xe8, 0x80, 0xf8, 0x3e, 0x6b, 0xd2, 0xdf, 0xbc, 0xa3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x09,
0x74, 0x67, 0x46, 0x10, 0x0e, 0x00, 0x00,
} }
...@@ -778,3 +778,11 @@ func ParseExpire(expire string) (int64, error) { ...@@ -778,3 +778,11 @@ func ParseExpire(expire string) (int64, error) {
return 0, err return 0, err
} }
//CalcTxShortHash 取txhash的前指定字节,目前默认5
func CalcTxShortHash(hash []byte) string {
if len(hash) >= 5 {
return hex.EncodeToString(hash[0:5])
}
return ""
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment