github.com/iotexproject/iotex-core@v1.14.1-rc1/api/web3server_utils.go (about)

     1  package api
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math/big"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/ethereum/go-ethereum/common"
    13  	"github.com/ethereum/go-ethereum/common/math"
    14  	"github.com/ethereum/go-ethereum/core/types"
    15  	"github.com/ethereum/go-ethereum/eth/tracers/logger"
    16  	"github.com/go-redis/redis/v8"
    17  	"github.com/iotexproject/go-pkgs/cache/ttl"
    18  	"github.com/iotexproject/go-pkgs/hash"
    19  	"github.com/iotexproject/go-pkgs/util"
    20  	"github.com/iotexproject/iotex-address/address"
    21  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    22  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    23  	"github.com/pkg/errors"
    24  	"github.com/tidwall/gjson"
    25  	"go.uber.org/zap"
    26  
    27  	"github.com/iotexproject/iotex-core/action"
    28  	logfilter "github.com/iotexproject/iotex-core/api/logfilter"
    29  	apitypes "github.com/iotexproject/iotex-core/api/types"
    30  	"github.com/iotexproject/iotex-core/blockchain/block"
    31  	"github.com/iotexproject/iotex-core/pkg/log"
    32  	"github.com/iotexproject/iotex-core/pkg/util/addrutil"
    33  )
    34  
    35  func hexStringToNumber(hexStr string) (uint64, error) {
    36  	return strconv.ParseUint(util.Remove0xPrefix(hexStr), 16, 64)
    37  }
    38  
    39  func ethAddrToIoAddr(ethAddr string) (address.Address, error) {
    40  	if ok := common.IsHexAddress(ethAddr); !ok {
    41  		return nil, errors.Wrapf(errUnkownType, "ethAddr: %s", ethAddr)
    42  	}
    43  	return address.FromHex(ethAddr)
    44  }
    45  
    46  func ioAddrToEthAddr(ioAddr string) (string, error) {
    47  	if len(ioAddr) == 0 {
    48  		return "0x0000000000000000000000000000000000000000", nil
    49  	}
    50  	addr, err := addrutil.IoAddrToEvmAddr(ioAddr)
    51  	if err != nil {
    52  		return "", err
    53  	}
    54  	return addr.String(), nil
    55  }
    56  
    57  func uint64ToHex(val uint64) string {
    58  	return "0x" + strconv.FormatUint(val, 16)
    59  }
    60  
    61  func intStrToHex(str string) (string, error) {
    62  	amount, ok := new(big.Int).SetString(str, 10)
    63  	if !ok {
    64  		return "", errors.Wrapf(errUnkownType, "int: %s", str)
    65  	}
    66  	return "0x" + fmt.Sprintf("%x", amount), nil
    67  }
    68  
    69  func (svr *web3Handler) getBlockWithTransactions(blk *block.Block, receipts []*action.Receipt, isDetailed bool) (*getBlockResult, error) {
    70  	if blk == nil || receipts == nil {
    71  		return nil, errInvalidBlock
    72  	}
    73  	transactions := make([]interface{}, 0)
    74  	for i, selp := range blk.Actions {
    75  		if isDetailed {
    76  			tx, err := svr.assembleConfirmedTransaction(blk.HashBlock(), selp, receipts[i])
    77  			if err != nil {
    78  				if errors.Cause(err) != errUnsupportedAction {
    79  					h, _ := selp.Hash()
    80  					log.Logger("api").Error("failed to get info from action", zap.Error(err), zap.String("actHash", hex.EncodeToString(h[:])))
    81  				}
    82  				continue
    83  			}
    84  			transactions = append(transactions, tx)
    85  		} else {
    86  			actHash, err := selp.Hash()
    87  			if err != nil {
    88  				return nil, err
    89  			}
    90  			transactions = append(transactions, "0x"+hex.EncodeToString(actHash[:]))
    91  		}
    92  	}
    93  	return &getBlockResult{
    94  		blk:          blk,
    95  		transactions: transactions,
    96  	}, nil
    97  }
    98  
    99  func (svr *web3Handler) assembleConfirmedTransaction(blkHash hash.Hash256, selp *action.SealedEnvelope, receipt *action.Receipt) (*getTransactionResult, error) {
   100  	// sanity check
   101  	if receipt == nil {
   102  		return nil, errors.New("receipt is empty")
   103  	}
   104  	actHash, err := selp.Hash()
   105  	if err != nil || actHash != receipt.ActionHash {
   106  		return nil, errors.Errorf("the action %s of receipt doesn't match", hex.EncodeToString(actHash[:]))
   107  	}
   108  	return newGetTransactionResult(&blkHash, selp, receipt, svr.coreService.EVMNetworkID())
   109  }
   110  
   111  func (svr *web3Handler) assemblePendingTransaction(selp *action.SealedEnvelope) (*getTransactionResult, error) {
   112  	return newGetTransactionResult(nil, selp, nil, svr.coreService.EVMNetworkID())
   113  }
   114  
   115  func getRecipientAndContractAddrFromAction(selp *action.SealedEnvelope, receipt *action.Receipt) (*string, *string, error) {
   116  	// recipient is empty when contract is created
   117  	if exec, ok := selp.Action().(*action.Execution); ok && len(exec.Contract()) == 0 {
   118  		addr, err := ioAddrToEthAddr(receipt.ContractAddress)
   119  		if err != nil {
   120  			return nil, nil, err
   121  		}
   122  		return nil, &addr, nil
   123  	}
   124  	act, ok := selp.Action().(action.EthCompatibleAction)
   125  	if !ok {
   126  		actHash, _ := selp.Hash()
   127  		return nil, nil, errors.Wrapf(errUnsupportedAction, "actHash: %s", hex.EncodeToString(actHash[:]))
   128  	}
   129  	ethTx, err := act.ToEthTx(0)
   130  	if err != nil {
   131  		return nil, nil, err
   132  	}
   133  	toTmp := ethTx.To().String()
   134  	return &toTmp, nil, nil
   135  }
   136  
   137  func (svr *web3Handler) parseBlockNumber(str string) (uint64, error) {
   138  	switch str {
   139  	case _earliestBlockNumber:
   140  		return 1, nil
   141  	case "", _pendingBlockNumber, _latestBlockNumber:
   142  		return svr.coreService.TipHeight(), nil
   143  	default:
   144  		return hexStringToNumber(str)
   145  	}
   146  }
   147  
   148  func (svr *web3Handler) parseBlockRange(fromStr string, toStr string) (from uint64, to uint64, err error) {
   149  	from, err = svr.parseBlockNumber(fromStr)
   150  	if err != nil {
   151  		return
   152  	}
   153  	to, err = svr.parseBlockNumber(toStr)
   154  	return
   155  }
   156  
   157  func (svr *web3Handler) ethTxToEnvelope(tx *types.Transaction) (action.Envelope, error) {
   158  	to := ""
   159  	if tx.To() != nil {
   160  		ioAddr, _ := address.FromBytes(tx.To().Bytes())
   161  		to = ioAddr.String()
   162  	}
   163  	elpBuilder := (&action.EnvelopeBuilder{}).SetChainID(svr.coreService.ChainID())
   164  	if to == address.StakingProtocolAddr {
   165  		return elpBuilder.BuildStakingAction(tx)
   166  	}
   167  	if to == address.RewardingProtocol {
   168  		return elpBuilder.BuildRewardingAction(tx)
   169  	}
   170  	isContract, err := svr.checkContractAddr(to)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	if isContract {
   175  		return elpBuilder.BuildExecution(tx)
   176  	}
   177  	return elpBuilder.BuildTransfer(tx)
   178  }
   179  
   180  func (svr *web3Handler) checkContractAddr(to string) (bool, error) {
   181  	if to == "" {
   182  		return true, nil
   183  	}
   184  	ioAddr, err := address.FromString(to)
   185  	if err != nil {
   186  		return false, err
   187  	}
   188  	accountMeta, _, err := svr.coreService.Account(ioAddr)
   189  	return accountMeta.IsContract, err
   190  }
   191  
   192  func (svr *web3Handler) getLogsWithFilter(from uint64, to uint64, addrs []string, topics [][]string) ([]*getLogsResult, error) {
   193  	filter, err := newLogFilterFrom(addrs, topics)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	logs, hashes, err := svr.coreService.LogsInRange(filter, from, to, 0)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	ret := make([]*getLogsResult, 0, len(logs))
   202  	for i := range logs {
   203  		ret = append(ret, &getLogsResult{hashes[i], logs[i]})
   204  	}
   205  	return ret, nil
   206  }
   207  
   208  // construct filter topics and addresses
   209  func newLogFilterFrom(addrs []string, topics [][]string) (*logfilter.LogFilter, error) {
   210  	filter := iotexapi.LogsFilter{}
   211  	for _, ethAddr := range addrs {
   212  		ioAddr, err := ethAddrToIoAddr(ethAddr)
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		filter.Address = append(filter.Address, ioAddr.String())
   217  	}
   218  	for _, tp := range topics {
   219  		var topic [][]byte
   220  		for _, str := range tp {
   221  			b, err := hexToBytes(str)
   222  			if err != nil {
   223  				return nil, err
   224  			}
   225  			topic = append(topic, b)
   226  		}
   227  		filter.Topics = append(filter.Topics, &iotexapi.Topics{Topic: topic})
   228  	}
   229  	return logfilter.NewLogFilter(&filter), nil
   230  }
   231  
   232  func byteToHex(b []byte) string {
   233  	return "0x" + hex.EncodeToString(b)
   234  }
   235  
   236  func hexToBytes(str string) ([]byte, error) {
   237  	str = util.Remove0xPrefix(str)
   238  	if len(str)%2 == 1 {
   239  		str = "0" + str
   240  	}
   241  	return hex.DecodeString(str)
   242  }
   243  
   244  func parseLogRequest(in gjson.Result) (*filterObject, error) {
   245  	if !in.Exists() {
   246  		return nil, errInvalidFormat
   247  	}
   248  	var logReq filterObject
   249  	if len(in.Array()) > 0 {
   250  		req := in.Array()[0]
   251  		logReq.FromBlock = req.Get("fromBlock").String()
   252  		logReq.ToBlock = req.Get("toBlock").String()
   253  		for _, addr := range req.Get("address").Array() {
   254  			logReq.Address = append(logReq.Address, addr.String())
   255  		}
   256  		for _, topics := range req.Get("topics").Array() {
   257  			if topics.IsArray() {
   258  				var topicArr []string
   259  				for _, topic := range topics.Array() {
   260  					topicArr = append(topicArr, util.Remove0xPrefix(topic.String()))
   261  				}
   262  				logReq.Topics = append(logReq.Topics, topicArr)
   263  			} else {
   264  				logReq.Topics = append(logReq.Topics, []string{util.Remove0xPrefix(topics.String())})
   265  			}
   266  		}
   267  	}
   268  	return &logReq, nil
   269  }
   270  
   271  func parseCallObject(in *gjson.Result) (address.Address, string, uint64, *big.Int, *big.Int, []byte, error) {
   272  	var (
   273  		from     address.Address
   274  		to       string
   275  		gasLimit uint64
   276  		gasPrice *big.Int = big.NewInt(0)
   277  		value    *big.Int = big.NewInt(0)
   278  		data     []byte
   279  		err      error
   280  	)
   281  	fromStr := in.Get("params.0.from").String()
   282  	if fromStr == "" {
   283  		fromStr = "0x0000000000000000000000000000000000000000"
   284  	}
   285  	if from, err = ethAddrToIoAddr(fromStr); err != nil {
   286  		return nil, "", 0, nil, nil, nil, err
   287  	}
   288  
   289  	toStr := in.Get("params.0.to").String()
   290  	if toStr != "" {
   291  		ioAddr, err := ethAddrToIoAddr(toStr)
   292  		if err != nil {
   293  			return nil, "", 0, nil, nil, nil, err
   294  		}
   295  		to = ioAddr.String()
   296  	}
   297  
   298  	gasStr := in.Get("params.0.gas").String()
   299  	if gasStr != "" {
   300  		if gasLimit, err = hexStringToNumber(gasStr); err != nil {
   301  			return nil, "", 0, nil, nil, nil, err
   302  		}
   303  	}
   304  
   305  	gasPriceStr := in.Get("params.0.gasPrice").String()
   306  	if gasPriceStr != "" {
   307  		var ok bool
   308  		if gasPrice, ok = new(big.Int).SetString(util.Remove0xPrefix(gasPriceStr), 16); !ok {
   309  			return nil, "", 0, nil, nil, nil, errors.Wrapf(errUnkownType, "gasPrice: %s", gasPriceStr)
   310  		}
   311  	}
   312  
   313  	valStr := in.Get("params.0.value").String()
   314  	if valStr != "" {
   315  		var ok bool
   316  		if value, ok = new(big.Int).SetString(util.Remove0xPrefix(valStr), 16); !ok {
   317  			return nil, "", 0, nil, nil, nil, errors.Wrapf(errUnkownType, "value: %s", valStr)
   318  		}
   319  	}
   320  
   321  	input := in.Get("params.0.input")
   322  	if input.Exists() {
   323  		data = common.FromHex(input.String())
   324  	} else {
   325  		data = common.FromHex(in.Get("params.0.data").String())
   326  	}
   327  	return from, to, gasLimit, gasPrice, value, data, nil
   328  }
   329  
   330  func (svr *web3Handler) getLogQueryRange(fromStr, toStr string, logHeight uint64) (from uint64, to uint64, hasNewLogs bool, err error) {
   331  	if from, to, err = svr.parseBlockRange(fromStr, toStr); err != nil {
   332  		return
   333  	}
   334  	switch {
   335  	case logHeight < from:
   336  		hasNewLogs = true
   337  		return
   338  	case logHeight > to:
   339  		hasNewLogs = false
   340  		return
   341  	default:
   342  		from = logHeight
   343  		hasNewLogs = true
   344  		return
   345  	}
   346  }
   347  
   348  func loadFilterFromCache(c apiCache, filterID string) (filterObject, error) {
   349  	dataStr, isFound := c.Get(filterID)
   350  	if !isFound {
   351  		return filterObject{}, errInvalidFilterID
   352  	}
   353  	var filterObj filterObject
   354  	if err := json.Unmarshal([]byte(dataStr), &filterObj); err != nil {
   355  		return filterObject{}, err
   356  	}
   357  	return filterObj, nil
   358  }
   359  
   360  func newAPICache(expireTime time.Duration, remoteURL string) apiCache {
   361  	redisClient := redis.NewClient(&redis.Options{
   362  		Addr:     remoteURL,
   363  		Password: "", // no password set
   364  		DB:       0,  // use default DB
   365  	})
   366  	if redisClient.Ping(context.Background()).Err() != nil {
   367  		log.L().Info("local cache is used as API cache")
   368  		filterCache, _ := ttl.NewCache(ttl.AutoExpireOption(expireTime))
   369  		return &localCache{
   370  			ttlCache: filterCache,
   371  		}
   372  	}
   373  	log.L().Info("remote cache is used as API cache")
   374  	return &remoteCache{
   375  		redisCache: redisClient,
   376  		expireTime: expireTime,
   377  	}
   378  }
   379  
   380  type apiCache interface {
   381  	Set(key string, data []byte) error
   382  	Del(key string) bool
   383  	Get(key string) ([]byte, bool)
   384  }
   385  
   386  type localCache struct {
   387  	ttlCache *ttl.Cache
   388  }
   389  
   390  func (c *localCache) Set(key string, data []byte) error {
   391  	if c.ttlCache == nil {
   392  		return errNullPointer
   393  	}
   394  	c.ttlCache.Set(key, data)
   395  	return nil
   396  }
   397  
   398  func (c *localCache) Del(key string) bool {
   399  	if c.ttlCache == nil {
   400  		return false
   401  	}
   402  	return c.ttlCache.Delete(key)
   403  }
   404  
   405  func (c *localCache) Get(key string) ([]byte, bool) {
   406  	if c.ttlCache == nil {
   407  		return nil, false
   408  	}
   409  	val, exist := c.ttlCache.Get(key)
   410  	if !exist {
   411  		return nil, false
   412  	}
   413  	ret, ok := val.([]byte)
   414  	return ret, ok
   415  }
   416  
   417  type remoteCache struct {
   418  	redisCache *redis.Client
   419  	expireTime time.Duration
   420  }
   421  
   422  func (c *remoteCache) Set(key string, data []byte) error {
   423  	if c.redisCache == nil {
   424  		return errNullPointer
   425  	}
   426  	return c.redisCache.Set(context.Background(), key, data, c.expireTime).Err()
   427  }
   428  
   429  func (c *remoteCache) Del(key string) bool {
   430  	if c.redisCache == nil {
   431  		return false
   432  	}
   433  	err := c.redisCache.Unlink(context.Background(), key).Err()
   434  	return err == nil
   435  }
   436  
   437  func (c *remoteCache) Get(key string) ([]byte, bool) {
   438  	if c.redisCache == nil {
   439  		return nil, false
   440  	}
   441  	ret, err := c.redisCache.Get(context.Background(), key).Bytes()
   442  	if err == redis.Nil {
   443  		return nil, false
   444  	} else if err != nil {
   445  		return nil, false
   446  	}
   447  	c.redisCache.Expire(context.Background(), key, c.expireTime)
   448  	return ret, true
   449  }
   450  
   451  // fromLoggerStructLogs converts logger.StructLog to apitypes.StructLog
   452  func fromLoggerStructLogs(logs []logger.StructLog) []apitypes.StructLog {
   453  	ret := make([]apitypes.StructLog, len(logs))
   454  	for index, log := range logs {
   455  		ret[index] = apitypes.StructLog{
   456  			Pc:            log.Pc,
   457  			Op:            log.Op,
   458  			Gas:           math.HexOrDecimal64(log.Gas),
   459  			GasCost:       math.HexOrDecimal64(log.GasCost),
   460  			Memory:        log.Memory,
   461  			MemorySize:    log.MemorySize,
   462  			Stack:         log.Stack,
   463  			ReturnData:    log.ReturnData,
   464  			Storage:       log.Storage,
   465  			Depth:         log.Depth,
   466  			RefundCounter: log.RefundCounter,
   467  			OpName:        log.OpName(),
   468  			ErrorString:   log.ErrorString(),
   469  		}
   470  	}
   471  	return ret
   472  }
   473  
   474  func newGetTransactionResult(
   475  	blkHash *hash.Hash256,
   476  	selp *action.SealedEnvelope,
   477  	receipt *action.Receipt,
   478  	evmChainID uint32,
   479  ) (*getTransactionResult, error) {
   480  	act, ok := selp.Action().(action.EthCompatibleAction)
   481  	if !ok {
   482  		actHash, _ := selp.Hash()
   483  		return nil, errors.Wrapf(errUnsupportedAction, "actHash: %s", hex.EncodeToString(actHash[:]))
   484  	}
   485  	ethTx, err := act.ToEthTx(0)
   486  	if err != nil {
   487  		return nil, err
   488  	}
   489  	var to *string
   490  	if ethTx.To() != nil {
   491  		tmp := ethTx.To().String()
   492  		to = &tmp
   493  	}
   494  
   495  	signer, err := action.NewEthSigner(iotextypes.Encoding(selp.Encoding()), evmChainID)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  	tx, err := action.RawTxToSignedTx(ethTx, signer, selp.Signature())
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	return &getTransactionResult{
   504  		blockHash: blkHash,
   505  		to:        to,
   506  		ethTx:     tx,
   507  		receipt:   receipt,
   508  		pubkey:    selp.SrcPubkey(),
   509  	}, nil
   510  }