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
This commit is contained in:
Jerome 2022-01-21 21:10:44 +11:00 committed by GitHub
parent 646042a651
commit e063f67f34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 153 additions and 48 deletions

View file

@ -483,14 +483,14 @@ func (x *XDPoS_v2) GetTimeoutPoolSize(timeout *utils.Timeout) int {
SyncInfo workflow SyncInfo workflow
*/ */
// Verify syncInfo and trigger process QC or TC if successful // 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: 1. Verify items including:
- verifyQC - verifyQC
- verifyTC - verifyTC
2. Broadcast(Not part of consensus) 2. Broadcast(Not part of consensus)
*/ */
err := x.verifyQC(syncInfo.HighestQuorumCert) err := x.verifyQC(chain, syncInfo.HighestQuorumCert)
if err != nil { if err != nil {
log.Warn("SyncInfo message verification failed due to QC", err) log.Warn("SyncInfo message verification failed due to QC", err)
return err return err
@ -520,16 +520,21 @@ func (x *XDPoS_v2) SyncInfoHandler(chain consensus.ChainReader, syncInfo *utils.
/* /*
Vote workflow 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 ecRecover to get the public key
- Use the above public key to find out the xdc address - 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) - Use the above xdc address to check against the master node list from step 1(For the running epoch)
2. Verify blockInfo 3. Verify blockInfo
3. Broadcast(Not part of consensus) 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 // 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 // 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 ecRecover to get the public key
- Use the above public key to find out the xdc address - 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) - Use the above xdc address to check against the master node list from step 1(For the running epoch)
2. Broadcast(Not part of consensus) 3. Broadcast(Not part of consensus)
*/ */
func (x *XDPoS_v2) VerifyTimeoutMessage(timeoutMsg *utils.Timeout) (bool, error) { func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *utils.Timeout) (bool, error) {
return x.verifyMsgSignature(utils.TimeoutSigHash(&timeoutMsg.Round), timeoutMsg.Signature)
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 quorumCert := decodedExtraField.QuorumCert
round := decodedExtraField.Round round := decodedExtraField.Round
err = x.verifyQC(quorumCert) err = x.verifyQC(blockChainReader, quorumCert)
if err != nil { if err != nil {
log.Error("[ProposedBlockHandler] Fail to verify QC", "Extra round", round, "QC proposed BlockInfo Hash", quorumCert.ProposedBlockInfo.Hash) log.Error("[ProposedBlockHandler] Fail to verify QC", "Extra round", round, "QC proposed BlockInfo Hash", quorumCert.ProposedBlockInfo.Hash)
return err return err
@ -728,23 +736,57 @@ func (x *XDPoS_v2) VerifyBlockInfo(blockInfo *utils.BlockInfo) error {
return nil 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 ecRecover to get the public key
- Use the above public key to find out the xdc address - 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) - Use the above xdc address to check against the master node list from step 1(For the received QC epoch)
2. Verify blockInfo 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 { 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 ecRecover to get the public key
- Use the above public key to find out the xdc address - 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 return nil
} }
@ -917,7 +959,7 @@ func (x *XDPoS_v2) signSignature(signingHash common.Hash) (utils.Signature, erro
return signedHash, nil 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 // Recover the public key and the Ethereum address
pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature) pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature)
if err != nil { if err != nil {
@ -925,7 +967,6 @@ func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signat
} }
var signerAddress common.Address var signerAddress common.Address
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:]) copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
masternodes := x.getCurrentRoundMasterNodes()
for _, mn := range masternodes { for _, mn := range masternodes {
if mn == signerAddress { if mn == signerAddress {
return true, nil 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{} 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) { 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 var decodedExtraField utils.ExtraFields_v2
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField) err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
if err != nil { if err != nil {
@ -1133,22 +1180,38 @@ func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types
} }
if isEpochSwitch { if isEpochSwitch {
log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64()) log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64())
masternodes := x.GetMasternodesFromEpochSwitchHeader(h) var epochSwitchInfo *utils.EpochSwitchInfo
// create the epoch switch info and cache it // Special case, in case of last v1 block, we manually build the epoch switch info
var decodedExtraField utils.ExtraFields_v2 if h.Number.Cmp(x.config.XDPoSV2Block) == 0 {
err = utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField) masternodes := decodeMasternodesFromHeaderExtra(h)
if err != nil { epochSwitchInfo = &utils.EpochSwitchInfo{
return nil, err Masternodes: masternodes,
} EpochSwitchBlockInfo: &utils.BlockInfo{
epochSwitchInfo := &utils.EpochSwitchInfo{ Hash: hash,
Masternodes: masternodes, Number: h.Number,
EpochSwitchBlockInfo: &utils.BlockInfo{ Round: utils.Round(0),
Hash: hash, },
Number: h.Number, EpochSwitchParentBlockInfo: nil,
Round: decodedExtraField.Round, }
}, } else { // v2 normal flow
EpochSwitchParentBlockInfo: decodedExtraField.QuorumCert.ProposedBlockInfo, 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) x.epochSwitches.Add(hash, epochSwitchInfo)
return epochSwitchInfo, nil return epochSwitchInfo, nil
} }

View file

@ -2,6 +2,7 @@ package engine_v2
import ( import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3" "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) sigcache.Add(hash, signer)
return signer, nil 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
} }

View file

@ -20,7 +20,7 @@ func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
assert.Equal(t, poolSize, 1) assert.Equal(t, poolSize, 1)
assert.NotNil(t, timeoutMsg) 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 // We can only test valid = false for now as the implementation for getCurrentRoundMasterNodes is not complete
assert.False(t, valid) assert.False(t, valid)
// This shows we are able to decode the timeout message, which is what this test is all about // This shows we are able to decode the timeout message, which is what this test is all about

View file

@ -241,6 +241,11 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
currentBlock := blockchain.Genesis() currentBlock := blockchain.Genesis()
go func() {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}()
// Insert initial blocks // Insert initial blocks
for i := 1; i <= numOfBlocks; i++ { for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i) blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
@ -262,10 +267,6 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
go func() {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}()
return blockchain, backend, currentBlock, signer return blockchain, backend, currentBlock, signer
} }
@ -288,11 +289,45 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
var currentForkBlock *types.Block 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 // Insert initial blocks
for i := 1; i <= numOfBlocks; i++ { for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i) blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
roundNumber := int64(i) - chainConfig.XDPoS.XDPoSV2Block.Int64() roundNumber := int64(i) - chainConfig.XDPoS.XDPoSV2Block.Int64()
header := createBlock(chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn) 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) block, err := insertBlock(blockchain, header)
if err != nil { if err != nil {
@ -324,10 +359,6 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
if err != nil { if err != nil {
t.Fatal(err) 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 return blockchain, backend, currentBlock, signer, signFn, currentForkBlock
} }