cherry pick epoch api from dev-upgrade (#699)

* cherry-pick-epoch-api
This commit is contained in:
benjamin202410 2024-10-29 01:36:22 -07:00 committed by GitHub
parent 09fe4ec646
commit 118ccd08d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 121 additions and 23 deletions

View file

@ -350,7 +350,7 @@ An API exclusively for V2 consensus, designed to assist in getting rewards of th
Given the epoch number, search the epoch switch block. Given the epoch number, search the epoch switch block.
*/ */
func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, error) { func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, error) {
thisEpoch, nextEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber) thisEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -359,7 +359,9 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo,
EpochRound: thisEpoch.Round, EpochRound: thisEpoch.Round,
EpochFirstBlockNumber: thisEpoch.Number, EpochFirstBlockNumber: thisEpoch.Number,
} }
if nextEpoch != nil { nextEpoch, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber+1)
if err == nil {
info.EpochLastBlockNumber = new(big.Int).Sub(nextEpoch.Number, big.NewInt(1)) info.EpochLastBlockNumber = new(big.Int).Sub(nextEpoch.Number, big.NewInt(1))
} }
return info, nil return info, nil

View file

@ -37,6 +37,10 @@ type XDPoS_v2 struct {
epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info
verifiedHeaders *lru.ARCCache verifiedHeaders *lru.ARCCache
// only contains epoch switch block info
// input: round, output: infos of epoch switch block and next epoch switch block info
round2epochBlockInfo *lru.ARCCache
signer common.Address // Ethereum address of the signing key signer common.Address // Ethereum address of the signing key
signFn clique.SignerFn // Signer function to authorize hashes with signFn clique.SignerFn // Signer function to authorize hashes with
lock sync.RWMutex // Protects the signer fields lock sync.RWMutex // Protects the signer fields
@ -77,6 +81,7 @@ func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan i
signatures, _ := lru.NewARC(utils.InmemorySnapshots) signatures, _ := lru.NewARC(utils.InmemorySnapshots)
epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs)) epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs))
verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots) verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots)
round2epochBlockInfo, _ := lru.NewARC(utils.InmemoryRound2Epochs)
timeoutPool := utils.NewPool() timeoutPool := utils.NewPool()
votePool := utils.NewPool() votePool := utils.NewPool()
@ -96,6 +101,8 @@ func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan i
BroadcastCh: make(chan interface{}), BroadcastCh: make(chan interface{}),
minePeriodCh: minePeriodCh, minePeriodCh: minePeriodCh,
round2epochBlockInfo: round2epochBlockInfo,
timeoutPool: timeoutPool, timeoutPool: timeoutPool,
votePool: votePool, votePool: votePool,

View file

@ -56,7 +56,6 @@ func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types
log.Error("[getEpochSwitchInfo] get extra field", "err", err, "number", h.Number.Uint64()) log.Error("[getEpochSwitchInfo] get extra field", "err", err, "number", h.Number.Uint64())
return nil, err return nil, err
} }
snap, err := x.getSnapshot(chain, h.Number.Uint64(), false) snap, err := x.getSnapshot(chain, h.Number.Uint64(), false)
if err != nil { if err != nil {
log.Error("[getEpochSwitchInfo] Adaptor v2 getSnapshot has error", "err", err) log.Error("[getEpochSwitchInfo] Adaptor v2 getSnapshot has error", "err", err)
@ -155,6 +154,14 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
return true, epochNum, nil return true, epochNum, nil
} }
log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash()) log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash())
// if isEpochSwitch, add to cache
if parentRound < epochStartRound {
x.round2epochBlockInfo.Add(round, &types.BlockInfo{
Hash: header.Hash(),
Number: header.Number,
Round: round,
})
}
return parentRound < epochStartRound, epochNum, nil return parentRound < epochStartRound, epochNum, nil
} }

View file

@ -220,43 +220,123 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t
return missedRoundsMetadata, nil return missedRoundsMetadata, nil
} }
func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, *types.BlockInfo, error) { func (x *XDPoS_v2) getBlockByEpochNumberInCache(chain consensus.ChainReader, estRound types.Round) *types.BlockInfo {
epochSwitchInCache := make([]*types.BlockInfo, 0)
for r := estRound; r < estRound+types.Round(x.config.Epoch); r++ {
info, ok := x.round2epochBlockInfo.Get(r)
if ok {
blockInfo := info.(*types.BlockInfo)
epochSwitchInCache = append(epochSwitchInCache, blockInfo)
}
}
if len(epochSwitchInCache) == 1 {
return epochSwitchInCache[0]
} else if len(epochSwitchInCache) == 0 {
return nil
}
// when multiple cache hits, need to find the one in main chain
for _, blockInfo := range epochSwitchInCache {
header := chain.GetHeaderByNumber(blockInfo.Number.Uint64())
if header == nil {
continue
}
if header.Hash() == blockInfo.Hash {
return blockInfo
}
}
return nil
}
func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64, start, end uint64) (*types.BlockInfo, error) {
// `end` must be larger than the target and `start` could be the target
for start < end {
header := chain.GetHeaderByNumber((start + end) / 2)
if header == nil {
return nil, errors.New("header nil in binary search")
}
isEpochSwitch, epochNum, err := x.IsEpochSwitch(header)
if err != nil {
return nil, err
}
if epochNum == targetEpochNum {
_, round, _, err := x.getExtraFields(header)
if err != nil {
return nil, err
}
if isEpochSwitch {
return &types.BlockInfo{
Hash: header.Hash(),
Round: round,
Number: header.Number,
}, nil
} else {
end = header.Number.Uint64()
// trick to shorten the search
estStart := end - uint64(round)%x.config.Epoch
if start < estStart {
start = estStart
}
}
} else if epochNum > targetEpochNum {
end = header.Number.Uint64()
} else if epochNum < targetEpochNum {
// if start keeps the same, means no result and the search is over
nextStart := header.Number.Uint64()
if nextStart == start {
break
}
start = nextStart
}
}
return nil, errors.New("no epoch switch header in binary search (all rounds in this epoch are missed, which is very rare)")
}
func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) {
currentHeader := chain.CurrentHeader() currentHeader := chain.CurrentHeader()
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash()) epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash())
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch
// since below function GetEpochSwitchInfoBetween(chain, start, end) return nil if start == end, we early return the result // if current epoch is this epoch, we early return the result
if targetEpochNum == epochNum { if targetEpochNum == epochNum {
return epochSwitchInfo.EpochSwitchBlockInfo, nil, nil return epochSwitchInfo.EpochSwitchBlockInfo, nil
} }
if targetEpochNum > epochNum { if targetEpochNum > epochNum {
return nil, nil, errors.New("input epoch number > current epoch number") return nil, errors.New("input epoch number > current epoch number")
} }
if targetEpochNum < x.config.V2.SwitchBlock.Uint64()/x.config.Epoch { if targetEpochNum < x.config.V2.SwitchBlock.Uint64()/x.config.Epoch {
return nil, nil, errors.New("input epoch number < v2 begin epoch number") return nil, errors.New("input epoch number < v2 begin epoch number")
} }
// the block's round should be in [estRound,estRound+Epoch-1]
estRound := types.Round((targetEpochNum - x.config.V2.SwitchBlock.Uint64()/x.config.Epoch) * x.config.Epoch)
// check the round2epochBlockInfo cache
blockInfo := x.getBlockByEpochNumberInCache(chain, estRound)
if blockInfo != nil {
return blockInfo, nil
}
// if cache miss, we do search
epoch := big.NewInt(int64(x.config.Epoch)) epoch := big.NewInt(int64(x.config.Epoch))
estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum))) estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum)))
estBlockNum := new(big.Int).Sub(epochSwitchInfo.EpochSwitchBlockInfo.Number, estblockNumDiff) estBlockNum := new(big.Int).Sub(epochSwitchInfo.EpochSwitchBlockInfo.Number, estblockNumDiff)
if estBlockNum.Cmp(x.config.V2.SwitchBlock) == -1 { if estBlockNum.Cmp(x.config.V2.SwitchBlock) == -1 {
estBlockNum.Set(x.config.V2.SwitchBlock) estBlockNum.Set(x.config.V2.SwitchBlock)
} }
estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64()) // if the targrt is close, we search brute-forcily
epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader) closeEpochNum := uint64(2)
if err != nil { if closeEpochNum >= epochNum-targetEpochNum {
return nil, nil, err estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64())
} epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader)
for i, info := range epochSwitchInfos { if err != nil {
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch return nil, err
if epochNum == targetEpochNum { }
if i < len(epochSwitchInfos)-1 { for _, info := range epochSwitchInfos {
nextEpoch := epochSwitchInfos[i+1].EpochSwitchBlockInfo epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch
return info.EpochSwitchBlockInfo, nextEpoch, nil if epochNum == targetEpochNum {
return info.EpochSwitchBlockInfo, nil
} }
return info.EpochSwitchBlockInfo, nil, nil
} }
} }
return nil, nil, errors.New("input epoch number not found (all rounds in this epoch are missed, which is very rare)") // else, we use binary search
return x.binarySearchBlockByEpochNumber(chain, targetEpochNum, estBlockNum.Uint64(), epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64())
} }

View file

@ -17,6 +17,8 @@ var (
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory
InmemoryRound2Epochs = 65536 // Number of mapping of epoch switch blocks for quickly locating epoch switch block. One epoch ~ 0.5hours, so 65536 epochs ~ 3.7 years. And it uses ~ 10MB memory.
) )
const ( const (

View file

@ -217,7 +217,7 @@ func TestGetBlockByEpochNumber(t *testing.T) {
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3) info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3)
assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(1803)) assert.Equal(t, info.EpochFirstBlockNumber.Int64(), int64(1803))
assert.Equal(t, info.EpochLastBlockNumber.Int64(), int64(1803)) assert.Nil(t, info.EpochLastBlockNumber)
assert.Equal(t, info.EpochRound, types.Round(largeRound)) assert.Equal(t, info.EpochRound, types.Round(largeRound))
assert.Nil(t, err) assert.Nil(t, err)