Initial commit
This commit is contained in:
commit
0af715b62b
|
@ -0,0 +1 @@
|
||||||
|
*.json
|
|
@ -0,0 +1,3 @@
|
||||||
|
simulator
|
||||||
|
network.json
|
||||||
|
propagation.json
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Graph Data Generator
|
||||||
|
---
|
||||||
|
|
||||||
|
Data generator generates different kinds of graph data, ready to use with this library.
|
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/divan/graph-experiments/graph"
|
||||||
|
gethlog "github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/status-im/simulator/simulation"
|
||||||
|
"github.com/status-im/simulator/simulation/naivep2p"
|
||||||
|
"github.com/status-im/simulator/simulation/whisperv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
simType = flag.String("type", "whisperv6", "Type of simulators (naivep2p, whisperv6)")
|
||||||
|
ttl = flag.Int("ttl", 10, "Message TTL for simulation")
|
||||||
|
naiveP2PN = flag.Int("naivep2p.N", 3, "Number of peers to propagate (0..N of peers)")
|
||||||
|
naiveP2PDelay = flag.Duration("naivep2p.delay", 10*time.Millisecond, "Delay for each step")
|
||||||
|
input = flag.String("i", "network.json", "Input filename for pregenerated data to be used with simulation")
|
||||||
|
output = flag.String("o", "propagation.json", "Output filename for p2p sending data")
|
||||||
|
ggethlogLevel = flag.String("loglevel", "crit", "Geth log level for whisper simulator (crti, error, warn, info, debug, trace)")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
data, err := graph.NewGraphFromJSON(*input)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Opening input file failed: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Create(*output)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Opening output file failed: ", err)
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
var sim simulation.Simulator
|
||||||
|
switch *simType {
|
||||||
|
case "naivep2p":
|
||||||
|
sim = naivep2p.NewSimulator(data, *naiveP2PN, *naiveP2PDelay)
|
||||||
|
case "whisperv6":
|
||||||
|
lvl, err := gethlog.LvlFromString(*ggethlogLevel)
|
||||||
|
if err != nil {
|
||||||
|
lvl = gethlog.LvlCrit
|
||||||
|
}
|
||||||
|
gethlog.Root().SetHandler(gethlog.LvlFilterHandler(lvl, gethlog.StreamHandler(os.Stderr, gethlog.TerminalFormat(true))))
|
||||||
|
sim = whisperv6.NewSimulator(data)
|
||||||
|
default:
|
||||||
|
log.Fatal("Unknown simulation type: ", *simType)
|
||||||
|
}
|
||||||
|
defer sim.Stop()
|
||||||
|
|
||||||
|
// Start simulation by sending single message
|
||||||
|
log.Printf("Starting message sending %s simulation for graph with %d nodes...", *simType, len(data.Nodes()))
|
||||||
|
sendData := sim.SendMessage(0, *ttl)
|
||||||
|
err = json.NewEncoder(fd).Encode(sendData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Printf("Written %s propagation data into %s", *simType, *output)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package simulations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recorder records send/receive events for
|
||||||
|
// generating further propagation log.
|
||||||
|
type Recorder struct {
|
||||||
|
Log []*LogEntry
|
||||||
|
|
||||||
|
start time.Time
|
||||||
|
nodeMap map[discover.NodeID]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecorder inits new recorder.
|
||||||
|
func NewRecorder(nodeMap map[discover.NodeID]int) *Recorder {
|
||||||
|
return &Recorder{
|
||||||
|
start: time.Now(),
|
||||||
|
nodeMap: nodeMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Recorder) Reset() {
|
||||||
|
r.start = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Recorder) Send(from, to discover.NodeID) {
|
||||||
|
fromIdx, ok := r.nodeMap[from]
|
||||||
|
if !ok {
|
||||||
|
panic("node not found")
|
||||||
|
}
|
||||||
|
toIdx, ok := r.nodeMap[to]
|
||||||
|
if !ok {
|
||||||
|
panic("node not found")
|
||||||
|
}
|
||||||
|
log.Error("NewLogEntry", "start", r.start, "from", fromIdx, "to", toIdx)
|
||||||
|
e := NewLogEntry(r.start, fromIdx, toIdx)
|
||||||
|
r.Log = append(r.Log, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Recorder) Receive(from, to discover.NodeID) {
|
||||||
|
log.Info("DidReceive", "from", from, "to", to)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package naivep2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/divan/graph-experiments/graph"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkIndex stores link information in form of indexes, rather than nodes IP.
|
||||||
|
type LinkIndex struct {
|
||||||
|
From int
|
||||||
|
To int
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrecalculatePeers creates map with peers indexes for faster lookup.
|
||||||
|
func PrecalculatePeers(data *graph.Graph) map[int][]int {
|
||||||
|
links := data.Links()
|
||||||
|
|
||||||
|
ret := make(map[int][]int)
|
||||||
|
for _, link := range links {
|
||||||
|
if link.From == link.To {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := ret[link.From]; !ok {
|
||||||
|
ret[link.From] = make([]int, 0)
|
||||||
|
}
|
||||||
|
if _, ok := ret[link.To]; !ok {
|
||||||
|
ret[link.To] = make([]int, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
peers := ret[link.From]
|
||||||
|
peers = append(peers, link.To)
|
||||||
|
ret[link.From] = peers
|
||||||
|
|
||||||
|
peers = ret[link.To]
|
||||||
|
peers = append(peers, link.From)
|
||||||
|
ret[link.To] = peers
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package naivep2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/status-im/simulator/simulation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogEntry defines the reporting log entry for one
|
||||||
|
// p2p message sending.
|
||||||
|
type LogEntry struct {
|
||||||
|
From int
|
||||||
|
To int
|
||||||
|
Ts time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Stringer interface for LogEntry.
|
||||||
|
func (l LogEntry) String() string {
|
||||||
|
return fmt.Sprintf("%s: %d -> %d", l.Ts.String(), l.From, l.To)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogEntry creates new log entry.
|
||||||
|
func NewLogEntry(start time.Time, from, to int) LogEntry {
|
||||||
|
return LogEntry{
|
||||||
|
Ts: time.Since(start) / time.Millisecond,
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogEntries2PropagationLog converts raw slice of LogEntries to PropagationLog,
|
||||||
|
// aggregating by timestamps and converting nodes indices to link indices.
|
||||||
|
// We expect that timestamps already bucketed into Nms groups.
|
||||||
|
func (s *Simulator) LogEntries2PropagationLog(entries []*LogEntry) *simulation.Log {
|
||||||
|
findLink := func(from, to int) int {
|
||||||
|
links := s.data.Links()
|
||||||
|
for i := range links {
|
||||||
|
if links[i].From == from && links[i].To == to ||
|
||||||
|
links[i].To == from && links[i].From == to {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
tss := make(map[time.Duration][]int)
|
||||||
|
tsnodes := make(map[time.Duration][]int)
|
||||||
|
for _, entry := range entries {
|
||||||
|
idx := findLink(entry.From, entry.To)
|
||||||
|
if idx == -1 {
|
||||||
|
log.Println("[EE] Wrong link", entry)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill links map
|
||||||
|
if _, ok := tss[entry.Ts]; !ok {
|
||||||
|
tss[entry.Ts] = make([]int, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
values := tss[entry.Ts]
|
||||||
|
values = append(values, idx)
|
||||||
|
tss[entry.Ts] = values
|
||||||
|
|
||||||
|
// fill tsnodes map
|
||||||
|
if _, ok := tsnodes[entry.Ts]; !ok {
|
||||||
|
tsnodes[entry.Ts] = make([]int, 0)
|
||||||
|
}
|
||||||
|
nnodes := tsnodes[entry.Ts]
|
||||||
|
nnodes = append(nnodes, entry.From, entry.To)
|
||||||
|
tsnodes[entry.Ts] = nnodes
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = &simulation.Log{
|
||||||
|
Timestamps: make([]int, 0, len(tss)),
|
||||||
|
Indices: make([][]int, 0, len(tss)),
|
||||||
|
Nodes: make([][]int, 0, len(tss)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for ts, links := range tss {
|
||||||
|
ret.Timestamps = append(ret.Timestamps, int(ts))
|
||||||
|
ret.Indices = append(ret.Indices, links)
|
||||||
|
ret.Nodes = append(ret.Nodes, tsnodes[ts])
|
||||||
|
fmt.Println("Adding", ts*time.Millisecond, int(ts), links, tsnodes[ts])
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package naivep2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/divan/graph-experiments/graph"
|
||||||
|
"github.com/status-im/simulator/simulation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simulator is responsible for running propagation simulation.
|
||||||
|
type Simulator struct {
|
||||||
|
data *graph.Graph
|
||||||
|
delay time.Duration
|
||||||
|
peers map[int][]int
|
||||||
|
nodesCh []chan Message
|
||||||
|
reportCh chan LogEntry
|
||||||
|
peersToSendTo int // number of peers to propagate message
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
simulationStart time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message represents the message propagated in the simulation.
|
||||||
|
type Message struct {
|
||||||
|
Content string
|
||||||
|
TTL int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimulator initializes new simulator for the given graph data.
|
||||||
|
func NewSimulator(data *graph.Graph, N int, delay time.Duration) *Simulator {
|
||||||
|
nodeCount := len(data.Nodes())
|
||||||
|
sim := &Simulator{
|
||||||
|
data: data,
|
||||||
|
delay: delay,
|
||||||
|
peers: PrecalculatePeers(data),
|
||||||
|
peersToSendTo: N,
|
||||||
|
reportCh: make(chan LogEntry),
|
||||||
|
nodesCh: make([]chan Message, nodeCount), // one channel per node
|
||||||
|
wg: new(sync.WaitGroup),
|
||||||
|
}
|
||||||
|
sim.wg.Add(nodeCount)
|
||||||
|
for i := 0; i < nodeCount; i++ {
|
||||||
|
ch := sim.startNode(i)
|
||||||
|
sim.nodesCh[i] = ch // this channel will be used to talk to node by index
|
||||||
|
}
|
||||||
|
return sim
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops simulator and frees all resources if any.
|
||||||
|
func (s *Simulator) Stop() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Simulator) SendMessage(startNodeIdx, ttl int) *simulation.Log {
|
||||||
|
message := Message{
|
||||||
|
Content: "dummy",
|
||||||
|
TTL: ttl,
|
||||||
|
}
|
||||||
|
s.simulationStart = time.Now()
|
||||||
|
s.propagateMessage(startNodeIdx, message)
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
s.wg.Wait()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
var ret []*LogEntry
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case val := <-s.reportCh:
|
||||||
|
ret = append(ret, &val)
|
||||||
|
case <-done:
|
||||||
|
return s.LogEntries2PropagationLog(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Simulator) startNode(i int) chan Message {
|
||||||
|
ch := make(chan Message)
|
||||||
|
go s.runNode(i, ch)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// runNode does actual node processing part
|
||||||
|
func (s *Simulator) runNode(i int, ch chan Message) {
|
||||||
|
defer s.wg.Done()
|
||||||
|
t := time.NewTimer(10 * time.Second)
|
||||||
|
|
||||||
|
cache := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case message := <-ch:
|
||||||
|
if cache[message.Content] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cache[message.Content] = true
|
||||||
|
message.TTL--
|
||||||
|
if message.TTL == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.propagateMessage(i, message)
|
||||||
|
case <-t.C:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// propagateMessage simulates message sending from node to its peers.
|
||||||
|
func (s *Simulator) propagateMessage(from int, message Message) {
|
||||||
|
time.Sleep(s.delay)
|
||||||
|
peers := s.peers[from]
|
||||||
|
for i := range peers {
|
||||||
|
go s.sendMessage(from, peers[i], message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendMessage simulates message sending for given from and to indexes.
|
||||||
|
func (s *Simulator) sendMessage(from, to int, message Message) {
|
||||||
|
s.nodesCh[to] <- message
|
||||||
|
s.reportCh <- NewLogEntry(s.simulationStart, from, to)
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package simulation
|
||||||
|
|
||||||
|
// Simulator defines the simulators for message propagation within the graph.
|
||||||
|
type Simulator interface {
|
||||||
|
SendMessage(idx, ttl int) *Log
|
||||||
|
Stop() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log represnts log of p2p message propagation
|
||||||
|
// with relative timestamps (starting from T0).
|
||||||
|
type Log struct {
|
||||||
|
Timestamps []int // timestamps in milliseconds starting from T0
|
||||||
|
Indices [][]int // indices of links for each step, len should be equal to len of Timestamps field
|
||||||
|
Nodes [][]int // indices of nodes involved in each step
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package whisperv6
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogEntry defines the reporting log entry for one
|
||||||
|
// p2p message sending.
|
||||||
|
type LogEntry struct {
|
||||||
|
From int
|
||||||
|
To int
|
||||||
|
Ts time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Stringer interface for LogEntry.
|
||||||
|
func (l LogEntry) String() string {
|
||||||
|
return fmt.Sprintf("%s: %d -> %d", l.Ts.String(), l.From, l.To)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogEntry creates new log entry.
|
||||||
|
func NewLogEntry(start time.Time, from, to int) *LogEntry {
|
||||||
|
return &LogEntry{
|
||||||
|
Ts: time.Since(start) / time.Millisecond,
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package whisperv6
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// const from github.com/ethereum/go-ethereum/whisper/whisperv5/doc.go
|
||||||
|
const (
|
||||||
|
aesKeyLength = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateMessage(ttl int, symkeyID string) *whisperv6.NewMessage {
|
||||||
|
// set all the parameters except p.Dst and p.Padding
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
rand.Read(buf)
|
||||||
|
sz := rand.Intn(400)
|
||||||
|
|
||||||
|
msg := &whisperv6.NewMessage{
|
||||||
|
PowTarget: 0.01,
|
||||||
|
PowTime: 1,
|
||||||
|
Payload: make([]byte, sz),
|
||||||
|
SymKeyID: symkeyID,
|
||||||
|
Topic: whisperv6.BytesToTopic(buf),
|
||||||
|
TTL: uint32(ttl),
|
||||||
|
}
|
||||||
|
rand.Read(msg.Payload)
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
|
@ -0,0 +1,283 @@
|
||||||
|
package whisperv6
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/divan/graph-experiments/graph"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||||
|
"github.com/status-im/simulator/simulation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simulator simulates WhisperV6 message propagation through the
|
||||||
|
// given p2p network.
|
||||||
|
type Simulator struct {
|
||||||
|
data *graph.Graph
|
||||||
|
network *simulations.Network
|
||||||
|
whispers map[discover.NodeID]*whisper.Whisper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSimulator intializes simulator for the given graph data.
|
||||||
|
func NewSimulator(data *graph.Graph) *Simulator {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
cfg := &whisper.Config{
|
||||||
|
MaxMessageSize: whisper.DefaultMaxMessageSize,
|
||||||
|
MinimumAcceptedPOW: 0.001,
|
||||||
|
}
|
||||||
|
|
||||||
|
whispers := make(map[discover.NodeID]*whisper.Whisper, len(data.Nodes()))
|
||||||
|
services := map[string]adapters.ServiceFunc{
|
||||||
|
"shh": func(ctx *adapters.ServiceContext) (node.Service, error) {
|
||||||
|
return whispers[ctx.Config.ID], nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
adapters.RegisterServices(services)
|
||||||
|
|
||||||
|
adapter := adapters.NewSimAdapter(services)
|
||||||
|
network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
|
||||||
|
DefaultService: "shh",
|
||||||
|
})
|
||||||
|
|
||||||
|
nodes := data.Nodes()
|
||||||
|
nodeCount := len(nodes)
|
||||||
|
sim := &Simulator{
|
||||||
|
data: data,
|
||||||
|
network: network,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Creating nodes...")
|
||||||
|
for i := 0; i < nodeCount; i++ {
|
||||||
|
node, err := sim.network.NewNodeWithConfig(nodeConfig(i))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// it's important to init whisper service here, as it
|
||||||
|
// be initialized for each peer
|
||||||
|
log.Println("Generating new whisper: ", node.ID())
|
||||||
|
service := whisper.New(cfg)
|
||||||
|
whispers[node.ID()] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Starting nodes...")
|
||||||
|
if err := network.StartAll(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribing to network events
|
||||||
|
events := make(chan *simulations.Event)
|
||||||
|
sub := sim.network.Events().Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Println("Connecting nodes...")
|
||||||
|
for _, link := range data.Links() {
|
||||||
|
node1 := sim.network.Nodes[link.From]
|
||||||
|
node2 := sim.network.Nodes[link.To]
|
||||||
|
// if connection already exists, skip it, as network.Connect will fail
|
||||||
|
if network.GetConn(node1.ID(), node2.ID()) != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := network.Connect(node1.ID(), node2.ID()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait for all nodes to establish connections
|
||||||
|
var connected int
|
||||||
|
var subErr error
|
||||||
|
for connected < len(data.Links()) && subErr == nil {
|
||||||
|
select {
|
||||||
|
case event := <-events:
|
||||||
|
if event.Type == simulations.EventTypeConn {
|
||||||
|
if event.Conn.Up {
|
||||||
|
fmt.Println("Got connection", event)
|
||||||
|
connected++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case e := <-sub.Err():
|
||||||
|
subErr = e
|
||||||
|
log.Fatal("Failed to connect nodes", subErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("All connections established")
|
||||||
|
|
||||||
|
return sim
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops simulator and frees all resources if any.
|
||||||
|
func (s *Simulator) Stop() error {
|
||||||
|
log.Println("Shutting down simulation nodes...")
|
||||||
|
s.network.Shutdown()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage sends single message and tracks propagation. Implements simulator.Interface.
|
||||||
|
func (s *Simulator) SendMessage(startNodeIdx, ttl int) *simulation.Log {
|
||||||
|
node := s.network.Nodes[startNodeIdx]
|
||||||
|
|
||||||
|
// the easiest way to send a message through the node is
|
||||||
|
// by using its public RPC methods - ssh_post.
|
||||||
|
client, err := node.Client()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed getting client", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(" Sending Whisper message from %s...\n", node.ID().String())
|
||||||
|
|
||||||
|
var symkeyID string
|
||||||
|
symKey := make([]byte, aesKeyLength)
|
||||||
|
rand.Read(symKey)
|
||||||
|
|
||||||
|
err = client.Call(&symkeyID, "shh_addSymKey", hexutil.Bytes(symKey))
|
||||||
|
|
||||||
|
// subscribing to network events
|
||||||
|
events := make(chan *simulations.Event)
|
||||||
|
sub := s.network.Events().Subscribe(events)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
msg := generateMessage(ttl, symkeyID)
|
||||||
|
var ignored bool
|
||||||
|
err = client.Call(&ignored, "shh_post", msg)
|
||||||
|
|
||||||
|
// pre-cache node indexes
|
||||||
|
var ncache = make(map[discover.NodeID]int)
|
||||||
|
for i := range s.network.Nodes {
|
||||||
|
ncache[s.network.Nodes[i].ID()] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
subErr error
|
||||||
|
done bool
|
||||||
|
count int
|
||||||
|
plog []*LogEntry
|
||||||
|
)
|
||||||
|
for subErr == nil && !done {
|
||||||
|
select {
|
||||||
|
case event := <-events:
|
||||||
|
if event.Type == simulations.EventTypeMsg {
|
||||||
|
msg := event.Msg
|
||||||
|
if msg.Code == 1 && msg.Protocol == "shh" && msg.Received == false {
|
||||||
|
from := ncache[msg.One]
|
||||||
|
to := ncache[msg.Other]
|
||||||
|
entry := NewLogEntry(start, from, to)
|
||||||
|
plog = append(plog, entry)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
if count == 0 {
|
||||||
|
done = true
|
||||||
|
} else {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
case e := <-sub.Err():
|
||||||
|
subErr = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subErr != nil {
|
||||||
|
log.Fatal("Failed to collect propagation info", subErr)
|
||||||
|
}
|
||||||
|
return s.LogEntries2PropagationLog(plog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogEntries2PropagationLog converts raw slice of LogEntries to PropagationLog,
|
||||||
|
// aggregating by timestamps and converting nodes indices to link indices.
|
||||||
|
// We expect that timestamps already bucketed into Nms groups.
|
||||||
|
func (s *Simulator) LogEntries2PropagationLog(entries []*LogEntry) *simulation.Log {
|
||||||
|
links := s.data.Links()
|
||||||
|
findLink := func(from, to int) int {
|
||||||
|
for i := range links {
|
||||||
|
if links[i].From == from && links[i].To == to ||
|
||||||
|
links[i].To == from && links[i].From == to {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
tss := make(map[time.Duration][]int)
|
||||||
|
tsnodes := make(map[time.Duration][]int)
|
||||||
|
for _, entry := range entries {
|
||||||
|
idx := findLink(entry.From, entry.To)
|
||||||
|
if idx == -1 {
|
||||||
|
log.Println("[EE] Wrong link", entry)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill links map
|
||||||
|
if _, ok := tss[entry.Ts]; !ok {
|
||||||
|
tss[entry.Ts] = make([]int, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
values := tss[entry.Ts]
|
||||||
|
values = append(values, idx)
|
||||||
|
tss[entry.Ts] = values
|
||||||
|
|
||||||
|
// fill tsnodes map
|
||||||
|
if _, ok := tsnodes[entry.Ts]; !ok {
|
||||||
|
tsnodes[entry.Ts] = make([]int, 0)
|
||||||
|
}
|
||||||
|
nnodes := tsnodes[entry.Ts]
|
||||||
|
nnodes = append(nnodes, entry.From, entry.To)
|
||||||
|
tsnodes[entry.Ts] = nnodes
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = &simulation.Log{
|
||||||
|
Timestamps: make([]int, 0, len(tss)),
|
||||||
|
Indices: make([][]int, 0, len(tss)),
|
||||||
|
Nodes: make([][]int, 0, len(tss)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for ts, links := range tss {
|
||||||
|
ret.Timestamps = append(ret.Timestamps, int(ts))
|
||||||
|
ret.Indices = append(ret.Indices, links)
|
||||||
|
ret.Nodes = append(ret.Nodes, tsnodes[ts])
|
||||||
|
fmt.Println("Adding", ts*time.Millisecond, int(ts), links, tsnodes[ts])
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeConfig generates config for simulated node with random key.
|
||||||
|
func nodeConfig(idx int) *adapters.NodeConfig {
|
||||||
|
key, err := crypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic("unable to generate key")
|
||||||
|
}
|
||||||
|
var id discover.NodeID
|
||||||
|
pubkey := crypto.FromECDSAPub(&key.PublicKey)
|
||||||
|
copy(id[:], pubkey[1:])
|
||||||
|
return &adapters.NodeConfig{
|
||||||
|
ID: id,
|
||||||
|
PrivateKey: key,
|
||||||
|
Name: nodeIdxToName(idx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeIdxToName(id int) string {
|
||||||
|
return fmt.Sprintf("Node %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findNode is a helper for finding node index by it's ID.
|
||||||
|
// TODO: remove this when links replaces into indexes.
|
||||||
|
func findNode(nodes []graph.Node, ID string) (int, error) {
|
||||||
|
for i := range nodes {
|
||||||
|
if nodes[i].ID() == ID {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, fmt.Errorf("Node with ID '%s' not found", ID)
|
||||||
|
}
|
Loading…
Reference in New Issue