@@ -157,7 +157,7 @@ func NewServer(
 
 // GetAccount returns the metadata of an account
 func (api *Server) GetAccount(ctx context.Context, in *iotexapi.GetAccountRequest) (*iotexapi.GetAccountResponse, error) {
-	state, err := api.bc.StateByAddr(in.Address)
+	state, err := api.bc.Factory().AccountState(in.Address)
 	if err != nil {
 		return nil, status.Error(codes.NotFound, err.Error())
 	}
// Copyright (c) 2019 IoTeX Foundation
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
// License 2.0 that can be found in the LICENSE file.

package api

import (
	"context"
	"encoding/hex"
	"math"
	"math/big"
	"net"
	"strconv"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/iotexproject/go-pkgs/hash"
	"github.com/iotexproject/iotex-address/address"
	"github.com/iotexproject/iotex-election/committee"
	"github.com/iotexproject/iotex-proto/golang/iotexapi"
	"github.com/iotexproject/iotex-proto/golang/iotextypes"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	"google.golang.org/genproto/googleapis/rpc/errdetails"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"

	"github.com/iotexproject/iotex-core/action"
	"github.com/iotexproject/iotex-core/action/protocol"
	"github.com/iotexproject/iotex-core/action/protocol/poll"
	"github.com/iotexproject/iotex-core/action/protocol/rolldpos"
	"github.com/iotexproject/iotex-core/actpool"
	"github.com/iotexproject/iotex-core/blockchain"
	"github.com/iotexproject/iotex-core/blockchain/block"
	"github.com/iotexproject/iotex-core/blockchain/blockdao"
	"github.com/iotexproject/iotex-core/blockindex"
	"github.com/iotexproject/iotex-core/config"
	"github.com/iotexproject/iotex-core/db"
	"github.com/iotexproject/iotex-core/gasstation"
	"github.com/iotexproject/iotex-core/pkg/log"
	"github.com/iotexproject/iotex-core/pkg/util/byteutil"
	"github.com/iotexproject/iotex-core/pkg/version"
	"github.com/iotexproject/iotex-core/state"
)

var (
	// ErrInternalServer indicates the internal server error
	ErrInternalServer = errors.New("internal server error")
	// ErrReceipt indicates the error of receipt
	ErrReceipt = errors.New("invalid receipt")
	// ErrAction indicates the error of action
	ErrAction        = errors.New("invalid action")
	candidateNameLen = 12
)

// BroadcastOutbound sends a broadcast message to the whole network
type BroadcastOutbound func(ctx context.Context, chainID uint32, msg proto.Message) error

// Config represents the config to setup api
type Config struct {
	broadcastHandler  BroadcastOutbound
	electionCommittee committee.Committee
}

// Option is the option to override the api config
type Option func(cfg *Config) error

// WithBroadcastOutbound is the option to broadcast msg outbound
func WithBroadcastOutbound(broadcastHandler BroadcastOutbound) Option {
	return func(cfg *Config) error {
		cfg.broadcastHandler = broadcastHandler
		return nil
	}
}

// WithNativeElection is the option to return native election data through API.
func WithNativeElection(committee committee.Committee) Option {
	return func(cfg *Config) error {
		cfg.electionCommittee = committee
		return nil
	}
}

// Server provides api for user to query blockchain data
type Server struct {
	bc                blockchain.Blockchain
	dao               blockdao.BlockDAO
	indexer           blockindex.Indexer
	ap                actpool.ActPool
	gs                *gasstation.GasStation
	broadcastHandler  BroadcastOutbound
	cfg               config.Config
	registry          *protocol.Registry
	chainListener     Listener
	grpcserver        *grpc.Server
	hasActionIndex    bool
	electionCommittee committee.Committee
}

// NewServer creates a new server
func NewServer(
	cfg config.Config,
	chain blockchain.Blockchain,
	dao blockdao.BlockDAO,
	indexer blockindex.Indexer,
	actPool actpool.ActPool,
	registry *protocol.Registry,
	opts ...Option,
) (*Server, error) {
	apiCfg := Config{}
	for _, opt := range opts {
		if err := opt(&apiCfg); err != nil {
			return nil, err
		}
	}

	if cfg.API == (config.API{}) {
		log.L().Warn("API server is not configured.")
		cfg.API = config.Default.API
	}

	if cfg.API.RangeQueryLimit < uint64(cfg.API.TpsWindow) {
		return nil, errors.New("range query upper limit cannot be less than tps window")
	}

	svr := &Server{
		bc:                chain,
		dao:               dao,
		indexer:           indexer,
		ap:                actPool,
		broadcastHandler:  apiCfg.broadcastHandler,
		cfg:               cfg,
		registry:          registry,
		chainListener:     NewChainListener(),
		gs:                gasstation.NewGasStation(chain, cfg.API),
		electionCommittee: apiCfg.electionCommittee,
	}
	if _, ok := cfg.Plugins[config.GatewayPlugin]; ok {
		svr.hasActionIndex = true
	}
	svr.grpcserver = grpc.NewServer(
		grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
		grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
	)
	iotexapi.RegisterAPIServiceServer(svr.grpcserver, svr)
	grpc_prometheus.Register(svr.grpcserver)
	reflection.Register(svr.grpcserver)

	return svr, nil
}

// GetAccount returns the metadata of an account
func (api *Server) GetAccount(ctx context.Context, in *iotexapi.GetAccountRequest) (*iotexapi.GetAccountResponse, error) {
	state, err := api.bc.StateByAddr(in.Address)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	pendingNonce, err := api.ap.GetPendingNonce(in.Address)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	if api.indexer == nil {
		return nil, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
	}
	addr, err := address.FromString(in.Address)
	if err != nil {
		return nil, err
	}
	numActions, err := api.indexer.GetActionCountByAddress(hash.BytesToHash160(addr.Bytes()))
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	accountMeta := &iotextypes.AccountMeta{
		Address:      in.Address,
		Balance:      state.Balance.String(),
		Nonce:        state.Nonce,
		PendingNonce: pendingNonce,
		NumActions:   numActions,
	}
	return &iotexapi.GetAccountResponse{AccountMeta: accountMeta}, nil
}

// GetActions returns actions
func (api *Server) GetActions(ctx context.Context, in *iotexapi.GetActionsRequest) (*iotexapi.GetActionsResponse, error) {
	if (!api.hasActionIndex || api.indexer == nil) && (in.GetByHash() != nil || in.GetByAddr() != nil) {
		return nil, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
	}
	switch {
	case in.GetByIndex() != nil:
		request := in.GetByIndex()
		return api.getActions(request.Start, request.Count)
	case in.GetByHash() != nil:
		request := in.GetByHash()
		return api.getSingleAction(request.ActionHash, request.CheckPending)
	case in.GetByAddr() != nil:
		request := in.GetByAddr()
		return api.getActionsByAddress(request.Address, request.Start, request.Count)
	case in.GetUnconfirmedByAddr() != nil:
		request := in.GetUnconfirmedByAddr()
		return api.getUnconfirmedActionsByAddress(request.Address, request.Start, request.Count)
	case in.GetByBlk() != nil:
		request := in.GetByBlk()
		return api.getActionsByBlock(request.BlkHash, request.Start, request.Count)
	default:
		return nil, status.Error(codes.NotFound, "invalid GetActionsRequest type")
	}
}

// GetBlockMetas returns block metadata
func (api *Server) GetBlockMetas(ctx context.Context, in *iotexapi.GetBlockMetasRequest) (*iotexapi.GetBlockMetasResponse, error) {
	switch {
	case in.GetByIndex() != nil:
		request := in.GetByIndex()
		return api.getBlockMetas(request.Start, request.Count)
	case in.GetByHash() != nil:
		request := in.GetByHash()
		return api.getBlockMeta(request.BlkHash)
	default:
		return nil, status.Error(codes.NotFound, "invalid GetBlockMetasRequest type")
	}
}

// GetChainMeta returns blockchain metadata
func (api *Server) GetChainMeta(ctx context.Context, in *iotexapi.GetChainMetaRequest) (*iotexapi.GetChainMetaResponse, error) {
	tipHeight := api.bc.TipHeight()
	if tipHeight == 0 {
		return &iotexapi.GetChainMetaResponse{
			ChainMeta: &iotextypes.ChainMeta{
				Epoch: &iotextypes.EpochData{},
			},
		}, nil
	}
	if api.indexer == nil {
		// TODO: in case indexer does not exist, may consider return a value like 0 instead of exit
		return nil, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
	}
	totalActions, err := api.indexer.GetTotalActions()
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	blockLimit := int64(api.cfg.API.TpsWindow)
	if blockLimit <= 0 {
		return nil, status.Errorf(codes.Internal, "block limit is %d", blockLimit)
	}

	// avoid genesis block
	if int64(tipHeight) < blockLimit {
		blockLimit = int64(tipHeight)
	}
	r, err := api.getBlockMetas(tipHeight-uint64(blockLimit)+1, uint64(blockLimit))
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	blks := r.BlkMetas

	if len(blks) == 0 {
		return nil, status.Error(codes.NotFound, "get 0 blocks! not able to calculate aps")
	}

	var numActions int64
	for _, blk := range blks {
		numActions += blk.NumActions
	}

	p, ok := api.registry.Find(rolldpos.ProtocolID)
	if !ok {
		return nil, status.Error(codes.Internal, "rolldpos protocol is not registered")
	}
	rp, ok := p.(*rolldpos.Protocol)
	if !ok {
		return nil, status.Error(codes.Internal, "fail to cast rolldpos protocol")
	}
	epochNum := rp.GetEpochNum(tipHeight)
	epochHeight := rp.GetEpochHeight(epochNum)
	gravityChainStartHeight, err := api.getGravityChainStartHeight(epochHeight)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}

	t1 := time.Unix(blks[0].Timestamp.GetSeconds(), int64(blks[0].Timestamp.GetNanos()))
	t2 := time.Unix(blks[len(blks)-1].Timestamp.GetSeconds(), int64(blks[len(blks)-1].Timestamp.GetNanos()))
	// duration of time difference in milli-seconds
	// TODO: use config.Genesis.BlockInterval after PR1289 merges
	timeDiff := (t2.Sub(t1) + 10*time.Second) / time.Millisecond
	tps := float32(numActions*1000) / float32(timeDiff)

	chainMeta := &iotextypes.ChainMeta{
		Height: tipHeight,
		Epoch: &iotextypes.EpochData{
			Num:                     epochNum,
			Height:                  epochHeight,
			GravityChainStartHeight: gravityChainStartHeight,
		},
		NumActions: int64(totalActions),
		Tps:        int64(math.Ceil(float64(tps))),
		TpsFloat:   tps,
	}

	return &iotexapi.GetChainMetaResponse{ChainMeta: chainMeta}, nil
}

// GetServerMeta gets the server metadata
func (api *Server) GetServerMeta(ctx context.Context,
	in *iotexapi.GetServerMetaRequest) (*iotexapi.GetServerMetaResponse, error) {
	return &iotexapi.GetServerMetaResponse{ServerMeta: &iotextypes.ServerMeta{
		PackageVersion:  version.PackageVersion,
		PackageCommitID: version.PackageCommitID,
		GitStatus:       version.GitStatus,
		GoVersion:       version.GoVersion,
		BuildTime:       version.BuildTime,
	}}, nil
}

// SendAction is the API to send an action to blockchain.
func (api *Server) SendAction(ctx context.Context, in *iotexapi.SendActionRequest) (*iotexapi.SendActionResponse, error) {
	log.L().Debug("receive send action request")
	var selp action.SealedEnvelope
	var err error
	if err = selp.LoadProto(in.Action); err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	// Add to local actpool
	if err = api.ap.Add(selp); err != nil {
		log.L().Debug(err.Error())
		var desc string
		switch errors.Cause(err) {
		case action.ErrBalance:
			desc = "Invalid balance"
		case action.ErrInsufficientBalanceForGas:
			desc = "Insufficient balance for gas"
		case action.ErrNonce:
			desc = "Invalid nonce"
		case action.ErrAddress:
			desc = "Blacklisted address"
		case action.ErrActPool:
			desc = "Invalid actpool"
		case action.ErrGasPrice:
			desc = "Invalid gas price"
		default:
			desc = "Unknown"
		}
		st := status.New(codes.Internal, err.Error())
		v := &errdetails.BadRequest_FieldViolation{
			Field:       "Action rejected",
			Description: desc,
		}
		br := &errdetails.BadRequest{}
		br.FieldViolations = append(br.FieldViolations, v)
		st, err := st.WithDetails(br)
		if err != nil {
			log.S().Panicf("Unexpected error attaching metadata: %v", err)
		}
		return nil, st.Err()
	}
	// If there is no error putting into local actpool,
	// Broadcast it to the network
	if err = api.broadcastHandler(context.Background(), api.bc.ChainID(), in.Action); err != nil {
		log.L().Warn("Failed to broadcast SendAction request.", zap.Error(err))
	}
	hash := selp.Hash()
	return &iotexapi.SendActionResponse{ActionHash: hex.EncodeToString(hash[:])}, nil
}

// GetReceiptByAction gets receipt with corresponding action hash
func (api *Server) GetReceiptByAction(ctx context.Context, in *iotexapi.GetReceiptByActionRequest) (*iotexapi.GetReceiptByActionResponse, error) {
	if !api.hasActionIndex || api.indexer == nil {
		return nil, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
	}
	actHash, err := hash.HexStringToHash256(in.ActionHash)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	receipt, err := api.GetReceiptByActionHash(actHash)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	blkHash, err := api.getBlockHashByActionHash(actHash)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	return &iotexapi.GetReceiptByActionResponse{
		ReceiptInfo: &iotexapi.ReceiptInfo{
			Receipt: receipt.ConvertToReceiptPb(),
			BlkHash: hex.EncodeToString(blkHash[:]),
		},
	}, nil
}

// ReadContract reads the state in a contract address specified by the slot
func (api *Server) ReadContract(ctx context.Context, in *iotexapi.ReadContractRequest) (*iotexapi.ReadContractResponse, error) {
	log.L().Debug("receive read smart contract request")

	sc := &action.Execution{}
	if err := sc.LoadProto(in.Execution); err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}

	caller, err := api.bc.StateByAddr(in.CallerAddress)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}

	sc, _ = action.NewExecution(
		sc.Contract(),
		caller.Nonce+1,
		sc.Amount(),
		api.cfg.Genesis.BlockGasLimit,
		big.NewInt(0),
		sc.Data(),
	)

	callerAddr, err := address.FromString(in.CallerAddress)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}

	retval, receipt, err := api.bc.SimulateExecution(callerAddr, sc)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	return &iotexapi.ReadContractResponse{
		Data:    hex.EncodeToString(retval),
		Receipt: receipt.ConvertToReceiptPb(),
	}, nil
}

// ReadState reads state on blockchain
func (api *Server) ReadState(ctx context.Context, in *iotexapi.ReadStateRequest) (*iotexapi.ReadStateResponse, error) {
	res, err := api.readState(ctx, in)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	return res, nil
}

// SuggestGasPrice suggests gas price
func (api *Server) SuggestGasPrice(ctx context.Context, in *iotexapi.SuggestGasPriceRequest) (*iotexapi.SuggestGasPriceResponse, error) {
	suggestPrice, err := api.gs.SuggestGasPrice()
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	return &iotexapi.SuggestGasPriceResponse{GasPrice: suggestPrice}, nil
}

// EstimateGasForAction estimates gas for action
func (api *Server) EstimateGasForAction(ctx context.Context, in *iotexapi.EstimateGasForActionRequest) (*iotexapi.EstimateGasForActionResponse, error) {
	estimateGas, err := api.gs.EstimateGasForAction(in.Action)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	return &iotexapi.EstimateGasForActionResponse{Gas: estimateGas}, nil
}

// EstimateActionGasConsumption estimate gas consume for execution and transfer
func (api *Server) EstimateActionGasConsumption(ctx context.Context, in *iotexapi.EstimateActionGasConsumptionRequest) (*iotexapi.EstimateActionGasConsumptionResponse, error) {
	switch {
	case in.GetExecution() != nil:
		request := in.GetExecution()
		return api.estimateActionGasConsumptionForExecution(request, in.GetCallerAddress())
	case in.GetTransfer() != nil:
		request := in.GetTransfer()
		return api.estimateActionGasConsumptionForTransfer(request)
	}
	return nil, status.Error(codes.InvalidArgument, "invalid argument")
}

// GetEpochMeta gets epoch metadata
func (api *Server) GetEpochMeta(
	ctx context.Context,
	in *iotexapi.GetEpochMetaRequest,
) (*iotexapi.GetEpochMetaResponse, error) {
	if in.EpochNumber < 1 {
		return nil, status.Error(codes.InvalidArgument, "epoch number cannot be less than one")
	}
	p, ok := api.registry.Find(rolldpos.ProtocolID)
	if !ok {
		return nil, status.Error(codes.Internal, "rolldpos protocol is not registered")
	}
	rp, ok := p.(*rolldpos.Protocol)
	if !ok {
		return nil, status.Error(codes.Internal, "fail to cast rolldpos protocol")
	}
	epochHeight := rp.GetEpochHeight(in.EpochNumber)
	gravityChainStartHeight, err := api.getGravityChainStartHeight(epochHeight)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	epochData := &iotextypes.EpochData{
		Num:                     in.EpochNumber,
		Height:                  epochHeight,
		GravityChainStartHeight: gravityChainStartHeight,
	}

	numBlks, produce, err := api.bc.ProductivityByEpoch(in.EpochNumber)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}

	readStateRequest := &iotexapi.ReadStateRequest{
		ProtocolID: []byte(poll.ProtocolID),
		MethodName: []byte("BlockProducersByEpoch"),
		Arguments:  [][]byte{byteutil.Uint64ToBytes(in.EpochNumber)},
	}
	res, err := api.readState(context.Background(), readStateRequest)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	var BlockProducers state.CandidateList
	if err := BlockProducers.Deserialize(res.Data); err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	var blockProducersInfo []*iotexapi.BlockProducerInfo
	for _, bp := range BlockProducers {
		var active bool
		var blockProduction uint64
		if production, ok := produce[bp.Address]; ok {
			active = true
			blockProduction = production
		}
		blockProducersInfo = append(blockProducersInfo, &iotexapi.BlockProducerInfo{
			Address:    bp.Address,
			Votes:      bp.Votes.String(),
			Active:     active,
			Production: blockProduction,
		})
	}

	return &iotexapi.GetEpochMetaResponse{
		EpochData:          epochData,
		TotalBlocks:        numBlks,
		BlockProducersInfo: blockProducersInfo,
	}, nil
}

// GetRawBlocks gets raw block data
func (api *Server) GetRawBlocks(
	ctx context.Context,
	in *iotexapi.GetRawBlocksRequest,
) (*iotexapi.GetRawBlocksResponse, error) {
	if in.Count == 0 || in.Count > api.cfg.API.RangeQueryLimit {
		return nil, status.Error(codes.InvalidArgument, "range exceeds the limit")
	}

	tipHeight := api.bc.TipHeight()
	if in.StartHeight > tipHeight {
		return nil, status.Error(codes.InvalidArgument, "start height should not exceed tip height")
	}
	var res []*iotexapi.BlockInfo
	for height := int(in.StartHeight); height <= int(tipHeight); height++ {
		if uint64(len(res)) >= in.Count {
			break
		}
		blk, err := api.dao.GetBlockByHeight(uint64(height))
		if err != nil {
			return nil, status.Error(codes.NotFound, err.Error())
		}
		var receiptsPb []*iotextypes.Receipt
		if in.WithReceipts {
			receipts, err := api.dao.GetReceipts(uint64(height))
			if err != nil {
				return nil, status.Error(codes.NotFound, err.Error())
			}
			for _, receipt := range receipts {
				receiptsPb = append(receiptsPb, receipt.ConvertToReceiptPb())
			}
		}
		res = append(res, &iotexapi.BlockInfo{
			Block:    blk.ConvertToBlockPb(),
			Receipts: receiptsPb,
		})
	}

	return &iotexapi.GetRawBlocksResponse{Blocks: res}, nil
}

// GetLogs get logs filtered by contract address and topics
func (api *Server) GetLogs(
	ctx context.Context,
	in *iotexapi.GetLogsRequest,
) (*iotexapi.GetLogsResponse, error) {
	switch {
	case in.GetByBlock() != nil:
		req := in.GetByBlock()
		h, err := api.dao.GetBlockHeight(hash.BytesToHash256(req.BlockHash))
		if err != nil {
			return nil, status.Error(codes.InvalidArgument, "invalid block hash")
		}
		filter, ok := NewLogFilter(in.Filter, nil, nil).(*LogFilter)
		if !ok {
			return nil, status.Error(codes.Internal, "cannot convert to *LogFilter")
		}
		logs, err := api.getLogsInBlock(filter, h, 1)
		return &iotexapi.GetLogsResponse{Logs: logs}, err
	case in.GetByRange() != nil:
		req := in.GetByRange()
		if req.FromBlock > api.bc.TipHeight() {
			return nil, status.Error(codes.InvalidArgument, "start block > tip height")
		}
		filter, ok := NewLogFilter(in.Filter, nil, nil).(*LogFilter)
		if !ok {
			return nil, status.Error(codes.Internal, "cannot convert to *LogFilter")
		}
		logs, err := api.getLogsInBlock(filter, req.FromBlock, req.Count)
		return &iotexapi.GetLogsResponse{Logs: logs}, err
	default:
		return nil, status.Error(codes.InvalidArgument, "invalid GetLogsRequest type")
	}
}

// StreamBlocks streams blocks
func (api *Server) StreamBlocks(in *iotexapi.StreamBlocksRequest, stream iotexapi.APIService_StreamBlocksServer) error {
	errChan := make(chan error)
	if err := api.chainListener.AddResponder(NewBlockListener(stream, errChan)); err != nil {
		return status.Error(codes.Internal, err.Error())
	}

	for {
		select {
		case err := <-errChan:
			if err != nil {
				err = status.Error(codes.Aborted, err.Error())
			}
			return err
		}
	}
}

// StreamLogs streams logs that match the filter condition
func (api *Server) StreamLogs(in *iotexapi.StreamLogsRequest, stream iotexapi.APIService_StreamLogsServer) error {
	errChan := make(chan error)
	// register the log filter so it will match logs in new blocks
	if err := api.chainListener.AddResponder(NewLogFilter(in.Filter, stream, errChan)); err != nil {
		return status.Error(codes.Internal, err.Error())
	}

	for {
		select {
		case err := <-errChan:
			if err != nil {
				err = status.Error(codes.Aborted, err.Error())
			}
			return err
		}
	}
}

// GetVotes gets votes for req
func (api *Server) GetVotes(
	ctx context.Context,
	in *iotexapi.GetVotesRequest,
) (*iotexapi.GetVotesResponse, error) {
	// This is a very weird API for iotex-core, which tries to fetch election result by ethereum height.
	// Moreover, it relies on election committee but not the poll protocol.
	// Before we are able to provide an API to query votes by iotex chain height, deleting this implementation as if it has been deprecated.
	return nil, nil
}

// GetElectionBuckets returns the native election buckets.
func (api *Server) GetElectionBuckets(
	ctx context.Context,
	in *iotexapi.GetElectionBucketsRequest,
) (*iotexapi.GetElectionBucketsResponse, error) {
	if api.electionCommittee == nil {
		return nil, status.Error(codes.Unavailable, "Native election no supported")
	}
	buckets, err := api.electionCommittee.NativeBucketsByEpoch(in.GetEpochNum())
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	re := make([]*iotextypes.ElectionBucket, len(buckets))
	for i, b := range buckets {
		startTime, err := ptypes.TimestampProto(b.StartTime())
		if err != nil {
			return nil, status.Error(codes.Internal, err.Error())
		}
		re[i] = &iotextypes.ElectionBucket{
			Voter:     b.Voter(),
			Candidate: b.Candidate(),
			Amount:    b.Amount().Bytes(),
			StartTime: startTime,
			Duration:  ptypes.DurationProto(b.Duration()),
			Decay:     b.Decay(),
		}
	}
	return &iotexapi.GetElectionBucketsResponse{Buckets: re}, nil
}

// GetReceiptByActionHash returns receipt by action hash
func (api *Server) GetReceiptByActionHash(h hash.Hash256) (*action.Receipt, error) {
	if !api.hasActionIndex || api.indexer == nil {
		return nil, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
	}

	actIndex, err := api.indexer.GetActionIndex(h[:])
	if err != nil {
		return nil, err
	}
	return api.dao.GetReceiptByActionHash(h, actIndex.BlockHeight())
}

// GetActionByActionHash returns action by action hash
func (api *Server) GetActionByActionHash(h hash.Hash256) (action.SealedEnvelope, error) {
	if !api.hasActionIndex || api.indexer == nil {
		return action.SealedEnvelope{}, status.Error(codes.NotFound, blockindex.ErrActionIndexNA.Error())
	}

	selp, _, _, err := api.getActionByActionHash(h)
	return selp, err
}

// Start starts the API server
func (api *Server) Start() error {
	portStr := ":" + strconv.Itoa(api.cfg.API.Port)
	lis, err := net.Listen("tcp", portStr)
	if err != nil {
		log.L().Error("API server failed to listen.", zap.Error(err))
		return errors.Wrap(err, "API server failed to listen")
	}
	log.L().Info("API server is listening.", zap.String("addr", lis.Addr().String()))

	go func() {
		if err := api.grpcserver.Serve(lis); err != nil {
			log.L().Fatal("Node failed to serve.", zap.Error(err))
		}
	}()
	if err := api.bc.AddSubscriber(api.chainListener); err != nil {
		return errors.Wrap(err, "failed to subscribe to block creations")
	}
	if err := api.chainListener.Start(); err != nil {
		return errors.Wrap(err, "failed to start blockchain listener")
	}
	return nil
}

// Stop stops the API server
func (api *Server) Stop() error {
	api.grpcserver.Stop()
	if err := api.bc.RemoveSubscriber(api.chainListener); err != nil {
		return errors.Wrap(err, "failed to unsubscribe blockchain listener")
	}
	return api.chainListener.Stop()
}

func (api *Server) readState(ctx context.Context, in *iotexapi.ReadStateRequest) (*iotexapi.ReadStateResponse, error) {
	p, ok := api.registry.Find(string(in.ProtocolID))
	if !ok {
		return nil, status.Errorf(codes.Internal, "protocol %s isn't registered", string(in.ProtocolID))
	}
	// TODO: need to complete the context
	ctx = protocol.WithRunActionsCtx(ctx, protocol.RunActionsCtx{
		BlockHeight: api.bc.TipHeight(),
		Registry:    api.registry,
	})
	ws, err := api.bc.GetFactory().NewWorkingSet()
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	data, err := p.ReadState(ctx, ws, in.MethodName, in.Arguments...)
	// TODO: need to distinguish user error and system error
	if err != nil {
		return nil, err
	}
	out := iotexapi.ReadStateResponse{
		Data: data,
	}
	return &out, nil
}

func (api *Server) getActionsFromIndex(totalActions, start, count uint64) (*iotexapi.GetActionsResponse, error) {
	var actionInfo []*iotexapi.ActionInfo
	hashes, err := api.indexer.GetActionHashFromIndex(start, count)
	if err != nil {
		return nil, status.Error(codes.Unavailable, err.Error())
	}
	for i := range hashes {
		act, err := api.getAction(hash.BytesToHash256(hashes[i]), false)
		if err != nil {
			return nil, status.Error(codes.Unavailable, err.Error())
		}
		actionInfo = append(actionInfo, act)
	}
	return &iotexapi.GetActionsResponse{
		Total:      totalActions,
		ActionInfo: actionInfo,
	}, nil
}

// GetActions returns actions within the range
func (api *Server) getActions(start uint64, count uint64) (*iotexapi.GetActionsResponse, error) {
	if count == 0 {
		return nil, status.Error(codes.InvalidArgument, "count must be greater than zero")
	}
	if count > api.cfg.API.RangeQueryLimit {
		return nil, status.Error(codes.InvalidArgument, "range exceeds the limit")
	}

	totalActions, err := api.indexer.GetTotalActions()
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	if start >= totalActions {
		return nil, status.Error(codes.InvalidArgument, "start exceeds the limit")
	}
	if totalActions == uint64(0) || count == 0 {
		return &iotexapi.GetActionsResponse{}, nil
	}
	if start+count > totalActions {
		count = totalActions - start
	}
	if api.hasActionIndex {
		return api.getActionsFromIndex(totalActions, start, count)
	}
	// Finding actions in reverse order saves time for querying most recent actions
	reverseStart := totalActions - (start + count)
	if totalActions < start+count {
		reverseStart = uint64(0)
		count = totalActions - start
	}

	var res []*iotexapi.ActionInfo
	var hit bool
	for height := api.bc.TipHeight(); height >= 1 && count > 0; height-- {
		blk, err := api.dao.GetBlockByHeight(height)
		if err != nil {
			return nil, status.Error(codes.NotFound, err.Error())
		}
		if !hit && reverseStart >= uint64(len(blk.Actions)) {
			reverseStart -= uint64(len(blk.Actions))
			continue
		}
		// now reverseStart < len(blk.Actions), we are going to fetch actions from this block
		hit = true
		act := api.reverseActionsInBlock(blk, reverseStart, count)
		res = append(act, res...)
		count -= uint64(len(act))
		reverseStart = 0
	}
	return &iotexapi.GetActionsResponse{
		Total:      totalActions,
		ActionInfo: res,
	}, nil
}

// getSingleAction returns action by action hash
func (api *Server) getSingleAction(actionHash string, checkPending bool) (*iotexapi.GetActionsResponse, error) {
	actHash, err := hash.HexStringToHash256(actionHash)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	act, err := api.getAction(actHash, checkPending)
	if err != nil {
		return nil, status.Error(codes.Unavailable, err.Error())
	}
	return &iotexapi.GetActionsResponse{
		Total:      1,
		ActionInfo: []*iotexapi.ActionInfo{act},
	}, nil
}

// getActionsByAddress returns all actions associated with an address
func (api *Server) getActionsByAddress(addrStr string, start uint64, count uint64) (*iotexapi.GetActionsResponse, error) {
	if count == 0 {
		return nil, status.Error(codes.InvalidArgument, "count must be greater than zero")
	}
	if count > api.cfg.API.RangeQueryLimit {
		return nil, status.Error(codes.InvalidArgument, "range exceeds the limit")
	}

	addr, err := address.FromString(addrStr)
	if err != nil {
		return nil, err
	}
	actions, err := api.indexer.GetActionsByAddress(hash.BytesToHash160(addr.Bytes()), start, count)
	if err != nil && errors.Cause(err) == db.ErrBucketNotExist {
		// no actions associated with address, return nil
		return &iotexapi.GetActionsResponse{}, nil
	}
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}

	res := &iotexapi.GetActionsResponse{Total: uint64(len(actions))}
	for i := range actions {
		act, err := api.getAction(hash.BytesToHash256(actions[i]), false)
		if err != nil {
			continue
		}
		res.ActionInfo = append(res.ActionInfo, act)
	}
	return res, nil
}

// getBlockHashByActionHash returns block hash by action hash
func (api *Server) getBlockHashByActionHash(h hash.Hash256) (hash.Hash256, error) {
	actIndex, err := api.indexer.GetActionIndex(h[:])
	if err != nil {
		return hash.ZeroHash256, err
	}
	return api.dao.GetBlockHash(actIndex.BlockHeight())
}

// getActionByActionHash returns action by action hash
func (api *Server) getActionByActionHash(h hash.Hash256) (action.SealedEnvelope, hash.Hash256, uint64, error) {
	actIndex, err := api.indexer.GetActionIndex(h[:])
	if err != nil {
		return action.SealedEnvelope{}, hash.ZeroHash256, 0, err
	}

	blk, err := api.dao.GetBlockByHeight(actIndex.BlockHeight())
	if err != nil {
		return action.SealedEnvelope{}, hash.ZeroHash256, 0, err
	}

	selp, err := api.dao.GetActionByActionHash(h, actIndex.BlockHeight())
	return selp, blk.HashBlock(), actIndex.BlockHeight(), err
}

// getUnconfirmedActionsByAddress returns all unconfirmed actions in actpool associated with an address
func (api *Server) getUnconfirmedActionsByAddress(address string, start uint64, count uint64) (*iotexapi.GetActionsResponse, error) {
	if count == 0 {
		return nil, status.Error(codes.InvalidArgument, "count must be greater than zero")
	}
	if count > api.cfg.API.RangeQueryLimit {
		return nil, status.Error(codes.InvalidArgument, "range exceeds the limit")
	}

	selps := api.ap.GetUnconfirmedActs(address)
	if len(selps) == 0 {
		return &iotexapi.GetActionsResponse{}, nil
	}
	if start >= uint64(len(selps)) {
		return nil, status.Error(codes.InvalidArgument, "start exceeds the limit")
	}

	var res []*iotexapi.ActionInfo
	for i := start; i < uint64(len(selps)) && i < start+count; i++ {
		act, err := api.pendingAction(selps[i])
		if err != nil {
			continue
		}
		res = append(res, act)
	}
	return &iotexapi.GetActionsResponse{
		Total:      uint64(len(selps)),
		ActionInfo: res,
	}, nil
}

// getActionsByBlock returns all actions in a block
func (api *Server) getActionsByBlock(blkHash string, start uint64, count uint64) (*iotexapi.GetActionsResponse, error) {
	if count == 0 {
		return nil, status.Error(codes.InvalidArgument, "count must be greater than zero")
	}
	if count > api.cfg.API.RangeQueryLimit {
		return nil, status.Error(codes.InvalidArgument, "range exceeds the limit")
	}

	hash, err := hash.HexStringToHash256(blkHash)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	blk, err := api.dao.GetBlock(hash)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	if len(blk.Actions) == 0 {
		return &iotexapi.GetActionsResponse{}, nil
	}
	if start >= uint64(len(blk.Actions)) {
		return nil, status.Error(codes.InvalidArgument, "start exceeds the limit")
	}

	res := api.actionsInBlock(blk, start, count)
	return &iotexapi.GetActionsResponse{
		Total:      uint64(len(blk.Actions)),
		ActionInfo: res,
	}, nil
}

// getBlockMetas returns blockmetas response within the height range
func (api *Server) getBlockMetas(start uint64, count uint64) (*iotexapi.GetBlockMetasResponse, error) {
	if count == 0 {
		return nil, status.Error(codes.InvalidArgument, "count must be greater than zero")
	}
	if count > api.cfg.API.RangeQueryLimit {
		return nil, status.Error(codes.InvalidArgument, "range exceeds the limit")
	}

	tipHeight := api.bc.TipHeight()
	if start > tipHeight {
		return nil, status.Error(codes.InvalidArgument, "start height should not exceed tip height")
	}
	var res []*iotextypes.BlockMeta
	for height := start; height <= tipHeight && count > 0; height++ {
		blockMeta, err := api.getBlockMetasByHeader(height)
		if errors.Cause(err) == db.ErrNotExist {
			blockMeta, err = api.getBlockMetasByBlock(height)
			if err != nil {
				return nil, err
			}
		} else if err != nil {
			return nil, err
		}
		res = append(res, blockMeta)
		count--
	}
	return &iotexapi.GetBlockMetasResponse{
		Total:    tipHeight,
		BlkMetas: res,
	}, nil
}

// getBlockMeta returns blockmetas response by block hash
func (api *Server) getBlockMeta(blkHash string) (*iotexapi.GetBlockMetasResponse, error) {
	hash, err := hash.HexStringToHash256(blkHash)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	blockMeta, err := api.getBlockMetaByHeader(hash)
	if errors.Cause(err) == db.ErrNotExist {
		blockMeta, err = api.getBlockMetaByBlock(hash)
		if err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	}
	return &iotexapi.GetBlockMetasResponse{
		Total:    1,
		BlkMetas: []*iotextypes.BlockMeta{blockMeta},
	}, nil
}

// putBlockMetaUpgradeByBlock puts numActions and transferAmount for blockmeta by block
func (api *Server) putBlockMetaUpgradeByBlock(blk *block.Block, blockMeta *iotextypes.BlockMeta) *iotextypes.BlockMeta {
	blockMeta.NumActions = int64(len(blk.Actions))
	blockMeta.TransferAmount = blk.CalculateTransferAmount().String()
	return blockMeta
}

// putBlockMetaUpgradeByHeader puts numActions and transferAmount for blockmeta by header height
func (api *Server) putBlockMetaUpgradeByHeader(height uint64, blockMeta *iotextypes.BlockMeta) (*iotextypes.BlockMeta, error) {
	index, err := api.indexer.GetBlockIndex(height)
	if err != nil {
		return nil, errors.Wrapf(err, "missing block index at height %d", height)
	}
	blockMeta.NumActions = int64(index.NumAction())
	blockMeta.TransferAmount = index.TsfAmount().String()
	return blockMeta, nil
}

// getBlockMetasByHeader gets block header by height
func (api *Server) getBlockMetasByHeader(height uint64) (*iotextypes.BlockMeta, error) {
	header, err := api.bc.BlockHeaderByHeight(height)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	blockMeta := api.getCommonBlockMeta(header)
	blockMeta, err = api.putBlockMetaUpgradeByHeader(header.Height(), blockMeta)
	if err != nil {
		return nil, err
	}
	return blockMeta, nil
}

// getBlockMetasByBlock gets block by height
func (api *Server) getBlockMetasByBlock(height uint64) (*iotextypes.BlockMeta, error) {
	blk, err := api.dao.GetBlockByHeight(height)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	blockMeta := api.getCommonBlockMeta(blk)
	blockMeta = api.putBlockMetaUpgradeByBlock(blk, blockMeta)
	return blockMeta, nil
}

// getBlockMetaByHeader gets block header by hash
func (api *Server) getBlockMetaByHeader(h hash.Hash256) (*iotextypes.BlockMeta, error) {
	header, err := api.bc.BlockHeaderByHash(h)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	blockMeta := api.getCommonBlockMeta(header)
	blockMeta, err = api.putBlockMetaUpgradeByHeader(header.Height(), blockMeta)
	if err != nil {
		return nil, err
	}
	return blockMeta, nil
}

// getBlockMetaByBlock gets block by hash
func (api *Server) getBlockMetaByBlock(h hash.Hash256) (*iotextypes.BlockMeta, error) {
	blk, err := api.dao.GetBlock(h)
	if err != nil {
		return nil, status.Error(codes.NotFound, err.Error())
	}
	blockMeta := api.getCommonBlockMeta(blk)
	blockMeta = api.putBlockMetaUpgradeByBlock(blk, blockMeta)
	return blockMeta, nil
}

// getCommonBlockMeta gets blockmeta by empty interface
func (api *Server) getCommonBlockMeta(common interface{}) *iotextypes.BlockMeta {
	header, ok := common.(*block.Header)
	if !ok {
		blk := common.(*block.Block)
		header = &blk.Header
	}
	hash := header.HashBlock()
	height := header.Height()
	ts, _ := ptypes.TimestampProto(header.Timestamp())
	producerAddress := header.ProducerAddress()
	txRoot := header.TxRoot()
	receiptRoot := header.ReceiptRoot()
	deltaStateDigest := header.DeltaStateDigest()
	logsBloom := header.LogsBloomfilter()

	blockMeta := &iotextypes.BlockMeta{
		Hash:             hex.EncodeToString(hash[:]),
		Height:           height,
		Timestamp:        ts,
		ProducerAddress:  producerAddress,
		TxRoot:           hex.EncodeToString(txRoot[:]),
		ReceiptRoot:      hex.EncodeToString(receiptRoot[:]),
		DeltaStateDigest: hex.EncodeToString(deltaStateDigest[:]),
	}
	if logsBloom != nil {
		blockMeta.LogsBloom = hex.EncodeToString(logsBloom.Bytes())
	}
	return blockMeta
}

func (api *Server) getGravityChainStartHeight(epochHeight uint64) (uint64, error) {
	gravityChainStartHeight := epochHeight
	if _, ok := api.registry.Find(poll.ProtocolID); ok {
		readStateRequest := &iotexapi.ReadStateRequest{
			ProtocolID: []byte(poll.ProtocolID),
			MethodName: []byte("GetGravityChainStartHeight"),
			Arguments:  [][]byte{byteutil.Uint64ToBytes(epochHeight)},
		}
		res, err := api.readState(context.Background(), readStateRequest)
		if err != nil {
			return 0, err
		}
		gravityChainStartHeight = byteutil.BytesToUint64(res.GetData())
	}
	return gravityChainStartHeight, nil
}

func (api *Server) committedAction(selp action.SealedEnvelope, blkHash hash.Hash256, blkHeight uint64) (
	*iotexapi.ActionInfo, error) {
	actHash := selp.Hash()
	header, err := api.bc.BlockHeaderByHash(blkHash)
	if err != nil {
		return nil, err
	}
	sender, _ := address.FromBytes(selp.SrcPubkey().Hash())
	receipt, err := api.dao.GetReceiptByActionHash(actHash, blkHeight)
	if err != nil {
		return nil, err
	}
	gas := new(big.Int)
	gas = gas.Mul(selp.GasPrice(), big.NewInt(int64(receipt.GasConsumed)))
	return &iotexapi.ActionInfo{
		Action:    selp.Proto(),
		ActHash:   hex.EncodeToString(actHash[:]),
		BlkHash:   hex.EncodeToString(blkHash[:]),
		BlkHeight: header.Height(),
		Sender:    sender.String(),
		GasFee:    gas.String(),
		Timestamp: header.BlockHeaderCoreProto().Timestamp,
	}, nil
}

func (api *Server) pendingAction(selp action.SealedEnvelope) (*iotexapi.ActionInfo, error) {
	actHash := selp.Hash()
	sender, _ := address.FromBytes(selp.SrcPubkey().Hash())
	return &iotexapi.ActionInfo{
		Action:    selp.Proto(),
		ActHash:   hex.EncodeToString(actHash[:]),
		BlkHash:   hex.EncodeToString(hash.ZeroHash256[:]),
		BlkHeight: 0,
		Sender:    sender.String(),
		Timestamp: nil,
	}, nil
}

func (api *Server) getAction(actHash hash.Hash256, checkPending bool) (*iotexapi.ActionInfo, error) {
	selp, blkHash, blkHeight, err := api.getActionByActionHash(actHash)
	if err == nil {
		return api.committedAction(selp, blkHash, blkHeight)
	}
	// Try to fetch pending action from actpool
	if checkPending {
		selp, err = api.ap.GetActionByHash(actHash)
	}
	if err != nil {
		return nil, err
	}
	return api.pendingAction(selp)
}

func (api *Server) actionsInBlock(blk *block.Block, start, count uint64) []*iotexapi.ActionInfo {
	h := blk.HashBlock()
	blkHash := hex.EncodeToString(h[:])
	blkHeight := blk.Height()
	ts := blk.Header.BlockHeaderCoreProto().Timestamp

	var res []*iotexapi.ActionInfo
	for i := start; i < uint64(len(blk.Actions)) && i < start+count; i++ {
		selp := blk.Actions[i]
		actHash := selp.Hash()
		sender, _ := address.FromBytes(selp.SrcPubkey().Hash())
		res = append(res, &iotexapi.ActionInfo{
			Action:    selp.Proto(),
			ActHash:   hex.EncodeToString(actHash[:]),
			BlkHash:   blkHash,
			BlkHeight: blkHeight,
			Sender:    sender.String(),
			Timestamp: ts,
		})
	}
	return res
}

func (api *Server) reverseActionsInBlock(blk *block.Block, reverseStart, count uint64) []*iotexapi.ActionInfo {
	h := blk.HashBlock()
	blkHash := hex.EncodeToString(h[:])
	blkHeight := blk.Height()
	ts := blk.Header.BlockHeaderCoreProto().Timestamp

	var res []*iotexapi.ActionInfo
	for i := reverseStart; i < uint64(len(blk.Actions)) && i < reverseStart+count; i++ {
		ri := uint64(len(blk.Actions)) - 1 - i
		selp := blk.Actions[ri]
		actHash := selp.Hash()
		sender, _ := address.FromBytes(selp.SrcPubkey().Hash())
		res = append([]*iotexapi.ActionInfo{
			{
				Action:    selp.Proto(),
				ActHash:   hex.EncodeToString(actHash[:]),
				BlkHash:   blkHash,
				BlkHeight: blkHeight,
				Sender:    sender.String(),
				Timestamp: ts,
			},
		}, res...)
	}
	return res
}

func (api *Server) getLogsInBlock(filter *LogFilter, start, count uint64) ([]*iotextypes.Log, error) {
	if count == 0 {
		return nil, status.Error(codes.InvalidArgument, "count must be greater than zero")
	}
	// filter logs within start --> end
	var logs []*iotextypes.Log
	end := start + count - 1
	if end > api.bc.TipHeight() {
		end = api.bc.TipHeight()
	}
	for i := start; i <= end; i++ {
		receipts, err := api.dao.GetReceipts(i)
		if err != nil {
			return logs, status.Error(codes.InvalidArgument, err.Error())
		}
		logs = append(logs, filter.MatchLogs(receipts)...)
	}
	return logs, nil
}

func (api *Server) estimateActionGasConsumptionForExecution(exec *iotextypes.Execution, sender string) (*iotexapi.EstimateActionGasConsumptionResponse, error) {
	sc := &action.Execution{}
	if err := sc.LoadProto(exec); err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	callerAddr, err := address.FromString(sender)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	caller, err := api.bc.StateByAddr(sender)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}
	sc, _ = action.NewExecution(
		sc.Contract(),
		caller.Nonce+1,
		sc.Amount(),
		api.cfg.Genesis.BlockGasLimit,
		big.NewInt(0),
		sc.Data(),
	)

	_, receipt, err := api.bc.SimulateExecution(callerAddr, sc)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	if receipt.Status != uint64(iotextypes.ReceiptStatus_Success) {
		return nil, status.Error(codes.Internal, "execution simulation gets failure status")
	}
	return &iotexapi.EstimateActionGasConsumptionResponse{
		Gas: receipt.GasConsumed,
	}, nil
}

func (api *Server) estimateActionGasConsumptionForTransfer(transfer *iotextypes.Transfer) (*iotexapi.EstimateActionGasConsumptionResponse, error) {
	payloadSize := uint64(len(transfer.Payload))
	return &iotexapi.EstimateActionGasConsumptionResponse{
		Gas: payloadSize*action.TransferPayloadGas + action.TransferBaseIntrinsicGas,
	}, nil
}

importShadow: shadow of imported from 'github.com/iotexproject/iotex-core/state' package 'state' (from `gocritic`)