github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/test/e2e/app/app.go (about) 1 package app 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "encoding/binary" 8 "errors" 9 "fmt" 10 "math/rand" 11 "path/filepath" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/ari-anchor/sei-tendermint/abci/example/code" 19 abci "github.com/ari-anchor/sei-tendermint/abci/types" 20 "github.com/ari-anchor/sei-tendermint/crypto" 21 "github.com/ari-anchor/sei-tendermint/libs/log" 22 "github.com/ari-anchor/sei-tendermint/proto/tendermint/types" 23 "github.com/ari-anchor/sei-tendermint/version" 24 ) 25 26 const ( 27 voteExtensionKey string = "extensionSum" 28 voteExtensionMaxVal int64 = 128 29 ) 30 31 // Application is an ABCI application for use by end-to-end tests. It is a 32 // simple key/value store for strings, storing data in memory and persisting 33 // to disk as JSON, taking state sync snapshots if requested. 34 type Application struct { 35 abci.BaseApplication 36 mu sync.Mutex 37 logger log.Logger 38 state *State 39 snapshots *SnapshotStore 40 cfg *Config 41 restoreSnapshot *abci.Snapshot 42 restoreChunks [][]byte 43 } 44 45 // Config allows for the setting of high level parameters for running the e2e Application 46 // KeyType and ValidatorUpdates must be the same for all nodes running the same application. 47 type Config struct { 48 // The directory with which state.json will be persisted in. Usually $HOME/.tendermint/data 49 Dir string `toml:"dir"` 50 51 // SnapshotInterval specifies the height interval at which the application 52 // will take state sync snapshots. Defaults to 0 (disabled). 53 SnapshotInterval uint64 `toml:"snapshot_interval"` 54 55 // RetainBlocks specifies the number of recent blocks to retain. Defaults to 56 // 0, which retains all blocks. Must be greater that PersistInterval, 57 // SnapshotInterval and EvidenceAgeHeight. 58 RetainBlocks uint64 `toml:"retain_blocks"` 59 60 // KeyType sets the curve that will be used by validators. 61 // Options are ed25519 & secp256k1 62 KeyType string `toml:"key_type"` 63 64 // PersistInterval specifies the height interval at which the application 65 // will persist state to disk. Defaults to 1 (every height), setting this to 66 // 0 disables state persistence. 67 PersistInterval uint64 `toml:"persist_interval"` 68 69 // ValidatorUpdates is a map of heights to validator names and their power, 70 // and will be returned by the ABCI application. For example, the following 71 // changes the power of validator01 and validator02 at height 1000: 72 // 73 // [validator_update.1000] 74 // validator01 = 20 75 // validator02 = 10 76 // 77 // Specifying height 0 returns the validator update during InitChain. The 78 // application returns the validator updates as-is, i.e. removing a 79 // validator must be done by returning it with power 0, and any validators 80 // not specified are not changed. 81 // 82 // height <-> pubkey <-> voting power 83 ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"` 84 85 // Add artificial delays to each of the main ABCI calls to mimic computation time 86 // of the application 87 PrepareProposalDelayMS uint64 `toml:"prepare_proposal_delay_ms"` 88 ProcessProposalDelayMS uint64 `toml:"process_proposal_delay_ms"` 89 CheckTxDelayMS uint64 `toml:"check_tx_delay_ms"` 90 VoteExtensionDelayMS uint64 `toml:"vote_extension_delay_ms"` 91 FinalizeBlockDelayMS uint64 `toml:"finalize_block_delay_ms"` 92 } 93 94 func DefaultConfig(dir string) *Config { 95 return &Config{ 96 PersistInterval: 1, 97 SnapshotInterval: 100, 98 Dir: dir, 99 } 100 } 101 102 // NewApplication creates the application. 103 func NewApplication(cfg *Config) (*Application, error) { 104 state, err := NewState(cfg.Dir, cfg.PersistInterval) 105 if err != nil { 106 return nil, err 107 } 108 snapshots, err := NewSnapshotStore(filepath.Join(cfg.Dir, "snapshots")) 109 if err != nil { 110 return nil, err 111 } 112 logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo) 113 if err != nil { 114 return nil, err 115 } 116 117 return &Application{ 118 logger: logger, 119 state: state, 120 snapshots: snapshots, 121 cfg: cfg, 122 }, nil 123 } 124 125 // Info implements ABCI. 126 func (app *Application) Info(_ context.Context, req *abci.RequestInfo) (*abci.ResponseInfo, error) { 127 app.mu.Lock() 128 defer app.mu.Unlock() 129 130 return &abci.ResponseInfo{ 131 Version: version.ABCIVersion, 132 AppVersion: 1, 133 LastBlockHeight: int64(app.state.Height), 134 LastBlockAppHash: app.state.Hash, 135 }, nil 136 } 137 138 // Info implements ABCI. 139 func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { 140 app.mu.Lock() 141 defer app.mu.Unlock() 142 143 var err error 144 app.state.initialHeight = uint64(req.InitialHeight) 145 if len(req.AppStateBytes) > 0 { 146 err = app.state.Import(0, req.AppStateBytes) 147 if err != nil { 148 panic(err) 149 } 150 } 151 resp := &abci.ResponseInitChain{ 152 AppHash: app.state.Hash, 153 ConsensusParams: &types.ConsensusParams{ 154 Version: &types.VersionParams{ 155 AppVersion: 1, 156 }, 157 }, 158 } 159 if resp.Validators, err = app.validatorUpdates(0); err != nil { 160 panic(err) 161 } 162 return resp, nil 163 } 164 165 // CheckTx implements ABCI. 166 func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { 167 app.mu.Lock() 168 defer app.mu.Unlock() 169 170 _, _, err := parseTx(req.Tx) 171 if err != nil { 172 return &abci.ResponseCheckTx{ 173 Code: code.CodeTypeEncodingError, 174 }, nil 175 } 176 177 if app.cfg.CheckTxDelayMS != 0 { 178 time.Sleep(time.Duration(app.cfg.CheckTxDelayMS) * time.Millisecond) 179 } 180 181 return &abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil 182 } 183 184 // FinalizeBlock implements ABCI. 185 func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { 186 var txs = make([]*abci.ExecTxResult, len(req.Txs)) 187 188 app.mu.Lock() 189 defer app.mu.Unlock() 190 191 for i, tx := range req.Txs { 192 key, value, err := parseTx(tx) 193 if err != nil { 194 panic(err) // shouldn't happen since we verified it in CheckTx 195 } 196 app.state.Set(key, value) 197 198 txs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK} 199 } 200 201 valUpdates, err := app.validatorUpdates(uint64(req.Height)) 202 if err != nil { 203 panic(err) 204 } 205 206 if app.cfg.FinalizeBlockDelayMS != 0 { 207 time.Sleep(time.Duration(app.cfg.FinalizeBlockDelayMS) * time.Millisecond) 208 } 209 210 return &abci.ResponseFinalizeBlock{ 211 TxResults: txs, 212 ValidatorUpdates: valUpdates, 213 AppHash: app.state.Finalize(), 214 Events: []abci.Event{ 215 { 216 Type: "val_updates", 217 Attributes: []abci.EventAttribute{ 218 { 219 Key: []byte("size"), 220 Value: []byte(strconv.Itoa(valUpdates.Len())), 221 }, 222 { 223 Key: []byte("height"), 224 Value: []byte(strconv.Itoa(int(req.Height))), 225 }, 226 }, 227 }, 228 }, 229 }, nil 230 } 231 232 // Commit implements ABCI. 233 func (app *Application) Commit(_ context.Context) (*abci.ResponseCommit, error) { 234 app.mu.Lock() 235 defer app.mu.Unlock() 236 237 height, err := app.state.Commit() 238 if err != nil { 239 panic(err) 240 } 241 if app.cfg.SnapshotInterval > 0 && height%app.cfg.SnapshotInterval == 0 { 242 snapshot, err := app.snapshots.Create(app.state) 243 if err != nil { 244 panic(err) 245 } 246 app.logger.Info("created state sync snapshot", "height", snapshot.Height) 247 err = app.snapshots.Prune(maxSnapshotCount) 248 if err != nil { 249 app.logger.Error("failed to prune snapshots", "err", err) 250 } 251 } 252 retainHeight := int64(0) 253 if app.cfg.RetainBlocks > 0 { 254 retainHeight = int64(height - app.cfg.RetainBlocks + 1) 255 } 256 return &abci.ResponseCommit{ 257 RetainHeight: retainHeight, 258 }, nil 259 } 260 261 // Query implements ABCI. 262 func (app *Application) Query(_ context.Context, req *abci.RequestQuery) (*abci.ResponseQuery, error) { 263 app.mu.Lock() 264 defer app.mu.Unlock() 265 266 return &abci.ResponseQuery{ 267 Height: int64(app.state.Height), 268 Key: req.Data, 269 Value: []byte(app.state.Get(string(req.Data))), 270 }, nil 271 } 272 273 // ListSnapshots implements ABCI. 274 func (app *Application) ListSnapshots(_ context.Context, req *abci.RequestListSnapshots) (*abci.ResponseListSnapshots, error) { 275 app.mu.Lock() 276 defer app.mu.Unlock() 277 278 snapshots, err := app.snapshots.List() 279 if err != nil { 280 panic(err) 281 } 282 return &abci.ResponseListSnapshots{Snapshots: snapshots}, nil 283 } 284 285 // LoadSnapshotChunk implements ABCI. 286 func (app *Application) LoadSnapshotChunk(_ context.Context, req *abci.RequestLoadSnapshotChunk) (*abci.ResponseLoadSnapshotChunk, error) { 287 app.mu.Lock() 288 defer app.mu.Unlock() 289 290 chunk, err := app.snapshots.LoadChunk(req.Height, req.Format, req.Chunk) 291 if err != nil { 292 panic(err) 293 } 294 return &abci.ResponseLoadSnapshotChunk{Chunk: chunk}, nil 295 } 296 297 // OfferSnapshot implements ABCI. 298 func (app *Application) OfferSnapshot(_ context.Context, req *abci.RequestOfferSnapshot) (*abci.ResponseOfferSnapshot, error) { 299 app.mu.Lock() 300 defer app.mu.Unlock() 301 302 if app.restoreSnapshot != nil { 303 panic("A snapshot is already being restored") 304 } 305 app.restoreSnapshot = req.Snapshot 306 app.restoreChunks = [][]byte{} 307 return &abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}, nil 308 } 309 310 // ApplySnapshotChunk implements ABCI. 311 func (app *Application) ApplySnapshotChunk(_ context.Context, req *abci.RequestApplySnapshotChunk) (*abci.ResponseApplySnapshotChunk, error) { 312 app.mu.Lock() 313 defer app.mu.Unlock() 314 315 if app.restoreSnapshot == nil { 316 panic("No restore in progress") 317 } 318 app.restoreChunks = append(app.restoreChunks, req.Chunk) 319 if len(app.restoreChunks) == int(app.restoreSnapshot.Chunks) { 320 bz := []byte{} 321 for _, chunk := range app.restoreChunks { 322 bz = append(bz, chunk...) 323 } 324 err := app.state.Import(app.restoreSnapshot.Height, bz) 325 if err != nil { 326 panic(err) 327 } 328 app.restoreSnapshot = nil 329 app.restoreChunks = nil 330 } 331 return &abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}, nil 332 } 333 334 // PrepareProposal will take the given transactions and attempt to prepare a 335 // proposal from them when it's our turn to do so. In the process, vote 336 // extensions from the previous round of consensus, if present, will be used to 337 // construct a special transaction whose value is the sum of all of the vote 338 // extensions from the previous round. 339 // 340 // NB: Assumes that the supplied transactions do not exceed `req.MaxTxBytes`. 341 // If adding a special vote extension-generated transaction would cause the 342 // total number of transaction bytes to exceed `req.MaxTxBytes`, we will not 343 // append our special vote extension transaction. 344 func (app *Application) PrepareProposal(_ context.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { 345 var sum int64 346 var extCount int 347 for _, vote := range req.LocalLastCommit.Votes { 348 if !vote.SignedLastBlock || len(vote.VoteExtension) == 0 { 349 continue 350 } 351 extValue, err := parseVoteExtension(vote.VoteExtension) 352 // This should have been verified in VerifyVoteExtension 353 if err != nil { 354 panic(fmt.Errorf("failed to parse vote extension in PrepareProposal: %w", err)) 355 } 356 valAddr := crypto.Address(vote.Validator.Address) 357 app.logger.Info("got vote extension value in PrepareProposal", "valAddr", valAddr, "value", extValue) 358 sum += extValue 359 extCount++ 360 } 361 // We only generate our special transaction if we have vote extensions 362 if extCount > 0 { 363 var totalBytes int64 364 extTxPrefix := fmt.Sprintf("%s=", voteExtensionKey) 365 extTx := []byte(fmt.Sprintf("%s%d", extTxPrefix, sum)) 366 app.logger.Info("preparing proposal with custom transaction from vote extensions", "tx", extTx) 367 // Our generated transaction takes precedence over any supplied 368 // transaction that attempts to modify the "extensionSum" value. 369 txRecords := make([]*abci.TxRecord, len(req.Txs)+1) 370 for i, tx := range req.Txs { 371 if strings.HasPrefix(string(tx), extTxPrefix) { 372 txRecords[i] = &abci.TxRecord{ 373 Action: abci.TxRecord_REMOVED, 374 Tx: tx, 375 } 376 } else { 377 txRecords[i] = &abci.TxRecord{ 378 Action: abci.TxRecord_UNMODIFIED, 379 Tx: tx, 380 } 381 totalBytes += int64(len(tx)) 382 } 383 } 384 if totalBytes+int64(len(extTx)) < req.MaxTxBytes { 385 txRecords[len(req.Txs)] = &abci.TxRecord{ 386 Action: abci.TxRecord_ADDED, 387 Tx: extTx, 388 } 389 } else { 390 app.logger.Info( 391 "too many txs to include special vote extension-generated tx", 392 "totalBytes", totalBytes, 393 "MaxTxBytes", req.MaxTxBytes, 394 "extTx", extTx, 395 "extTxLen", len(extTx), 396 ) 397 } 398 return &abci.ResponsePrepareProposal{ 399 TxRecords: txRecords, 400 }, nil 401 } 402 // None of the transactions are modified by this application. 403 trs := make([]*abci.TxRecord, 0, len(req.Txs)) 404 var totalBytes int64 405 for _, tx := range req.Txs { 406 totalBytes += int64(len(tx)) 407 if totalBytes > req.MaxTxBytes { 408 break 409 } 410 trs = append(trs, &abci.TxRecord{ 411 Action: abci.TxRecord_UNMODIFIED, 412 Tx: tx, 413 }) 414 } 415 416 if app.cfg.PrepareProposalDelayMS != 0 { 417 time.Sleep(time.Duration(app.cfg.PrepareProposalDelayMS) * time.Millisecond) 418 } 419 420 return &abci.ResponsePrepareProposal{TxRecords: trs}, nil 421 } 422 423 // ProcessProposal implements part of the Application interface. 424 // It accepts any proposal that does not contain a malformed transaction. 425 func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { 426 for _, tx := range req.Txs { 427 k, v, err := parseTx(tx) 428 if err != nil { 429 app.logger.Error("malformed transaction in ProcessProposal", "tx", tx, "err", err) 430 return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil 431 } 432 // Additional check for vote extension-related txs 433 if k == voteExtensionKey { 434 _, err := strconv.Atoi(v) 435 if err != nil { 436 app.logger.Error("malformed vote extension transaction", k, v, "err", err) 437 return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil 438 } 439 } 440 } 441 442 if app.cfg.ProcessProposalDelayMS != 0 { 443 time.Sleep(time.Duration(app.cfg.ProcessProposalDelayMS) * time.Millisecond) 444 } 445 446 return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil 447 } 448 449 // ExtendVote will produce vote extensions in the form of random numbers to 450 // demonstrate vote extension nondeterminism. 451 // 452 // In the next block, if there are any vote extensions from the previous block, 453 // a new transaction will be proposed that updates a special value in the 454 // key/value store ("extensionSum") with the sum of all of the numbers collected 455 // from the vote extensions. 456 func (app *Application) ExtendVote(_ context.Context, req *abci.RequestExtendVote) (*abci.ResponseExtendVote, error) { 457 // We ignore any requests for vote extensions that don't match our expected 458 // next height. 459 if req.Height != int64(app.state.Height)+1 { 460 app.logger.Error( 461 "got unexpected height in ExtendVote request", 462 "expectedHeight", app.state.Height+1, 463 "requestHeight", req.Height, 464 ) 465 return &abci.ResponseExtendVote{}, nil 466 } 467 ext := make([]byte, binary.MaxVarintLen64) 468 // We don't care that these values are generated by a weak random number 469 // generator. It's just for test purposes. 470 // nolint:gosec // G404: Use of weak random number generator 471 num := rand.Int63n(voteExtensionMaxVal) 472 extLen := binary.PutVarint(ext, num) 473 474 if app.cfg.VoteExtensionDelayMS != 0 { 475 time.Sleep(time.Duration(app.cfg.VoteExtensionDelayMS) * time.Millisecond) 476 } 477 478 app.logger.Info("generated vote extension", "num", num, "ext", fmt.Sprintf("%x", ext[:extLen]), "state.Height", app.state.Height) 479 return &abci.ResponseExtendVote{ 480 VoteExtension: ext[:extLen], 481 }, nil 482 } 483 484 // VerifyVoteExtension simply validates vote extensions from other validators 485 // without doing anything about them. In this case, it just makes sure that the 486 // vote extension is a well-formed integer value. 487 func (app *Application) VerifyVoteExtension(_ context.Context, req *abci.RequestVerifyVoteExtension) (*abci.ResponseVerifyVoteExtension, error) { 488 // We allow vote extensions to be optional 489 if len(req.VoteExtension) == 0 { 490 return &abci.ResponseVerifyVoteExtension{ 491 Status: abci.ResponseVerifyVoteExtension_ACCEPT, 492 }, nil 493 } 494 if req.Height != int64(app.state.Height)+1 { 495 app.logger.Error( 496 "got unexpected height in VerifyVoteExtension request", 497 "expectedHeight", app.state.Height, 498 "requestHeight", req.Height, 499 ) 500 return &abci.ResponseVerifyVoteExtension{ 501 Status: abci.ResponseVerifyVoteExtension_REJECT, 502 }, nil 503 } 504 505 num, err := parseVoteExtension(req.VoteExtension) 506 if err != nil { 507 app.logger.Error("failed to verify vote extension", "req", req, "err", err) 508 return &abci.ResponseVerifyVoteExtension{ 509 Status: abci.ResponseVerifyVoteExtension_REJECT, 510 }, nil 511 } 512 513 if app.cfg.VoteExtensionDelayMS != 0 { 514 time.Sleep(time.Duration(app.cfg.VoteExtensionDelayMS) * time.Millisecond) 515 } 516 517 app.logger.Info("verified vote extension value", "req", req, "num", num) 518 return &abci.ResponseVerifyVoteExtension{ 519 Status: abci.ResponseVerifyVoteExtension_ACCEPT, 520 }, nil 521 } 522 523 func (app *Application) Rollback() error { 524 app.mu.Lock() 525 defer app.mu.Unlock() 526 527 return app.state.Rollback() 528 } 529 530 // validatorUpdates generates a validator set update. 531 func (app *Application) validatorUpdates(height uint64) (abci.ValidatorUpdates, error) { 532 updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)] 533 if len(updates) == 0 { 534 return nil, nil 535 } 536 537 valUpdates := abci.ValidatorUpdates{} 538 for keyString, power := range updates { 539 540 keyBytes, err := base64.StdEncoding.DecodeString(keyString) 541 if err != nil { 542 return nil, fmt.Errorf("invalid base64 pubkey value %q: %w", keyString, err) 543 } 544 valUpdates = append(valUpdates, abci.UpdateValidator(keyBytes, int64(power), app.cfg.KeyType)) 545 } 546 547 // the validator updates could be returned in arbitrary order, 548 // and that seems potentially bad. This orders the validator 549 // set. 550 sort.Slice(valUpdates, func(i, j int) bool { 551 return valUpdates[i].PubKey.Compare(valUpdates[j].PubKey) < 0 552 }) 553 554 return valUpdates, nil 555 } 556 557 // parseTx parses a tx in 'key=value' format into a key and value. 558 func parseTx(tx []byte) (string, string, error) { 559 parts := bytes.Split(tx, []byte("=")) 560 if len(parts) != 2 { 561 return "", "", fmt.Errorf("invalid tx format: %q", string(tx)) 562 } 563 if len(parts[0]) == 0 { 564 return "", "", errors.New("key cannot be empty") 565 } 566 return string(parts[0]), string(parts[1]), nil 567 } 568 569 // parseVoteExtension attempts to parse the given extension data into a positive 570 // integer value. 571 func parseVoteExtension(ext []byte) (int64, error) { 572 num, errVal := binary.Varint(ext) 573 if errVal == 0 { 574 return 0, errors.New("vote extension is too small to parse") 575 } 576 if errVal < 0 { 577 return 0, errors.New("vote extension value is too large") 578 } 579 if num >= voteExtensionMaxVal { 580 return 0, fmt.Errorf("vote extension value must be smaller than %d (was %d)", voteExtensionMaxVal, num) 581 } 582 return num, nil 583 }