go-ethereum/swarm/pss/pss.go

583 lines
16 KiB
Go

package pss
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
"github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/pot"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/storage"
)
const (
DefaultTTL = 6000
TopicLength = 32
TopicResolverLength = 8
PssPeerCapacity = 256
PssPeerTopicDefaultCapacity = 8
digestLength = 32
digestCapacity = 256
defaultDigestCacheTTL = time.Second
)
var (
errorForwardToSelf = errors.New("forward to self")
)
// Defines params for Pss
type PssParams struct {
Cachettl time.Duration
}
// Initializes default params for Pss
func NewPssParams() *PssParams {
return &PssParams{
Cachettl: defaultDigestCacheTTL,
}
}
// Encapsulates the message transported over pss.
type PssMsg struct {
To []byte
Payload *PssEnvelope
}
// String representation of PssMsg
func (self *PssMsg) String() string {
return fmt.Sprintf("PssMsg: Recipient: %x", common.ByteLabel(self.To))
}
// Topic defines the context of a message being transported over pss
// It is used by pss to determine what action is to be taken on an incoming message
// Typically, one can map protocol handlers for the message payloads by mapping topic to them; see *Pss.Register()
type PssTopic [TopicLength]byte
func (self *PssTopic) String() string {
return fmt.Sprintf("%x", self)
}
// Pre-Whisper placeholder, payload of PssMsg
type PssEnvelope struct {
Topic PssTopic
TTL uint16
Payload []byte
From []byte
}
// creates Pss envelope from sender address, topic and raw payload
func NewPssEnvelope(addr []byte, topic PssTopic, payload []byte) *PssEnvelope {
return &PssEnvelope{
From: addr,
Topic: topic,
TTL: DefaultTTL,
Payload: payload,
}
}
func (msg *PssMsg) serialize() []byte {
rlpdata, _ := rlp.EncodeToBytes(msg)
/*buf := bytes.NewBuffer(nil)
buf.Write(self.PssEnvelope.Topic[:])
buf.Write(self.PssEnvelope.Payload)
buf.Write(self.PssEnvelope.From)
return buf.Bytes()*/
return rlpdata
}
var pssTransportProtocol = &protocols.Spec{
Name: "pss",
Version: 1,
MaxMsgSize: 10 * 1024 * 1024,
Messages: []interface{}{
PssMsg{},
},
}
// encapsulates a protocol msg as PssEnvelope data
type PssProtocolMsg struct {
Code uint64
Size uint32
Payload []byte
ReceivedAt time.Time
}
type pssCacheEntry struct {
expiresAt time.Time
receivedFrom []byte
}
type pssDigest [digestLength]byte
// Message handler func for a topic
type pssHandler func(msg []byte, p *p2p.Peer, from []byte) error
// pss provides sending messages to nodes without having to be directly connected to them.
//
// The messages are wrapped in a PssMsg structure and routed using the swarm kademlia routing.
//
// The top-level Pss object provides:
//
// - access to the swarm overlay and routing (kademlia)
// - a collection of remote overlay addresses mapped to MsgReadWriters, representing the virtually connected peers
// - a collection of remote underlay address, mapped to the overlay addresses above
// - a method to send a message to specific overlayaddr
// - a dispatcher lookup, mapping protocols to topics
// - a message cache to spot messages that previously have been forwarded
type Pss struct {
network.Overlay // we can get the overlayaddress from this
peerPool map[pot.Address]map[PssTopic]p2p.MsgReadWriter // keep track of all virtual p2p.Peers we are currently speaking to
handlers map[PssTopic]map[*pssHandler]bool // topic and version based pss payload handlers
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
lock sync.Mutex
dpa *storage.DPA
}
func (self *Pss) storeMsg(msg *PssMsg) (pssDigest, error) {
swg := &sync.WaitGroup{}
wwg := &sync.WaitGroup{}
buf := bytes.NewReader(msg.serialize())
key, err := self.dpa.Store(buf, int64(buf.Len()), swg, wwg)
if err != nil {
log.Warn("Could not store in swarm", "err", err)
return pssDigest{}, err
}
log.Trace("Stored msg in swarm", "key", key)
digest := pssDigest{}
copy(digest[:], key[:digestLength])
return digest, nil
}
// Creates a new Pss instance. A node should only need one of these
//
// TODO: error check overlay integrity
func NewPss(k network.Overlay, dpa *storage.DPA, params *PssParams) *Pss {
return &Pss{
Overlay: k,
peerPool: make(map[pot.Address]map[PssTopic]p2p.MsgReadWriter, PssPeerCapacity),
handlers: make(map[PssTopic]map[*pssHandler]bool),
fwdcache: make(map[pssDigest]pssCacheEntry),
cachettl: params.Cachettl,
dpa: dpa,
}
}
func (self *Pss) Start(srv *p2p.Server) error {
return nil
}
func (self *Pss) Stop() error {
return nil
}
func (self *Pss) Protocols() []p2p.Protocol {
return []p2p.Protocol{
p2p.Protocol{
Name: pssTransportProtocol.Name,
Version: pssTransportProtocol.Version,
Length: pssTransportProtocol.Length(),
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
pp := protocols.NewPeer(p, rw, pssTransportProtocol)
err := pp.Run(self.handlePssMsg)
log.Warn("pss protocol peer returned", "peer", p, "err", err)
return nil
},
},
}
}
func (self *Pss) APIs() []rpc.API {
return []rpc.API{
rpc.API {
Namespace: "pss",
Version: "0.1",
Service: NewPssAPI(self),
Public: true,
},
}
}
// Takes the generated PssTopic of a protocol/chatroom etc, and links a handler function to it
// This allows the implementer to retrieve the right handler functions (invoke the right protocol)
// for an incoming message by inspecting the topic on it.
// a topic allows for multiple handlers
// returns a deregister function which needs to be called to deregister the handler
// (similar to event.Subscription.Unsubscribe())
func (self *Pss) Register(topic PssTopic, handler pssHandler) func() {
self.lock.Lock()
defer self.lock.Unlock()
handlers := self.handlers[topic]
if handlers == nil {
handlers = make(map[*pssHandler]bool)
self.handlers[topic] = handlers
}
handlers[&handler] = true
return func() { self.deregister(topic, &handler) }
}
func (self *Pss) deregister(topic PssTopic, h *pssHandler) {
self.lock.Lock()
defer self.lock.Unlock()
handlers := self.handlers[topic]
if len(handlers) == 1 {
delete(self.handlers, topic)
return
}
delete(handlers, h)
}
// enables to set address of node, to avoid backwards forwarding
//
// currently not in use as forwarder address is not known in the handler function hooked to the pss dispatcher.
// it is included as a courtesy to custom transport layers that may want to implement this
func (self *Pss) AddToCache(addr []byte, msg *PssMsg) error {
//digest := self.hashMsg(msg)
digest, err := self.storeMsg(msg)
if err != nil {
return err
}
return self.addFwdCacheSender(addr, digest)
}
func (self *Pss) addFwdCacheSender(addr []byte, digest pssDigest) error {
self.lock.Lock()
defer self.lock.Unlock()
var entry pssCacheEntry
var ok bool
if entry, ok = self.fwdcache[digest]; !ok {
entry = pssCacheEntry{}
}
entry.receivedFrom = addr
self.fwdcache[digest] = entry
return nil
}
func (self *Pss) addFwdCacheExpire(digest pssDigest) error {
self.lock.Lock()
defer self.lock.Unlock()
var entry pssCacheEntry
var ok bool
if entry, ok = self.fwdcache[digest]; !ok {
entry = pssCacheEntry{}
}
entry.expiresAt = time.Now().Add(self.cachettl)
self.fwdcache[digest] = entry
return nil
}
func (self *Pss) checkFwdCache(addr []byte, digest pssDigest) bool {
self.lock.Lock()
defer self.lock.Unlock()
entry, ok := self.fwdcache[digest]
if ok {
if entry.expiresAt.After(time.Now()) {
log.Debug(fmt.Sprintf("unexpired cache for digest %x", digest))
return true
} else if entry.expiresAt.IsZero() && bytes.Equal(addr, entry.receivedFrom) {
log.Debug(fmt.Sprintf("sendermatch %x for digest %x", common.ByteLabel(addr), digest))
return true
}
}
return false
}
func (self *Pss) getHandlers(topic PssTopic) map[*pssHandler]bool {
self.lock.Lock()
defer self.lock.Unlock()
return self.handlers[topic]
}
//
func (self *Pss) handlePssMsg(msg interface{}) error {
pssmsg := msg.(*PssMsg)
if !self.isSelfRecipient(pssmsg) {
log.Trace("pss was for someone else :'( ... forwarding")
return self.Forward(pssmsg)
}
log.Trace("pss for us, yay! ... let's process!")
return self.Process(pssmsg)
}
// processes a message with self as recipient
func (self *Pss) Process(pssmsg *PssMsg) error {
env := pssmsg.Payload
payload := env.Payload
handlers := self.getHandlers(env.Topic)
if len(handlers) == 0 {
return fmt.Errorf("No registered handler for topic '%s'", env.Topic)
}
nid, _ := discover.HexID("0x00")
p := p2p.NewPeer(nid, fmt.Sprintf("%x", env.From), []p2p.Cap{})
for f := range handlers {
err := (*f)(payload, p, env.From)
if err != nil {
return err
}
}
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
func (self *Pss) Send(to []byte, topic PssTopic, msg []byte) error {
sender := self.Overlay.BaseAddr()
pssenv := NewPssEnvelope(sender, topic, msg)
pssmsg := &PssMsg{
To: to,
Payload: pssenv,
}
return self.Forward(pssmsg)
}
// Forwards a pss message to the peer(s) closest to the to address
//
// Handlers that want to pass on a message should call this directly
func (self *Pss) Forward(msg *PssMsg) error {
if self.isSelfRecipient(msg) {
return errorForwardToSelf
}
digest, err := self.storeMsg(msg)
if err != nil {
log.Warn(fmt.Sprintf("could not store message %v to cache: %v", msg, err))
}
if self.checkFwdCache(nil, digest) {
log.Trace(fmt.Sprintf("pss relay block-cache match: FROM %x TO %x", common.ByteLabel(self.Overlay.BaseAddr()), common.ByteLabel(msg.To)))
return nil
}
// TODO:check integrity of message
sent := 0
// send with kademlia
// find the closest peer to the recipient and attempt to send
self.Overlay.EachConn(msg.To, 256, func(op network.OverlayConn, po int, isproxbin bool) bool {
p, ok := op.(network.Peer)
if !ok {
return true
}
addr := self.Overlay.BaseAddr()
sendMsg := fmt.Sprintf("%x: msg to %x via %x", common.ByteLabel(addr), common.ByteLabel(msg.To), common.ByteLabel(p.Over()))
if self.checkFwdCache(p.Over(), digest) {
log.Info(fmt.Sprintf("%v: peer already forwarded to", sendMsg))
return true
}
err := p.Send(msg)
if err != nil {
log.Warn(fmt.Sprintf("%v: failed forwarding: %v", sendMsg, err))
return true
}
log.Trace(fmt.Sprintf("%v: successfully forwarded", sendMsg))
sent++
// if equality holds, p is always the first peer given in the iterator
if bytes.Equal(msg.To, p.Over()) || !isproxbin {
return false
}
log.Trace(fmt.Sprintf("%x is in proxbin, keep forwarding", common.ByteLabel(p.Over())))
return true
})
if sent == 0 {
log.Error("PSS: unable to forward to any peers")
return nil
}
self.addFwdCacheExpire(digest)
return nil
}
// Links a pss peer address and topic to a dedicated p2p.MsgReadWriter in the pss peerpool, and runs the specificed protocol on this p2p.MsgReadWriter and the specified peer
//
// The effect is that now we have a "virtual" protocol running on an artificial p2p.Peer, which can be looked up and piped to through Pss using swarm overlay address and topic
func (self *Pss) AddPeer(p *p2p.Peer, addr pot.Address, run adapters.RunProtocol, topic PssTopic, rw p2p.MsgReadWriter) error {
self.lock.Lock()
defer self.lock.Unlock()
self.addPeerTopic(addr, topic, rw)
go func() {
err := run(p, rw)
log.Warn(fmt.Sprintf("pss vprotocol quit on addr %v topic %v: %v", addr, topic, err))
self.removePeerTopic(rw, topic)
}()
return nil
}
func (self *Pss) addPeerTopic(id pot.Address, topic PssTopic, rw p2p.MsgReadWriter) error {
if self.peerPool[id] == nil {
self.peerPool[id] = make(map[PssTopic]p2p.MsgReadWriter, PssPeerTopicDefaultCapacity)
}
self.peerPool[id][topic] = rw
return nil
}
func (self *Pss) removePeerTopic(rw p2p.MsgReadWriter, topic PssTopic) {
prw, ok := rw.(*PssReadWriter)
if !ok {
return
}
delete(self.peerPool[prw.To], topic)
if len(self.peerPool[prw.To]) == 0 {
delete(self.peerPool, prw.To)
}
}
func (self *Pss) isActive(id pot.Address, topic PssTopic) bool {
return self.peerPool[id][topic] != nil
}
// Convenience object that:
//
// - allows passing of the unwrapped PssMsg payload to the p2p level message handlers
// - interprets outgoing p2p.Msg from the p2p level to pass in to *Pss.Send()
//
// Implements p2p.MsgReadWriter
type PssReadWriter struct {
*Pss
To pot.Address
LastActive time.Time
rw chan p2p.Msg
spec *protocols.Spec
topic *PssTopic
}
// Implements p2p.MsgReader
func (prw PssReadWriter) ReadMsg() (p2p.Msg, error) {
msg := <-prw.rw
log.Trace(fmt.Sprintf("pssrw readmsg: %v", msg))
return msg, nil
}
// Implements p2p.MsgWriter
func (prw PssReadWriter) WriteMsg(msg p2p.Msg) error {
log.Trace(fmt.Sprintf("pssrw writemsg: %v", msg))
ifc, found := prw.spec.NewMsg(msg.Code)
if !found {
return fmt.Errorf("Writemsg couldn't find matching interface for code %d", msg.Code)
}
msg.Decode(ifc)
pmsg, err := newProtocolMsg(msg.Code, ifc)
if err != nil {
return err
}
return prw.Pss.Send(prw.To.Bytes(), *prw.topic, pmsg)
}
// Injects a p2p.Msg into the MsgReadWriter, so that it appears on the associated p2p.MsgReader
func (prw PssReadWriter) injectMsg(msg p2p.Msg) error {
log.Trace(fmt.Sprintf("pssrw injectmsg: %v", msg))
prw.rw <- msg
return nil
}
// Convenience object for passing messages in and out of the p2p layer
type PssProtocol struct {
*Pss
proto *p2p.Protocol
topic *PssTopic
spec *protocols.Spec
}
// Constructor
func NewPssProtocol(pss *Pss, topic *PssTopic, spec *protocols.Spec, targetprotocol *p2p.Protocol) *PssProtocol {
pp := &PssProtocol{
Pss: pss,
proto: targetprotocol,
topic: topic,
spec: spec,
}
return pp
}
func (self *PssProtocol) handle(msg []byte, p *p2p.Peer, senderAddr []byte) error {
hashoaddr := pot.NewHashAddressFromBytes(senderAddr).Address
if !self.isActive(hashoaddr, *self.topic) {
rw := &PssReadWriter{
Pss: self.Pss,
To: hashoaddr,
rw: make(chan p2p.Msg),
spec: self.spec,
topic: self.topic,
}
self.Pss.AddPeer(p, hashoaddr, self.proto.Run, *self.topic, rw)
}
pmsg, err := ToP2pMsg(msg)
if err != nil {
return fmt.Errorf("could not decode pssmsg")
}
vrw := self.Pss.peerPool[hashoaddr][*self.topic].(*PssReadWriter)
vrw.injectMsg(pmsg)
return nil
}
func (self *Pss) isSelfRecipient(msg *PssMsg) bool {
return bytes.Equal(msg.To, self.Overlay.BaseAddr())
}
func newProtocolMsg(code uint64, msg interface{}) ([]byte, error) {
rlpdata, err := rlp.EncodeToBytes(msg)
if err != nil {
return nil, err
}
// previous attempts corrupted nested structs in the payload iself upon deserializing
// therefore we use two separate []byte fields instead of peerAddr
// TODO verify that nested structs cannot be used in rlp
smsg := &PssProtocolMsg{
Code: code,
Size: uint32(len(rlpdata)),
Payload: rlpdata,
}
return rlp.EncodeToBytes(smsg)
}
// constructs a new PssTopic from a given name and version.
//
// Analogous to the name and version members of p2p.Protocol
func NewTopic(s string, v int) (topic PssTopic) {
h := sha3.NewKeccak256()
h.Write([]byte(s))
buf := make([]byte, TopicLength / 8)
binary.PutUvarint(buf, uint64(v))
h.Write(buf)
copy(topic[:], h.Sum(buf)[:])
return topic
}
func ToP2pMsg(msg []byte) (p2p.Msg, error) {
payload := &PssProtocolMsg{}
if err := rlp.DecodeBytes(msg, payload); err != nil {
return p2p.Msg{}, fmt.Errorf("pss protocol handler unable to decode payload as p2p message: %v", err)
}
return p2p.Msg{
Code: payload.Code,
Size: uint32(len(payload.Payload)),
ReceivedAt: time.Now(),
Payload: bytes.NewBuffer(payload.Payload),
}, nil
}