github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/sdk/baseapp.go (about) 1 package sdk 2 3 import ( 4 "fmt" 5 "log/slog" 6 "os" 7 "runtime/debug" 8 "sort" 9 "strings" 10 "syscall" 11 12 "github.com/gnolang/gno/tm2/pkg/amino" 13 abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" 14 bft "github.com/gnolang/gno/tm2/pkg/bft/types" 15 dbm "github.com/gnolang/gno/tm2/pkg/db" 16 "github.com/gnolang/gno/tm2/pkg/errors" 17 "github.com/gnolang/gno/tm2/pkg/std" 18 "github.com/gnolang/gno/tm2/pkg/store" 19 ) 20 21 // Key to store the consensus params in the main store. 22 var ( 23 mainConsensusParamsKey = []byte("consensus_params") 24 mainLastHeaderKey = []byte("last_header") 25 ) 26 27 // BaseApp reflects the ABCI application implementation. 28 type BaseApp struct { 29 // initialized on creation 30 logger *slog.Logger 31 name string // application name from abci.Info 32 db dbm.DB // common DB backend 33 cms store.CommitMultiStore // Main (uncached) state 34 router Router // handle any kind of message 35 36 // set upon LoadVersion or LoadLatestVersion. 37 baseKey store.StoreKey // Base Store in cms (raw db, not hashed) 38 mainKey store.StoreKey // Main Store in cms (e.g. iavl, merkle-ized) 39 40 anteHandler AnteHandler // ante handler for fee and auth 41 initChainer InitChainer // initialize state with validators and state blob 42 beginBlocker BeginBlocker // logic to run before any txs 43 endBlocker EndBlocker // logic to run after all txs, and to determine valset changes 44 45 // -------------------- 46 // Volatile state 47 // checkState is set on initialization and reset on Commit. 48 // deliverState is set in InitChain and BeginBlock and cleared on Commit. 49 // See methods setCheckState and setDeliverState. 50 checkState *state // for CheckTx 51 deliverState *state // for DeliverTx 52 voteInfos []abci.VoteInfo // absent validators from begin block 53 54 // consensus params 55 // TODO: Move this in the future to baseapp param store on main store. 56 consensusParams *abci.ConsensusParams 57 58 // The minimum gas prices a validator is willing to accept for processing a 59 // transaction. This is mainly used for DoS and spam prevention. 60 minGasPrices []GasPrice 61 62 // flag for sealing options and parameters to a BaseApp 63 sealed bool // TODO: needed? 64 65 // block height at which to halt the chain and gracefully shutdown 66 haltHeight uint64 67 68 // minimum block time (in Unix seconds) at which to halt the chain and gracefully shutdown 69 haltTime uint64 70 71 // application's version string 72 appVersion string 73 } 74 75 var _ abci.Application = (*BaseApp)(nil) 76 77 // NewBaseApp returns a reference to an initialized BaseApp. It accepts a 78 // variadic number of option functions, which act on the BaseApp to set 79 // configuration choices. 80 // 81 // NOTE: The db is used to store the version number for now. 82 func NewBaseApp( 83 name string, 84 logger *slog.Logger, 85 db dbm.DB, 86 baseKey store.StoreKey, 87 mainKey store.StoreKey, 88 options ...func(*BaseApp), 89 ) *BaseApp { 90 app := &BaseApp{ 91 logger: logger, 92 name: name, 93 db: db, 94 cms: store.NewCommitMultiStore(db), 95 router: NewRouter(), 96 baseKey: baseKey, 97 mainKey: mainKey, 98 } 99 for _, option := range options { 100 option(app) 101 } 102 103 return app 104 } 105 106 // Name returns the name of the BaseApp. 107 func (app *BaseApp) Name() string { 108 return app.name 109 } 110 111 // AppVersion returns the application's version string. 112 func (app *BaseApp) AppVersion() string { 113 return app.appVersion 114 } 115 116 // Logger returns the logger of the BaseApp. 117 func (app *BaseApp) Logger() *slog.Logger { 118 return app.logger 119 } 120 121 // MountStoreWithDB mounts a store to the provided key in the BaseApp 122 // multistore, using a specified DB. 123 func (app *BaseApp) MountStoreWithDB(key store.StoreKey, cons store.CommitStoreConstructor, db dbm.DB) { 124 app.cms.MountStoreWithDB(key, cons, db) 125 } 126 127 // MountStore mounts a store to the provided key in the BaseApp multistore, 128 // using the default DB. 129 func (app *BaseApp) MountStore(key store.StoreKey, cons store.CommitStoreConstructor) { 130 app.cms.MountStoreWithDB(key, cons, nil) 131 } 132 133 // LoadLatestVersion loads the latest application version. It will panic if 134 // called more than once on a running BaseApp. 135 // This, or LoadVersion() MUST be called even after first init. 136 func (app *BaseApp) LoadLatestVersion() error { 137 err := app.cms.LoadLatestVersion() 138 if err != nil { 139 return err 140 } 141 return app.initFromMainStore() 142 } 143 144 // LoadVersion loads the BaseApp application version. It will panic if called 145 // more than once on a running baseapp. 146 // This, or LoadLatestVersion() MUST be called even after first init. 147 func (app *BaseApp) LoadVersion(version int64) error { 148 err := app.cms.LoadVersion(version) 149 if err != nil { 150 return err 151 } 152 return app.initFromMainStore() 153 } 154 155 // LastCommitID returns the last CommitID of the multistore. 156 func (app *BaseApp) LastCommitID() store.CommitID { 157 return app.cms.LastCommitID() 158 } 159 160 // LastBlockHeight returns the last committed block height. 161 func (app *BaseApp) LastBlockHeight() int64 { 162 return app.cms.LastCommitID().Version 163 } 164 165 // initializes the app from app.cms after loading. 166 func (app *BaseApp) initFromMainStore() error { 167 baseStore := app.cms.GetStore(app.baseKey) 168 if baseStore == nil { 169 return errors.New("baseapp expects MultiStore with 'base' Store") 170 } 171 mainStore := app.cms.GetStore(app.mainKey) 172 if mainStore == nil { 173 return errors.New("baseapp expects MultiStore with 'main' Store") 174 } 175 176 // Load the consensus params from the main store. If the consensus params are 177 // nil, it will be saved later during InitChain. 178 // 179 // TODO: assert that InitChain hasn't yet been called. 180 consensusParamsBz := mainStore.Get(mainConsensusParamsKey) 181 if consensusParamsBz != nil { 182 consensusParams := &abci.ConsensusParams{} 183 err := amino.Unmarshal(consensusParamsBz, consensusParams) 184 if err != nil { 185 panic(err) 186 } 187 188 app.setConsensusParams(consensusParams) 189 } 190 191 // Load the consensus header from the main store. 192 // This is needed to setCheckState with the right chainID etc. 193 lastHeaderBz := baseStore.Get(mainLastHeaderKey) 194 if lastHeaderBz != nil { 195 lastHeader := &bft.Header{} 196 err := amino.Unmarshal(lastHeaderBz, lastHeader) 197 if err != nil { 198 panic(err) 199 } 200 app.setCheckState(lastHeader) 201 } 202 // Done. 203 app.Seal() 204 205 return nil 206 } 207 208 func (app *BaseApp) setMinGasPrices(gasPrices []GasPrice) { 209 app.minGasPrices = gasPrices 210 } 211 212 func (app *BaseApp) setHaltHeight(haltHeight uint64) { 213 app.haltHeight = haltHeight 214 } 215 216 func (app *BaseApp) setHaltTime(haltTime uint64) { 217 app.haltTime = haltTime 218 } 219 220 // Returns a read-only (cache) MultiStore. 221 // This may be used by keepers for initialization upon restart. 222 func (app *BaseApp) GetCacheMultiStore() store.MultiStore { 223 return app.cms.MultiCacheWrap() 224 } 225 226 // Router returns the router of the BaseApp. 227 func (app *BaseApp) Router() Router { 228 if app.sealed { 229 // We cannot return a router when the app is sealed because we can't have 230 // any routes modified which would cause unexpected routing behavior. 231 panic("Router() on sealed BaseApp") 232 } 233 return app.router 234 } 235 236 // Seal seals a BaseApp. It prohibits any further modifications to a BaseApp. 237 func (app *BaseApp) Seal() { app.sealed = true } 238 239 // IsSealed returns true if the BaseApp is sealed and false otherwise. 240 func (app *BaseApp) IsSealed() bool { return app.sealed } 241 242 // setCheckState sets checkState with the cached multistore and 243 // the context wrapping it. 244 // It is called by InitChain() and Commit() 245 func (app *BaseApp) setCheckState(header abci.Header) { 246 ms := app.cms.MultiCacheWrap() 247 app.checkState = &state{ 248 ms: ms, 249 ctx: NewContext(RunTxModeCheck, ms, header, app.logger).WithMinGasPrices(app.minGasPrices), 250 } 251 } 252 253 // setDeliverState sets deliverState with the cached multistore and 254 // the context wrapping it. 255 // It is called by InitChain() and BeginBlock(), 256 // and deliverState is set nil on Commit(). 257 func (app *BaseApp) setDeliverState(header abci.Header) { 258 ms := app.cms.MultiCacheWrap() 259 app.deliverState = &state{ 260 ms: ms, 261 ctx: NewContext(RunTxModeDeliver, ms, header, app.logger), 262 } 263 } 264 265 // setConsensusParams memoizes the consensus params. 266 func (app *BaseApp) setConsensusParams(consensusParams *abci.ConsensusParams) { 267 app.consensusParams = consensusParams 268 } 269 270 // setConsensusParams stores the consensus params to the main store. 271 func (app *BaseApp) storeConsensusParams(consensusParams *abci.ConsensusParams) { 272 consensusParamsBz, err := amino.Marshal(consensusParams) 273 if err != nil { 274 panic(err) 275 } 276 mainStore := app.cms.GetStore(app.mainKey) 277 mainStore.Set(mainConsensusParamsKey, consensusParamsBz) 278 } 279 280 // getMaximumBlockGas gets the maximum gas from the consensus params. It panics 281 // if maximum block gas is less than negative one and returns zero if negative 282 // one. 283 func (app *BaseApp) getMaximumBlockGas() int64 { 284 if app.consensusParams == nil || app.consensusParams.Block == nil { 285 return 0 286 } 287 288 maxGas := app.consensusParams.Block.MaxGas 289 switch { 290 case maxGas < -1: 291 panic(fmt.Sprintf("invalid maximum block gas: %d", maxGas)) 292 293 case maxGas == -1: 294 return 0 295 296 default: 297 return maxGas 298 } 299 } 300 301 // ---------------------------------------------------------------------------- 302 // ABCI 303 304 // Info implements the ABCI interface. 305 func (app *BaseApp) Info(req abci.RequestInfo) (res abci.ResponseInfo) { 306 lastCommitID := app.cms.LastCommitID() 307 308 // return res 309 res.Data = []byte(app.Name()) 310 res.LastBlockHeight = lastCommitID.Version 311 res.LastBlockAppHash = lastCommitID.Hash 312 return 313 } 314 315 // SetOption implements the ABCI interface. 316 func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) { 317 // TODO: Implement! 318 return 319 } 320 321 // InitChain implements the ABCI interface. It runs the initialization logic 322 // directly on the CommitMultiStore. 323 func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { 324 // stash the consensus params in the cms main store and memoize 325 if req.ConsensusParams != nil { 326 app.setConsensusParams(req.ConsensusParams) 327 app.storeConsensusParams(req.ConsensusParams) 328 } 329 330 initHeader := &bft.Header{ChainID: req.ChainID, Time: req.Time} 331 332 // initialize the deliver state and check state with a correct header 333 app.setDeliverState(initHeader) 334 app.setCheckState(initHeader) 335 336 if app.initChainer == nil { 337 return 338 } 339 340 // add block gas meter for any genesis transactions (allow infinite gas) 341 app.deliverState.ctx = app.deliverState.ctx. 342 WithBlockGasMeter(store.NewInfiniteGasMeter()) 343 344 res = app.initChainer(app.deliverState.ctx, req) 345 346 // sanity check 347 if len(req.Validators) > 0 { 348 if len(req.Validators) != len(res.Validators) { 349 panic(fmt.Errorf( 350 "len(RequestInitChain.Validators) != len(validators) (%d != %d)", 351 len(req.Validators), len(res.Validators))) 352 } 353 sort.Sort(abci.ValidatorUpdates(req.Validators)) 354 sort.Sort(abci.ValidatorUpdates(res.Validators)) 355 for i, val := range res.Validators { 356 if !val.Equals(req.Validators[i]) { 357 panic(fmt.Errorf("validators[%d] != req.Validators[%d] ", i, i)) 358 } 359 } 360 } 361 362 // NOTE: We don't commit, but BeginBlock for block 1 starts from this 363 // deliverState. 364 return 365 } 366 367 // Splits a string path using the delimiter '/'. 368 // e.g. "this/is/funny" becomes []string{"this", "is", "funny"} 369 func splitPath(requestPath string) (path []string) { 370 path = strings.Split(requestPath, "/") 371 // first element is empty string 372 if len(path) > 0 && path[0] == "" { 373 path = path[1:] 374 } 375 return path 376 } 377 378 // Query implements the ABCI interface. It delegates to CommitMultiStore if it 379 // implements Queryable. 380 func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { 381 path := splitPath(req.Path) 382 if len(path) == 0 { 383 msg := "no query path provided" 384 res.Error = ABCIError(std.ErrUnknownRequest(msg)) 385 return 386 } 387 388 switch path[0] { 389 // "/.app", "/.store" prefix for special application queries 390 case ".app": 391 return handleQueryApp(app, path, req) 392 393 case ".store": 394 return handleQueryStore(app, path, req) 395 396 // default router queries 397 default: 398 return handleQueryCustom(app, path, req) 399 } 400 401 msg := "unknown query path " + req.Path 402 res.Error = ABCIError(std.ErrUnknownRequest(msg)) 403 return 404 } 405 406 func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { 407 if len(path) >= 2 { 408 var result Result 409 410 switch path[1] { 411 case "simulate": 412 txBytes := req.Data 413 var tx Tx 414 err := amino.Unmarshal(txBytes, &tx) 415 if err != nil { 416 res.Error = ABCIError(std.ErrTxDecode(err.Error())) 417 } else { 418 result = app.Simulate(txBytes, tx) 419 } 420 res.Height = req.Height 421 res.Value = amino.MustMarshal(result) 422 return res 423 case "version": 424 res.Height = req.Height 425 res.Value = []byte(app.appVersion) 426 return res 427 default: 428 res.Error = ABCIError(std.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path))) 429 return 430 } 431 } else { 432 res.Error = ABCIError(std.ErrUnknownRequest(fmt.Sprintf("Unknown query: %s", path))) 433 return 434 } 435 } 436 437 func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { 438 // "/store" prefix for store queries 439 queryable, ok := app.cms.(store.Queryable) 440 if !ok { 441 msg := "multistore doesn't support queries" 442 res.Error = ABCIError(std.ErrUnknownRequest(msg)) 443 return 444 } 445 446 req.Path = "/" + strings.Join(path[1:], "/") 447 448 // when a client did not provide a query height, manually inject the latest 449 if req.Height == 0 { 450 req.Height = app.LastBlockHeight() 451 } 452 453 if req.Height <= 1 && req.Prove { 454 res.Error = ABCIError(std.ErrInternal("cannot query with proof when height <= 1; please provide a valid height")) 455 return 456 } 457 458 resp := queryable.Query(req) 459 resp.Height = req.Height 460 return resp 461 } 462 463 func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) (res abci.ResponseQuery) { 464 if len(path) < 1 || path[0] == "" { 465 res.Error = ABCIError(std.ErrUnknownRequest("No route for custom query specified")) 466 return 467 } 468 469 handler := app.router.Route(path[0]) 470 if handler == nil { 471 res.Error = ABCIError(std.ErrUnknownRequest(fmt.Sprintf("no custom handler found for route %s", path[0]))) 472 return 473 } 474 475 // when a client did not provide a query height, manually inject the latest 476 if req.Height == 0 { 477 req.Height = app.LastBlockHeight() 478 } 479 480 if req.Height <= 1 && req.Prove { 481 res.Error = ABCIError(std.ErrInternal("cannot query with proof when height <= 1; please provide a valid height")) 482 return 483 } 484 485 cacheMS, err := app.cms.MultiImmutableCacheWrapWithVersion(req.Height) 486 if err != nil { 487 res.Error = ABCIError(std.ErrInternal( 488 fmt.Sprintf( 489 "failed to load state at height %d; %s (latest height: %d)", 490 req.Height, err, app.LastBlockHeight(), 491 ), 492 )) 493 return 494 } 495 496 // cache wrap the commit-multistore for safety 497 // XXX RunTxModeQuery? 498 ctx := NewContext(RunTxModeCheck, cacheMS, app.checkState.ctx.BlockHeader(), app.logger).WithMinGasPrices(app.minGasPrices) 499 500 // Passes the query to the handler. 501 res = handler.Query(ctx, req) 502 return 503 } 504 505 func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error { 506 if req.Header.GetHeight() < 1 { 507 return fmt.Errorf("invalid height: %d", req.Header.GetHeight()) 508 } 509 510 prevHeight := app.LastBlockHeight() 511 if req.Header.GetHeight() != prevHeight+1 { 512 return fmt.Errorf("invalid height: %d; expected: %d", req.Header.GetHeight(), prevHeight+1) 513 } 514 515 return nil 516 } 517 518 // BeginBlock implements the ABCI application interface. 519 func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { 520 if err := app.validateHeight(req); err != nil { 521 panic(err) 522 } 523 524 // Initialize the DeliverTx state. If this is the first block, it should 525 // already be initialized in InitChain. Otherwise app.deliverState will be 526 // nil, since it is reset on Commit. 527 if app.deliverState == nil { 528 app.setDeliverState(req.Header) 529 } else { 530 // In the first block, app.deliverState.ctx will already be initialized 531 // by InitChain. Context is now updated with Header information. 532 app.deliverState.ctx = app.deliverState.ctx. 533 WithBlockHeader(req.Header) 534 } 535 536 // add block gas meter 537 var gasMeter store.GasMeter 538 if maxGas := app.getMaximumBlockGas(); maxGas > 0 { 539 gasMeter = store.NewGasMeter(maxGas) 540 } else { 541 gasMeter = store.NewInfiniteGasMeter() 542 } 543 544 app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(gasMeter) 545 546 if app.beginBlocker != nil { 547 res = app.beginBlocker(app.deliverState.ctx, req) 548 } 549 550 // set the signed validators for addition to context in deliverTx 551 if req.LastCommitInfo != nil { 552 app.voteInfos = req.LastCommitInfo.Votes 553 } 554 return 555 } 556 557 // CheckTx implements the ABCI interface. It runs the "basic checks" to see 558 // whether or not a transaction can possibly be executed, first decoding and then 559 // the ante handler (which checks signatures/fees/ValidateBasic). 560 // 561 // NOTE:CheckTx does not run the actual Msg handler function(s). 562 func (app *BaseApp) CheckTx(req abci.RequestCheckTx) (res abci.ResponseCheckTx) { 563 var tx Tx 564 err := amino.Unmarshal(req.Tx, &tx) 565 if err != nil { 566 res.Error = ABCIError(std.ErrTxDecode(err.Error())) 567 return 568 } else { 569 result := app.runTx(RunTxModeCheck, req.Tx, tx) 570 res.ResponseBase = result.ResponseBase 571 res.GasWanted = result.GasWanted 572 res.GasUsed = result.GasUsed 573 return 574 } 575 } 576 577 // DeliverTx implements the ABCI interface. 578 func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliverTx) { 579 var tx Tx 580 err := amino.Unmarshal(req.Tx, &tx) 581 if err != nil { 582 res.Error = ABCIError(std.ErrTxDecode(err.Error())) 583 return 584 } else { 585 result := app.runTx(RunTxModeDeliver, req.Tx, tx) 586 res.ResponseBase = result.ResponseBase 587 res.GasWanted = result.GasWanted 588 res.GasUsed = result.GasUsed 589 return 590 } 591 } 592 593 // validateBasicTxMsgs executes basic validator calls for messages. 594 func validateBasicTxMsgs(msgs []Msg) error { 595 if msgs == nil || len(msgs) == 0 { 596 return std.ErrUnknownRequest("Tx.GetMsgs() must return at least one message in list") 597 } 598 599 for _, msg := range msgs { 600 // Validate the Msg. 601 err := msg.ValidateBasic() 602 if err != nil { 603 return err 604 } 605 } 606 607 return nil 608 } 609 610 // retrieve the context for the tx w/ txBytes and other memoized values. 611 func (app *BaseApp) getContextForTx(mode RunTxMode, txBytes []byte) (ctx Context) { 612 ctx = app.getState(mode).ctx. 613 WithMode(mode). 614 WithTxBytes(txBytes). 615 WithVoteInfos(app.voteInfos). 616 WithConsensusParams(app.consensusParams) 617 618 if mode == RunTxModeSimulate { 619 ctx, _ = ctx.CacheContext() 620 } 621 622 return 623 } 624 625 // / runMsgs iterates through all the messages and executes them. 626 func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Result) { 627 msgLogs := make([]string, 0, len(msgs)) 628 629 data := make([]byte, 0, len(msgs)) 630 err := error(nil) 631 events := []Event{} 632 633 // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. 634 for i, msg := range msgs { 635 // match message route 636 msgRoute := msg.Route() 637 handler := app.router.Route(msgRoute) 638 if handler == nil { 639 result.Error = ABCIError(std.ErrUnknownRequest("unrecognized message type: " + msgRoute)) 640 return 641 } 642 643 var msgResult Result 644 ctx = ctx.WithEventLogger(NewEventLogger()) 645 646 // run the message! 647 // skip actual execution for CheckTx mode 648 if mode != RunTxModeCheck { 649 msgResult = handler.Process(ctx, msg) 650 } 651 652 // Each message result's Data must be length prefixed in order to separate 653 // each result. 654 data = append(data, msgResult.Data...) 655 events = append(events, msgResult.Events...) 656 defer func() { 657 events = append(events, ctx.EventLogger().Events()...) 658 result.Events = events 659 }() 660 // TODO append msgevent from ctx. XXX XXX 661 662 // stop execution and return on first failed message 663 if !msgResult.IsOK() { 664 msgLogs = append(msgLogs, 665 fmt.Sprintf("msg:%d,success:%v,log:%s,events:%v", 666 i, false, msgResult.Log, events)) 667 err = msgResult.Error 668 break 669 } 670 671 msgLogs = append(msgLogs, 672 fmt.Sprintf("msg:%d,success:%v,log:%s,events:%v", 673 i, true, msgResult.Log, events)) 674 } 675 676 result.Error = ABCIError(err) 677 result.Data = data 678 result.Log = strings.Join(msgLogs, "\n") 679 result.GasUsed = ctx.GasMeter().GasConsumed() 680 result.Events = events 681 return result 682 } 683 684 // Returns the applications's deliverState if app is in RunTxModeDeliver, 685 // otherwise it returns the application's checkstate. 686 func (app *BaseApp) getState(mode RunTxMode) *state { 687 if mode == RunTxModeCheck || mode == RunTxModeSimulate { 688 return app.checkState 689 } 690 691 return app.deliverState 692 } 693 694 // cacheTxContext returns a new context based off of the provided context with 695 // a cache wrapped multi-store. 696 func (app *BaseApp) cacheTxContext(ctx Context, txBytes []byte) ( 697 Context, store.MultiStore, 698 ) { 699 ms := ctx.MultiStore() 700 // TODO: https://github.com/tendermint/classic/sdk/issues/2824 701 msCache := ms.MultiCacheWrap() 702 return ctx.WithMultiStore(msCache), msCache 703 } 704 705 // runTx processes a transaction. The transactions is processed via an 706 // anteHandler. The provided txBytes may be nil in some cases, eg. in tests. For 707 // further details on transaction execution, reference the BaseApp SDK 708 // documentation. 709 func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) { 710 // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is 711 // determined by the GasMeter. We need access to the context to get the gas 712 // meter so we initialize upfront. 713 var gasWanted int64 714 715 ctx := app.getContextForTx(mode, txBytes) 716 ms := ctx.MultiStore() 717 if mode == RunTxModeDeliver { 718 gasleft := ctx.BlockGasMeter().Remaining() 719 ctx = ctx.WithGasMeter(store.NewPassthroughGasMeter( 720 ctx.GasMeter(), 721 gasleft, 722 )) 723 } 724 725 // only run the tx if there is block gas remaining 726 if mode == RunTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() { 727 result.Error = ABCIError(std.ErrOutOfGas("no block gas left to run tx")) 728 return 729 } 730 731 var startingGas int64 732 if mode == RunTxModeDeliver { 733 startingGas = ctx.BlockGasMeter().GasConsumed() 734 } 735 736 defer func() { 737 if r := recover(); r != nil { 738 switch ex := r.(type) { 739 case store.OutOfGasException: 740 log := fmt.Sprintf( 741 "out of gas, gasWanted: %d, gasUsed: %d location: %v", 742 gasWanted, 743 ctx.GasMeter().GasConsumed(), 744 ex.Descriptor, 745 ) 746 result.Error = ABCIError(std.ErrOutOfGas(log)) 747 result.Log = log 748 result.GasWanted = gasWanted 749 result.GasUsed = ctx.GasMeter().GasConsumed() 750 return 751 default: 752 log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) 753 result.Error = ABCIError(std.ErrInternal(log)) 754 result.Log = log 755 result.GasWanted = gasWanted 756 result.GasUsed = ctx.GasMeter().GasConsumed() 757 return 758 } 759 } 760 // Whether AnteHandler panics or not. 761 result.GasWanted = gasWanted 762 result.GasUsed = ctx.GasMeter().GasConsumed() 763 }() 764 765 // If BlockGasMeter() panics it will be caught by the above recover and will 766 // return an error - in any case BlockGasMeter will consume gas past the limit. 767 // 768 // NOTE: This must exist in a separate defer function for the above recovery 769 // to recover from this one. 770 defer func() { 771 if mode == RunTxModeDeliver { 772 ctx.BlockGasMeter().ConsumeGas( 773 ctx.GasMeter().GasConsumedToLimit(), 774 "block gas meter", 775 ) 776 777 if ctx.BlockGasMeter().GasConsumed() < startingGas { 778 panic(std.ErrGasOverflow("tx gas summation")) 779 } 780 } 781 }() 782 783 msgs := tx.GetMsgs() 784 if err := validateBasicTxMsgs(msgs); err != nil { 785 result.Error = ABCIError(err) 786 return 787 } 788 789 if app.anteHandler != nil { 790 var anteCtx Context 791 var msCache store.MultiStore 792 793 // Cache wrap context before anteHandler call in case 794 // it aborts. This is required for both CheckTx and 795 // DeliverTx. Ref: 796 // https://github.com/tendermint/classic/sdk/issues/2772 797 // 798 // NOTE: Alternatively, we could require that 799 // anteHandler ensures that writes do not happen if 800 // aborted/failed. This may have some performance 801 // benefits, but it'll be more difficult to get 802 // right. 803 anteCtx, msCache = app.cacheTxContext(ctx, txBytes) 804 // Call AnteHandler. 805 // NOTE: It is the responsibility of the anteHandler 806 // to use something like passthroughGasMeter to 807 // account for ante handler gas usage, despite 808 // OutOfGasExceptions. 809 newCtx, result, abort := app.anteHandler(anteCtx, tx, mode == RunTxModeSimulate) 810 if newCtx.IsZero() { 811 panic("newCtx must not be zero") 812 } 813 if abort && result.Error == nil { 814 panic("result.Error should be set for abort") 815 } 816 if abort { 817 // NOTE: first we must set ctx above, 818 // because a previous defer call sets 819 // result.GasUsed, regardless of error. 820 return result 821 } else { 822 // Revert cache wrapping of multistore. 823 ctx = newCtx.WithMultiStore(ms) 824 msCache.MultiWrite() 825 gasWanted = result.GasWanted 826 } 827 } 828 829 // Create a new context based off of the existing context with a cache wrapped 830 // multi-store in case message processing fails. 831 runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) 832 result = app.runMsgs(runMsgCtx, msgs, mode) 833 result.GasWanted = gasWanted 834 835 // Safety check: don't write the cache state unless we're in DeliverTx. 836 if mode != RunTxModeDeliver { 837 return result 838 } 839 840 // only update state if all messages pass 841 if result.IsOK() { 842 msCache.MultiWrite() 843 } 844 845 return result 846 } 847 848 // EndBlock implements the ABCI interface. 849 func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { 850 if app.endBlocker != nil { 851 res = app.endBlocker(app.deliverState.ctx, req) 852 } 853 854 return 855 } 856 857 // Commit implements the ABCI interface. It will commit all state that exists in 858 // the deliver state's multi-store and includes the resulting commit ID in the 859 // returned abci.ResponseCommit. Commit will set the check state based on the 860 // latest header and reset the deliver state. Also, if a non-zero halt height is 861 // defined in config, Commit will execute a deferred function call to check 862 // against that height and gracefully halt if it matches the latest committed 863 // height. 864 func (app *BaseApp) Commit() (res abci.ResponseCommit) { 865 header := app.deliverState.ctx.BlockHeader() 866 867 var halt bool 868 869 switch { 870 case app.haltHeight > 0 && uint64(header.GetHeight()) >= app.haltHeight: 871 halt = true 872 873 case app.haltTime > 0 && header.GetTime().Unix() >= int64(app.haltTime): 874 halt = true 875 } 876 877 if halt { 878 app.halt() 879 880 // Note: State is not actually committed when halted. Logs from Tendermint 881 // can be ignored. 882 return abci.ResponseCommit{} 883 } 884 885 // Write the DeliverTx state which is cache-wrapped and commit the MultiStore. 886 // The write to the DeliverTx state writes all state transitions to the root 887 // MultiStore (app.cms) so when Commit() is called is persists those values. 888 app.deliverState.ms.MultiWrite() 889 commitID := app.cms.Commit() 890 app.logger.Debug("Commit synced", "commit", fmt.Sprintf("%X", commitID)) 891 892 // Save this header. 893 baseStore := app.cms.GetStore(app.baseKey) 894 if baseStore == nil { 895 res.Error = ABCIError(errors.New("baseapp expects MultiStore with 'base' Store")) 896 return 897 } 898 headerBz := amino.MustMarshal(header) 899 baseStore.Set(mainLastHeaderKey, headerBz) 900 901 // Reset the Check state to the latest committed. 902 // 903 // NOTE: This is safe because Tendermint holds a lock on the mempool for 904 // Commit. Use the header from this latest block. 905 app.setCheckState(header) 906 907 // empty/reset the deliver state 908 app.deliverState = nil 909 910 // return. 911 res.Data = commitID.Hash 912 return 913 } 914 915 // halt attempts to gracefully shutdown the node via SIGINT and SIGTERM falling 916 // back on os.Exit if both fail. 917 func (app *BaseApp) halt() { 918 app.logger.Info("halting node per configuration", "height", app.haltHeight, "time", app.haltTime) 919 920 p, err := os.FindProcess(os.Getpid()) 921 if err == nil { 922 // attempt cascading signals in case SIGINT fails (os dependent) 923 sigIntErr := p.Signal(syscall.SIGINT) 924 sigTermErr := p.Signal(syscall.SIGTERM) 925 926 if sigIntErr == nil || sigTermErr == nil { 927 return 928 } 929 } 930 931 // Resort to exiting immediately if the process could not be found or killed 932 // via SIGINT/SIGTERM signals. 933 app.logger.Info("failed to send SIGINT/SIGTERM; exiting...") 934 os.Exit(0) 935 } 936 937 // TODO implement cleanup 938 func (app *BaseApp) Close() error { 939 return nil // XXX 940 } 941 942 // ---------------------------------------------------------------------------- 943 // State 944 945 type state struct { 946 ms store.MultiStore 947 ctx Context 948 } 949 950 func (st *state) MultiCacheWrap() store.MultiStore { 951 return st.ms.MultiCacheWrap() 952 } 953 954 func (st *state) Context() Context { 955 return st.ctx 956 }