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 }