verify header including validator (#71)

* verify header including validator

* re-structure v1 v2 tests

* remove unused test function

* add test to check coinbase and validator address matches

* refactor engine v2 to group private functions into same file
This commit is contained in:
Jerome 2022-03-20 21:14:35 +11:00 committed by GitHub
parent fbb9e87251
commit d55229677d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1657 additions and 1084 deletions

View file

@ -1,7 +1,6 @@
package engine_v2
import (
"bytes"
"encoding/json"
"errors"
"fmt"
@ -17,10 +16,8 @@ import (
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
@ -271,6 +268,15 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
header.Time = big.NewInt(time.Now().Unix())
}
x.signLock.RLock()
signer := x.signer
x.signLock.RUnlock()
if header.Coinbase != signer {
log.Error("[Prepare] The mined blocker header coinbase address mismatch with waller address", "headerCoinbase", header.Coinbase.Hex(), "WalletAddress", signer.Hex())
return consensus.ErrCoinbaseMismatch
}
return nil
}
@ -387,7 +393,7 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s
}
round := x.currentRound
isEpochSwitch, _, err := x.IsEpochSwitchAtRound(round, parent)
isEpochSwitch, _, err := x.isEpochSwitchAtRound(round, parent)
if err != nil {
log.Error("[YourTurn] check epoch switch at round failed", "Error", err)
return false, err
@ -470,7 +476,6 @@ func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *type
return false
}
// Copy from v1
func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) {
number := header.Number.Uint64()
log.Trace("get snapshot", "number", number)
@ -481,36 +486,6 @@ func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header
return snap, nil
}
// snapshot retrieves the authorization snapshot at a given point in time.
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGapNumber bool) (*SnapshotV2, error) {
var gapBlockNum uint64
if isGapNumber {
gapBlockNum = number
} else {
gapBlockNum = number - number%x.config.Epoch - x.config.Gap
}
gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())
// If an in-memory SnapshotV2 was found, use that
if s, ok := x.snapshots.Get(gapBlockHash); ok {
snap := s.(*SnapshotV2)
log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
return snap, nil
}
// If an on-disk checkpoint snapshot can be found, use that
snap, err := loadSnapshot(x.db, gapBlockHash)
if err != nil {
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
return nil, err
}
log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
x.snapshots.Add(snap.Hash, snap)
return snap, nil
}
func (x *XDPoS_v2) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
number := header.Number.Uint64()
log.Trace("take snapshot", "number", number, "hash", header.Hash())
@ -563,122 +538,6 @@ func (x *XDPoS_v2) VerifyHeaders(chain consensus.ChainReader, headers []*types.H
}()
}
// Verify individual header
func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
// If we're running a engine faking, accept any block as valid
if x.config.V2.SkipV2Validation {
return nil
}
_, check := x.verifiedHeaders.Get(header.Hash())
if check {
return nil
}
if header.Number == nil {
return utils.ErrUnknownBlock
}
if fullVerify {
if len(header.Validator) == 0 {
return consensus.ErrNoValidatorSignature
}
// Don't waste time checking blocks from the future
if header.Time.Int64() > time.Now().Unix() {
return consensus.ErrFutureBlock
}
}
// Verify this is truely a v2 block first
quorumCert, round, _, err := x.getExtraFields(header)
if err != nil {
return utils.ErrInvalidV2Extra
}
if round <= quorumCert.ProposedBlockInfo.Round {
return utils.ErrRoundInvalid
}
err = x.verifyQC(chain, quorumCert)
if err != nil {
log.Warn("[verifyHeader] fail to verify QC", "QCNumber", quorumCert.ProposedBlockInfo.Number, "QCsigLength", len(quorumCert.Signatures))
return err
}
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
if !bytes.Equal(header.Nonce[:], utils.NonceAuthVote) && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
return utils.ErrInvalidVote
}
// Ensure that the mix digest is zero as we don't have fork protection currently
if header.MixDigest != (common.Hash{}) {
return utils.ErrInvalidMixDigest
}
// Ensure that the block doesn't contain any uncles which are meaningless in XDPoS_v1
if header.UncleHash != utils.UncleHash {
return utils.ErrInvalidUncleHash
}
if header.Difficulty.Cmp(big.NewInt(1)) != 0 {
return utils.ErrInvalidDifficulty
}
isEpochSwitch, _, err := x.IsEpochSwitch(header) // Verify v2 block that is on the epoch switch
if err != nil {
log.Error("[verifyHeader] error when checking if header is epoch switch header", "Hash", header.Hash(), "Number", header.Number, "Error", err)
return err
}
if isEpochSwitch {
if !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
return utils.ErrInvalidCheckpointVote
}
if header.Validators == nil || len(header.Validators) == 0 {
return utils.ErrEmptyEpochSwitchValidators
}
if len(header.Validators)%common.AddressLength != 0 {
return utils.ErrInvalidCheckpointSigners
}
// TODO: Add checkMasternodesOnEpochSwitch
} else {
if len(header.Validators) != 0 {
log.Warn("[verifyHeader] Validators shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "Validators", header.Validators)
return utils.ErrInvalidFieldInNonEpochSwitch
}
}
// If all checks passed, validate any special fields for hard forks
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
return err
}
// Ensure that the block's timestamp isn't too close to it's parent
var parent *types.Header
number := header.Number.Uint64()
if len(parents) > 0 {
parent = parents[len(parents)-1]
} else {
parent = chain.GetHeader(header.ParentHash, number-1)
}
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
return consensus.ErrUnknownAncestor
}
if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() {
return utils.ErrInvalidTimestamp
}
// TODO: item 9. check validator
_, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash)
if err != nil {
log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash())
return err
}
if !utils.CompareSignersLists(common.ExtractAddressFromBytes(header.Penalties), penalties) {
return utils.ErrPenaltyListDoesNotMatch
}
x.verifiedHeaders.Add(header.Hash(), true)
return nil
}
/*
SyncInfo workflow
*/
@ -746,7 +605,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *utils.Vo
if err != nil {
log.Error("[VerifyVoteMessage] fail to get snapshot for a vote message", "BlockNum", vote.ProposedBlockInfo.Number, "Hash", vote.ProposedBlockInfo.Hash, "Error", err.Error())
}
verified, err := x.verifyMsgSignature(utils.VoteSigHash(vote.ProposedBlockInfo), vote.Signature, snapshot.NextEpochMasterNodes)
verified, _, err := x.verifyMsgSignature(utils.VoteSigHash(vote.ProposedBlockInfo), vote.Signature, snapshot.NextEpochMasterNodes)
if err != nil {
for i, mn := range snapshot.NextEpochMasterNodes {
log.Warn("[VerifyVoteMessage] Master node list item", "index", i, "Master node", mn.Hex())
@ -763,101 +622,6 @@ func (x *XDPoS_v2) VoteHandler(chain consensus.ChainReader, voteMsg *utils.Vote)
return x.voteHandler(chain, voteMsg)
}
func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *utils.Vote) error {
// 1. checkRoundNumber
if (voteMsg.ProposedBlockInfo.Round != x.currentRound) && (voteMsg.ProposedBlockInfo.Round != x.currentRound+1) {
return &utils.ErrIncomingMessageRoundTooFarFromCurrentRound{
Type: "vote",
IncomingRound: voteMsg.ProposedBlockInfo.Round,
CurrentRound: x.currentRound,
}
}
// Collect vote
thresholdReached, numberOfVotesInPool, pooledVotes := x.votePool.Add(voteMsg)
log.Info("[voteHandler] collect votes", "number", numberOfVotesInPool)
if thresholdReached {
log.Info(fmt.Sprintf("[voteHandler] Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool))
// Check if the block already exist, otherwise we try luck with the next vote
proposedBlockHeader := chain.GetHeaderByHash(voteMsg.ProposedBlockInfo.Hash)
if proposedBlockHeader == nil {
log.Warn("[voteHandler] The proposed block from vote message does not exist yet, wait for the next vote to try again", "Hash", voteMsg.ProposedBlockInfo.Hash, "Round", voteMsg.ProposedBlockInfo.Round)
return nil
}
err := x.VerifyBlockInfo(chain, voteMsg.ProposedBlockInfo)
if err != nil {
x.votePool.ClearPoolKeyByObj(voteMsg)
return err
}
err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader)
if err != nil {
return err
}
}
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)
// Filter out non-Master nodes signatures
var wg sync.WaitGroup
wg.Add(len(pooledVotes))
signatureSlice := make([]utils.Signature, len(pooledVotes))
counter := 0
for h, vote := range pooledVotes {
go func(hash common.Hash, v *utils.Vote, i int) {
defer wg.Done()
verified, err := x.verifyMsgSignature(utils.VoteSigHash(v.ProposedBlockInfo), v.Signature, masternodes)
if !verified || err != nil {
log.Warn("[onVotePoolThresholdReached] Skip not verified vote signatures when building QC", "Error", err.Error(), "verified", verified)
} else {
signatureSlice[i] = v.Signature
}
}(h, vote.(*utils.Vote), counter)
counter++
}
wg.Wait()
// The signature list may contain empty entey. we only care the ones with values
var validSignatureSlice []utils.Signature
for _, v := range signatureSlice {
if len(v) != 0 {
validSignatureSlice = append(validSignatureSlice, v)
}
}
// Skip and wait for the next vote to process again if valid votes is less than what we required
if len(validSignatureSlice) < x.config.V2.CertThreshold {
log.Warn("[onVotePoolThresholdReached] Not enough valid signatures to generate QC", "VotesSignaturesAfterFilter", validSignatureSlice, "NumberOfValidVotes", len(validSignatureSlice), "NumberOfVotes", len(pooledVotes))
return nil
}
// Genrate QC
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: currentVoteMsg.(*utils.Vote).ProposedBlockInfo,
Signatures: validSignatureSlice,
}
err := x.processQC(chain, quorumCert)
if err != nil {
log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err)
return err
}
log.Info("Successfully processed the vote and produced QC!", "QcRound", quorumCert.ProposedBlockInfo.Round, "QcNumOfSig", len(quorumCert.Signatures), "QcHash", quorumCert.ProposedBlockInfo.Hash, "QcNumber", quorumCert.ProposedBlockInfo.Number.Uint64())
// clean up vote at the same poolKey. and pookKey is proposed block hash
x.votePool.ClearPoolKeyByObj(currentVoteMsg)
return nil
}
/*
Timeout workflow
*/
@ -880,10 +644,11 @@ func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg
return false, fmt.Errorf("Empty master node lists from snapshot")
}
return x.verifyMsgSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
verified, _, err := x.verifyMsgSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: timeoutMsg.Round,
GapNumber: timeoutMsg.GapNumber,
}), timeoutMsg.Signature, snap.NextEpochMasterNodes)
return verified, err
}
/*
@ -898,64 +663,6 @@ func (x *XDPoS_v2) TimeoutHandler(blockChainReader consensus.ChainReader, timeou
return x.timeoutHandler(blockChainReader, timeout)
}
func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeout *utils.Timeout) error {
// 1. checkRoundNumber
if timeout.Round != x.currentRound {
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
Type: "timeout",
IncomingRound: timeout.Round,
CurrentRound: x.currentRound,
}
}
// Collect timeout, generate TC
isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
log.Info("[timeoutHandler] collect timeout", "number", numberOfTimeoutsInPool)
// Threshold reached
if isThresholdReached {
log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool))
err := x.onTimeoutPoolThresholdReached(blockChainReader, pooledTimeouts, timeout, timeout.GapNumber)
if err != nil {
return err
}
// clean up timeout message, regardless its GapNumber or round
x.timeoutPool.Clear()
}
return nil
}
/*
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 := []utils.Signature{}
for _, v := range pooledTimeouts {
signatures = append(signatures, v.(*utils.Timeout).Signature)
}
// Genrate TC
timeoutCert := &utils.TimeoutCert{
Round: currentTimeoutMsg.(*utils.Timeout).Round,
Signatures: signatures,
GapNumber: gapNumber,
}
// Process TC
err := x.processTC(blockChainReader, timeoutCert)
if err != nil {
log.Error("Error while processing TC in the Timeout handler after reaching pool threshold", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures), "GapNumber", gapNumber, "Error", err)
return err
}
// Generate and broadcast syncInfo
syncInfo := x.getSyncInfo()
x.broadcastToBftChannel(syncInfo)
log.Info("Successfully processed the timeout message and produced TC & SyncInfo!", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures))
return nil
}
/*
Proposed Block workflow
*/
@ -1099,7 +806,7 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
for _, signature := range signatures {
go func(sig utils.Signature) {
defer wg.Done()
verified, err := x.verifyMsgSignature(utils.VoteSigHash(quorumCert.ProposedBlockInfo), sig, epochInfo.Masternodes)
verified, _, err := x.verifyMsgSignature(utils.VoteSigHash(quorumCert.ProposedBlockInfo), sig, epochInfo.Masternodes)
if err != nil {
log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err)
haveError = fmt.Errorf("Error while verfying QC message signatures")
@ -1120,65 +827,6 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
return x.VerifyBlockInfo(blockChainReader, quorumCert.ProposedBlockInfo)
}
func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *utils.TimeoutCert) error {
/*
1. Get epoch master node list by gapNumber
2. Check number of signatures > threshold, as well as it's format. (Same as verifyQC)
2. Verify signer signature: (List of signatures)
- Use ecRecover to get the public key
- Use the above public key to find out the xdc address
- Use the above xdc address to check against the master node list from step 1(For the received TC epoch)
*/
snap, err := x.getSnapshot(chain, timeoutCert.GapNumber, true)
if err != nil {
log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "TCGapNumber", timeoutCert.GapNumber)
return fmt.Errorf("[verifyTC] Unable to get snapshot")
}
if snap == nil || len(snap.NextEpochMasterNodes) == 0 {
log.Error("[verifyTC] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutCert.GapNumber, "snapshot", snap)
return fmt.Errorf("Empty master node lists from snapshot")
}
if timeoutCert == nil {
log.Warn("[verifyTC] TC is Nil")
return utils.ErrInvalidTC
} else if timeoutCert.Signatures == nil || (len(timeoutCert.Signatures) < x.config.V2.CertThreshold) {
log.Warn("[verifyTC] Invalid TC Signature is nil or empty", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures))
return utils.ErrInvalidTC
}
var wg sync.WaitGroup
wg.Add(len(timeoutCert.Signatures))
var haveError error
signedTimeoutObj := utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: timeoutCert.Round,
GapNumber: timeoutCert.GapNumber,
})
for _, signature := range timeoutCert.Signatures {
go func(sig utils.Signature) {
defer wg.Done()
verified, err := x.verifyMsgSignature(signedTimeoutObj, sig, snap.NextEpochMasterNodes)
if err != nil {
log.Error("[verifyTC] Error while verfying TC message signatures", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures), "Error", err)
haveError = fmt.Errorf("Error while verfying TC message signatures")
return
}
if !verified {
log.Warn("[verifyTC] Signature not verified doing TC verification", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures))
haveError = fmt.Errorf("Fail to verify TC due to signature mis-match")
return
}
}(signature)
}
wg.Wait()
if haveError != nil {
return haveError
}
return nil
}
// Update local QC variables including highestQC & lockQuorumCert, as well as commit the blocks that satisfy the algorithm requirements
func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert) error {
log.Trace("[ProcessQC][Before]", "HighQC", x.highestQuorumCert)
@ -1222,23 +870,6 @@ func (x *XDPoS_v2) processQC(blockChainReader consensus.ChainReader, quorumCert
return nil
}
/*
1. Update highestTC
2. Check TC round >= node's currentRound. If yes, call setNewRound
*/
func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *utils.TimeoutCert) error {
if timeoutCert.Round > x.highestTimeoutCert.Round {
x.highestTimeoutCert = timeoutCert
}
if timeoutCert.Round >= x.currentRound {
err := x.setNewRound(blockChainReader, timeoutCert.Round+1)
if err != nil {
return err
}
}
return nil
}
/*
1. Set currentRound = QC round + 1 (or TC round +1)
2. Reset timer
@ -1254,185 +885,6 @@ func (x *XDPoS_v2) setNewRound(blockChainReader consensus.ChainReader, round uti
return nil
}
// Hot stuff rule to decide whether this node is eligible to vote for the received block
func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *utils.BlockInfo, quorumCert *utils.QuorumCert) (bool, error) {
// Make sure this node has not voted for this round.
if x.currentRound <= x.highestVotedRound {
return false, nil
}
/*
HotStuff Voting rule:
header's round == local current round, AND (one of the following two:)
header's block extends lockQuorumCert's ProposedBlockInfo (we need a isExtending(block_a, block_b) function), OR
header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round
*/
if blockInfo.Round != x.currentRound {
return false, nil
}
// XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule
if x.lockQuorumCert == nil {
return true, nil
}
if quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
return true, nil
}
isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo)
if err != nil {
return false, err
}
if isExtended {
return true, nil
}
return false, nil
}
// Once Hot stuff voting rule has verified, this node can then send vote
func (x *XDPoS_v2) sendVote(chainReader consensus.ChainReader, blockInfo *utils.BlockInfo) error {
// First step: Update the highest Voted round
// Second step: Generate the signature by using node's private key(The signature is the blockInfo signature)
// Third step: Construct the vote struct with the above signature & blockinfo struct
// Forth step: Send the vote to broadcast channel
signedHash, err := x.signSignature(utils.VoteSigHash(blockInfo))
if err != nil {
log.Error("signSignature when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err)
return err
}
x.highestVotedRound = x.currentRound
voteMsg := &utils.Vote{
ProposedBlockInfo: blockInfo,
Signature: signedHash,
}
err = x.voteHandler(chainReader, voteMsg)
if err != nil {
log.Error("sendVote error", "BlockInfoHash", blockInfo.Hash, "Error", err)
return err
}
x.broadcastToBftChannel(voteMsg)
return nil
}
// Generate and send timeout into BFT channel.
/*
1. timeout.round = currentRound
2. Sign the signature
3. send to broadcast channel
*/
func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error {
// Construct the gapNumber
var gapNumber uint64
currentBlockHeader := chain.CurrentHeader()
isEpochSwitch, epochNum, err := x.IsEpochSwitchAtRound(x.currentRound, currentBlockHeader)
if err != nil {
log.Error("[sendTimeout] Error while checking if the currentBlock is epoch switch", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
return err
}
if isEpochSwitch {
// Notice this +1 is because we expect a block whos is the child of currentHeader
currentNumber := currentBlockHeader.Number.Uint64() + 1
gapNumber = currentNumber - currentNumber%x.config.Epoch - x.config.Gap
log.Debug("[sendTimeout] is epoch switch when sending out timeout message", "currentNumber", currentNumber, "gapNumber", gapNumber)
} else {
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentBlockHeader, currentBlockHeader.Hash())
if err != nil {
log.Error("[sendTimeout] Error when trying to get current epoch switch info for a non-epoch block", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
}
gapNumber = epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() - epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch - x.config.Gap
log.Debug("[sendTimeout] non-epoch-switch block found its epoch block and calculated the gapNumber", "epochSwitchInfo.EpochSwitchBlockInfo.Number", epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64(), "gapNumber", gapNumber)
}
signedHash, err := x.signSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: x.currentRound,
GapNumber: gapNumber,
}))
if err != nil {
log.Error("[sendTimeout] signSignature when sending out TC", "Error", err)
return err
}
timeoutMsg := &utils.Timeout{
Round: x.currentRound,
Signature: signedHash,
GapNumber: gapNumber,
}
log.Info("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber)
err = x.timeoutHandler(chain, timeoutMsg)
if err != nil {
log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err)
return err
}
x.broadcastToBftChannel(timeoutMsg)
return nil
}
func (x *XDPoS_v2) signSignature(signingHash common.Hash) (utils.Signature, error) {
// Don't hold the signFn for the whole signing operation
x.signLock.RLock()
signer, signFn := x.signer, x.signFn
x.signLock.RUnlock()
signedHash, err := signFn(accounts.Account{Address: signer}, signingHash.Bytes())
if err != nil {
return nil, fmt.Errorf("Error while signing hash")
}
return signedHash, nil
}
func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature utils.Signature, masternodes []common.Address) (bool, error) {
if len(masternodes) == 0 {
return false, fmt.Errorf("Empty masternode list detected when verifying message signatures")
}
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature)
if err != nil {
return false, fmt.Errorf("Error while verifying message: %v", err)
}
var signerAddress common.Address
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
for _, mn := range masternodes {
if mn == signerAddress {
return true, nil
}
}
return false, fmt.Errorf("Masternodes list does not contain signer address, Signer address: %v", signerAddress.Hex())
}
/*
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()
defer x.lock.Unlock()
// Check if we are within the master node list
err := x.allowedToSend(chain.(consensus.ChainReader), chain.(consensus.ChainReader).CurrentHeader(), "timeout")
if err != nil {
return err
}
err = x.sendTimeout(chain.(consensus.ChainReader))
if err != nil {
log.Error("Error while sending out timeout message at time: ", time)
return err
}
x.timeoutCount++
if x.timeoutCount%x.config.V2.TimeoutSyncThreshold == 0 {
log.Info("[OnCountdownTimeout] timeout sync threadhold reached, send syncInfo message")
syncInfo := x.getSyncInfo()
x.broadcastToBftChannel(syncInfo)
}
return nil
}
func (x *XDPoS_v2) broadcastToBftChannel(msg interface{}) {
go func() {
x.BroadcastCh <- msg
@ -1490,26 +942,6 @@ func (x *XDPoS_v2) commitBlocks(blockChainReader consensus.ChainReader, proposed
return false, nil
}
func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *utils.BlockInfo, ancestorBlock *utils.BlockInfo) (bool, error) {
blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64())
nextBlockHash := currentBlock.Hash
for i := 0; i < blockNumDiff; i++ {
parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash)
if parentBlock == nil {
return false, fmt.Errorf("Could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number)
} else {
nextBlockHash = parentBlock.ParentHash
}
log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash)
}
if nextBlockHash == ancestorBlock.Hash {
return true, nil
}
return false, nil
}
// Get master nodes over extra data of epoch switch block.
func (x *XDPoS_v2) GetMasternodesFromEpochSwitchHeader(epochSwitchHeader *types.Header) []common.Address {
if epochSwitchHeader == nil {
@ -1548,77 +980,6 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
return parentRound < epochStartRound, epochNum, nil
}
// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent
func (x *XDPoS_v2) IsEpochSwitchAtRound(round utils.Round, parentHeader *types.Header) (bool, uint64, error) {
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
if parentHeader.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
return true, epochNum, nil
}
_, round, _, err := x.getExtraFields(parentHeader)
if err != nil {
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", parentHeader, "extra", common.Bytes2Hex(parentHeader.Extra))
return false, 0, err
}
parentRound := round
epochStartRound := round - round%utils.Round(x.config.Epoch)
return parentRound < epochStartRound, epochNum, 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)
if h == nil {
log.Warn("[getEpochSwitchInfo] can not find header from db", "hash", hash.Hex())
return nil, fmt.Errorf("[getEpochSwitchInfo] can not find header from db hash %v", hash.Hex())
}
}
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())
quorumCert, round, masternodes, err := x.getExtraFields(h)
if err != nil {
return nil, err
}
epochSwitchInfo := &utils.EpochSwitchInfo{
Masternodes: masternodes,
EpochSwitchBlockInfo: &utils.BlockInfo{
Hash: hash,
Number: h.Number,
Round: round,
},
}
if quorumCert != nil {
epochSwitchInfo.EpochSwitchParentBlockInfo = 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())
@ -1671,23 +1032,6 @@ func (x *XDPoS_v2) GetMasternodesByHash(chain consensus.ChainReader, hash common
return epochSwitchInfo.Masternodes
}
// get epoch switch of the previous `limit` epoch
func (x *XDPoS_v2) getPreviousEpochSwitchInfoByHash(chain consensus.ChainReader, hash common.Hash, limit int) (*utils.EpochSwitchInfo, error) {
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash)
if err != nil {
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return nil, err
}
for i := 0; i < limit; i++ {
epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash)
if err != nil {
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return nil, err
}
}
return epochSwitchInfo, nil
}
// Given hash, get master node from the epoch switch block of the previous `limit` epoch
func (x *XDPoS_v2) GetPreviousPenaltyByHash(chain consensus.ChainReader, hash common.Hash, limit int) []common.Address {
epochSwitchInfo, err := x.getPreviousEpochSwitchInfoByHash(chain, hash, limit)
@ -1707,26 +1051,6 @@ func (x *XDPoS_v2) FindParentBlockToAssign(chain consensus.ChainReader) *types.B
return parent
}
func (x *XDPoS_v2) getExtraFields(header *types.Header) (*utils.QuorumCert, utils.Round, []common.Address, error) {
var masternodes []common.Address
// last v1 block
if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
masternodes = decodeMasternodesFromHeaderExtra(header)
return nil, utils.Round(0), masternodes, nil
}
// v2 block
masternodes = x.GetMasternodesFromEpochSwitchHeader(header)
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
if err != nil {
return nil, utils.Round(0), masternodes, err
}
return decodedExtraField.QuorumCert, decodedExtraField.Round, masternodes, nil
}
func (x *XDPoS_v2) allowedToSend(chain consensus.ChainReader, blockHeader *types.Header, sendType string) error {
allowedToSend := false
// Don't hold the signFn for the whole signing operation

View file

@ -0,0 +1,99 @@
package engine_v2
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
)
// get epoch switch of the previous `limit` epoch
func (x *XDPoS_v2) getPreviousEpochSwitchInfoByHash(chain consensus.ChainReader, hash common.Hash, limit int) (*utils.EpochSwitchInfo, error) {
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash)
if err != nil {
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return nil, err
}
for i := 0; i < limit; i++ {
epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash)
if err != nil {
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return nil, err
}
}
return epochSwitchInfo, 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)
if h == nil {
log.Warn("[getEpochSwitchInfo] can not find header from db", "hash", hash.Hex())
return nil, fmt.Errorf("[getEpochSwitchInfo] can not find header from db hash %v", hash.Hex())
}
}
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())
quorumCert, round, masternodes, err := x.getExtraFields(h)
if err != nil {
return nil, err
}
epochSwitchInfo := &utils.EpochSwitchInfo{
Masternodes: masternodes,
EpochSwitchBlockInfo: &utils.BlockInfo{
Hash: hash,
Number: h.Number,
Round: round,
},
}
if quorumCert != nil {
epochSwitchInfo.EpochSwitchParentBlockInfo = 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
}
// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent
func (x *XDPoS_v2) isEpochSwitchAtRound(round utils.Round, parentHeader *types.Header) (bool, uint64, error) {
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
if parentHeader.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
return true, epochNum, nil
}
_, round, _, err := x.getExtraFields(parentHeader)
if err != nil {
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", parentHeader, "extra", common.Bytes2Hex(parentHeader.Extra))
return false, 0, err
}
parentRound := round
epochStartRound := round - round%utils.Round(x.config.Epoch)
return parentRound < epochStartRound, epochNum, nil
}

View file

@ -4,7 +4,9 @@ import (
"encoding/json"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
)
// Snapshot is the state of the smart contract validator list
@ -68,3 +70,33 @@ func (s *SnapshotV2) IsMasterNodes(address common.Address) bool {
}
return false
}
// snapshot retrieves the authorization snapshot at a given point in time.
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGapNumber bool) (*SnapshotV2, error) {
var gapBlockNum uint64
if isGapNumber {
gapBlockNum = number
} else {
gapBlockNum = number - number%x.config.Epoch - x.config.Gap
}
gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())
// If an in-memory SnapshotV2 was found, use that
if s, ok := x.snapshots.Get(gapBlockHash); ok {
snap := s.(*SnapshotV2)
log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
return snap, nil
}
// If an on-disk checkpoint snapshot can be found, use that
snap, err := loadSnapshot(x.db, gapBlockHash)
if err != nil {
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
return nil, err
}
log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
x.snapshots.Add(snap.Hash, snap)
return snap, nil
}

View file

@ -0,0 +1,229 @@
package engine_v2
import (
"fmt"
"sync"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/log"
)
func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeout *utils.Timeout) error {
// 1. checkRoundNumber
if timeout.Round != x.currentRound {
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
Type: "timeout",
IncomingRound: timeout.Round,
CurrentRound: x.currentRound,
}
}
// Collect timeout, generate TC
isThresholdReached, numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
log.Info("[timeoutHandler] collect timeout", "number", numberOfTimeoutsInPool)
// Threshold reached
if isThresholdReached {
log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool))
err := x.onTimeoutPoolThresholdReached(blockChainReader, pooledTimeouts, timeout, timeout.GapNumber)
if err != nil {
return err
}
// clean up timeout message, regardless its GapNumber or round
x.timeoutPool.Clear()
}
return nil
}
/*
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 := []utils.Signature{}
for _, v := range pooledTimeouts {
signatures = append(signatures, v.(*utils.Timeout).Signature)
}
// Genrate TC
timeoutCert := &utils.TimeoutCert{
Round: currentTimeoutMsg.(*utils.Timeout).Round,
Signatures: signatures,
GapNumber: gapNumber,
}
// Process TC
err := x.processTC(blockChainReader, timeoutCert)
if err != nil {
log.Error("Error while processing TC in the Timeout handler after reaching pool threshold", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures), "GapNumber", gapNumber, "Error", err)
return err
}
// Generate and broadcast syncInfo
syncInfo := x.getSyncInfo()
x.broadcastToBftChannel(syncInfo)
log.Info("Successfully processed the timeout message and produced TC & SyncInfo!", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures))
return nil
}
func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *utils.TimeoutCert) error {
/*
1. Get epoch master node list by gapNumber
2. Check number of signatures > threshold, as well as it's format. (Same as verifyQC)
2. Verify signer signature: (List of signatures)
- Use ecRecover to get the public key
- Use the above public key to find out the xdc address
- Use the above xdc address to check against the master node list from step 1(For the received TC epoch)
*/
snap, err := x.getSnapshot(chain, timeoutCert.GapNumber, true)
if err != nil {
log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "TCGapNumber", timeoutCert.GapNumber)
return fmt.Errorf("[verifyTC] Unable to get snapshot")
}
if snap == nil || len(snap.NextEpochMasterNodes) == 0 {
log.Error("[verifyTC] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutCert.GapNumber, "snapshot", snap)
return fmt.Errorf("Empty master node lists from snapshot")
}
if timeoutCert == nil {
log.Warn("[verifyTC] TC is Nil")
return utils.ErrInvalidTC
} else if timeoutCert.Signatures == nil || (len(timeoutCert.Signatures) < x.config.V2.CertThreshold) {
log.Warn("[verifyTC] Invalid TC Signature is nil or empty", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures))
return utils.ErrInvalidTC
}
var wg sync.WaitGroup
wg.Add(len(timeoutCert.Signatures))
var haveError error
signedTimeoutObj := utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: timeoutCert.Round,
GapNumber: timeoutCert.GapNumber,
})
for _, signature := range timeoutCert.Signatures {
go func(sig utils.Signature) {
defer wg.Done()
verified, _, err := x.verifyMsgSignature(signedTimeoutObj, sig, snap.NextEpochMasterNodes)
if err != nil {
log.Error("[verifyTC] Error while verfying TC message signatures", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures), "Error", err)
haveError = fmt.Errorf("Error while verfying TC message signatures")
return
}
if !verified {
log.Warn("[verifyTC] Signature not verified doing TC verification", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures))
haveError = fmt.Errorf("Fail to verify TC due to signature mis-match")
return
}
}(signature)
}
wg.Wait()
if haveError != nil {
return haveError
}
return nil
}
/*
1. Update highestTC
2. Check TC round >= node's currentRound. If yes, call setNewRound
*/
func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *utils.TimeoutCert) error {
if timeoutCert.Round > x.highestTimeoutCert.Round {
x.highestTimeoutCert = timeoutCert
}
if timeoutCert.Round >= x.currentRound {
err := x.setNewRound(blockChainReader, timeoutCert.Round+1)
if err != nil {
return err
}
}
return nil
}
// Generate and send timeout into BFT channel.
/*
1. timeout.round = currentRound
2. Sign the signature
3. send to broadcast channel
*/
func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error {
// Construct the gapNumber
var gapNumber uint64
currentBlockHeader := chain.CurrentHeader()
isEpochSwitch, epochNum, err := x.isEpochSwitchAtRound(x.currentRound, currentBlockHeader)
if err != nil {
log.Error("[sendTimeout] Error while checking if the currentBlock is epoch switch", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
return err
}
if isEpochSwitch {
// Notice this +1 is because we expect a block whos is the child of currentHeader
currentNumber := currentBlockHeader.Number.Uint64() + 1
gapNumber = currentNumber - currentNumber%x.config.Epoch - x.config.Gap
log.Debug("[sendTimeout] is epoch switch when sending out timeout message", "currentNumber", currentNumber, "gapNumber", gapNumber)
} else {
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentBlockHeader, currentBlockHeader.Hash())
if err != nil {
log.Error("[sendTimeout] Error when trying to get current epoch switch info for a non-epoch block", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
}
gapNumber = epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() - epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch - x.config.Gap
log.Debug("[sendTimeout] non-epoch-switch block found its epoch block and calculated the gapNumber", "epochSwitchInfo.EpochSwitchBlockInfo.Number", epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64(), "gapNumber", gapNumber)
}
signedHash, err := x.signSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: x.currentRound,
GapNumber: gapNumber,
}))
if err != nil {
log.Error("[sendTimeout] signSignature when sending out TC", "Error", err)
return err
}
timeoutMsg := &utils.Timeout{
Round: x.currentRound,
Signature: signedHash,
GapNumber: gapNumber,
}
log.Info("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber)
err = x.timeoutHandler(chain, timeoutMsg)
if err != nil {
log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err)
return err
}
x.broadcastToBftChannel(timeoutMsg)
return nil
}
/*
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()
defer x.lock.Unlock()
// Check if we are within the master node list
err := x.allowedToSend(chain.(consensus.ChainReader), chain.(consensus.ChainReader).CurrentHeader(), "timeout")
if err != nil {
return err
}
err = x.sendTimeout(chain.(consensus.ChainReader))
if err != nil {
log.Error("Error while sending out timeout message at time: ", time)
return err
}
x.timeoutCount++
if x.timeoutCount%x.config.V2.TimeoutSyncThreshold == 0 {
log.Info("[OnCountdownTimeout] timeout sync threadhold reached, send syncInfo message")
syncInfo := x.getSyncInfo()
x.broadcastToBftChannel(syncInfo)
}
return nil
}

View file

@ -1,6 +1,9 @@
package engine_v2
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
@ -84,3 +87,57 @@ func UniqueSignatures(signatureSlice []utils.Signature) ([]utils.Signature, []ut
}
return list, duplicates
}
func (x *XDPoS_v2) signSignature(signingHash common.Hash) (utils.Signature, error) {
// Don't hold the signFn for the whole signing operation
x.signLock.RLock()
signer, signFn := x.signer, x.signFn
x.signLock.RUnlock()
signedHash, err := signFn(accounts.Account{Address: signer}, signingHash.Bytes())
if err != nil {
return nil, fmt.Errorf("Error while signing hash")
}
return signedHash, nil
}
func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature utils.Signature, masternodes []common.Address) (bool, common.Address, error) {
var signerAddress common.Address
if len(masternodes) == 0 {
return false, signerAddress, fmt.Errorf("Empty masternode list detected when verifying message signatures")
}
// Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature)
if err != nil {
return false, signerAddress, fmt.Errorf("Error while verifying message: %v", err)
}
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
for _, mn := range masternodes {
if mn == signerAddress {
return true, signerAddress, nil
}
}
return false, signerAddress, fmt.Errorf("Masternodes list does not contain signer address, Signer address: %v", signerAddress.Hex())
}
func (x *XDPoS_v2) getExtraFields(header *types.Header) (*utils.QuorumCert, utils.Round, []common.Address, error) {
var masternodes []common.Address
// last v1 block
if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
masternodes = decodeMasternodesFromHeaderExtra(header)
return nil, utils.Round(0), masternodes, nil
}
// v2 block
masternodes = x.GetMasternodesFromEpochSwitchHeader(header)
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
if err != nil {
return nil, utils.Round(0), masternodes, err
}
return decodedExtraField.QuorumCert, decodedExtraField.Round, masternodes, nil
}

View file

@ -0,0 +1,188 @@
package engine_v2
import (
"bytes"
"math/big"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
)
// Verify individual header
func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
// If we're running a engine faking, accept any block as valid
if x.config.V2.SkipV2Validation {
return nil
}
_, check := x.verifiedHeaders.Get(header.Hash())
if check {
return nil
}
if header.Number == nil {
return utils.ErrUnknownBlock
}
if len(header.Validator) == 0 {
return consensus.ErrNoValidatorSignature
}
if fullVerify {
// Don't waste time checking blocks from the future
if header.Time.Int64() > time.Now().Unix() {
return consensus.ErrFutureBlock
}
}
// Verify this is truely a v2 block first
quorumCert, round, _, err := x.getExtraFields(header)
if err != nil {
return utils.ErrInvalidV2Extra
}
if round <= quorumCert.ProposedBlockInfo.Round {
return utils.ErrRoundInvalid
}
err = x.verifyQC(chain, quorumCert)
if err != nil {
log.Warn("[verifyHeader] fail to verify QC", "QCNumber", quorumCert.ProposedBlockInfo.Number, "QCsigLength", len(quorumCert.Signatures))
return err
}
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
if !bytes.Equal(header.Nonce[:], utils.NonceAuthVote) && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
return utils.ErrInvalidVote
}
// Ensure that the mix digest is zero as we don't have fork protection currently
if header.MixDigest != (common.Hash{}) {
return utils.ErrInvalidMixDigest
}
// Ensure that the block doesn't contain any uncles which are meaningless in XDPoS_v1
if header.UncleHash != utils.UncleHash {
return utils.ErrInvalidUncleHash
}
if header.Difficulty.Cmp(big.NewInt(1)) != 0 {
return utils.ErrInvalidDifficulty
}
isEpochSwitch, _, err := x.IsEpochSwitch(header) // Verify v2 block that is on the epoch switch
if err != nil {
log.Error("[verifyHeader] error when checking if header is epoch switch header", "Hash", header.Hash(), "Number", header.Number, "Error", err)
return err
}
if isEpochSwitch {
if !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
return utils.ErrInvalidCheckpointVote
}
if header.Validators == nil || len(header.Validators) == 0 {
return utils.ErrEmptyEpochSwitchValidators
}
if len(header.Validators)%common.AddressLength != 0 {
return utils.ErrInvalidCheckpointSigners
}
isLegit, err := x.isValidatorsLegit(chain, header)
if err != nil {
log.Error("[verifyHeader] Error while trying to check if the validators are legit", "Hash", header.Hash(), "Number", header.Number, "ValidatorsLength", len(header.Validators))
return err
}
if !isLegit {
return utils.ErrValidatorsNotLegit
}
} else {
if len(header.Validators) != 0 {
log.Warn("[verifyHeader] Validators shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "ValidatorsLength", len(header.Validators))
return utils.ErrInvalidFieldInNonEpochSwitch
}
}
// If all checks passed, validate any special fields for hard forks
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
return err
}
// Ensure that the block's timestamp isn't too close to it's parent
var parent *types.Header
number := header.Number.Uint64()
if len(parents) > 0 {
parent = parents[len(parents)-1]
} else {
parent = chain.GetHeader(header.ParentHash, number-1)
}
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
return consensus.ErrUnknownAncestor
}
if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+uint64(x.config.V2.MinePeriod) > header.Time.Uint64() {
return utils.ErrInvalidTimestamp
}
_, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash)
if err != nil {
log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash())
return err
}
if !utils.CompareSignersLists(common.ExtractAddressFromBytes(header.Penalties), penalties) {
return utils.ErrPenaltyListDoesNotMatch
}
// Check its validator
masterNodes := x.GetMasternodes(chain, header)
verified, validatorAddress, err := x.verifyMsgSignature(sigHash(header), header.Validator, masterNodes)
if err != nil {
for index, mn := range masterNodes {
log.Error("[verifyHeader] masternode list during validator verification", "Masternode Address", mn.Hex(), "index", index)
}
log.Error("[verifyHeader] Error while verifying header validator signature", "BlockNumber", header.Number, "Hash", header.Hash().Hex(), "validator in hex", common.ToHex(header.Validator))
return err
}
if !verified {
log.Warn("[verifyHeader] Fail to verify the block validator as the validator address not within the masternode list", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex())
return utils.ErrValidatorNotWithinMasternodes
}
if validatorAddress != header.Coinbase {
log.Warn("[verifyHeader] Header validator and coinbase address not match", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex(), "coinbase", header.Coinbase.Hex())
return utils.ErrCoinbaseAndValidatorMismatch
}
// Check the proposer is the leader
curIndex := utils.Position(masterNodes, validatorAddress)
leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes))
if masterNodes[leaderIndex] != validatorAddress {
log.Warn("[verifyHeader] Invalid blocker proposer, not its turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", header.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "validatorAddress", validatorAddress)
return utils.ErrNotItsTurn
}
x.verifiedHeaders.Add(header.Hash(), true)
return nil
}
// Verify the header validators address is legit by checking against its snapshot masternode list minutes the penalty list, we also ensure the order matches
func (x *XDPoS_v2) isValidatorsLegit(chain consensus.ChainReader, header *types.Header) (bool, error) {
snap, err := x.getSnapshot(chain, header.Number.Uint64(), false)
if err != nil {
log.Error("[checkMasternodesOnEpochSwitch] Error while trying to get snapshot", "BlockNumber", header.Number.Int64(), "Hash", header.Hash().Hex(), "error", err)
return false, err
}
// snap.NextEpochMasterNodes
penaltyList := common.ExtractAddressFromBytes(header.Penalties)
penaltyMap := make(map[common.Address]bool)
for _, item := range penaltyList {
penaltyMap[item] = true
}
var finalValidMasternodes []common.Address
for _, mn := range snap.NextEpochMasterNodes {
if penaltyMap[mn] {
continue
} else {
finalValidMasternodes = append(finalValidMasternodes, mn)
}
}
validatorsAddress := common.ExtractAddressFromBytes(header.Validators)
return utils.CompareSignersLists(finalValidMasternodes, validatorsAddress), nil
}

View file

@ -0,0 +1,191 @@
package engine_v2
import (
"fmt"
"math/big"
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
)
// Once Hot stuff voting rule has verified, this node can then send vote
func (x *XDPoS_v2) sendVote(chainReader consensus.ChainReader, blockInfo *utils.BlockInfo) error {
// First step: Update the highest Voted round
// Second step: Generate the signature by using node's private key(The signature is the blockInfo signature)
// Third step: Construct the vote struct with the above signature & blockinfo struct
// Forth step: Send the vote to broadcast channel
signedHash, err := x.signSignature(utils.VoteSigHash(blockInfo))
if err != nil {
log.Error("signSignature when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err)
return err
}
x.highestVotedRound = x.currentRound
voteMsg := &utils.Vote{
ProposedBlockInfo: blockInfo,
Signature: signedHash,
}
err = x.voteHandler(chainReader, voteMsg)
if err != nil {
log.Error("sendVote error", "BlockInfoHash", blockInfo.Hash, "Error", err)
return err
}
x.broadcastToBftChannel(voteMsg)
return nil
}
func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *utils.Vote) error {
// 1. checkRoundNumber
if (voteMsg.ProposedBlockInfo.Round != x.currentRound) && (voteMsg.ProposedBlockInfo.Round != x.currentRound+1) {
return &utils.ErrIncomingMessageRoundTooFarFromCurrentRound{
Type: "vote",
IncomingRound: voteMsg.ProposedBlockInfo.Round,
CurrentRound: x.currentRound,
}
}
// Collect vote
thresholdReached, numberOfVotesInPool, pooledVotes := x.votePool.Add(voteMsg)
log.Info("[voteHandler] collect votes", "number", numberOfVotesInPool)
if thresholdReached {
log.Info(fmt.Sprintf("[voteHandler] Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool))
// Check if the block already exist, otherwise we try luck with the next vote
proposedBlockHeader := chain.GetHeaderByHash(voteMsg.ProposedBlockInfo.Hash)
if proposedBlockHeader == nil {
log.Warn("[voteHandler] The proposed block from vote message does not exist yet, wait for the next vote to try again", "Hash", voteMsg.ProposedBlockInfo.Hash, "Round", voteMsg.ProposedBlockInfo.Round)
return nil
}
err := x.VerifyBlockInfo(chain, voteMsg.ProposedBlockInfo)
if err != nil {
x.votePool.ClearPoolKeyByObj(voteMsg)
return err
}
err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader)
if err != nil {
return err
}
}
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)
// Filter out non-Master nodes signatures
var wg sync.WaitGroup
wg.Add(len(pooledVotes))
signatureSlice := make([]utils.Signature, len(pooledVotes))
counter := 0
for h, vote := range pooledVotes {
go func(hash common.Hash, v *utils.Vote, i int) {
defer wg.Done()
verified, _, err := x.verifyMsgSignature(utils.VoteSigHash(v.ProposedBlockInfo), v.Signature, masternodes)
if !verified || err != nil {
log.Warn("[onVotePoolThresholdReached] Skip not verified vote signatures when building QC", "Error", err.Error(), "verified", verified)
} else {
signatureSlice[i] = v.Signature
}
}(h, vote.(*utils.Vote), counter)
counter++
}
wg.Wait()
// The signature list may contain empty entey. we only care the ones with values
var validSignatureSlice []utils.Signature
for _, v := range signatureSlice {
if len(v) != 0 {
validSignatureSlice = append(validSignatureSlice, v)
}
}
// Skip and wait for the next vote to process again if valid votes is less than what we required
if len(validSignatureSlice) < x.config.V2.CertThreshold {
log.Warn("[onVotePoolThresholdReached] Not enough valid signatures to generate QC", "VotesSignaturesAfterFilter", validSignatureSlice, "NumberOfValidVotes", len(validSignatureSlice), "NumberOfVotes", len(pooledVotes))
return nil
}
// Genrate QC
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: currentVoteMsg.(*utils.Vote).ProposedBlockInfo,
Signatures: validSignatureSlice,
}
err := x.processQC(chain, quorumCert)
if err != nil {
log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err)
return err
}
log.Info("Successfully processed the vote and produced QC!", "QcRound", quorumCert.ProposedBlockInfo.Round, "QcNumOfSig", len(quorumCert.Signatures), "QcHash", quorumCert.ProposedBlockInfo.Hash, "QcNumber", quorumCert.ProposedBlockInfo.Number.Uint64())
// clean up vote at the same poolKey. and pookKey is proposed block hash
x.votePool.ClearPoolKeyByObj(currentVoteMsg)
return nil
}
// Hot stuff rule to decide whether this node is eligible to vote for the received block
func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *utils.BlockInfo, quorumCert *utils.QuorumCert) (bool, error) {
// Make sure this node has not voted for this round.
if x.currentRound <= x.highestVotedRound {
return false, nil
}
/*
HotStuff Voting rule:
header's round == local current round, AND (one of the following two:)
header's block extends lockQuorumCert's ProposedBlockInfo (we need a isExtending(block_a, block_b) function), OR
header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round
*/
if blockInfo.Round != x.currentRound {
return false, nil
}
// XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule
if x.lockQuorumCert == nil {
return true, nil
}
if quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
return true, nil
}
isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo)
if err != nil {
return false, err
}
if isExtended {
return true, nil
}
return false, nil
}
func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *utils.BlockInfo, ancestorBlock *utils.BlockInfo) (bool, error) {
blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64())
nextBlockHash := currentBlock.Hash
for i := 0; i < blockNumDiff; i++ {
parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash)
if parentBlock == nil {
return false, fmt.Errorf("Could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number)
} else {
nextBlockHash = parentBlock.ParentHash
}
log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash)
}
if nextBlockHash == ancestorBlock.Hash {
return true, nil
}
return false, nil
}

View file

@ -45,6 +45,8 @@ var (
ErrInvalidCheckpointPenalties = errors.New("invalid penalty list on checkpoint block")
ErrValidatorsNotLegit = errors.New("Validators does not match what's stored in snapshot minutes its penalty")
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
ErrInvalidMixDigest = errors.New("non-zero mix digest")
@ -80,11 +82,14 @@ var (
ErrEmptyEpochSwitchValidators = errors.New("empty validators list on epoch switch block")
ErrInvalidV2Extra = errors.New("Invalid v2 extra in the block")
ErrInvalidQC = errors.New("Invalid QC content")
ErrInvalidTC = errors.New("Invalid TC content")
ErrEmptyBlockInfoHash = errors.New("BlockInfo hash is empty")
ErrInvalidFieldInNonEpochSwitch = errors.New("Invalid field exist in a non-epoch swtich block")
ErrInvalidV2Extra = errors.New("Invalid v2 extra in the block")
ErrInvalidQC = errors.New("Invalid QC content")
ErrInvalidTC = errors.New("Invalid TC content")
ErrEmptyBlockInfoHash = errors.New("BlockInfo hash is empty")
ErrInvalidFieldInNonEpochSwitch = errors.New("Invalid field exist in a non-epoch swtich block")
ErrValidatorNotWithinMasternodes = errors.New("Validaotor address is not in the master node list")
ErrCoinbaseAndValidatorMismatch = errors.New("Validaotor and coinbase address in header does not match")
ErrNotItsTurn = errors.New("Not validator's turn to mine this block")
ErrPenaltyListDoesNotMatch = errors.New("Incoming block penalty list does not match")
ErrRoundInvalid = errors.New("Invalid Round, it shall be bigger than QC round")

View file

@ -40,4 +40,6 @@ var (
ErrNoValidatorSignature = errors.New("no validator in header")
ErrNotReadyToPropose = errors.New("not ready to propose, QC is not ready")
ErrCoinbaseMismatch = errors.New("Block Coinbase address does not match its wallte address")
)

View file

@ -0,0 +1,75 @@
package engine_v1_tests
import (
"math/big"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestIsAuthorisedMNForConsensusV1(t *testing.T) {
/*
V1 consensus engine
*/
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
// Insert first Block 449
t.Logf("Inserting block with propose at 449...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
tx, err := voteTX(37117, 0, acc1Addr.String())
if err != nil {
t.Fatal(err)
}
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(449)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
assert.Nil(t, err)
err = blockchain.InsertBlock(block449)
assert.Nil(t, err)
if err != nil {
t.Fatal(err)
}
parentBlock = block449
// At block 449, we should not update signerList. we need to update it till block 450 gap block.
// Acc3 is the default account that is on the signerList
engine := blockchain.Engine().(*XDPoS.XDPoS)
isAuthorisedMN := engine.IsAuthorisedAddress(blockchain, block449.Header(), acc3Addr)
assert.True(t, isAuthorisedMN)
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block449.Header(), acc1Addr)
assert.False(t, isAuthorisedMN)
// Now, let's mine another block to trigger the GAP block signerList update
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block450CoinbaseAddress),
}
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block450)
assert.Nil(t, err)
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc3Addr)
assert.False(t, isAuthorisedMN)
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc1Addr)
assert.True(t, isAuthorisedMN)
}

View file

@ -1,4 +1,4 @@
package tests
package engine_v1_tests
import (
"fmt"
@ -9,11 +9,12 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
// Should NOT update signerList if not on the gap block
func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
if err != nil {
t.Fatal(err)
@ -33,12 +34,12 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(blockA)
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
if err != nil {
t.Fatal(err)
@ -58,7 +59,7 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
// Should call updateM1 at the gap block, and have the same snapshot values as the parent block if no SM transaction is involved
func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
blockchain, _, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Insert block 450
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
@ -68,11 +69,12 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := createBlockFromHeader(blockchain, header, nil)
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block)
err = blockchain.InsertBlock(block)
assert.Nil(t, err)
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
if err != nil {
t.Fatal(err)
@ -93,7 +95,7 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
//Should call updateM1 at gap block, and update the snapshot if there are SM transactions involved
func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
// Insert first Block 449
t.Logf("Inserting block with propose at 449...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
@ -110,12 +112,12 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block449)
err = blockchain.InsertBlock(block449)
assert.Nil(t, err)
parentBlock = block449
signers, err := GetSnapshotSigner(blockchain, block449.Header())
@ -142,12 +144,12 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block450CoinbaseAddress),
}
block450, err := createBlockFromHeader(blockchain, header, nil)
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450)
err = blockchain.InsertBlock(block450)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450.Header())
if err != nil {
t.Fatalf("Failed while trying to get signers")
@ -166,7 +168,7 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
//Should call updateM1 before gap block, and update the snapshot if there are SM transactions involved
func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
blockchain, backend, currentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Insert first Block 450 A
t.Logf("Inserting block with propose at 450 A...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000450"
@ -183,12 +185,12 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(blockA)
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
if err != nil {
t.Fatal(err)
@ -202,7 +204,7 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
// Should call updateM1 and update snapshot when a forked block(at gap block number) is inserted back into main chain (Edge case)
func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
blockchain, backend, currentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Check initial signer, by default, acc3 is in the signerList
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
if err != nil {
@ -232,12 +234,12 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(blockA)
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, blockA.Header())
if err != nil {
t.Fatal(err)
@ -267,11 +269,12 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450B)
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
if err != nil {
t.Fatal(err)
@ -297,9 +300,10 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := createBlockFromHeader(blockchain, header, nil)
blockchain.InsertBlock(block451B)
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
assert.Nil(t, err)
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
if err != nil {
t.Fatal(err)
}
@ -346,7 +350,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testing.T) {
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
state, err := blockchain.State()
if err != nil {
@ -380,11 +384,12 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction})
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(blockA)
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
state, err = blockchain.State()
if err != nil {
t.Fatalf("Failed while trying to get blockchain state")
@ -422,11 +427,12 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction})
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450B)
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
state, err = blockchain.State()
if err != nil {
t.Fatalf("Failed while trying to get blockchain state")
@ -456,11 +462,12 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := createBlockFromHeader(blockchain, header, nil)
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block451B)
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
if err != nil {
@ -500,7 +507,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
}
func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
// Check initial signer, by default, acc3 is in the signerList
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
if err != nil {
@ -524,11 +531,12 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450A),
}
block450A, err := createBlockFromHeader(blockchain, header, nil)
block450A, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450A)
err = blockchain.InsertBlock(block450A)
assert.Nil(t, err)
// Insert 451 A with vote
blockCoinbase451A := "0xaaa0000000000000000000000000000000000451"
@ -544,11 +552,12 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
ParentHash: block450A.Hash(),
Coinbase: common.HexToAddress(blockCoinbase451A),
}
block451A, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
block451A, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block451A)
err = blockchain.InsertBlock(block451A)
assert.Nil(t, err)
// SignerList should be unchanged as the vote happen after GAP block
signers, err = GetSnapshotSigner(blockchain, block451A.Header())
@ -574,11 +583,12 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase450B),
}
block450B, err := createBlockFromHeader(blockchain, header, nil)
block450B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450B)
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
@ -588,11 +598,12 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
ParentHash: block450B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase451B),
}
block451B, err := createBlockFromHeader(blockchain, header, nil)
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block451B)
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
@ -602,11 +613,12 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
ParentHash: block451B.Hash(),
Coinbase: common.HexToAddress(blockCoinBase452B),
}
block452B, err := createBlockFromHeader(blockchain, header, nil)
block452B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block452B)
err = blockchain.InsertBlock(block452B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block452B.Header())
if err != nil {
t.Fatal(err)
@ -626,7 +638,7 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
// Pending for creating cross version blocks
func TestV2UpdateSignerListIfVotedBeforeGap(t *testing.T) {
config := params.TestXDPoSMockChainConfig
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)+GAP-2, config)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)+GAP-2, config)
// Insert first Block 1349
t.Logf("Inserting block with propose at 1349...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000001349"
@ -643,7 +655,7 @@ func TestV2UpdateSignerListIfVotedBeforeGap(t *testing.T) {
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block1349, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
block1349, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}

View file

@ -1,4 +1,4 @@
package tests
package engine_v1_tests
import (
"math/big"
@ -7,12 +7,13 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
// Snapshot try to read before blockchain is written
func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
blockchain, backend, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
state, err := blockchain.State()
if err != nil {
@ -48,11 +49,12 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
Coinbase: common.HexToAddress(blockCoinbaseA),
}
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction})
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(blockA)
err = blockchain.InsertBlock(blockA)
assert.Nil(t, err)
state, err = blockchain.State()
if err != nil {
t.Fatalf("Failed while trying to get blockchain state")
@ -93,11 +95,12 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
Difficulty: big.NewInt(2),
}
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction})
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450B)
err = blockchain.InsertBlock(block450B)
assert.Nil(t, err)
if blockchain.CurrentHeader().Hash() != block450B.Hash() {
t.Fatalf("the block with higher difficulty should be current header")
}
@ -131,11 +134,12 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
Coinbase: common.HexToAddress(blockCoinBase451B),
Difficulty: big.NewInt(3),
}
block451B, err := createBlockFromHeader(blockchain, header, nil)
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block451B)
err = blockchain.InsertBlock(block451B)
assert.Nil(t, err)
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
if err != nil {

View file

@ -0,0 +1,420 @@
package engine_v1_tests
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"math/big"
"math/rand"
"strings"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
"github.com/XinFinOrg/XDPoSChain/core"
. "github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
type masterNodes map[string]big.Int
type signersList map[string]bool
const GAP = int(450)
var (
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
acc3Key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
voterKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee04aefe388d1e14474d32c45c72ce7b7a")
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) //xdc703c4b2bD70c169f5717101CaeE543299Fc946C7
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) //xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
acc3Addr = crypto.PubkeyToAddress(acc3Key.PublicKey) //xdc71562b71999873DB5b286dF957af199Ec94617F7
voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434
chainID = int64(1337)
)
func debugMessage(backend *backends.SimulatedBackend, signers signersList, t *testing.T) {
ms := GetCandidateFromCurrentSmartContract(backend, t)
fmt.Println("=== current smart contract")
for nodeAddr, cap := range ms {
if !strings.Contains(nodeAddr, "000000000000000000000000000000000000") { //remove defaults
fmt.Println(nodeAddr, cap)
}
}
fmt.Println("=== this block signer list")
for signer := range signers {
if !strings.Contains(signer, "000000000000000000000000000000000000") { //remove defaults
fmt.Println(signer)
}
}
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
// initial helper backend
contractBackendForSC := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
}, 10000000, chainConfig)
transactOpts := bind.NewKeyedTransactor(voterKey)
var candidates []common.Address
var caps []*big.Int
defalutCap := new(big.Int)
defalutCap.SetString("1000000000", 10)
for i := 1; i <= 16; i++ {
addr := fmt.Sprintf("%02d", i)
candidates = append(candidates, common.StringToAddress(addr)) // StringToAddress does not exist
caps = append(caps, defalutCap)
}
acc1Cap, acc2Cap, acc3Cap, voterCap := new(big.Int), new(big.Int), new(big.Int), new(big.Int)
acc1Cap.SetString("10000001", 10)
acc2Cap.SetString("10000002", 10)
acc3Cap.SetString("10000003", 10)
voterCap.SetString("1000000000", 10)
caps = append(caps, voterCap, acc1Cap, acc2Cap, acc3Cap)
candidates = append(candidates, voterAddr, acc1Addr, acc2Addr, acc3Addr)
// create validator smart contract
validatorSCAddr, _, _, err := contractValidator.DeployXDCValidator(
transactOpts,
contractBackendForSC,
candidates,
caps,
voterAddr, // first owner, not used
big.NewInt(50000),
big.NewInt(1),
big.NewInt(99),
big.NewInt(100),
big.NewInt(100),
)
if err != nil {
t.Fatalf("can't deploy root registry: %v", err)
}
contractBackendForSC.Commit() // Write into database(state)
// Prepare Code and Storage
d := time.Now().Add(1000 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
storage := make(map[common.Hash]common.Hash)
f := func(key, val common.Hash) bool {
decode := []byte{}
trim := bytes.TrimLeft(val.Bytes(), "\x00")
err := rlp.DecodeBytes(trim, &decode)
if err != nil {
t.Fatalf("Failed while decode byte")
}
storage[key] = common.BytesToHash(decode)
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
return true
}
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
if err != nil {
t.Fatalf("Failed while trying to read all keys from SC")
}
// create test backend with smart contract in it
contractBackend2 := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
acc1Addr: {Balance: new(big.Int).SetUint64(10000000000)},
acc2Addr: {Balance: new(big.Int).SetUint64(10000000000)},
acc3Addr: {Balance: new(big.Int).SetUint64(10000000000)},
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
common.HexToAddress(common.MasternodeVotingSMC): {Balance: new(big.Int).SetUint64(1), Code: code, Storage: storage}, // Binding the MasternodeVotingSMC with newly created 'code' for SC execution
}, 10000000, chainConfig)
return contractBackend2
}
func transferTx(t *testing.T, to common.Address, transferAmount int64) *types.Transaction {
t.Logf("Transfering %v to address: %v", transferAmount, to.String())
data := []byte{}
gasPrice := big.NewInt(int64(0))
gasLimit := uint64(21000)
amount := big.NewInt(transferAmount)
nonce := uint64(1)
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
if err != nil {
t.Fatal(err)
}
return signedTX
}
func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, error) {
vote := "6dd7d8ea" // VoteMethod = "0x6dd7d8ea"
action := fmt.Sprintf("%s%s%s", vote, "000000000000000000000000", addr[3:])
data := common.Hex2Bytes(action)
gasPrice := big.NewInt(int64(0))
amountInt := new(big.Int)
amount, ok := amountInt.SetString("60000", 10)
if !ok {
return nil, fmt.Errorf("big int init failed")
}
to := common.HexToAddress(common.MasternodeVotingSMC)
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
if err != nil {
return nil, err
}
return signedTX, nil
}
func UpdateSigner(bc *BlockChain) error {
err := bc.UpdateM1()
return err
}
func GetSnapshotSigner(bc *BlockChain, header *types.Header) (signersList, error) {
engine := bc.Engine().(*XDPoS.XDPoS)
snap, err := engine.GetSnapshot(bc, header)
if err != nil {
return nil, err
}
ms := make(signersList)
for addr := range snap.Signers {
ms[addr.Hex()] = true
}
return ms, nil
}
func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testing.T) masterNodes {
addr := common.HexToAddress(common.MasternodeVotingSMC)
validator, err := contractValidator.NewXDCValidator(addr, backend)
if err != nil {
t.Fatal(err)
}
opts := new(bind.CallOpts)
candidates, err := validator.GetCandidates(opts)
if err != nil {
t.Fatal(err)
}
ms := make(masterNodes)
for _, candidate := range candidates {
v, err := validator.GetCandidateCap(opts, candidate)
if err != nil {
t.Fatal(err)
}
ms[candidate.String()] = *v
}
return ms
}
// V1 consensus engine
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
// Preparation
var err error
// Authorise
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
backend := getCommonBackend(t, chainConfig)
blockchain := backend.GetBlockChain()
blockchain.Client = backend
if err != nil {
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
}
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
currentBlock := blockchain.Genesis()
go func() {
for range core.CheckpointCh {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}
}()
// Insert initial blocks
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
if err != nil {
t.Fatal(err)
}
err = blockchain.InsertBlock(block)
if err != nil {
panic(err)
}
currentBlock = block
}
// Update Signer as there is no previous signer assigned
err = UpdateSigner(blockchain)
if err != nil {
t.Fatal(err)
}
return blockchain, backend, currentBlock, signer, signFn
}
func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte) *types.Block {
currentBlock := startingBlock
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(blockNumber)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
// reset extra
header.Extra = []byte{}
if len(header.Extra) < utils.ExtraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
}
header.Extra = header.Extra[:utils.ExtraVanity]
var masternodes []common.Address
// Place the test's signer address to the last
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
// masternodesFromV1LastEpoch = masternodes
for _, masternode := range masternodes {
header.Extra = append(header.Extra, masternode[:]...)
}
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
// Sign all the things for v1 block use v1 sigHash function
sighash, err := signFn(accounts.Account{Address: signer}, blockchain.Engine().(*XDPoS.XDPoS).SigHash(header).Bytes())
if err != nil {
panic(fmt.Errorf("Error when sign last v1 block hash during test block creation"))
}
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
}
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
if err != nil {
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
}
return block
}
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
if customHeader.Extra == nil {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
}
var difficulty *big.Int
if customHeader.Difficulty == nil {
difficulty = big.NewInt(1)
} else {
difficulty = customHeader.Difficulty
}
// TODO: check if this is needed
if len(txs) != 0 {
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
} else {
customHeader.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
}
header := types.Header{
ParentHash: customHeader.ParentHash,
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
ReceiptHash: customHeader.ReceiptHash,
Root: customHeader.Root,
Coinbase: customHeader.Coinbase,
Difficulty: difficulty,
Number: customHeader.Number,
GasLimit: 1200000000,
Time: big.NewInt(time.Now().Unix()),
Extra: customHeader.Extra,
Validator: customHeader.Validator,
Validators: customHeader.Validators,
Penalties: customHeader.Penalties,
}
var block *types.Block
if len(txs) == 0 {
block = types.NewBlockWithHeader(&header)
} else {
// Prepare Receipt
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
if err != nil {
return nil, fmt.Errorf("%v when get state", err)
}
gp := new(GasPool).AddGas(header.GasLimit)
var gasUsed = new(uint64)
var receipts types.Receipts
for i, tx := range txs {
statedb.Prepare(tx.Hash(), header.Hash(), i)
receipt, _, err, _ := ApplyTransaction(bc.Config(), nil, bc, &header.Coinbase, gp, statedb, nil, &header, tx, gasUsed, vm.Config{})
if err != nil {
return nil, fmt.Errorf("%v when applying transaction", err)
}
receipts = append(receipts, receipt)
}
header.GasUsed = *gasUsed
block = types.NewBlock(&header, txs, nil, receipts)
}
return block, nil
}
// /*
// func proposeTX(t *testing.T) *types.Transaction {
// data := common.Hex2Bytes("012679510000000000000000000000000d3ab14bbad3d99f4203bd7a11acb94882050e7e")
// //data := []byte{}
// fmt.Println("data", string(data[:]))
// gasPrice := big.NewInt(int64(0))
// gasLimit := uint64(22680)
// amountInt := new(big.Int)
// amount, ok := amountInt.SetString("11000000000000000000000000", 10)
// if !ok {
// t.Fatal("big int init failed")
// }
// nonce := uint64(0)
// to := common.HexToAddress("xdc35658f7b2a9e7701e65e7a654659eb1c481d1dc5")
// tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
// signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), acc4Key)
// if err != nil {
// t.Fatal(err)
// }
// return signedTX
// }
// */

View file

@ -1,7 +1,6 @@
package tests
package engine_v2_tests
import (
"fmt"
"math/big"
"reflect"
"testing"
@ -15,7 +14,7 @@ import (
)
func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
blockchain, backend, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, 0)
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, 0)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header())
@ -32,23 +31,22 @@ func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
// Insert one more block to make it above 10, which means now we are on v2 of consensus engine
// Insert block 901
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 901)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(901)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
Coinbase: signer,
}
err := generateSignature(backend, adaptor, header)
header.Extra = generateV2Extra(1, currentBlock, signer, signFn)
block901, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
block901, err := createBlockFromHeader(blockchain, header, nil)
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block901)
err = blockchain.InsertBlock(block901)
assert.Nil(t, err)
addressFromAdaptor, errorAdaptor = adaptor.Author(block901.Header())
if errorAdaptor != nil {
@ -181,14 +179,16 @@ func TestAdaptorGetMasternodesV2(t *testing.T) {
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil)
// block 901 is the first v2 block, and is treated as epoch switch block
blockchain.InsertBlock(currentBlock)
err := blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
masternodes1 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
assert.Equal(t, 4, len(masternodes1))
assert.Equal(t, 5, len(masternodes1))
masternodes1ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64())
assert.True(t, reflect.DeepEqual(masternodes1, masternodes1ByNumber), "at block number", blockNum)
for blockNum = 902; blockNum < 915; blockNum++ {
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(currentBlock)
err = blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
masternodes2 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
assert.True(t, reflect.DeepEqual(masternodes1, masternodes2), "at block number", blockNum)
masternodes2ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64())
@ -210,7 +210,8 @@ func TestGetCurrentEpochSwitchBlock(t *testing.T) {
blockNum := 901
blockCoinBase := "0x111000000000000000000000000000000123"
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(currentBlock)
err = blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
currentCheckpointNumber, epochNum, err = adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number())
assert.Nil(t, err)
assert.Equal(t, uint64(901), currentCheckpointNumber)
@ -219,7 +220,8 @@ func TestGetCurrentEpochSwitchBlock(t *testing.T) {
for blockNum = 902; blockNum < 915; blockNum++ {
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(currentBlock)
err = blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
currentCheckpointNumber, epochNum, err := adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number())
assert.Nil(t, err)
assert.Equal(t, uint64(901), currentCheckpointNumber)
@ -243,13 +245,14 @@ func TestGetParentBlock(t *testing.T) {
blockNum := 901
blockCoinBase := "0x111000000000000000000000000000000123"
block901 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block900, blockNum, 1, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block901)
err = blockchain.InsertBlock(block901)
assert.Nil(t, err)
// let's inject another one, but the highestedQC has not been updated, so it shall still point to 900
blockNum = 902
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block901, blockNum, 1, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block902)
err = blockchain.InsertBlock(block902)
assert.Nil(t, err)
block = adaptor.FindParentBlockToAssign(blockchain, block902)
assert.Equal(t, block900.Hash(), block.Hash())

View file

@ -1,77 +1,15 @@
package tests
package engine_v2_tests
import (
"math/big"
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestIsAuthorisedMNForConsensusV1(t *testing.T) {
/*
V1 consensus engine
*/
blockchain, _, parentBlock, _ := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
// Insert first Block 449
t.Logf("Inserting block with propose at 449...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
tx, err := voteTX(37117, 0, acc1Addr.String())
if err != nil {
t.Fatal(err)
}
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(449)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
blockchain.InsertBlock(block449)
if err != nil {
t.Fatal(err)
}
parentBlock = block449
// At block 449, we should not update signerList. we need to update it till block 450 gap block.
// Acc3 is the default account that is on the signerList
engine := blockchain.Engine().(*XDPoS.XDPoS)
isAuthorisedMN := engine.IsAuthorisedAddress(blockchain, block449.Header(), acc3Addr)
assert.True(t, isAuthorisedMN)
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block449.Header(), acc1Addr)
assert.False(t, isAuthorisedMN)
// Now, let's mine another block to trigger the GAP block signerList update
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(450)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(block450CoinbaseAddress),
}
block450, err := createBlockFromHeader(blockchain, header, nil)
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block450)
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc3Addr)
assert.False(t, isAuthorisedMN)
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc1Addr)
assert.True(t, isAuthorisedMN)
}
func TestIsAuthorisedMNForConsensusV2(t *testing.T) {
// we skip test for v1 since it's hard to make a real genesis block
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, 0)
@ -79,8 +17,8 @@ func TestIsAuthorisedMNForConsensusV2(t *testing.T) {
blockNum := 901
blockCoinBase := "0x111000000000000000000000000000000123"
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(currentBlock)
err := blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
// As long as the address is in the master node list, they are all valid
isAuthorisedMN := adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
assert.True(t, isAuthorisedMN)
@ -100,8 +38,8 @@ func TestIsYourTurnConsensusV2(t *testing.T) {
blockNum := 901
blockCoinBase := "0x111000000000000000000000000000000123"
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(currentBlock)
err := blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
// Less then Mine Period
isYourTurn, err := adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
assert.Nil(t, err)
@ -124,7 +62,8 @@ func TestIsYourTurnConsensusV2(t *testing.T) {
// We continue to grow the chain which will increase the round number
blockNum = 902
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(currentBlock)
err = blockchain.InsertBlock(currentBlock)
assert.Nil(t, err)
time.Sleep(time.Duration(minePeriod) * time.Second)
adaptor.EngineV2.SetNewRoundFaker(blockchain, 2, false)

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"bytes"
@ -10,7 +10,6 @@ import (
"math/big"
"math/rand"
"os"
"strings"
"testing"
"time"
@ -50,22 +49,6 @@ var (
chainID = int64(1337)
)
func debugMessage(backend *backends.SimulatedBackend, signers signersList, t *testing.T) {
ms := GetCandidateFromCurrentSmartContract(backend, t)
fmt.Println("=== current smart contract")
for nodeAddr, cap := range ms {
if !strings.Contains(nodeAddr, "000000000000000000000000000000000000") { //remove defaults
fmt.Println(nodeAddr, cap)
}
}
fmt.Println("=== this block signer list")
for signer := range signers {
if !strings.Contains(signer, "000000000000000000000000000000000000") { //remove defaults
fmt.Println(signer)
}
}
}
func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte {
signer, signFn, err := getSignerAndSignFn(pk)
if err != nil {
@ -110,6 +93,27 @@ func getSignerAndSignFn(pk *ecdsa.PrivateKey) (common.Address, func(account acco
return a1.Address, ks.SignHash, nil
}
func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, error) {
vote := "6dd7d8ea" // VoteMethod = "0x6dd7d8ea"
action := fmt.Sprintf("%s%s%s", vote, "000000000000000000000000", addr[3:])
data := common.Hex2Bytes(action)
gasPrice := big.NewInt(int64(0))
amountInt := new(big.Int)
amount, ok := amountInt.SetString("60000", 10)
if !ok {
return nil, fmt.Errorf("big int init failed")
}
to := common.HexToAddress(common.MasternodeVotingSMC)
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
if err != nil {
return nil, err
}
return signedTX, nil
}
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
// initial helper backend
@ -191,50 +195,13 @@ func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.S
}, 10000000, chainConfig)
return contractBackend2
}
func transferTx(t *testing.T, to common.Address, transferAmount int64) *types.Transaction {
t.Logf("Transfering %v to address: %v", transferAmount, to.String())
data := []byte{}
gasPrice := big.NewInt(int64(0))
gasLimit := uint64(21000)
amount := big.NewInt(transferAmount)
nonce := uint64(1)
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
if err != nil {
t.Fatal(err)
}
return signedTX
}
func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, error) {
vote := "6dd7d8ea" // VoteMethod = "0x6dd7d8ea"
action := fmt.Sprintf("%s%s%s", vote, "000000000000000000000000", addr[3:])
data := common.Hex2Bytes(action)
gasPrice := big.NewInt(int64(0))
amountInt := new(big.Int)
amount, ok := amountInt.SetString("60000", 10)
if !ok {
return nil, fmt.Errorf("big int init failed")
}
to := common.HexToAddress(common.MasternodeVotingSMC)
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
if err != nil {
return nil, err
}
return signedTX, nil
}
func signingTxWithSignerFn(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) {
func signingTxWithKey(header *types.Header, nonce uint64, privateKey *ecdsa.PrivateKey) (*types.Transaction, error) {
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
s := types.NewEIP155Signer(big.NewInt(chainID))
h := s.Hash(tx)
sig, err := signFn(accounts.Account{Address: signer}, h[:])
sig, err := crypto.Sign(h[:], privateKey)
if err != nil {
return nil, err
}
@ -245,11 +212,11 @@ func signingTxWithSignerFn(header *types.Header, nonce uint64, signer common.Add
return signedTx, nil
}
func signingTxWithKey(header *types.Header, nonce uint64, privateKey *ecdsa.PrivateKey) (*types.Transaction, error) {
func signingTxWithSignerFn(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) {
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
s := types.NewEIP155Signer(big.NewInt(chainID))
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], privateKey)
sig, err := signFn(accounts.Account{Address: signer}, h[:])
if err != nil {
return nil, err
}
@ -305,57 +272,6 @@ func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testi
return ms
}
// V1 consensus engine
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address) {
// Preparation
var err error
// Authorise
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
backend := getCommonBackend(t, chainConfig)
blockchain := backend.GetBlockChain()
blockchain.Client = backend
if err != nil {
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
}
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
currentBlock := blockchain.Genesis()
go func() {
for range core.CheckpointCh {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}
}()
// Insert initial blocks
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
}
block, err := createBlockFromHeader(blockchain, header, nil)
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block)
currentBlock = block
}
// Update Signer as there is no previous signer assigned
err = UpdateSigner(blockchain)
if err != nil {
t.Fatal(err)
}
return blockchain, backend, currentBlock, signer
}
// V2 concensus engine
func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig, numOfForkedBlocks int) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error), *types.Block) {
// Preparation
@ -409,7 +325,10 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
forkedBlock := CreateBlock(blockchain, chainConfig, currentForkBlock, i, forkedBlockRoundNumber, forkedBlockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(forkedBlock)
err = blockchain.InsertBlock(forkedBlock)
if err != nil {
panic(err)
}
currentForkBlock = forkedBlock
}
@ -493,51 +412,14 @@ func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, starti
var header *types.Header
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 1 { // Build engine v2 compatible extra data field
var extraField utils.ExtraFields_v2
var round utils.Round
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
if err != nil {
round = utils.Round(0)
} else {
round = extraField.Round
}
extraInBytes := generateV2Extra(roundNumber, currentBlock, signer, signFn)
proposedBlockInfo := &utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: round,
Number: currentBlock.Number(),
}
// Genrate QC
signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes())
if err != nil {
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
}
// Sign from acc 1, 2, 3
acc1SignedHash := SignHashByPK(acc1Key, utils.VoteSigHash(proposedBlockInfo).Bytes())
acc2SignedHash := SignHashByPK(acc2Key, utils.VoteSigHash(proposedBlockInfo).Bytes())
acc3SignedHash := SignHashByPK(acc3Key, utils.VoteSigHash(proposedBlockInfo).Bytes())
var signatures []utils.Signature
signatures = append(signatures, signedHash, acc1SignedHash, acc2SignedHash, acc3SignedHash)
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: proposedBlockInfo,
Signatures: signatures,
}
extra := utils.ExtraFields_v2{
Round: utils.Round(roundNumber),
QuorumCert: quorumCert,
}
extraInBytes, err := extra.EncodeToBytes()
if err != nil {
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
}
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(blockNumber)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinBase),
Extra: extraInBytes,
Validator: signedHash,
}
if int64(blockNumber) == (chainConfig.XDPoS.V2.SwitchBlock.Int64() + 1) { // This is the first v2 block, we need to copy the last v1 epoch master node list and inject into v2 validators
// Get last master node list from last v1 block
@ -576,7 +458,8 @@ func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, starti
}
header.Extra = header.Extra[:utils.ExtraVanity]
var masternodes []common.Address
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, signer)
// Place the test's signer address to the last
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
// masternodesFromV1LastEpoch = masternodes
for _, masternode := range masternodes {
header.Extra = append(header.Extra, masternode[:]...)
@ -591,28 +474,14 @@ func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, starti
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
}
}
block, err := createBlockFromHeader(blockchain, header, nil)
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
if err != nil {
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
}
return block
}
func generateSignature(backend *backends.SimulatedBackend, adaptor *XDPoS.XDPoS, header *types.Header) error {
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
if err != nil {
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
}
signature, err := signFn(accounts.Account{Address: signer}, adaptor.SigHash(header).Bytes())
if err != nil {
return err
}
header.Validator = signature
return nil
}
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction) (*types.Block, error) {
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
if customHeader.Extra == nil {
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
@ -624,7 +493,6 @@ func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*ty
difficulty = customHeader.Difficulty
}
// TODO: check if this is needed
if len(txs) != 0 {
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
} else {
@ -649,6 +517,11 @@ func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*ty
}
var block *types.Block
if len(txs) == 0 {
// Sign all the things and seal it
signerAddress, signerFunction := findSignerAndSignFn(bc, &header, signer, signFn, config)
header.Coinbase = signerAddress
sealHeader(bc, &header, signerAddress, signerFunction)
block = types.NewBlockWithHeader(&header)
} else {
// Prepare Receipt
@ -671,35 +544,17 @@ func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*ty
header.GasUsed = *gasUsed
// Sign all the things and seal it
signerAddress, signerFunction := findSignerAndSignFn(bc, &header, signer, signFn, config)
header.Coinbase = signerAddress
sealHeader(bc, &header, signerAddress, signerFunction)
block = types.NewBlock(&header, txs, nil, receipts)
}
return block, nil
}
// /*
// func proposeTX(t *testing.T) *types.Transaction {
// data := common.Hex2Bytes("012679510000000000000000000000000d3ab14bbad3d99f4203bd7a11acb94882050e7e")
// //data := []byte{}
// fmt.Println("data", string(data[:]))
// gasPrice := big.NewInt(int64(0))
// gasLimit := uint64(22680)
// amountInt := new(big.Int)
// amount, ok := amountInt.SetString("11000000000000000000000000", 10)
// if !ok {
// t.Fatal("big int init failed")
// }
// nonce := uint64(0)
// to := common.HexToAddress("xdc35658f7b2a9e7701e65e7a654659eb1c481d1dc5")
// tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
// signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), acc4Key)
// if err != nil {
// t.Fatal(err)
// }
// return signedTX
// }
// */
// Get masternodes address from checkpoint Header. Only used for v1 last block
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
@ -708,3 +563,99 @@ func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.A
}
return masternodes
}
func findSignerAndSignFn(bc *BlockChain, header *types.Header, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
addressToSign := signer
addressedSignFn := signFn
// If v2 block, we need to use extra data's round to find who is creating the block in order to verify the validator
if header.Number.Cmp(config.XDPoS.V2.SwitchBlock) > 0 {
var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
if err != nil {
panic(fmt.Errorf("fail to seal header for v2 block"))
}
round := decodedExtraField.Round
masterNodes := getMasternodesList(signer)
index := uint64(round) % config.XDPoS.Epoch % uint64(len(masterNodes))
// index 0 to 2 are acc1Addr, acc2Addr, acc3Addr
addressToSign = masterNodes[index]
if index == 0 {
_, signFn, err = getSignerAndSignFn(acc1Key)
} else if index == 1 {
_, signFn, err = getSignerAndSignFn(acc2Key)
} else if index == 2 {
_, signFn, err = getSignerAndSignFn(acc3Key)
} else if index == 3 {
// Skip signing anything for voterAddress to simulate penalty
return signer, signFn
}
addressedSignFn = signFn
if err != nil {
panic(fmt.Errorf("Error trying to use one of the pre-defined private key to sign"))
}
}
return addressToSign, addressedSignFn
}
func sealHeader(bc *BlockChain, header *types.Header, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) {
// Sign all the things and seal it
signedBlockHeader := bc.Engine().(*XDPoS.XDPoS).SigHash(header)
signature, err := signFn(accounts.Account{Address: signer}, signedBlockHeader.Bytes())
if err != nil {
panic(err)
}
header.Validator = signature
}
func getMasternodesList(signer common.Address) []common.Address {
var masternodes []common.Address
// Place the test's signer address to the last
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
return masternodes
}
func generateV2Extra(roundNumber int64, currentBlock *types.Block, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) []byte {
var extraField utils.ExtraFields_v2
var round utils.Round
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
if err != nil {
round = utils.Round(0)
} else {
round = extraField.Round
}
proposedBlockInfo := &utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: round,
Number: currentBlock.Number(),
}
signedHash, err := signFn(accounts.Account{Address: signer}, utils.VoteSigHash(proposedBlockInfo).Bytes())
if err != nil {
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
}
// Sign from acc 1, 2, 3
acc1SignedHash := SignHashByPK(acc1Key, utils.VoteSigHash(proposedBlockInfo).Bytes())
acc2SignedHash := SignHashByPK(acc2Key, utils.VoteSigHash(proposedBlockInfo).Bytes())
acc3SignedHash := SignHashByPK(acc3Key, utils.VoteSigHash(proposedBlockInfo).Bytes())
var signatures []utils.Signature
signatures = append(signatures, acc1SignedHash, acc2SignedHash, acc3SignedHash, signedHash)
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: proposedBlockInfo,
Signatures: signatures,
}
extra := utils.ExtraFields_v2{
Round: utils.Round(roundNumber),
QuorumCert: quorumCert,
}
extraInBytes, err := extra.EncodeToBytes()
if err != nil {
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
}
return extraInBytes
}

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"math/big"
@ -82,18 +82,19 @@ func TestInitialOtherV2Block(t *testing.T) {
assert.Nil(t, err)
header := &types.Header{
Root: common.HexToHash("ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"),
Root: common.HexToHash("35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"),
Number: big.NewInt(int64(911)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress("0x111000000000000000000000000000000123"),
}
header.Extra = extraBytes
block, err := createBlockFromHeader(blockchain, header, nil)
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block)
err = blockchain.InsertBlock(block)
assert.Nil(t, err)
// Initialise
err = adaptor.EngineV2.Initial(blockchain, block.Header())
assert.Nil(t, err)

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"fmt"
@ -7,6 +7,7 @@ import (
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types"
@ -16,7 +17,7 @@ import (
func TestYourTurnInitialV2(t *testing.T) {
config := params.TestXDPoSMockChainConfig
blockchain, _, parentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)-1, config, 0)
blockchain, _, parentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)-1, config, 0)
minePeriod := config.XDPoS.V2.MinePeriod
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
@ -32,11 +33,12 @@ func TestYourTurnInitialV2(t *testing.T) {
Coinbase: common.HexToAddress(blockCoinbaseA),
Extra: common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400"),
}
block900, err := createBlockFromHeader(blockchain, header, nil)
block900, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config)
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block900)
err = blockchain.InsertBlock(block900)
assert.Nil(t, err)
time.Sleep(time.Duration(minePeriod) * time.Second)
// YourTurn is called before mine first v2 block
@ -60,7 +62,7 @@ func TestYourTurnInitialV2(t *testing.T) {
func TestUpdateMasterNodes(t *testing.T) {
config := params.TestXDPoSMockChainConfig
blockchain, backend, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config, 0)
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config, 0)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
x := adaptor.EngineV2
snap, err := x.GetSnapshot(blockchain, currentBlock.Header())
@ -84,12 +86,10 @@ func TestUpdateMasterNodes(t *testing.T) {
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
// insert header validator
err = generateSignature(backend, adaptor, header)
if err != nil {
t.Fatal(err)
}
parentBlock, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
header.Extra = generateV2Extra(450, currentBlock, signer, signFn)
parentBlock, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, config)
assert.Nil(t, err)
err = blockchain.InsertBlock(parentBlock)
assert.Nil(t, err)
@ -100,22 +100,21 @@ func TestUpdateMasterNodes(t *testing.T) {
for i := 1351; i <= 1800; i++ {
blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i)
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbase),
}
err = generateSignature(backend, adaptor, header)
header.Extra = generateV2Extra(int64(i), currentBlock, signer, signFn)
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config)
if err != nil {
t.Fatal(err)
}
block, err := createBlockFromHeader(blockchain, header, nil)
if err != nil {
t.Fatal(err)
}
blockchain.InsertBlock(block)
err = blockchain.InsertBlock(block)
assert.Nil(t, err)
parentBlock = block
}
@ -128,20 +127,32 @@ func TestUpdateMasterNodes(t *testing.T) {
func TestPrepare(t *testing.T) {
config := params.TestXDPoSMockChainConfig
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, 0)
blockchain, _, currentBlock, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, 0)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))
_, err := adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))
assert.Nil(t, err)
tstamp := time.Now().Unix()
header901 := &types.Header{
header901WithoutCoinbase := &types.Header{
ParentHash: currentBlock.Hash(),
Number: big.NewInt(int64(901)),
GasLimit: params.TargetGasLimit,
Time: big.NewInt(tstamp),
}
err := adaptor.Prepare(blockchain, header901)
err = adaptor.Prepare(blockchain, header901WithoutCoinbase)
assert.Equal(t, consensus.ErrCoinbaseMismatch, err)
header901 := &types.Header{
ParentHash: currentBlock.Hash(),
Number: big.NewInt(int64(901)),
GasLimit: params.TargetGasLimit,
Time: big.NewInt(tstamp),
Coinbase: signer,
}
err = adaptor.Prepare(blockchain, header901)
assert.Nil(t, err)
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())

View file

@ -1,8 +1,7 @@
package tests
package engine_v2_tests
import (
"math/big"
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
@ -16,7 +15,7 @@ import (
func TestHookPenaltyV2Mining(t *testing.T) {
config := params.TestXDPoSMockChainConfig
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*7, config, 0)
blockchain, _, _, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*7, config, 0)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
@ -26,13 +25,19 @@ func TestHookPenaltyV2Mining(t *testing.T) {
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
assert.Nil(t, err)
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
assert.Equal(t, 4, len(masternodes))
assert.Equal(t, 5, len(masternodes))
header6300 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 7)
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*7)), header6300.ParentHash, masternodes)
assert.Nil(t, err)
// miner (coinbase) is not in penalty. all others are in penalty
assert.Equal(t, 3, len(penalty))
assert.True(t, reflect.DeepEqual([]common.Address{header901.Coinbase}, common.RemoveItemFromArray(masternodes, penalty)))
// only 1 signer address not in the masternode list
assert.Equal(t, 1, len(penalty))
contains := false
for _, mn := range common.RemoveItemFromArray(masternodes, penalty) {
if mn == header901.Coinbase {
contains = true
}
}
assert.True(t, contains)
// set adaptor round/qc to that of 6299
err = utils.DecodeBytesExtraFields(header6300.Extra, &extraField)
assert.Nil(t, err)
@ -43,12 +48,13 @@ func TestHookPenaltyV2Mining(t *testing.T) {
Number: header6300.Number,
GasLimit: params.TargetGasLimit,
Time: header6300.Time,
Coinbase: signer,
}
err = adaptor.Prepare(blockchain, headerMining)
assert.Nil(t, err)
assert.Equal(t, 3, len(headerMining.Penalties)/common.AddressLength)
assert.Equal(t, 1, len(headerMining.Penalties)/common.AddressLength)
// 20 candidates (set by PrepareXDCTestBlockChainForV2Engine) - 3 penalty = 17
assert.Equal(t, 17, len(headerMining.Validators)/common.AddressLength)
assert.Equal(t, 19, len(headerMining.Validators)/common.AddressLength)
}
func TestHookPenaltyV2Comeback(t *testing.T) {
@ -63,12 +69,12 @@ func TestHookPenaltyV2Comeback(t *testing.T) {
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
assert.Nil(t, err)
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
assert.Equal(t, 4, len(masternodes))
assert.Equal(t, 5, len(masternodes))
header6300 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 7)
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*7)), header6300.ParentHash, masternodes)
assert.Nil(t, err)
// miner (coinbase) is in comeback. so all addresses are in penalty
assert.Equal(t, 4, len(penalty))
assert.Equal(t, 2, len(penalty))
header6285 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*7 - common.MergeSignRange)
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 3
tx, err := signingTxWithSignerFn(header6285, 0, signer, signFn)
@ -76,7 +82,7 @@ func TestHookPenaltyV2Comeback(t *testing.T) {
adaptor.CacheSigningTxs(header6285.Hash(), []*types.Transaction{tx})
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*7)), header6300.ParentHash, masternodes)
assert.Nil(t, err)
assert.Equal(t, 3, len(penalty))
assert.Equal(t, 1, len(penalty))
}
func TestHookPenaltyV2Jump(t *testing.T) {
@ -92,11 +98,11 @@ func TestHookPenaltyV2Jump(t *testing.T) {
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
assert.Nil(t, err)
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
assert.Equal(t, 4, len(masternodes))
assert.Equal(t, 5, len(masternodes))
header6285 := blockchain.GetHeaderByNumber(uint64(end))
adaptor.EngineV2.SetNewRoundFaker(blockchain, utils.Round(config.XDPoS.Epoch*7), false)
// round 6285-6300 miss blocks, penalty should work as usual
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header6285.Number, header6285.ParentHash, masternodes)
assert.Nil(t, err)
assert.Equal(t, 4, len(penalty))
assert.Equal(t, 2, len(penalty))
}

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"fmt"
@ -45,7 +45,8 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) {
blockNum := 902
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block902)
err = blockchain.InsertBlock(block902)
assert.Nil(t, err)
err = engineV2.ProposedBlockHandler(blockchain, block902.Header())
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)
@ -62,7 +63,8 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) {
blockNum = 903
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
block903 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block902, blockNum, 3, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block903)
err = blockchain.InsertBlock(block903)
assert.Nil(t, err)
err = engineV2.ProposedBlockHandler(blockchain, block903.Header())
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)
@ -80,7 +82,8 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) {
blockNum = 904
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
block904 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block903, blockNum, 4, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block904)
err = blockchain.InsertBlock(block904)
assert.Nil(t, err)
err = engineV2.ProposedBlockHandler(blockchain, block904.Header())
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)
@ -131,7 +134,8 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) {
blockNum := 906
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
block906 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 7, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block906)
err = blockchain.InsertBlock(block906)
assert.Nil(t, err)
err = engineV2.ProposedBlockHandler(blockchain, block906.Header())
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)
@ -152,7 +156,8 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) {
blockNum = 907
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
block907 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block906, blockNum, 8, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block907)
err = blockchain.InsertBlock(block907)
assert.Nil(t, err)
err = engineV2.ProposedBlockHandler(blockchain, block907.Header())
if err != nil {
t.Fatal("Fail propose proposedBlock handler", err)

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"encoding/json"

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"math/big"

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"fmt"

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"fmt"
@ -27,7 +27,8 @@ func TestShouldVerifyBlockInfo(t *testing.T) {
blockNum := 902
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil)
blockchain.InsertBlock(block902)
err = blockchain.InsertBlock(block902)
assert.Nil(t, err)
blockInfo = &utils.BlockInfo{
Hash: block902.Hash(),

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"encoding/json"
@ -34,7 +34,8 @@ func TestShouldVerifyBlock(t *testing.T) {
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
// Happy path
err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(901).Header(), true)
happyPathHeader := blockchain.GetBlockByNumber(901).Header()
err = adaptor.VerifyHeader(blockchain, happyPathHeader, true)
assert.Nil(t, err)
// Unhappy path
@ -142,6 +143,22 @@ func TestShouldVerifyBlock(t *testing.T) {
err = adaptor.VerifyHeader(blockchain, invalidPenaltiesExistBlock, true)
assert.Equal(t, utils.ErrPenaltyListDoesNotMatch, err)
// Not valid validator
coinbaseValidatorMismatchBlock := blockchain.GetBlockByNumber(902).Header()
notQualifiedSigner, notQualifiedSignFn, err := getSignerAndSignFn(voterKey)
assert.Nil(t, err)
sealHeader(blockchain, coinbaseValidatorMismatchBlock, notQualifiedSigner, notQualifiedSignFn)
err = adaptor.VerifyHeader(blockchain, coinbaseValidatorMismatchBlock, true)
assert.Equal(t, utils.ErrCoinbaseAndValidatorMismatch, err)
// Make the validators not legit by adding something to the penalty
validatorsNotLegit := blockchain.GetBlockByNumber(901).Header()
penalties := []common.Address{acc1Addr}
for _, v := range penalties {
validatorsNotLegit.Penalties = append(validatorsNotLegit.Penalties, v[:]...)
}
err = adaptor.VerifyHeader(blockchain, validatorsNotLegit, true)
assert.Equal(t, utils.ErrValidatorsNotLegit, err)
}
func TestShouldFailIfNotEnoughQCSignatures(t *testing.T) {

View file

@ -1,4 +1,4 @@
package tests
package engine_v2_tests
import (
"fmt"
@ -354,7 +354,8 @@ func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) {
assert.Equal(t, utils.Round(6), currentRound)
// Now, inject the block into the chain
blockchain.InsertBlock(block)
err = blockchain.InsertBlock(block)
assert.Nil(t, err)
voteMsg = &utils.Vote{
ProposedBlockInfo: blockInfo,