github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/signature/packer_test.go (about)

     1  package signature
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/mock"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/onflow/flow-go/consensus/hotstuff"
    12  	"github.com/onflow/flow-go/consensus/hotstuff/mocks"
    13  	"github.com/onflow/flow-go/consensus/hotstuff/model"
    14  	"github.com/onflow/flow-go/model/flow"
    15  	"github.com/onflow/flow-go/module/signature"
    16  	"github.com/onflow/flow-go/utils/unittest"
    17  )
    18  
    19  func newPacker(identities flow.IdentitySkeletonList) *ConsensusSigDataPacker {
    20  	// mock consensus committee
    21  	committee := &mocks.DynamicCommittee{}
    22  	committee.On("IdentitiesByEpoch", mock.Anything).Return(
    23  		func(_ uint64) flow.IdentitySkeletonList {
    24  			return identities
    25  		},
    26  		nil,
    27  	)
    28  
    29  	return NewConsensusSigDataPacker(committee)
    30  }
    31  
    32  func makeBlockSigData(committee flow.IdentitySkeletonList) *hotstuff.BlockSignatureData {
    33  	blockSigData := &hotstuff.BlockSignatureData{
    34  		StakingSigners: []flow.Identifier{
    35  			committee[0].NodeID, // A
    36  			committee[2].NodeID, // C
    37  		},
    38  		RandomBeaconSigners: []flow.Identifier{
    39  			committee[3].NodeID, // D
    40  			committee[5].NodeID, // F
    41  		},
    42  		AggregatedStakingSig:         unittest.SignatureFixture(),
    43  		AggregatedRandomBeaconSig:    unittest.SignatureFixture(),
    44  		ReconstructedRandomBeaconSig: unittest.SignatureFixture(),
    45  	}
    46  	return blockSigData
    47  }
    48  
    49  // test that a packed data can be unpacked
    50  // given the consensus committee [A, B, C, D, E, F]
    51  // [B,D,F] are random beacon nodes
    52  // [A,C,E] are non-random beacon nodes
    53  // aggregated staking sigs are from [A,C]
    54  // aggregated random beacon sigs are from [D,F]
    55  func TestPackUnpack(t *testing.T) {
    56  	// prepare data for testing
    57  	committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity]).ToSkeleton()
    58  	view := rand.Uint64()
    59  	blockSigData := makeBlockSigData(committee)
    60  
    61  	// create packer with the committee
    62  	packer := newPacker(committee)
    63  
    64  	// pack & unpack
    65  	signerIndices, sig, err := packer.Pack(view, blockSigData)
    66  	require.NoError(t, err)
    67  
    68  	signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices)
    69  	require.NoError(t, err)
    70  
    71  	unpacked, err := packer.Unpack(signers, sig)
    72  	require.NoError(t, err)
    73  
    74  	// check that the unpacked data match with the original data
    75  	require.Equal(t, blockSigData.StakingSigners, unpacked.StakingSigners)
    76  	require.Equal(t, blockSigData.RandomBeaconSigners, unpacked.RandomBeaconSigners)
    77  	require.Equal(t, blockSigData.AggregatedStakingSig, unpacked.AggregatedStakingSig)
    78  	require.Equal(t, blockSigData.AggregatedRandomBeaconSig, unpacked.AggregatedRandomBeaconSig)
    79  	require.Equal(t, blockSigData.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig)
    80  
    81  	// check the packed signer IDs
    82  	var expectedSignerIDs flow.IdentifierList
    83  	expectedSignerIDs = append(expectedSignerIDs, blockSigData.StakingSigners...)
    84  	expectedSignerIDs = append(expectedSignerIDs, blockSigData.RandomBeaconSigners...)
    85  	require.Equal(t, expectedSignerIDs, signers.NodeIDs())
    86  }
    87  
    88  // TestUnpack_EmptySignerList verifies that `Unpack` gracefully handles the edge case
    89  // of an empty signer list, as such could be an input from a byzantine node.
    90  func TestPackUnpack_EmptySigners(t *testing.T) {
    91  	// encode SignatureData with empty SigType vector (this could be an input from a byzantine node)
    92  	byzantineInput := model.SignatureData{
    93  		SigType:                      []byte{},
    94  		AggregatedStakingSig:         unittest.SignatureFixture(),
    95  		AggregatedRandomBeaconSig:    unittest.SignatureFixture(),
    96  		ReconstructedRandomBeaconSig: unittest.SignatureFixture(),
    97  	}
    98  	encoder := new(model.SigDataPacker)
    99  	sig, err := encoder.Encode(&byzantineInput)
   100  	require.NoError(t, err)
   101  
   102  	// create packer with a non-empty committee (honest node trying to decode the sig data)
   103  	committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   104  	packer := newPacker(committee)
   105  	unpacked, err := packer.Unpack(make(flow.IdentitySkeletonList, 0), sig)
   106  	require.NoError(t, err)
   107  
   108  	// check that the unpack data match with the original data
   109  	require.Empty(t, unpacked.StakingSigners)
   110  	require.Empty(t, unpacked.RandomBeaconSigners)
   111  	require.Equal(t, byzantineInput.AggregatedStakingSig, unpacked.AggregatedStakingSig)
   112  	require.Equal(t, byzantineInput.AggregatedRandomBeaconSig, unpacked.AggregatedRandomBeaconSig)
   113  	require.Equal(t, byzantineInput.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig)
   114  }
   115  
   116  // if signed by 60 staking nodes, and 50 random beacon nodes among a 200 nodes committee,
   117  // it's able to pack and unpack
   118  func TestPackUnpackManyNodes(t *testing.T) {
   119  	// prepare data for testing
   120  	committee := unittest.IdentityListFixture(200, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   121  	view := rand.Uint64()
   122  	blockSigData := makeBlockSigData(committee)
   123  	stakingSigners := make([]flow.Identifier, 0)
   124  	for i := 0; i < 60; i++ {
   125  		stakingSigners = append(stakingSigners, committee[i].NodeID)
   126  	}
   127  	randomBeaconSigners := make([]flow.Identifier, 0)
   128  	for i := 100; i < 100+50; i++ {
   129  		randomBeaconSigners = append(randomBeaconSigners, committee[i].NodeID)
   130  	}
   131  	blockSigData.StakingSigners = stakingSigners
   132  	blockSigData.RandomBeaconSigners = randomBeaconSigners
   133  
   134  	// create packer with the committee
   135  	packer := newPacker(committee)
   136  
   137  	// pack & unpack
   138  	signerIndices, sig, err := packer.Pack(view, blockSigData)
   139  	require.NoError(t, err)
   140  
   141  	signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices)
   142  	require.NoError(t, err)
   143  
   144  	unpacked, err := packer.Unpack(signers, sig)
   145  	require.NoError(t, err)
   146  
   147  	// check that the unpack data match with the original data
   148  	require.Equal(t, blockSigData.StakingSigners, unpacked.StakingSigners)
   149  	require.Equal(t, blockSigData.RandomBeaconSigners, unpacked.RandomBeaconSigners)
   150  	require.Equal(t, blockSigData.AggregatedStakingSig, unpacked.AggregatedStakingSig)
   151  	require.Equal(t, blockSigData.AggregatedRandomBeaconSig, unpacked.AggregatedRandomBeaconSig)
   152  	require.Equal(t, blockSigData.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig)
   153  
   154  	// check the packed signer IDs
   155  	var expectedSignerIDs flow.IdentifierList
   156  	expectedSignerIDs = append(expectedSignerIDs, blockSigData.StakingSigners...)
   157  	expectedSignerIDs = append(expectedSignerIDs, blockSigData.RandomBeaconSigners...)
   158  	require.Equal(t, expectedSignerIDs, signers.NodeIDs())
   159  }
   160  
   161  // if the sig data can not be decoded, return model.InvalidFormatError
   162  func TestFailToDecode(t *testing.T) {
   163  	// prepare data for testing
   164  	committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   165  	view := rand.Uint64()
   166  	blockSigData := makeBlockSigData(committee)
   167  
   168  	// create packer with the committee
   169  	packer := newPacker(committee)
   170  
   171  	signerIndices, sig, err := packer.Pack(view, blockSigData)
   172  	require.NoError(t, err)
   173  
   174  	signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices)
   175  	require.NoError(t, err)
   176  
   177  	// prepare invalid data by modifying the valid data and unpack:
   178  	invalidSigData := sig[1:]
   179  	_, err = packer.Unpack(signers, invalidSigData)
   180  	require.True(t, model.IsInvalidFormatError(err))
   181  }
   182  
   183  // TestMismatchSignerIDs
   184  // if the signer IDs doesn't match, return InvalidFormatError
   185  func TestMismatchSignerIDs(t *testing.T) {
   186  	// prepare data for testing
   187  	committee := unittest.IdentityListFixture(9, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   188  	view := rand.Uint64()
   189  	blockSigData := makeBlockSigData(committee[:6])
   190  
   191  	// create packer with the committee
   192  	packer := newPacker(committee)
   193  
   194  	signerIndices, sig, err := packer.Pack(view, blockSigData)
   195  	require.NoError(t, err)
   196  
   197  	signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices)
   198  	require.NoError(t, err)
   199  
   200  	// prepare invalid signers by modifying the valid signers
   201  	// remove the first signer
   202  	invalidSignerIDs := signers[1:]
   203  
   204  	_, err = packer.Unpack(invalidSignerIDs, sig)
   205  	require.True(t, model.IsInvalidFormatError(err))
   206  
   207  	// with additional signer
   208  	// 9 nodes committee would require two bytes for sig type, the additional byte
   209  	// would cause the sig type and signer IDs to be mismatch
   210  	invalidSignerIDs = committee
   211  	misPacked, err := packer.Unpack(invalidSignerIDs, sig)
   212  	require.Error(t, err, fmt.Sprintf("packed signers: %v", misPacked))
   213  	require.True(t, model.IsInvalidFormatError(err))
   214  }
   215  
   216  // if sig type doesn't match, return InvalidFormatError
   217  func TestInvalidSigType(t *testing.T) {
   218  	// prepare data for testing
   219  	committee := unittest.IdentityListFixture(6, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   220  	view := rand.Uint64()
   221  	blockSigData := makeBlockSigData(committee)
   222  
   223  	// create packer with the committee
   224  	packer := newPacker(committee)
   225  
   226  	signerIndices, sig, err := packer.Pack(view, blockSigData)
   227  	require.NoError(t, err)
   228  
   229  	signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices)
   230  	require.NoError(t, err)
   231  
   232  	data, err := packer.Decode(sig)
   233  	require.NoError(t, err)
   234  
   235  	data.SigType = []byte{1}
   236  
   237  	encoded, err := packer.Encode(data)
   238  	require.NoError(t, err)
   239  
   240  	_, err = packer.Unpack(signers, encoded)
   241  	require.True(t, model.IsInvalidFormatError(err))
   242  }
   243  
   244  // TestPackUnpackWithoutRBAggregatedSig test that a packed data without random beacon signers and
   245  // aggregated random beacon sig can be correctly packed and unpacked
   246  // given the consensus committee [A, B, C]
   247  // [A, B, C] are non-random beacon nodes
   248  // aggregated staking sigs are from [A,B,C]
   249  // no aggregated random beacon sigs
   250  // no random beacon signers
   251  func TestPackUnpackWithoutRBAggregatedSig(t *testing.T) {
   252  	// prepare data for testing
   253  	committee := unittest.IdentityListFixture(3, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   254  	view := rand.Uint64()
   255  
   256  	blockSigData := &hotstuff.BlockSignatureData{
   257  		StakingSigners:               committee.NodeIDs(),
   258  		RandomBeaconSigners:          nil,
   259  		AggregatedStakingSig:         unittest.SignatureFixture(),
   260  		AggregatedRandomBeaconSig:    nil,
   261  		ReconstructedRandomBeaconSig: unittest.SignatureFixture(),
   262  	}
   263  
   264  	// create packer with the committee
   265  	packer := newPacker(committee)
   266  
   267  	// pack & unpack
   268  	signerIndices, sig, err := packer.Pack(view, blockSigData)
   269  	require.NoError(t, err)
   270  
   271  	signers, err := signature.DecodeSignerIndicesToIdentities(committee, signerIndices)
   272  	require.NoError(t, err)
   273  
   274  	unpacked, err := packer.Unpack(signers, sig)
   275  	require.NoError(t, err)
   276  
   277  	// check that the unpack data match with the original data
   278  	require.Equal(t, blockSigData.StakingSigners, unpacked.StakingSigners)
   279  	require.Equal(t, blockSigData.AggregatedStakingSig, unpacked.AggregatedStakingSig)
   280  	require.Equal(t, blockSigData.ReconstructedRandomBeaconSig, unpacked.ReconstructedRandomBeaconSig)
   281  
   282  	// we need to specifically test if it's empty, it has to be by test definition
   283  	require.Empty(t, unpacked.RandomBeaconSigners)
   284  	require.Empty(t, unpacked.AggregatedRandomBeaconSig)
   285  
   286  	// check the packed signer IDs
   287  	expectedSignerIDs := append(flow.IdentifierList{}, blockSigData.StakingSigners...)
   288  	require.Equal(t, expectedSignerIDs, signers.NodeIDs())
   289  }
   290  
   291  // TestPackWithoutRBAggregatedSig tests that packer correctly handles BlockSignatureData
   292  // with different structure format, more specifically there is no difference between
   293  // nil and empty slices for RandomBeaconSigners and AggregatedRandomBeaconSig.
   294  func TestPackWithoutRBAggregatedSig(t *testing.T) {
   295  	identities := unittest.IdentityListFixture(3, unittest.WithRole(flow.RoleConsensus)).ToSkeleton()
   296  	committee := identities.NodeIDs()
   297  
   298  	// prepare data for testing
   299  	view := rand.Uint64()
   300  
   301  	aggregatedSig := unittest.SignatureFixture()
   302  	reconstructedSig := unittest.SignatureFixture()
   303  
   304  	blockSigDataWithEmptySlices := &hotstuff.BlockSignatureData{
   305  		StakingSigners:               committee,
   306  		RandomBeaconSigners:          []flow.Identifier{},
   307  		AggregatedStakingSig:         aggregatedSig,
   308  		AggregatedRandomBeaconSig:    []byte{},
   309  		ReconstructedRandomBeaconSig: reconstructedSig,
   310  	}
   311  
   312  	blockSigDataWithNils := &hotstuff.BlockSignatureData{
   313  		StakingSigners:               committee,
   314  		RandomBeaconSigners:          nil,
   315  		AggregatedStakingSig:         aggregatedSig,
   316  		AggregatedRandomBeaconSig:    nil,
   317  		ReconstructedRandomBeaconSig: reconstructedSig,
   318  	}
   319  
   320  	// create packer with the committee
   321  	packer := newPacker(identities)
   322  
   323  	// pack
   324  	signerIDs_A, sig_A, err := packer.Pack(view, blockSigDataWithEmptySlices)
   325  	require.NoError(t, err)
   326  
   327  	signerIDs_B, sig_B, err := packer.Pack(view, blockSigDataWithNils)
   328  	require.NoError(t, err)
   329  
   330  	// should be the same
   331  	require.Equal(t, signerIDs_A, signerIDs_B)
   332  	require.Equal(t, sig_A, sig_B)
   333  }