From e0696efb51f281eb49a859d48594464f60c02e95 Mon Sep 17 00:00:00 2001 From: migwi Date: Mon, 31 Dec 2018 23:09:54 +0300 Subject: [PATCH] Auto scan chain to the specified depth --- analytics/analyticstypes.go | 13 ++-- analytics/chains.go | 136 +++++++++++++++++++++++++++--------- explorer.go | 27 +++---- rpcutils/utils.go | 2 +- 4 files changed, 121 insertions(+), 57 deletions(-) diff --git a/analytics/analyticstypes.go b/analytics/analyticstypes.go index 4f2a1ca..0a97ecd 100644 --- a/analytics/analyticstypes.go +++ b/analytics/analyticstypes.go @@ -33,10 +33,10 @@ type Node struct { // output TxHash to back in time where the source of funds can be identified. type Hub struct { // Unique details of the current output. - Address string - Amount float64 - TxHash string - OutTxIndex uint32 + Address string + Amount float64 + TxHash string + Probability float64 `json:",omitempty"` // Linked funds flow input(s). Matched []Set `json:",omitempty"` @@ -44,7 +44,10 @@ type Hub struct { // Set defines a group or individual inputs that can be correctly linked to an // output as their source of funds. -type Set []*Hub +type Set struct { + Inputs []*Hub + PercentOfInputs float64 +} // GroupedValues clusters together values as duplicates or other grouped values. // It holds the total sum and the list of the duplicates/grouped values. diff --git a/analytics/chains.go b/analytics/chains.go index e61c43d..f426d5d 100644 --- a/analytics/chains.go +++ b/analytics/chains.go @@ -14,15 +14,35 @@ import ( // processed transaction data. func RetrieveTxData(client *rpcclient.Client, txHash string) ( data *rpcutils.Transaction, err error) { + log.Infof("Retrieving data for transaction: %s", txHash) + if txHash == "" { + return &rpcutils.Transaction{}, nil + } txData, err := rpcutils.GetTransactionVerboseByID(client, txHash) if err != nil { - return nil, fmt.Errorf("failed to fetch transaction %s", txHash) + return nil, fmt.Errorf("RetrieveTxData: failed to fetch transaction %s", txHash) } return rpcutils.ExtractRawTxTransaction(txData), nil } +// RetrieveTxProbability returns the tx level probability values for each output. +func RetrieveTxProbability(client *rpcclient.Client, txHash string) ( + []*FlowProbability, *rpcutils.Transaction, error) { + tx, err := RetrieveTxData(client, txHash) + if err != nil { + return nil, nil, err + } + + rawSolution, inputs, outputs, err := TransactionFundsFlow(tx) + if err != nil { + return nil, nil, err + } + + return TxFundsFlowProbability(rawSolution, inputs, outputs), tx, nil +} + // ChainDiscovery returns all the possible chains associated with the tx hash used. func ChainDiscovery(client *rpcclient.Client, txHash string) ([]*Hub, error) { tx, err := RetrieveTxData(client, txHash) @@ -34,14 +54,22 @@ func ChainDiscovery(client *rpcclient.Client, txHash string) ([]*Hub, error) { // back in time when the source for each path can be identified. var hubsChain []*Hub + var depth = 3 + for _, val := range tx.Outpoints { + var setCount, hubCount, newDepth int + count := 1 + entry := &Hub{ + TxHash: tx.TxID, Amount: val.Value, Address: val.PkScriptData.Addresses[0], - TxHash: tx.TxID, } - err = entry.getDepth(client, 1) + hubCopy := entry + origHub := entry + + err = handleDepths(entry, hubCopy, origHub, client, count, depth, setCount, newDepth, hubCount) if err != nil { return nil, err } @@ -49,55 +77,97 @@ func ChainDiscovery(client *rpcclient.Client, txHash string) ([]*Hub, error) { hubsChain = append(hubsChain, entry) } + log.Info("Finished auto chain(s) discovery and appending all needed data") + return hubsChain, nil } -// getDepth appends all the sets linked to a given output after a given amount -// probability solution is resolved. -func (h *Hub) getDepth(client *rpcclient.Client, depth int) error { - for i := 0; i < depth; i++ { - tx, err := RetrieveTxData(client, h.TxHash) - if err != nil { - return err - } +func handleDepths(prevHub, curHub, origHub *Hub, client *rpcclient.Client, count, depth, setCount, newDepth, hubCount int) error { + err := curHub.getDepth(client) + if err != nil || count == depth { + return err + } - rawSolution, inputs, outputs, err := TransactionFundsFlow(tx) - if err != nil { - return err + if hubCount == len(prevHub.Matched[setCount].Inputs) { + hubCount = 0 + count++ + + if setCount+1 < len(prevHub.Matched) { + setCount++ + } else { + setCount = 0 + prevHub = origHub.Matched[setCount].Inputs[hubCount] } - probability := TxFundsFlowProbability(rawSolution, inputs, outputs) - for _, item := range probability { - if item.OutputAmount == h.Amount { - for _, entry := range item.ProbableInputs { - h.Matched = append(h.Matched, getSet(tx, entry)) + } + + curHub = prevHub.Matched[setCount].Inputs[hubCount] + + if len(prevHub.Matched) > 0 && hubCount < len(prevHub.Matched[setCount].Inputs) { + hubCount++ + } + + return handleDepths(prevHub, curHub, origHub, client, count, depth, setCount, newDepth, hubCount) +} + +// getDepth appends all the sets linked to a given output after a given amount +// probability solution is resolved. +func (h *Hub) getDepth(client *rpcclient.Client) error { + probability, tx, err := RetrieveTxProbability(client, h.TxHash) + if err != nil { + return err + } + + for _, item := range probability { + if item.OutputAmount == h.Amount { + for _, entry := range item.ProbableInputs { + d, err := getSet(client, tx, entry) + if err != nil { + return err } + + h.Probability = item.LinkingProbability + h.Matched = append(h.Matched, d) } } - } return nil } // The Set returned in a given output probability solution does not have a lot of // data, this functions reconstructs the Set adding the necessary information. -func getSet(txData *rpcutils.Transaction, matchedInputs *InputSets) (setData Set) { +func getSet(client *rpcclient.Client, txData *rpcutils.Transaction, + matchedInputs *InputSets) (set Set, err error) { + inputs := make([]rpcutils.TxInput, len(txData.Inpoints)) + copy(inputs, txData.Inpoints) + for _, item := range matchedInputs.Set { - for i := 0; i < item.Count; i++ { - for k, d := range txData.Inpoints { - if d.ValueIn == item.Amount { - setData = append(setData, &Hub{ - Amount: d.ValueIn, - OutTxIndex: d.OutputTxIndex, - TxHash: d.TxHash, - }) - - copy(txData.Inpoints[k:], txData.Inpoints[k+1:]) - txData.Inpoints = txData.Inpoints[:len(txData.Inpoints)-1] - break + // for i := 0; i < item.Count; i++ { + for k, d := range inputs { + if d.ValueIn == item.Amount { + s := &Hub{Amount: d.ValueIn, TxHash: d.TxHash} + + tx, err := RetrieveTxData(client, s.TxHash) + if err != nil { + return Set{}, err + } + + // Fetch the current hub's Address. + for k := range tx.Outpoints { + if d.OutputTxIndex == tx.Outpoints[k].TxIndex { + s.Address = tx.Outpoints[k].PkScriptData.Addresses[0] + } } + + set.Inputs = append(set.Inputs, s) + set.PercentOfInputs = matchedInputs.PercentOfInputs + + copy(inputs[k:], inputs[k+1:]) + inputs = inputs[:len(inputs)-1] + break } } + // } } return } diff --git a/explorer.go b/explorer.go index 339bbe7..8b76a7d 100644 --- a/explorer.go +++ b/explorer.go @@ -49,13 +49,19 @@ func (exp *explorer) StatusHandler(w http.ResponseWriter, r *http.Request, err e func (exp *explorer) AllTxSolutionsHandler(w http.ResponseWriter, r *http.Request) { transactionX := mux.Vars(r)["tx"] - d, _, _, err := exp.analyzeTx(transactionX) + txData, err := analytics.RetrieveTxData(exp.Client, transactionX) if err != nil { exp.StatusHandler(w, r, err) return } - strData, err := json.Marshal(d) + rawTxSolution, _, _, err := analytics.TransactionFundsFlow(txData) + if err != nil { + exp.StatusHandler(w, r, err) + return + } + + strData, err := json.Marshal(rawTxSolution) if err != nil { exp.StatusHandler(w, r, fmt.Errorf("error occured: %v", err)) return @@ -71,14 +77,12 @@ func (exp *explorer) AllTxSolutionsHandler(w http.ResponseWriter, r *http.Reques func (exp *explorer) TxProbabilityHandler(w http.ResponseWriter, r *http.Request) { transactionX := mux.Vars(r)["tx"] - d, inArr, outArr, err := exp.analyzeTx(transactionX) + solProbability, _, err := analytics.RetrieveTxProbability(exp.Client, transactionX) if err != nil { exp.StatusHandler(w, r, err) return } - solProbability := analytics.TxFundsFlowProbability(d, inArr, outArr) - strData, err := json.Marshal(solProbability) if err != nil { exp.StatusHandler(w, r, fmt.Errorf("error occured: %v", err)) @@ -112,19 +116,6 @@ func (exp *explorer) ChainHandler(w http.ResponseWriter, r *http.Request) { w.Write(strData) } -// analyzeTx fetches all the possible solutions available for the provided transaction. -func (exp *explorer) analyzeTx(transactionX string) ([]*analytics.AllFundsFlows, - []float64, []float64, error) { - log.Infof("Fetching transaction %s", transactionX) - - txData, err := analytics.RetrieveTxData(exp.Client, transactionX) - if err != nil { - return nil, nil, nil, err - } - - return analytics.TransactionFundsFlow(txData) -} - // PprofHandler fetches the correct pprof handler needed. func (exp *explorer) PprofHandler(w http.ResponseWriter, r *http.Request) { handlerType := mux.Vars(r)["name"] diff --git a/rpcutils/utils.go b/rpcutils/utils.go index b04ac75..8fbbaa6 100644 --- a/rpcutils/utils.go +++ b/rpcutils/utils.go @@ -121,7 +121,7 @@ func GetTransactionVerboseByID(client *rpcclient.Client, txid string) ( txraw, err := client.GetRawTransactionVerbose(txhash) if err != nil { - log.Errorf("GetRawTransactionVerbose failed for: %v", txhash) + log.Errorf("GetRawTransactionVerbose failed for: %v >>> %v", txhash, txid) return nil, err } return txraw, nil