mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
529 lines
16 KiB
Go
529 lines
16 KiB
Go
package network
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"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/protocols"
|
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
|
"github.com/ethereum/go-ethereum/pot"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
|
)
|
|
|
|
const (
|
|
DefaultTTL = 6000
|
|
TopicLength = 32
|
|
TopicResolverLength = 8
|
|
PssPeerCapacity = 256
|
|
PssPeerTopicDefaultCapacity = 8
|
|
digestLength = 64
|
|
digestCapacity = 256
|
|
defaultDigestCacheTTL = time.Second
|
|
pingTopicName = "pss"
|
|
pingTopicVersion = 1
|
|
)
|
|
|
|
var (
|
|
errorNoForwarder = errors.New("no available forwarders in routing table")
|
|
errorForwardToSelf = errors.New("forward to self")
|
|
errorBlockByCache = errors.New("message found in blocking cache")
|
|
)
|
|
|
|
// 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.
|
|
//
|
|
// Warning: do not access the To-member directly. Use *PssMsg.GetRecipient() and *PssMsg.SetRecipient() instead.
|
|
type PssMsg struct {
|
|
// (we need the To-member exported for type inference)
|
|
To []byte
|
|
Payload pssEnvelope
|
|
}
|
|
|
|
// Retrieve the remote peer receipient address of the message
|
|
func (self *PssMsg) GetRecipient() []byte {
|
|
return self.To
|
|
}
|
|
|
|
// Set the remote peer recipient address of the message
|
|
func (self *PssMsg) SetRecipient(to []byte) {
|
|
self.To = to
|
|
}
|
|
|
|
// String representation of PssMsg
|
|
func (self *PssMsg) String() string {
|
|
return fmt.Sprintf("PssMsg: Recipient: %x", common.ByteLabel(self.GetRecipient()))
|
|
}
|
|
|
|
// Pre-Whisper placeholder
|
|
type pssEnvelope struct {
|
|
Topic PssTopic
|
|
TTL uint16
|
|
Payload []byte
|
|
SenderOAddr []byte
|
|
// SenderUAddr []byte
|
|
}
|
|
|
|
// Pre-Whisper placeholder
|
|
type pssPayload struct {
|
|
Code uint64
|
|
Size uint32
|
|
Data []byte
|
|
ReceivedAt time.Time
|
|
}
|
|
|
|
// Pre-Whisper placeholder
|
|
type pssCacheEntry struct {
|
|
expiresAt time.Time
|
|
receivedFrom []byte
|
|
}
|
|
|
|
// 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
|
|
|
|
// Pre-Whisper placeholder
|
|
type pssDigest uint32
|
|
|
|
// 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 structure is used by normal incoming message handlers on the nodes to determine which action to take, forward or process.
|
|
// Thus it is up to the implementer to write a handler, and link the PssMsg to this appropriate handler.
|
|
//
|
|
// 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 {
|
|
Overlay // we can get the overlayaddress from this
|
|
//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
|
|
baseAddr []byte
|
|
lock sync.Mutex
|
|
}
|
|
|
|
func (self *Pss) hashMsg(msg *PssMsg) pssDigest {
|
|
hasher := self.hasher("SHA3")()
|
|
hasher.Reset()
|
|
hasher.Write(msg.GetRecipient())
|
|
// hasher.Write(msg.Payload.SenderUAddr)
|
|
hasher.Write(msg.Payload.SenderOAddr)
|
|
hasher.Write(msg.Payload.Topic[:])
|
|
hasher.Write(msg.Payload.Payload)
|
|
b := hasher.Sum([]byte{})
|
|
return pssDigest(binary.BigEndian.Uint32(b))
|
|
}
|
|
|
|
// Creates a new Pss instance. A node should only need one of these
|
|
//
|
|
// TODO error check overlay integrity
|
|
func NewPss(k Overlay, params *PssParams) *Pss {
|
|
baseAddr := k.BaseAddr()
|
|
return &Pss{
|
|
Overlay: k,
|
|
//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,
|
|
baseAddr: baseAddr,
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
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
|
|
}
|
|
|
|
// Takes the generated PssTopic of a protocol, and links a handler function to it
|
|
// This allows the implementer to retrieve the right handler function (invoke the right protocol) for an incoming message by inspecting the topic on it.
|
|
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] = func(msg []byte, p *p2p.Peer, from []byte) error {
|
|
self.alertSubscribers(&topic, msg)
|
|
return handler(msg, p, from)
|
|
}
|
|
self.registerFeed(topic)
|
|
return nil
|
|
}
|
|
|
|
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()
|
|
return self.handlers[topic]
|
|
}
|
|
|
|
// 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))
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
// Removes a pss peer from the pss peerpool
|
|
func (self *Pss) RemovePeer(id pot.Address) {
|
|
self.lock.Lock()
|
|
defer self.lock.Unlock()
|
|
self.peerPool[id] = nil
|
|
return
|
|
}
|
|
|
|
func (self *Pss) addPeerTopic(id pot.Address, topic PssTopic, rw p2p.MsgReadWriter) error {
|
|
if self.peerPool[id][topic] == nil {
|
|
self.peerPool[id] = make(map[PssTopic]p2p.MsgReadWriter, PssPeerTopicDefaultCapacity)
|
|
}
|
|
self.peerPool[id][topic] = rw
|
|
return nil
|
|
}
|
|
|
|
func (self *Pss) removePeerTopic(id pot.Address, topic PssTopic) {
|
|
self.peerPool[id][topic] = nil
|
|
return
|
|
}
|
|
|
|
func (self *Pss) isActive(id pot.Address, topic PssTopic) bool {
|
|
if self.peerPool[id][topic] == nil {
|
|
return false
|
|
}
|
|
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
|
|
func (self *Pss) Send(to []byte, topic PssTopic, msg []byte) error {
|
|
|
|
pssenv := pssEnvelope{
|
|
SenderOAddr: self.baseAddr,
|
|
// SenderUAddr: self.baseAddr.Under(),
|
|
Topic: topic,
|
|
TTL: DefaultTTL,
|
|
Payload: msg,
|
|
}
|
|
|
|
pssmsg := &PssMsg{
|
|
Payload: pssenv,
|
|
}
|
|
pssmsg.SetRecipient(to)
|
|
|
|
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 := self.hashMsg(msg)
|
|
|
|
if self.checkFwdCache(nil, digest) {
|
|
log.Trace(fmt.Sprintf("pss relay block-cache match: FROM %x TO %x", common.ByteLabel(self.baseAddr), common.ByteLabel(msg.GetRecipient())))
|
|
//return errorBlockByCache
|
|
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.GetRecipient(), 256, func(p OverlayConn, po int, isproxbin bool) bool {
|
|
if self.checkFwdCache(p.Address(), digest) {
|
|
log.Warn(fmt.Sprintf("BOUNCE DEFER PSS-relay FROM %x TO %x THRU %x:", common.ByteLabel(self.baseAddr), common.ByteLabel(msg.GetRecipient()), common.ByteLabel(p.Address())))
|
|
return true
|
|
}
|
|
log.Warn(fmt.Sprintf("Attempting PSS-relay FROM %x TO %x THRU %x", common.ByteLabel(self.baseAddr), common.ByteLabel(msg.GetRecipient()), common.ByteLabel(p.Address())))
|
|
err := p.(Peer).Send(msg)
|
|
if err != nil {
|
|
log.Warn(fmt.Sprintf("FAILED PSS-relay FROM %x TO %x THRU %x: %v", common.ByteLabel(self.baseAddr), common.ByteLabel(msg.GetRecipient()), common.ByteLabel(p.Address()), err))
|
|
return true
|
|
}
|
|
sent++
|
|
if bytes.Equal(msg.GetRecipient(), p.Address()) || !isproxbin {
|
|
return false
|
|
}
|
|
log.Trace(fmt.Sprintf("%x is in proxbin, so we continue sending", common.ByteLabel(p.Address())))
|
|
return true
|
|
})
|
|
if sent == 0 {
|
|
return fmt.Errorf("PSS Was not able to send to any peers")
|
|
} else {
|
|
self.addFwdCacheExpire(digest)
|
|
}
|
|
|
|
return 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
|
|
RecipientOAddr pot.Address
|
|
LastActive time.Time
|
|
rw chan p2p.Msg
|
|
ct *protocols.CodeMap
|
|
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.ct.GetInterface(msg.Code)
|
|
if !found {
|
|
return fmt.Errorf("Writemsg couldn't find matching interface for code %d", msg.Code)
|
|
}
|
|
msg.Decode(ifc)
|
|
|
|
to := prw.RecipientOAddr.Bytes()
|
|
|
|
pmsg, _ := makeMsg(msg.Code, ifc)
|
|
|
|
return prw.Pss.Send(to, *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
|
|
virtualProtocol *p2p.Protocol
|
|
topic *PssTopic
|
|
ct *protocols.CodeMap
|
|
}
|
|
|
|
// Constructor
|
|
func NewPssProtocol(pss *Pss, topic *PssTopic, ct *protocols.CodeMap, targetprotocol *p2p.Protocol) *PssProtocol {
|
|
pp := &PssProtocol{
|
|
Pss: pss,
|
|
virtualProtocol: targetprotocol,
|
|
topic: topic,
|
|
ct: ct,
|
|
}
|
|
return pp
|
|
}
|
|
|
|
// Retrieves a convenience method for passing an incoming message into the p2p layer
|
|
//
|
|
// If the implementer wishes to use the p2p.Protocol (or p2p/protocols) message handling, this handler can be directly registered as a handler for the PssMsg structure
|
|
func (self *PssProtocol) GetHandler() func([]byte, *p2p.Peer, []byte) error {
|
|
return self.handle
|
|
}
|
|
|
|
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,
|
|
RecipientOAddr: hashoaddr,
|
|
rw: make(chan p2p.Msg),
|
|
ct: self.ct,
|
|
topic: self.topic,
|
|
}
|
|
self.Pss.AddPeer(p, hashoaddr, self.virtualProtocol.Run, *self.topic, rw)
|
|
}
|
|
|
|
payload := &pssPayload{}
|
|
rlp.DecodeBytes(msg, payload)
|
|
|
|
pmsg := p2p.Msg{
|
|
Code: payload.Code,
|
|
Size: uint32(len(payload.Data)),
|
|
ReceivedAt: time.Now(),
|
|
Payload: bytes.NewBuffer(payload.Data),
|
|
}
|
|
|
|
vrw := self.Pss.peerPool[hashoaddr][*self.topic].(*PssReadWriter)
|
|
vrw.injectMsg(pmsg)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (self *Pss) IsSelfRecipient(msg *PssMsg) bool {
|
|
if bytes.Equal(msg.GetRecipient(), self.baseAddr) {
|
|
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) {
|
|
|
|
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 := &pssPayload{
|
|
Code: code,
|
|
Size: uint32(len(rlpdata)),
|
|
Data: rlpdata,
|
|
}
|
|
|
|
rlpbundle, err := rlp.EncodeToBytes(smsg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rlpbundle, nil
|
|
}
|
|
|
|
// Compiles a new PssTopic from a given name and version.
|
|
//
|
|
// Analogous to the name and version members of p2p.Protocol
|
|
func MakeTopic(s string, v int) (PssTopic, error) {
|
|
t := [TopicLength]byte{}
|
|
if len(s)+4 <= TopicLength {
|
|
copy(t[4:len(s)+4], s)
|
|
} else {
|
|
return t, fmt.Errorf("topic '%t' too long", s)
|
|
}
|
|
binary.PutVarint(t[:4], int64(v))
|
|
return t, nil
|
|
}
|