github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/kas/repository_transactions.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  
    23  	"github.com/klaytn/klaytn/blockchain"
    24  	"github.com/klaytn/klaytn/blockchain/types"
    25  	"github.com/klaytn/klaytn/common"
    26  	"github.com/klaytn/klaytn/common/hexutil"
    27  )
    28  
    29  // TxFilteringTypes filters types which are only stored in KAS database.
    30  var TxFilteringTypes = map[types.TxType]bool{
    31  	types.TxTypeLegacyTransaction: true,
    32  
    33  	types.TxTypeValueTransfer:                      true,
    34  	types.TxTypeFeeDelegatedValueTransfer:          true,
    35  	types.TxTypeFeeDelegatedValueTransferWithRatio: true,
    36  
    37  	types.TxTypeValueTransferMemo:                      true,
    38  	types.TxTypeFeeDelegatedValueTransferMemo:          true,
    39  	types.TxTypeFeeDelegatedValueTransferMemoWithRatio: true,
    40  
    41  	types.TxTypeSmartContractDeploy:                      true,
    42  	types.TxTypeFeeDelegatedSmartContractDeploy:          true,
    43  	types.TxTypeFeeDelegatedSmartContractDeployWithRatio: true,
    44  
    45  	types.TxTypeSmartContractExecution:                      true,
    46  	types.TxTypeFeeDelegatedSmartContractExecution:          true,
    47  	types.TxTypeFeeDelegatedSmartContractExecutionWithRatio: true,
    48  }
    49  
    50  // transformToTxs transforms a chain event to transaction list according to the KAS database scheme.
    51  func transformToTxs(event blockchain.ChainEvent) ([]*Tx, map[common.Address]struct{}) {
    52  	var txs []*Tx
    53  	updatedEOAs := make(map[common.Address]struct{})
    54  
    55  	block := event.Block
    56  	head := block.Header()
    57  	receipts := event.Receipts
    58  
    59  	for idx, rawTx := range block.Transactions() {
    60  		txId := head.Number.Int64()*maxTxCountPerBlock*maxTxLogCountPerTx + int64(idx)*maxInternalTxCountPerTx
    61  
    62  		// from
    63  		var from common.Address
    64  		if rawTx.IsEthereumTransaction() {
    65  			signer := types.LatestSignerForChainID(rawTx.ChainId())
    66  			from, _ = types.Sender(signer, rawTx)
    67  		} else {
    68  			from, _ = rawTx.From()
    69  		}
    70  
    71  		// to
    72  		to := rawTx.To()
    73  		if to == nil {
    74  			to = &common.Address{}
    75  		}
    76  
    77  		// value
    78  		value := hexutil.EncodeBig(rawTx.Value())
    79  
    80  		// status
    81  		var status int
    82  		if receipts[idx].Status != types.ReceiptStatusSuccessful {
    83  			status = int(types.ReceiptStatusFailed)
    84  		} else {
    85  			status = int(receipts[idx].Status)
    86  		}
    87  
    88  		// fee payer
    89  		// fee ratio
    90  		var (
    91  			feePayer []byte
    92  			feeRatio uint
    93  		)
    94  
    95  		if rawTx.IsFeeDelegatedTransaction() {
    96  			payer, _ := rawTx.FeePayer()
    97  			feePayer = payer.Bytes()
    98  
    99  			ratio, ok := rawTx.FeeRatio()
   100  			if ok {
   101  				feeRatio = uint(ratio)
   102  			}
   103  		}
   104  
   105  		updatedEOAs[from] = struct{}{}
   106  		updatedEOAs[*to] = struct{}{}
   107  
   108  		tx := &Tx{
   109  			Timestamp:       head.Time.Int64(),
   110  			TransactionId:   txId,
   111  			FromAddr:        from.Bytes(),
   112  			ToAddr:          to.Bytes(),
   113  			Value:           value,
   114  			TransactionHash: rawTx.Hash().Bytes(),
   115  			Status:          status,
   116  			TypeInt:         int(rawTx.Type()),
   117  			GasPrice:        rawTx.GasPrice().Uint64(),
   118  			GasUsed:         receipts[idx].GasUsed,
   119  			FeePayer:        feePayer,
   120  			FeeRatio:        feeRatio,
   121  		}
   122  
   123  		txs = append(txs, tx)
   124  	}
   125  	return txs, updatedEOAs
   126  }
   127  
   128  // InsertTransactions inserts transactions in the given chain event into KAS database.
   129  func (r *repository) InsertTransactions(event blockchain.ChainEvent) error {
   130  	txs, updatedEOAs := transformToTxs(event)
   131  	if err := r.insertTransactions(txs); err != nil {
   132  		logger.Error("Failed to insertTransactions", "err", err, "blockNumber", event.Block.NumberU64(), "numTxs", len(txs))
   133  		return err
   134  	}
   135  	go r.InvalidateCacheEOAList(updatedEOAs)
   136  	return nil
   137  }
   138  
   139  // insertTransactions inserts the given transactions divided into chunkUnit because of the max number of placeholders.
   140  func (r *repository) insertTransactions(txs []*Tx) error {
   141  	chunkUnit := maxPlaceholders / placeholdersPerTxItem
   142  	var chunks []*Tx
   143  
   144  	for txs != nil {
   145  		if placeholdersPerTxItem*len(txs) > maxPlaceholders {
   146  			chunks = txs[:chunkUnit]
   147  			txs = txs[chunkUnit:]
   148  		} else {
   149  			chunks = txs
   150  			txs = nil
   151  		}
   152  
   153  		if err := r.bulkInsertTransactions(chunks); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // bulkInsertTransactions inserts the given transactions in multiple rows at once.
   162  func (r *repository) bulkInsertTransactions(txs []*Tx) error {
   163  	var valueStrings []string
   164  	var valueArgs []interface{}
   165  
   166  	for _, tx := range txs {
   167  		if _, ok := TxFilteringTypes[types.TxType(tx.TypeInt)]; ok {
   168  			valueStrings = append(valueStrings, "(?,?,?,?,?,?,?,?,?,?,?,?,?)")
   169  
   170  			valueArgs = append(valueArgs, tx.TransactionId)
   171  			valueArgs = append(valueArgs, tx.FromAddr)
   172  			valueArgs = append(valueArgs, tx.ToAddr)
   173  			valueArgs = append(valueArgs, tx.Value)
   174  			valueArgs = append(valueArgs, tx.TransactionHash)
   175  			valueArgs = append(valueArgs, tx.Status)
   176  			valueArgs = append(valueArgs, tx.Timestamp)
   177  			valueArgs = append(valueArgs, tx.TypeInt)
   178  			valueArgs = append(valueArgs, tx.GasPrice)
   179  			valueArgs = append(valueArgs, tx.GasUsed)
   180  			valueArgs = append(valueArgs, tx.FeePayer)
   181  			valueArgs = append(valueArgs, tx.FeeRatio)
   182  			valueArgs = append(valueArgs, tx.Internal)
   183  		}
   184  	}
   185  
   186  	if len(valueStrings) == 0 {
   187  		return nil
   188  	}
   189  
   190  	rawQuery := `
   191  			INSERT INTO klay_transfers(transactionId, fromAddr, toAddr, value, transactionHash, status, timestamp, typeInt, gasPrice, gasUsed, feePayer, feeRatio, internal)
   192  			VALUES %s
   193  			ON DUPLICATE KEY
   194  			UPDATE transactionId=transactionId`
   195  	query := fmt.Sprintf(rawQuery, strings.Join(valueStrings, ","))
   196  
   197  	if _, err := r.db.DB().Exec(query, valueArgs...); err != nil {
   198  		return err
   199  	}
   200  
   201  	return nil
   202  }