github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/kas/repository_contracts.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  	"strings"
    22  	"time"
    23  
    24  	"github.com/klaytn/klaytn/blockchain"
    25  	"github.com/klaytn/klaytn/blockchain/types"
    26  	"github.com/klaytn/klaytn/common"
    27  )
    28  
    29  // filterKIPContracts filters the deployed contracts to KIP7, KIP17 and others.
    30  func filterKIPContracts(api BlockchainAPI, event blockchain.ChainEvent) ([]*FT, []*NFT, []*Contract, error) {
    31  	var (
    32  		kip7s  []*FT
    33  		kip17s []*NFT
    34  		others []*Contract
    35  	)
    36  	caller := newContractCaller(api)
    37  	for _, receipt := range event.Receipts {
    38  		if receipt.Status != types.ReceiptStatusSuccessful || receipt.ContractAddress == (common.Address{}) {
    39  			continue
    40  		}
    41  		contract := receipt.ContractAddress
    42  		isKIP13, err := caller.isKIP13(contract, nil)
    43  		if err != nil {
    44  			logger.Error("Failed to call isKIP13", "err", err, "contract", contract.String())
    45  			return nil, nil, nil, err
    46  		} else if !isKIP13 {
    47  			others = append(others, &Contract{Address: contract.Bytes()})
    48  			continue
    49  		}
    50  
    51  		if isKIP7, err := caller.isKIP7(contract, nil); err != nil {
    52  			logger.Error("Failed to call isKIP7", "err", err, "contract", contract.String())
    53  			return nil, nil, nil, err
    54  		} else if isKIP7 {
    55  			kip7s = append(kip7s, &FT{Address: contract.Bytes()})
    56  			continue
    57  		}
    58  
    59  		if isKIP17, err := caller.isKIP17(contract, nil); err != nil {
    60  			logger.Error("Failed to call isKIP17", "err", err, "contract", contract.String())
    61  			return nil, nil, nil, err
    62  		} else if isKIP17 {
    63  			kip17s = append(kip17s, &NFT{Address: contract.Bytes()})
    64  			continue
    65  		}
    66  		others = append(others, &Contract{Address: contract.Bytes()})
    67  	}
    68  	return kip7s, kip17s, others, nil
    69  }
    70  
    71  // InsertContracts inserts deployed contracts in the given chain event into KAS database.
    72  func (r *repository) InsertContracts(event blockchain.ChainEvent) error {
    73  	kip7s, kip17s, others, err := filterKIPContracts(r.blockchainApi, event)
    74  	if err != nil {
    75  		logger.Error("Failed to filter KIP contracts", "err", err, "blockNumber", event.Block.NumberU64())
    76  		return err
    77  	}
    78  
    79  	if err := r.insertFTs(kip7s); err != nil {
    80  		logger.Error("Failed to insert KIP7 contracts", "err", err, "blockNumber", event.Block.NumberU64(), "numKIP7s", len(kip7s))
    81  		return err
    82  	}
    83  
    84  	if err := r.insertNFTs(kip17s); err != nil {
    85  		logger.Error("Failed to insert KIP17 contracts", "err", err, "blockNumber", event.Block.NumberU64(), "numKIP17s", len(kip17s))
    86  		return err
    87  	}
    88  
    89  	if err := r.insertContracts(others); err != nil {
    90  		logger.Error("Failed to insert other contracts", "err", err, "blockNumber", event.Block.NumberU64(), "numContracts", len(others))
    91  		return err
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // insertContracts inserts the contracts which are divided into chunkUnit because of max number of placeholders.
    98  func (r *repository) insertContracts(contracts []*Contract) error {
    99  	chunkUnit := maxPlaceholders / placeholdersPerContractItem
   100  	var chunks []*Contract
   101  
   102  	for contracts != nil {
   103  		if placeholdersPerContractItem*len(contracts) > maxPlaceholders {
   104  			chunks = contracts[:chunkUnit]
   105  			contracts = contracts[chunkUnit:]
   106  		} else {
   107  			chunks = contracts
   108  			contracts = nil
   109  		}
   110  
   111  		if err := r.bulkInsertContracts(chunks); err != nil {
   112  			return err
   113  		}
   114  	}
   115  
   116  	return nil
   117  }
   118  
   119  // bulkInsertTransactions inserts the given contracts in multiple rows at once.
   120  func (r *repository) bulkInsertContracts(contracts []*Contract) error {
   121  	if len(contracts) == 0 {
   122  		return nil
   123  	}
   124  	var valueStrings []string
   125  	var valueArgs []interface{}
   126  
   127  	for _, ft := range contracts {
   128  		valueStrings = append(valueStrings, "(?)")
   129  		valueArgs = append(valueArgs, ft.Address)
   130  	}
   131  
   132  	rawQuery := `
   133  			INSERT INTO contract(address)
   134  			VALUES %s
   135  			ON DUPLICATE KEY
   136  			UPDATE address=address`
   137  	query := fmt.Sprintf(rawQuery, strings.Join(valueStrings, ","))
   138  
   139  	if _, err := r.db.DB().Exec(query, valueArgs...); err != nil {
   140  		return err
   141  	}
   142  	return nil
   143  }
   144  
   145  // insertFTs inserts the FT contracts which are divided into chunkUnit because of max number of placeholders.
   146  func (r *repository) insertFTs(fts []*FT) error {
   147  	chunkUnit := maxPlaceholders / placeholdersPerFTItem
   148  	var chunks []*FT
   149  
   150  	for fts != nil {
   151  		if placeholdersPerFTItem*len(fts) > maxPlaceholders {
   152  			chunks = fts[:chunkUnit]
   153  			fts = fts[chunkUnit:]
   154  		} else {
   155  			chunks = fts
   156  			fts = nil
   157  		}
   158  
   159  		if err := r.bulkInsertFTs(chunks); err != nil {
   160  			return err
   161  		}
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // bulkInsertFTs inserts the given FT contracts in multiple rows at once.
   168  func (r *repository) bulkInsertFTs(fts []*FT) error {
   169  	if len(fts) == 0 {
   170  		return nil
   171  	}
   172  	var valueStrings []string
   173  	var valueArgs []interface{}
   174  
   175  	now := time.Now()
   176  	for _, ft := range fts {
   177  		valueStrings = append(valueStrings, "(?, ?, ?)")
   178  		valueArgs = append(valueArgs, ft.Address)
   179  		valueArgs = append(valueArgs, &now)
   180  		valueArgs = append(valueArgs, &now)
   181  	}
   182  
   183  	rawQuery := `
   184  			INSERT INTO kct_ft_metadata(address, createdAt, updatedAt)
   185  			VALUES %s
   186  			ON DUPLICATE KEY
   187  			UPDATE address=address`
   188  	query := fmt.Sprintf(rawQuery, strings.Join(valueStrings, ","))
   189  
   190  	if _, err := r.db.DB().Exec(query, valueArgs...); err != nil {
   191  		return err
   192  	}
   193  	return nil
   194  }
   195  
   196  // insertNFTs inserts the NFT contracts which are divided into chunkUnit because of max number of placeholders.
   197  func (r *repository) insertNFTs(nfts []*NFT) error {
   198  	chunkUnit := maxPlaceholders / placeholdersPerNFTItem
   199  	var chunks []*NFT
   200  
   201  	for nfts != nil {
   202  		if placeholdersPerNFTItem*len(nfts) > maxPlaceholders {
   203  			chunks = nfts[:chunkUnit]
   204  			nfts = nfts[chunkUnit:]
   205  		} else {
   206  			chunks = nfts
   207  			nfts = nil
   208  		}
   209  
   210  		if err := r.bulkInsertNFTs(chunks); err != nil {
   211  			return err
   212  		}
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  // bulkInsertNFTs inserts the given NFT contracts in multiple rows at once.
   219  func (r *repository) bulkInsertNFTs(nfts []*NFT) error {
   220  	if len(nfts) == 0 {
   221  		return nil
   222  	}
   223  	var valueStrings []string
   224  	var valueArgs []interface{}
   225  
   226  	now := time.Now()
   227  	for _, nft := range nfts {
   228  		valueStrings = append(valueStrings, "(?, ?, ?)")
   229  		valueArgs = append(valueArgs, nft.Address)
   230  		valueArgs = append(valueArgs, &now)
   231  		valueArgs = append(valueArgs, &now)
   232  	}
   233  
   234  	rawQuery := `
   235  			INSERT INTO kct_nft_metadata(address, createdAt, updatedAt)
   236  			VALUES %s
   237  			ON DUPLICATE KEY
   238  			UPDATE address=address`
   239  	query := fmt.Sprintf(rawQuery, strings.Join(valueStrings, ","))
   240  
   241  	if _, err := r.db.DB().Exec(query, valueArgs...); err != nil {
   242  		return err
   243  	}
   244  	return nil
   245  }