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 }