github.com/celestiaorg/celestia-node@v0.15.0-beta.1/header/headertest/testing.go (about)

     1  package headertest
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	mrand "math/rand"
     7  	"sort"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/tendermint/tendermint/crypto/tmhash"
    13  	"github.com/tendermint/tendermint/libs/bytes"
    14  	tmrand "github.com/tendermint/tendermint/libs/rand"
    15  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    16  	"github.com/tendermint/tendermint/proto/tendermint/version"
    17  	"github.com/tendermint/tendermint/types"
    18  	tmtime "github.com/tendermint/tendermint/types/time"
    19  
    20  	"github.com/celestiaorg/celestia-app/pkg/da"
    21  	libhead "github.com/celestiaorg/go-header"
    22  	"github.com/celestiaorg/go-header/headertest"
    23  	"github.com/celestiaorg/rsmt2d"
    24  
    25  	"github.com/celestiaorg/celestia-node/header"
    26  	"github.com/celestiaorg/celestia-node/share"
    27  )
    28  
    29  // TestSuite provides everything you need to test chain of Headers.
    30  // If not, please don't hesitate to extend it for your case.
    31  type TestSuite struct {
    32  	t *testing.T
    33  
    34  	vals    []types.PrivValidator
    35  	valSet  *types.ValidatorSet
    36  	valPntr int
    37  
    38  	head *header.ExtendedHeader
    39  
    40  	blockTime time.Duration
    41  }
    42  
    43  func NewStore(t *testing.T) libhead.Store[*header.ExtendedHeader] {
    44  	return headertest.NewStore[*header.ExtendedHeader](t, NewTestSuite(t, 3, 0), 10)
    45  }
    46  
    47  func NewCustomStore(
    48  	t *testing.T,
    49  	generator headertest.Generator[*header.ExtendedHeader],
    50  	numHeaders int,
    51  ) libhead.Store[*header.ExtendedHeader] {
    52  	return headertest.NewStore[*header.ExtendedHeader](t, generator, numHeaders)
    53  }
    54  
    55  // NewTestSuite setups a new test suite with a given number of validators.
    56  func NewTestSuite(t *testing.T, numValidators int, blockTime time.Duration) *TestSuite {
    57  	valSet, vals := RandValidatorSet(numValidators, 10)
    58  	return &TestSuite{
    59  		t:         t,
    60  		vals:      vals,
    61  		valSet:    valSet,
    62  		blockTime: blockTime,
    63  	}
    64  }
    65  
    66  func (s *TestSuite) genesis() *header.ExtendedHeader {
    67  	dah := share.EmptyRoot()
    68  
    69  	gen := RandRawHeader(s.t)
    70  
    71  	gen.DataHash = dah.Hash()
    72  	gen.ValidatorsHash = s.valSet.Hash()
    73  	gen.NextValidatorsHash = s.valSet.Hash()
    74  	gen.Height = 1
    75  	voteSet := types.NewVoteSet(gen.ChainID, gen.Height, 0, tmproto.PrecommitType, s.valSet)
    76  	blockID := RandBlockID(s.t)
    77  	blockID.Hash = gen.Hash()
    78  	commit, err := MakeCommit(blockID, gen.Height, 0, voteSet, s.vals, time.Now())
    79  	require.NoError(s.t, err)
    80  
    81  	eh := &header.ExtendedHeader{
    82  		RawHeader:    *gen,
    83  		Commit:       commit,
    84  		ValidatorSet: s.valSet,
    85  		DAH:          dah,
    86  	}
    87  	require.NoError(s.t, eh.Validate())
    88  	return eh
    89  }
    90  
    91  func MakeCommit(
    92  	blockID types.BlockID, height int64, round int32,
    93  	voteSet *types.VoteSet, validators []types.PrivValidator, now time.Time,
    94  ) (*types.Commit, error) {
    95  
    96  	// all sign
    97  	for i := 0; i < len(validators); i++ {
    98  		pubKey, err := validators[i].GetPubKey()
    99  		if err != nil {
   100  			return nil, fmt.Errorf("can't get pubkey: %w", err)
   101  		}
   102  		vote := &types.Vote{
   103  			ValidatorAddress: pubKey.Address(),
   104  			ValidatorIndex:   int32(i),
   105  			Height:           height,
   106  			Round:            round,
   107  			Type:             tmproto.PrecommitType,
   108  			BlockID:          blockID,
   109  			Timestamp:        now,
   110  		}
   111  
   112  		_, err = signAddVote(validators[i], vote, voteSet)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  	}
   117  
   118  	return voteSet.MakeCommit(), nil
   119  }
   120  
   121  func signAddVote(privVal types.PrivValidator, vote *types.Vote, voteSet *types.VoteSet) (signed bool, err error) {
   122  	v := vote.ToProto()
   123  	err = privVal.SignVote(voteSet.ChainID(), v)
   124  	if err != nil {
   125  		return false, err
   126  	}
   127  	vote.Signature = v.Signature
   128  	return voteSet.AddVote(vote)
   129  }
   130  
   131  func (s *TestSuite) Head() *header.ExtendedHeader {
   132  	if s.head == nil {
   133  		s.head = s.genesis()
   134  	}
   135  	return s.head
   136  }
   137  
   138  func (s *TestSuite) GenExtendedHeaders(num int) []*header.ExtendedHeader {
   139  	headers := make([]*header.ExtendedHeader, num)
   140  	for i := range headers {
   141  		headers[i] = s.NextHeader()
   142  	}
   143  	return headers
   144  }
   145  
   146  var _ headertest.Generator[*header.ExtendedHeader] = &TestSuite{}
   147  
   148  func (s *TestSuite) NextHeader() *header.ExtendedHeader {
   149  	if s.head == nil {
   150  		s.head = s.genesis()
   151  		return s.head
   152  	}
   153  
   154  	dah := share.EmptyRoot()
   155  	height := s.Head().Height() + 1
   156  	rh := s.GenRawHeader(height, s.Head().Hash(), libhead.Hash(s.Head().Commit.Hash()), dah.Hash())
   157  	s.head = &header.ExtendedHeader{
   158  		RawHeader:    *rh,
   159  		Commit:       s.Commit(rh),
   160  		ValidatorSet: s.valSet,
   161  		DAH:          dah,
   162  	}
   163  	require.NoError(s.t, s.head.Validate())
   164  	return s.head
   165  }
   166  
   167  func (s *TestSuite) GenRawHeader(
   168  	height uint64, lastHeader, lastCommit, dataHash libhead.Hash,
   169  ) *header.RawHeader {
   170  	rh := RandRawHeader(s.t)
   171  	rh.Height = int64(height)
   172  	rh.LastBlockID = types.BlockID{Hash: bytes.HexBytes(lastHeader)}
   173  	rh.LastCommitHash = bytes.HexBytes(lastCommit)
   174  	rh.DataHash = bytes.HexBytes(dataHash)
   175  	rh.ValidatorsHash = s.valSet.Hash()
   176  	rh.NextValidatorsHash = s.valSet.Hash()
   177  	rh.ProposerAddress = s.nextProposer().Address
   178  
   179  	rh.Time = time.Now()
   180  	if s.blockTime > 0 {
   181  		rh.Time = s.Head().Time().Add(s.blockTime)
   182  	}
   183  
   184  	return rh
   185  }
   186  
   187  func (s *TestSuite) Commit(h *header.RawHeader) *types.Commit {
   188  	bid := types.BlockID{
   189  		Hash: h.Hash(),
   190  		// Unfortunately, we still have to commit PartSetHeader even we don't need it in Celestia
   191  		PartSetHeader: types.PartSetHeader{Total: 1, Hash: tmrand.Bytes(32)},
   192  	}
   193  	round := int32(0)
   194  	comms := make([]types.CommitSig, len(s.vals))
   195  	for i, val := range s.vals {
   196  		v := &types.Vote{
   197  			ValidatorAddress: s.valSet.Validators[i].Address,
   198  			ValidatorIndex:   int32(i),
   199  			Height:           h.Height,
   200  			Round:            round,
   201  			Timestamp:        tmtime.Now(),
   202  			Type:             tmproto.PrecommitType,
   203  			BlockID:          bid,
   204  		}
   205  		sgntr, err := val.(types.MockPV).PrivKey.Sign(types.VoteSignBytes(h.ChainID, v.ToProto()))
   206  		require.Nil(s.t, err)
   207  		v.Signature = sgntr
   208  		comms[i] = v.CommitSig()
   209  	}
   210  
   211  	return types.NewCommit(h.Height, round, bid, comms)
   212  }
   213  
   214  func (s *TestSuite) nextProposer() *types.Validator {
   215  	if s.valPntr == len(s.valSet.Validators)-1 {
   216  		s.valPntr = 0
   217  	} else {
   218  		s.valPntr++
   219  	}
   220  	val := s.valSet.Validators[s.valPntr]
   221  	return val
   222  }
   223  
   224  // RandExtendedHeader provides an ExtendedHeader fixture.
   225  func RandExtendedHeader(t testing.TB) *header.ExtendedHeader {
   226  	timestamp := time.Now()
   227  	return RandExtendedHeaderAtTimestamp(t, timestamp)
   228  }
   229  
   230  func RandExtendedHeaderAtTimestamp(t testing.TB, timestamp time.Time) *header.ExtendedHeader {
   231  	dah := share.EmptyRoot()
   232  
   233  	rh := RandRawHeader(t)
   234  	rh.DataHash = dah.Hash()
   235  
   236  	valSet, vals := RandValidatorSet(3, 1)
   237  	rh.ValidatorsHash = valSet.Hash()
   238  	voteSet := types.NewVoteSet(rh.ChainID, rh.Height, 0, tmproto.PrecommitType, valSet)
   239  	blockID := RandBlockID(t)
   240  	blockID.Hash = rh.Hash()
   241  	commit, err := MakeCommit(blockID, rh.Height, 0, voteSet, vals, timestamp)
   242  	require.NoError(t, err)
   243  
   244  	return &header.ExtendedHeader{
   245  		RawHeader:    *rh,
   246  		Commit:       commit,
   247  		ValidatorSet: valSet,
   248  		DAH:          dah,
   249  	}
   250  }
   251  
   252  func RandExtendedHeaderWithRoot(t testing.TB, dah *da.DataAvailabilityHeader) *header.ExtendedHeader {
   253  	h := RandExtendedHeader(t)
   254  	h.DataHash = dah.Hash()
   255  	h.DAH = dah
   256  	return h
   257  }
   258  
   259  func RandValidatorSet(numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) {
   260  	var (
   261  		valz           = make([]*types.Validator, numValidators)
   262  		privValidators = make([]types.PrivValidator, numValidators)
   263  	)
   264  
   265  	for i := 0; i < numValidators; i++ {
   266  		val, privValidator := RandValidator(false, votingPower)
   267  		valz[i] = val
   268  		privValidators[i] = privValidator
   269  	}
   270  
   271  	sort.Sort(types.PrivValidatorsByAddress(privValidators))
   272  
   273  	return types.NewValidatorSet(valz), privValidators
   274  }
   275  
   276  func RandValidator(randPower bool, minPower int64) (*types.Validator, types.PrivValidator) {
   277  	privVal := types.NewMockPV()
   278  	votePower := minPower
   279  	if randPower {
   280  		votePower += int64(mrand.Uint32()) //nolint:gosec
   281  	}
   282  	pubKey, err := privVal.GetPubKey()
   283  	if err != nil {
   284  		panic(fmt.Errorf("could not retrieve pubkey %w", err))
   285  	}
   286  	val := types.NewValidator(pubKey, votePower)
   287  	return val, privVal
   288  }
   289  
   290  // RandRawHeader provides a RawHeader fixture.
   291  func RandRawHeader(t testing.TB) *header.RawHeader {
   292  	return &header.RawHeader{
   293  		Version:            version.Consensus{Block: 11, App: 1},
   294  		ChainID:            "test",
   295  		Height:             mrand.Int63(), //nolint:gosec
   296  		Time:               time.Now(),
   297  		LastBlockID:        RandBlockID(t),
   298  		LastCommitHash:     tmrand.Bytes(32),
   299  		DataHash:           tmrand.Bytes(32),
   300  		ValidatorsHash:     tmrand.Bytes(32),
   301  		NextValidatorsHash: tmrand.Bytes(32),
   302  		ConsensusHash:      tmrand.Bytes(32),
   303  		AppHash:            tmrand.Bytes(32),
   304  		LastResultsHash:    tmrand.Bytes(32),
   305  		EvidenceHash:       tmhash.Sum([]byte{}),
   306  		ProposerAddress:    tmrand.Bytes(20),
   307  	}
   308  }
   309  
   310  // RandBlockID provides a BlockID fixture.
   311  func RandBlockID(testing.TB) types.BlockID {
   312  	bid := types.BlockID{
   313  		Hash: make([]byte, 32),
   314  		PartSetHeader: types.PartSetHeader{
   315  			Total: 123,
   316  			Hash:  make([]byte, 32),
   317  		},
   318  	}
   319  	_, _ = rand.Read(bid.Hash)
   320  	_, _ = rand.Read(bid.PartSetHeader.Hash)
   321  	return bid
   322  }
   323  
   324  func ExtendedHeaderFromEDS(t testing.TB, height uint64, eds *rsmt2d.ExtendedDataSquare) *header.ExtendedHeader {
   325  	valSet, vals := RandValidatorSet(10, 10)
   326  	gen := RandRawHeader(t)
   327  	dah, err := share.NewRoot(eds)
   328  	require.NoError(t, err)
   329  
   330  	gen.DataHash = dah.Hash()
   331  	gen.ValidatorsHash = valSet.Hash()
   332  	gen.NextValidatorsHash = valSet.Hash()
   333  	gen.Height = int64(height)
   334  	blockID := RandBlockID(t)
   335  	blockID.Hash = gen.Hash()
   336  	voteSet := types.NewVoteSet(gen.ChainID, gen.Height, 0, tmproto.PrecommitType, valSet)
   337  	commit, err := MakeCommit(blockID, gen.Height, 0, voteSet, vals, time.Now())
   338  	require.NoError(t, err)
   339  
   340  	eh := &header.ExtendedHeader{
   341  		RawHeader:    *gen,
   342  		Commit:       commit,
   343  		ValidatorSet: valSet,
   344  		DAH:          dah,
   345  	}
   346  	require.NoError(t, eh.Validate())
   347  	return eh
   348  }
   349  
   350  type Subscriber struct {
   351  	headertest.Subscriber[*header.ExtendedHeader]
   352  }
   353  
   354  var _ libhead.Subscriber[*header.ExtendedHeader] = &Subscriber{}