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{}