add verifyTC and verifyTimeoutMessage (#63)

* add verifyTC and verifyTimeoutMessage

* remove v2 func from adaptor
This commit is contained in:
Jerome 2022-03-02 09:17:57 +11:00 committed by GitHub
parent d773e15ca8
commit e493ddfd6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 165 additions and 76 deletions

View file

@ -521,15 +521,3 @@ func (x *XDPoS) initialV2FromLastV1(chain consensus.ChainReader, header *types.H
}
return nil
}
func (x *XDPoS) VerifyVote(*utils.Vote) error {
return nil
}
func (x *XDPoS) VerifyTimeout(*utils.Timeout) error {
return nil
}
func (x *XDPoS) VerifySyncInfo(*utils.SyncInfo) error {
return nil
}

View file

@ -378,7 +378,7 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s
var masterNodes []common.Address
if isEpochSwitch {
if x.config.V2.SwitchBlock.Cmp(parent.Number) == 0 {
snap, err := x.getSnapshot(chain, x.config.V2.SwitchBlock.Uint64())
snap, err := x.getSnapshot(chain, x.config.V2.SwitchBlock.Uint64(), false)
if err != nil {
log.Error("[YourTurn] Cannot find snapshot at gap num of last V1", "err", err, "number", x.config.V2.SwitchBlock.Uint64())
return false, err
@ -456,7 +456,7 @@ func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *type
func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*SnapshotV2, error) {
number := header.Number.Uint64()
log.Trace("get snapshot", "number", number)
snap, err := x.getSnapshot(chain, number)
snap, err := x.getSnapshot(chain, number, false)
if err != nil {
return nil, err
}
@ -464,9 +464,14 @@ func (x *XDPoS_v2) GetSnapshot(chain consensus.ChainReader, header *types.Header
}
// snapshot retrieves the authorization snapshot at a given point in time.
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64) (*SnapshotV2, error) {
// checkpoint snapshot = checkpoint - gap
gapBlockNum := number - number%x.config.Epoch - x.config.Gap
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())
@ -663,7 +668,7 @@ func (x *XDPoS_v2) VerifySyncInfoMessage(chain consensus.ChainReader, syncInfo *
log.Warn("SyncInfo message verification failed due to QC", err)
return err
}
err = x.verifyTC(syncInfo.HighestTimeoutCert)
err = x.verifyTC(chain, syncInfo.HighestTimeoutCert)
if err != nil {
log.Warn("SyncInfo message verification failed due to TC", err)
return err
@ -701,7 +706,7 @@ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *utils.Vo
if err != nil {
return false, err
}
snapshot, err := x.getSnapshot(chain, vote.ProposedBlockInfo.Number.Uint64())
snapshot, err := x.getSnapshot(chain, vote.ProposedBlockInfo.Number.Uint64(), false)
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())
}
@ -820,12 +825,19 @@ func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, poole
3. Broadcast(Not part of consensus)
*/
func (x *XDPoS_v2) VerifyTimeoutMessage(chain consensus.ChainReader, timeoutMsg *utils.Timeout) (bool, error) {
snap, err := x.getSnapshot(chain, timeoutMsg.GapNumber, true)
if err != nil {
log.Error("[VerifyTimeoutMessage] Fail to get snapshot when verifying timeout message!", "messageGapNumber", timeoutMsg.GapNumber)
}
if snap == nil || len(snap.NextEpochMasterNodes) == 0 {
log.Error("[VerifyTimeoutMessage] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutMsg.GapNumber, "snapshot", snap)
return false, fmt.Errorf("Empty master node lists from snapshot")
}
masternodes := x.GetMasternodesAtRound(chain, timeoutMsg.Round, chain.CurrentHeader())
return x.verifyMsgSignature(utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: timeoutMsg.Round,
GapNumber: timeoutMsg.GapNumber,
}), timeoutMsg.Signature, masternodes)
}), timeoutMsg.Signature, snap.NextEpochMasterNodes)
}
/*
@ -1045,7 +1057,7 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
return x.VerifyBlockInfo(blockChainReader, quorumCert.ProposedBlockInfo)
}
func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error {
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)
@ -1054,6 +1066,53 @@ func (x *XDPoS_v2) verifyTC(timeoutCert *utils.TimeoutCert) error {
- 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
}
@ -1213,14 +1272,14 @@ func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error {
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
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
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)
}
@ -1302,10 +1361,6 @@ func (x *XDPoS_v2) broadcastToBftChannel(msg interface{}) {
}()
}
func (x *XDPoS_v2) GetMasternodesAtRound(chain consensus.ChainReader, round utils.Round, currentHeader *types.Header) []common.Address {
return []common.Address{}
}
func (x *XDPoS_v2) getSyncInfo() *utils.SyncInfo {
return &utils.SyncInfo{
HighestQuorumCert: x.highestQuorumCert,
@ -1560,7 +1615,7 @@ func (x *XDPoS_v2) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, block
}
func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash) ([]common.Address, []common.Address, error) {
snap, err := x.getSnapshot(chain, blockNum.Uint64())
snap, err := x.getSnapshot(chain, blockNum.Uint64(), false)
if err != nil {
log.Error("[calcMasternodes] Adaptor v2 getSnapshot has error", "err", err)
return nil, nil, err

View file

@ -82,6 +82,7 @@ var (
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")
)

View file

@ -1,30 +0,0 @@
package tests
import (
"testing"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, 0)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
engineV2.SetNewRoundFaker(blockchain, utils.Round(1), true)
timeoutMsg := <-engineV2.BroadcastCh
poolSize := engineV2.GetTimeoutPoolSize(timeoutMsg.(*utils.Timeout))
assert.Equal(t, poolSize, 1)
assert.NotNil(t, timeoutMsg)
assert.Equal(t, uint64(0), timeoutMsg.(*utils.Timeout).GapNumber)
assert.Equal(t, utils.Round(1), timeoutMsg.(*utils.Timeout).Round)
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
assert.Regexp(t, "Empty masternode list detected when verifying message signatures", err.Error())
}

View file

@ -66,17 +66,18 @@ func TestUpdateMasterNodes(t *testing.T) {
snap, err := x.GetSnapshot(blockchain, currentBlock.Header())
assert.Nil(t, err)
assert.Equal(t, int(snap.Number), 450)
assert.Equal(t, 450, int(snap.Number))
// Insert block 1350
t.Logf("Inserting block with propose at 1350...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000001350"
tx, err := voteTX(37117, 0, acc1Addr.String())
// NOTE: voterAddr never exist in the Masternode list, but all acc1,2,3 already does
tx, err := voteTX(37117, 0, voterAddr.String())
if err != nil {
t.Fatal(err)
}
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
merkleRoot := "ef9198eb14b003774a505033f6cdcea2d357cbf7a7e7b004d8034d4e2a9770ee"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(1350)),
@ -90,7 +91,11 @@ func TestUpdateMasterNodes(t *testing.T) {
}
parentBlock, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx})
assert.Nil(t, err)
blockchain.InsertBlock(parentBlock)
err = blockchain.InsertBlock(parentBlock)
assert.Nil(t, err)
// 1350 is a gap block, need to update the snapshot
err = blockchain.UpdateM1()
assert.Nil(t, err)
t.Logf("Inserting block from 1351 to 1800...")
for i := 1351; i <= 1800; i++ {
blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i)
@ -117,8 +122,7 @@ func TestUpdateMasterNodes(t *testing.T) {
snap, err = x.GetSnapshot(blockchain, parentBlock.Header())
assert.Nil(t, err)
assert.False(t, snap.IsMasterNodes(acc3Addr))
assert.True(t, snap.IsMasterNodes(acc1Addr))
assert.True(t, snap.IsMasterNodes(voterAddr))
assert.Equal(t, int(snap.Number), 1350)
}

View file

@ -3,12 +3,25 @@ package tests
import (
"testing"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 2251, params.TestXDPoSMockChainConfig, 0)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
timeoutMsg := <-engineV2.BroadcastCh
poolSize := engineV2.GetTimeoutPoolSize(timeoutMsg.(*utils.Timeout))
assert.Equal(t, poolSize, 1)
assert.NotNil(t, timeoutMsg)
assert.Equal(t, uint64(1350), timeoutMsg.(*utils.Timeout).GapNumber)
assert.Equal(t, utils.Round(1), timeoutMsg.(*utils.Timeout).Round)
}
// Timeout handler
func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfig, 0)
@ -101,3 +114,57 @@ func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
// Timeout msg round < currentRound
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 1", err.Error())
}
func TestShouldVerifyTimeoutMessageForFirstV2Block(t *testing.T) {
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, 0)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
signedHash, err := signFn(accounts.Account{Address: signer}, utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: utils.Round(1),
GapNumber: 450,
}).Bytes())
assert.Nil(t, err)
timeoutMsg := &utils.Timeout{
Round: utils.Round(1),
GapNumber: 450,
Signature: signedHash,
}
verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
assert.Nil(t, err)
assert.True(t, verified)
signedHash, err = signFn(accounts.Account{Address: signer}, utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: utils.Round(2),
GapNumber: 450,
}).Bytes())
assert.Nil(t, err)
timeoutMsg = &utils.Timeout{
Round: utils.Round(2),
GapNumber: 450,
Signature: signedHash,
}
verified, err = engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
assert.Nil(t, err)
assert.True(t, verified)
}
func TestShouldVerifyTimeoutMessage(t *testing.T) {
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 2251, params.TestXDPoSMockChainConfig, 0)
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
signedHash := SignHashByPK(acc1Key, utils.TimeoutSigHash(&utils.TimeoutForSign{
Round: utils.Round(5000),
GapNumber: 2250,
}).Bytes())
timeoutMsg := &utils.Timeout{
Round: utils.Round(5000),
GapNumber: 2250,
Signature: signedHash,
}
verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
assert.Nil(t, err)
assert.True(t, verified)
}

View file

@ -2516,7 +2516,7 @@ func (bc *BlockChain) UpdateM1() error {
header := bc.CurrentHeader()
var maxMasternodes int
// check if block number is increase ms checkpoint
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) {
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) || (bc.chainConfig.XDPoS.V2.SwitchBlock != nil && header.Number.Cmp(bc.chainConfig.XDPoS.V2.SwitchBlock) == 1) {
// using new masterndoes
maxMasternodes = common.MaxMasternodesV2
} else {

View file

@ -155,9 +155,9 @@ func TestTimeoutHandler(t *testing.T) {
broadcastCounter := uint32(0)
targetVotes := 1
tester.bfter.consensus.verifyTimeout = func(timeout *utils.Timeout) error {
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *utils.Timeout) (bool, error) {
atomic.AddUint32(&verifyCounter, 1)
return nil
return true, nil
}
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *utils.Timeout) error {
@ -186,8 +186,8 @@ func TestTimeoutHandler(t *testing.T) {
func TestTimeoutHandlerRoundNotEqual(t *testing.T) {
tester := newTester()
tester.bfter.consensus.verifyTimeout = func(timeout *utils.Timeout) error {
return nil
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *utils.Timeout) (bool, error) {
return true, nil
}
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *utils.Timeout) error {

View file

@ -37,10 +37,10 @@ type ConsensusFns struct {
verifyVote func(consensus.ChainReader, *utils.Vote) (bool, error)
voteHandler func(consensus.ChainReader, *utils.Vote) error
verifyTimeout func(*utils.Timeout) error
verifyTimeout func(consensus.ChainReader, *utils.Timeout) (bool, error)
timeoutHandler func(consensus.ChainReader, *utils.Timeout) error
verifySyncInfo func(*utils.SyncInfo) error
verifySyncInfo func(consensus.ChainReader, *utils.SyncInfo) error
syncInfoHandler func(consensus.ChainReader, *utils.SyncInfo) error
}
@ -69,9 +69,9 @@ func (b *Bfter) SetConsensusFuns(engine consensus.Engine) {
e := engine.(*XDPoS.XDPoS)
b.broadcastCh = e.EngineV2.BroadcastCh
b.consensus = ConsensusFns{
verifySyncInfo: e.VerifySyncInfo,
verifySyncInfo: e.EngineV2.VerifySyncInfoMessage,
verifyVote: e.EngineV2.VerifyVoteMessage,
verifyTimeout: e.VerifyTimeout,
verifyTimeout: e.EngineV2.VerifyTimeoutMessage,
voteHandler: e.EngineV2.VoteHandler,
timeoutHandler: e.EngineV2.TimeoutHandler,
@ -116,11 +116,15 @@ func (b *Bfter) Timeout(timeout *utils.Timeout) error {
log.Trace("Discarded Timeout, known Timeout", "Signature", timeout.Signature, "hash", timeout.Hash(), "round", timeout.Round)
return nil
}
err := b.consensus.verifyTimeout(timeout)
verified, err := b.consensus.verifyTimeout(b.blockChainReader, timeout)
if err != nil {
log.Error("Verify BFT Timeout", "error", err)
return err
}
if !verified {
log.Warn("Timeout message failed to verify", "timeoutRound", timeout.Round, "timeoutGapNum", timeout.GapNumber)
return fmt.Errorf("Fail to verify the received timeout message")
}
b.broadcastCh <- timeout
err = b.consensus.timeoutHandler(b.blockChainReader, timeout)
@ -140,7 +144,7 @@ func (b *Bfter) SyncInfo(syncInfo *utils.SyncInfo) error {
log.Trace("Discarded SyncInfo, known SyncInfo", "hash", syncInfo.Hash())
return nil
}
err := b.consensus.verifySyncInfo(syncInfo)
err := b.consensus.verifySyncInfo(b.blockChainReader, syncInfo)
if err != nil {
log.Error("Verify BFT SyncInfo", "error", err)
return err