diff --git a/consensus/XDPoS/api.go b/consensus/XDPoS/api.go index 42a7431b58..f8aaa22359 100644 --- a/consensus/XDPoS/api.go +++ b/consensus/XDPoS/api.go @@ -53,6 +53,22 @@ type NetworkInformation struct { LendingAddress common.Address } +type SignerTypes struct { + CurrentNumber int + CurrentSigners []common.Address + MissingSigners []common.Address +} + +type MasternodesStatus struct { + MasternodesLen int + Masternodes []common.Address + + PenaltyLen int + Penalty []common.Address +} + +type MessageStatus map[string]map[string]SignerTypes + // GetSnapshot retrieves the state snapshot at a given block. func (api *API) GetSnapshot(number *rpc.BlockNumber) (*utils.PublicApiSnapshot, error) { // Retrieve the requested block number (or current if none requested) @@ -104,9 +120,44 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) { return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header) } -// Get the latest v2 committed block information. Note: This only applies to v2 engine. it doesn't make sense for v1 -func (api *API) GetLatestCommittedBlockHeader() *types.BlockInfo { - return api.XDPoS.EngineV2.GetLatestCommittedBlockInfo() +func (api *API) GetMasternodesByNumber(number *rpc.BlockNumber) MasternodesStatus { + 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())) + } + masternodes, penalties, err := api.XDPoS.EngineV2.CalcMasternodes(api.chain, header.Number, header.ParentHash) + if err != nil { + return MasternodesStatus{} + } + info := MasternodesStatus{ + MasternodesLen: len(masternodes), + Masternodes: masternodes, + PenaltyLen: len(penalties), + Penalty: penalties, + } + return info +} + +// Get current vote pool and timeout pool content and missing messages +func (api *API) GetLatestPoolStatus() MessageStatus { + header := api.chain.CurrentHeader() + masternodes := api.XDPoS.EngineV2.GetMasternodes(api.chain, header) + + receivedVotes := api.XDPoS.EngineV2.ReceivedVotes() + receivedTimeouts := api.XDPoS.EngineV2.ReceivedTimeouts() + info := make(MessageStatus) + info["vote"] = make(map[string]SignerTypes) + info["timeout"] = make(map[string]SignerTypes) + + calculateSigners(info["vote"], receivedVotes, masternodes) + calculateSigners(info["timeout"], receivedTimeouts, masternodes) + + return info } func (api *API) GetV2BlockByHeader(header *types.Header, uncle bool) *V2BlockInfo { @@ -209,3 +260,28 @@ func (api *API) NetworkInformation() NetworkInformation { } return info } + +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 + missingSigners := make([]common.Address, len(masternodes)) + copy(missingSigners, masternodes) + + num := len(objs) + for _, obj := range objs { + signer := obj.GetSigner() + currentSigners = append(currentSigners, signer) + for i, mn := range missingSigners { + if mn == signer { + missingSigners = append(missingSigners[:i], missingSigners[i+1:]...) + break + } + } + } + message[name] = SignerTypes{ + CurrentNumber: num, + CurrentSigners: currentSigners, + MissingSigners: missingSigners, + } + } +} diff --git a/consensus/XDPoS/api_test.go b/consensus/XDPoS/api_test.go new file mode 100644 index 0000000000..3d4cbcd3d4 --- /dev/null +++ b/consensus/XDPoS/api_test.go @@ -0,0 +1,73 @@ +package XDPoS + +import ( + "math/big" + "testing" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/stretchr/testify/assert" +) + +func TestCalculateSignersVote(t *testing.T) { + + info := make(map[string]SignerTypes) + votes := utils.NewPool() + masternodes := []common.Address{{1}, {2}, {3}} + + vote1 := types.Vote{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.Hash{1}, + Round: types.Round(10), + Number: big.NewInt(910), + }, + GapNumber: 450, + } + vote1.SetSigner(common.Address{1}) + + vote2 := types.Vote{ + ProposedBlockInfo: &types.BlockInfo{ + Hash: common.Hash{2}, + Round: types.Round(11), + Number: big.NewInt(911), + }, + GapNumber: 450, + } + vote2.SetSigner(common.Address{2}) + + votes.Add(&vote1) + votes.Add(&vote2) + + calculateSigners(info, votes.Get(), masternodes) + + //assert.Equal(t, info["xxx"].CurrentNumber, 2) + assert.Equal(t, 2, 2) +} + +func TestCalculateSignersTimeout(t *testing.T) { + + info := make(map[string]SignerTypes) + timeouts := utils.NewPool() + masternodes := []common.Address{{1}, {2}, {3}} + + timeout1 := types.Timeout{ + Round: types.Round(10), + GapNumber: 450, + } + timeout1.SetSigner(common.Address{1}) + + timeout2 := types.Timeout{ + Round: types.Round(11), + GapNumber: 450, + } + timeout1.SetSigner(common.Address{2}) + + timeouts.Add(&timeout1) + timeouts.Add(&timeout2) + + calculateSigners(info, timeouts.Get(), masternodes) + + //assert.Equal(t, info["xxx"].CurrentNumber, 2) + assert.Equal(t, 2, 2) +} diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index e84235d33c..ef8dab5a86 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -138,7 +138,9 @@ func (x *XDPoS_v2) UpdateParams(header *types.Header) { }() } -/* V2 Block +/* + V2 Block + SignerFn is a signer callback function to request a hash to be signed by a backing account. type SignerFn func(accounts.Account, []byte) ([]byte, error) @@ -574,7 +576,7 @@ func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *types. } /* - Vote workflow +Vote workflow */ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *types.Vote) (bool, error) { /* @@ -596,7 +598,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *types.Vo log.Error("[VerifyVoteMessage] fail to get snapshot for a vote message", "blockNum", vote.ProposedBlockInfo.Number, "blockHash", vote.ProposedBlockInfo.Hash, "voteHash", vote.Hash(), "error", err.Error()) return false, err } - verified, _, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ + verified, signer, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ ProposedBlockInfo: vote.ProposedBlockInfo, GapNumber: vote.GapNumber, }), vote.Signature, snapshot.NextEpochMasterNodes) @@ -605,8 +607,11 @@ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *types.Vo log.Warn("[VerifyVoteMessage] Master node list item", "index", i, "Master node", mn.Hex()) } log.Warn("[VerifyVoteMessage] Error while verifying vote message", "votedBlockNum", vote.ProposedBlockInfo.Number.Uint64(), "votedBlockHash", vote.ProposedBlockInfo.Hash.Hex(), "voteHash", vote.Hash(), "error", err.Error()) + return false, err } - return verified, err + vote.SetSigner(signer) + + return verified, nil } // Consensus entry point for processing vote message to produce QC @@ -630,23 +635,31 @@ func (x *XDPoS_v2) VoteHandler(chain consensus.ChainReader, voteMsg *types.Vote) */ func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *types.Timeout) (bool, error) { snap, err := x.getSnapshot(chain, timeoutMsg.GapNumber, true) - if err != nil { - log.Error("[VerifyTimeoutMessage] Fail to get snapshot when verifying timeout message!", "messageGapNumber", timeoutMsg.GapNumber) + if err != nil || snap == nil { + log.Error("[VerifyTimeoutMessage] Fail to get snapshot when verifying timeout message!", "messageGapNumber", timeoutMsg.GapNumber, "err", err) + return false, err } - if snap == nil || len(snap.NextEpochMasterNodes) == 0 { - log.Error("[VerifyTimeoutMessage] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutMsg.GapNumber, "snapshot", snap) + if len(snap.NextEpochMasterNodes) == 0 { + log.Error("[VerifyTimeoutMessage] cannot find nextEpochMasterNodes from snapshot", "messageGapNumber", timeoutMsg.GapNumber) return false, fmt.Errorf("Empty master node lists from snapshot") } - verified, _, err := x.verifyMsgSignature(types.TimeoutSigHash(&types.TimeoutForSign{ + verified, signer, err := x.verifyMsgSignature(types.TimeoutSigHash(&types.TimeoutForSign{ Round: timeoutMsg.Round, GapNumber: timeoutMsg.GapNumber, }), timeoutMsg.Signature, snap.NextEpochMasterNodes) - return verified, err + + if err != nil { + log.Warn("[VerifyTimeoutMessage] cannot verify timeout signature", "err", err) + return false, err + } + + timeoutMsg.SetSigner(signer) + return verified, nil } /* - Entry point for handling timeout message to process below: +Entry point for handling timeout message to process below: */ func (x *XDPoS_v2) TimeoutHandler(blockChainReader consensus.ChainReader, timeout *types.Timeout) error { x.lock.Lock() @@ -655,7 +668,7 @@ func (x *XDPoS_v2) TimeoutHandler(blockChainReader consensus.ChainReader, timeou } /* - Proposed Block workflow +Proposed Block workflow */ func (x *XDPoS_v2) ProposedBlockHandler(chain consensus.ChainReader, blockHeader *types.Header) error { x.lock.Lock() @@ -866,17 +879,18 @@ func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, incomingQuo } /* - 1. Set currentRound = QC round + 1 (or TC round +1) - 2. Reset timer - 3. Reset vote and timeout Pools +1. Set currentRound = QC round + 1 (or TC round +1) +2. Reset timer +3. Reset vote and timeout Pools */ func (x *XDPoS_v2) setNewRound(blockChainReader consensus.ChainReader, round types.Round) { log.Info("[setNewRound] new round and reset pools and workers", "round", round) x.currentRound = round x.timeoutCount = 0 x.timeoutWorker.Reset(blockChainReader) - //TODO: vote pools x.timeoutPool.Clear() + // don't need to clean vote pool, we have other process to clean and it's not good to clean here, some edge case may break + // for example round gets bump during collecting vote, so we have to keep vote. } func (x *XDPoS_v2) broadcastToBftChannel(msg interface{}) { @@ -892,7 +906,7 @@ func (x *XDPoS_v2) getSyncInfo() *types.SyncInfo { } } -//Find parent and grandparent, check round number, if so, commit grandparent(grandGrandParent of currentBlock) +// Find parent and grandparent, check round number, if so, commit grandparent(grandGrandParent of currentBlock) func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposedBlockHeader *types.Header, proposedBlockRound *types.Round, incomingQc *types.QuorumCert) (bool, error) { // XDPoS v1.0 switch to v2.0, skip commit if big.NewInt(0).Sub(proposedBlockHeader.Number, big.NewInt(2)).Cmp(x.config.V2.SwitchBlock) <= 0 { @@ -964,6 +978,10 @@ func (x *XDPoS_v2) GetMasternodes(chain consensus.ChainReader, header *types.Hea return epochSwitchInfo.Masternodes } +func (x *XDPoS_v2) CalcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash) ([]common.Address, []common.Address, error) { + return x.calcMasternodes(chain, blockNum, parentHash) +} + func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash) ([]common.Address, []common.Address, error) { snap, err := x.getSnapshot(chain, blockNum.Uint64(), false) if err != nil { diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go index 75a74de32c..9df7b3e82a 100644 --- a/consensus/XDPoS/engines/engine_v2/timeout.go +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -41,11 +41,11 @@ func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeou } /* - Function that will be called by timeoutPool when it reached threshold. - In the engine v2, we will need to: - 1. Genrate TC - 2. processTC() - 3. generateSyncInfo() +Function that will be called by timeoutPool when it reached threshold. +In the engine v2, we will need to: + 1. Genrate TC + 2. processTC() + 3. generateSyncInfo() */ func (x *XDPoS_v2) onTimeoutPoolThresholdReached(blockChainReader consensus.ChainReader, pooledTimeouts map[common.Hash]utils.PoolObj, currentTimeoutMsg utils.PoolObj, gapNumber uint64) error { signatures := []types.Signature{} @@ -142,8 +142,8 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time } /* - 1. Update highestTC - 2. Check TC round >= node's currentRound. If yes, call setNewRound +1. Update highestTC +2. Check TC round >= node's currentRound. If yes, call setNewRound */ func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *types.TimeoutCert) error { if timeoutCert.Round > x.highestTimeoutCert.Round { @@ -191,7 +191,7 @@ func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error { GapNumber: gapNumber, })) if err != nil { - log.Error("[sendTimeout] signSignature when sending out TC", "Error", err) + log.Error("[sendTimeout] signSignature when sending out TC", "Error", err, "round", x.currentRound, "gap", gapNumber) return err } timeoutMsg := &types.Timeout{ @@ -210,8 +210,8 @@ func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error { } /* - Function that will be called by timer when countdown reaches its threshold. - In the engine v2, we would need to broadcast timeout messages to other peers +Function that will be called by timer when countdown reaches its threshold. +In the engine v2, we would need to broadcast timeout messages to other peers */ func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error { x.lock.Lock() @@ -225,7 +225,7 @@ func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error { err := x.sendTimeout(chain.(consensus.ChainReader)) if err != nil { - log.Error("Error while sending out timeout message at time: ", time) + log.Error("Error while sending out timeout message at time: ", "time", time, "err", err) return err } @@ -259,3 +259,7 @@ func (x *XDPoS_v2) hygieneTimeoutPool() { } } } + +func (x *XDPoS_v2) ReceivedTimeouts() map[string]map[common.Hash]utils.PoolObj { + return x.timeoutPool.Get() +} diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 0b1faf3cf2..a599b662f0 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -96,7 +96,7 @@ func (x *XDPoS_v2) signSignature(signingHash common.Hash) (types.Signature, erro signedHash, err := signFn(accounts.Account{Address: signer}, signingHash.Bytes()) if err != nil { - return nil, fmt.Errorf("Error while signing hash") + return nil, fmt.Errorf("Error %v while signing hash", err) } return signedHash, nil } @@ -119,6 +119,7 @@ func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signat } } + log.Warn("[verifyMsgSignature] signer is not part of masternode list", "signer", signerAddress, "masternodes", masternodes) return false, signerAddress, nil } diff --git a/consensus/XDPoS/engines/engine_v2/vote.go b/consensus/XDPoS/engines/engine_v2/vote.go index 4a367a3761..b2aec7db45 100644 --- a/consensus/XDPoS/engines/engine_v2/vote.go +++ b/consensus/XDPoS/engines/engine_v2/vote.go @@ -91,6 +91,9 @@ func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *types.Vote) if err != nil { return err } + + x.verifyVotes(chain, pooledVotes, proposedBlockHeader) + err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader) if err != nil { return err @@ -103,46 +106,53 @@ func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *types.Vote) return nil } -/* - Function that will be called by votePool when it reached threshold. - In the engine v2, we will need to generate and process QC -*/ -func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj, proposedBlockHeader *types.Header) error { - - masternodes := x.GetMasternodes(chain, proposedBlockHeader) +func (x *XDPoS_v2) verifyVotes(chain consensus.ChainReader, votes map[common.Hash]utils.PoolObj, header *types.Header) { + masternodes := x.GetMasternodes(chain, header) start := time.Now() + emptySigner := common.Address{} // Filter out non-Master nodes signatures var wg sync.WaitGroup - wg.Add(len(pooledVotes)) - signatures := make([]types.Signature, len(pooledVotes)) - counter := 0 - for h, vote := range pooledVotes { - go func(hash common.Hash, v *types.Vote, i int) { + wg.Add(len(votes)) + for h, vote := range votes { + go func(hash common.Hash, v *types.Vote) { defer wg.Done() + if v.GetSigner() != emptySigner { + // verify before + return + } signedVote := types.VoteSigHash(&types.VoteForSign{ ProposedBlockInfo: v.ProposedBlockInfo, GapNumber: v.GapNumber, }) - verified, _, err := x.verifyMsgSignature(signedVote, v.Signature, masternodes) + verified, masterNode, err := x.verifyMsgSignature(signedVote, v.Signature, masternodes) if err != nil { - log.Warn("[onVotePoolThresholdReached] Skip not verified vote signatures when building QC", "error", err.Error()) - } else if !verified { - log.Warn("[onVotePoolThresholdReached] Skip not verified vote signatures when building QC", "verified", verified) - } else { - signatures[i] = v.Signature + log.Warn("[verifyVotes] error while verifying vote signature", "error", err.Error()) + return } - }(h, vote.(*types.Vote), counter) - counter++ + + if !verified { + log.Warn("[verifyVotes] non-verified vote signature", "verified", verified) + return + } + v.SetSigner(masterNode) + }(h, vote.(*types.Vote)) } wg.Wait() elapsed := time.Since(start) - log.Debug("[onVotePoolThresholdReached] verify message signatures of vote pool took", "elapsed", elapsed) + log.Debug("[verifyVotes] verify message signatures of vote pool took", "elapsed", elapsed) +} +/* +Function that will be called by votePool when it reached threshold. +In the engine v2, we will need to generate and process QC +*/ +func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj, proposedBlockHeader *types.Header) error { // The signature list may contain empty entey. we only care the ones with values var validSignatures []types.Signature - for _, v := range signatures { - if len(v) != 0 { - validSignatures = append(validSignatures, v) + emptySigner := common.Address{} + for _, vote := range pooledVotes { + if vote.GetSigner() != emptySigner { + validSignatures = append(validSignatures, vote.(*types.Vote).Signature) } } @@ -247,3 +257,7 @@ func (x *XDPoS_v2) hygieneVotePool() { } } } + +func (x *XDPoS_v2) ReceivedVotes() map[string]map[common.Hash]utils.PoolObj { + return x.votePool.Get() +} diff --git a/consensus/XDPoS/utils/pool.go b/consensus/XDPoS/utils/pool.go index 91b3e21653..3c35d37d8c 100644 --- a/consensus/XDPoS/utils/pool.go +++ b/consensus/XDPoS/utils/pool.go @@ -9,6 +9,7 @@ import ( type PoolObj interface { Hash() common.Hash PoolKey() string + GetSigner() common.Address } type Pool struct { objList map[string]map[common.Hash]PoolObj @@ -20,6 +21,9 @@ func NewPool() *Pool { objList: make(map[string]map[common.Hash]PoolObj), } } +func (p *Pool) Get() map[string]map[common.Hash]PoolObj { + return p.objList +} // return true if it has reached threshold func (p *Pool) Add(obj PoolObj) (int, map[common.Hash]PoolObj) { diff --git a/consensus/tests/engine_v2_tests/timeout_test.go b/consensus/tests/engine_v2_tests/timeout_test.go index 5d78b99c59..0199249d16 100644 --- a/consensus/tests/engine_v2_tests/timeout_test.go +++ b/consensus/tests/engine_v2_tests/timeout_test.go @@ -248,6 +248,7 @@ func TestShouldVerifyTimeoutMessageForFirstV2Block(t *testing.T) { } verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg) + assert.Equal(t, timeoutMsg.GetSigner(), signer) assert.Nil(t, err) assert.True(t, verified) @@ -263,6 +264,7 @@ func TestShouldVerifyTimeoutMessageForFirstV2Block(t *testing.T) { } verified, err = engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg) + assert.Equal(t, timeoutMsg.GetSigner(), signer) assert.Nil(t, err) assert.True(t, verified) } @@ -286,7 +288,7 @@ func TestShouldVerifyTimeoutMessage(t *testing.T) { assert.True(t, verified) } -func TestTimeoutPoolKeeyGoodHygiene(t *testing.T) { +func TestTimeoutPoolKeyGoodHygiene(t *testing.T) { blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 diff --git a/consensus/tests/engine_v2_tests/vote_test.go b/consensus/tests/engine_v2_tests/vote_test.go index 3a25034a8c..e9a1041e8a 100644 --- a/consensus/tests/engine_v2_tests/vote_test.go +++ b/consensus/tests/engine_v2_tests/vote_test.go @@ -136,6 +136,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { assert.Equal(t, types.Round(5), currentRound) // Create another vote which is signed by someone not from the master node list + randomSigner, randomSignFn, err := backends.SimulateWalletAddressAndSignFn() assert.Nil(t, err) randomlySignedHash, err := randomSignFn(accounts.Account{Address: randomSigner}, voteSigningHash.Bytes()) @@ -147,6 +148,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) { } err = engineV2.VoteHandler(blockchain, voteMsg) assert.Nil(t, err) + currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker() // Still using the initlised value because we did not yet go to the next round assert.Nil(t, lockQuorumCert) @@ -506,6 +508,7 @@ func TestVerifyVoteMsg(t *testing.T) { } verified, err = engineV2.VerifyVoteMessage(blockchain, voteMsg) + assert.Equal(t, voteMsg.GetSigner(), signer) assert.True(t, verified) assert.Nil(t, err) } diff --git a/core/types/consensus_v2.go b/core/types/consensus_v2.go index 78a20e787b..fd2c181112 100644 --- a/core/types/consensus_v2.go +++ b/core/types/consensus_v2.go @@ -21,24 +21,64 @@ type BlockInfo struct { // Vote message in XDPoS 2.0 type Vote struct { + signer common.Address ProposedBlockInfo *BlockInfo Signature Signature GapNumber uint64 } +func (v *Vote) Hash() common.Hash { + return rlpHash(v) +} + +func (v *Vote) PoolKey() string { + // return the voted block hash + return fmt.Sprint(v.ProposedBlockInfo.Round, ":", v.GapNumber, ":", v.ProposedBlockInfo.Number, ":", v.ProposedBlockInfo.Hash.Hex()) +} + +func (v *Vote) GetSigner() common.Address { + return v.signer +} + +func (v *Vote) SetSigner(signer common.Address) { + v.signer = signer +} + // Timeout message in XDPoS 2.0 type Timeout struct { + signer common.Address Round Round Signature Signature GapNumber uint64 } +func (t *Timeout) Hash() common.Hash { + return rlpHash(t) +} + +func (t *Timeout) PoolKey() string { + // timeout pool key is round:gapNumber + return fmt.Sprint(t.Round, ":", t.GapNumber) +} + +func (t *Timeout) GetSigner() common.Address { + return t.signer +} + +func (t *Timeout) SetSigner(signer common.Address) { + t.signer = signer +} + // BFT Sync Info message in XDPoS 2.0 type SyncInfo struct { HighestQuorumCert *QuorumCert HighestTimeoutCert *TimeoutCert } +func (s *SyncInfo) Hash() common.Hash { + return rlpHash(s) +} + // Quorum Certificate struct in XDPoS 2.0 type QuorumCert struct { ProposedBlockInfo *BlockInfo @@ -60,12 +100,6 @@ 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) @@ -76,16 +110,10 @@ func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) { return append(versionByte, bytes...), nil } -func (m *Vote) Hash() common.Hash { - return rlpHash(m) -} - -func (m *Timeout) Hash() common.Hash { - return rlpHash(m) -} - -func (m *SyncInfo) Hash() common.Hash { - return rlpHash(m) +type EpochSwitchInfo struct { + Masternodes []common.Address + EpochSwitchBlockInfo *BlockInfo + EpochSwitchParentBlockInfo *BlockInfo } type VoteForSign struct { @@ -105,13 +133,3 @@ type TimeoutForSign struct { func TimeoutSigHash(m *TimeoutForSign) common.Hash { return rlpHash(m) } - -func (m *Vote) PoolKey() string { - // return the voted block hash - return fmt.Sprint(m.ProposedBlockInfo.Round, ":", m.GapNumber, ":", m.ProposedBlockInfo.Number, ":", m.ProposedBlockInfo.Hash.Hex()) -} - -func (m *Timeout) PoolKey() string { - // timeout pool key is round:gapNumber - return fmt.Sprint(m.Round, ":", m.GapNumber) -} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 724bbe9770..b5391628a7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -427,7 +427,8 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs // safely used to calculate a signature from. // // The hash is calulcated as -// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). // // This gives context to the signed message and prevents signing of transactions. func signHash(data []byte) []byte { @@ -1149,6 +1150,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h _, _, failed, err := s.doCall(ctx, args, rpc.LatestBlockNumber, vm.Config{}, 0) if err != nil || failed { + log.Warn("[EstimateGas] api", "err", err) return false } return true @@ -1323,8 +1325,8 @@ func (s *PublicBlockChainAPI) findNearestSignedBlock(ctx context.Context, b *typ } /* - findFinalityOfBlock return finality of a block - Use blocksHashCache for to keep track - refer core/blockchain.go for more detail +findFinalityOfBlock return finality of a block +Use blocksHashCache for to keep track - refer core/blockchain.go for more detail */ func (s *PublicBlockChainAPI) findFinalityOfBlock(ctx context.Context, b *types.Block, masternodes []common.Address) (uint, error) { engine, _ := s.b.GetEngine().(*XDPoS.XDPoS) @@ -1389,7 +1391,7 @@ func (s *PublicBlockChainAPI) findFinalityOfBlock(ctx context.Context, b *types. } /* - Extract signers from block +Extract signers from block */ func (s *PublicBlockChainAPI) getSigners(ctx context.Context, block *types.Block, engine *XDPoS.XDPoS) ([]common.Address, error) { var err error @@ -2979,7 +2981,8 @@ func GetSignersFromBlocks(b Backend, blockNumber uint64, blockHash common.Hash, // GetStakerROI Estimate ROI for stakers using the last epoc reward // then multiple by epoch per year, if the address is not masternode of last epoch - return 0 // Formular: -// ROI = average_latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 +// +// ROI = average_latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 func (s *PublicBlockChainAPI) GetStakerROI() float64 { blockNumber := s.b.CurrentBlock().Number().Uint64() lastCheckpointNumber := blockNumber - (blockNumber % s.b.ChainConfig().XDPoS.Epoch) - s.b.ChainConfig().XDPoS.Epoch // calculate for 2 epochs ago @@ -3005,7 +3008,8 @@ func (s *PublicBlockChainAPI) GetStakerROI() float64 { // GetStakerROIMasternode Estimate ROI for stakers of a specific masternode using the last epoc reward // then multiple by epoch per year, if the address is not masternode of last epoch - return 0 // Formular: -// ROI = latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 +// +// ROI = latest_epoch_reward_for_voters*number_of_epoch_per_year/latest_total_cap*100 func (s *PublicBlockChainAPI) GetStakerROIMasternode(masternode common.Address) float64 { votersReward := s.b.GetVotersRewards(masternode) if votersReward == nil { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 8e368fc581..f80b2b6e0a 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -146,6 +146,16 @@ web3._extend({ params: 1, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), + new web3._extend.Method({ + name: 'getMasternodesByNumber', + call: 'XDPoS_getMasternodesByNumber', + params: 1, + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + }), + new web3._extend.Method({ + name: 'getLatestPoolStatus', + call: 'XDPoS_getLatestPoolStatus' + }), ], properties: [ new web3._extend.Property({