github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/rpc/tests/mock_client.go (about) 1 package tests 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "net" 7 "net/http" 8 "time" 9 10 blockindexer "github.com/fibonacci-chain/fbc/libs/tendermint/state/indexer/block/kv" 11 12 "github.com/fibonacci-chain/fbc/libs/tendermint/global" 13 14 apptesting "github.com/fibonacci-chain/fbc/libs/ibc-go/testing" 15 abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types" 16 tmcfg "github.com/fibonacci-chain/fbc/libs/tendermint/config" 17 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/bytes" 18 tmbytes "github.com/fibonacci-chain/fbc/libs/tendermint/libs/bytes" 19 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log" 20 tmmath "github.com/fibonacci-chain/fbc/libs/tendermint/libs/math" 21 "github.com/fibonacci-chain/fbc/libs/tendermint/mempool" 22 mempl "github.com/fibonacci-chain/fbc/libs/tendermint/mempool" 23 "github.com/fibonacci-chain/fbc/libs/tendermint/proxy" 24 "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/client" 25 "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/client/mock" 26 rpccore "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/core" 27 ctypes "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/core/types" 28 rpcserver "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/jsonrpc/server" 29 sm "github.com/fibonacci-chain/fbc/libs/tendermint/state" 30 tmstate "github.com/fibonacci-chain/fbc/libs/tendermint/state" 31 "github.com/fibonacci-chain/fbc/libs/tendermint/state/txindex" 32 "github.com/fibonacci-chain/fbc/libs/tendermint/state/txindex/kv" 33 "github.com/fibonacci-chain/fbc/libs/tendermint/state/txindex/null" 34 "github.com/fibonacci-chain/fbc/libs/tendermint/store" 35 "github.com/fibonacci-chain/fbc/libs/tendermint/types" 36 dbm "github.com/fibonacci-chain/fbc/libs/tm-db" 37 "github.com/tendermint/go-amino" 38 ) 39 40 type MockClient struct { 41 mock.Client 42 chain apptesting.TestChainI 43 env *rpccore.Environment 44 state tmstate.State 45 priv types.PrivValidator 46 } 47 48 func (m *MockClient) BlockInfo(height *int64) (meta *types.BlockMeta, err error) { 49 defer func() { 50 if r := recover(); r != nil { 51 meta = nil 52 err = fmt.Errorf("panic in BlockInfo: %v", r) 53 } 54 }() 55 if m.Client.SignClient != nil { 56 return m.Client.BlockInfo(height) 57 } 58 if height == nil { 59 return nil, fmt.Errorf("height is nil") 60 } 61 if m.env != nil && m.env.BlockStore != nil { 62 return m.env.BlockStore.LoadBlockMeta(*height), nil 63 } 64 return nil, fmt.Errorf("blockstore is nil") 65 } 66 67 func (m *MockClient) StartTmRPC() (net.Listener, string, error) { 68 69 rpccore.SetEnvironment(m.env) 70 coreCodec := amino.NewCodec() 71 ctypes.RegisterAmino(coreCodec) 72 rpccore.AddUnsafeRoutes() 73 rpcLogger := log.NewNopLogger() 74 config := rpcserver.DefaultConfig() 75 76 // we may expose the rpc over both a unix and tcp socket 77 mux := http.NewServeMux() 78 wm := rpcserver.NewWebsocketManager(rpccore.Routes, coreCodec, 79 rpcserver.OnDisconnect(func(remoteAddr string) {}), 80 rpcserver.ReadLimit(config.MaxBodyBytes), 81 ) 82 mux.HandleFunc("/websocket", wm.WebsocketHandler) 83 rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger) 84 listener, err := rpcserver.Listen( 85 "tcp://127.0.0.1:0", 86 config, 87 ) 88 if err != nil { 89 return nil, "", err 90 } 91 92 var rootHandler http.Handler = mux 93 go rpcserver.Serve( 94 listener, 95 rootHandler, 96 rpcLogger, 97 config, 98 ) 99 return listener, fmt.Sprintf("http://localhost:%d", listener.Addr().(*net.TCPAddr).Port), nil 100 } 101 func createAndStartProxyAppConns(clientCreator proxy.ClientCreator, logger log.Logger) (proxy.AppConns, error) { 102 proxyApp := proxy.NewAppConns(clientCreator) 103 proxyApp.SetLogger(logger.With("module", "proxy")) 104 if err := proxyApp.Start(); err != nil { 105 return nil, fmt.Errorf("error starting proxy app connections: %v", err) 106 } 107 return proxyApp, nil 108 } 109 110 func NewMockClient(chainId string, chain apptesting.TestChainI, app abci.Application) *MockClient { 111 config := tmcfg.ResetTestRootWithChainID("blockchain_reactor_test", chainId) 112 113 papp := proxy.NewLocalClientCreator(app) 114 proxyApp, err := createAndStartProxyAppConns(papp, log.NewNopLogger()) 115 if err != nil { 116 panic(err) 117 } 118 119 mc := &MockClient{ 120 chain: chain, 121 env: &rpccore.Environment{ 122 BlockStore: store.NewBlockStore(dbm.NewMemDB()), 123 StateDB: dbm.NewMemDB(), 124 TxIndexer: kv.NewTxIndex(dbm.NewMemDB()), 125 BlockIndexer: blockindexer.New(dbm.NewMemDB()), 126 }, 127 } 128 mc.state, err = tmstate.LoadStateFromDBOrGenesisFile(mc.env.StateDB, config.GenesisFile()) 129 if err != nil { 130 panic(err) 131 } 132 mempool := mempool.NewCListMempool( 133 config.Mempool, 134 proxyApp.Mempool(), 135 mc.state.LastBlockHeight, 136 ) 137 mc.env.Mempool = mempool 138 mc.env.PubKey = chain.SenderAccount().GetPubKey() 139 140 db := dbm.NewMemDB() 141 sm.SaveState(db, mc.state) 142 return mc 143 } 144 func (c MockClient) makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { 145 tx := c.env.Mempool.ReapMaxTxs(1000) 146 block, _ := state.MakeBlock(height, tx, lastCommit, nil, state.Validators.GetProposer().Address) 147 c.env.Mempool.Flush() 148 return block 149 } 150 func (c *MockClient) CommitBlock() { 151 if c.priv == nil { 152 _, c.priv = types.RandValidator(false, 30) 153 } 154 blockHeight := c.state.LastBlockHeight + 1 155 lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil) 156 thisBlock := c.makeBlock(blockHeight, c.state, lastCommit) 157 thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) 158 blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()} 159 160 if blockHeight > 1 { 161 lastBlockMeta := c.env.BlockStore.LoadBlockMeta(blockHeight - 1) 162 lastBlock := c.env.BlockStore.LoadBlock(blockHeight - 1) 163 164 vote, err := types.MakeVote( 165 lastBlock.Header.Height, 166 lastBlockMeta.BlockID, 167 c.state.Validators, 168 c.priv, 169 lastBlock.Header.ChainID, 170 time.Now(), 171 ) 172 if err != nil { 173 panic(err) 174 } 175 lastCommit = types.NewCommit(vote.Height, vote.Round, 176 lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()}) 177 178 header := abci.Header{ 179 Height: blockHeight, 180 LastBlockId: abci.BlockID{ 181 Hash: c.state.LastBlockID.Hash, 182 }, 183 ChainID: c.state.ChainID, 184 } 185 c.chain.App().BeginBlock(abci.RequestBeginBlock{ 186 Hash: thisBlock.Hash(), 187 Header: header, 188 }) 189 var resDeliverTxs []*abci.ResponseDeliverTx 190 for _, tx := range thisBlock.Txs { 191 resp := c.chain.App().DeliverTx(abci.RequestDeliverTx{ 192 Tx: tx, 193 }) 194 resDeliverTxs = append(resDeliverTxs, &resp) 195 } 196 endBlockResp := c.chain.App().EndBlock(abci.RequestEndBlock{ 197 Height: blockHeight, 198 }) 199 blockResp := &tmstate.ABCIResponses{ 200 DeliverTxs: resDeliverTxs, 201 EndBlock: &endBlockResp, 202 } 203 c.state = tmstate.State{ 204 Version: c.state.Version, 205 ChainID: c.state.ChainID, 206 LastBlockHeight: blockHeight, 207 LastBlockID: blockID, 208 LastBlockTime: thisBlock.Header.Time, 209 NextValidators: c.state.NextValidators, 210 Validators: c.state.NextValidators.Copy(), 211 LastValidators: c.state.Validators.Copy(), 212 LastHeightValidatorsChanged: 0, 213 ConsensusParams: c.state.ConsensusParams, 214 LastHeightConsensusParamsChanged: blockHeight + 1, 215 LastResultsHash: blockResp.ResultsHash(), 216 AppHash: nil, 217 } 218 //thisBlock.Height = state.LastBlockHeight + 1 219 c.env.BlockStore.SaveBlock(thisBlock, thisParts, lastCommit) 220 c.CommitTx(blockHeight, thisBlock.Txs, resDeliverTxs) 221 c.chain.App().Commit(abci.RequestCommit{}) 222 } else { 223 c.env.BlockStore.SaveBlock(thisBlock, thisParts, lastCommit) 224 c.state = tmstate.State{ 225 Version: c.state.Version, 226 ChainID: c.state.ChainID, 227 LastBlockHeight: blockHeight, 228 LastBlockID: blockID, 229 LastBlockTime: thisBlock.Header.Time, 230 NextValidators: c.state.NextValidators, 231 Validators: c.state.NextValidators.Copy(), 232 LastValidators: c.state.Validators.Copy(), 233 LastHeightValidatorsChanged: 0, 234 ConsensusParams: c.state.ConsensusParams, 235 LastHeightConsensusParamsChanged: blockHeight + 1, 236 LastResultsHash: c.state.LastResultsHash, 237 AppHash: nil, 238 } 239 } 240 global.SetGlobalHeight(blockHeight) 241 } 242 func (c *MockClient) CommitTx(height int64, txs types.Txs, resDeliverTxs []*abci.ResponseDeliverTx) { 243 batch := txindex.NewBatch(int64(len(txs))) 244 for i, tx := range txs { 245 txResult := &types.TxResult{ 246 Height: height, 247 Index: uint32(i), 248 Tx: tx, 249 Result: *resDeliverTxs[i], 250 } 251 252 if err := batch.Add(txResult); err != nil { 253 panic(err) 254 } 255 err := c.env.TxIndexer.AddBatch(batch) 256 if err != nil { 257 panic(err) 258 } 259 } 260 } 261 func (c MockClient) ABCIQueryWithOptions( 262 path string, 263 data bytes.HexBytes, 264 opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 265 resQuery := c.chain.App().Query(abci.RequestQuery{ 266 Path: path, 267 Data: data, 268 Height: opts.Height, 269 Prove: opts.Prove, 270 }) 271 return &ctypes.ResultABCIQuery{Response: resQuery}, nil 272 } 273 func (c MockClient) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 274 resCh := make(chan *abci.Response, 1) 275 err := c.env.Mempool.CheckTx(tx, func(res *abci.Response) { 276 resCh <- res 277 }, mempl.TxInfo{}) 278 if err != nil { 279 return nil, err 280 } 281 res := <-resCh 282 r := res.GetCheckTx() 283 return &ctypes.ResultBroadcastTx{ 284 Code: r.Code, 285 Data: r.Data, 286 Log: r.Log, 287 Codespace: r.Codespace, 288 Hash: tx.Hash(c.env.BlockStore.Height()), 289 }, nil 290 } 291 292 // error if either min or max are negative or min > max 293 // if 0, use blockstore base for min, latest block height for max 294 // enforce limit. 295 func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) { 296 // filter negatives 297 if min < 0 || max < 0 { 298 return min, max, fmt.Errorf("heights must be non-negative") 299 } 300 301 // adjust for default values 302 if max == 0 { 303 max = height 304 } 305 306 // limit max to the height 307 max = tmmath.MinInt64(height, max) 308 309 // limit min to the base 310 min = tmmath.MaxInt64(base, min) 311 312 // limit min to within `limit` of max 313 // so the total number of blocks returned will be `limit` 314 min = tmmath.MaxInt64(min, max-limit+1) 315 316 if min > max { 317 return min, max, fmt.Errorf("min height %d can't be greater than max height %d", min, max) 318 } 319 return min, max, nil 320 } 321 func (c MockClient) Status() (*ctypes.ResultStatus, error) { 322 var ( 323 earliestBlockHash tmbytes.HexBytes 324 earliestAppHash tmbytes.HexBytes 325 earliestBlockTimeNano int64 326 327 earliestBlockHeight = c.env.BlockStore.Base() 328 ) 329 330 if earliestBlockMeta := c.env.BlockStore.LoadBlockMeta(earliestBlockHeight); earliestBlockMeta != nil { 331 earliestAppHash = earliestBlockMeta.Header.AppHash 332 earliestBlockHash = earliestBlockMeta.BlockID.Hash 333 earliestBlockTimeNano = earliestBlockMeta.Header.Time.UnixNano() 334 } 335 336 var ( 337 latestBlockHash tmbytes.HexBytes 338 latestAppHash tmbytes.HexBytes 339 latestBlockTimeNano int64 340 341 latestHeight = c.env.BlockStore.Height() 342 ) 343 344 if latestHeight != 0 { 345 latestBlockMeta := c.env.BlockStore.LoadBlockMeta(latestHeight) 346 if latestBlockMeta != nil { 347 latestBlockHash = latestBlockMeta.BlockID.Hash 348 latestAppHash = latestBlockMeta.Header.AppHash 349 latestBlockTimeNano = latestBlockMeta.Header.Time.UnixNano() 350 } 351 } 352 353 // Return the very last voting power, not the voting power of this validator 354 // during the last block. 355 var votingPower int64 356 blockHeight := c.env.BlockStore.Height() + 1 357 if val := c.validatorAtHeight(blockHeight); val != nil { 358 votingPower = val.VotingPower 359 } 360 361 result := &ctypes.ResultStatus{ 362 //NodeInfo: c.env.P2PTransport.NodeInfo().(p2p.DefaultNodeInfo), 363 SyncInfo: ctypes.SyncInfo{ 364 LatestBlockHash: latestBlockHash, 365 LatestAppHash: latestAppHash, 366 LatestBlockHeight: latestHeight, 367 LatestBlockTime: time.Unix(0, latestBlockTimeNano), 368 EarliestBlockHash: earliestBlockHash, 369 EarliestAppHash: earliestAppHash, 370 EarliestBlockHeight: earliestBlockHeight, 371 EarliestBlockTime: time.Unix(0, earliestBlockTimeNano), 372 //CatchingUp: c.env.ConsensusReactor.FastSync(), 373 }, 374 ValidatorInfo: ctypes.ValidatorInfo{ 375 Address: c.env.PubKey.Address(), 376 PubKey: c.env.PubKey, 377 VotingPower: votingPower, 378 }, 379 } 380 381 return result, nil 382 } 383 func (c MockClient) validatorAtHeight(h int64) *types.Validator { 384 vals, err := sm.LoadValidators(c.env.StateDB, h) 385 if err != nil { 386 return nil 387 } 388 _, val := vals.GetByIndex(0) 389 return val 390 } 391 392 // latestHeight can be either latest committed or uncommitted (+1) height. 393 func (c MockClient) getHeight(latestHeight int64, heightPtr *int64) (int64, error) { 394 if heightPtr != nil { 395 height := *heightPtr 396 if height <= 0 { 397 return 0, fmt.Errorf("height must be greater than 0, but got %d", height) 398 } 399 if height > latestHeight { 400 return 0, fmt.Errorf("height %d must be less than or equal to the current blockchain height %d", 401 height, latestHeight) 402 } 403 base := c.env.BlockStore.Base() 404 if height < base { 405 return 0, fmt.Errorf("height %v is not available, blocks pruned at height %v", 406 height, base) 407 } 408 return height, nil 409 } 410 return latestHeight, nil 411 } 412 func (c *MockClient) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 413 const limit int64 = 20 414 var err error 415 minHeight, maxHeight, err = filterMinMax( 416 c.env.BlockStore.Base(), 417 c.env.BlockStore.Height(), 418 minHeight, 419 maxHeight, 420 limit) 421 if err != nil { 422 return nil, err 423 } 424 blockMetas := []*types.BlockMeta{} 425 for height := maxHeight; height >= minHeight; height-- { 426 blockMeta := c.env.BlockStore.LoadBlockMeta(height) 427 blockMetas = append(blockMetas, blockMeta) 428 } 429 return &ctypes.ResultBlockchainInfo{ 430 LastHeight: c.env.BlockStore.Height(), 431 BlockMetas: blockMetas}, nil 432 } 433 434 func (c *MockClient) LatestBlockNumber() (int64, error) { 435 return c.env.BlockStore.Height(), nil 436 } 437 438 func (c *MockClient) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { 439 return &ctypes.ResultUnconfirmedTxs{ 440 Count: c.env.Mempool.Size(), 441 Total: c.env.Mempool.Size(), 442 TotalBytes: c.env.Mempool.TxsBytes()}, nil 443 } 444 func (c *MockClient) Block(heightPtr *int64) (*ctypes.ResultBlock, error) { 445 height, err := c.getHeight(c.env.BlockStore.Height(), heightPtr) 446 if err != nil { 447 return nil, err 448 } 449 450 block := c.env.BlockStore.LoadBlock(height) 451 blockMeta := c.env.BlockStore.LoadBlockMeta(height) 452 if blockMeta == nil { 453 return &ctypes.ResultBlock{BlockID: types.BlockID{}, Block: block}, nil 454 } 455 return &ctypes.ResultBlock{BlockID: blockMeta.BlockID, Block: block}, nil 456 } 457 func (c *MockClient) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { 458 // if index is disabled, return error 459 if _, ok := c.env.TxIndexer.(*null.TxIndex); ok { 460 return nil, fmt.Errorf("transaction indexing is disabled") 461 } 462 463 r, err := c.env.TxIndexer.Get(hash) 464 if err != nil { 465 return nil, err 466 } 467 468 if r == nil { 469 return nil, fmt.Errorf("tx (%X) not found", hash) 470 } 471 472 height := r.Height 473 index := r.Index 474 475 var proof types.TxProof 476 if prove { 477 block := c.env.BlockStore.LoadBlock(height) 478 proof = block.Data.Txs.Proof(int(index), block.Height) // XXX: overflow on 32-bit machines 479 } 480 481 return &ctypes.ResultTx{ 482 Hash: hash, 483 Height: height, 484 Index: index, 485 TxResult: r.Result, 486 Tx: r.Tx, 487 Proof: proof, 488 }, nil 489 } 490 func (c *MockClient) GetAddressList() (*ctypes.ResultUnconfirmedAddresses, error) { 491 addressList := c.env.Mempool.GetAddressList() 492 return &ctypes.ResultUnconfirmedAddresses{ 493 Addresses: addressList, 494 }, nil 495 } 496 func (c *MockClient) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { 497 txs := c.env.Mempool.ReapMaxTxs(limit) 498 return &ctypes.ResultUnconfirmedTxs{ 499 Count: len(txs), 500 Total: c.env.Mempool.Size(), 501 TotalBytes: c.env.Mempool.TxsBytes(), 502 Txs: txs}, nil 503 } 504 func (c MockClient) GetUnconfirmedTxByHash(hash [sha256.Size]byte) (types.Tx, error) { 505 return c.env.Mempool.GetTxByHash(hash) 506 } 507 func (c *MockClient) UserUnconfirmedTxs(address string, limit int) (*ctypes.ResultUserUnconfirmedTxs, error) { 508 txs := c.env.Mempool.ReapUserTxs(address, limit) 509 return &ctypes.ResultUserUnconfirmedTxs{ 510 Count: len(txs), 511 Txs: txs}, nil 512 } 513 func (c MockClient) UserNumUnconfirmedTxs(address string) (*ctypes.ResultUserUnconfirmedTxs, error) { 514 nums := c.env.Mempool.ReapUserTxsCnt(address) 515 return &ctypes.ResultUserUnconfirmedTxs{ 516 Count: nums}, nil 517 } 518 func (c MockClient) GetPendingNonce(address string) (*ctypes.ResultPendingNonce, bool) { 519 nonce, ok := c.env.Mempool.GetPendingNonce(address) 520 if !ok { 521 return nil, false 522 } 523 return &ctypes.ResultPendingNonce{ 524 Nonce: nonce, 525 }, true 526 }