github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/kas/repository_token_transfer.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 "fmt" 21 "math/big" 22 "strings" 23 24 "github.com/klaytn/klaytn/blockchain" 25 "github.com/klaytn/klaytn/blockchain/types" 26 "github.com/klaytn/klaytn/common" 27 ) 28 29 var tokenTransferEventHash = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") 30 31 // splitToWords divides log data to the words. 32 func splitToWords(data []byte) ([]common.Hash, error) { 33 if len(data)%common.HashLength != 0 { 34 return nil, fmt.Errorf("data length is not valid. want: %v, actual: %v", common.HashLength, len(data)) 35 } 36 var words []common.Hash 37 for i := 0; i < len(data); i += common.HashLength { 38 words = append(words, common.BytesToHash(data[i:i+common.HashLength])) 39 } 40 return words, nil 41 } 42 43 // wordToAddress trims input word to get address field only. 44 func wordToAddress(word common.Hash) common.Address { 45 return common.BytesToAddress(word[common.HashLength-common.AddressLength:]) 46 } 47 48 // transformLogsToTokenTransfers converts the given event into Klaytn Compatible Token transfers. 49 func transformLogsToTokenTransfers(event blockchain.ChainEvent) ([]*KCTTransfer, map[common.Address]struct{}, error) { 50 timestamp := event.Block.Time().Int64() 51 var kctTransfers []*KCTTransfer 52 mergedUpdatedEOAs := make(map[common.Address]struct{}) 53 for _, log := range event.Logs { 54 if len(log.Topics) > 0 && log.Topics[0] == tokenTransferEventHash { 55 transfer, updatedEOAs, err := transformLogToTokenTransfer(log) 56 if err != nil { 57 return nil, nil, err 58 } 59 transfer.Timestamp = timestamp 60 kctTransfers = append(kctTransfers, transfer) 61 for key := range updatedEOAs { 62 mergedUpdatedEOAs[key] = struct{}{} 63 } 64 } 65 } 66 67 return kctTransfers, mergedUpdatedEOAs, nil 68 } 69 70 // transformLogToTokenTransfer converts the given log to Klaytn Compatible Token transfer. 71 func transformLogToTokenTransfer(log *types.Log) (*KCTTransfer, map[common.Address]struct{}, error) { 72 // in case of token transfer, 73 // case 1: 74 // log.LogTopics[0] = token transfer event hash 75 // log.LogData = concat(fromAddress, toAddress, value) 76 // case 2: 77 // log.LogTopics[0] = token transfer event hash 78 // log.LogTopics[1] = fromAddress 79 // log.LogTopics[2] = toAddresss 80 // log.LogData = value 81 words, err := splitToWords(log.Data) 82 if err != nil { 83 return nil, nil, err 84 } 85 data := append(log.Topics, words...) 86 from := wordToAddress(data[1]) 87 to := wordToAddress(data[2]) 88 value := new(big.Int).SetBytes(data[3].Bytes()) 89 90 txLogId := int64(log.BlockNumber)*maxTxCountPerBlock*maxTxLogCountPerTx + int64(log.TxIndex)*maxTxLogCountPerTx + int64(log.Index) 91 updatedEOAs := make(map[common.Address]struct{}) 92 updatedEOAs[from] = struct{}{} 93 updatedEOAs[to] = struct{}{} 94 95 return &KCTTransfer{ 96 ContractAddress: log.Address.Bytes(), 97 From: from.Bytes(), 98 To: to.Bytes(), 99 TransactionLogId: txLogId, 100 Value: "0x" + value.Text(16), 101 TransactionHash: log.TxHash.Bytes(), 102 }, updatedEOAs, nil 103 } 104 105 // InsertTokenTransfers inserts token transfers in the given chain event into KAS database. 106 // The token transfers are divided into chunkUnit because of max number of place holders. 107 func (r *repository) InsertTokenTransfers(event blockchain.ChainEvent) error { 108 tokenTransfers, updatedEOAs, err := transformLogsToTokenTransfers(event) 109 if err != nil { 110 return err 111 } 112 113 chunkUnit := maxPlaceholders / placeholdersPerKCTTransferItem 114 var chunks []*KCTTransfer 115 116 for tokenTransfers != nil { 117 if placeholdersPerKCTTransferItem*len(tokenTransfers) > maxPlaceholders { 118 chunks = tokenTransfers[:chunkUnit] 119 tokenTransfers = tokenTransfers[chunkUnit:] 120 } else { 121 chunks = tokenTransfers 122 tokenTransfers = nil 123 } 124 125 if err := r.bulkInsertTokenTransfers(chunks); err != nil { 126 logger.Error("Failed to insertTokenTransfers", "err", err, "numTokenTransfers", len(chunks)) 127 return err 128 } 129 } 130 131 go r.InvalidateCacheEOAList(updatedEOAs) 132 return nil 133 } 134 135 // bulkInsertTokenTransfers inserts the given token transfers in multiple rows at once. 136 func (r *repository) bulkInsertTokenTransfers(tokenTransfers []*KCTTransfer) error { 137 if len(tokenTransfers) == 0 { 138 logger.Debug("the token transfer list is empty") 139 return nil 140 } 141 var valueStrings []string 142 var valueArgs []interface{} 143 144 for _, transfer := range tokenTransfers { 145 valueStrings = append(valueStrings, "(?,?,?,?,?,?,?)") 146 147 valueArgs = append(valueArgs, transfer.TransactionLogId) 148 valueArgs = append(valueArgs, transfer.From) 149 valueArgs = append(valueArgs, transfer.To) 150 valueArgs = append(valueArgs, transfer.Value) 151 valueArgs = append(valueArgs, transfer.ContractAddress) 152 valueArgs = append(valueArgs, transfer.TransactionHash) 153 valueArgs = append(valueArgs, transfer.Timestamp) 154 } 155 156 rawQuery := ` 157 INSERT INTO kct_transfers(transactionLogId, fromAddr, toAddr, value, contractAddress, transactionHash, timestamp) 158 VALUES %s 159 ON DUPLICATE KEY 160 UPDATE transactionLogId=transactionLogId` 161 query := fmt.Sprintf(rawQuery, strings.Join(valueStrings, ",")) 162 163 if _, err := r.db.DB().Exec(query, valueArgs...); err != nil { 164 return err 165 } 166 return nil 167 }