mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
isEpochSwitch, getMasternodes (#35)
* isEpochSwitch, getMasternodes * recursive getEpochSwitchInfo, getMasternodes * more log, make getEpochSwitchInfo clearer
This commit is contained in:
parent
ebbbf26127
commit
5c326961ce
8 changed files with 253 additions and 19 deletions
|
|
@ -305,12 +305,27 @@ func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) {
|
|||
func (x *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(preCheckpointHeader.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return []common.Address{}
|
||||
return x.EngineV2.GetMasternodesFromEpochSwitchHeader(preCheckpointHeader)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodesFromCheckpointHeader(preCheckpointHeader, n, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Check is epoch switch (checkpoint) block
|
||||
func (x *XDPoS) IsEpochSwitch(header *types.Header) bool {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
b, _, err := x.EngineV2.IsEpochSwitch(header)
|
||||
if err != nil {
|
||||
log.Error("[IsEpochSwitch] Adaptor v2 IsEpochSwitch has error", "err", err)
|
||||
return false
|
||||
}
|
||||
return b
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.IsEpochSwitch(header)
|
||||
}
|
||||
}
|
||||
|
||||
// Same DB across all consensus engines
|
||||
func (x *XDPoS) GetDb() ethdb.Database {
|
||||
return x.db
|
||||
|
|
|
|||
|
|
@ -988,3 +988,8 @@ func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
|
|||
}
|
||||
return fakeEngine
|
||||
}
|
||||
|
||||
// Epoch Switch is also known as checkpoint in v1
|
||||
func (x *XDPoS_v1) IsEpochSwitch(header *types.Header) bool {
|
||||
return (header.Number.Uint64() % x.config.Epoch) == 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ type XDPoS_v2 struct {
|
|||
config *params.XDPoSConfig // Consensus engine configuration parameters
|
||||
db ethdb.Database // Database to store and retrieve snapshot checkpoints
|
||||
|
||||
recents *lru.ARCCache // Snapshots for recent block to speed up reorgs
|
||||
signatures *lru.ARCCache // Signatures of recent blocks to speed up mining
|
||||
recents *lru.ARCCache // Snapshots for recent block to speed up reorgs
|
||||
signatures *lru.ARCCache // Signatures of recent blocks to speed up mining
|
||||
epochSwitches *lru.ARCCache // infos of epoch: master nodes, epoch switch block info, parent of that info
|
||||
|
||||
signer common.Address // Ethereum address of the signing key
|
||||
signFn clique.SignerFn // Signer function to authorize hashes with
|
||||
|
|
@ -61,6 +62,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
|
|||
|
||||
recents, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
signatures, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
epochSwitches, _ := lru.NewARC(int(utils.InmemoryEpochs))
|
||||
|
||||
votePool := utils.NewPool(config.V2.CertThreshold)
|
||||
engine := &XDPoS_v2{
|
||||
|
|
@ -69,6 +71,7 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v2 {
|
|||
signatures: signatures,
|
||||
|
||||
recents: recents,
|
||||
epochSwitches: epochSwitches,
|
||||
timeoutWorker: timer,
|
||||
BroadcastCh: make(chan interface{}),
|
||||
timeoutPool: timeoutPool,
|
||||
|
|
@ -359,21 +362,6 @@ func whoIsCreator(snap *SnapshotV2, header *types.Header) (common.Address, error
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// Copy from v1
|
||||
func (x *XDPoS_v2) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||
n := header.Number.Uint64()
|
||||
e := x.config.Epoch
|
||||
switch {
|
||||
case n%e == 0:
|
||||
return utils.GetMasternodesFromCheckpointHeader(header)
|
||||
case n%e != 0:
|
||||
h := chain.GetHeaderByNumber(n - (n % e))
|
||||
return utils.GetMasternodesFromCheckpointHeader(h)
|
||||
default:
|
||||
return []common.Address{}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy from v1
|
||||
func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) {
|
||||
number := header.Number.Uint64()
|
||||
|
|
@ -1053,3 +1041,95 @@ func (x *XDPoS_v2) GetProperties() (utils.Round, *utils.QuorumCert, *utils.Quoru
|
|||
defer x.lock.Unlock()
|
||||
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert, x.highestVotedRound, x.highestCommitBlock
|
||||
}
|
||||
|
||||
// Get master nodes over extra data of epoch switch block.
|
||||
func (x *XDPoS_v2) GetMasternodesFromEpochSwitchHeader(epochSwitchHeader *types.Header) []common.Address {
|
||||
if epochSwitchHeader == nil {
|
||||
log.Error("[GetMasternodesFromEpochSwitchHeader] use nil epoch switch block to get master nodes")
|
||||
return []common.Address{}
|
||||
}
|
||||
masternodes := make([]common.Address, len(epochSwitchHeader.Validators)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], epochSwitchHeader.Validators[i*common.AddressLength:])
|
||||
}
|
||||
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
var decodedExtraField utils.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", header, "extra", common.Bytes2Hex(header.Extra))
|
||||
return false, 0, err
|
||||
}
|
||||
parentRound := decodedExtraField.QuorumCert.ProposedBlockInfo.Round
|
||||
round := decodedExtraField.Round
|
||||
epochStart := round - round%utils.Round(x.config.Epoch)
|
||||
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Number.Cmp(x.config.XDPoSV2Block) == 0 {
|
||||
log.Info("[IsEpochSwitch] true, parent equals XDPoSV2Block", "round", round, "number", header.Number.Uint64(), "hash", header.Hash())
|
||||
return true, x.config.XDPoSV2Block.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch, nil
|
||||
}
|
||||
log.Info("[IsEpochSwitch]", "parent round", parentRound, "round", round, "number", header.Number.Uint64(), "hash", header.Hash())
|
||||
return parentRound < epochStart, x.config.XDPoSV2Block.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch, nil
|
||||
}
|
||||
|
||||
// Given header and its hash, get epoch switch info from the epoch switch block of that epoch,
|
||||
// header is allow to be nil.
|
||||
func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types.Header, hash common.Hash) (*utils.EpochSwitchInfo, error) {
|
||||
e, ok := x.epochSwitches.Get(hash)
|
||||
if ok {
|
||||
log.Debug("[getEpochSwitchInfo] cache hit", "hash", hash.Hex())
|
||||
epochSwitchInfo := e.(*utils.EpochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
h := header
|
||||
if h == nil {
|
||||
log.Debug("[getEpochSwitchInfo] header missing, get header", "hash", hash.Hex())
|
||||
h = chain.GetHeaderByHash(hash)
|
||||
}
|
||||
isEpochSwitch, _, err := x.IsEpochSwitch(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isEpochSwitch {
|
||||
log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
masternodes := x.GetMasternodesFromEpochSwitchHeader(h)
|
||||
// create the epoch switch info and cache it
|
||||
var decodedExtraField utils.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epochSwitchInfo := &utils.EpochSwitchInfo{
|
||||
Masternodes: masternodes,
|
||||
EpochSwitchBlockInfo: &utils.BlockInfo{
|
||||
Hash: hash,
|
||||
Number: h.Number,
|
||||
Round: decodedExtraField.Round,
|
||||
},
|
||||
EpochSwitchParentBlockInfo: decodedExtraField.QuorumCert.ProposedBlockInfo,
|
||||
}
|
||||
x.epochSwitches.Add(hash, epochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, h.ParentHash)
|
||||
if err != nil {
|
||||
log.Error("[getEpochSwitchInfo] recursive error", "err", err, "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("[getEpochSwitchInfo] get epoch switch info recursively", "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
x.epochSwitches.Add(hash, epochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
|
||||
// Given header, get master node from the epoch switch block of that epoch
|
||||
func (x *XDPoS_v2) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash())
|
||||
if err != nil {
|
||||
log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
|
||||
return []common.Address{}
|
||||
}
|
||||
return epochSwitchInfo.Masternodes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ var (
|
|||
NonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer
|
||||
NonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer.
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -108,6 +108,12 @@ type ExtraFields_v2 struct {
|
|||
QuorumCert *QuorumCert
|
||||
}
|
||||
|
||||
type EpochSwitchInfo struct {
|
||||
Masternodes []common.Address
|
||||
EpochSwitchBlockInfo *BlockInfo
|
||||
EpochSwitchParentBlockInfo *BlockInfo
|
||||
}
|
||||
|
||||
// Encode XDPoS 2.0 extra fields into bytes
|
||||
func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) {
|
||||
bytes, err := rlp.EncodeToBytes(e)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package tests
|
|||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -58,3 +60,126 @@ func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
|
|||
// Make sure the value is exactly the same as from V2 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV2Engine)
|
||||
}
|
||||
|
||||
func TestAdaptorGetMasternodesFromCheckpointHeader(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1, params.TestXDPoSMockChainConfigWithV2Engine, 0)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
headerV1 := currentBlock.Header()
|
||||
headerV1.Extra = common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400")
|
||||
masternodesV1 := adaptor.GetMasternodesFromCheckpointHeader(headerV1, 0, 0)
|
||||
headerV2 := currentBlock.Header()
|
||||
headerV2.Number.Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(1))
|
||||
headerV2.Validators = common.Hex2Bytes("0278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758c")
|
||||
masternodesV2 := adaptor.GetMasternodesFromCheckpointHeader(headerV2, 0, 0)
|
||||
assert.True(t, reflect.DeepEqual(masternodesV1, masternodesV2), "GetMasternodesFromCheckpointHeader in adaptor for v1 v2 not equal", "v1", masternodesV1, "v2", masternodesV2)
|
||||
}
|
||||
func TestAdaptorIsEpochSwitch(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1, params.TestXDPoSMockChainConfigWithV2Engine, 0)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
header := currentBlock.Header()
|
||||
// v1
|
||||
header.Number.SetUint64(0)
|
||||
assert.True(t, adaptor.IsEpochSwitch(header), "header should be epoch switch", header)
|
||||
header.Number.SetUint64(1)
|
||||
assert.False(t, adaptor.IsEpochSwitch(header), "header should not be epoch switch", header)
|
||||
// v2
|
||||
parentBlockInfo := &utils.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: utils.Round(0),
|
||||
Number: big.NewInt(0).Set(blockchain.Config().XDPoS.XDPoSV2Block),
|
||||
}
|
||||
quorumCert := &utils.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
}
|
||||
extra := utils.ExtraFields_v2{
|
||||
Round: 1,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err := extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(1))
|
||||
assert.True(t, adaptor.IsEpochSwitch(header), "header should be epoch switch", header)
|
||||
parentBlockInfo = &utils.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: utils.Round(1),
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(1)),
|
||||
}
|
||||
quorumCert = &utils.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
}
|
||||
extra = utils.ExtraFields_v2{
|
||||
Round: 2,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(2))
|
||||
assert.False(t, adaptor.IsEpochSwitch(header), "header should not be epoch switch", header)
|
||||
parentBlockInfo = &utils.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: utils.Round(blockchain.Config().XDPoS.Epoch) - 1,
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(100)),
|
||||
}
|
||||
quorumCert = &utils.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
}
|
||||
extra = utils.ExtraFields_v2{
|
||||
Round: utils.Round(blockchain.Config().XDPoS.Epoch) + 1,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(101))
|
||||
assert.True(t, adaptor.IsEpochSwitch(header), "header should be epoch switch", header)
|
||||
parentBlockInfo = &utils.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: utils.Round(blockchain.Config().XDPoS.Epoch) + 1,
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(100)),
|
||||
}
|
||||
quorumCert = &utils.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
}
|
||||
extra = utils.ExtraFields_v2{
|
||||
Round: utils.Round(blockchain.Config().XDPoS.Epoch) + 2,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.XDPoSV2Block, big.NewInt(101))
|
||||
assert.False(t, adaptor.IsEpochSwitch(header), "header should not be epoch switch", header)
|
||||
}
|
||||
|
||||
func TestAdaptorGetMasternodesV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 10, params.TestXDPoSMockChainConfigWithV2Engine, 0)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 11
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
blockHeader := createBlock(params.TestXDPoSMockChainConfigWithV2Engine, currentBlock, blockNum, 1, blockCoinBase, signer, signFn)
|
||||
// it contains 3 master nodes
|
||||
blockHeader.Validators = common.Hex2Bytes("0278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758c")
|
||||
// block 11 is the first v2 block, and is treated as epoch switch block
|
||||
currentBlock, err := insertBlock(blockchain, blockHeader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
masternodes1 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
|
||||
assert.Equal(t, 3, len(masternodes1))
|
||||
for blockNum = 12; blockNum < 15; blockNum++ {
|
||||
blockHeader = createBlock(params.TestXDPoSMockChainConfigWithV2Engine, currentBlock, blockNum, int64(blockNum-10), blockCoinBase, signer, signFn)
|
||||
currentBlock, err = insertBlock(blockchain, blockHeader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
masternodes2 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
|
||||
assert.True(t, reflect.DeepEqual(masternodes1, masternodes2), "at block number", blockNum)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@ func createXDPoSTestBlock(bc *BlockChain, customHeader *types.Header, txs []*typ
|
|||
Time: big.NewInt(customHeader.Number.Int64() * 10),
|
||||
Extra: customHeader.Extra,
|
||||
Validator: customHeader.Validator,
|
||||
Validators: customHeader.Validators,
|
||||
}
|
||||
var block *types.Block
|
||||
if len(txs) == 0 {
|
||||
|
|
|
|||
|
|
@ -352,6 +352,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
}
|
||||
}
|
||||
header = chain.GetHeader(header.ParentHash, prevCheckpoint)
|
||||
//TODO: i think this should be c.GetMasternodesFrom...
|
||||
masternodes := utils.GetMasternodesFromCheckpointHeader(header)
|
||||
|
||||
for i := startBlockNumber; i <= endBlockNumber; i++ {
|
||||
|
|
|
|||
Loading…
Reference in a new issue