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 }