swarm/network: event feed/websockets pss msg notification

Adds pub/sub of incoming Pss messages using event.Feed,
and a websockets interface for pss send and notifications
through persistent connection using rpc.notifier.

Websocket RPC are started automatically on the swarm executable
if the --pss flag is present. If more instances are to be run,
alternate ws ports can be specified with the --pssport flag.

Current implementation also includes a shallow builtin ping/pong handler
with topic "pss" and version 1, to simplify POCs and testing against pss.
This will eventually be removed, so no code should be made dependent on it.
This commit is contained in:
nolash 2017-03-20 13:05:12 +01:00 committed by Lewis Marshall
parent b865560130
commit d5daa9a323
8 changed files with 512 additions and 293 deletions

View file

@ -116,10 +116,15 @@ var (
SwarmUploadMimeType = cli.StringFlag{
Name: "mime",
Usage: "force mime type",
}
PssEnabledFlag = cli.BoolFlag{
Name: "pss",
Usage: "Enable pss (message passing over swarm)",
}
PssPortFlag = cli.IntFlag{
Name: "pssport",
Usage: fmt.Sprintf("Websockets port for pss (default %d)", node.DefaultWSPort),
}
CorsStringFlag = cli.StringFlag{
Name: "corsdomain",
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
@ -265,6 +270,7 @@ Cleans database of corrupted entries.
SwarmUploadMimeType,
// pss flags
PssEnabledFlag,
PssPortFlag,
}
app.Flags = append(app.Flags, debug.Flags...)
app.Before = func(ctx *cli.Context) error {
@ -300,12 +306,19 @@ func version(ctx *cli.Context) error {
func bzzd(ctx *cli.Context) error {
cfg := defaultNodeConfig
if ctx.GlobalIsSet(PssEnabledFlag.Name) {
cfg.WSHost = "127.0.0.1"
cfg.WSModules = []string{"eth","pss"}
cfg.WSOrigins = []string{"*"}
if ctx.GlobalIsSet(PssPortFlag.Name) {
cfg.WSPort = ctx.GlobalInt(PssPortFlag.Name)
}
}
utils.SetNodeConfig(ctx, &cfg)
stack, err := node.New(&cfg)
if err != nil {
utils.Fatalf("can't create node: %v", err)
}
registerBzzService(ctx, stack)
utils.StartNode(stack)

View file

@ -15,7 +15,7 @@ import (
type ProtocolSession struct {
TestNodeAdapter
Ids []*adapters.NodeId
Ids []*adapters.NodeId
ignore []uint64
}

View file

@ -1,54 +0,0 @@
package ws
import (
"net"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc"
)
// Server is the basic configuration needs for the HTTP server and also
// includes CORS settings.
type Server struct {
//Addr string
CorsString string
Endpoint string
}
// startWS initializes and starts the websocket RPC endpoint.
func StartWSServer(apis []rpc.API, server *Server) error {
// Generate the whitelist based on the allowed modules
/*whitelist := make(map[string]bool)
for _, module := range modules {
whitelist[module] = true
}*/
// Register all the APIs exposed by the services
handler := rpc.NewServer()
for _, api := range apis {
//if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
return err
}
glog.V(logger.Debug).Infof("WebSocket registered %T under '%s'", api.Service, api.Namespace)
//}
}
// All APIs registered, start the HTTP listener
var (
listener net.Listener
err error
)
if listener, err = net.Listen("tcp", server.Endpoint); err != nil {
return err
}
rpc.NewWSServer(server.CorsString, handler).Serve(listener)
glog.V(logger.Info).Infof("WebSocket endpoint opened: ws://%s", server.Endpoint)
// All listeners booted successfully
//n.wsEndpoint = endpoint
//n.wsListener = listener
//n.wsHandler = handler
return nil
}

View file

@ -1,62 +0,0 @@
package ws
import (
"context"
"testing"
"time"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/rpc"
)
func init() {
glog.SetV(logger.Detail)
glog.SetToStderr(true)
}
type TestResult struct {
Foo string `json:"foo"`
}
func TestStartWSServer(t *testing.T) {
ep := "localhost:8099"
server := &Server{
Endpoint: ep,
CorsString: "*",
}
apis := []rpc.API{
{
Namespace: "pss",
Version: "0.1",
Service: makeFakeAPIHandler(),
Public: true,
},
}
go func() {
err := StartWSServer(apis, server)
t.Logf("wsserver exited: %v", err)
}()
time.Sleep(time.Second)
client, err := rpc.DialWebsocket(context.Background(), "ws://" + ep, "ws://localhost")
if err != nil {
t.Fatalf("could not connect: %v", err)
} else {
t.Logf("client: %v", client)
client.Call(&TestResult{}, "pss_test")
}
}
func makeFakeAPIHandler() *FakeAPIHandler {
return &FakeAPIHandler{}
}
type FakeAPIHandler struct {
}
func (self *FakeAPIHandler) Test() {
glog.V(logger.Detail).Infof("in fakehandler Test()")
}

View file

@ -9,6 +9,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/adapters"
@ -27,6 +28,8 @@ const (
digestLength = 64
digestCapacity = 256
defaultDigestCacheTTL = time.Second
pingTopicName = "pss"
pingTopicVersion = 1
)
var (
@ -121,6 +124,7 @@ type Pss struct {
//peerPool map[pot.Address]map[PssTopic]*PssReadWriter // keep track of all virtual p2p.Peers we are currently speaking to
peerPool map[pot.Address]map[PssTopic]p2p.MsgReadWriter // keep track of all virtual p2p.Peers we are currently speaking to
handlers map[PssTopic]func([]byte, *p2p.Peer, []byte) error // topic and version based pss payload handlers
events map[PssTopic]*event.Feed // subscriptions for each topic
fwdcache map[pssDigest]pssCacheEntry // checksum of unique fields from pssmsg mapped to expiry, cache to determine whether to drop msg
cachettl time.Duration // how long to keep messages in fwdcache
hasher func(string) storage.Hasher // hasher to digest message to cache
@ -148,6 +152,7 @@ func NewPss(k Overlay, params *PssParams) *Pss {
//peerPool: make(map[pot.Address]map[PssTopic]*PssReadWriter, PssPeerCapacity),
peerPool: make(map[pot.Address]map[PssTopic]p2p.MsgReadWriter, PssPeerCapacity),
handlers: make(map[PssTopic]func([]byte, *p2p.Peer, []byte) error),
events: make(map[PssTopic]*event.Feed),
fwdcache: make(map[pssDigest]pssCacheEntry),
cachettl: params.Cachettl,
hasher: storage.MakeHashFunc,
@ -210,11 +215,24 @@ func (self *Pss) checkFwdCache(addr []byte, digest pssDigest) bool {
func (self *Pss) Register(topic PssTopic, handler func(msg []byte, p *p2p.Peer, from []byte) error) error {
self.lock.Lock()
defer self.lock.Unlock()
self.handlers[topic] = handler
self.handlers[topic] = func(msg []byte, p *p2p.Peer, from []byte) error {
self.alertSubscribers(&topic, msg)
return handler(msg, p, from)
}
self.registerFeed(topic)
return nil
}
// Retrieves the handler function registered by *Pss.Register()
func (self *Pss) Subscribe(topic *PssTopic, ch chan []byte) (event.Subscription, error) {
_, ok := self.events[*topic]
if !ok {
return nil, fmt.Errorf("No feed registered for topic %v", topic)
}
sub := self.events[*topic].Subscribe(ch)
log.Trace("new pss subscribe", "topic", topic, "sub", sub)
return sub, nil
}
func (self *Pss) GetHandler(topic PssTopic) func([]byte, *p2p.Peer, []byte) error {
self.lock.Lock()
defer self.lock.Unlock()
@ -263,6 +281,20 @@ func (self *Pss) isActive(id pot.Address, topic PssTopic) bool {
return true
}
func (self *Pss) registerFeed(topic PssTopic) {
self.events[topic] = &event.Feed{}
}
func (self *Pss) alertSubscribers(topic *PssTopic, msg []byte) error {
feed, ok := self.events[*topic]
if !ok {
return fmt.Errorf("No subscriptions registered for topic %v", topic)
}
numsent := feed.Send(msg)
log.Trace(fmt.Sprintf("pss sent to %d subscribers", numsent))
return nil
}
// Sends a message using pss. The message could be anything at all, and will be handled by whichever handler function is mapped to PssTopic using *Pss.Register()
//
// The to address is a swarm overlay address
@ -289,7 +321,7 @@ func (self *Pss) Send(to []byte, topic PssTopic, msg []byte) error {
// Handlers that want to pass on a message should call this directly
func (self *Pss) Forward(msg *PssMsg) error {
if self.isSelfRecipient(msg) {
if self.IsSelfRecipient(msg) {
return errorForwardToSelf
}
@ -326,7 +358,7 @@ func (self *Pss) Forward(msg *PssMsg) error {
return true
})
if sent == 0 {
log.Warn("PSS Was not able to send to any peers")
return fmt.Errorf("PSS Was not able to send to any peers")
} else {
self.addFwdCacheExpire(digest)
}
@ -436,13 +468,24 @@ func (self *PssProtocol) handle(msg []byte, p *p2p.Peer, senderAddr []byte) erro
return nil
}
func (ps *Pss) isSelfRecipient(msg *PssMsg) bool {
if bytes.Equal(msg.GetRecipient(), ps.Overlay.GetAddr().OverlayAddr()) {
func (self *Pss) IsSelfRecipient(msg *PssMsg) bool {
if bytes.Equal(msg.GetRecipient(), self.Overlay.GetAddr().OverlayAddr()) {
return true
}
return false
}
func (self *Pss) GetPingHandler() func([]byte, *p2p.Peer, []byte) error {
pingtopic, _ := MakeTopic(pingTopicName, pingTopicVersion)
return func(msg []byte, p *p2p.Peer, from []byte) error {
if bytes.Equal([]byte("ping"), msg) {
log.Trace(fmt.Sprintf("swarm pss ping from %x sending pong", common.ByteLabel(from)))
self.Send(from, pingtopic, []byte("pong"))
}
return nil
}
}
// Pre-Whisper placeholder
func makeMsg(code uint64, msg interface{}) ([]byte, error) {

View file

@ -5,6 +5,8 @@ import (
"encoding/hex"
"fmt"
"math/rand"
"net"
"net/http"
"os"
"strconv"
"testing"
@ -31,7 +33,7 @@ func init() {
// example protocol implementation peer
// message handlers are methods of this
// goal is that we can use the same for "normal" p2p.protocols operations aswell as pss
// channels allow receipt reporting from p2p.Protocol message handler
type pssTestPeer struct {
*protocols.Peer
hasProtocol bool
@ -41,6 +43,7 @@ type pssTestPeer struct {
// example node simulation peer
// modeled from swarm/network/simulations/discovery/discovery_test.go - commit 08b1e42f
// contains reporting channel for expect results so we can collect all async incoming msgs before deciding results
type pssTestNode struct {
*Hive
*Pss
@ -52,6 +55,8 @@ type pssTestNode struct {
run adapters.ProtoCall
ct *protocols.CodeMap
expectC chan []int
ws *http.Handler
apifunc func() []rpc.API
}
func (n *pssTestNode) Add(peer Peer) error {
@ -74,24 +79,14 @@ func (n *pssTestNode) Stop() error {
return nil
}
func (n *pssTestNode) connectPeer(s string) error {
return n.network.Connect(n.id, adapters.NewNodeIdFromHex(s))
}
func (n *pssTestNode) hiveKeepAlive() <-chan time.Time {
return time.Tick(time.Second * 10)
}
func (n *pssTestNode) triggerCheck() {
//go func() { n.trigger <- n.id }()
go func() { n.trigger <- adapters.NewNodeId(n.Addr()) }()
}
/*
func (n *pssTestNode) RunProtocol(id *adapters.NodeId, rw, rrw p2p.MsgReadWriter, peer *adapters.Peer) error {
return n.NodeAdapter.(adapters.ProtocolRunner).RunProtocol(id, rw, rrw, peer)
}
*/
func (n *pssTestNode) ProtoCall() adapters.ProtoCall {
return n.run
}
@ -102,18 +97,75 @@ func (n *pssTestNode) OverlayAddr() []byte {
func (n *pssTestNode) UnderlayAddr() []byte {
return n.id.Bytes()
//return n.Addr()
}
// the content of the msgs we're sending in the tests
type PssTestPayload struct {
type pssTestPayload struct {
Data string
}
func (m *PssTestPayload) String() string {
func (m *pssTestPayload) String() string {
return m.Data
}
type pssTestService struct {
node *pssTestNode // get addrs from this
msgFunc func(interface{}) error
}
func newPssTestService(t *testing.T, handlefunc func(interface{}) error, testnode *pssTestNode) *pssTestService {
hp := NewHiveParams()
//hp.CallInterval = 250
testnode.Hive = NewHive(hp, testnode.Pss.Overlay)
return &pssTestService{
//nid := adapters.NewNodeId(addr.UnderlayAddr())
msgFunc: handlefunc,
node: testnode,
}
}
func (self *pssTestService) Start(server p2p.Server) error {
self.node.SimNode = server.(*adapters.SimNode) // server is adapter.SimnNode now
return nil
}
func (self *pssTestService) Stop() error {
return nil
}
func (self *pssTestService) Protocols() []p2p.Protocol {
ct := BzzCodeMap()
for _, m := range DiscoveryMsgs {
ct.Register(m)
}
ct.Register(&PssMsg{})
srv := func(p Peer) error {
p.Register(&PssMsg{}, self.msgFunc)
self.node.Add(p)
p.DisconnectHook(func(err error) {
self.node.Remove(p)
})
return nil
}
proto := Bzz(self.node.OverlayAddr(), self.node.UnderlayAddr(), ct, srv, nil, nil)
return []p2p.Protocol{*proto}
}
func (self *pssTestService) APIs() []rpc.API {
return []rpc.API{
rpc.API{
Namespace: "eth",
Version: "0.1/pss",
Service: NewPssApi(self.node.Pss),
Public: true,
},
}
return nil
}
func TestPssCache(t *testing.T) {
var err error
to, _ := hex.DecodeString("08090a0b0c0d0e0f1011121314150001020304050607161718191a1b1c1d1e1f")
@ -266,7 +318,7 @@ func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes in
expectnodesids := []*adapters.NodeId{} // the nodes to expect on (needed by checker)
expectnodesresults := make(map[*adapters.NodeId][]int) // which messages expect actually got
vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &PssTestPayload{})
vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &pssTestPayload{})
topic, _ := MakeTopic(protocolName, protocolVersion)
trigger := make(chan *adapters.NodeId)
@ -367,10 +419,10 @@ func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes in
// send and monitor receive of pss
action = func(ctx context.Context) error {
code, _ := vct.GetCode(&PssTestPayload{})
code, _ := vct.GetCode(&pssTestPayload{})
for i := 0; i < len(sends); i += 2 {
msgbytes, _ := makeMsg(code, &PssTestPayload{
msgbytes, _ := makeMsg(code, &pssTestPayload{
Data: fmt.Sprintf("%v", i+1),
})
go func(i int, expectnodesresults map[*adapters.NodeId][]int) {
@ -464,8 +516,6 @@ func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes in
}
}
// pss simulation test
// (simnodes running protocols)
func TestPssFullLinearEcho(t *testing.T) {
var action func(ctx context.Context) error
@ -478,7 +528,7 @@ func TestPssFullLinearEcho(t *testing.T) {
var firstpssnode *adapters.NodeId
var secondpssnode *adapters.NodeId
vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &PssTestPayload{})
vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &pssTestPayload{})
topic, _ := MakeTopic(protocolName, protocolVersion)
fullnodes := []*adapters.NodeId{}
@ -598,8 +648,8 @@ func TestPssFullLinearEcho(t *testing.T) {
}
action = func(ctx context.Context) error {
code, _ := vct.GetCode(&PssTestPayload{})
msgbytes, _ := makeMsg(code, &PssTestPayload{
code, _ := vct.GetCode(&pssTestPayload{})
msgbytes, _ := makeMsg(code, &pssTestPayload{
Data: "ping",
})
@ -646,6 +696,234 @@ func TestPssFullLinearEcho(t *testing.T) {
t.Log("Simulation Passed:")
}
func TestPssFullWS(t *testing.T) {
// settings for ws servers
var srvsendep = "localhost:18546"
var srvrecvep = "localhost:18547"
var clientrecvok, clientsendok bool
var clientrecv, clientsend *rpc.Client
var action func(ctx context.Context) error
var check func(ctx context.Context, id *adapters.NodeId) (bool, error)
var ctx context.Context
var result *simulations.StepResult
var timeout time.Duration
var cancel context.CancelFunc
var firstpssnode, secondpssnode *adapters.NodeId
fullnodes := []*adapters.NodeId{}
vct := protocols.NewCodeMap(protocolName, protocolVersion, 65535, &pssTestPayload{})
topic, _ := MakeTopic(pingTopicName, pingTopicVersion)
trigger := make(chan *adapters.NodeId)
simnet := simulations.NewNetwork(&simulations.NetworkConfig{
Id: "0",
Backend: true,
})
testpeers := make(map[*adapters.NodeId]*pssTestPeer)
nodes := newPssSimulationTester(t, 3, 2, simnet, trigger, vct, protocolName, protocolVersion, testpeers)
ids := []*adapters.NodeId{} // ohh risky! but the action for a specific id should come before the expect anyway
action = func(ctx context.Context) error {
var thinnodeid *adapters.NodeId
for id, node := range nodes {
ids = append(ids, id)
if _, ok := testpeers[id]; ok {
log.Trace(fmt.Sprintf("adding fullnode %x to testpeers %p", common.ByteLabel(id.Bytes()), testpeers))
fullnodes = append(fullnodes, id)
node.Pss.Register(topic, node.Pss.GetPingHandler())
srv := rpc.NewServer()
for _, rpcapi := range node.apifunc() {
srv.RegisterName(rpcapi.Namespace, rpcapi.Service)
}
ws := srv.WebsocketHandler([]string{"*"})
node.ws = &ws
} else {
thinnodeid = id
}
}
if err := simnet.Connect(fullnodes[0], thinnodeid); err != nil {
return err
}
if err := simnet.Connect(thinnodeid, fullnodes[1]); err != nil {
return err
}
return nil
}
check = func(ctx context.Context, id *adapters.NodeId) (bool, error) {
select {
case <-ctx.Done():
return false, ctx.Err()
default:
}
node, ok := nodes[id]
if !ok {
return false, fmt.Errorf("unknown node: %s (%v)", id, node)
} else {
log.Trace(fmt.Sprintf("sim check ok node %v", id))
}
return true, nil
}
timeout = 10 * time.Second
ctx, cancel = context.WithTimeout(context.Background(), timeout)
result = simulations.NewSimulation(simnet).Run(ctx, &simulations.Step{
Action: action,
Trigger: trigger,
Expect: &simulations.Expectation{
Nodes: ids,
Check: check,
},
})
if result.Error != nil {
t.Fatalf("simulation failed: %s", result.Error)
}
cancel()
nonode := &adapters.NodeId{}
firstpssnode = nonode
secondpssnode = nonode
// first find a node that we're connected to
for firstpssnode == nonode {
log.Debug(fmt.Sprintf("Waiting for pss relaypeer for %x close to %x ...", common.ByteLabel(nodes[fullnodes[0]].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr())))
nodes[fullnodes[0]].Pss.Overlay.EachLivePeer(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool {
for _, id := range ids {
if id.NodeID == p.ID() {
firstpssnode = id
log.Debug(fmt.Sprintf("PSS relay found; relaynode %x", common.ByteLabel(nodes[firstpssnode].OverlayAddr())))
}
}
if firstpssnode == nonode {
return true
}
return false
})
if firstpssnode == nonode {
time.Sleep(time.Millisecond * 100)
}
}
// then find the node it's connected to
for secondpssnode == nonode {
log.Debug(fmt.Sprintf("PSS kademlia: Waiting for recipientpeer for %x close to %x ...", common.ByteLabel(nodes[firstpssnode].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr())))
nodes[firstpssnode].Pss.Overlay.EachLivePeer(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool {
for _, id := range ids {
if id.NodeID == p.ID() && id.NodeID != fullnodes[0].NodeID {
secondpssnode = id
log.Debug(fmt.Sprintf("PSS recipient found; relaynode %x", common.ByteLabel(nodes[secondpssnode].OverlayAddr())))
}
}
if secondpssnode == nonode {
return true
}
return false
})
if secondpssnode == nonode {
time.Sleep(time.Millisecond * 100)
}
}
srvrecvl, err := net.Listen("tcp", srvrecvep)
if err != nil {
t.Fatalf("Tcp (recv) on %s failed: %v", srvrecvep, err)
}
go func() {
err := http.Serve(srvrecvl, *nodes[fullnodes[1]].ws)
if err != nil {
t.Fatalf("http serve (recv) on %s failed: %v", srvrecvep, err)
}
}()
srvsendl, err := net.Listen("tcp", srvsendep)
if err != nil {
t.Fatalf("Tcp (send) on %s failed: %v", srvsendep, err)
}
go func() {
err := http.Serve(srvsendl, *nodes[fullnodes[0]].ws)
if err != nil {
t.Fatalf("http serve (send) on %s failed: %v", srvrecvep, err)
}
}()
for !clientrecvok {
log.Trace("attempting clientrecv connect")
clientrecv, err = rpc.DialWebsocket(context.Background(), "ws://"+srvrecvep, "ws://localhost")
if err == nil {
clientrecvok = true
} else {
log.Debug("clientrecv failed, retrying", "error", err)
time.Sleep(time.Millisecond * 250)
}
}
for !clientsendok {
log.Trace("attempting clientsend connect")
clientsend, err = rpc.DialWebsocket(context.Background(), "ws://"+srvsendep, "ws://localhost")
if err == nil {
clientsendok = true
} else {
log.Debug("clientsend failed, retrying", "error", err)
time.Sleep(time.Millisecond * 250)
}
}
trigger = make(chan *adapters.NodeId)
ch := make(chan string)
action = func(ctx context.Context) error {
go func() {
clientrecv.EthSubscribe(ctx, ch, "newMsg", topic)
clientsend.Call(nil, "eth_sendRaw", nodes[secondpssnode].Pss.Overlay.GetAddr().OverlayAddr(), topic, []byte("ping"))
trigger <- secondpssnode
}()
return nil
}
check = func(ctx context.Context, id *adapters.NodeId) (bool, error) {
select {
case <-ctx.Done():
return false, ctx.Err()
default:
}
select {
case msg := <-ch:
log.Trace(fmt.Sprintf("notify!: %v", msg))
case <-time.NewTimer(time.Second).C:
log.Trace(fmt.Sprintf("no notifies :'("))
}
// also need to know if the protocolpeer is set up
return true, nil
}
timeout = 10 * time.Second
ctx, cancel = context.WithTimeout(context.Background(), timeout)
defer cancel()
result = simulations.NewSimulation(simnet).Run(ctx, &simulations.Step{
Action: action,
Trigger: trigger,
Expect: &simulations.Expectation{
Nodes: []*adapters.NodeId{secondpssnode},
Check: check,
},
})
if result.Error != nil {
t.Fatalf("simulation failed: %s", result.Error)
}
t.Log("Simulation Passed:")
}
// test framework below
// numnodes: how many nodes to create
// pssnodeidx: on which node indices to start the pss
// net: the simulated network
@ -655,36 +933,51 @@ func TestPssFullLinearEcho(t *testing.T) {
// version: name for virtual protocol (and pss topic)
// testpeers: pss-specific peers, with hook needed for simulation event reporting
func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, net *simulations.Network, trigger chan *adapters.NodeId, vct *protocols.CodeMap, name string, version int, testpeers map[*adapters.NodeId]*pssTestPeer) map[*adapters.NodeId]*pssTestNode {
// the simulation tester constructor is currently a hack to fit previous code with later stack using node.Services to start SimNodes
func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, simnet *simulations.Network, trigger chan *adapters.NodeId, vct *protocols.CodeMap, name string, version int, testpeers map[*adapters.NodeId]*pssTestPeer) map[*adapters.NodeId]*pssTestNode {
topic, _ := MakeTopic(name, version)
nodes := make(map[*adapters.NodeId]*pssTestNode, numnodes)
//svcs := make(map[*adapters.NodeId]*pssTestService, numnodes)
psss := make(map[*adapters.NodeId]*Pss)
net.SetNaf(func(conf *simulations.NodeConfig) adapters.NodeAdapter {
simnet.SetNaf(func(conf *simulations.NodeConfig) adapters.NodeAdapter {
node := &pssTestNode{
Pss: psss[conf.Id],
Hive: nil,
SimNode: &adapters.SimNode{},
id: conf.Id,
network: net,
network: simnet,
trigger: trigger,
ct: vct,
apifunc: func() []rpc.API { return nil },
expectC: make(chan []int),
}
// set up handlers for the encapsulating PssMsg
var handlefunc func(interface{}) error
addr := NewPeerAddrFromNodeId(conf.Id)
if testpeers[conf.Id] != nil {
handlefunc = makePssHandleProtocol(psss[conf.Id])
log.Trace(fmt.Sprintf("Making full protocol id %x addr %x (testpeers %p)", common.ByteLabel(conf.Id.Bytes()), common.ByteLabel(addr.OverlayAddr()), testpeers))
} else {
handlefunc = makePssHandleForward(psss[conf.Id])
}
//node = newPssTester(t, psss[conf.Id], addr, 0, handlefunc, net, trigger)
// protocols are now registered by invoking node services
// since adapters.SimNode implements p2p.Server, needed for the services to start, we use this as a convenience wrapper
testservice := newPssTestService(t, handlefunc, node)
nodes[conf.Id] = testservice.node
svc := adapters.NewSimNode(conf.Id, testservice, net)
svc := adapters.NewSimNode(conf.Id, testservice, simnet)
testservice.Start(svc)
//svcs[conf.Id] = testservice
// the network sim wants a adapters.NodeAdapter, so we pass back to it a SimNode
// this is the SimNode member of the testNode initialized above, but assigned through the service start
// that is so say: node == testservice.node, but we access it as a member of testservice below for clarity (to the extent that this can be clear)
nodes[conf.Id] = testservice.node
testservice.node.apifunc = testservice.APIs
return node.SimNode
})
configs := make([]*simulations.NodeConfig, numnodes)
@ -708,106 +1001,15 @@ func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, net *s
psss[conf.Id].Register(topic, pssprotocol.GetHandler())
}
net.NewNodeWithConfig(conf)
if err := net.Start(conf.Id); err != nil {
simnet.NewNodeWithConfig(conf)
if err := simnet.Start(conf.Id); err != nil {
t.Fatalf("error starting node %s: %s", conf.Id.Label(), err)
}
}
return nodes
//return svcs
}
type pssTestService struct {
node *pssTestNode // get addrs from this
msgFunc func(interface{}) error
}
func newPssTestService(t *testing.T, handlefunc func(interface{}) error, testnode *pssTestNode) *pssTestService {
hp := NewHiveParams()
//hp.CallInterval = 250
testnode.Hive = NewHive(hp, testnode.Pss.Overlay)
return &pssTestService{
//nid := adapters.NewNodeId(addr.UnderlayAddr())
msgFunc: handlefunc,
node: testnode,
}
}
func (self *pssTestService) Start(server p2p.Server) error {
self.node.SimNode = server.(*adapters.SimNode) // server is adapter.SimnNode now
return nil
}
func (self *pssTestService) Stop() error {
return nil
}
func (self *pssTestService) Protocols() []p2p.Protocol {
ct := BzzCodeMap()
for _, m := range DiscoveryMsgs {
ct.Register(m)
}
ct.Register(&PssMsg{})
//Bzz(addr.OverlayAddr(), addr.UnderlayAddr(), ct, srv, nil, nil).Run
/*
node := &pssTestNode{
Hive: hive,
Pss: ps,
NodeAdapter: nil,
id: nid,
network: net,
trigger: trigger,
ct: ct,
expectC: make(chan []int),
}*/
srv := func(p Peer) error {
p.Register(&PssMsg{}, self.msgFunc)
self.node.Add(p)
p.DisconnectHook(func(err error) {
self.node.Remove(p)
})
return nil
}
proto := Bzz(self.node.OverlayAddr(), self.node.UnderlayAddr(), ct, srv, nil, nil)
return []p2p.Protocol{*proto}
}
func (self *pssTestService) APIs() []rpc.API {
return nil
}
/*
func newPssTester(t *testing.T, ps *Pss, addr *peerAddr, numsimnodes int, handlefunc func(interface{}) error, net *simulations.Network, trigger chan *adapters.NodeId) *pssTestNode {
// set up the outer protocol
srv := func(p Peer) error {
p.Register(&PssMsg{}, handlefunc)
node.Add(p)
p.DisconnectHook(func(err error) {
hive.Remove(p)
})
return nil
}
node.run = Bzz(addr.OverlayAddr(), addr.UnderlayAddr(), ct, srv, nil, nil).Run
nodeAdapter.Run = node.run
node.NodeAdapter = adapters.NewSimNode(nid, net)
return node
}
*/
func makePss(addr []byte) *Pss {
kp := NewKadParams()
kp.MinProxBinSize = 3
@ -816,7 +1018,7 @@ func makePss(addr []byte) *Pss {
overlay := NewKademlia(addr, kp)
ps := NewPss(overlay, pp)
overlay.Prune(time.Tick(time.Millisecond * 250))
//overlay.Prune(time.Tick(time.Millisecond * 250))
return ps
}
@ -827,7 +1029,7 @@ func makeCustomProtocol(name string, version int, ct *protocols.CodeMap, testpee
testpeer = &pssTestPeer{}
}
testpeer.Peer = p
p.Register(&PssTestPayload{}, testpeer.SimpleHandlePssPayload)
p.Register(&pssTestPayload{}, testpeer.SimpleHandlePssPayload)
err := p.Run()
return err
}
@ -836,7 +1038,7 @@ func makeCustomProtocol(name string, version int, ct *protocols.CodeMap, testpee
}
func makeFakeMsg(ps *Pss, ct *protocols.CodeMap, topic PssTopic, senderaddr PeerAddr, content string) PssMsg {
data := PssTestPayload{}
data := pssTestPayload{}
code, found := ct.GetCode(&data)
if !found {
return PssMsg{}
@ -868,7 +1070,7 @@ func makePssHandleForward(ps *Pss) func(msg interface{}) error {
// for the simple check it passes on the message if it's not for us
return func(msg interface{}) error {
pssmsg := msg.(*PssMsg)
if ps.isSelfRecipient(pssmsg) {
if ps.IsSelfRecipient(pssmsg) {
log.Trace("pss for us .. yay!")
} else {
log.Trace("passing on pss")
@ -882,7 +1084,7 @@ func makePssHandleProtocol(ps *Pss) func(msg interface{}) error {
return func(msg interface{}) error {
pssmsg := msg.(*PssMsg)
if ps.isSelfRecipient(pssmsg) {
if ps.IsSelfRecipient(pssmsg) {
log.Trace("pss for us ... let's process!")
env := pssmsg.Payload
umsg := env.Payload // this will be rlp encrypted
@ -905,21 +1107,21 @@ func makePssHandleProtocol(ps *Pss) func(msg interface{}) error {
// it comes in through
// Any pointer receiver that has protocols.Peer
func (ptp *pssTestPeer) SimpleHandlePssPayload(msg interface{}) error {
pmsg := msg.(*PssTestPayload)
log.Trace(fmt.Sprintf("PssTestPayloadhandler got message %v", pmsg))
pmsg := msg.(*pssTestPayload)
log.Trace(fmt.Sprintf("pssTestPayloadhandler got message %v", pmsg))
if pmsg.Data == "ping" {
pmsg.Data = "pong"
log.Trace(fmt.Sprintf("PssTestPayloadhandler reply %v", pmsg))
log.Trace(fmt.Sprintf("pssTestPayloadhandler reply %v", pmsg))
ptp.Send(pmsg)
} else if pmsg.Data == "pong" {
ptp.successC <- true
} else {
res, err := strconv.Atoi(pmsg.Data)
if err != nil {
log.Trace(fmt.Sprintf("PssTestPayloadhandlererr %v", err))
log.Trace(fmt.Sprintf("pssTestPayloadhandlererr %v", err))
ptp.successC <- false
} else {
log.Trace(fmt.Sprintf("PssTestPayloadhandler sending %d on chan", pmsg))
log.Trace(fmt.Sprintf("pssTestPayloadhandler sending %d on chan", pmsg))
ptp.successC <- true
ptp.resultC <- res
}

66
swarm/network/pssapi.go Normal file
View file

@ -0,0 +1,66 @@
package network
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
type PssApi struct {
*Pss
}
func NewPssApi(ps *Pss) *PssApi {
return &PssApi{Pss: ps}
}
func (self *PssApi) NewMsg(ctx context.Context, topic PssTopic) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return nil, fmt.Errorf("Subscribe not supported")
}
sub := notifier.CreateSubscription()
ch := make(chan []byte)
psssub, err := self.Pss.Subscribe(&topic, ch)
if err != nil {
return nil, fmt.Errorf("pss subscription topic %v (rpc sub id %v) failed: %v", topic, sub.ID, err)
}
go func(topic PssTopic) error {
for {
select {
case msg := <-ch:
if err := notifier.Notify(sub.ID, msg); err != nil {
log.Warn(fmt.Sprintf("notification on pss sub topic %v rpc (sub %v) msg %v failed!", topic, sub.ID, msg))
return err
}
case err := <-psssub.Err():
log.Warn(fmt.Sprintf("caught subscription error in pss sub topic: %v", topic, err))
return err
case <-notifier.Closed():
log.Warn(fmt.Sprintf("rpc sub notifier closed"))
psssub.Unsubscribe()
return nil
case err := <-sub.Err():
log.Warn(fmt.Sprintf("rpc sub closed: %v", err))
psssub.Unsubscribe()
return nil
}
}
return nil
}(topic)
return sub, nil
}
func (self *PssApi) SendRaw(to []byte, topic PssTopic, msg []byte) error {
err := self.Pss.Send(to, topic, msg)
if err != nil {
return fmt.Errorf("send error: %v", err)
}
return fmt.Errorf("ok sent")
}

View file

@ -33,7 +33,6 @@ import (
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/adapters"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/api"
httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
@ -47,10 +46,8 @@ type Swarm struct {
config *api.Config // swarm configuration
api *api.Api // high level api layer (fs/manifest)
dns api.Resolver // DNS registrar
//dbAccess *network.DbAccess // access to local chunk db iterator and storage counter
storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends
dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support
//depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage
cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud)
hive *network.Hive // the logistic manager
backend chequebook.Backend // simple blockchain Backend
@ -104,11 +101,7 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
return
}
// setup local store
//glog.V(logger.Debug).Infof("Set up local storage")
//self.dbAccess = network.NewDbAccess(self.lstore)
glog.V(logger.Debug).Infof("Set up local db access (iterator/counter)")
log.Debug("Set up local db access (iterator/counter)")
kp := network.NewKadParams()
@ -123,21 +116,14 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
)
log.Debug(fmt.Sprintf("Set up swarm network with Kademlia hive"))
// setup cloud storage backend
//cloud := network.NewForwarder(self.hive)
//glog.V(logger.Debug).Infof("-> set swarm forwarder as cloud storage backend")
// setup cloud storage internal access layer
self.storage = storage.NewNetStore(hash, self.lstore, nil, config.StoreParams)
glog.V(logger.Debug).Infof("-> swarm net store shared access layer to Swarm Chunk Store")
// set up Depo (storage handler = cloud storage access layer for incoming remote requests)
// self.depo = network.NewDepo(hash, self.lstore, self.storage)
// glog.V(logger.Debug).Infof("-> REmote Access to CHunks")
log.Debug("-> swarm net store shared access layer to Swarm Chunk Store")
// set up DPA, the cloud storage local access layer
dpaChunkStore := storage.NewDpaChunkStore(self.lstore, self.storage)
log.Debug(fmt.Sprintf("-> Local Access to Swarm"))
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams)
log.Debug(fmt.Sprintf("-> Content Store API"))
@ -156,6 +142,7 @@ func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.
log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar @ address %v", config.EnsRoot.Hex()))
self.api = api.NewApi(self.dpa, self.dns)
// Manifests for Smart Hosting
log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
@ -177,14 +164,6 @@ Start is called when the stack is started
*/
// implements the node.Service interface
func (self *Swarm) Start(net p2p.Server) error {
connectPeer := func(url string) error {
node, err := discover.ParseNode(url)
if err != nil {
return fmt.Errorf("invalid node URL: %v", err)
}
net.AddPeer(node)
return nil
}
// set chequebook
if self.swapEnabled {
ctx := context.Background() // The initial setup has no deadline.
@ -199,20 +178,24 @@ func (self *Swarm) Start(net p2p.Server) error {
log.Warn(fmt.Sprintf("Starting Swarm service"))
glog.V(logger.Warn).Infof("Starting Swarm service")
self.hive.Start(
connectPeer,
net,
func () <-chan time.Time{
return time.NewTicker(time.Second).C
},
)
log.Info(fmt.Sprintf("Swarm network started on bzz address: %v", self.hive.Addr()))
log.Info(fmt.Sprintf("Swarm network started on bzz address: %v", self.hive.GetAddr()))
if self.pssEnabled {
pssparams := network.NewPssParams()
self.pss = network.NewPss(self.hive.Overlay, pssparams)
glog.V(logger.Info).Infof("Pss started: %v", self.pss)
// for testing purposes, shold be removed in production environment!!
pingtopic, _ := network.MakeTopic("pss", 1)
self.pss.Register(pingtopic, self.pss.GetPingHandler())
log.Debug("Pss started: %v", self.pss)
}
self.dpa.Start()
@ -255,7 +238,6 @@ func (self *Swarm) Stop() error {
// implements the node.Service interface
func (self *Swarm) Protocols() []p2p.Protocol {
//proto, err := network.Bzz(self.depo, self.backend, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams, self.config.NetworkId)
ct := network.BzzCodeMap()
for _, m := range network.DiscoveryMsgs {
ct.Register(m)
@ -266,8 +248,26 @@ func (self *Swarm) Protocols() []p2p.Protocol {
srv := func(p network.Peer) error {
if self.pssEnabled {
//p.Register(&PssMsg{}, self.pssFunc)
glog.V(logger.Warn).Infof("pss is enabled, but handler not yet implemented - it won't work yet, sorry")
p.Register(&network.PssMsg{}, func(msg interface{}) error {
pssmsg := msg.(*network.PssMsg)
if self.pss.IsSelfRecipient(pssmsg) {
log.Trace("pss for us, yay! ... let's process!")
env := pssmsg.Payload
umsg := env.Payload
f := self.pss.GetHandler(env.Topic)
if f == nil {
return fmt.Errorf("No registered handler for topic '%s'", env.Topic)
}
nid := adapters.NewNodeId(env.SenderUAddr)
p := p2p.NewPeer(nid.NodeID, fmt.Sprintf("%x", common.ByteLabel(nid.Bytes())), []p2p.Cap{})
return f(umsg, p, env.SenderOAddr)
} else {
log.Trace("pss was for someone else :'( ... forwarding")
return self.pss.Forward(pssmsg)
}
return nil
})
}
self.hive.Add(p)
p.DisconnectHook(func(err error) {
@ -291,7 +291,8 @@ func (self *Swarm) Protocols() []p2p.Protocol {
// implements node.Service
// Apis returns the RPC Api descriptors the Swarm implementation offers
func (self *Swarm) APIs() []rpc.API {
return []rpc.API{
apis := []rpc.API{
// public APIs
{
Namespace: "bzz",
@ -334,6 +335,17 @@ func (self *Swarm) APIs() []rpc.API {
},
// {Namespace, Version, api.NewAdmin(self), false},
}
if self.pssEnabled {
apis = append(apis, rpc.API{
Namespace: "eth",
Version: "0.1/pss",
Service: network.NewPssApi(self.pss),
Public: true,
})
}
return apis
}
func (self *Swarm) Api() *api.Api {
@ -348,7 +360,6 @@ func (self *Swarm) SetChequebook(ctx context.Context) error {
}
log.Info(fmt.Sprintf("new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract.Hex()))
self.config.Save()
//self.hive.DropAll()
return nil
}