Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add header verification unit tests #6769

Merged
merged 12 commits into from
Feb 6, 2025
9 changes: 9 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/data"
"github.com/multiversx/mx-chain-go/consensus"
)

// IsValidRelayedTxV3 returns true if the provided transaction is a valid transaction of type relayed v3
Expand Down Expand Up @@ -39,6 +40,14 @@ func IsEpochChangeBlockForFlagActivation(header data.HeaderHandler, enableEpochs
return isStartOfEpochBlock && isBlockInActivationEpoch
}

// IsEpochStartProofForFlagActivation returns true if the provided proof is the proof of the epoch start block on the activation epoch of equivalent messages
func IsEpochStartProofForFlagActivation(proof consensus.ProofHandler, enableEpochsHandler EnableEpochsHandler) bool {
isStartOfEpochProof := proof.GetIsStartOfEpoch()
isProofInActivationEpoch := proof.GetHeaderEpoch() == enableEpochsHandler.GetActivationEpoch(EquivalentMessagesFlag)

return isStartOfEpochProof && isProofInActivationEpoch
}

// isFlagEnabledAfterEpochsStartBlock returns true if the flag is enabled for the header, but it is not the epoch start block
func isFlagEnabledAfterEpochsStartBlock(header data.HeaderHandler, enableEpochsHandler EnableEpochsHandler, flag core.EnableEpochFlag) bool {
isFlagEnabled := enableEpochsHandler.IsFlagEnabledInEpoch(flag, header.GetEpoch())
Expand Down
1 change: 1 addition & 0 deletions consensus/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,5 @@ type ProofHandler interface {
GetHeaderEpoch() uint32
GetHeaderNonce() uint64
GetHeaderShardId() uint32
GetIsStartOfEpoch() bool
}
7 changes: 7 additions & 0 deletions process/block/interceptedBlocks/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ func checkMetaShardInfo(
return err
}

isSelfMeta := coordinator.SelfId() == core.MetachainShardId
isHeaderFromMeta := sd.GetShardID() == core.MetachainShardId
isHeaderFromSelf := sd.GetShardID() == coordinator.SelfId()
if !(isSelfMeta || isHeaderFromMeta || isHeaderFromSelf) {
continue
}

wgProofsVerification.Add(1)
checkProofAsync(sd.GetPreviousProof(), headerSigVerifier, &wgProofsVerification, errChan)
}
Expand Down
35 changes: 17 additions & 18 deletions process/headerCheck/headerSignatureVerify.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,12 @@ func (hsv *HeaderSigVerifier) getConsensusSignersForEquivalentProofs(proof data.
return nil, process.ErrUnexpectedHeaderProof
}

// TODO: remove if start of epochForConsensus block needs to be validated by the new epochForConsensus nodes
// safe to use proof.GetHeaderEpoch, as the transition block will be treated separately
epochForConsensus := proof.GetHeaderEpoch()
if proof.GetIsStartOfEpoch() && epochForConsensus > 0 {
epochForConsensus = epochForConsensus - 1
}

consensusPubKeys, err := hsv.nodesCoordinator.GetAllEligibleValidatorsPublicKeysForShard(
epochForConsensus,
proof.GetHeaderShardId(),
)

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -315,7 +310,7 @@ func (hsv *HeaderSigVerifier) VerifyHeaderWithProof(header data.HeaderHandler) e
}

prevProof := header.GetPreviousProof()
if prevProof.GetIsStartOfEpoch() {
if common.IsEpochStartProofForFlagActivation(prevProof, hsv.enableEpochsHandler) {
return hsv.verifyHeaderProofAtTransition(prevProof)
}

Expand All @@ -332,33 +327,33 @@ func (hsv *HeaderSigVerifier) getHeaderForProof(proof data.HeaderProofHandler) (
return process.GetHeader(proof.GetHeaderHash(), hsv.headersPool, headersStorer, hsv.marshalizer, proof.GetHeaderShardId())
}

func (hsv *HeaderSigVerifier) verifyHeaderProofAtTransition(prevProof data.HeaderProofHandler) error {
if check.IfNilReflect(prevProof) {
func (hsv *HeaderSigVerifier) verifyHeaderProofAtTransition(proof data.HeaderProofHandler) error {
if check.IfNilReflect(proof) {
return process.ErrNilHeaderProof
}
header, err := hsv.getHeaderForProof(prevProof)
header, err := hsv.getHeaderForProof(proof)
if err != nil {
return err
}

consensusPubKeys, err := hsv.getConsensusSigners(
header.GetPrevRandSeed(),
prevProof.GetHeaderShardId(),
prevProof.GetHeaderEpoch(),
prevProof.GetIsStartOfEpoch(),
prevProof.GetHeaderRound(),
prevProof.GetHeaderHash(),
prevProof.GetPubKeysBitmap())
proof.GetHeaderShardId(),
proof.GetHeaderEpoch(),
proof.GetIsStartOfEpoch(),
proof.GetHeaderRound(),
proof.GetHeaderHash(),
proof.GetPubKeysBitmap())
if err != nil {
return err
}

multiSigVerifier, err := hsv.multiSigContainer.GetMultiSigner(prevProof.GetHeaderEpoch())
multiSigVerifier, err := hsv.multiSigContainer.GetMultiSigner(proof.GetHeaderEpoch())
if err != nil {
return err
}

return multiSigVerifier.VerifyAggregatedSig(consensusPubKeys, prevProof.GetHeaderHash(), prevProof.GetAggregatedSignature())
return multiSigVerifier.VerifyAggregatedSig(consensusPubKeys, proof.GetHeaderHash(), proof.GetAggregatedSignature())
}

// VerifyHeaderProof checks if the proof is correct for the header
Expand All @@ -370,6 +365,10 @@ func (hsv *HeaderSigVerifier) VerifyHeaderProof(proofHandler data.HeaderProofHan
return fmt.Errorf("%w for flag %s", process.ErrFlagNotActive, common.EquivalentMessagesFlag)
}

if common.IsEpochStartProofForFlagActivation(proofHandler, hsv.enableEpochsHandler) {
return hsv.verifyHeaderProofAtTransition(proofHandler)
}

multiSigVerifier, err := hsv.multiSigContainer.GetMultiSigner(proofHandler.GetHeaderEpoch())
if err != nil {
return err
Expand Down
228 changes: 228 additions & 0 deletions process/headerCheck/headerSignatureVerify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package headerCheck
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"testing"

Expand All @@ -21,6 +23,7 @@ import (
"github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock"
"github.com/multiversx/mx-chain-go/testscommon/genericMocks"
"github.com/multiversx/mx-chain-go/testscommon/hashingMocks"
"github.com/multiversx/mx-chain-go/testscommon/pool"
"github.com/multiversx/mx-chain-go/testscommon/shardingMocks"
)

Expand Down Expand Up @@ -712,6 +715,231 @@ func TestHeaderSigVerifier_VerifySignatureOkWhenFallbackThresholdCouldBeApplied(
require.True(t, wasCalled)
}

func TestHeaderSigVerifier_VerifySignatureWithEquivalentProofsActivated(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add t.Parallel() ?

wasCalled := false
args := createHeaderSigVerifierArgs()
numValidatorsConsensusBeforeActivation := 7
numValidatorsConsensusAfterActivation := 10
eligibleListSize := numValidatorsConsensusAfterActivation
eligibleValidatorsKeys := make([]string, eligibleListSize)
eligibleValidators := make([]nodesCoordinator.Validator, eligibleListSize)
activationEpoch := uint32(1)

for i := 0; i < eligibleListSize; i++ {
eligibleValidatorsKeys[i] = "pubKey" + strconv.Itoa(i)
eligibleValidators[i], _ = nodesCoordinator.NewValidator([]byte(eligibleValidatorsKeys[i]), 1, defaultChancesSelection)
}

nc := &shardingMocks.NodesCoordinatorMock{
ComputeValidatorsGroupCalled: func(randomness []byte, round uint64, shardId uint32, epoch uint32) (leader nodesCoordinator.Validator, validators []nodesCoordinator.Validator, err error) {
if epoch < activationEpoch {
return eligibleValidators[0], eligibleValidators[:numValidatorsConsensusBeforeActivation], nil
}
return eligibleValidators[0], eligibleValidators, nil
},
GetAllEligibleValidatorsPublicKeysForShardCalled: func(epoch uint32, shardID uint32) ([]string, error) {
return eligibleValidatorsKeys, nil
},
}

t.Run("check transition block which has no previous proof", func(t *testing.T) {
enableEpochs := &enableEpochsHandlerMock.EnableEpochsHandlerStub{}
args.EnableEpochsHandler = enableEpochs
enableEpochs.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool {
return epoch >= activationEpoch
}
enableEpochs.GetActivationEpochCalled = func(flag core.EnableEpochFlag) uint32 {
return activationEpoch
}

args.NodesCoordinator = nc
args.MultiSigContainer = cryptoMocks.NewMultiSignerContainerMock(&cryptoMocks.MultisignerMock{
VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error {
wasCalled = true
return nil
}})
hdrSigVerifier, _ := NewHeaderSigVerifier(args)
header := &dataBlock.HeaderV2{
Header: &dataBlock.Header{
ShardID: 0,
PrevRandSeed: []byte("prevRandSeed"),
PubKeysBitmap: nil,
Signature: nil,
Epoch: 1,
EpochStartMetaHash: []byte("epoch start meta hash"), // to make this the epoch start block in the shard

},
PreviousHeaderProof: nil,
}

err := hdrSigVerifier.VerifySignature(header)
require.Nil(t, err)
require.False(t, wasCalled)

// check current block proof
err = hdrSigVerifier.VerifyHeaderProof(&dataBlock.HeaderProof{
PubKeysBitmap: []byte{0xff}, // bitmap should still have the old format
AggregatedSignature: []byte("aggregated signature"),
HeaderHash: []byte("hash"),
HeaderEpoch: 1,
IsStartOfEpoch: true,
})
require.Nil(t, err)
})
t.Run("check shard block following the transition block, which has lower consensus size but with a proof", func(t *testing.T) {
enableEpochs := &enableEpochsHandlerMock.EnableEpochsHandlerStub{}
args.EnableEpochsHandler = enableEpochs
args.StorageService = &genericMocks.ChainStorerMock{}

prevHeader := &dataBlock.HeaderV2{
Header: &dataBlock.Header{
Nonce: 99,
Round: 99,
ShardID: 0,
PrevHash: []byte("prevPrevHash"),
PrevRandSeed: []byte("prevRandSeed"),
PubKeysBitmap: nil,
Signature: nil,
Epoch: 0,
EpochStartMetaHash: []byte("epoch start meta hash"), // to make this the epoch start block in the shard
},
PreviousHeaderProof: nil,
}
prevHeaderHash := []byte("prevHeaderHash")
headersPool := &pool.HeadersPoolStub{
GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) {
if bytes.Equal(hash, []byte("prevHeaderHash")) {
return prevHeader, nil
}
return nil, fmt.Errorf("header not found")
},
}
args.HeadersPool = headersPool
args.NodesCoordinator = nc
args.MultiSigContainer = cryptoMocks.NewMultiSignerContainerMock(&cryptoMocks.MultisignerMock{
VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error {
wasCalled = true
return nil
}})
enableEpochs.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool {
return epoch >= activationEpoch
}
enableEpochs.GetActivationEpochCalled = func(flag core.EnableEpochFlag) uint32 {
return activationEpoch
}

hdrSigVerifier, _ := NewHeaderSigVerifier(args)
header := &dataBlock.HeaderV2{
Header: &dataBlock.Header{
Nonce: 100,
Round: 100,
ShardID: 0,
PrevHash: prevHeaderHash,
PrevRandSeed: []byte("prevRandSeed"),
PubKeysBitmap: nil,
Signature: nil,
Epoch: 1,
},
PreviousHeaderProof: &dataBlock.HeaderProof{
PubKeysBitmap: []byte{0x3F},
AggregatedSignature: []byte("aggregated signature"),
HeaderHash: prevHeaderHash,
HeaderEpoch: 1,
HeaderNonce: 99,
HeaderShardId: 0,
HeaderRound: 99,
IsStartOfEpoch: true,
},
}

err := hdrSigVerifier.VerifySignature(header)
require.Nil(t, err)
require.True(t, wasCalled)
})
t.Run("check regular shard block with full size consensus for previous proof", func(t *testing.T) {
enableEpochs := &enableEpochsHandlerMock.EnableEpochsHandlerStub{}
args.EnableEpochsHandler = enableEpochs
args.StorageService = &genericMocks.ChainStorerMock{}

prevHeader := &dataBlock.HeaderV2{
Header: &dataBlock.Header{
Nonce: 100,
Round: 100,
ShardID: 0,
PrevHash: []byte("prevPrevHash"),
PrevRandSeed: []byte("prevRandSeed"),
PubKeysBitmap: nil,
Signature: nil,
Epoch: 1,
},
PreviousHeaderProof: &dataBlock.HeaderProof{},
}
prevHeaderHash := []byte("prevHeaderHash")
headersPool := &pool.HeadersPoolStub{
GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) {
if bytes.Equal(hash, []byte("prevHeaderHash")) {
return prevHeader, nil
}
return nil, fmt.Errorf("header not found")
},
}
args.HeadersPool = headersPool
args.NodesCoordinator = nc
args.MultiSigContainer = cryptoMocks.NewMultiSignerContainerMock(&cryptoMocks.MultisignerMock{
VerifyAggregatedSigCalled: func(pubKeysSigners [][]byte, message []byte, aggSig []byte) error {
wasCalled = true
return nil
}})
enableEpochs.IsFlagEnabledInEpochCalled = func(flag core.EnableEpochFlag, epoch uint32) bool {
return epoch >= activationEpoch
}
enableEpochs.GetActivationEpochCalled = func(flag core.EnableEpochFlag) uint32 {
return activationEpoch
}

hdrSigVerifier, _ := NewHeaderSigVerifier(args)
header := &dataBlock.HeaderV2{
Header: &dataBlock.Header{
Nonce: 101,
Round: 101,
ShardID: 0,
PrevHash: prevHeaderHash,
PrevRandSeed: []byte("prevRandSeed"),
PubKeysBitmap: nil,
Signature: nil,
Epoch: 1,
},
PreviousHeaderProof: &dataBlock.HeaderProof{
PubKeysBitmap: []byte{0xff, 0x03},
AggregatedSignature: []byte("aggregated signature"),
HeaderHash: prevHeaderHash,
HeaderEpoch: 1,
HeaderNonce: 100,
HeaderShardId: 0,
HeaderRound: 100,
IsStartOfEpoch: false,
},
}

err := hdrSigVerifier.VerifySignature(header)
require.Nil(t, err)
require.True(t, wasCalled)

// check current block proof
err = hdrSigVerifier.VerifyHeaderProof(&dataBlock.HeaderProof{
PubKeysBitmap: []byte{0xff, 0x3f}, // for current block, bitmap should have the new format
AggregatedSignature: []byte("aggregated signature"),
HeaderHash: []byte("hash"),
HeaderEpoch: 1,
HeaderNonce: 100,
HeaderShardId: 0,
HeaderRound: 100,
IsStartOfEpoch: false,
})
require.Nil(t, err)
})
}

func getFilledHeader() data.HeaderHandler {
return &dataBlock.Header{
PrevHash: []byte("prev hash"),
Expand Down