package light_test import ( "testing" "time" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" tmtime "github.com/tendermint/tendermint/libs/time" provider_mocks "github.com/tendermint/tendermint/light/provider/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" ) // privKeys is a helper type for testing. // // It lets us simulate signing with many keys. The main use case is to create // a set, and call GenSignedHeader to get properly signed header for testing. // // You can set different weights of validators each time you call ToValidators, // and can optionally extend the validator set later with Extend. type privKeys []crypto.PrivKey // genPrivKeys produces an array of private keys to generate commits. func genPrivKeys(n int) privKeys { res := make(privKeys, n) for i := range res { res[i] = ed25519.GenPrivKey() } return res } // Extend adds n more keys (to remove, just take a slice). func (pkz privKeys) Extend(n int) privKeys { extra := genPrivKeys(n) return append(pkz, extra...) } // ToValidators produces a valset from the set of keys. // The first key has weight `init` and it increases by `inc` every step // so we can have all the same weight, or a simple linear distribution // (should be enough for testing). func (pkz privKeys) ToValidators(init, inc int64) *types.ValidatorSet { res := make([]*types.Validator, len(pkz)) for i, k := range pkz { res[i] = types.NewValidator(k.PubKey(), init+int64(i)*inc) } return types.NewValidatorSet(res) } // signHeader properly signs the header with all keys from first to last exclusive. func (pkz privKeys) signHeader(t testing.TB, header *types.Header, valSet *types.ValidatorSet, first, last int) *types.Commit { t.Helper() commitSigs := make([]types.CommitSig, len(pkz)) for i := 0; i < len(pkz); i++ { commitSigs[i] = types.NewCommitSigAbsent() } blockID := types.BlockID{ Hash: header.Hash(), PartSetHeader: types.PartSetHeader{Total: 1, Hash: crypto.CRandBytes(32)}, } // Fill in the votes we want. for i := first; i < last && i < len(pkz); i++ { vote := makeVote(t, header, valSet, pkz[i], blockID) commitSigs[vote.ValidatorIndex] = vote.CommitSig() } return types.NewCommit(header.Height, 1, blockID, commitSigs) } func makeVote(t testing.TB, header *types.Header, valset *types.ValidatorSet, key crypto.PrivKey, blockID types.BlockID) *types.Vote { t.Helper() addr := key.PubKey().Address() idx, _ := valset.GetByAddress(addr) vote := &types.Vote{ ValidatorAddress: addr, ValidatorIndex: idx, Height: header.Height, Round: 1, Timestamp: tmtime.Now(), Type: tmproto.PrecommitType, BlockID: blockID, } v := vote.ToProto() // Sign it signBytes := types.VoteSignBytes(header.ChainID, v) sig, err := key.Sign(signBytes) require.NoError(t, err) vote.Signature = sig return vote } func genHeader(chainID string, height int64, bTime time.Time, txs types.Txs, valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte) *types.Header { return &types.Header{ Version: version.Consensus{Block: version.BlockProtocol, App: 0}, ChainID: chainID, Height: height, Time: bTime, // LastBlockID // LastCommitHash ValidatorsHash: valset.Hash(), NextValidatorsHash: nextValset.Hash(), DataHash: txs.Hash(), AppHash: appHash, ConsensusHash: consHash, LastResultsHash: resHash, ProposerAddress: valset.Validators[0].Address, } } // GenSignedHeader calls genHeader and signHeader and combines them into a SignedHeader. func (pkz privKeys) GenSignedHeader(t testing.TB, chainID string, height int64, bTime time.Time, txs types.Txs, valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) *types.SignedHeader { t.Helper() header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash) return &types.SignedHeader{ Header: header, Commit: pkz.signHeader(t, header, valset, first, last), } } // GenSignedHeaderLastBlockID calls genHeader and signHeader and combines them into a SignedHeader. func (pkz privKeys) GenSignedHeaderLastBlockID(t testing.TB, chainID string, height int64, bTime time.Time, txs types.Txs, valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int, lastBlockID types.BlockID) *types.SignedHeader { t.Helper() header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash) header.LastBlockID = lastBlockID return &types.SignedHeader{ Header: header, Commit: pkz.signHeader(t, header, valset, first, last), } } func (pkz privKeys) ChangeKeys(delta int) privKeys { newKeys := pkz[delta:] return newKeys.Extend(delta) } // genLightBlocksWithKeys generates the header and validator set to create // blocks to height. BlockIntervals are in per minute. // NOTE: Expected to have a large validator set size ~ 100 validators. func genLightBlocksWithKeys( t testing.TB, chainID string, numBlocks int64, valSize int, valVariation float32, bTime time.Time, ) (map[int64]*types.SignedHeader, map[int64]*types.ValidatorSet, map[int64]privKeys) { t.Helper() var ( headers = make(map[int64]*types.SignedHeader, numBlocks) valset = make(map[int64]*types.ValidatorSet, numBlocks+1) keymap = make(map[int64]privKeys, numBlocks+1) keys = genPrivKeys(valSize) totalVariation = valVariation valVariationInt int newKeys privKeys ) valVariationInt = int(totalVariation) totalVariation = -float32(valVariationInt) newKeys = keys.ChangeKeys(valVariationInt) keymap[1] = keys keymap[2] = newKeys // genesis header and vals lastHeader := keys.GenSignedHeader(t, chainID, 1, bTime.Add(1*time.Minute), nil, keys.ToValidators(2, 0), newKeys.ToValidators(2, 0), hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)) currentHeader := lastHeader headers[1] = currentHeader valset[1] = keys.ToValidators(2, 0) keys = newKeys for height := int64(2); height <= numBlocks; height++ { totalVariation += valVariation valVariationInt = int(totalVariation) totalVariation = -float32(valVariationInt) newKeys = keys.ChangeKeys(valVariationInt) currentHeader = keys.GenSignedHeaderLastBlockID(t, chainID, height, bTime.Add(time.Duration(height)*time.Minute), nil, keys.ToValidators(2, 0), newKeys.ToValidators(2, 0), hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.BlockID{Hash: lastHeader.Hash()}) headers[height] = currentHeader valset[height] = keys.ToValidators(2, 0) lastHeader = currentHeader keys = newKeys keymap[height+1] = keys } return headers, valset, keymap } func mockNodeFromHeadersAndVals(headers map[int64]*types.SignedHeader, vals map[int64]*types.ValidatorSet) *provider_mocks.Provider { mockNode := &provider_mocks.Provider{} for i, header := range headers { lb := &types.LightBlock{SignedHeader: header, ValidatorSet: vals[i]} mockNode.On("LightBlock", mock.Anything, i).Return(lb, nil) } return mockNode } func hash(s string) []byte { return tmhash.Sum([]byte(s)) }