github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/baseapp/abci.go (about) 1 package baseapp 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "sort" 8 "strconv" 9 "strings" 10 "sync" 11 "syscall" 12 "time" 13 14 "github.com/fibonacci-chain/fbc/libs/system/trace/persist" 15 "github.com/spf13/viper" 16 17 "github.com/fibonacci-chain/fbc/app/rpc/simulator" 18 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec" 19 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/mpt" 20 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 21 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 22 sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 23 "github.com/fibonacci-chain/fbc/libs/iavl" 24 "github.com/fibonacci-chain/fbc/libs/system/trace" 25 abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types" 26 tmtypes "github.com/fibonacci-chain/fbc/libs/tendermint/types" 27 "github.com/tendermint/go-amino" 28 ) 29 30 // InitChain implements the ABCI interface. It runs the initialization logic 31 // directly on the CommitMultiStore. 32 func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) { 33 // stash the consensus params in the cms main store and memoize 34 if req.ConsensusParams != nil { 35 app.setConsensusParams(req.ConsensusParams) 36 app.storeConsensusParams(req.ConsensusParams) 37 } 38 39 initHeader := abci.Header{ChainID: req.ChainId, Time: req.Time} 40 41 // initialize the deliver state and check state with a correct header 42 app.setDeliverState(initHeader) 43 app.setCheckState(initHeader) 44 45 if app.initChainer == nil { 46 return 47 } 48 49 // add block gas meter for any genesis transactions (allow infinite gas) 50 app.deliverState.ctx.SetBlockGasMeter(sdk.NewInfiniteGasMeter()) 51 52 res = app.initChainer(app.deliverState.ctx, req) 53 54 // sanity check 55 if len(req.Validators) > 0 { 56 if len(req.Validators) != len(res.Validators) { 57 panic( 58 fmt.Errorf( 59 "len(RequestInitChain.Validators) != len(GenesisValidators) (%d != %d)", 60 len(req.Validators), len(res.Validators), 61 ), 62 ) 63 } 64 65 sort.Sort(abci.ValidatorUpdates(req.Validators)) 66 sort.Sort(abci.ValidatorUpdates(res.Validators)) 67 68 for i, val := range res.Validators { 69 if !val.Equal(req.Validators[i]) { 70 panic(fmt.Errorf("genesisValidators[%d] != req.Validators[%d] ", i, i)) 71 } 72 } 73 } 74 75 // NOTE: We don't commit, but BeginBlock for block 1 starts from this 76 // deliverState. 77 return res 78 } 79 80 // Info implements the ABCI interface. 81 func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo { 82 lastCommitID := app.cms.LastCommitID() 83 84 return abci.ResponseInfo{ 85 Data: app.name, 86 LastBlockHeight: lastCommitID.Version, 87 LastBlockAppHash: lastCommitID.Hash, 88 } 89 } 90 91 // SetOption implements the ABCI interface. 92 func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOption) { 93 // TODO: Implement! 94 switch req.Key { 95 case "ResetCheckState": 96 // reset check state 97 app.setCheckState(app.checkState.ctx.BlockHeader()) 98 default: 99 // do nothing 100 } 101 return 102 } 103 104 // FilterPeerByAddrPort filters peers by address/port. 105 func (app *BaseApp) FilterPeerByAddrPort(info string) abci.ResponseQuery { 106 if app.addrPeerFilter != nil { 107 return app.addrPeerFilter(info) 108 } 109 return abci.ResponseQuery{} 110 } 111 112 // FilterPeerByIDfilters peers by node ID. 113 func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery { 114 if app.idPeerFilter != nil { 115 return app.idPeerFilter(info) 116 } 117 return abci.ResponseQuery{} 118 } 119 120 // BeginBlock implements the ABCI application interface. 121 func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { 122 app.blockDataCache.Clear() 123 app.PutCacheMultiStore(nil) 124 if app.cms.TracingEnabled() { 125 app.cms.SetTracingContext(sdk.TraceContext( 126 map[string]interface{}{"blockHeight": req.Header.Height}, 127 )) 128 } 129 130 if err := app.validateHeight(req); err != nil { 131 panic(err) 132 } 133 134 // Initialize the DeliverTx state. If this is the first block, it should 135 // already be initialized in InitChain. Otherwise app.deliverState will be 136 // nil, since it is reset on Commit. 137 if req.Header.Height > 1+tmtypes.GetStartBlockHeight() { 138 if app.deliverState != nil { 139 app.logger.Info( 140 "deliverState was not reset by BaseApp.Commit due to the previous prerun task being stopped", 141 "height", req.Header.Height) 142 } 143 app.setDeliverState(req.Header) 144 } else { 145 // In the first block, app.deliverState.ctx will already be initialized 146 // by InitChain. Context is now updated with Header information. 147 app.deliverState.ctx. 148 SetBlockHeader(req.Header). 149 SetBlockHeight(req.Header.Height) 150 } 151 152 app.newBlockCache() 153 // add block gas meter 154 var gasMeter sdk.GasMeter 155 if maxGas := app.getMaximumBlockGas(); maxGas > 0 { 156 gasMeter = sdk.NewGasMeter(maxGas) 157 } else { 158 gasMeter = sdk.NewInfiniteGasMeter() 159 } 160 161 app.deliverState.ctx.SetBlockGasMeter(gasMeter) 162 163 if app.beginBlocker != nil { 164 res = app.beginBlocker(app.deliverState.ctx, req) 165 } 166 167 // set the signed validators for addition to context in deliverTx 168 app.voteInfos = req.LastCommitInfo.GetVotes() 169 170 app.anteTracer = trace.NewTracer(trace.AnteChainDetail) 171 172 app.feeCollector = sdk.Coins{} 173 app.feeChanged = false 174 // clean FeeSplitCollector 175 app.FeeSplitCollector = make([]*sdk.FeeSplitInfo, 0) 176 177 return res 178 } 179 180 func (app *BaseApp) UpdateFeeCollector(fee sdk.Coins, add bool) { 181 if fee.IsZero() { 182 return 183 } 184 app.feeChanged = true 185 if add { 186 app.feeCollector = app.feeCollector.Add(fee...) 187 } else { 188 app.feeCollector = app.feeCollector.Sub(fee) 189 } 190 } 191 192 func (app *BaseApp) updateFeeCollectorAccount(isEndBlock bool) { 193 if app.updateFeeCollectorAccHandler == nil || !app.feeChanged { 194 return 195 } 196 197 defer func() { 198 if r := recover(); r != nil { 199 err := fmt.Errorf("panic: %v", r) 200 app.logger.Error("update fee collector account failed", "err", err) 201 } 202 }() 203 204 ctx, cache := app.cacheTxContext(app.getContextForTx(runTxModeDeliver, []byte{}), []byte{}) 205 if isEndBlock { 206 // The feesplit is only processed at the endblock 207 if err := app.updateFeeCollectorAccHandler(ctx, app.feeCollector, app.FeeSplitCollector); err != nil { 208 panic(err) 209 } 210 } else { 211 if err := app.updateFeeCollectorAccHandler(ctx, app.feeCollector, nil); err != nil { 212 panic(err) 213 } 214 } 215 cache.Write() 216 } 217 218 // EndBlock implements the ABCI interface. 219 func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { 220 app.updateFeeCollectorAccount(true) 221 222 if app.deliverState.ms.TracingEnabled() { 223 app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore) 224 } 225 226 if app.endBlocker != nil { 227 res = app.endBlocker(app.deliverState.ctx, req) 228 } 229 230 return 231 } 232 233 func (app *BaseApp) addCommitTraceInfo() { 234 nodeReadCountStr := strconv.Itoa(app.cms.GetNodeReadCount()) 235 dbReadCountStr := strconv.Itoa(app.cms.GetDBReadCount()) 236 dbReadTimeStr := strconv.FormatInt(time.Duration(app.cms.GetDBReadTime()).Milliseconds(), 10) 237 dbWriteCountStr := strconv.Itoa(app.cms.GetDBWriteCount()) 238 239 iavlInfo := strings.Join([]string{"getnode<", nodeReadCountStr, ">, rdb<", dbReadCountStr, ">, rdbTs<", dbReadTimeStr, "ms>, savenode<", dbWriteCountStr, ">"}, "") 240 241 elapsedInfo := trace.GetElapsedInfo() 242 elapsedInfo.AddInfo(trace.Iavl, iavlInfo) 243 244 flatKvReadCountStr := strconv.Itoa(app.cms.GetFlatKVReadCount()) 245 flatKvReadTimeStr := strconv.FormatInt(time.Duration(app.cms.GetFlatKVReadTime()).Milliseconds(), 10) 246 flatKvWriteCountStr := strconv.Itoa(app.cms.GetFlatKVWriteCount()) 247 flatKvWriteTimeStr := strconv.FormatInt(time.Duration(app.cms.GetFlatKVWriteTime()).Milliseconds(), 10) 248 249 flatInfo := strings.Join([]string{"rflat<", flatKvReadCountStr, ">, rflatTs<", flatKvReadTimeStr, "ms>, wflat<", flatKvWriteCountStr, ">, wflatTs<", flatKvWriteTimeStr, "ms>"}, "") 250 251 elapsedInfo.AddInfo(trace.FlatKV, flatInfo) 252 253 //rtx := float64(atomic.LoadInt64(&app.checkTxNum)) 254 //wtx := float64(atomic.LoadInt64(&app.wrappedCheckTxNum)) 255 256 //elapsedInfo.AddInfo(trace.WtxRatio, 257 // amino.BytesToStr(strconv.AppendFloat(make([]byte, 0, 4), wtx/(wtx+rtx), 'f', 2, 32)), 258 //) 259 260 readCache := float64(tmtypes.SignatureCache().ReadCount()) 261 hitCache := float64(tmtypes.SignatureCache().HitCount()) 262 263 elapsedInfo.AddInfo(trace.SigCacheRatio, 264 amino.BytesToStr(strconv.AppendFloat(make([]byte, 0, 4), hitCache/readCache, 'f', 2, 32)), 265 ) 266 267 elapsedInfo.AddInfo(trace.AnteChainDetail, app.anteTracer.FormatRepeatingPins(sdk.AnteTerminatorTag)) 268 } 269 270 // Commit implements the ABCI interface. It will commit all state that exists in 271 // the deliver state's multi-store and includes the resulting commit ID in the 272 // returned abci.ResponseCommit. Commit will set the check state based on the 273 // latest header and reset the deliver state. Also, if a non-zero halt height is 274 // defined in config, Commit will execute a deferred function call to check 275 // against that height and gracefully halt if it matches the latest committed 276 // height. 277 func (app *BaseApp) Commit(req abci.RequestCommit) abci.ResponseCommit { 278 279 persist.GetStatistics().Init(trace.PreChange, trace.FlushCache, trace.CommitStores, trace.FlushMeta) 280 defer func() { 281 trace.GetElapsedInfo().AddInfo(trace.PersistDetails, persist.GetStatistics().Format()) 282 }() 283 header := app.deliverState.ctx.BlockHeader() 284 285 if app.mptCommitHandler != nil { 286 app.mptCommitHandler(app.deliverState.ctx) 287 } 288 if mptStore := app.cms.GetCommitKVStore(sdk.NewKVStoreKey(mpt.StoreKey)); mptStore != nil { 289 // notify mptStore to tryUpdateTrie, must call before app.deliverState.ms.Write() 290 mpt.GAccTryUpdateTrieChannel <- struct{}{} 291 <-mpt.GAccTrieUpdatedChannel 292 } 293 294 // Write the DeliverTx state which is cache-wrapped and commit the MultiStore. 295 // The write to the DeliverTx state writes all state transitions to the root 296 // MultiStore (app.cms) so when Commit() is called is persists those values. 297 app.commitBlockCache() 298 app.deliverState.ms.Write() 299 300 var input iavl.TreeDeltaMap 301 if tmtypes.DownloadDelta && req.DeltaMap != nil { 302 var ok bool 303 input, ok = req.DeltaMap.(iavl.TreeDeltaMap) 304 if !ok { 305 panic("use TreeDeltaMap failed") 306 } 307 } 308 309 commitID, output := app.cms.CommitterCommitMap(input) // CommitterCommitMap 310 311 app.addCommitTraceInfo() 312 313 app.cms.ResetCount() 314 app.logger.Debug("Commit synced", "commit", amino.BytesHexStringer(commitID.Hash)) 315 316 // Reset the Check state to the latest committed. 317 // 318 // NOTE: This is safe because Tendermint holds a lock on the mempool for 319 // Commit. Use the header from this latest block. 320 app.setCheckState(header) 321 322 app.logger.Debug("deliverState reset by BaseApp.Commit", "height", header.Height) 323 // empty/reset the deliver state 324 app.deliverState = nil 325 326 var halt bool 327 328 switch { 329 case app.haltHeight > 0 && uint64(header.Height) >= app.haltHeight: 330 halt = true 331 332 case app.haltTime > 0 && header.Time.Unix() >= int64(app.haltTime): 333 halt = true 334 } 335 336 if halt { 337 // Halt the binary and allow Tendermint to receive the ResponseCommit 338 // response with the commit ID hash. This will allow the node to successfully 339 // restart and process blocks assuming the halt configuration has been 340 // reset or moved to a more distant value. 341 app.halt() 342 } 343 344 return abci.ResponseCommit{ 345 Data: commitID.Hash, 346 DeltaMap: output, 347 } 348 } 349 350 // halt attempts to gracefully shutdown the node via SIGINT and SIGTERM falling 351 // back on os.Exit if both fail. 352 func (app *BaseApp) halt() { 353 app.logger.Info("halting node per configuration", "height", app.haltHeight, "time", app.haltTime) 354 355 p, err := os.FindProcess(os.Getpid()) 356 if err == nil { 357 // attempt cascading signals in case SIGINT fails (os dependent) 358 sigIntErr := p.Signal(syscall.SIGINT) 359 sigTermErr := p.Signal(syscall.SIGTERM) 360 //Make sure the TrapSignal execute first 361 time.Sleep(50 * time.Millisecond) 362 if sigIntErr == nil || sigTermErr == nil { 363 return 364 } 365 } 366 367 // Resort to exiting immediately if the process could not be found or killed 368 // via SIGINT/SIGTERM signals. 369 app.logger.Info("failed to send SIGINT/SIGTERM; exiting...") 370 os.Exit(0) 371 } 372 373 // Query implements the ABCI interface. It delegates to CommitMultiStore if it 374 // implements Queryable. 375 func (app *BaseApp) Query(req abci.RequestQuery) abci.ResponseQuery { 376 ceptor := app.interceptors[req.Path] 377 if nil != ceptor { 378 // interceptor is like `aop`,it may record the request or rewrite the data in the request 379 // it should have funcs like `Begin` `End`, 380 // but for now, we will just redirect the path router,so once the request was intercepted(see #makeInterceptors), 381 // grpcQueryRouter#Route will return nil 382 ceptor.Intercept(&req) 383 } 384 path := splitPath(req.Path) 385 if len(path) == 0 { 386 return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "no query path provided")) 387 } 388 389 if grpcHandler := app.grpcQueryRouter.Route(req.Path); grpcHandler != nil { 390 return app.handleQueryGRPC(grpcHandler, req) 391 } 392 393 switch path[0] { 394 // "/app" prefix for special application queries 395 case "app": 396 return handleQueryApp(app, path, req) 397 398 case "store": 399 return handleQueryStore(app, path, req) 400 401 case "p2p": 402 return handleQueryP2P(app, path) 403 404 case "custom": 405 return handleQueryCustom(app, path, req) 406 default: 407 return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown query path")) 408 } 409 } 410 func handleSimulate(app *BaseApp, path []string, height int64, txBytes []byte, overrideBytes []byte) abci.ResponseQuery { 411 // if path contains address, it means 'eth_estimateGas' the sender 412 hasExtraPaths := len(path) > 2 413 var from string 414 if hasExtraPaths { 415 if addr, err := sdk.AccAddressFromBech32(path[2]); err == nil { 416 if err = sdk.VerifyAddressFormat(addr); err == nil { 417 from = path[2] 418 } 419 } 420 } 421 422 var tx sdk.Tx 423 var err error 424 if mem := GetGlobalMempool(); mem != nil { 425 tx, _ = mem.ReapEssentialTx(txBytes).(sdk.Tx) 426 } 427 if tx == nil { 428 tx, err = app.txDecoder(txBytes) 429 if err != nil { 430 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to decode tx")) 431 } 432 } 433 434 msgs := tx.GetMsgs() 435 436 if enableFastQuery() { 437 isPureWasm := true 438 for _, msg := range msgs { 439 if msg.Route() != "wasm" { 440 isPureWasm = false 441 break 442 } 443 } 444 if isPureWasm { 445 wasmSimulator := simulator.NewWasmSimulator() 446 wasmSimulator.Context().GasMeter().ConsumeGas(73000, "general ante check cost") 447 wasmSimulator.Context().GasMeter().ConsumeGas(uint64(10*len(txBytes)), "tx size cost") 448 res, err := wasmSimulator.Simulate(msgs) 449 if err != nil { 450 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to simulate wasm tx")) 451 } 452 453 gasMeter := wasmSimulator.Context().GasMeter() 454 simRes := sdk.SimulationResponse{ 455 GasInfo: sdk.GasInfo{ 456 GasUsed: gasMeter.GasConsumed(), 457 }, 458 Result: res, 459 } 460 return abci.ResponseQuery{ 461 Codespace: sdkerrors.RootCodespace, 462 Height: height, 463 Value: codec.Cdc.MustMarshalBinaryBare(simRes), 464 } 465 } 466 467 } 468 gInfo, res, err := app.Simulate(txBytes, tx, height, overrideBytes, from) 469 470 // if path contains mempool, it means to enable MaxGasUsedPerBlock 471 // return the actual gasUsed even though simulate tx failed 472 isMempoolSim := hasExtraPaths && path[2] == "mempool" 473 if err != nil && !isMempoolSim { 474 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to simulate tx")) 475 } 476 477 simRes := sdk.SimulationResponse{ 478 GasInfo: gInfo, 479 Result: res, 480 } 481 482 return abci.ResponseQuery{ 483 Codespace: sdkerrors.RootCodespace, 484 Height: height, 485 Value: codec.Cdc.MustMarshalBinaryBare(simRes), 486 } 487 } 488 func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery { 489 if len(path) >= 2 { 490 switch path[1] { 491 case "simulate": 492 return handleSimulate(app, path, req.Height, req.Data, nil) 493 494 case "simulateWithOverrides": 495 queryBytes := req.Data 496 var queryData types.SimulateData 497 if err := json.Unmarshal(queryBytes, &queryData); err != nil { 498 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to decode simulateOverrideData")) 499 } 500 return handleSimulate(app, path, req.Height, queryData.TxBytes, queryData.OverridesBytes) 501 502 case "trace": 503 var queryParam sdk.QueryTraceTx 504 err := json.Unmarshal(req.Data, &queryParam) 505 if err != nil { 506 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "invalid trace tx params")) 507 } 508 tmtx, err := GetABCITx(queryParam.TxHash.Bytes()) 509 if err != nil { 510 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "invalid trace tx bytes")) 511 } 512 tx, err := app.txDecoder(tmtx.Tx, tmtx.Height) 513 if err != nil { 514 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to decode tx")) 515 } 516 block, err := GetABCIBlock(tmtx.Height) 517 if err != nil { 518 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "invalid trace tx block header")) 519 } 520 res, err := app.TraceTx(queryParam, tx, tmtx.Index, block.Block) 521 if err != nil { 522 return sdkerrors.QueryResult(sdkerrors.Wrap(err, "failed to trace tx")) 523 } 524 return abci.ResponseQuery{ 525 Codespace: sdkerrors.RootCodespace, 526 Height: req.Height, 527 Value: codec.Cdc.MustMarshalBinaryBare(res), 528 } 529 530 case "version": 531 return abci.ResponseQuery{ 532 Codespace: sdkerrors.RootCodespace, 533 Height: req.Height, 534 Value: []byte(app.appVersion), 535 } 536 537 default: 538 return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown query: %s", path)) 539 } 540 } 541 542 return sdkerrors.QueryResult( 543 sdkerrors.Wrap( 544 sdkerrors.ErrUnknownRequest, 545 "expected second parameter to be either 'simulate' or 'version', neither was present", 546 ), 547 ) 548 } 549 550 func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery { 551 // "/store" prefix for store queries 552 queryable, ok := app.cms.(sdk.Queryable) 553 if !ok { 554 return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "multistore doesn't support queries")) 555 } 556 557 req.Path = "/" + strings.Join(path[1:], "/") 558 559 // when a client did not provide a query height, manually inject the latest 560 if req.Height == 0 { 561 req.Height = app.LastBlockHeight() 562 } 563 564 if req.Height <= 1 && req.Prove { 565 return sdkerrors.QueryResult( 566 sdkerrors.Wrap( 567 sdkerrors.ErrInvalidRequest, 568 "cannot query with proof when height <= 1; please provide a valid height", 569 ), 570 ) 571 } 572 573 resp := queryable.Query(req) 574 resp.Height = req.Height 575 576 return resp 577 } 578 579 func handleQueryP2P(app *BaseApp, path []string) abci.ResponseQuery { 580 // "/p2p" prefix for p2p queries 581 if len(path) >= 4 { 582 cmd, typ, arg := path[1], path[2], path[3] 583 switch cmd { 584 case "filter": 585 switch typ { 586 case "addr": 587 return app.FilterPeerByAddrPort(arg) 588 589 case "id": 590 return app.FilterPeerByID(arg) 591 } 592 593 default: 594 return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "expected second parameter to be 'filter'")) 595 } 596 } 597 598 return sdkerrors.QueryResult( 599 sdkerrors.Wrap( 600 sdkerrors.ErrUnknownRequest, "expected path is p2p filter <addr|id> <parameter>", 601 ), 602 ) 603 } 604 605 func handleQueryCustom(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery { 606 // path[0] should be "custom" because "/custom" prefix is required for keeper 607 // queries. 608 // 609 // The QueryRouter routes using path[1]. For example, in the path 610 // "custom/gov/proposal", QueryRouter routes using "gov". 611 if len(path) < 2 || path[1] == "" { 612 return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "no route for custom query specified")) 613 } 614 615 querier := app.queryRouter.Route(path[1]) 616 if querier == nil { 617 return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "no custom querier found for route %s", path[1])) 618 } 619 620 // when a client did not provide a query height, manually inject the latest 621 if req.Height == 0 { 622 req.Height = app.LastBlockHeight() 623 } 624 625 if req.Height <= 1 && req.Prove { 626 return sdkerrors.QueryResult( 627 sdkerrors.Wrap( 628 sdkerrors.ErrInvalidRequest, 629 "cannot query with proof when height <= 1; please provide a valid height", 630 ), 631 ) 632 } 633 634 cacheMS, err := app.cms.CacheMultiStoreWithVersion(req.Height) 635 if err != nil { 636 return sdkerrors.QueryResult( 637 sdkerrors.Wrapf( 638 sdkerrors.ErrInvalidRequest, 639 "failed to load state at height %d; %s (latest height: %d)", req.Height, err, app.LastBlockHeight(), 640 ), 641 ) 642 } 643 644 // cache wrap the commit-multistore for safety 645 ctx := sdk.NewContext( 646 cacheMS, app.checkState.ctx.BlockHeader(), true, app.logger, 647 ) 648 ctx.SetMinGasPrices(app.minGasPrices) 649 ctx.SetBlockHeight(req.Height) 650 651 // Passes the rest of the path as an argument to the querier. 652 // 653 // For example, in the path "custom/gov/proposal/test", the gov querier gets 654 // []string{"proposal", "test"} as the path. 655 resBytes, err := querier(ctx, path[2:], req) 656 if err != nil { 657 space, code, log := sdkerrors.ABCIInfo(err, false) 658 return abci.ResponseQuery{ 659 Code: code, 660 Codespace: space, 661 Log: log, 662 Height: req.Height, 663 } 664 } 665 666 return abci.ResponseQuery{ 667 Height: req.Height, 668 Value: resBytes, 669 } 670 } 671 672 // splitPath splits a string path using the delimiter '/'. 673 // 674 // e.g. "this/is/funny" becomes []string{"this", "is", "funny"} 675 func splitPath(requestPath string) (path []string) { 676 path = strings.Split(requestPath, "/") 677 678 // first element is empty string 679 if len(path) > 0 && path[0] == "" { 680 path = path[1:] 681 } 682 683 return path 684 } 685 686 var ( 687 fastQuery bool 688 fqOnce sync.Once 689 ) 690 691 func enableFastQuery() bool { 692 fqOnce.Do(func() { 693 fastQuery = viper.GetBool("fast-query") 694 }) 695 return fastQuery 696 }