From e063f67f34b65dc16afe52055700fee5c8220f13 Mon Sep 17 00:00:00 2001 From: Jerome Date: Fri, 21 Jan 2022 21:10:44 +1100 Subject: [PATCH] implement verify functions (#43) * implement verify functions * Use go routine to call verifyMsgSignature * verify timeout msg shall use round from the msg * add comment for verifyQC --- consensus/XDPoS/engines/engine_v2/engine.go | 141 ++++++++++++++------ consensus/XDPoS/engines/engine_v2/utils.go | 11 ++ consensus/tests/countdown_test.go | 2 +- consensus/tests/test_helper.go | 47 +++++-- 4 files changed, 153 insertions(+), 48 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index f307eb256be2..75329ece3446 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -483,14 +483,14 @@ func (x *XDPoS_v2) GetTimeoutPoolSize(timeout *utils.Timeout) int { SyncInfo workflow */ // Verify syncInfo and trigger process QC or TC if successful -func (x *XDPoS_v2) VerifySyncInfoMessage(syncInfo *utils.SyncInfo) error { +func (x *XDPoS_v2) VerifySyncInfoMessage(chain consensus.ChainReader, syncInfo *utils.SyncInfo) error { /* 1. Verify items including: - verifyQC - verifyTC 2. Broadcast(Not part of consensus) */ - err := x.verifyQC(syncInfo.HighestQuorumCert) + err := x.verifyQC(chain, syncInfo.HighestQuorumCert) if err != nil { log.Warn("SyncInfo message verification failed due to QC", err) return err @@ -520,16 +520,21 @@ func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *utils. /* Vote workflow */ -func (x *XDPoS_v2) VerifyVoteMessage(vote *utils.Vote) (bool, error) { +func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *utils.Vote) (bool, error) { /* - 1. Check signature: + 1. Get masterNode list belong to this epoch by hash + 2. Check signature: - 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(For the running epoch) - 2. Verify blockInfo - 3. Broadcast(Not part of consensus) + - Use the above xdc address to check against the master node list from step 1(For the running epoch) + 3. Verify blockInfo + 4. Broadcast(Not part of consensus) */ - return x.verifyMsgSignature(utils.VoteSigHash(vote.ProposedBlockInfo), vote.Signature) + epochInfo, err := x.getEpochSwitchInfo(chain, nil, vote.ProposedBlockInfo.Hash) + if err != nil { + log.Error("[VerifyVoteMessage] Error when getting epoch switch Info to verify vote message", "Error", err) + } + return x.verifyMsgSignature(utils.VoteSigHash(vote.ProposedBlockInfo), vote.Signature, epochInfo.Masternodes) } // Consensus entry point for processing vote message to produce QC @@ -591,14 +596,17 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, poole */ // Verify timeout message type from peers in bft.go /* - 1. Check signature: + 1. Get master node list by timeout msg round + 2. Check signature: - 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(For the running epoch) - 2. Broadcast(Not part of consensus) + - Use the above xdc address to check against the master node list from step 1(For the running epoch) + 3. Broadcast(Not part of consensus) */ -func (x *XDPoS_v2) VerifyTimeoutMessage(timeoutMsg *utils.Timeout) (bool, error) { - return x.verifyMsgSignature(utils.TimeoutSigHash(&timeoutMsg.Round), timeoutMsg.Signature) +func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *utils.Timeout) (bool, error) { + + masternodes := x.GetMasternodesAtRound(chain, timeoutMsg.Round, chain.CurrentHeader()) + return x.verifyMsgSignature(utils.TimeoutSigHash(&timeoutMsg.Round), timeoutMsg.Signature, masternodes) } /* @@ -689,7 +697,7 @@ func (x *XDPoS_v2) ProposedBlockHandler(blockChainReader consensus.ChainReader, quorumCert := decodedExtraField.QuorumCert round := decodedExtraField.Round - err = x.verifyQC(quorumCert) + err = x.verifyQC(blockChainReader, quorumCert) if err != nil { log.Error("[ProposedBlockHandler] Fail to verify QC", "Extra round", round, "QC proposed BlockInfo Hash", quorumCert.ProposedBlockInfo.Hash) return err @@ -728,23 +736,57 @@ func (x *XDPoS_v2) VerifyBlockInfo(blockInfo *utils.BlockInfo) error { return nil } -func (x *XDPoS_v2) verifyQC(quorumCert *utils.QuorumCert) error { +func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *utils.QuorumCert) error { /* - 1. Verify signer signatures: (List of signatures) + 1. Check if num of QC signatures is >= x.config.v2.CertThreshold + 2. Get epoch master node list by hash + 3. Verify signer signatures: (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(For the received QC epoch) - 2. Verify blockInfo + - Use the above xdc address to check against the master node list from step 1(For the received QC epoch) + 4. Verify blockInfo */ - return nil + epochInfo, err := x.getEpochSwitchInfo(blockChainReader, nil, quorumCert.ProposedBlockInfo.Hash) + if err != nil { + log.Error("[verifyQC] Error when getting epoch switch Info to verify QC", "Error", err) + return fmt.Errorf("Fail to verify QC due to failure in getting epoch switch info") + } + + var wg sync.WaitGroup + wg.Add(len(quorumCert.Signatures)) + var haveError error + + for _, signature := range quorumCert.Signatures { + go func(sig utils.Signature) { + defer wg.Done() + 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") + return + } + if !verified { + log.Warn("[verifyQC] Signature not verified doing QC verification", "QC", quorumCert) + haveError = fmt.Errorf("Fail to verify QC due to signature mis-match") + return + } + }(signature) + } + wg.Wait() + if haveError != nil { + return haveError + } + + return x.VerifyBlockInfo(quorumCert.ProposedBlockInfo) } func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error { /* - 1. Verify signer signature: (List of signatures) + 1. Get epoch master node list by round/number + 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(For the received TC epoch) + - Use the above xdc address to check against the master node list from step 1(For the received TC epoch) */ return nil } @@ -917,7 +959,7 @@ func (x *XDPoS_v2) signSignature(signingHash common.Hash) (utils.Signature, erro return signedHash, nil } -func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature utils.Signature) (bool, error) { +func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature utils.Signature, masternodes []common.Address) (bool, error) { // Recover the public key and the Ethereum address pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature) if err != nil { @@ -925,7 +967,6 @@ func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signat } var signerAddress common.Address copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:]) - masternodes := x.getCurrentRoundMasterNodes() for _, mn := range masternodes { if mn == signerAddress { return true, nil @@ -957,7 +998,7 @@ func (x *XDPoS_v2) broadcastToBftChannel(msg interface{}) { }() } -func (x *XDPoS_v2) getCurrentRoundMasterNodes() []common.Address { +func (x *XDPoS_v2) GetMasternodesAtRound(chain consensus.ChainReader, round utils.Round, currentHeader *types.Header) []common.Address { return []common.Address{} } @@ -1076,6 +1117,12 @@ func (x *XDPoS_v2) GetMasternodesFromEpochSwitchHeader(epochSwitchHeader *types. } func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { + // Return true directly if we are examing the last v1 block. This could happen if the calling function is examing parent block + if header.Number.Cmp(x.config.XDPoSV2Block) == 0 { + log.Info("[IsEpochSwitch] examing last v1 block 👯‍♂️") + return true, header.Number.Uint64() / x.config.Epoch, nil + } + var decodedExtraField utils.ExtraFields_v2 err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField) if err != nil { @@ -1133,22 +1180,38 @@ func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types } if isEpochSwitch { log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64()) - masternodes := x.GetMasternodesFromEpochSwitchHeader(h) - // create the epoch switch info and cache it - var decodedExtraField utils.ExtraFields_v2 - err = utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField) - if err != nil { - return nil, err - } - epochSwitchInfo := &utils.EpochSwitchInfo{ - Masternodes: masternodes, - EpochSwitchBlockInfo: &utils.BlockInfo{ - Hash: hash, - Number: h.Number, - Round: decodedExtraField.Round, - }, - EpochSwitchParentBlockInfo: decodedExtraField.QuorumCert.ProposedBlockInfo, + var epochSwitchInfo *utils.EpochSwitchInfo + // Special case, in case of last v1 block, we manually build the epoch switch info + if h.Number.Cmp(x.config.XDPoSV2Block) == 0 { + masternodes := decodeMasternodesFromHeaderExtra(h) + epochSwitchInfo = &utils.EpochSwitchInfo{ + Masternodes: masternodes, + EpochSwitchBlockInfo: &utils.BlockInfo{ + Hash: hash, + Number: h.Number, + Round: utils.Round(0), + }, + EpochSwitchParentBlockInfo: nil, + } + } else { // v2 normal flow + masternodes := x.GetMasternodesFromEpochSwitchHeader(h) + // create the epoch switch info and cache it + var decodedExtraField utils.ExtraFields_v2 + err = utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField) + if err != nil { + return nil, err + } + epochSwitchInfo = &utils.EpochSwitchInfo{ + Masternodes: masternodes, + EpochSwitchBlockInfo: &utils.BlockInfo{ + Hash: hash, + Number: h.Number, + Round: decodedExtraField.Round, + }, + EpochSwitchParentBlockInfo: decodedExtraField.QuorumCert.ProposedBlockInfo, + } } + x.epochSwitches.Add(hash, epochSwitchInfo) return epochSwitchInfo, nil } diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 7414dce8cd17..be99cad26a1e 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -2,6 +2,7 @@ package engine_v2 import ( "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto/sha3" @@ -56,4 +57,14 @@ func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, er sigcache.Add(hash, signer) return signer, nil + +} + +// 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) + for i := 0; i < len(masternodes); i++ { + copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:]) + } + return masternodes } diff --git a/consensus/tests/countdown_test.go b/consensus/tests/countdown_test.go index 3e52cd9593e0..f5639f22c4f9 100644 --- a/consensus/tests/countdown_test.go +++ b/consensus/tests/countdown_test.go @@ -20,7 +20,7 @@ func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) { assert.Equal(t, poolSize, 1) assert.NotNil(t, timeoutMsg) - valid, err := engineV2.VerifyTimeoutMessage(timeoutMsg.(*utils.Timeout)) + valid, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg.(*utils.Timeout)) // We can only test valid = false for now as the implementation for getCurrentRoundMasterNodes is not complete assert.False(t, valid) // This shows we are able to decode the timeout message, which is what this test is all about diff --git a/consensus/tests/test_helper.go b/consensus/tests/test_helper.go index 51fb51bae144..1552bab40c06 100644 --- a/consensus/tests/test_helper.go +++ b/consensus/tests/test_helper.go @@ -241,6 +241,11 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params currentBlock := blockchain.Genesis() + go func() { + 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) @@ -262,10 +267,6 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params if err != nil { t.Fatal(err) } - go func() { - checkpointChanMsg := <-core.CheckpointCh - log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg) - }() return blockchain, backend, currentBlock, signer } @@ -288,11 +289,45 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon var currentForkBlock *types.Block + go func() { + checkpointChanMsg := <-core.CheckpointCh + log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg) + }() + + var masternodesFromV1LastEpoch []common.Address + // Insert initial blocks for i := 1; i <= numOfBlocks; i++ { blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i) roundNumber := int64(i) - chainConfig.XDPoS.XDPoSV2Block.Int64() header := createBlock(chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn) + // Inject the hardcoded master node list for the last v1 epoch block + if int64(i) == chainConfig.XDPoS.XDPoSV2Block.Int64() { + // 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 + masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, 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 { + t.Fatal(err) + } + copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash) + } else if (int64(i) == (chainConfig.XDPoS.XDPoSV2Block.Int64() + 1)) && masternodesFromV1LastEpoch != nil { // This is the first v2 block, we need to copy the last v1 epoch master node list and inject into v2 validators + for _, v := range masternodesFromV1LastEpoch { + header.Validators = append(header.Validators, v[:]...) + } + } block, err := insertBlock(blockchain, header) if err != nil { @@ -324,10 +359,6 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon if err != nil { t.Fatal(err) } - go func() { - checkpointChanMsg := <-core.CheckpointCh - log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg) - }() return blockchain, backend, currentBlock, signer, signFn, currentForkBlock }