Skip to content

Commit

Permalink
Auto scan chain to the specified depth
Browse files Browse the repository at this point in the history
  • Loading branch information
dmigwi committed Jan 1, 2019
1 parent aa0b9d7 commit e0696ef
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 57 deletions.
13 changes: 8 additions & 5 deletions analytics/analyticstypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,21 @@ 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"`
}

// 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.
Expand Down
136 changes: 103 additions & 33 deletions analytics/chains.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -34,70 +54,120 @@ 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
}

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
}
27 changes: 9 additions & 18 deletions explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion rpcutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e0696ef

Please sign in to comment.