Skip to content

Commit

Permalink
cherry pick epoch api from dev-upgrade (#699)
Browse files Browse the repository at this point in the history
* cherry-pick-epoch-api
  • Loading branch information
benjamin202410 authored Oct 29, 2024
1 parent 09fe4ec commit 118ccd0
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 23 deletions.
6 changes: 4 additions & 2 deletions consensus/XDPoS/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
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 {
return nil, err
}
Expand All @@ -359,7 +359,9 @@ func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo,
EpochRound: thisEpoch.Round,
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))
}
return info, nil
Expand Down
7 changes: 7 additions & 0 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ type XDPoS_v2 struct {
epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info
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
signFn clique.SignerFn // Signer function to authorize hashes with
lock sync.RWMutex // Protects the signer fields
Expand Down Expand Up @@ -77,6 +81,7 @@ func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan i
signatures, _ := lru.NewARC(utils.InmemorySnapshots)
epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs))
verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots)
round2epochBlockInfo, _ := lru.NewARC(utils.InmemoryRound2Epochs)

timeoutPool := utils.NewPool()
votePool := utils.NewPool()
Expand All @@ -96,6 +101,8 @@ func New(chainConfig *params.ChainConfig, db ethdb.Database, minePeriodCh chan i
BroadcastCh: make(chan interface{}),
minePeriodCh: minePeriodCh,

round2epochBlockInfo: round2epochBlockInfo,

timeoutPool: timeoutPool,
votePool: votePool,

Expand Down
9 changes: 8 additions & 1 deletion consensus/XDPoS/engines/engine_v2/epochSwitch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
return nil, err
}

snap, err := x.getSnapshot(chain, h.Number.Uint64(), false)
if err != nil {
log.Error("[getEpochSwitchInfo] Adaptor v2 getSnapshot has error", "err", err)
Expand Down Expand Up @@ -155,6 +154,14 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
return true, epochNum, nil
}
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
}

Expand Down
118 changes: 99 additions & 19 deletions consensus/XDPoS/engines/engine_v2/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,43 +220,123 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t
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()
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentHeader, currentHeader.Hash())
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
// 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 {
return epochSwitchInfo.EpochSwitchBlockInfo, nil, nil
return epochSwitchInfo.EpochSwitchBlockInfo, nil
}
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 {
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))
estblockNumDiff := new(big.Int).Mul(epoch, big.NewInt(int64(epochNum-targetEpochNum)))
estBlockNum := new(big.Int).Sub(epochSwitchInfo.EpochSwitchBlockInfo.Number, estblockNumDiff)
if estBlockNum.Cmp(x.config.V2.SwitchBlock) == -1 {
estBlockNum.Set(x.config.V2.SwitchBlock)
}
estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64())
epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader)
if err != nil {
return nil, nil, err
}
for i, info := range epochSwitchInfos {
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch
if epochNum == targetEpochNum {
if i < len(epochSwitchInfos)-1 {
nextEpoch := epochSwitchInfos[i+1].EpochSwitchBlockInfo
return info.EpochSwitchBlockInfo, nextEpoch, nil
// if the targrt is close, we search brute-forcily
closeEpochNum := uint64(2)
if closeEpochNum >= epochNum-targetEpochNum {
estBlockHeader := chain.GetHeaderByNumber(estBlockNum.Uint64())
epochSwitchInfos, err := x.GetEpochSwitchInfoBetween(chain, estBlockHeader, currentHeader)
if err != nil {
return nil, err
}
for _, info := range epochSwitchInfos {
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(info.EpochSwitchBlockInfo.Round)/x.config.Epoch
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())
}
2 changes: 2 additions & 0 deletions consensus/XDPoS/utils/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ var (

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

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 (
Expand Down
2 changes: 1 addition & 1 deletion consensus/tests/engine_v2_tests/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func TestGetBlockByEpochNumber(t *testing.T) {

info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3)
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.Nil(t, err)

Expand Down

0 comments on commit 118ccd0

Please sign in to comment.