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  }