implemented generation of network map of address history, not finished

This commit is contained in:
arnaucode
2017-07-27 13:57:05 +02:00
parent 568bd6af46
commit f9262f72a1
12 changed files with 401 additions and 144 deletions

View File

@@ -3,13 +3,15 @@ package main
import ( import (
"fmt" "fmt"
"gopkg.in/mgo.v2/bson"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcrpcclient" "github.com/btcsuite/btcrpcclient"
"github.com/fatih/color"
) )
func explore(client *btcrpcclient.Client, blockHash string) { func explore(client *btcrpcclient.Client, blockHash string) {
var realBlocks int var realBlocks int
var nOrigin NodeModel var nOrigin NodeModel
nOrigin.Id = "origin" nOrigin.Id = "origin"
nOrigin.Label = "origin" nOrigin.Label = "origin"
@@ -26,116 +28,122 @@ func explore(client *btcrpcclient.Client, blockHash string) {
block, err := client.GetBlockVerbose(bh) block, err := client.GetBlockVerbose(bh)
check(err) check(err)
var newBlock BlockModel
newBlock.Hash = block.Hash
newBlock.Height = block.Height
newBlock.Confirmations = block.Confirmations
//get Fee value
th, err := chainhash.NewHashFromStr(block.Tx[0])
check(err)
tx, err := client.GetRawTransactionVerbose(th)
check(err)
var totalFee float64
for _, Vo := range tx.Vout {
totalFee = totalFee + Vo.Value
}
newBlock.Fee = totalFee
//for each Tx, get the Tx value
var totalAmount float64
/*inside each block, there are []Tx
inside each Tx, if is the Tx[0], is the Fees
in the Tx[n] where n>0, each Tx is independent,
and inside each Tx there are []Vout.
Usually Vout[0] is the real Tx value
and the Vout[1] is the rest of the amount in the original wallet.
Each Tx moves all the wallet amount, and the realTx amount is sent to the destination
and the rest of the wallet amount, is send to another owned wallet
*/
//if len(block.Tx) < 10 { //if len(block.Tx) < 10 {
for k, txHash := range block.Tx { for k, txHash := range block.Tx {
//first Tx is the Fee
//after first Tx is the Sent Amount
if k > 0 { if k > 0 {
realBlocks++
fmt.Print("Block Height: ")
fmt.Print(block.Height)
fmt.Print(", num of Tx: ")
fmt.Print(k)
fmt.Print("/")
fmt.Println(len(block.Tx) - 1)
th, err := chainhash.NewHashFromStr(txHash) th, err := chainhash.NewHashFromStr(txHash)
check(err) check(err)
tx, err := client.GetRawTransactionVerbose(th) tx, err := client.GetRawTransactionVerbose(th)
check(err) check(err)
var originAddress string
var originAddresses []string
var outputAddresses []string
for _, Vi := range tx.Vin { for _, Vi := range tx.Vin {
th, err := chainhash.NewHashFromStr(Vi.Txid) th, err := chainhash.NewHashFromStr(Vi.Txid)
check(err) check(err)
txVi, err := client.GetRawTransactionVerbose(th) txVi, err := client.GetRawTransactionVerbose(th)
check(err) check(err)
if len(txVi.Vout[0].ScriptPubKey.Addresses) > 0 { if len(txVi.Vout[Vi.Vout].ScriptPubKey.Addresses) > 0 {
originAddress = txVi.Vout[0].ScriptPubKey.Addresses[0] for _, originAddr := range txVi.Vout[Vi.Vout].ScriptPubKey.Addresses {
originAddresses = append(originAddresses, originAddr)
var n1 NodeModel
n1.Id = originAddr
n1.Label = originAddr
n1.Title = originAddr
n1.Group = string(block.Height)
n1.Value = 1
n1.Shape = "dot"
saveNode(nodeCollection, n1)
}
} else { } else {
originAddress = "origin" originAddresses = append(originAddresses, "origin")
} }
} }
for _, Vo := range tx.Vout { for _, Vo := range tx.Vout {
totalAmount = totalAmount + Vo.Value //if Vo.Value > 0 {
for _, outputAddr := range Vo.ScriptPubKey.Addresses {
var blockTx TxModel outputAddresses = append(outputAddresses, outputAddr)
blockTx.Txid = tx.Txid
blockTx.Amount = Vo.Value
blockTx.From = originAddress
blockTx.To = Vo.ScriptPubKey.Addresses[0]
newBlock.Tx = append(newBlock.Tx, blockTx)
}
}
}
if totalAmount > 0 {
newBlock.Amount = totalAmount
saveBlock(blockCollection, newBlock)
fmt.Print("Height: ")
fmt.Println(newBlock.Height)
fmt.Print("Amount: ")
fmt.Println(newBlock.Amount)
fmt.Print("Fee: ")
fmt.Println(newBlock.Fee)
fmt.Println("-----")
realBlocks++
}
//}
//set the next block
blockHash = block.NextHash
//analyze block creator
for _, t := range newBlock.Tx {
var n1 NodeModel
var n2 NodeModel var n2 NodeModel
n1.Id = t.From n2.Id = outputAddr
n1.Label = t.From n2.Label = outputAddr
n1.Title = t.From n2.Title = outputAddr
n1.Group = newBlock.Hash n2.Group = string(block.Height)
n1.Value = 1
n1.Shape = "dot"
n2.Id = t.To
n2.Label = t.To
n2.Title = t.To
n2.Group = newBlock.Hash
n2.Value = 1 n2.Value = 1
n2.Shape = "dot" n2.Shape = "dot"
var e EdgeModel
e.From = t.From
e.To = t.To
e.Label = t.Amount
e.Txid = t.Txid
e.Arrows = "to"
saveNode(nodeCollection, n1)
saveNode(nodeCollection, n2) saveNode(nodeCollection, n2)
for _, originAddr := range originAddresses {
var e EdgeModel
e.From = originAddr
e.To = outputAddr
e.Label = Vo.Value
e.Txid = tx.Txid
e.Arrows = "to"
e.BlockHeight = block.Height
saveEdge(edgeCollection, e) saveEdge(edgeCollection, e)
//fmt.Println(e)
}
}
//}
}
fmt.Print("originAddresses: ")
fmt.Println(len(originAddresses))
fmt.Print("outputAddresses: ")
fmt.Println(len(outputAddresses))
}
}
//}
//set the next block
blockHash = block.NextHash
} }
}
fmt.Print("realBlocks (blocks with Fee and Amount values): ") fmt.Print("realBlocks (blocks with Fee and Amount values): ")
fmt.Println(realBlocks) fmt.Println(realBlocks)
fmt.Println("reached the end of blockchain") fmt.Println("reached the end of blockchain")
} }
func addressTree(address string) NetworkModel {
var network NetworkModel
var currentEdge EdgeModel
currentEdge.From = "a"
currentEdge.To = "b"
for currentEdge.From != currentEdge.To {
color.Green("for")
fmt.Println(address)
//get edges before the address
edges := []EdgeModel{}
err := edgeCollection.Find(bson.M{"to": address}).All(&edges)
check(err)
for _, edge := range edges {
network.Edges = append(network.Edges, edge)
fmt.Println(edge)
}
//get all nodes from edges
for _, edge := range edges {
node := NodeModel{}
err := nodeCollection.Find(bson.M{"id": edge.From}).One(&node)
check(err)
if nodeInNodes(network.Nodes, node) == false {
network.Nodes = append(network.Nodes, node)
}
err = nodeCollection.Find(bson.M{"id": edge.To}).One(&node)
check(err)
if nodeInNodes(network.Nodes, node) == false {
network.Nodes = append(network.Nodes, node)
}
}
address = edges[0].From
currentEdge = edges[0]
}
return network
}

View File

@@ -55,6 +55,10 @@ func main() {
color.Blue("starting to explore blockchain") color.Blue("starting to explore blockchain")
explore(client, config.GenesisBlock) explore(client, config.GenesisBlock)
} }
/*if os.Args[1] == "-tree" {
color.Blue("starting to make tree")
addressTree(client, "fY3HZxu7HFKRcYzVSTXRZpAJMP4qba2oR6")
}*/
} }
// Get the current block count. // Get the current block count.

View File

@@ -30,6 +30,7 @@ type EdgeModel struct {
To string `json:"to"` To string `json:"to"`
Label float64 `json:"label"` Label float64 `json:"label"`
Arrows string `json:"arrows"` Arrows string `json:"arrows"`
BlockHeight int64 `json:"blockheight"`
} }
type NetworkModel struct { type NetworkModel struct {
Nodes []NodeModel `json:"nodes"` Nodes []NodeModel `json:"nodes"`

View File

@@ -66,7 +66,7 @@ func saveBlock(c *mgo.Collection, block BlockModel) {
func getAllNodes() ([]NodeModel, error) { func getAllNodes() ([]NodeModel, error) {
result := []NodeModel{} result := []NodeModel{}
iter := nodeCollection.Find(bson.M{}).Limit(100).Iter() iter := nodeCollection.Find(bson.M{}).Limit(10000).Iter()
err := iter.All(&result) err := iter.All(&result)
return result, err return result, err
} }
@@ -74,13 +74,13 @@ func getAllNodes() ([]NodeModel, error) {
func saveNode(c *mgo.Collection, node NodeModel) { func saveNode(c *mgo.Collection, node NodeModel) {
//first, check if the node already exists //first, check if the node already exists
result := NodeModel{} result := NodeModel{}
err := c.Find(bson.M{"id": node.Id}).One(&result) err := c.Find(bson.M{"id": node.Id, "group": node.Group}).One(&result)
if err != nil { if err != nil {
//node not found, so let's add a new entry //node not found, so let's add a new entry
err = c.Insert(node) err = c.Insert(node)
check(err) check(err)
} else { } else {
err = c.Update(bson.M{"id": node.Id}, &node) err = c.Update(bson.M{"id": node.Id, "group": node.Group}, &node)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -89,22 +89,31 @@ func saveNode(c *mgo.Collection, node NodeModel) {
func getAllEdges() ([]EdgeModel, error) { func getAllEdges() ([]EdgeModel, error) {
result := []EdgeModel{} result := []EdgeModel{}
iter := edgeCollection.Find(bson.M{}).Limit(100).Iter() iter := edgeCollection.Find(bson.M{}).Limit(10000).Iter()
err := iter.All(&result) err := iter.All(&result)
return result, err return result, err
} }
func saveEdge(c *mgo.Collection, edge EdgeModel) { func saveEdge(c *mgo.Collection, edge EdgeModel) {
//first, check if the edge already exists //first, check if the edge already exists
result := EdgeModel{} result := EdgeModel{}
err := c.Find(bson.M{"txid": edge.Txid}).One(&result) err := c.Find(bson.M{"txid": edge.Txid, "to": edge.To, "from": edge.From, "blockheight": edge.BlockHeight, "label": edge.Label}).One(&result)
if err != nil { if err != nil {
//edge not found, so let's add a new entry //edge not found, so let's add a new entry
err = c.Insert(edge) err = c.Insert(edge)
check(err) check(err)
} else { } else {
err = c.Update(bson.M{"txid": edge.Txid}, &edge) err = c.Update(bson.M{"txid": edge.Txid, "to": edge.To, "from": edge.From, "blockheight": edge.BlockHeight, "label": edge.Label}, &edge)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
} }
func nodeInNodes(nodes []NodeModel, node NodeModel) bool {
for _, n := range nodes {
if n.Id == node.Id {
return true
}
}
return false
}

View File

@@ -4,6 +4,10 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"gopkg.in/mgo.v2/bson"
"github.com/gorilla/mux"
) )
type Routes []Route type Routes []Route
@@ -15,13 +19,18 @@ var routes = Routes{
"/", "/",
Index, Index,
}, },
/* Route{ Route{
"Recommendations", "AllAddresses",
"GET", "Get",
"/r/{userid}/{nrec}", "/alladdresses",
Recommendations, AllAddresses,
},
Route{
"AddressNetwork",
"GET",
"/address/network/{address}",
AddressNetwork,
}, },
*/
Route{ Route{
"NetworkMap", "NetworkMap",
"Get", "Get",
@@ -60,6 +69,36 @@ func NewUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "new user added: ", newUser.ID) fmt.Fprintln(w, "new user added: ", newUser.ID)
} }
*/ */
func AllAddresses(w http.ResponseWriter, r *http.Request) {
ipFilter(w, r)
nodes := []NodeModel{}
iter := nodeCollection.Find(bson.M{}).Limit(10000).Iter()
err := iter.All(&nodes)
//convert []resp struct to json
jsonNodes, err := json.Marshal(nodes)
check(err)
fmt.Fprintln(w, string(jsonNodes))
}
func AddressNetwork(w http.ResponseWriter, r *http.Request) {
ipFilter(w, r)
vars := mux.Vars(r)
address := vars["address"]
if address == "undefined" {
fmt.Fprintln(w, "not valid address")
} else {
network := addressTree(address)
//convert []resp struct to json
jNetwork, err := json.Marshal(network)
check(err)
fmt.Fprintln(w, string(jNetwork))
}
}
func NetworkMap(w http.ResponseWriter, r *http.Request) { func NetworkMap(w http.ResponseWriter, r *http.Request) {
ipFilter(w, r) ipFilter(w, r)

View File

@@ -11,6 +11,7 @@ angular.module('webApp', [
'app.navbar', 'app.navbar',
'app.main', 'app.main',
'app.network', 'app.network',
'app.addressNetwork',
'app.sankey' 'app.sankey'
]). ]).
config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {

View File

@@ -21,7 +21,7 @@
--> -->
<style type="text/css"> <style type="text/css">
#mynetwork { #mynetwork {
background: black; /*background: black;*/
} }
.o-height600 { .o-height600 {
@@ -89,6 +89,7 @@
<script src="views/navbar.js"></script> <script src="views/navbar.js"></script>
<script src="views/main/main.js"></script> <script src="views/main/main.js"></script>
<script src="views/network/network.js"></script> <script src="views/network/network.js"></script>
<script src="views/addressNetwork/addressNetwork.js"></script>
<script src="views/sankey/sankey.js"></script> <script src="views/sankey/sankey.js"></script>
</body> </body>

View File

@@ -0,0 +1,29 @@
<div class="container">
<div class="row">
<div class="col-sm-3">
<div class="panel-heading c_blueGrey300">
<h3 class="panel-title">All addresses</h3>
</div>
<div class="panel-body" style="max-height: 500px;overflow-y: scroll;">
<div class="form-group label-floating">
<input ng-model="filterAddress" abmFormControl class="form-control" placeholder="Filter" type="text">
</div>
<div ng-click="getAddressNetwork(node)" class="list-group-item" ng-repeat="node in addresses | filter: filterAddress">
<div class="row-content">
<p class="list-group-item-text">{{node.id}}</p>
<!--<p class="list-group-item-text">Maecenas sed diam eget risus varius blandit.</p>-->
</div>
</div>
</div>
</div>
<div class="col-sm-9">
<div class="panel-heading c_blueGrey300">
<h3 class="panel-title">Network</h3>
</div>
<div class="panel-body">
<div id="mynetwork" style="height:800px;"></div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,104 @@
'use strict';
angular.module('app.addressNetwork', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/addressNetwork', {
templateUrl: 'views/addressNetwork/addressNetwork.html',
controller: 'AddressNetworkCtrl'
});
}])
.controller('AddressNetworkCtrl', function($scope, $http, $routeParams) {
$scope.data = [];
$scope.addresses;
$scope.nodes = [];
$scope.edges = [];
$scope.selectedNode = {};
var nodes, edges, container, network;
var options = {
layout: {
improvedLayout: false
},
interaction: {
hover: true
},
physics: {
stabilization: false,
//enabled: false
}
};
$scope.showMap = function() {
var nodes = $scope.nodes;
var edges = $scope.edges;
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
network = new vis.Network(container, data, options);
network.on("click", function(params) {
params.event = "[original event]";
//$scope.selectedNode = JSON.stringify(params, null, 4);
$scope.selectedNode = params;
console.log($scope.selectedNode);
console.log($scope.selectedNode.nodes);
var options = {
// position: {x:positionx,y:positiony}, // this is not relevant when focusing on nodes
scale: 1,
offset: {
x: 0,
y: 0
},
animation: {
duration: 500,
easingFunction: "easeInOutQuad"
}
};
network.focus($scope.selectedNode.nodes[0], options);
//console.log('click event, getNodeAt returns: ' + this.getNodeAt(params.pointer.DOM));
});
};
$http.get(urlapi + 'alladdresses')
.then(function(data, status, headers, config) {
console.log('data success');
console.log(data);
$scope.addresses = data.data;
}, function(data, status, headers, config) {
console.log('data error');
});
$scope.getAddressNetwork = function(address) {
console.log(address);
$http.get(urlapi + 'address/network/' + address.id)
.then(function(data, status, headers, config) {
console.log('data success');
console.log(data);
$scope.nodes = data.data.nodes;
$scope.edges = data.data.edges;
$scope.showMap();
}, function(data, status, headers, config) {
console.log('data error');
});
};
$scope.focusNode = function(node) {
var options = {
// position: {x:positionx,y:positiony}, // this is not relevant when focusing on nodes
scale: 1,
offset: {
x: 0,
y: 0
},
animation: {
duration: 500,
easingFunction: "easeInOutQuad"
}
};
network.focus(node.id, options);
};
});

View File

@@ -12,6 +12,7 @@
<div class="navbar-collapse collapse navbar-responsive-collapse"> <div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="active"><a href="#!/network">Network</a></li> <li class="active"><a href="#!/network">Network</a></li>
<li><a href="#!/addressNetwork">Address Network</a></li>
<li><a href="#!/sankey">Sankey diagram</a></li> <li><a href="#!/sankey">Sankey diagram</a></li>
<li><a href="javascript:void(0)">Timeline</a></li> <li><a href="javascript:void(0)">Timeline</a></li>
</ul> </ul>

View File

@@ -1,6 +1,26 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-3">
<div class="panel-heading c_blueGrey300">
<h3 class="panel-title">Nodes</h3>
</div>
<div class="panel-body" style="max-height: 300px;overflow-y: scroll;">
<div ng-click="focusNode(node)" class="list-group-item" ng-repeat="node in nodes">
<div class="row-content">
<p class="list-group-item-text">{{node.id}}</p>
<!--<p class="list-group-item-text">Maecenas sed diam eget risus varius blandit.</p>-->
</div>
</div>
</div>
<div class="panel-heading c_blueGrey300">
<h3 class="panel-title">Selected node</h3>
</div>
<div class="panel-body" style="max-height: 200px;overflow-y: scroll;">
{{selectedNode.nodes}}
</div>
</div>
<div class="col-sm-9">
<div class="panel-heading c_blueGrey300"> <div class="panel-heading c_blueGrey300">
<h3 class="panel-title">Network</h3> <h3 class="panel-title">Network</h3>
</div> </div>

View File

@@ -13,16 +13,19 @@ angular.module('app.network', ['ngRoute'])
$scope.data = []; $scope.data = [];
$scope.nodes = []; $scope.nodes = [];
$scope.edges = []; $scope.edges = [];
var nodes, edges, container; $scope.selectedNode = {};
var nodes, edges, container, network;
var options = { var options = {
layout: { layout: {
improvedLayout: false improvedLayout: false
} },
/*, interaction: {
hover: true
},
physics: { physics: {
//stabilization: false, stabilization: false,
//enabled: false //enabled: false
}*/ }
}; };
@@ -35,7 +38,28 @@ angular.module('app.network', ['ngRoute'])
nodes: nodes, nodes: nodes,
edges: edges edges: edges
}; };
var network = new vis.Network(container, data, options); network = new vis.Network(container, data, options);
network.on("click", function(params) {
params.event = "[original event]";
//$scope.selectedNode = JSON.stringify(params, null, 4);
$scope.selectedNode = params;
console.log($scope.selectedNode);
console.log($scope.selectedNode.nodes);
var options = {
// position: {x:positionx,y:positiony}, // this is not relevant when focusing on nodes
scale: 1,
offset: {
x: 0,
y: 0
},
animation: {
duration: 500,
easingFunction: "easeInOutQuad"
}
};
network.focus($scope.selectedNode.nodes[0], options);
//console.log('click event, getNodeAt returns: ' + this.getNodeAt(params.pointer.DOM));
});
}; };
$http.get(urlapi + 'map') $http.get(urlapi + 'map')
@@ -50,4 +74,20 @@ angular.module('app.network', ['ngRoute'])
console.log('data error'); console.log('data error');
}); });
$scope.focusNode = function(node) {
var options = {
// position: {x:positionx,y:positiony}, // this is not relevant when focusing on nodes
scale: 1,
offset: {
x: 0,
y: 0
},
animation: {
duration: 500,
easingFunction: "easeInOutQuad"
}
};
network.focus(node.id, options);
};
}); });