From d55229677d29b2184bf93cbf357c6a8a5a8fe515 Mon Sep 17 00:00:00 2001 From: Jerome Date: Sun, 20 Mar 2022 21:14:35 +1100 Subject: [PATCH] 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 --- consensus/XDPoS/engines/engine_v2/engine.go | 704 +----------------- .../XDPoS/engines/engine_v2/epochSwitch.go | 99 +++ consensus/XDPoS/engines/engine_v2/snapshot.go | 32 + consensus/XDPoS/engines/engine_v2/timeout.go | 229 ++++++ consensus/XDPoS/engines/engine_v2/utils.go | 57 ++ .../XDPoS/engines/engine_v2/verifyHeader.go | 188 +++++ consensus/XDPoS/engines/engine_v2/vote.go | 191 +++++ consensus/XDPoS/utils/errors.go | 15 +- consensus/errors.go | 2 + .../tests/engine_v1_tests/authorised_test.go | 75 ++ .../block_signer_test.go | 108 +-- .../blockchain_race_condition_test.go | 20 +- consensus/tests/engine_v1_tests/helper.go | 420 +++++++++++ .../{ => engine_v2_tests}/adaptor_test.go | 41 +- .../authorised_masternode_test.go | 75 +- .../helper.go} | 331 ++++---- .../{ => engine_v2_tests}/initial_test.go | 9 +- .../tests/{ => engine_v2_tests}/mine_test.go | 57 +- .../{ => engine_v2_tests}/penalty_test.go | 34 +- .../proposed_block_test.go | 17 +- .../{ => engine_v2_tests}/reward_test.go | 2 +- .../{ => engine_v2_tests}/sync_info_test.go | 2 +- .../{ => engine_v2_tests}/timeout_test.go | 2 +- .../verify_blockinfo_test.go | 5 +- .../verify_header_test.go | 21 +- .../tests/{ => engine_v2_tests}/vote_test.go | 5 +- 26 files changed, 1657 insertions(+), 1084 deletions(-) create mode 100644 consensus/XDPoS/engines/engine_v2/epochSwitch.go create mode 100644 consensus/XDPoS/engines/engine_v2/timeout.go create mode 100644 consensus/XDPoS/engines/engine_v2/verifyHeader.go create mode 100644 consensus/XDPoS/engines/engine_v2/vote.go create mode 100644 consensus/tests/engine_v1_tests/authorised_test.go rename consensus/tests/{ => engine_v1_tests}/block_signer_test.go (87%) rename consensus/tests/{ => engine_v1_tests}/blockchain_race_condition_test.go (90%) create mode 100644 consensus/tests/engine_v1_tests/helper.go rename consensus/tests/{ => engine_v2_tests}/adaptor_test.go (92%) rename consensus/tests/{ => engine_v2_tests}/authorised_masternode_test.go (60%) rename consensus/tests/{test_helper.go => engine_v2_tests/helper.go} (78%) rename consensus/tests/{ => engine_v2_tests}/initial_test.go (93%) rename consensus/tests/{ => engine_v2_tests}/mine_test.go (77%) rename consensus/tests/{ => engine_v2_tests}/penalty_test.go (84%) rename consensus/tests/{ => engine_v2_tests}/proposed_block_test.go (97%) rename consensus/tests/{ => engine_v2_tests}/reward_test.go (99%) rename consensus/tests/{ => engine_v2_tests}/sync_info_test.go (99%) rename consensus/tests/{ => engine_v2_tests}/timeout_test.go (99%) rename consensus/tests/{ => engine_v2_tests}/verify_blockinfo_test.go (95%) rename consensus/tests/{ => engine_v2_tests}/verify_header_test.go (88%) rename consensus/tests/{ => engine_v2_tests}/vote_test.go (99%) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 698920883c..75c29b802a 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -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 diff --git a/consensus/XDPoS/engines/engine_v2/epochSwitch.go b/consensus/XDPoS/engines/engine_v2/epochSwitch.go new file mode 100644 index 0000000000..eb16de6bd5 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/epochSwitch.go @@ -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 +} diff --git a/consensus/XDPoS/engines/engine_v2/snapshot.go b/consensus/XDPoS/engines/engine_v2/snapshot.go index 7ce1e72510..60b145c479 100644 --- a/consensus/XDPoS/engines/engine_v2/snapshot.go +++ b/consensus/XDPoS/engines/engine_v2/snapshot.go @@ -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 +} diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go new file mode 100644 index 0000000000..bf5c7c2cd6 --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -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 +} diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index a6105b72e5..1efbf66c67 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -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 +} diff --git a/consensus/XDPoS/engines/engine_v2/verifyHeader.go b/consensus/XDPoS/engines/engine_v2/verifyHeader.go new file mode 100644 index 0000000000..23097c177e --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/verifyHeader.go @@ -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 +} diff --git a/consensus/XDPoS/engines/engine_v2/vote.go b/consensus/XDPoS/engines/engine_v2/vote.go new file mode 100644 index 0000000000..f25a6ff37f --- /dev/null +++ b/consensus/XDPoS/engines/engine_v2/vote.go @@ -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 +} diff --git a/consensus/XDPoS/utils/errors.go b/consensus/XDPoS/utils/errors.go index 34e1f06af4..0aa3d7e70e 100644 --- a/consensus/XDPoS/utils/errors.go +++ b/consensus/XDPoS/utils/errors.go @@ -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") diff --git a/consensus/errors.go b/consensus/errors.go index 77380ea15d..03e3ed50ee 100644 --- a/consensus/errors.go +++ b/consensus/errors.go @@ -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") ) diff --git a/consensus/tests/engine_v1_tests/authorised_test.go b/consensus/tests/engine_v1_tests/authorised_test.go new file mode 100644 index 0000000000..eb19274fa1 --- /dev/null +++ b/consensus/tests/engine_v1_tests/authorised_test.go @@ -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) +} diff --git a/consensus/tests/block_signer_test.go b/consensus/tests/engine_v1_tests/block_signer_test.go similarity index 87% rename from consensus/tests/block_signer_test.go rename to consensus/tests/engine_v1_tests/block_signer_test.go index 6fb2b0659e..df176d8114 100644 --- a/consensus/tests/block_signer_test.go +++ b/consensus/tests/engine_v1_tests/block_signer_test.go @@ -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) } diff --git a/consensus/tests/blockchain_race_condition_test.go b/consensus/tests/engine_v1_tests/blockchain_race_condition_test.go similarity index 90% rename from consensus/tests/blockchain_race_condition_test.go rename to consensus/tests/engine_v1_tests/blockchain_race_condition_test.go index 338d7faa7e..17ea596b71 100644 --- a/consensus/tests/blockchain_race_condition_test.go +++ b/consensus/tests/engine_v1_tests/blockchain_race_condition_test.go @@ -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 { diff --git a/consensus/tests/engine_v1_tests/helper.go b/consensus/tests/engine_v1_tests/helper.go new file mode 100644 index 0000000000..3b777e487b --- /dev/null +++ b/consensus/tests/engine_v1_tests/helper.go @@ -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 +// } +// */ diff --git a/consensus/tests/adaptor_test.go b/consensus/tests/engine_v2_tests/adaptor_test.go similarity index 92% rename from consensus/tests/adaptor_test.go rename to consensus/tests/engine_v2_tests/adaptor_test.go index c5cbcafb2a..51bce6ad35 100644 --- a/consensus/tests/adaptor_test.go +++ b/consensus/tests/engine_v2_tests/adaptor_test.go @@ -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()) diff --git a/consensus/tests/authorised_masternode_test.go b/consensus/tests/engine_v2_tests/authorised_masternode_test.go similarity index 60% rename from consensus/tests/authorised_masternode_test.go rename to consensus/tests/engine_v2_tests/authorised_masternode_test.go index ae046013c7..99ac58b0fd 100644 --- a/consensus/tests/authorised_masternode_test.go +++ b/consensus/tests/engine_v2_tests/authorised_masternode_test.go @@ -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) diff --git a/consensus/tests/test_helper.go b/consensus/tests/engine_v2_tests/helper.go similarity index 78% rename from consensus/tests/test_helper.go rename to consensus/tests/engine_v2_tests/helper.go index 0eada0fa19..adef49848c 100644 --- a/consensus/tests/test_helper.go +++ b/consensus/tests/engine_v2_tests/helper.go @@ -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 +} diff --git a/consensus/tests/initial_test.go b/consensus/tests/engine_v2_tests/initial_test.go similarity index 93% rename from consensus/tests/initial_test.go rename to consensus/tests/engine_v2_tests/initial_test.go index 9e114d3fdd..182a3b201c 100644 --- a/consensus/tests/initial_test.go +++ b/consensus/tests/engine_v2_tests/initial_test.go @@ -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) diff --git a/consensus/tests/mine_test.go b/consensus/tests/engine_v2_tests/mine_test.go similarity index 77% rename from consensus/tests/mine_test.go rename to consensus/tests/engine_v2_tests/mine_test.go index bdb9e4c19b..17080d65c5 100644 --- a/consensus/tests/mine_test.go +++ b/consensus/tests/engine_v2_tests/mine_test.go @@ -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()) diff --git a/consensus/tests/penalty_test.go b/consensus/tests/engine_v2_tests/penalty_test.go similarity index 84% rename from consensus/tests/penalty_test.go rename to consensus/tests/engine_v2_tests/penalty_test.go index 4c0cecba16..ef6bb7f202 100644 --- a/consensus/tests/penalty_test.go +++ b/consensus/tests/engine_v2_tests/penalty_test.go @@ -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)) } diff --git a/consensus/tests/proposed_block_test.go b/consensus/tests/engine_v2_tests/proposed_block_test.go similarity index 97% rename from consensus/tests/proposed_block_test.go rename to consensus/tests/engine_v2_tests/proposed_block_test.go index a723983c29..8c765f2959 100644 --- a/consensus/tests/proposed_block_test.go +++ b/consensus/tests/engine_v2_tests/proposed_block_test.go @@ -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) diff --git a/consensus/tests/reward_test.go b/consensus/tests/engine_v2_tests/reward_test.go similarity index 99% rename from consensus/tests/reward_test.go rename to consensus/tests/engine_v2_tests/reward_test.go index 34a7165539..b7221cf0af 100644 --- a/consensus/tests/reward_test.go +++ b/consensus/tests/engine_v2_tests/reward_test.go @@ -1,4 +1,4 @@ -package tests +package engine_v2_tests import ( "encoding/json" diff --git a/consensus/tests/sync_info_test.go b/consensus/tests/engine_v2_tests/sync_info_test.go similarity index 99% rename from consensus/tests/sync_info_test.go rename to consensus/tests/engine_v2_tests/sync_info_test.go index e2cebb1c5f..7f5e69fc76 100644 --- a/consensus/tests/sync_info_test.go +++ b/consensus/tests/engine_v2_tests/sync_info_test.go @@ -1,4 +1,4 @@ -package tests +package engine_v2_tests import ( "math/big" diff --git a/consensus/tests/timeout_test.go b/consensus/tests/engine_v2_tests/timeout_test.go similarity index 99% rename from consensus/tests/timeout_test.go rename to consensus/tests/engine_v2_tests/timeout_test.go index 6eca5f723a..1b8920c041 100644 --- a/consensus/tests/timeout_test.go +++ b/consensus/tests/engine_v2_tests/timeout_test.go @@ -1,4 +1,4 @@ -package tests +package engine_v2_tests import ( "fmt" diff --git a/consensus/tests/verify_blockinfo_test.go b/consensus/tests/engine_v2_tests/verify_blockinfo_test.go similarity index 95% rename from consensus/tests/verify_blockinfo_test.go rename to consensus/tests/engine_v2_tests/verify_blockinfo_test.go index 9812172e25..e3f7285440 100644 --- a/consensus/tests/verify_blockinfo_test.go +++ b/consensus/tests/engine_v2_tests/verify_blockinfo_test.go @@ -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(), diff --git a/consensus/tests/verify_header_test.go b/consensus/tests/engine_v2_tests/verify_header_test.go similarity index 88% rename from consensus/tests/verify_header_test.go rename to consensus/tests/engine_v2_tests/verify_header_test.go index c9132532a3..7ed44dd5c2 100644 --- a/consensus/tests/verify_header_test.go +++ b/consensus/tests/engine_v2_tests/verify_header_test.go @@ -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) { diff --git a/consensus/tests/vote_test.go b/consensus/tests/engine_v2_tests/vote_test.go similarity index 99% rename from consensus/tests/vote_test.go rename to consensus/tests/engine_v2_tests/vote_test.go index 77de5f0e4b..da209f5162 100644 --- a/consensus/tests/vote_test.go +++ b/consensus/tests/engine_v2_tests/vote_test.go @@ -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,