Skip to content

Commit

Permalink
isEpochSwitch, getMasternodes (ethereum#35)
Browse files Browse the repository at this point in the history
* isEpochSwitch, getMasternodes

* recursive getEpochSwitchInfo, getMasternodes

* more log, make getEpochSwitchInfo clearer
  • Loading branch information
wgr523 authored Jan 7, 2022
1 parent ebbbf26 commit 5c32696
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 19 deletions.
17 changes: 16 additions & 1 deletion consensus/XDPoS/XDPoS.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions consensus/XDPoS/engines/engine_v1/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
114 changes: 97 additions & 17 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion consensus/XDPoS/utils/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
6 changes: 6 additions & 0 deletions consensus/XDPoS/utils/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
125 changes: 125 additions & 0 deletions consensus/tests/adaptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
}
1 change: 1 addition & 0 deletions consensus/tests/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions contracts/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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++ {
Expand Down

0 comments on commit 5c32696

Please sign in to comment.