Skip to content

Commit

Permalink
implement verify functions (ethereum#43)
Browse files Browse the repository at this point in the history
* implement verify functions

* Use go routine to call verifyMsgSignature

* verify timeout msg shall use round from the msg

* add comment for verifyQC
  • Loading branch information
wjrjerome authored Jan 21, 2022
1 parent 646042a commit e063f67
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 48 deletions.
141 changes: 102 additions & 39 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

/*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -917,15 +959,14 @@ 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 {
return false, fmt.Errorf("Error while verifying message: %v", err)
}
var signerAddress common.Address
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
masternodes := x.getCurrentRoundMasterNodes()
for _, mn := range masternodes {
if mn == signerAddress {
return true, nil
Expand Down Expand Up @@ -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{}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down
11 changes: 11 additions & 0 deletions consensus/XDPoS/engines/engine_v2/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion consensus/tests/countdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 39 additions & 8 deletions consensus/tests/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit e063f67

Please sign in to comment.