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  }