Merge pull request #385 from XinFinOrg/dev-upgrade

Dev upgrade resolve sync issue
This commit is contained in:
Liam 2023-12-30 01:21:34 +11:00 committed by GitHub
commit 42f0f649ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 325 additions and 25 deletions

View file

@ -433,6 +433,15 @@ func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum
}
}
func (x *XDPoS) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) {
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
case params.ConsensusEngineVersion2:
return x.EngineV2.CalculateMissingRounds(chain, header)
default: // Default "v1"
return nil, fmt.Errorf("Not supported in the v1 consensus")
}
}
// Same DB across all consensus engines
func (x *XDPoS) GetDb() ethdb.Database {
return x.db

View file

@ -23,6 +23,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
@ -53,6 +54,7 @@ type NetworkInformation struct {
XDCXListingAddress common.Address
XDCZAddress common.Address
LendingAddress common.Address
ConsensusConfigs params.XDPoSConfig
}
type SignerTypes struct {
@ -222,16 +224,7 @@ func (api *API) GetV2BlockByHeader(header *types.Header, uncle bool) *V2BlockInf
}
func (api *API) GetV2BlockByNumber(number *rpc.BlockNumber) *V2BlockInfo {
var header *types.Header
if number == nil || *number == rpc.LatestBlockNumber {
header = api.chain.CurrentHeader()
} else if *number == rpc.CommittedBlockNumber {
hash := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash
header = api.chain.GetHeaderByHash(hash)
} else {
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
}
header := api.getHeaderFromApiBlockNum(number)
if header == nil {
return &V2BlockInfo{
Number: big.NewInt(number.Int64()),
@ -278,9 +271,31 @@ func (api *API) NetworkInformation() NetworkInformation {
info.XDCXListingAddress = common.XDCXListingSMC
info.XDCZAddress = common.TRC21IssuerSMC
}
info.ConsensusConfigs = *api.XDPoS.config
return info
}
/*
An API exclusively for V2 consensus, designed to assist in troubleshooting miners by identifying who mined during their allocated term.
*/
func (api *API) GetMissedRoundsInEpochByBlockNum(number *rpc.BlockNumber) (*utils.PublicApiMissedRoundsMetadata, error) {
return api.XDPoS.CalculateMissingRounds(api.chain, api.getHeaderFromApiBlockNum(number))
}
func (api *API) getHeaderFromApiBlockNum(number *rpc.BlockNumber) *types.Header {
var header *types.Header
if number == nil || *number == rpc.LatestBlockNumber {
header = api.chain.CurrentHeader()
} else if *number == rpc.CommittedBlockNumber {
hash := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash
header = api.chain.GetHeaderByHash(hash)
} else {
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
}
return header
}
func calculateSigners(message map[string]SignerTypes, pool map[string]map[common.Hash]utils.PoolObj, masternodes []common.Address) {
for name, objs := range pool {
var currentSigners []common.Address

View file

@ -336,7 +336,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
return err
}
if isEpochSwitchBlock {
masterNodes, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash)
masterNodes, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash, currentRound)
if err != nil {
return err
}
@ -360,7 +360,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
}
if header.Coinbase != signer {
log.Error("[Prepare] The mined blocker header coinbase address mismatch with waller address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex())
log.Error("[Prepare] The mined blocker header coinbase address mismatch with wallet address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex())
return consensus.ErrCoinbaseMismatch
}
@ -997,9 +997,9 @@ func (x *XDPoS_v2) GetStandbynodes(chain consensus.ChainReader, header *types.He
}
// Calculate masternodes for a block number and parent hash. In V2, truncating candidates[:MaxMasternodes] is done in this function.
func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash) ([]common.Address, []common.Address, error) {
func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash, round types.Round) ([]common.Address, []common.Address, error) {
// using new max masterndoes
maxMasternodes := x.config.V2.Config(uint64(x.currentRound)).MaxMasternodes
maxMasternodes := x.config.V2.Config(uint64(round)).MaxMasternodes
snap, err := x.getSnapshot(chain, blockNum.Uint64(), false)
if err != nil {
log.Error("[calcMasternodes] Adaptor v2 getSnapshot has error", "err", err)
@ -1081,7 +1081,7 @@ func (x *XDPoS_v2) allowedToSend(chain consensus.ChainReader, blockHeader *types
for _, mn := range masterNodes {
log.Debug("[allowedToSend] Master node list", "masterNodeAddress", mn.Hash())
}
log.Info("[allowedToSend] Not in the Masternode list, not suppose to send message", "sendType", sendType, "MyAddress", signer.Hex())
log.Debug("[allowedToSend] Not in the Masternode list, not suppose to send message", "sendType", sendType, "MyAddress", signer.Hex())
return false
}

View file

@ -25,7 +25,7 @@ func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, pare
}
var masterNodes []common.Address
if isEpochSwitch {
masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash())
masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash(), round)
if err != nil {
log.Error("[yourturn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number)
return false, err

View file

@ -163,3 +163,54 @@ func (x *XDPoS_v2) GetSignersFromSnapshot(chain consensus.ChainReader, header *t
snap, err := x.getSnapshot(chain, header.Number.Uint64(), false)
return snap.NextEpochMasterNodes, err
}
func (x *XDPoS_v2) CalculateMissingRounds(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiMissedRoundsMetadata, error) {
var missedRounds []utils.MissedRoundInfo
switchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash())
if err != nil {
return nil, err
}
masternodes := switchInfo.Masternodes
// Loop through from the epoch switch block to the current "header" block
nextHeader := header
for nextHeader.Number.Cmp(switchInfo.EpochSwitchBlockInfo.Number) > 0 {
parentHeader := chain.GetHeaderByHash(nextHeader.ParentHash)
parentRound, err := x.GetRoundNumber(parentHeader)
if err != nil {
return nil, err
}
currRound, err := x.GetRoundNumber(nextHeader)
if err != nil {
return nil, err
}
// This indicates that an increment in the round number is missing during the block production process.
if parentRound+1 != currRound {
// We need to iterate from the parentRound to the currRound to determine which miner did not perform mining.
for i := parentRound + 1; i < currRound; i++ {
leaderIndex := uint64(i) % x.config.Epoch % uint64(len(masternodes))
whosTurn := masternodes[leaderIndex]
missedRounds = append(
missedRounds,
utils.MissedRoundInfo{
Round: i,
Miner: whosTurn,
CurrentBlockHash: nextHeader.Hash(),
CurrentBlockNum: nextHeader.Number,
ParentBlockHash: parentHeader.Hash(),
ParentBlockNum: parentHeader.Number,
},
)
}
}
// Assign the pointer to the next one
nextHeader = parentHeader
}
missedRoundsMetadata := &utils.PublicApiMissedRoundsMetadata{
EpochRound: switchInfo.EpochSwitchBlockInfo.Round,
EpochBlockNumber: switchInfo.EpochSwitchBlockInfo.Number,
MissedRounds: missedRounds,
}
return missedRoundsMetadata, nil
}

View file

@ -115,7 +115,7 @@ func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Heade
return utils.ErrInvalidCheckpointSigners
}
localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash)
localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash, round)
masterNodes = localMasterNodes
if err != nil {
log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash())

View file

@ -193,7 +193,7 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, poole
func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *types.BlockInfo, quorumCert *types.QuorumCert) (bool, error) {
// Make sure this node has not voted for this round.
if x.currentRound <= x.highestVotedRound {
log.Warn("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound)
log.Info("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound)
return false, nil
}
/*
@ -203,7 +203,7 @@ func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, bloc
header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round
*/
if blockInfo.Round != x.currentRound {
log.Warn("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round)
log.Info("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round)
return false, nil
}
// XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule

View file

@ -57,3 +57,17 @@ type PublicApiSnapshot struct {
Votes []*clique.Vote `json:"votes"` // List of votes cast in chronological order
Tally map[common.Address]clique.Tally `json:"tally"` // Current vote tally to avoid recalculating
}
type MissedRoundInfo struct {
Round types.Round
Miner common.Address
CurrentBlockHash common.Hash
CurrentBlockNum *big.Int
ParentBlockHash common.Hash
ParentBlockNum *big.Int
}
type PublicApiMissedRoundsMetadata struct {
EpochRound types.Round
EpochBlockNumber *big.Int
MissedRounds []MissedRoundInfo
}

View file

@ -0,0 +1,34 @@
package tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
var (
voterKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee04aefe388d1e14474d32c45c72ce7b7a")
voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434
)
func TestConfigApi(t *testing.T) {
bc := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
}, 10000000, params.TestXDPoSMockChainConfig)
engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS)
info := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).NetworkInformation()
assert.Equal(t, info.NetworkId, big.NewInt(1337))
assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.MaxMasternodes, 18)
assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.CertThreshold, 0.667)
assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.MinePeriod, 2)
assert.Equal(t, info.ConsensusConfigs.V2.CurrentConfig.TimeoutSyncThreshold, 2)
}

View file

@ -0,0 +1,111 @@
package engine_v2_tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/stretchr/testify/assert"
)
func TestGetMissedRoundsInEpochByBlockNumOnlyForV2Consensus(t *testing.T) {
_, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig)
engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS)
blockNum := rpc.BlockNumber(123)
data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum)
assert.EqualError(t, err, "Not supported in the v1 consensus")
assert.Nil(t, data)
}
func TestGetMissedRoundsInEpochByBlockNumReturnEmptyForV2(t *testing.T) {
_, bc, cb, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig)
engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS)
blockNum := rpc.BlockNumber(cb.NumberU64())
data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum)
assert.Nil(t, err)
assert.Equal(t, types.Round(900), data.EpochRound)
assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber)
assert.Equal(t, 0, len(data.MissedRounds))
blockNum = rpc.BlockNumber(1800)
data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum)
assert.Nil(t, err)
assert.Equal(t, types.Round(900), data.EpochRound)
assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber)
assert.Equal(t, 0, len(data.MissedRounds))
blockNum = rpc.BlockNumber(1801)
data, err = engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum)
assert.Nil(t, err)
assert.Equal(t, types.Round(900), data.EpochRound)
assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber)
assert.Equal(t, 0, len(data.MissedRounds))
}
func TestGetMissedRoundsInEpochByBlockNumReturnEmptyForV2FistEpoch(t *testing.T) {
_, bc, _, _, _ := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig)
engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS)
blockNum := rpc.BlockNumber(901)
data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum)
assert.Nil(t, err)
assert.Equal(t, types.Round(1), data.EpochRound)
assert.Equal(t, big.NewInt(901), data.EpochBlockNumber)
assert.Equal(t, 0, len(data.MissedRounds))
}
func TestGetMissedRoundsInEpochByBlockNum(t *testing.T) {
blockchain, bc, currentBlock, signer, signFn := PrepareXDCTestBlockChainWith128Candidates(t, 1802, params.TestXDPoSMockChainConfig)
chainConfig := params.TestXDPoSMockChainConfig
engine := bc.GetBlockChain().Engine().(*XDPoS.XDPoS)
blockCoinBase := signer.Hex()
startingBlockNum := currentBlock.Number().Int64() + 1
// Skipped the round
roundNumber := startingBlockNum - chainConfig.XDPoS.V2.SwitchBlock.Int64() + 2
block := CreateBlock(blockchain, chainConfig, currentBlock, int(startingBlockNum), roundNumber, blockCoinBase, signer, signFn, nil, nil, "b345a8560bd51926803dd17677c9f0751193914a851a4ec13063d6bf50220b53")
err := blockchain.InsertBlock(block)
if err != nil {
t.Fatal(err)
}
// Update Signer as there is no previous signer assigned
err = UpdateSigner(blockchain)
if err != nil {
t.Fatal(err)
}
blockNum := rpc.BlockNumber(1803)
data, err := engine.APIs(bc.GetBlockChain())[0].Service.(*XDPoS.API).GetMissedRoundsInEpochByBlockNum(&blockNum)
assert.Nil(t, err)
assert.Equal(t, types.Round(900), data.EpochRound)
assert.Equal(t, big.NewInt(1800), data.EpochBlockNumber)
assert.Equal(t, 2, len(data.MissedRounds))
assert.NotEmpty(t, data.MissedRounds[0].Miner)
assert.Equal(t, data.MissedRounds[0].Round, types.Round(903))
assert.Equal(t, data.MissedRounds[0].CurrentBlockNum, big.NewInt(1803))
assert.Equal(t, data.MissedRounds[0].ParentBlockNum, big.NewInt(1802))
assert.NotEmpty(t, data.MissedRounds[1].Miner)
assert.Equal(t, data.MissedRounds[1].Round, types.Round(904))
assert.Equal(t, data.MissedRounds[0].CurrentBlockNum, big.NewInt(1803))
assert.Equal(t, data.MissedRounds[0].ParentBlockNum, big.NewInt(1802))
assert.NotEqual(t, data.MissedRounds[0].Miner, data.MissedRounds[1].Miner)
}

View file

@ -545,6 +545,18 @@ func PrepareXDCTestBlockChainWith128Candidates(t *testing.T, numOfBlocks int, ch
if err != nil {
t.Fatal(err)
}
// First v2 block
if (int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()) == 1 {
lastv1BlockNumber := block.Header().Number.Uint64() - 1
checkpointBlockNumber := lastv1BlockNumber - lastv1BlockNumber%chainConfig.XDPoS.Epoch
checkpointHeader := blockchain.GetHeaderByNumber(checkpointBlockNumber)
err := engine.EngineV2.Initial(blockchain, checkpointHeader)
if err != nil {
panic(err)
}
}
currentBlock = block
}

View file

@ -260,6 +260,50 @@ func TestConfigSwitchOnDifferentCertThreshold(t *testing.T) {
assert.Equal(t, utils.ErrValidatorNotWithinMasternodes, err)
}
/*
1. Insert 20 masternode before gap block
2. Prepare 20 masternode block header with round 9000
3. verify this header while node is on round 899,
This is to simulate node is syncing from remote during config switch
*/
func TestConfigSwitchOnDifferentMasternodeCount(t *testing.T) {
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
assert.Nil(t, err)
configString := string(b)
var config params.ChainConfig
err = json.Unmarshal([]byte(configString), &config)
assert.Nil(t, err)
// Enable verify
config.XDPoS.V2.SkipV2Validation = false
// Block 901 is the first v2 block with round of 1
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*2, &config, nil)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
x := adaptor.EngineV2
// Generate round 900 header, num 1800
header1800 := blockchain.GetBlockByNumber(1800).Header()
snap, err := x.GetSnapshot(blockchain, currentBlock.Header())
assert.Nil(t, err)
assert.Equal(t, len(snap.NextEpochMasterNodes), 20)
header1800.Validators = []byte{}
for i := 0; i < 20; i++ {
header1800.Validators = append(header1800.Validators, snap.NextEpochMasterNodes[i].Bytes()...)
}
round, err := x.GetRoundNumber(header1800)
assert.Nil(t, err)
assert.Equal(t, round, types.Round(900))
adaptor.EngineV2.SetNewRoundFaker(blockchain, 899, false)
err = adaptor.VerifyHeader(blockchain, header1800, true)
// error ErrValidatorNotWithinMasternodes means verifyQC is passed and move to next verification process
assert.Equal(t, utils.ErrValidatorNotWithinMasternodes, err)
}
func TestConfigSwitchOnDifferentMindPeriod(t *testing.T) {
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
assert.Nil(t, err)

View file

@ -146,6 +146,9 @@ func (q *queue) Reset() {
// Close marks the end of the sync, unblocking WaitResults.
// It may be called even if the queue is already closed.
func (q *queue) Close() {
q.lock.Lock()
defer q.lock.Unlock()
q.closed = true
q.active.Broadcast()
}
@ -507,6 +510,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
// If we're the first to request this task, initialise the result container
index := int(header.Number.Int64() - int64(q.resultOffset))
if index >= len(q.resultCache) || index < 0 {
log.Error("index allocation went beyond available resultCache space", "index", index, "len.resultCache", len(q.resultCache), "blockNum", header.Number.Int64(), "resultOffset", q.resultOffset)
common.Report("index allocation went beyond available resultCache space")
return nil, false, errInvalidChain
}

View file

@ -37,9 +37,9 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
break
}
log.Info("[V2 Hook Penalty] parentHeader is nil, wait block to be writen in disk", "parentNumber", parentNumber)
time.Sleep(200 * time.Millisecond) // 0.2s
time.Sleep(time.Second) // 1s
if timeout > 50 { // wait over 10s
if timeout > 30 { // wait over 30s
log.Error("[V2 Hook Penalty] parentHeader is nil, wait too long not writen in to disk", "parentNumber", parentNumber)
return []common.Address{}, fmt.Errorf("parentHeader is nil")
}

View file

@ -156,6 +156,12 @@ web3._extend({
name: 'getLatestPoolStatus',
call: 'XDPoS_getLatestPoolStatus'
}),
new web3._extend.Method({
name: 'getMissedRoundsInEpochByBlockNum',
call: 'XDPoS_getMissedRoundsInEpochByBlockNum',
params: 1,
inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter]
}),
],
properties: [
new web3._extend.Property({

View file

@ -21,10 +21,10 @@ import (
)
const (
VersionMajor = 1 // Major version component of the current release
VersionMinor = 5 // Minor version component of the current release
VersionPatch = 0 // Patch version component of the current release
VersionMeta = "" // Version metadata to append to the version string
VersionMajor = 2 // Major version component of the current release
VersionMinor = 0 // Minor version component of the current release
VersionPatch = 1 // Patch version component of the current release
VersionMeta = "beta1" // Version metadata to append to the version string
)
// Version holds the textual version string.