github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/kas/repository_traces.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  	"reflect"
    22  	"strings"
    23  
    24  	"github.com/klaytn/klaytn/blockchain"
    25  	"github.com/klaytn/klaytn/blockchain/types"
    26  	"github.com/klaytn/klaytn/blockchain/vm"
    27  	"github.com/klaytn/klaytn/common"
    28  )
    29  
    30  const selfDestructType = "SELFDESTRUCT"
    31  
    32  var emptyTraceResult = &vm.InternalTxTrace{
    33  	Value: "0x0",
    34  	Calls: []*vm.InternalTxTrace{},
    35  }
    36  
    37  func isEmptyTraceResult(trace *vm.InternalTxTrace) bool {
    38  	return reflect.DeepEqual(trace, emptyTraceResult)
    39  }
    40  
    41  // getEntryTx returns a entry transaction which may call internal transactions.
    42  func getEntryTx(block *types.Block, txIdx int, tx *types.Transaction) *Tx {
    43  	head := block.Header()
    44  	txId := head.Number.Int64()*maxTxCountPerBlock*maxTxLogCountPerTx + int64(txIdx)*maxInternalTxCountPerTx
    45  	return &Tx{
    46  		TransactionId:   txId,
    47  		TransactionHash: tx.Hash().Bytes(),
    48  		Status:          int(types.ReceiptStatusSuccessful),
    49  		Timestamp:       block.Time().Int64(),
    50  		TypeInt:         int(tx.Type()),
    51  		Internal:        true,
    52  	}
    53  }
    54  
    55  // transformToInternalTx converts the result of call tracer into the internal transaction list according to the KAS database scheme.
    56  func transformToInternalTx(trace *vm.InternalTxTrace, offset *int64, entryTx *Tx, isFirstCall bool) ([]*Tx, error) {
    57  	if trace.Error != nil {
    58  		// ignore the internal transaction if it has an error field
    59  		return nil, nil
    60  	}
    61  
    62  	if trace.Type == "" {
    63  		return nil, noOpcodeError
    64  	} else if trace.Type == selfDestructType {
    65  		// TODO-ChainDataFetcher currently, skip it when self-destruct is encountered.
    66  		return nil, nil
    67  	}
    68  
    69  	if trace.From == nil {
    70  		return nil, noFromFieldError
    71  	}
    72  
    73  	if trace.To == nil {
    74  		return nil, noToFieldError
    75  	}
    76  
    77  	var txs []*Tx
    78  	if !isFirstCall && trace.Value != "" && trace.Value != "0x0" { // filter the internal tx if the value is 0
    79  		*offset++ // adding 1 to calculate the transaction id in the order of internal transactions
    80  		txs = append(txs, &Tx{
    81  			TransactionId:   entryTx.TransactionId + *offset,
    82  			FromAddr:        trace.From.Bytes(),
    83  			ToAddr:          trace.To.Bytes(),
    84  			Value:           trace.Value,
    85  			TransactionHash: entryTx.TransactionHash,
    86  			Status:          entryTx.Status,
    87  			Timestamp:       entryTx.Timestamp,
    88  			TypeInt:         entryTx.TypeInt,
    89  			Internal:        true,
    90  		})
    91  	}
    92  
    93  	for _, call := range trace.Calls {
    94  		nestedCalls, err := transformToInternalTx(call, offset, entryTx, false)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  		txs = append(txs, nestedCalls...)
    99  	}
   100  
   101  	return txs, nil
   102  }
   103  
   104  // transformToRevertedTx converts the result of call tracer into the reverted transaction information according to the KAS database scheme.
   105  func transformToRevertedTx(trace *vm.InternalTxTrace, block *types.Block, entryTx *types.Transaction) (*RevertedTx, error) {
   106  	return &RevertedTx{
   107  		TransactionHash: entryTx.Hash().Bytes(),
   108  		BlockNumber:     block.Number().Int64(),
   109  		RevertMessage:   trace.Reverted.Message,
   110  		ContractAddress: trace.Reverted.Contract.Bytes(),
   111  		Timestamp:       block.Time().Int64(),
   112  	}, nil
   113  }
   114  
   115  // transformToTraceResults converts the chain event to internal transactions as well as reverted transactions.
   116  func transformToTraceResults(event blockchain.ChainEvent) ([]*Tx, []*RevertedTx, error) {
   117  	var (
   118  		internalTxs []*Tx
   119  		revertedTxs []*RevertedTx
   120  	)
   121  	for txIdx, trace := range event.InternalTxTraces {
   122  		if isEmptyTraceResult(trace) {
   123  			continue
   124  		}
   125  
   126  		tx := event.Block.Transactions()[txIdx]
   127  		receipt := event.Receipts[txIdx]
   128  
   129  		entryTx := getEntryTx(event.Block, txIdx, tx)
   130  		offset := int64(0)
   131  
   132  		// transforms the result into internal transaction which is associated with KLAY transfer recursively.
   133  		if receipt.Status == types.ReceiptStatusSuccessful {
   134  			internalTx, err := transformToInternalTx(trace, &offset, entryTx, true)
   135  			if err != nil {
   136  				logger.Error("Failed to transform tracing result into internal tx", "err", err, "txHash", common.BytesToHash(entryTx.TransactionHash).String())
   137  				return nil, nil, err
   138  			}
   139  			internalTxs = append(internalTxs, internalTx...)
   140  		}
   141  
   142  		// transforms the result into an evm reverted transaction.
   143  		if receipt.Status == types.ReceiptStatusErrExecutionReverted {
   144  			revertedTx, err := transformToRevertedTx(trace, event.Block, tx)
   145  			if err != nil {
   146  				logger.Error("Failed to transform tracing result into reverted tx", "err", err, "txHash", common.BytesToHash(entryTx.TransactionHash).String())
   147  				return nil, nil, err
   148  			}
   149  			revertedTxs = append(revertedTxs, revertedTx)
   150  		}
   151  	}
   152  	return internalTxs, revertedTxs, nil
   153  }
   154  
   155  // InsertTraceResults inserts internal and reverted transactions in the given chain event into KAS database.
   156  func (r *repository) InsertTraceResults(event blockchain.ChainEvent) error {
   157  	internalTxs, revertedTxs, err := transformToTraceResults(event)
   158  	if err != nil {
   159  		logger.Error("Failed to transform the given event to tracing results", "err", err, "blockNumber", event.Block.NumberU64())
   160  		return err
   161  	}
   162  
   163  	if err := r.insertTransactions(internalTxs); err != nil {
   164  		logger.Error("Failed to insert internal transactions", "err", err, "blockNumber", event.Block.NumberU64(), "numInternalTxs", len(internalTxs))
   165  		return err
   166  	}
   167  
   168  	if err := r.insertRevertedTransactions(revertedTxs); err != nil {
   169  		logger.Error("Failed to insert reverted transactions", "err", err, "blockNumber", event.Block.NumberU64(), "numRevertedTxs", len(revertedTxs))
   170  		return err
   171  	}
   172  	return nil
   173  }
   174  
   175  // insertRevertedTransactions inserts the given reverted transactions divided into chunkUnit because of the max number of placeholders.
   176  func (r *repository) insertRevertedTransactions(revertedTxs []*RevertedTx) error {
   177  	chunkUnit := maxPlaceholders / placeholdersPerRevertedTxItem
   178  	var chunks []*RevertedTx
   179  
   180  	for revertedTxs != nil {
   181  		if placeholdersPerRevertedTxItem*len(revertedTxs) > maxPlaceholders {
   182  			chunks = revertedTxs[:chunkUnit]
   183  			revertedTxs = revertedTxs[chunkUnit:]
   184  		} else {
   185  			chunks = revertedTxs
   186  			revertedTxs = nil
   187  		}
   188  
   189  		if err := r.bulkInsertRevertedTransactions(chunks); err != nil {
   190  			return err
   191  		}
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  // bulkInsertRevertedTransactions inserts the given reverted transactions in multiple rows at once.
   198  func (r *repository) bulkInsertRevertedTransactions(revertedTxs []*RevertedTx) error {
   199  	if len(revertedTxs) == 0 {
   200  		return nil
   201  	}
   202  	var valueStrings []string
   203  	var valueArgs []interface{}
   204  
   205  	for _, revertedTx := range revertedTxs {
   206  		valueStrings = append(valueStrings, "(?,?,?,?,?)")
   207  
   208  		valueArgs = append(valueArgs, revertedTx.TransactionHash)
   209  		valueArgs = append(valueArgs, revertedTx.BlockNumber)
   210  		valueArgs = append(valueArgs, revertedTx.ContractAddress)
   211  		valueArgs = append(valueArgs, revertedTx.RevertMessage)
   212  		valueArgs = append(valueArgs, revertedTx.Timestamp)
   213  	}
   214  
   215  	rawQuery := `
   216  			INSERT INTO reverted_transactions(transactionHash, blockNumber, contractAddress, revertMessage, timestamp)
   217  			VALUES %s
   218  			ON DUPLICATE KEY
   219  			UPDATE transactionHash=transactionHash`
   220  	query := fmt.Sprintf(rawQuery, strings.Join(valueStrings, ","))
   221  
   222  	if _, err := r.db.DB().Exec(query, valueArgs...); err != nil {
   223  		return err
   224  	}
   225  	return nil
   226  }