github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/light/helpers_test.go (about)

     1  package light_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"time"
     7  
     8  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
     9  	tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
    10  
    11  	"github.com/line/ostracon/crypto"
    12  	"github.com/line/ostracon/crypto/ed25519"
    13  	"github.com/line/ostracon/crypto/tmhash"
    14  	tmbytes "github.com/line/ostracon/libs/bytes"
    15  	"github.com/line/ostracon/libs/rand"
    16  	"github.com/line/ostracon/types"
    17  	tmtime "github.com/line/ostracon/types/time"
    18  	"github.com/line/ostracon/version"
    19  )
    20  
    21  // privKeys is a helper type for testing.
    22  //
    23  // It lets us simulate signing with many keys.  The main use case is to create
    24  // a set, and call GenSignedHeader to get properly signed header for testing.
    25  //
    26  // You can set different weights of validators each time you call ToValidators,
    27  // and can optionally extend the validator set later with Extend.
    28  type privKeys []crypto.PrivKey
    29  
    30  // genPrivKeys produces an array of private keys to generate commits.
    31  func genPrivKeys(n int) privKeys {
    32  	res := make(privKeys, n)
    33  	for i := range res {
    34  		res[i] = ed25519.GenPrivKey()
    35  	}
    36  	return res
    37  }
    38  
    39  // // Change replaces the key at index i.
    40  // func (pkz privKeys) Change(i int) privKeys {
    41  // 	res := make(privKeys, len(pkz))
    42  // 	copy(res, pkz)
    43  // 	res[i] = ed25519.GenPrivKey()
    44  // 	return res
    45  // }
    46  
    47  // Extend adds n more keys (to remove, just take a slice).
    48  func (pkz privKeys) Extend(n int) privKeys {
    49  	extra := genPrivKeys(n)
    50  	return append(pkz, extra...)
    51  }
    52  
    53  // // GenSecpPrivKeys produces an array of secp256k1 private keys to generate commits.
    54  // func GenSecpPrivKeys(n int) privKeys {
    55  // 	res := make(privKeys, n)
    56  // 	for i := range res {
    57  // 		res[i] = secp256k1.GenPrivKey()
    58  // 	}
    59  // 	return res
    60  // }
    61  
    62  // // ExtendSecp adds n more secp256k1 keys (to remove, just take a slice).
    63  // func (pkz privKeys) ExtendSecp(n int) privKeys {
    64  // 	extra := GenSecpPrivKeys(n)
    65  // 	return append(pkz, extra...)
    66  // }
    67  
    68  // ToValidators produces a valset from the set of keys.
    69  // The first key has weight `init` and it increases by `inc` every step
    70  // so we can have all the same weight, or a simple linear distribution
    71  // (should be enough for testing).
    72  func (pkz privKeys) ToValidators(init, inc int64) *types.ValidatorSet {
    73  	res := make([]*types.Validator, len(pkz))
    74  	for i, k := range pkz {
    75  		res[i] = types.NewValidator(k.PubKey(), init+int64(i)*inc)
    76  	}
    77  	return types.NewValidatorSet(res)
    78  }
    79  
    80  // signHeader properly signs the header with all keys from first to last exclusive.
    81  func (pkz privKeys) signHeader(header *types.Header, valSet *types.ValidatorSet, first, last int) *types.Commit {
    82  	commitSigs := make([]types.CommitSig, len(pkz))
    83  	for i := 0; i < len(pkz); i++ {
    84  		commitSigs[i] = types.NewCommitSigAbsent()
    85  	}
    86  
    87  	blockID := types.BlockID{
    88  		Hash:          header.Hash(),
    89  		PartSetHeader: types.PartSetHeader{Total: 1, Hash: crypto.CRandBytes(32)},
    90  	}
    91  
    92  	// Fill in the votes we want.
    93  	for i := first; i < last && i < len(pkz); i++ {
    94  		vote := makeVote(header, valSet, pkz[i], blockID)
    95  		commitSigs[vote.ValidatorIndex] = vote.CommitSig()
    96  	}
    97  
    98  	return types.NewCommit(header.Height, 1, blockID, commitSigs)
    99  }
   100  
   101  func (pkz privKeys) signHeaderByRate(header *types.Header, valSet *types.ValidatorSet, rate float64) *types.Commit {
   102  	commitSigs := make([]types.CommitSig, len(pkz))
   103  	for i := 0; i < len(pkz); i++ {
   104  		commitSigs[i] = types.NewCommitSigAbsent()
   105  	}
   106  
   107  	blockID := types.BlockID{
   108  		Hash:          header.Hash(),
   109  		PartSetHeader: types.PartSetHeader{Total: 1, Hash: crypto.CRandBytes(32)},
   110  	}
   111  
   112  	// Fill in the votes we want.
   113  	until := int64(float64(valSet.TotalVotingPower()) * rate)
   114  	sum := int64(0)
   115  	for i := 0; i < len(pkz); i++ {
   116  		_, val := valSet.GetByAddress(pkz[i].PubKey().Address())
   117  		if val == nil {
   118  			continue
   119  		}
   120  		vote := makeVote(header, valSet, pkz[i], blockID)
   121  		commitSigs[vote.ValidatorIndex] = vote.CommitSig()
   122  
   123  		sum += val.VotingPower
   124  		if sum > until {
   125  			break
   126  		}
   127  	}
   128  
   129  	return types.NewCommit(header.Height, 1, blockID, commitSigs)
   130  }
   131  
   132  func makeVote(header *types.Header, valset *types.ValidatorSet,
   133  	key crypto.PrivKey, blockID types.BlockID) *types.Vote {
   134  
   135  	addr := key.PubKey().Address()
   136  	idx, _ := valset.GetByAddress(addr)
   137  	if idx < 0 {
   138  		panic(fmt.Sprintf("address %X is not contained in ValSet: %+v", addr, valset))
   139  	}
   140  	vote := &types.Vote{
   141  		ValidatorAddress: addr,
   142  		ValidatorIndex:   idx,
   143  		Height:           header.Height,
   144  		Round:            1,
   145  		Timestamp:        tmtime.Now(),
   146  		Type:             tmproto.PrecommitType,
   147  		BlockID:          blockID,
   148  	}
   149  
   150  	v := vote.ToProto()
   151  	// Sign it
   152  	signBytes := types.VoteSignBytes(header.ChainID, v)
   153  	sig, err := key.Sign(signBytes)
   154  	if err != nil {
   155  		panic(err)
   156  	}
   157  
   158  	vote.Signature = sig
   159  
   160  	return vote
   161  }
   162  
   163  func genHeader(chainID string, height int64, bTime time.Time, txs types.Txs,
   164  	valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, proof tmbytes.HexBytes) *types.Header {
   165  
   166  	return &types.Header{
   167  		Version: tmversion.Consensus{Block: version.BlockProtocol, App: version.AppProtocol},
   168  		ChainID: chainID,
   169  		Height:  height,
   170  		Time:    bTime,
   171  		// LastBlockID
   172  		// LastCommitHash
   173  		ValidatorsHash:     valset.Hash(),
   174  		NextValidatorsHash: nextValset.Hash(),
   175  		DataHash:           txs.Hash(),
   176  		AppHash:            appHash,
   177  		ConsensusHash:      consHash,
   178  		LastResultsHash:    resHash,
   179  		ProposerAddress:    valset.SelectProposer(proof, height, 0).Address,
   180  	}
   181  }
   182  
   183  // GenSignedHeader calls genHeader and signHeader and combines them into a SignedHeader.
   184  func (pkz privKeys) GenSignedHeader(chainID string, height int64, bTime time.Time, txs types.Txs,
   185  	valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte,
   186  	first, last int) *types.SignedHeader {
   187  
   188  	secret := [64]byte{}
   189  	privateKey := ed25519.GenPrivKeyFromSecret(secret[:])
   190  	message := rand.Bytes(10)
   191  	proof, _ := privateKey.VRFProve(message)
   192  
   193  	header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash,
   194  		tmbytes.HexBytes(proof))
   195  	return &types.SignedHeader{
   196  		Header: header,
   197  		Commit: pkz.signHeader(header, valset, first, last),
   198  	}
   199  }
   200  
   201  func (pkz privKeys) GenSignedHeaderByRate(chainID string, height int64, bTime time.Time, txs types.Txs,
   202  	valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte,
   203  	rate float64) *types.SignedHeader {
   204  
   205  	secret := [64]byte{}
   206  	privateKey := ed25519.GenPrivKeyFromSecret(secret[:])
   207  	message := rand.Bytes(10)
   208  	proof, _ := privateKey.VRFProve(message)
   209  
   210  	header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash,
   211  		tmbytes.HexBytes(proof))
   212  	return &types.SignedHeader{
   213  		Header: header,
   214  		Commit: pkz.signHeaderByRate(header, valset, rate),
   215  	}
   216  }
   217  
   218  // GenSignedHeaderLastBlockID calls genHeader and signHeader and combines them into a SignedHeader.
   219  func (pkz privKeys) GenSignedHeaderLastBlockID(chainID string, height int64, bTime time.Time, txs types.Txs,
   220  	valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int,
   221  	lastBlockID types.BlockID) *types.SignedHeader {
   222  
   223  	secret := [64]byte{}
   224  	privateKey := ed25519.GenPrivKeyFromSecret(secret[:])
   225  	message := rand.Bytes(10)
   226  	proof, _ := privateKey.VRFProve(message)
   227  
   228  	header := genHeader(chainID, height, bTime, txs, valset, nextValset, appHash, consHash, resHash,
   229  		tmbytes.HexBytes(proof))
   230  	header.LastBlockID = lastBlockID
   231  	return &types.SignedHeader{
   232  		Header: header,
   233  		Commit: pkz.signHeader(header, valset, first, last),
   234  	}
   235  }
   236  
   237  func (pkz privKeys) ChangeKeys(delta int) privKeys {
   238  	newKeys := pkz[delta:]
   239  	return newKeys.Extend(delta)
   240  }
   241  
   242  func genCalcValVariationFunc() func(valVariation float32) int {
   243  	totalVariation := float32(0)
   244  	valVariationInt := int(totalVariation)
   245  	return func(valVariation float32) int {
   246  		totalVariation += valVariation
   247  		valVariationInt = int(totalVariation)
   248  		totalVariation = -float32(valVariationInt)
   249  		return valVariationInt
   250  	}
   251  }
   252  
   253  func genMockNodeWithKey(
   254  	chainID string,
   255  	height int64,
   256  	txs types.Txs,
   257  	keys, newKeys privKeys,
   258  	valSet, newValSet *types.ValidatorSet,
   259  	lastHeader *types.SignedHeader,
   260  	bTime time.Time,
   261  	first, last int,
   262  	valVariation float32,
   263  	calcValVariation func(valVariation float32) int) (
   264  	*types.SignedHeader, *types.ValidatorSet, privKeys) {
   265  
   266  	if newKeys == nil {
   267  		newKeys = keys.ChangeKeys(calcValVariation(valVariation))
   268  	}
   269  	if valSet == nil {
   270  		valSet = keys.ToValidators(2, 2)
   271  	}
   272  	if newValSet == nil {
   273  		newValSet = newKeys.ToValidators(2, 2)
   274  	}
   275  	if lastHeader == nil {
   276  		header := keys.GenSignedHeader(
   277  			chainID, height, bTime,
   278  			txs, valSet, newValSet,
   279  			hash("app_hash"), hash("cons_hash"), hash("results_hash"),
   280  			first, last,
   281  		)
   282  		return header, valSet, newKeys
   283  	}
   284  	header := keys.GenSignedHeaderLastBlockID(
   285  		chainID, height, bTime,
   286  		nil, valSet, newValSet,
   287  		hash("app_hash"), hash("cons_hash"), hash("results_hash"),
   288  		0, len(keys),
   289  		types.BlockID{Hash: lastHeader.Hash()},
   290  	)
   291  	return header, valSet, newKeys
   292  }
   293  
   294  // Generates the header and validator set to create a full entire mock node with blocks to height (
   295  // blockSize) and with variation in validator sets. BlockIntervals are in per minute.
   296  // NOTE: Expected to have a large validator set size ~ 100 validators.
   297  func genMockNodeWithKeys(chainID string, blockSize int64, valSize int, valVariation float32, bTime time.Time) (
   298  	map[int64]*types.SignedHeader,
   299  	map[int64]*types.ValidatorSet,
   300  	map[int64]privKeys) {
   301  
   302  	var (
   303  		headers = make(map[int64]*types.SignedHeader, blockSize)
   304  		valSet  = make(map[int64]*types.ValidatorSet, blockSize+1)
   305  		keymap  = make(map[int64]privKeys, blockSize+1)
   306  	)
   307  
   308  	setter := func(height int64,
   309  		header *types.SignedHeader, vals *types.ValidatorSet, newKeys privKeys) {
   310  		headers[height] = header
   311  		valSet[height] = vals
   312  		keymap[height+1] = newKeys
   313  	}
   314  
   315  	height := int64(1)
   316  	keys := genPrivKeys(valSize)
   317  	keymap[height] = keys
   318  	calcValVariationFunc := genCalcValVariationFunc()
   319  
   320  	header, vals, newKeys := genMockNodeWithKey(chainID, height, nil,
   321  		keys, nil,
   322  		nil, nil, nil,
   323  		bTime.Add(time.Duration(height)*time.Minute),
   324  		0, len(keys),
   325  		valVariation, calcValVariationFunc)
   326  
   327  	// genesis header and vals
   328  	setter(height, header, vals, newKeys)
   329  
   330  	keys = newKeys
   331  	lastHeader := header
   332  
   333  	for height := int64(2); height <= blockSize; height++ {
   334  		header, vals, newKeys := genMockNodeWithKey(chainID, height, nil,
   335  			keys, nil,
   336  			nil, nil, lastHeader,
   337  			bTime.Add(time.Duration(height)*time.Minute),
   338  			0, len(keys),
   339  			valVariation, calcValVariationFunc)
   340  		if !bytes.Equal(header.Hash(), header.Commit.BlockID.Hash) {
   341  			panic(fmt.Sprintf("commit hash didn't match: %X != %X", header.Hash(), header.Commit.BlockID.Hash))
   342  		}
   343  		setter(height, header, vals, newKeys)
   344  
   345  		keys = newKeys
   346  		lastHeader = header
   347  	}
   348  
   349  	return headers, valSet, keymap
   350  }
   351  
   352  func genMockNode(
   353  	chainID string,
   354  	blockSize int64,
   355  	valSize int,
   356  	valVariation float32,
   357  	bTime time.Time) (
   358  	string,
   359  	map[int64]*types.SignedHeader,
   360  	map[int64]*types.ValidatorSet) {
   361  	headers, valset, _ := genMockNodeWithKeys(chainID, blockSize, valSize, valVariation, bTime)
   362  	return chainID, headers, valset
   363  }
   364  
   365  func hash(s string) []byte {
   366  	return tmhash.Sum([]byte(s))
   367  }