github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/test/e2e/app/app.go (about) 1 package app 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "errors" 7 "fmt" 8 "os" 9 "path/filepath" 10 "strconv" 11 "time" 12 13 abci "github.com/tendermint/tendermint/abci/types" 14 "github.com/tendermint/tendermint/proto/tendermint/crypto" 15 16 "github.com/line/ostracon/abci/example/code" 17 ocabci "github.com/line/ostracon/abci/types" 18 cryptoenc "github.com/line/ostracon/crypto/encoding" 19 "github.com/line/ostracon/libs/log" 20 "github.com/line/ostracon/version" 21 ) 22 23 const E2EAppVersion = 999 24 25 // Application is an ABCI application for use by end-to-end tests. It is a 26 // simple key/value store for strings, storing data in memory and persisting 27 // to disk as JSON, taking state sync snapshots if requested. 28 29 type Application struct { 30 ocabci.BaseApplication 31 logger log.Logger 32 state *State 33 snapshots *SnapshotStore 34 cfg *Config 35 restoreSnapshot *abci.Snapshot 36 restoreChunks [][]byte 37 } 38 39 // Config allows for the setting of high level parameters for running the e2e Application 40 // KeyType and ValidatorUpdates must be the same for all nodes running the same application. 41 type Config struct { 42 // The directory with which state.json will be persisted in. Usually $HOME/.ostracon/data 43 Dir string `toml:"dir"` 44 45 // SnapshotInterval specifies the height interval at which the application 46 // will take state sync snapshots. Defaults to 0 (disabled). 47 SnapshotInterval uint64 `toml:"snapshot_interval"` 48 49 // RetainBlocks specifies the number of recent blocks to retain. Defaults to 50 // 0, which retains all blocks. Must be greater that PersistInterval, 51 // SnapshotInterval and EvidenceAgeHeight. 52 RetainBlocks uint64 `toml:"retain_blocks"` 53 54 // KeyType sets the curve that will be used by validators. 55 // Options are ed25519 & secp256k1 56 KeyType string `toml:"key_type"` 57 58 // PersistInterval specifies the height interval at which the application 59 // will persist state to disk. Defaults to 1 (every height), setting this to 60 // 0 disables state persistence. 61 PersistInterval uint64 `toml:"persist_interval"` 62 63 // ValidatorUpdates is a map of heights to validator names and their power, 64 // and will be returned by the ABCI application. For example, the following 65 // changes the power of validator01 and validator02 at height 1000: 66 // 67 // [validator_update.1000] 68 // validator01 = 20 69 // validator02 = 10 70 // 71 // Specifying height 0 returns the validator update during InitChain. The 72 // application returns the validator updates as-is, i.e. removing a 73 // validator must be done by returning it with power 0, and any validators 74 // not specified are not changed. 75 // 76 // height <-> pubkey <-> voting power 77 ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"` 78 79 // Add artificial delays to each of the main ABCI calls to mimic computation time 80 // of the application 81 PrepareProposalDelay time.Duration `toml:"prepare_proposal_delay"` 82 ProcessProposalDelay time.Duration `toml:"process_proposal_delay"` 83 CheckTxDelay time.Duration `toml:"check_tx_delay"` 84 // TODO: add vote extension and finalize block delays once completed (@cmwaters) 85 } 86 87 func DefaultConfig(dir string) *Config { 88 return &Config{ 89 PersistInterval: 1, 90 SnapshotInterval: 100, 91 Dir: dir, 92 } 93 } 94 95 // NewApplication creates the application. 96 func NewApplication(cfg *Config) (*Application, error) { 97 state, err := NewState(cfg.Dir, cfg.PersistInterval) 98 if err != nil { 99 return nil, err 100 } 101 snapshots, err := NewSnapshotStore(filepath.Join(cfg.Dir, "snapshots")) 102 if err != nil { 103 return nil, err 104 } 105 return &Application{ 106 logger: log.NewOCLogger(log.NewSyncWriter(os.Stdout)), 107 state: state, 108 snapshots: snapshots, 109 cfg: cfg, 110 }, nil 111 } 112 113 // Info implements ABCI. 114 func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo { 115 return abci.ResponseInfo{ 116 Version: version.ABCIVersion, 117 AppVersion: E2EAppVersion, 118 LastBlockHeight: int64(app.state.Height), 119 LastBlockAppHash: app.state.Hash, 120 } 121 } 122 123 // Info implements ABCI. 124 func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { 125 var err error 126 app.state.initialHeight = uint64(req.InitialHeight) 127 if len(req.AppStateBytes) > 0 { 128 err = app.state.Import(0, req.AppStateBytes) 129 if err != nil { 130 panic(err) 131 } 132 } 133 resp := abci.ResponseInitChain{ 134 AppHash: app.state.Hash, 135 } 136 if resp.Validators, err = app.validatorUpdates(0); err != nil { 137 panic(err) 138 } 139 return resp 140 } 141 142 // CheckTx implements ABCI. 143 func (app *Application) CheckTx(req abci.RequestCheckTx) ocabci.ResponseCheckTx { 144 _, _, err := parseTx(req.Tx) 145 if err != nil { 146 return ocabci.ResponseCheckTx{ 147 Code: code.CodeTypeEncodingError, 148 Log: err.Error(), 149 } 150 } 151 152 if app.cfg.CheckTxDelay != 0 { 153 time.Sleep(app.cfg.CheckTxDelay) 154 } 155 156 return ocabci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} 157 } 158 159 // DeliverTx implements ABCI. 160 func (app *Application) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { 161 key, value, err := parseTx(req.Tx) 162 if err != nil { 163 panic(err) // shouldn't happen since we verified it in CheckTx 164 } 165 app.state.Set(key, value) 166 return abci.ResponseDeliverTx{Code: code.CodeTypeOK} 167 } 168 169 // EndBlock implements ABCI. 170 func (app *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { 171 valUpdates, err := app.validatorUpdates(uint64(req.Height)) 172 if err != nil { 173 panic(err) 174 } 175 176 return abci.ResponseEndBlock{ 177 ValidatorUpdates: valUpdates, 178 Events: []abci.Event{ 179 { 180 Type: "val_updates", 181 Attributes: []abci.EventAttribute{ 182 { 183 Key: []byte("size"), 184 Value: []byte(strconv.Itoa(valUpdates.Len())), 185 }, 186 { 187 Key: []byte("height"), 188 Value: []byte(strconv.Itoa(int(req.Height))), 189 }, 190 }, 191 }, 192 }, 193 } 194 } 195 196 // Commit implements ABCI. 197 func (app *Application) Commit() abci.ResponseCommit { 198 height, hash, err := app.state.Commit() 199 if err != nil { 200 panic(err) 201 } 202 if app.cfg.SnapshotInterval > 0 && height%app.cfg.SnapshotInterval == 0 { 203 snapshot, err := app.snapshots.Create(app.state) 204 if err != nil { 205 panic(err) 206 } 207 app.logger.Info("Created state sync snapshot", "height", snapshot.Height) 208 } 209 retainHeight := int64(0) 210 if app.cfg.RetainBlocks > 0 { 211 retainHeight = int64(height - app.cfg.RetainBlocks + 1) 212 } 213 return abci.ResponseCommit{ 214 Data: hash, 215 RetainHeight: retainHeight, 216 } 217 } 218 219 // Query implements ABCI. 220 func (app *Application) Query(req abci.RequestQuery) abci.ResponseQuery { 221 return abci.ResponseQuery{ 222 Height: int64(app.state.Height), 223 Key: req.Data, 224 Value: []byte(app.state.Get(string(req.Data))), 225 } 226 } 227 228 // ListSnapshots implements ABCI. 229 func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots { 230 snapshots, err := app.snapshots.List() 231 if err != nil { 232 panic(err) 233 } 234 return abci.ResponseListSnapshots{Snapshots: snapshots} 235 } 236 237 // LoadSnapshotChunk implements ABCI. 238 func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk { 239 chunk, err := app.snapshots.LoadChunk(req.Height, req.Format, req.Chunk) 240 if err != nil { 241 panic(err) 242 } 243 return abci.ResponseLoadSnapshotChunk{Chunk: chunk} 244 } 245 246 // OfferSnapshot implements ABCI. 247 func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot { 248 if app.restoreSnapshot != nil { 249 panic("A snapshot is already being restored") 250 } 251 app.restoreSnapshot = req.Snapshot 252 app.restoreChunks = [][]byte{} 253 return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT} 254 } 255 256 // ApplySnapshotChunk implements ABCI. 257 func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk { 258 if app.restoreSnapshot == nil { 259 panic("No restore in progress") 260 } 261 app.restoreChunks = append(app.restoreChunks, req.Chunk) 262 if len(app.restoreChunks) == int(app.restoreSnapshot.Chunks) { 263 bz := []byte{} 264 for _, chunk := range app.restoreChunks { 265 bz = append(bz, chunk...) 266 } 267 err := app.state.Import(app.restoreSnapshot.Height, bz) 268 if err != nil { 269 panic(err) 270 } 271 app.restoreSnapshot = nil 272 app.restoreChunks = nil 273 } 274 return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT} 275 } 276 277 func (app *Application) Rollback() error { 278 return app.state.Rollback() 279 } 280 281 // validatorUpdates generates a validator set update. 282 func (app *Application) validatorUpdates(height uint64) (ocabci.ValidatorUpdates, error) { 283 updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)] 284 if len(updates) == 0 { 285 return nil, nil 286 } 287 288 valUpdates := ocabci.ValidatorUpdates{} 289 for keyString, power := range updates { 290 291 keyBytes, err := base64.StdEncoding.DecodeString(keyString) 292 if err != nil { 293 return nil, fmt.Errorf("invalid base64.enc pubkey %q: %w", keyString, err) 294 } 295 pubKeyProto := crypto.PublicKey{} 296 err = pubKeyProto.Unmarshal(keyBytes) 297 if err != nil { 298 return nil, fmt.Errorf("invalid marshalled pubkey %q: %w", keyString, err) 299 } 300 pubKey, err := cryptoenc.PubKeyFromProto(&pubKeyProto) 301 if err != nil { 302 return nil, fmt.Errorf("invalid crypto pubkey %q: %w", keyString, err) 303 } 304 valUpdates = append(valUpdates, ocabci.NewValidatorUpdate(pubKey, int64(power))) 305 } 306 return valUpdates, nil 307 } 308 309 // parseTx parses a tx in 'key=value' format into a key and value. 310 func parseTx(tx []byte) (string, string, error) { 311 parts := bytes.Split(tx, []byte("=")) 312 if len(parts) != 2 { 313 return "", "", fmt.Errorf("invalid tx format: %q", string(tx)) 314 } 315 if len(parts[0]) == 0 { 316 return "", "", errors.New("key cannot be empty") 317 } 318 return string(parts[0]), string(parts[1]), nil 319 }