Browse Source

added block, tx, address models and store them in mongodb. Joined config files to one config.json file

master
arnaucode 7 years ago
parent
commit
f88b6021e2
19 changed files with 270 additions and 120 deletions
  1. +35
    -0
      DevelopmentNotes.md
  2. +28
    -20
      README.md
  3. +2
    -2
      dateAnalysis.go
  4. +59
    -19
      exploreBlockchain.go
  5. BIN
      goBlockchainDataAnalysis
  6. +2
    -2
      ipFilter.go
  7. +14
    -6
      main.go
  8. +36
    -10
      mongoModels.go
  9. +36
    -22
      mongoOperations.go
  10. +49
    -7
      readConfig.go
  11. +0
    -0
      screenshots/goBlockchainDataAnalysis00.png
  12. +0
    -0
      screenshots/goBlockchainDataAnalysis01.png
  13. +0
    -0
      screenshots/goBlockchainDataAnalysis02.png
  14. +0
    -0
      screenshots/goBlockchainDataAnalysis04.png
  15. +0
    -0
      screenshots/goBlockchainDataAnalysis05.png
  16. +0
    -0
      screenshots/goBlockchainDataAnalysis06.gif
  17. +0
    -23
      serverConfig.go
  18. +7
    -7
      serverRoutes.go
  19. +2
    -2
      web/views/main/main.html

+ 35
- 0
DevelopmentNotes.md

@ -0,0 +1,35 @@
# Development Notes
## ToDo list
- Backend
- Network Address generation avoiding infinite relation loops
- Sankey Address generation without loops
- Frontend
- After Sankey visualization, go to Network Address visualization and render without Sankey dots
- Both
- Tx/day
- Tx volume
- Block size
- Blockchain size
other
- To get tx/hours of last 24 hours
Search for TxModel with DateF > last24h
Count for each hour
- To get tx/day of last month
Search TxModel with DateF > last month
Count each day
- Add counter with total blocks, total tx, total address
- store date hour, day, etc:
```Go
type DateModel struct {
Hour string `json:"hour"`
Day string `json:"day"`
Month string `json:"month"`
Amount float64 `json:"amount"`
BlockHash string `json:"blockhash"`
BlockHeight string `json:"blockheight"`
}
```

+ 28
- 20
README.md

@ -2,16 +2,9 @@
blockchain data analysis, written in Go
#### Not finished - ToDo list
- Backend
- Network Address generation avoiding infinite relation loops
- Sankey Address generation without loops
- Frontend
- After Sankey visualization, go to Network Address visualization and render without Sankey dots
- Both
- Tx/day
- Tx volume
- Block size
- Blockchain size
To Do list in DevelopmentNotes.md https://github.com/arnaucode/goBlockchainDataAnalysis/blob/master/DevelopmentNotes.md
## Instructions
### Install
1. Nodejs & NPM https://nodejs.org/ --> to get npm packages for the web
@ -22,7 +15,7 @@ blockchain data analysis, written in Go
### Configure
- Wallet /home/user/.faircoin2/faircoin.conf:
```
rpcuser=usernamerpc
rpcuser=faircoinrpc
rpcpassword=password
rpcport=3021
rpcworkqueue=2000
@ -34,12 +27,26 @@ rpcallowip=127.0.0.1
- goBlockchainDataAnalysis/config.json:
```json
{
"user": "usernamerpc",
"user": "faircoinrpc",
"pass": "password",
"host": "127.0.0.1",
"port": "3021",
"genesisTx": "7c27ade2c28e67ed3077f8f77b8ea6d36d4f5eba04c099be3c9faa9a4a04c046",
"genesisBlock": "beed44fa5e96150d95d56ebd5d2625781825a9407a5215dd7eda723373a0a1d7"
"genesisTx": "7c27ade2c28e67ed3077f8f77b8ea6d36d4f5eba04c099be3c9faa9a4a04c046",
"genesisBlock": "beed44fa5e96150d95d56ebd5d2625781825a9407a5215dd7eda723373a0a1d7",
"startFromBlock": 160,
"server": {
"serverIP": "127.0.0.1",
"serverPort": "3014",
"webServerPort": "8080",
"allowedIPs": [
"127.0.0.1"
],
"blockedIPs": []
},
"mongodb": {
"ip": "127.0.0.1",
"database": "goBlockchainDataAnalysis"
}
}
```
@ -87,18 +94,19 @@ Webapp will run on 127.0.0.1:8080
### Some screenshots
Some screenshots can be old, and can contain errors.
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/goBlockchainDataAnalysis00.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/screenshots/goBlockchainDataAnalysis00.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/goBlockchainDataAnalysis06.gif "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/screenshots/goBlockchainDataAnalysis06.gif "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/goBlockchainDataAnalysis05.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/screenshots/goBlockchainDataAnalysis05.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/goBlockchainDataAnalysis01.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/screenshots/goBlockchainDataAnalysis01.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/goBlockchainDataAnalysis02.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/screenshots/goBlockchainDataAnalysis02.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/goBlockchainDataAnalysis04.png "goBlockchainDataAnalysis")
![goBlockchainDataAnalysis](https://raw.githubusercontent.com/arnaucode/goBlockchainDataAnalysis/master/screenshots/goBlockchainDataAnalysis04.png "goBlockchainDataAnalysis")

+ 2
- 2
dateAnalysis.go

@ -24,8 +24,8 @@ func timeToDate(blockTime int64) string {
func hourAnalysis(e EdgeModel, blockTime int64) {
//fmt.Println(blockTime)
date := timeToDate(blockTime)
date_hour := strings.Split(date, " ")[1]
hour := strings.Split(date_hour, ":")[0]
dateHour := strings.Split(date, " ")[1]
hour := strings.Split(dateHour, ":")[0]
hourCount := HourCountModel{}
err := hourCountCollection.Find(bson.M{"hour": hour}).One(&hourCount)

+ 59
- 19
exploreBlockchain.go

@ -10,14 +10,14 @@ import (
func explore(client *btcrpcclient.Client, blockHash string) {
var realBlocks int
var nOrigin NodeModel
/*var nOrigin NodeModel
nOrigin.Id = "origin"
nOrigin.Label = "origin"
nOrigin.Title = "origin"
nOrigin.Group = "origin"
nOrigin.Value = 1
nOrigin.Shape = "dot"
saveNode(nodeCollection, nOrigin)
saveNode(nodeCollection, nOrigin)*/
for blockHash != "" {
//generate hash from string
@ -27,6 +27,17 @@ func explore(client *btcrpcclient.Client, blockHash string) {
check(err)
if block.Height > config.StartFromBlock {
var newBlock BlockModel
newBlock.Hash = block.Hash
newBlock.Confirmations = block.Confirmations
newBlock.Size = block.Size
newBlock.Height = block.Height
//newBlock.Amount = block.Amount
//newBlock.Fee = block.Fee
newBlock.PreviousHash = block.PreviousHash
newBlock.NextHash = block.NextHash
newBlock.Time = block.Time
for k, txHash := range block.Tx {
if k > 0 {
realBlocks++
@ -39,25 +50,24 @@ func explore(client *btcrpcclient.Client, blockHash string) {
th, err := chainhash.NewHashFromStr(txHash)
check(err)
tx, err := client.GetRawTransactionVerbose(th)
blockTx, err := client.GetRawTransactionVerbose(th)
check(err)
//save Tx
var nTx NodeModel
nTx.Id = txHash
nTx.Label = txHash
nTx.Title = txHash
nTx.Group = strconv.FormatInt(block.Height, 10)
nTx.Value = 1
nTx.Shape = "square"
nTx.Type = "tx"
saveNode(nodeCollection, nTx)
var nodeTx NodeModel
nodeTx.Id = txHash
nodeTx.Label = txHash
nodeTx.Title = txHash
nodeTx.Group = strconv.FormatInt(block.Height, 10)
nodeTx.Value = 1
nodeTx.Shape = "square"
nodeTx.Type = "tx"
saveNode(nodeCollection, nodeTx)
var originAddresses []string
var outputAddresses []string
var outputAmount []float64
for _, Vo := range tx.Vout {
//if Vo.Value > 0 {
for _, Vo := range blockTx.Vout {
for _, outputAddr := range Vo.ScriptPubKey.Addresses {
outputAddresses = append(outputAddresses, outputAddr)
outputAmount = append(outputAmount, Vo.Value)
@ -70,17 +80,36 @@ func explore(client *btcrpcclient.Client, blockHash string) {
n2.Shape = "dot"
n2.Type = "address"
saveNode(nodeCollection, n2)
//Address
var addr AddressModel
addr.Hash = outputAddr
addr.InBittrex = false
saveAddress(addr)
}
//}
}
for _, Vi := range tx.Vin {
for _, Vi := range blockTx.Vin {
th, err := chainhash.NewHashFromStr(Vi.Txid)
check(err)
txVi, err := client.GetRawTransactionVerbose(th)
check(err)
if len(txVi.Vout[Vi.Vout].ScriptPubKey.Addresses) > 0 {
//add tx to newBlock
newBlock.Tx = append(newBlock.Tx, blockTx.Txid)
//Tx save
var newTx TxModel
newTx.Hex = blockTx.Hex
newTx.Txid = blockTx.Txid
newTx.Hash = blockTx.Hash
newTx.BlockHash = block.Hash
newTx.BlockHeight = strconv.FormatInt(block.Height, 10)
newTx.Time = blockTx.Time
for _, originAddr := range txVi.Vout[Vi.Vout].ScriptPubKey.Addresses {
originAddresses = append(originAddresses, originAddr)
newTx.From = originAddr
var n1 NodeModel
n1.Id = originAddr
n1.Label = originAddr
@ -91,12 +120,18 @@ func explore(client *btcrpcclient.Client, blockHash string) {
n1.Type = "address"
saveNode(nodeCollection, n1)
//Address
var addr AddressModel
addr.Hash = originAddr
addr.InBittrex = false
saveAddress(addr)
for k, outputAddr := range outputAddresses {
var eIn EdgeModel
eIn.From = originAddr
eIn.To = txHash
eIn.Label = txVi.Vout[Vi.Vout].Value
eIn.Txid = tx.Txid
eIn.Txid = blockTx.Txid
eIn.Arrows = "to"
eIn.BlockHeight = block.Height
saveEdge(edgeCollection, eIn)
@ -105,7 +140,7 @@ func explore(client *btcrpcclient.Client, blockHash string) {
eOut.From = txHash
eOut.To = outputAddr
eOut.Label = outputAmount[k]
eOut.Txid = tx.Txid
eOut.Txid = blockTx.Txid
eOut.Arrows = "to"
eOut.BlockHeight = block.Height
saveEdge(edgeCollection, eOut)
@ -113,9 +148,13 @@ func explore(client *btcrpcclient.Client, blockHash string) {
//date analysis
//dateAnalysis(e, tx.Time)
//hour analysis
hourAnalysis(eIn, tx.Time)
hourAnalysis(eIn, blockTx.Time)
newTx.To = outputAddr
}
}
saveTx(newTx)
} else {
originAddresses = append(originAddresses, "origin")
}
@ -127,6 +166,7 @@ func explore(client *btcrpcclient.Client, blockHash string) {
fmt.Println(len(outputAddresses))
}
}
saveBlock(newBlock)
}
//set the next block
blockHash = block.NextHash

BIN
goBlockchainDataAnalysis


+ 2
- 2
ipFilter.go

@ -11,13 +11,13 @@ func ipFilter(w http.ResponseWriter, r *http.Request) {
var err error
fmt.Println(r.RemoteAddr)
reqIP := strings.Split(r.RemoteAddr, ":")[0]
for _, ip := range serverConfig.BlockedIPs {
for _, ip := range config.Server.BlockedIPs {
if reqIP == ip {
err = errors.New("ip not allowed to post images")
}
}
for _, ip := range serverConfig.AllowedIPs {
for _, ip := range config.Server.AllowedIPs {
if reqIP != ip {
err = errors.New("ip not allowed to post images")
}

+ 14
- 6
main.go

@ -4,6 +4,7 @@ import (
"log"
"net/http"
"os"
"time"
mgo "gopkg.in/mgo.v2"
@ -13,6 +14,8 @@ import (
)
var blockCollection *mgo.Collection
var txCollection *mgo.Collection
var addressCollection *mgo.Collection
var nodeCollection *mgo.Collection
var edgeCollection *mgo.Collection
var dateCountCollection *mgo.Collection
@ -24,10 +27,12 @@ func main() {
readConfig("config.json")
//connect with mongodb
readMongodbConfig("./mongodbConfig.json")
//readMongodbConfig("./mongodbConfig.json")
session, err := getSession()
check(err)
blockCollection = getCollection(session, "blocks")
txCollection = getCollection(session, "txs")
addressCollection = getCollection(session, "address")
nodeCollection = getCollection(session, "nodes")
edgeCollection = getCollection(session, "edges")
dateCountCollection = getCollection(session, "dateCounts")
@ -57,7 +62,10 @@ func main() {
log.Printf("%s: %s", label, amount)
}
color.Blue("starting to explore blockchain")
start := time.Now()
explore(client, config.GenesisBlock)
log.Println("blockchain exploration finished, time:")
log.Println(time.Since(start))
// Get the current block count.
blockCount, err := client.GetBlockCount()
@ -71,22 +79,22 @@ func main() {
go webserver()
//http server start
readServerConfig("./serverConfig.json")
//readServerConfig("./serverConfig.json")
log.Println("server running")
log.Print("port: ")
log.Println(serverConfig.ServerPort)
log.Println(config.Server.ServerPort)
router := NewRouter()
headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Access-Control-Allow-Origin"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
log.Fatal(http.ListenAndServe(":"+serverConfig.ServerPort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))
log.Fatal(http.ListenAndServe(":"+config.Server.ServerPort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))
//log.Fatal(http.ListenAndServe(":"+serverConfig.ServerPort, router))
}
func webserver() {
log.Println("webserver in port " + serverConfig.WebServerPort)
log.Println("webserver in port " + config.Server.WebServerPort)
http.Handle("/", http.FileServer(http.Dir("./web")))
http.ListenAndServe(":"+serverConfig.WebServerPort, nil)
http.ListenAndServe(":"+config.Server.WebServerPort, nil)
}

+ 36
- 10
mongoModels.go

@ -1,18 +1,44 @@
package main
type AddressModel struct {
Hash string `json:"hash"`
Amount float64 `json:"amount"`
InBittrex bool `json:"inbittrex"`
}
type DateModel struct {
Hour string `json:"hour"`
Day string `json:"day"`
Month string `json:"month"`
Amount float64 `json:"amount"`
BlockHash string `json:"blockhash"`
BlockHeight string `json:"blockheight"`
}
type TxModel struct {
Txid string
From string
To string
Amount float64
Hex string `json:"hex"`
Txid string `json:"txid"`
Hash string `json:"hash"`
From string `json:"from"` //hash of address
To string `json:"to"` //hash of address
Amount float64 `json:"amount"`
BlockHash string `json:"blockhash"`
BlockHeight string `json:"blockheight"`
Time int64 `json:"time"`
DateF string `json:"datef"` //date formated
Date DateModel
}
type BlockModel struct {
Hash string
Height int64
Confirmations uint64
Amount float64
Fee float64
Tx []TxModel
Hash string `json:"hash"`
Confirmations uint64 `json:"confirmations"`
Size int32 `json:"size"`
Height int64 `json:"height"`
//Amount float64 `json:"amount"`
//Fee float64 `json:"fee"`
Tx []string `json:"txid"` //txid of the TxModel
PreviousHash string `json:"previoushash"`
NextHash string `json:"nexthash"`
Time int64 `json:"time"`
DateF string `json:"datef"` //date formated
Date DateModel
}
type NodeModel struct {

+ 36
- 22
mongoOperations.go

@ -1,34 +1,14 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
//MongoConfig stores the configuration of mongodb to connect
type MongoConfig struct {
IP string `json:"ip"`
Database string `json:"database"`
}
var mongoConfig MongoConfig
func readMongodbConfig(path string) {
file, e := ioutil.ReadFile(path)
if e != nil {
fmt.Println("error:", e)
}
content := string(file)
json.Unmarshal([]byte(content), &mongoConfig)
}
func getSession() (*mgo.Session, error) {
session, err := mgo.Dial("mongodb://" + mongoConfig.IP)
session, err := mgo.Dial("mongodb://" + config.Mongodb.IP)
if err != nil {
panic(err)
}
@ -44,9 +24,11 @@ func getSession() (*mgo.Session, error) {
}
func getCollection(session *mgo.Session, collection string) *mgo.Collection {
c := session.DB(mongoConfig.Database).C(collection)
c := session.DB(config.Mongodb.Database).C(collection)
return c
}
/*
func saveBlock(c *mgo.Collection, block BlockModel) {
//first, check if the item already exists
result := BlockModel{}
@ -63,6 +45,7 @@ func saveBlock(c *mgo.Collection, block BlockModel) {
}
}
*/
func getAllNodes() ([]NodeModel, error) {
result := []NodeModel{}
@ -126,3 +109,34 @@ func edgeInEdges(edges []EdgeModel, edge EdgeModel) bool {
}
return false
}
func saveAddress(address AddressModel) {
result := AddressModel{}
err := addressCollection.Find(bson.M{"hash": address.Hash}).One(&result)
if err != nil {
//address not found, so let's add a new entry
err = addressCollection.Insert(address)
check(err)
}
}
func saveTx(tx TxModel) {
result := TxModel{}
err := txCollection.Find(bson.M{"txid": tx.Txid}).One(&result)
if err != nil {
//tx not found, so let's add a new entry
err = txCollection.Insert(tx)
check(err)
}
}
func saveBlock(block BlockModel) {
result := BlockModel{}
err := blockCollection.Find(bson.M{"hash": block.Hash}).One(&result)
if err != nil {
//block not found, so let's add a new entry
err = blockCollection.Insert(block)
check(err)
}
}

+ 49
- 7
readConfig.go

@ -5,14 +5,32 @@ import (
"io/ioutil"
)
//MongoConfig stores the configuration of mongodb to connect
type MongoConfig struct {
IP string `json:"ip"`
Database string `json:"database"`
}
//ServerConfig reads the server configuration
type ServerConfig struct {
ServerIP string `json:"serverIP"`
ServerPort string `json:"serverPort"`
WebServerPort string `json:"webserverPort"`
AllowedIPs []string `json:"allowedIPs"`
BlockedIPs []string `json:"blockedIPs"`
}
//Config reads the config
type Config struct {
User string `json:"user"`
Pass string `json:"pass"`
Host string `json:"host"`
Port string `json:"port"`
GenesisTx string `json:"genesisTx"`
GenesisBlock string `json:"genesisBlock"`
StartFromBlock int64 `json:"startFromBlock"`
User string `json:"user"`
Pass string `json:"pass"`
Host string `json:"host"`
Port string `json:"port"`
GenesisTx string `json:"genesisTx"`
GenesisBlock string `json:"genesisBlock"`
StartFromBlock int64 `json:"startFromBlock"`
Server ServerConfig `json:"server"`
Mongodb MongoConfig `json:"mongodb"`
}
var config Config
@ -23,3 +41,27 @@ func readConfig(path string) {
content := string(file)
json.Unmarshal([]byte(content), &config)
}
/*
var mongoConfig MongoConfig
func readMongodbConfig(path string) {
file, e := ioutil.ReadFile(path)
if e != nil {
fmt.Println("error:", e)
}
content := string(file)
json.Unmarshal([]byte(content), &mongoConfig)
}
var serverConfig ServerConfig
func readServerConfig(path string) {
file, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println("error: ", err)
}
content := string(file)
json.Unmarshal([]byte(content), &serverConfig)
}
*/

goBlockchainDataAnalysis00.png → screenshots/goBlockchainDataAnalysis00.png


goBlockchainDataAnalysis01.png → screenshots/goBlockchainDataAnalysis01.png


goBlockchainDataAnalysis02.png → screenshots/goBlockchainDataAnalysis02.png


goBlockchainDataAnalysis04.png → screenshots/goBlockchainDataAnalysis04.png


goBlockchainDataAnalysis05.png → screenshots/goBlockchainDataAnalysis05.png


goBlockchainDataAnalysis06.gif → screenshots/goBlockchainDataAnalysis06.gif


+ 0
- 23
serverConfig.go

@ -1,9 +1,6 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
@ -18,26 +15,6 @@ type Route struct {
HandlerFunc http.HandlerFunc
}
//server config
type ServerConfig struct {
ServerIP string `json:"serverIP"`
ServerPort string `json:"serverPort"`
WebServerPort string `json:"webserverPort"`
AllowedIPs []string `json:"allowedIPs"`
BlockedIPs []string `json:"blockedIPs"`
}
var serverConfig ServerConfig
func readServerConfig(path string) {
file, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println("error: ", err)
}
content := string(file)
json.Unmarshal([]byte(content), &serverConfig)
}
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

+ 7
- 7
serverRoutes.go

@ -102,25 +102,25 @@ func AllAddresses(w http.ResponseWriter, r *http.Request) {
func GetLastAddr(w http.ResponseWriter, r *http.Request) {
ipFilter(w, r)
nodes := []NodeModel{}
err := nodeCollection.Find(bson.M{}).Limit(10).Sort("-$natural").All(&nodes)
addresses := []AddressModel{}
err := addressCollection.Find(bson.M{}).Limit(10).Sort("-$natural").All(&addresses)
check(err)
//convert []resp struct to json
jNodes, err := json.Marshal(nodes)
jsonResp, err := json.Marshal(addresses)
check(err)
fmt.Fprintln(w, string(jNodes))
fmt.Fprintln(w, string(jsonResp))
}
func GetLastTx(w http.ResponseWriter, r *http.Request) {
ipFilter(w, r)
edges := []EdgeModel{}
err := edgeCollection.Find(bson.M{}).Limit(10).Sort("-$natural").All(&edges)
txs := []TxModel{}
err := txCollection.Find(bson.M{}).Limit(10).Sort("-$natural").All(&txs)
check(err)
//convert []resp struct to json
jsonData, err := json.Marshal(edges)
jsonData, err := json.Marshal(txs)
check(err)
fmt.Fprintln(w, string(jsonData))

+ 2
- 2
web/views/main/main.html

@ -5,9 +5,9 @@
<h3 class="panel-title">Last addresses used</h3>
</div>
<div class="panel-body" style="max-height: 300px;overflow-y: scroll;">
<div class="list-group-item" ng-repeat="node in addresses">
<div class="list-group-item" ng-repeat="address in addresses">
<div class="row-content">
<p class="list-group-item-text">{{node.id}}</p>
<p class="list-group-item-text">{{address.hash}}</p>
</div>
</div>
</div>

Loading…
Cancel
Save