github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/uniq/uniq_appearances_details.go (about) 1 package uniq 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strings" 8 "sync" 9 10 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 11 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 12 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/names" 13 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" 14 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 15 ) 16 17 var AppearanceFmt = "%s\t%09d\t%05d" 18 19 type UniqProcFunc func(s *types.Appearance) error 20 type AddressBooleanMap map[string]bool 21 22 // Insert generates item's key according to `AppearanceFmt` and adds the item to the map 23 func (a *AddressBooleanMap) Insert(address string, bn base.Blknum, txid base.Txnum) string { 24 key := fmt.Sprintf(AppearanceFmt, address, bn, txid) 25 v := *a 26 v[key] = true 27 return key 28 } 29 30 func GetUniqAddressesInBlock(chain, flow string, conn *rpc.Connection, procFunc UniqProcFunc, bn base.Blknum) error { 31 ts := conn.GetBlockTimestamp(bn) 32 addrMap := AddressBooleanMap{} 33 traceid := base.NOPOSN 34 if bn == 0 { 35 if namesMap, err := names.LoadNamesMap(chain, types.Prefund, []string{}); err != nil { 36 return err 37 } else { 38 namesArray := make([]*types.Name, 0, len(namesMap)) 39 for _, name := range namesMap { 40 namesArray = append(namesArray, &name) 41 } 42 sort.Slice(namesArray, func(i, j int) bool { 43 return namesArray[i].Name < namesArray[j].Name 44 }) 45 for i, name := range namesArray { 46 tx_id := base.Txnum(i) 47 address := name.Address.Hex() 48 streamAppearance(procFunc, flow, "genesis", address, bn, tx_id, traceid, ts, addrMap) 49 } 50 } 51 52 } else { 53 if block, err := conn.GetBlockBodyByNumber(bn); err != nil { 54 return err 55 } else { 56 author := block.Miner.Hex() 57 fakeId := types.BlockReward 58 if base.IsPrecompile(author) { 59 // Some blocks have a misconfigured miner setting. We process this block, so that 60 // every block gets a record, but it will be excluded from the index. See #3252. 61 author = base.SentinalAddr.Hex() 62 fakeId = types.MisconfigReward 63 } 64 streamAppearance(procFunc, flow, "miner", author, bn, fakeId, traceid, ts, addrMap) 65 66 if uncles, err := conn.GetUncleBodiesByNumber(bn); err != nil { 67 return err 68 } else { 69 for _, uncle := range uncles { 70 author := uncle.Miner.Hex() 71 fakeId := types.UncleReward 72 if base.IsPrecompile(author) { 73 // Some blocks have a misconfigured miner setting. We process this block, so that 74 // every block gets a record, but it will be excluded from the index. See #3252. 75 author = base.SentinalAddr.Hex() 76 fakeId = types.MisconfigReward 77 } 78 streamAppearance(procFunc, flow, "uncle", author, bn, fakeId, traceid, ts, addrMap) 79 } 80 } 81 82 for _, trans := range block.Transactions { 83 if trans.Traces, err = conn.GetTracesByTransactionId(trans.BlockNumber, trans.TransactionIndex); err != nil { 84 return err 85 } 86 if err = GetUniqAddressesInTransaction(chain, procFunc, flow, &trans, ts, addrMap, conn); err != nil { 87 return err 88 } 89 } 90 91 for _, withdrawal := range block.Withdrawals { 92 streamAppearance(procFunc, flow, "withdrawal", withdrawal.Address.Hex(), bn, withdrawal.Index, traceid, ts, addrMap) 93 } 94 } 95 } 96 97 return nil 98 } 99 100 func GetUniqAddressesInTransaction(chain string, procFunc UniqProcFunc, flow string, trans *types.Transaction, ts base.Timestamp, addrMap AddressBooleanMap, conn *rpc.Connection) error { 101 bn := trans.BlockNumber 102 txid := trans.TransactionIndex 103 traceid := base.NOPOSN 104 from := trans.From.Hex() 105 streamAppearance(procFunc, flow, "from", from, bn, txid, traceid, ts, addrMap) 106 107 to := trans.To.Hex() 108 streamAppearance(procFunc, flow, "to", to, bn, txid, traceid, ts, addrMap) 109 110 if trans.Receipt != nil && !trans.Receipt.ContractAddress.IsZero() { 111 contract := trans.Receipt.ContractAddress.Hex() 112 streamAppearance(procFunc, flow, "creation", contract, bn, txid, traceid, ts, addrMap) 113 } 114 115 if len(trans.Input) > 10 { 116 reason := "input" 117 inputData := trans.Input[10:] 118 for i := 0; i < len(inputData)/64; i++ { 119 str := string(inputData[i*64 : (i+1)*64]) 120 if IsImplicitAddress(str) { 121 streamAppearance(procFunc, flow, str, reason, bn, txid, traceid, ts, addrMap) 122 } 123 } 124 } 125 126 // TODO: See issue #3195 - there are addresses on the receipt that do not appear in traces 127 if trans.Receipt != nil { 128 if err := uniqFromLogsDetails(chain, procFunc, flow, trans.Receipt.Logs, ts, addrMap); err != nil { 129 return err 130 } 131 } 132 133 if err := uniqFromTracesDetails(chain, procFunc, flow, trans.Traces, ts, addrMap, conn); err != nil { 134 return err 135 } 136 137 return nil 138 } 139 140 // uniqFromLogsDetails extracts addresses from the logs 141 func uniqFromLogsDetails(chain string, procFunc UniqProcFunc, flow string, logs []types.Log, ts base.Timestamp, addrMap AddressBooleanMap) (err error) { 142 traceid := base.NOPOSN 143 for l, log := range logs { 144 generator := log.Address.Hex() 145 reason := fmt.Sprintf("log_%d_generator", l) 146 streamAppearance(procFunc, flow, reason, generator, log.BlockNumber, log.TransactionIndex, traceid, ts, addrMap) 147 148 for t, topic := range log.Topics { 149 str := string(topic.Hex()[2:]) 150 if IsImplicitAddress(str) { 151 reason := fmt.Sprintf("log_%d_topic_%d", l, t) 152 streamAppearance(procFunc, flow, reason, str, log.BlockNumber, log.TransactionIndex, traceid, ts, addrMap) 153 } 154 } 155 156 if len(log.Data) > 2 { 157 reason := fmt.Sprintf("log_%d_data", l) 158 inputData := log.Data[2:] 159 for i := 0; i < len(inputData)/64; i++ { 160 str := string(inputData[i*64 : (i+1)*64]) 161 if IsImplicitAddress(str) { 162 streamAppearance(procFunc, flow, reason, str, log.BlockNumber, log.TransactionIndex, traceid, ts, addrMap) 163 } 164 } 165 } 166 } 167 168 return 169 } 170 171 func traceReason(traceId base.Tracenum, trace *types.Trace, r string) string { 172 switch r { 173 case "from": 174 fallthrough 175 case "to": 176 fallthrough 177 case "input": 178 fallthrough 179 case "output": 180 fallthrough 181 case "self-destruct": 182 fallthrough 183 case "refund": 184 fallthrough 185 case "creation": 186 fallthrough 187 case "code": 188 if traceId == 0 { 189 return r 190 } else { 191 a := "" 192 for i, x := range trace.TraceAddress { 193 if i > 0 { 194 a += fmt.Sprintf("%d_", x) 195 } 196 } 197 if len(a) > 0 { 198 a = "[" + strings.Trim(a, "_") + "]_" 199 } 200 return fmt.Sprintf("trace_%d_%s%s", traceId, a, r) 201 } 202 default: 203 return "unknown" 204 } 205 } 206 207 // uniqFromTracesDetails extracts addresses from traces 208 func uniqFromTracesDetails(chain string, procFunc UniqProcFunc, flow string, traces []types.Trace, ts base.Timestamp, addrMap AddressBooleanMap, conn *rpc.Connection) (err error) { 209 for _, trace := range traces { 210 traceid := trace.TraceIndex 211 bn := base.Blknum(trace.BlockNumber) 212 txid := trace.TransactionIndex 213 214 from := trace.Action.From.Hex() 215 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "from"), from, bn, txid, traceid, ts, addrMap) 216 217 to := trace.Action.To.Hex() 218 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "to"), to, bn, txid, traceid, ts, addrMap) 219 220 if trace.TraceType == "call" { 221 // If it's a call, get the to and from, we're done 222 223 } else if trace.TraceType == "reward" { 224 if trace.Action.RewardType == "block" { 225 author := trace.Action.Author.Hex() 226 fakeId := types.BlockReward 227 if base.IsPrecompile(author) { 228 // Some blocks have a misconfigured miner setting. We process this block, so that 229 // every block gets a record, but it will be excluded from the index. See #3252. 230 author = base.SentinalAddr.Hex() 231 fakeId = types.MisconfigReward 232 } 233 streamAppearance(procFunc, flow, "miner", author, bn, fakeId, traceid, ts, addrMap) 234 235 } else if trace.Action.RewardType == "uncle" { 236 author := trace.Action.Author.Hex() 237 fakeId := types.UncleReward 238 if base.IsPrecompile(author) { 239 // Some blocks have a misconfigured miner setting. We process this block, so that 240 // every block gets a record, but it will be excluded from the index. See #3252. 241 author = base.SentinalAddr.Hex() 242 fakeId = types.MisconfigReward 243 } 244 streamAppearance(procFunc, flow, "uncle", author, bn, fakeId, traceid, ts, addrMap) 245 246 } else if trace.Action.RewardType == "external" { 247 author := trace.Action.Author.Hex() 248 fakeId := types.ExternalReward 249 if base.IsPrecompile(author) { 250 // Some blocks have a misconfigured miner setting. We process this block, so that 251 // every block gets a record, but it will be excluded from the index. See #3252. 252 author = base.SentinalAddr.Hex() 253 fakeId = types.MisconfigReward 254 } 255 streamAppearance(procFunc, flow, "external", author, bn, fakeId, traceid, ts, addrMap) 256 257 } else { 258 return errors.New("Unknown reward type" + trace.Action.RewardType) 259 } 260 261 } else if trace.TraceType == "suicide" { 262 // add the contract that died, and where it sent it's money 263 refundAddress := trace.Action.RefundAddress.Hex() 264 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "refund"), refundAddress, bn, txid, traceid, ts, addrMap) 265 266 address := trace.Action.Address.Hex() 267 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "self-destruct"), address, bn, txid, traceid, ts, addrMap) 268 269 } else if trace.TraceType == "create" { 270 if trace.Result != nil { 271 // may be both...record the self-destruct instead of the creation since we can only report on one 272 address := trace.Result.Address.Hex() 273 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "self-destruct"), address, bn, txid, traceid, ts, addrMap) 274 } 275 276 // If it's a top level trace, then the call data is the init, 277 // so to match with TrueBlocks, we just parse init 278 if len(trace.TraceAddress) == 0 { 279 if len(trace.Action.Init) > 10 { 280 initData := trace.Action.Init[10:] 281 for i := 0; i < len(initData)/64; i++ { 282 str := string(initData[i*64 : (i+1)*64]) 283 if IsImplicitAddress(str) { 284 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "code"), str, bn, txid, traceid, ts, addrMap) 285 } 286 } 287 } 288 } 289 290 // Handle contract creations that may have errored out 291 if trace.Action.To.IsZero() { 292 if trace.Result != nil && trace.Result.Address.IsZero() { 293 if trace.Error != "" { 294 if receipt, err := conn.GetReceiptNoTimestamp(bn, txid); err == nil { 295 address := receipt.ContractAddress.Hex() 296 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "self-destruct"), address, bn, txid, traceid, ts, addrMap) 297 } 298 } 299 } 300 } 301 302 } else { 303 if len(trace.TraceType) > 0 && trace.BlockNumber != 0 { 304 logger.Warn(fmt.Sprintf("Unknown trace type %s for trace: %d.%d.%d", trace.TraceType, trace.BlockNumber, trace.TransactionIndex, trace.TraceIndex)) 305 } 306 return err 307 } 308 309 // Try to get addresses from the input data 310 if len(trace.Action.Input) > 10 { 311 inputData := trace.Action.Input[10:] 312 for i := 0; i < len(inputData)/64; i++ { 313 str := string(inputData[i*64 : (i+1)*64]) 314 if IsImplicitAddress(str) { 315 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "input"), str, bn, txid, traceid, ts, addrMap) 316 } 317 } 318 } 319 320 // Parse output of trace 321 if trace.Result != nil && len(trace.Result.Output) > 2 { 322 outputData := trace.Result.Output[2:] 323 for i := 0; i < len(outputData)/64; i++ { 324 str := string(outputData[i*64 : (i+1)*64]) 325 if IsImplicitAddress(str) { 326 streamAppearance(procFunc, flow, traceReason(traceid, &trace, "output"), str, bn, txid, traceid, ts, addrMap) 327 } 328 } 329 } 330 } 331 332 return 333 } 334 335 var mapSync2 sync.Mutex 336 337 // streamAppearance streams an appearance to the model channel if we've not seen this appearance before. We 338 // keep track of appearances we've seen with `appsMap`. 339 func streamAppearance(procFunc UniqProcFunc, flow string, reason string, address string, bn base.Blknum, txid base.Txnum, traceid base.Tracenum, ts base.Timestamp, addrMap AddressBooleanMap) { 340 if base.IsPrecompile(address) { 341 return 342 } 343 344 if len(flow) > 0 { 345 switch flow { 346 case "from": 347 if !strings.Contains(reason, "from") { 348 return 349 } 350 case "to": 351 test := strings.Replace(reason, "topic", "", -1) 352 test = strings.Replace(test, "generator", "", -1) 353 if !strings.Contains(test, "to") { 354 return 355 } 356 case "reward": 357 if !strings.Contains(reason, "miner") && !strings.Contains(reason, "uncle") { 358 return 359 } 360 default: 361 logger.Error("Unknown flow:", flow) 362 } 363 } 364 365 // Normalize implicit strings. (Implicit strings come in 32-bytes long with no leading `0x`.) 366 if !strings.HasPrefix(address, "0x") { 367 addr := base.HexToAddress("0x" + address) 368 address = addr.Hex() 369 } 370 371 key := fmt.Sprintf("%s\t%09d\t%05d", address, bn, txid) 372 373 mapSync2.Lock() 374 if !addrMap[key] { 375 addrMap[key] = true 376 mapSync2.Unlock() 377 378 s := &types.Appearance{ 379 Address: base.HexToAddress(address), 380 BlockNumber: uint32(bn), 381 TransactionIndex: uint32(txid), 382 Reason: reason, 383 Timestamp: ts, 384 } 385 386 if traceid != base.NOPOSN { 387 s.TraceIndex = uint32(traceid) 388 } 389 390 if procFunc != nil { 391 _ = procFunc(s) 392 } 393 } else { 394 mapSync2.Unlock() 395 } 396 }