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  }