github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/kas/repository.go (about)

     1  // Copyright 2020 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package kas
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"strings"
    25  	"time"
    26  
    27  	_ "github.com/go-sql-driver/mysql"
    28  	"github.com/jinzhu/gorm"
    29  	"github.com/klaytn/klaytn/api"
    30  	"github.com/klaytn/klaytn/blockchain"
    31  	"github.com/klaytn/klaytn/common"
    32  	"github.com/klaytn/klaytn/datasync/chaindatafetcher/types"
    33  	"github.com/klaytn/klaytn/log"
    34  	"github.com/klaytn/klaytn/networks/rpc"
    35  )
    36  
    37  const (
    38  	maxTxCountPerBlock      = int64(1000000)
    39  	maxTxLogCountPerTx      = int64(100000)
    40  	maxInternalTxCountPerTx = int64(10000)
    41  
    42  	maxPlaceholders = 65535
    43  
    44  	placeholdersPerTxItem          = 13
    45  	placeholdersPerKCTTransferItem = 7
    46  	placeholdersPerRevertedTxItem  = 5
    47  	placeholdersPerContractItem    = 1
    48  	placeholdersPerFTItem          = 3
    49  	placeholdersPerNFTItem         = 3
    50  
    51  	maxOpenConnection = 100
    52  	maxIdleConnection = 10
    53  	connMaxLifetime   = 24 * time.Hour
    54  	maxDBRetryCount   = 20
    55  	DBRetryInterval   = 1 * time.Second
    56  	apiCtxTimeout     = 200 * time.Millisecond
    57  )
    58  
    59  var logger = log.NewModuleLogger(log.ChainDataFetcher)
    60  
    61  type repository struct {
    62  	db *gorm.DB
    63  
    64  	config *KASConfig
    65  
    66  	contractCaller *contractCaller
    67  	blockchainApi  BlockchainAPI
    68  }
    69  
    70  func getEndpoint(user, password, host, port, name string) string {
    71  	return user + ":" + password + "@tcp(" + host + ":" + port + ")/" + name + "?parseTime=True&charset=utf8mb4"
    72  }
    73  
    74  func NewRepository(config *KASConfig) (*repository, error) {
    75  	endpoint := getEndpoint(config.DBUser, config.DBPassword, config.DBHost, config.DBPort, config.DBName)
    76  	var (
    77  		db  *gorm.DB
    78  		err error
    79  	)
    80  	for i := 0; i < maxDBRetryCount; i++ {
    81  		db, err = gorm.Open("mysql", endpoint)
    82  		if err != nil {
    83  			logger.Warn("Retrying to connect DB", "endpoint", endpoint, "err", err)
    84  			time.Sleep(DBRetryInterval)
    85  		} else {
    86  			db.DB().SetMaxOpenConns(maxOpenConnection)
    87  			db.DB().SetMaxIdleConns(maxIdleConnection)
    88  			db.DB().SetConnMaxLifetime(connMaxLifetime)
    89  
    90  			return &repository{db: db, config: config}, nil
    91  		}
    92  	}
    93  	logger.Error("Failed to connect to the database", "endpoint", endpoint, "err", err)
    94  	return nil, err
    95  }
    96  
    97  func (r *repository) setBlockchainAPI(apis []rpc.API) {
    98  	for _, a := range apis {
    99  		switch s := a.Service.(type) {
   100  		case *api.PublicBlockChainAPI:
   101  			r.blockchainApi = s
   102  		}
   103  	}
   104  }
   105  
   106  func (r *repository) SetComponent(component interface{}) {
   107  	switch c := component.(type) {
   108  	case []rpc.API:
   109  		r.setBlockchainAPI(c)
   110  	}
   111  }
   112  
   113  func (r *repository) HandleChainEvent(event blockchain.ChainEvent, reqType types.RequestType) error {
   114  	switch reqType {
   115  	case types.RequestTypeTransaction:
   116  		return r.InsertTransactions(event)
   117  	case types.RequestTypeTrace:
   118  		return r.InsertTraceResults(event)
   119  	case types.RequestTypeTokenTransfer:
   120  		return r.InsertTokenTransfers(event)
   121  	case types.RequestTypeContract:
   122  		return r.InsertContracts(event)
   123  	default:
   124  		return fmt.Errorf("unsupported data type. [blockNumber: %v, reqType: %v]", event.Block.NumberU64(), reqType)
   125  	}
   126  }
   127  
   128  func makeEOAListStr(eoaList map[common.Address]struct{}) string {
   129  	eoaStrList := ""
   130  	for key := range eoaList {
   131  		eoaStrList += "\""
   132  		eoaStrList += strings.ToLower(key.String())
   133  		eoaStrList += "\""
   134  		eoaStrList += ","
   135  	}
   136  	return eoaStrList[:len(eoaStrList)-1]
   137  }
   138  
   139  func makeBasicAuthWithParam(param string) string {
   140  	return "Basic " + param
   141  }
   142  
   143  func (r *repository) InvalidateCacheEOAList(eoaList map[common.Address]struct{}) {
   144  	numEOAs := len(eoaList)
   145  	logger.Trace("the number of EOA list for KAS cache invalidation", "numEOAs", numEOAs)
   146  	if numEOAs <= 0 || !r.config.CacheUse {
   147  		return
   148  	}
   149  
   150  	url := r.config.CacheInvalidationURL
   151  	payloadStr := fmt.Sprintf(`{"type": "stateChange","payload": {"addresses": [%v]}}`, makeEOAListStr(eoaList))
   152  	payload := strings.NewReader(payloadStr)
   153  
   154  	// set up timeout for API call
   155  	ctx, cancel := context.WithTimeout(context.Background(), apiCtxTimeout)
   156  	defer cancel()
   157  
   158  	req, err := http.NewRequestWithContext(ctx, "POST", url, payload)
   159  	if err != nil {
   160  		logger.Error("Creating a new http request is failed", "err", err, "url", url, "payload", payloadStr)
   161  		return
   162  	}
   163  	req.Header.Add("x-chain-id", r.config.XChainId)
   164  	req.Header.Add("Authorization", makeBasicAuthWithParam(r.config.BasicAuthParam))
   165  	req.Header.Add("Content-Type", "text/plain")
   166  
   167  	res, err := http.DefaultClient.Do(req)
   168  	if err != nil {
   169  		logger.Error("Client do method is failed", "err", err, "url", url, "payload", payloadStr, "header", req.Header)
   170  		return
   171  	}
   172  	defer res.Body.Close()
   173  
   174  	if res.StatusCode != http.StatusOK {
   175  		body, err := ioutil.ReadAll(res.Body)
   176  		if err != nil {
   177  			logger.Error("Reading response body is failed", "err", err, "body", res.Body)
   178  			return
   179  		}
   180  		logger.Error("cache invalidation is failed", "response", string(body))
   181  	}
   182  }