github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/rpc/namespaces/eth/api_multi.go (about)

     1  package eth
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/big"
     7  
     8  	"github.com/fibonacci-chain/fbc/app/rpc/monitor"
     9  	rpctypes "github.com/fibonacci-chain/fbc/app/rpc/types"
    10  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    11  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    12  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth"
    13  	authexported "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth/exported"
    14  	ctypes "github.com/fibonacci-chain/fbc/libs/tendermint/rpc/core/types"
    15  	evmtypes "github.com/fibonacci-chain/fbc/x/evm/types"
    16  	"github.com/fibonacci-chain/fbc/x/evm/watcher"
    17  	"github.com/fibonacci-chain/fbc/x/token"
    18  
    19  	"github.com/ethereum/go-ethereum/common"
    20  	"github.com/ethereum/go-ethereum/common/hexutil"
    21  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    22  	"github.com/spf13/viper"
    23  )
    24  
    25  const (
    26  	FlagEnableMultiCall = "rpc.enable-multi-call"
    27  )
    28  
    29  // GetBalanceBatch returns the provided account's balance up to the provided block number.
    30  func (api *PublicEthereumAPI) GetBalanceBatch(addresses []string, blockNrOrHash rpctypes.BlockNumberOrHash) (interface{}, error) {
    31  	if !viper.GetBool(FlagEnableMultiCall) {
    32  		return nil, errors.New("the method is not allowed")
    33  	}
    34  
    35  	monitor := monitor.GetMonitor("eth_getBalanceBatch", api.logger, api.Metrics).OnBegin()
    36  	defer monitor.OnEnd("addresses", addresses, "block number", blockNrOrHash)
    37  
    38  	blockNum, err := api.backend.ConvertToBlockNumber(blockNrOrHash)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	clientCtx := api.clientCtx
    43  
    44  	useWatchBackend := api.useWatchBackend(blockNum)
    45  	if !(blockNum == rpctypes.PendingBlockNumber || blockNum == rpctypes.LatestBlockNumber) && !useWatchBackend {
    46  		clientCtx = api.clientCtx.WithHeight(blockNum.Int64())
    47  	}
    48  
    49  	type accBalance struct {
    50  		Type    token.AccType `json:"type"`
    51  		Balance *hexutil.Big  `json:"balance"`
    52  	}
    53  	balances := make(map[string]accBalance)
    54  	for _, addr := range addresses {
    55  		address, err := sdk.AccAddressFromBech32(addr)
    56  		if err != nil {
    57  			return nil, fmt.Errorf("addr:%s,err:%s", addr, err)
    58  		}
    59  		if acc, err := api.wrappedBackend.MustGetAccount(address); err == nil {
    60  			balance := acc.GetCoins().AmountOf(sdk.DefaultBondDenom).BigInt()
    61  			if balance == nil {
    62  				balances[addr] = accBalance{accountType(acc), (*hexutil.Big)(sdk.ZeroInt().BigInt())}
    63  			} else {
    64  				balances[addr] = accBalance{accountType(acc), (*hexutil.Big)(balance)}
    65  			}
    66  			continue
    67  		}
    68  
    69  		bs, err := api.clientCtx.Codec.MarshalJSON(auth.NewQueryAccountParams(address))
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  		res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", auth.QuerierRoute, auth.QueryAccount), bs)
    74  		if err != nil {
    75  			continue
    76  		}
    77  
    78  		var account authexported.Account
    79  		if err := api.clientCtx.Codec.UnmarshalJSON(res, &account); err != nil {
    80  			return nil, err
    81  		}
    82  
    83  		val := account.GetCoins().AmountOf(sdk.DefaultBondDenom).BigInt()
    84  		accType := accountType(account)
    85  		if accType == token.UserAccount || accType == token.ContractAccount {
    86  			api.watcherBackend.CommitAccountToRpcDb(account)
    87  			if blockNum != rpctypes.PendingBlockNumber {
    88  				balances[addr] = accBalance{accType, (*hexutil.Big)(val)}
    89  				continue
    90  			}
    91  
    92  			// update the address balance with the pending transactions value (if applicable)
    93  			pendingTxs, err := api.backend.UserPendingTransactions(addr, -1)
    94  			if err != nil {
    95  				return nil, err
    96  			}
    97  
    98  			for _, tx := range pendingTxs {
    99  				if tx == nil {
   100  					continue
   101  				}
   102  
   103  				if tx.From.String() == addr {
   104  					val = new(big.Int).Sub(val, tx.Value.ToInt())
   105  				}
   106  				if tx.To.String() == addr {
   107  					val = new(big.Int).Add(val, tx.Value.ToInt())
   108  				}
   109  			}
   110  		}
   111  		balances[addr] = accBalance{accType, (*hexutil.Big)(val)}
   112  	}
   113  	return balances, nil
   114  }
   115  
   116  // MultiCall performs multiple raw contract call.
   117  func (api *PublicEthereumAPI) MultiCall(args []rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *[]evmtypes.StateOverrides) ([]hexutil.Bytes, error) {
   118  	if !viper.GetBool(FlagEnableMultiCall) {
   119  		return nil, errors.New("the method is not allowed")
   120  	}
   121  
   122  	monitor := monitor.GetMonitor("eth_multiCall", api.logger, api.Metrics).OnBegin()
   123  	defer monitor.OnEnd("args", args, "block number", blockNr)
   124  
   125  	blockNrOrHash := rpctypes.BlockNumberOrHashWithNumber(blockNr)
   126  	rets := make([]hexutil.Bytes, 0, len(args))
   127  	for _, arg := range args {
   128  		ret, err := api.Call(arg, blockNrOrHash, nil)
   129  		if err != nil {
   130  			return rets, err
   131  		}
   132  		rets = append(rets, ret)
   133  	}
   134  	return rets, nil
   135  }
   136  
   137  // GetTransactionsByBlock returns some transactions identified by number or hash.
   138  func (api *PublicEthereumAPI) GetTransactionsByBlock(blockNrOrHash rpctypes.BlockNumberOrHash, offset, limit hexutil.Uint) ([]*watcher.Transaction, error) {
   139  	if !viper.GetBool(FlagEnableMultiCall) {
   140  		return nil, errors.New("the method is not allowed")
   141  	}
   142  
   143  	monitor := monitor.GetMonitor("eth_getTransactionsByBlock", api.logger, api.Metrics).OnBegin()
   144  	defer monitor.OnEnd("block number", blockNrOrHash, "offset", offset, "limit", limit)
   145  
   146  	blockNum, err := api.backend.ConvertToBlockNumber(blockNrOrHash)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	txs, e := api.wrappedBackend.GetTransactionsByBlockNumber(uint64(blockNum), uint64(offset), uint64(limit))
   152  	if e == nil && txs != nil {
   153  		return txs, nil
   154  	}
   155  
   156  	height := blockNum.Int64()
   157  	switch blockNum {
   158  	case rpctypes.PendingBlockNumber:
   159  		// get all the EVM pending txs
   160  		pendingTxs, err := api.backend.PendingTransactions()
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		switch {
   165  		case len(pendingTxs) <= int(offset):
   166  			return nil, nil
   167  		case len(pendingTxs) < int(offset+limit):
   168  			return pendingTxs[offset:], nil
   169  		default:
   170  			return pendingTxs[offset : offset+limit], nil
   171  		}
   172  	case rpctypes.LatestBlockNumber:
   173  		height, err = api.backend.LatestBlockNumber()
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  	}
   178  
   179  	resBlock, err := api.backend.Block(&height)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	for idx := offset; idx < offset+limit && int(idx) < len(resBlock.Block.Txs); idx++ {
   184  		tx, _ := api.getTransactionByBlockAndIndex(resBlock.Block, idx)
   185  		if tx != nil {
   186  			txs = append(txs, tx)
   187  		}
   188  	}
   189  	return txs, nil
   190  }
   191  
   192  // GetTransactionReceiptsByBlock returns the transaction receipt identified by block hash or number.
   193  func (api *PublicEthereumAPI) GetTransactionReceiptsByBlock(blockNrOrHash rpctypes.BlockNumberOrHash, offset, limit hexutil.Uint) ([]*watcher.TransactionReceipt, error) {
   194  	if !viper.GetBool(FlagEnableMultiCall) {
   195  		return nil, errors.New("the method is not allowed")
   196  	}
   197  
   198  	monitor := monitor.GetMonitor("eth_getTransactionReceiptsByBlock", api.logger, api.Metrics).OnBegin()
   199  	defer monitor.OnEnd("block number", blockNrOrHash, "offset", offset, "limit", limit)
   200  
   201  	txs, err := api.GetTransactionsByBlock(blockNrOrHash, offset, limit)
   202  	if err != nil || len(txs) == 0 {
   203  		return nil, err
   204  	}
   205  
   206  	var receipts []*watcher.TransactionReceipt
   207  	var block *ctypes.ResultBlock
   208  	var blockHash common.Hash
   209  	for _, tx := range txs {
   210  		res, _ := api.wrappedBackend.GetTransactionReceipt(tx.Hash)
   211  		if res != nil {
   212  			receipts = append(receipts, res)
   213  			continue
   214  		}
   215  
   216  		tx, err := api.clientCtx.Client.Tx(tx.Hash.Bytes(), false)
   217  		if err != nil {
   218  			// Return nil for transaction when not found
   219  			return nil, nil
   220  		}
   221  
   222  		if block == nil {
   223  			// Query block for consensus hash
   224  			block, err = api.backend.Block(&tx.Height)
   225  			if err != nil {
   226  				return nil, err
   227  			}
   228  			blockHash = common.BytesToHash(block.Block.Hash())
   229  		}
   230  
   231  		// Convert tx bytes to eth transaction
   232  		ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, tx.Tx, tx.Height)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  
   237  		err = ethTx.VerifySig(ethTx.ChainID(), tx.Height)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  
   242  		// Set status codes based on tx result
   243  		var status = hexutil.Uint64(0)
   244  		if tx.TxResult.IsOK() {
   245  			status = hexutil.Uint64(1)
   246  		}
   247  
   248  		txData := tx.TxResult.GetData()
   249  		data, err := evmtypes.DecodeResultData(txData)
   250  		if err != nil {
   251  			status = 0 // transaction failed
   252  		}
   253  
   254  		if len(data.Logs) == 0 || status == 0 {
   255  			data.Logs = []*ethtypes.Log{}
   256  			data.Bloom = ethtypes.BytesToBloom(make([]byte, 256))
   257  		}
   258  		contractAddr := &data.ContractAddress
   259  		if data.ContractAddress == common.HexToAddress("0x00000000000000000000") {
   260  			contractAddr = nil
   261  		}
   262  
   263  		// fix gasUsed when deliverTx ante handler check sequence invalid
   264  		gasUsed := tx.TxResult.GasUsed
   265  		if tx.TxResult.Code == sdkerrors.ErrInvalidSequence.ABCICode() {
   266  			gasUsed = 0
   267  		}
   268  
   269  		receipt := &watcher.TransactionReceipt{
   270  			Status: status,
   271  			//CumulativeGasUsed: hexutil.Uint64(cumulativeGasUsed),
   272  			LogsBloom:        data.Bloom,
   273  			Logs:             data.Logs,
   274  			TransactionHash:  common.BytesToHash(tx.Hash.Bytes()).String(),
   275  			ContractAddress:  contractAddr,
   276  			GasUsed:          hexutil.Uint64(gasUsed),
   277  			BlockHash:        blockHash.String(),
   278  			BlockNumber:      hexutil.Uint64(tx.Height),
   279  			TransactionIndex: hexutil.Uint64(tx.Index),
   280  			From:             ethTx.GetFrom(),
   281  			To:               ethTx.To(),
   282  		}
   283  		receipts = append(receipts, receipt)
   284  	}
   285  
   286  	return receipts, nil
   287  }
   288  
   289  // GetTransactionReceiptsByBlock returns the transaction receipt identified by block hash or number.
   290  func (api *PublicEthereumAPI) GetAllTransactionResultsByBlock(blockNrOrHash rpctypes.BlockNumberOrHash, offset, limit hexutil.Uint) ([]*watcher.TransactionResult, error) {
   291  	if !viper.GetBool(FlagEnableMultiCall) {
   292  		return nil, errors.New("the method is not allowed")
   293  	}
   294  
   295  	monitor := monitor.GetMonitor("eth_getAllTransactionResultsByBlock", api.logger, api.Metrics).OnBegin()
   296  	defer monitor.OnEnd("block number", blockNrOrHash, "offset", offset, "limit", limit)
   297  
   298  	var results []*watcher.TransactionResult
   299  
   300  	blockNum, err := api.backend.ConvertToBlockNumber(blockNrOrHash)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	height := blockNum.Int64()
   306  	if blockNum == rpctypes.LatestBlockNumber {
   307  		height, err = api.backend.LatestBlockNumber()
   308  		if err != nil {
   309  			return nil, err
   310  		}
   311  	}
   312  
   313  	// try to get from watch db
   314  	results, err = api.wrappedBackend.GetTxResultByBlock(api.clientCtx, uint64(height), uint64(offset), uint64(limit))
   315  	if err == nil {
   316  		return results, nil
   317  	}
   318  
   319  	// try to get from node
   320  	resBlock, err := api.backend.Block(&height)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  	blockHash := common.BytesToHash(resBlock.Block.Hash())
   325  	for idx := offset; idx < offset+limit && int(idx) < len(resBlock.Block.Txs); idx++ {
   326  		realTx, err := rpctypes.RawTxToRealTx(api.clientCtx, resBlock.Block.Txs[idx],
   327  			blockHash, uint64(resBlock.Block.Height), uint64(idx))
   328  		if err != nil {
   329  			return nil, err
   330  		}
   331  
   332  		if realTx != nil {
   333  			txHash := resBlock.Block.Txs[idx].Hash(resBlock.Block.Height)
   334  			queryTx, err := api.clientCtx.Client.Tx(txHash, false)
   335  			if err != nil {
   336  				// Return nil for transaction when not found
   337  				return nil, err
   338  			}
   339  
   340  			var res *watcher.TransactionResult
   341  			switch realTx.GetType() {
   342  			case sdk.EvmTxType:
   343  				res, err = rpctypes.RawTxResultToEthReceipt(api.chainIDEpoch, queryTx, realTx, blockHash)
   344  			case sdk.StdTxType:
   345  				res, err = watcher.RawTxResultToStdResponse(api.clientCtx, queryTx, realTx, resBlock.Block.Time)
   346  			}
   347  
   348  			if err != nil {
   349  				// Return nil for transaction when not found
   350  				return nil, err
   351  			}
   352  
   353  			results = append(results, res)
   354  		}
   355  	}
   356  
   357  	return results, nil
   358  }