mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 18:30:45 +00:00
feat: add api xdpos_getBlockInfoByEpochNum (#674)
* feat: add api xdpos_getBlockInfoByEpochNum * feat: add cache round2epochBlockInfo * fix: round2epochBlockInfo contains round now * feat: binary search in GetBlockByEpochNumber * fix: change some code back, refine style
This commit is contained in:
parent
f12e92bbc7
commit
71b9005f34
8 changed files with 230 additions and 1 deletions
|
|
@ -344,3 +344,19 @@ func (api *API) GetEpochNumbersBetween(begin, end *rpc.BlockNumber) ([]uint64, e
|
||||||
}
|
}
|
||||||
return epochSwitchNumbers, nil
|
return epochSwitchNumbers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
An API exclusively for V2 consensus, designed to assist in getting rewards of the epoch number.
|
||||||
|
Given the epoch number, search the epoch switch block.
|
||||||
|
*/
|
||||||
|
func (api *API) GetBlockInfoByEpochNum(epochNumber uint64) (*utils.EpochNumInfo, error) {
|
||||||
|
result, err := api.XDPoS.EngineV2.GetBlockByEpochNumber(api.chain, epochNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &utils.EpochNumInfo{
|
||||||
|
EpochBlockHash: result.Hash,
|
||||||
|
EpochRound: result.Round,
|
||||||
|
EpochBlockNumber: result.Number,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,6 +182,10 @@ func (x *XDPoS_v2) GetEpochSwitchInfoBetween(chain consensus.ChainReader, begin,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
iteratorHeader = nil
|
iteratorHeader = nil
|
||||||
|
// V2 switch epoch switch info has nil parent
|
||||||
|
if epochSwitchInfo.EpochSwitchParentBlockInfo == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
iteratorHash = epochSwitchInfo.EpochSwitchParentBlockInfo.Hash
|
iteratorHash = epochSwitchInfo.EpochSwitchParentBlockInfo.Hash
|
||||||
iteratorNum = epochSwitchInfo.EpochSwitchBlockInfo.Number
|
iteratorNum = epochSwitchInfo.EpochSwitchBlockInfo.Number
|
||||||
if iteratorNum.Cmp(begin.Number) >= 0 {
|
if iteratorNum.Cmp(begin.Number) >= 0 {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package engine_v2
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
|
|
@ -218,3 +219,124 @@ func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *t
|
||||||
|
|
||||||
return missedRoundsMetadata, nil
|
return missedRoundsMetadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, err
|
||||||
|
}
|
||||||
|
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch
|
||||||
|
// if current epoch is this epoch, we early return the result
|
||||||
|
if targetEpochNum == epochNum {
|
||||||
|
return epochSwitchInfo.EpochSwitchBlockInfo, nil
|
||||||
|
}
|
||||||
|
if targetEpochNum > epochNum {
|
||||||
|
return nil, errors.New("input epoch number > current epoch number")
|
||||||
|
}
|
||||||
|
if targetEpochNum < x.config.V2.SwitchBlock.Uint64()/x.config.Epoch {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else, we use binary search
|
||||||
|
return x.binarySearchBlockByEpochNumber(chain, targetEpochNum, estBlockNum.Uint64(), epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -71,3 +71,10 @@ type PublicApiMissedRoundsMetadata struct {
|
||||||
EpochBlockNumber *big.Int
|
EpochBlockNumber *big.Int
|
||||||
MissedRounds []MissedRoundInfo
|
MissedRounds []MissedRoundInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an epoch number, this struct records the epoch switch block (first block in epoch) infos such as block number
|
||||||
|
type EpochNumInfo struct {
|
||||||
|
EpochBlockHash common.Hash `json:"hash"`
|
||||||
|
EpochRound types.Round `json:"round"`
|
||||||
|
EpochBlockNumber *big.Int `json:"number"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,3 +168,62 @@ func TestGetEpochNumbersBetween(t *testing.T) {
|
||||||
assert.Nil(t, numbers)
|
assert.Nil(t, numbers)
|
||||||
assert.EqualError(t, err, "illegal begin block number")
|
assert.EqualError(t, err, "illegal begin block number")
|
||||||
}
|
}
|
||||||
|
func TestGetBlockByEpochNumber(t *testing.T) {
|
||||||
|
blockchain, _, currentBlock, signer, signFn := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, 1802, params.TestXDPoSMockChainConfig)
|
||||||
|
|
||||||
|
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||||
|
largeRound := int64(1802)
|
||||||
|
newBlock := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, int(currentBlock.NumberU64())+1, largeRound, blockCoinBase, signer, signFn, nil, nil, currentBlock.Header().Root.Hex())
|
||||||
|
err := blockchain.InsertBlock(newBlock)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
largeRound2 := int64(3603)
|
||||||
|
newBlock2 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, newBlock, int(newBlock.NumberU64())+1, largeRound2, blockCoinBase, signer, signFn, nil, nil, newBlock.Header().Root.Hex())
|
||||||
|
err = blockchain.InsertBlock(newBlock2)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// block num, round, epoch is as follows
|
||||||
|
// 900,0,1 (v2 switch block, not v2 epoch switch block)
|
||||||
|
// 901,1,1 (1st epoch switch block)
|
||||||
|
// 902,2,1
|
||||||
|
// ...
|
||||||
|
// 1800,900,2 (2nd epoch switch block)
|
||||||
|
// 1801,901,2
|
||||||
|
// 1802,902,2
|
||||||
|
// 1803,1802,3 (epoch switch)
|
||||||
|
// epoch 4 has no block
|
||||||
|
// 1804,3603,5 (epoch switch)
|
||||||
|
engine := blockchain.Engine().(*XDPoS.XDPoS)
|
||||||
|
|
||||||
|
// init the snapshot, otherwise getEpochSwitchInfo would return error
|
||||||
|
checkpointHeader := blockchain.GetHeaderByNumber(blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() + 1)
|
||||||
|
err = engine.Initial(blockchain, checkpointHeader)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
info, err := engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(0)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, info)
|
||||||
|
|
||||||
|
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(1)
|
||||||
|
assert.Equal(t, info.EpochRound, types.Round(1))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(2)
|
||||||
|
assert.Equal(t, info.EpochRound, types.Round(900))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(3)
|
||||||
|
assert.Equal(t, info.EpochRound, types.Round(largeRound))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(4)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, info)
|
||||||
|
|
||||||
|
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(5)
|
||||||
|
assert.Equal(t, info.EpochRound, types.Round(largeRound2))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
info, err = engine.APIs(blockchain)[0].Service.(*XDPoS.API).GetBlockInfoByEpochNum(6)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
assert.Nil(t, info)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,11 @@ web3._extend({
|
||||||
params: 2,
|
params: 2,
|
||||||
inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter]
|
inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter]
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'getBlockInfoByEpochNum',
|
||||||
|
call: 'XDPoS_getBlockInfoByEpochNum',
|
||||||
|
params: 1,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
properties: [
|
properties: [
|
||||||
new web3._extend.Property({
|
new web3._extend.Property({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue